Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.d/interpretation-run-cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- `orchestrator_live` `tepp-interpretation-runs create` mints a typed contextual-orchestrator `POST /v1/interpretation-runs` onto spawned `tepp-orchestrator-loopback` TCP (ADR 0064). Metric-free hypothetical JSON only. `tepp.scientific_acceptance.v1` never appears. Does not infer causality. Naruon and LineageWeave are refused. Not analysis-run CLI, not export CLI, not persistence.
1 change: 1 addition & 0 deletions CHANGELOG.d/interpretation-run-collection-cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- `orchestrator_live` publishes `tepp-interpretation-runs list` minting typed contextual-orchestrator `GET /v1/interpretation-runs` onto spawned `tepp-orchestrator-loopback` TCP (ADR 0070). Metric-free identities only (`claim_status=hypothetical`, `scientific_authority=false`). Empty stdin admitted. `tepp.scientific_acceptance.v1` never appears. Does not infer causality. Naruon and LineageWeave are refused. Not interpretation-run create CLI, not collection GET, not project-history collection CLI, not persistence.
1 change: 1 addition & 0 deletions CHANGELOG.d/interpretation-run-collection-http.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- `orchestrator_live` loopback `GET /v1/interpretation-runs` enumerates accepted hypothetical interpretation runs on `tepp-orchestrator-loopback` (ADR 0069). Metric-free identities only (`claim_status=hypothetical`, `scientific_authority=false`). `tepp.scientific_acceptance.v1` never appears. Does not infer causality. Naruon and LineageWeave are refused. Not interpretation-run CLI, not project-history collection GET, not persistence.
3 changes: 3 additions & 0 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ TEPP's approved PRD v0.4 and implementation plan are the primary product baselin
| naruon modular consumer contract | [`docs/connectors/naruon-artifact-consumer.md`](docs/connectors/naruon-artifact-consumer.md) |
| contextual-orchestrator interpretation port | [`docs/connectors/contextual-orchestrator-interpretation-port.md`](docs/connectors/contextual-orchestrator-interpretation-port.md) |
| Orchestrator live HTTP doctoring | [`docs/research/orchestrator-live-http.md`](docs/research/orchestrator-live-http.md) |
| Interpretation-run CLI doctoring | [`docs/research/interpretation-run-cli.md`](docs/research/interpretation-run-cli.md) |
| Interpretation-run collection GET doctoring | [`docs/research/interpretation-run-collection-http.md`](docs/research/interpretation-run-collection-http.md) |
| Interpretation-run collection CLI doctoring | [`docs/research/interpretation-run-collection-cli.md`](docs/research/interpretation-run-collection-cli.md) |
| UML/runtime/scientific flows | [`docs/UML.md`](docs/UML.md) |
| Logical/physical ERD | [`docs/ERD.md`](docs/ERD.md) |
| Security policy | [`SECURITY.md`](SECURITY.md) |
Expand Down
12 changes: 12 additions & 0 deletions crates/orchestrator_live/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,17 @@ publish = false
serde = { workspace = true }
serde_json = { workspace = true }

[[bin]]
name = "tepp-orchestrator-loopback"
path = "src/bin/tepp_orchestrator_loopback.rs"
test = false
bench = false

[[bin]]
name = "tepp-interpretation-runs"
path = "src/bin/tepp_interpretation_runs.rs"
test = false
bench = false

[lints]
workspace = true
51 changes: 51 additions & 0 deletions crates/orchestrator_live/src/bin/tepp_interpretation_runs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//! Operator CLI for loopback contextual-orchestrator interpretation-run POST and collection GET.

use std::io::{self, IsTerminal};
use std::process::ExitCode;

use orchestrator_live::{
execute_interpretation_run_cli, execute_interpretation_run_collection_cli,
read_interpretation_run_cli_stdin, read_interpretation_run_collection_cli_stdin,
render_interpretation_run_cli_stdout, render_interpretation_run_collection_cli_stdout,
InterpretationRunCliInvocation, InterpretationRunCollectionCliInvocation,
OrchestratorLiveError,
};

fn main() -> ExitCode {
match run() {
Ok(()) => ExitCode::SUCCESS,
Err(_) => ExitCode::FAILURE,
}
}

fn run() -> Result<(), OrchestratorLiveError> {
let args: Vec<String> = std::env::args().skip(1).collect();
match args.first().map(String::as_str) {
Some("create") => run_create(&args),
Some("list") => run_list(&args),
_ => Err(OrchestratorLiveError::InvalidWirePayload),
}
}

fn run_create(args: &[String]) -> Result<(), OrchestratorLiveError> {
let body = read_interpretation_run_cli_stdin(io::stdin().is_terminal(), io::stdin())?;
let invocation = InterpretationRunCliInvocation::from_args(args, body)?;
let response = execute_interpretation_run_cli(&invocation)?;
let stdout = render_interpretation_run_cli_stdout(&invocation, &response)?;
println!("{stdout}");
if (200..300).contains(&response.status_code) {
Ok(())
} else {
Err(OrchestratorLiveError::InvalidWirePayload)
}
}

