Skip to content
Closed
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-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.
1 change: 1 addition & 0 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ TEPP's approved PRD v0.4 and implementation plan are the primary product baselin
| 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) |
| 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
55 changes: 53 additions & 2 deletions crates/orchestrator_live/src/http.rs

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Bodyless GET requires content type

refuse_common_live_headers requires content-type: application/json for an empty GET. The documented exchange includes it, but generic HTTP clients often omit it.

(Refers to this code)

Devin Review

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

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