fn run_list(args: &[String]) -> Result<(), OrchestratorLiveError> {
let body =
read_interpretation_run_collection_cli_stdin(io::stdin().is_terminal(), io::stdin())?;
let invocation = InterpretationRunCollectionCliInvocation::from_args(args, body)?;
let response = execute_interpretation_run_collection_cli(&invocation)?;
let stdout = render_interpretation_run_collection_cli_stdout(&invocation, &response)?;
println!("{stdout}");
Ok(())
Comment on lines +30 to +50

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 CLI failures expose inconsistent output

Create prints server error bodies before failing, while list suppresses every non-200 body. Operators receive different diagnostics from one consolidated command.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

}
24 changes: 24 additions & 0 deletions crates/orchestrator_live/src/bin/tepp_orchestrator_loopback.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//! Runnable loopback ingress for trusted same-host contextual-orchestrator.

use std::net::SocketAddr;

use orchestrator_live::OrchestratorLiveService;

const DEFAULT_BIND_ADDR: &str = "127.0.0.1:18082";

fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut arguments = std::env::args().skip(1);
let bind_addr = arguments
.next()
.unwrap_or(DEFAULT_BIND_ADDR.to_owned())
.parse::<SocketAddr>()?;
let request_limit = arguments
.next()
.map(|value| value.parse::<usize>())
.transpose()?
.unwrap_or(usize::MAX);
let mut service = OrchestratorLiveService::bind(bind_addr)?;
println!("{}", service.local_addr()?);
(0..request_limit).for_each(|_| drop(service.serve_one()));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Server failures exit successfully

drop(service.serve_one()) discards accept and response-write errors. A bounded server counts failed attempts and exits successfully without serving the requested traffic.

Suggested change
(0..request_limit).for_each(|_| drop(service.serve_one()));
for _ in 0..request_limit {
service.serve_one()?;
}
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Ok(())
}
55 changes: 53 additions & 2 deletions crates/orchestrator_live/src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,25 @@ pub(crate) fn split_header_line(line: &str) -> Result<(&str, &str), Orchestrator

pub(crate) fn refuse_live_headers(
headers: &HashMap<String, String>,
) -> Result<(), OrchestratorLiveError> {
refuse_common_live_headers(headers)?;
let _idempotency_key = header_value(headers, "idempotency-key")?;
Ok(())
}

/// Collection GET admits empty bodies and refuses `idempotency-key`.
pub(crate) fn refuse_collection_get_headers(
headers: &HashMap<String, String>,
) -> Result<(), OrchestratorLiveError> {
refuse_common_live_headers(headers)?;
if headers.contains_key("idempotency-key") {
return Err(OrchestratorLiveError::InvalidWirePayload);
}
Ok(())
}

fn refuse_common_live_headers(
headers: &HashMap<String, String>,
) -> Result<(), OrchestratorLiveError> {
for (name, value) in headers {
if header_is_credential(name) || header_is_credential(value) {
Expand All @@ -213,7 +232,6 @@ pub(crate) fn refuse_live_headers(
if header_value(headers, "tepp-contract-version")? != "1" {
return Err(OrchestratorLiveError::InvalidWirePayload);
}
let _idempotency_key = header_value(headers, "idempotency-key")?;
Ok(())
}

Expand Down Expand Up @@ -262,7 +280,8 @@ pub(crate) fn status_for(error: OrchestratorLiveError) -> (u16, &'static str) {
mod tests {
use super::{
declared_content_length, header_is_credential, map_io_error, parse_headers,
parse_request_line, refuse_live_headers, split_header_line, split_request, status_for,
parse_request_line, refuse_collection_get_headers, refuse_live_headers, split_header_line,
split_request, status_for,
};
use crate::error::OrchestratorLiveError;
use std::collections::HashMap;
Expand Down Expand Up @@ -423,4 +442,36 @@ mod tests {
assert!(header_is_credential("x-nvidia_nim_api_key"));
assert!(!header_is_credential("x-safe-header"));
}

#[test]
fn collection_get_headers_refuse_idempotency_key_and_foreign_consumers() {
let mut headers = HashMap::new();
headers.insert("host".into(), "127.0.0.1".into());
headers.insert("content-type".into(), "application/json".into());
headers.insert("tepp-consumer".into(), "contextual-orchestrator".into());
headers.insert("tepp-contract-version".into(), "1".into());
headers.insert("idempotency-key".into(), "idem".into());
assert_eq!(
refuse_collection_get_headers(&headers),
Err(OrchestratorLiveError::InvalidWirePayload)
);
headers.remove("idempotency-key");
refuse_collection_get_headers(&headers).expect("collection headers");
headers.insert("tepp-consumer".into(), "naruon".into());
assert_eq!(
refuse_collection_get_headers(&headers),
Err(OrchestratorLiveError::InvalidWirePayload)
);
headers.insert("tepp-consumer".into(), "lineageweave".into());
assert_eq!(
refuse_collection_get_headers(&headers),
Err(OrchestratorLiveError::InvalidWirePayload)
);
headers.insert("tepp-consumer".into(), "contextual-orchestrator".into());
headers.insert("authorization".into(), "Bearer x".into());
assert_eq!(
refuse_collection_get_headers(&headers),
Err(OrchestratorLiveError::AuthorizationDenied)
);
}
}
Loading
Loading