From 79584042369ac61a230088065d3c9c373aed383e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 31 Aug 2026 12:38:32 +0000 Subject: [PATCH] feat(api): poll analysis-run status from LineageWeave on loopback Add lineageweave_analysis_run_status_exchange and a tepp-loopback TCP GET proof stacked on the ADR 0027 status path. NaruonLiveService stays POST-only. --- .../analysis-run-status-consumer-parity.md | 1 + CHANGELOG.md | 2 + DOCUMENTATION.md | 2 + crates/tepp_api/src/lib.rs | 2 + crates/tepp_api/src/lineageweave_http.rs | 62 +++++++++++++++- .../tests/lineageweave_http_contract.rs | 42 +++++++++++ .../tests/loopback_binary_contract.rs | 41 +++++++++++ docs/API_CONTRACT.md | 2 +- docs/TRACEABILITY.md | 1 + ...028-analysis-run-status-consumer-parity.md | 73 +++++++++++++++++++ docs/adr/README.md | 3 + docs/connectors/naruon-artifact-consumer.md | 2 + .../analysis-run-status-consumer-parity.md | 41 +++++++++++ 13 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 CHANGELOG.d/analysis-run-status-consumer-parity.md create mode 100644 docs/adr/0028-analysis-run-status-consumer-parity.md create mode 100644 docs/research/analysis-run-status-consumer-parity.md diff --git a/CHANGELOG.d/analysis-run-status-consumer-parity.md b/CHANGELOG.d/analysis-run-status-consumer-parity.md new file mode 100644 index 000000000..8212cc78d --- /dev/null +++ b/CHANGELOG.d/analysis-run-status-consumer-parity.md @@ -0,0 +1 @@ +- `tepp_api` adds `lineageweave_analysis_run_status_exchange` and a `tepp-loopback` TCP GET-status proof (ADR 0028). Metric-free accepted/running GET is unchanged from ADR 0027. `NaruonLiveService` stays POST-only. Not lifecycle POST, not cancel, not an ADR 0014 claim. diff --git a/CHANGELOG.md b/CHANGELOG.md index 7993299cb..03cc6a759 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang ## [Unreleased] +- `tepp_api` adds `lineageweave_analysis_run_status_exchange` and a `tepp-loopback` TCP GET-status proof (ADR 0028). Metric-free accepted/running GET is unchanged from ADR 0027. `NaruonLiveService` stays POST-only. Not lifecycle POST, not cancel, not an ADR 0014 claim. + - `tepp_api` loopback `AnalysisRunLiveService` now serves `GET /v1/analysis-runs/{run_id}` so accepted/running statuses stay metric-free and only a succeeded status with profile `scientific_acceptance_v1` may return `tepp.scientific_acceptance.v1`. Receipt RMSE/bias/coverage/SE-gate keys, a GET body, failed-plus-artifact emission, an all-zero digest, and digest mismatch fail closed. This is the GAP-003A HTTP status slice for issue #166; it does not duplicate the `analysis_engine` library bind (#356) or the terminal-result DTO wire (#358); persistence remains GAP-003B. - `event_core` adds bounded Allen interval-consistency classification, atomic path-consistency closure, contradiction/resource refusals, and an explicit dependency-error fallback without claiming unrestricted global satisfiability. diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 6fa4b9683..0b4b3c7d7 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -11,6 +11,8 @@ TEPP's approved PRD v0.4 and implementation plan are the primary product baselin | Architecture | [`ARCHITECTURE.md`](ARCHITECTURE.md) | | Modular/API integration contract | [`docs/API_CONTRACT.md`](docs/API_CONTRACT.md) | | naruon modular consumer contract | [`docs/connectors/naruon-artifact-consumer.md`](docs/connectors/naruon-artifact-consumer.md) | +| Analysis-run status GET doctoring | [`docs/research/scientific-acceptance-http-status.md`](docs/research/scientific-acceptance-http-status.md) | +| Analysis-run status consumer-parity doctoring | [`docs/research/analysis-run-status-consumer-parity.md`](docs/research/analysis-run-status-consumer-parity.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) | | UML/runtime/scientific flows | [`docs/UML.md`](docs/UML.md) | diff --git a/crates/tepp_api/src/lib.rs b/crates/tepp_api/src/lib.rs index 0f03ebe59..69d37f9b3 100644 --- a/crates/tepp_api/src/lib.rs +++ b/crates/tepp_api/src/lib.rs @@ -156,6 +156,8 @@ pub use lineageweave_http::LINEAGEWEAVE_CONSUMER_CODE; pub use lineageweave_http::NARUON_CONSUMER_CODE; /// Build a `LineageWeave` analysis-run exchange without provider credentials. pub use lineageweave_http::lineageweave_analysis_run_exchange; +/// Build a `LineageWeave` analysis-run status GET without credentials. +pub use lineageweave_http::lineageweave_analysis_run_status_exchange; /// Build a `LineageWeave` project-history exchange without provider credentials. pub use lineageweave_http::lineageweave_project_history_exchange; /// Build a credential-free `LineageWeave` temporal-context exchange. diff --git a/crates/tepp_api/src/lineageweave_http.rs b/crates/tepp_api/src/lineageweave_http.rs index 6094760ed..d11de63af 100644 --- a/crates/tepp_api/src/lineageweave_http.rs +++ b/crates/tepp_api/src/lineageweave_http.rs @@ -5,7 +5,7 @@ use crate::project_history::build_project_history_exchange; use crate::{ AnalysisRunRequest, ApiError, NaruonHttpExchange, ProjectHistoryHttpExchange, ProjectHistoryRequest, TEMPORAL_CONTEXT_CONTRACT_VERSION, TEMPORAL_CONTEXT_PATH, - TemporalContextRequest, naruon_analysis_run_exchange, + TemporalContextRequest, naruon_analysis_run_exchange, naruon_analysis_run_status_exchange, }; /// Stable consumer identity used by the Naruon adapter. @@ -38,6 +38,30 @@ pub fn lineageweave_analysis_run_exchange( Ok(exchange) } +/// Build a `LineageWeave` → TEPP analysis-run status GET without credentials. +/// +/// The function reuses TEPP's existing origin, path, and header validation, +/// then replaces only the published modular-consumer identity. Status remains +/// a metric-free read except on a succeeded scientific-acceptance profile. +/// +/// # Errors +/// +/// Returns the same fail-closed errors as [`naruon_analysis_run_status_exchange`]. +pub fn lineageweave_analysis_run_status_exchange( + origin: &str, + run_id: &str, + idempotency_key: &str, +) -> Result { + let mut exchange = naruon_analysis_run_status_exchange(origin, run_id, idempotency_key)?; + let consumer_header = exchange + .headers + .iter_mut() + .find(|(name, _)| name.eq_ignore_ascii_case("tepp-consumer")) + .ok_or(ApiError::InvalidWirePayload)?; + LINEAGEWEAVE_CONSUMER_CODE.clone_into(&mut consumer_header.1); + Ok(exchange) +} + /// Build a credential-free `LineageWeave` temporal-context exchange. /// /// # Errors @@ -94,7 +118,7 @@ pub(crate) fn consumer_is_supported(consumer_code: &str) -> bool { mod tests { use super::{ LINEAGEWEAVE_CONSUMER_CODE, NARUON_CONSUMER_CODE, consumer_is_supported, - lineageweave_analysis_run_exchange, + lineageweave_analysis_run_exchange, lineageweave_analysis_run_status_exchange, }; use crate::{ANALYSIS_RUN_CONTRACT_VERSION, AnalysisRunRequest, ApiError}; @@ -132,4 +156,38 @@ mod tests { Err(ApiError::InvalidWirePayload) ); } + + #[test] + fn lineageweave_status_exchange_swaps_only_the_consumer_header() { + let exchange = lineageweave_analysis_run_status_exchange( + "https://tepp.example.test", + "tepp-run-1", + "idem-1", + ) + .expect("exchange"); + assert_eq!(exchange.method, "GET"); + assert_eq!( + exchange.target_url, + "https://tepp.example.test/v1/analysis-runs/tepp-run-1" + ); + assert!(exchange.body.is_empty()); + assert!( + exchange + .headers + .contains(&("tepp-consumer".into(), LINEAGEWEAVE_CONSUMER_CODE.into())) + ); + assert!( + !exchange + .headers + .contains(&("tepp-consumer".into(), NARUON_CONSUMER_CODE.into())) + ); + assert_eq!( + lineageweave_analysis_run_status_exchange( + "http://tepp.example.test", + "tepp-run-1", + "k" + ), + Err(ApiError::InvalidWirePayload) + ); + } } diff --git a/crates/tepp_api/tests/lineageweave_http_contract.rs b/crates/tepp_api/tests/lineageweave_http_contract.rs index 19b3e352e..bdaa20c09 100644 --- a/crates/tepp_api/tests/lineageweave_http_contract.rs +++ b/crates/tepp_api/tests/lineageweave_http_contract.rs @@ -10,6 +10,7 @@ use tepp_api::{ ANALYSIS_RUN_CONTRACT_VERSION, AnalysisRunAccepted, AnalysisRunLiveService, AnalysisRunRequest, ApiError, LINEAGEWEAVE_CONSUMER_CODE, NARUON_ANALYSIS_RUN_PATH, NARUON_CONSUMER_CODE, NARUON_LIVE_HEADER_BYTE_LIMIT, lineageweave_analysis_run_exchange, + lineageweave_analysis_run_status_exchange, }; fn sample_run() -> AnalysisRunRequest { @@ -68,6 +69,37 @@ fn lineageweave_exchange_uses_the_published_consumer_header_without_credentials( })); } +#[test] +fn lineageweave_status_exchange_gets_the_published_consumer_without_credentials() { + let exchange = lineageweave_analysis_run_status_exchange( + "https://tepp.example.test", + "tepp-run-9", + "shared-idempotency-key", + ) + .expect("lineageweave status"); + assert_eq!(exchange.method, "GET"); + assert_eq!( + exchange.target_url, + "https://tepp.example.test/v1/analysis-runs/tepp-run-9" + ); + assert!(exchange.body.is_empty()); + assert!( + exchange + .headers + .contains(&("tepp-consumer".into(), LINEAGEWEAVE_CONSUMER_CODE.into())) + ); + assert!(exchange.headers.iter().all(|(name, _)| { + !matches!( + name.to_ascii_lowercase().as_str(), + "authorization" | "proxy-authorization" | "cookie" | "x-api-key" + ) + })); + assert_eq!( + lineageweave_analysis_run_status_exchange("http://tepp.example.test", "tepp-run-9", "k"), + Err(ApiError::InvalidWirePayload) + ); +} + #[test] fn live_listener_accepts_lineageweave_and_isolates_consumer_idempotency() { let loopback = AnalysisRunLiveService::bind_loopback().expect("loopback bind"); @@ -108,6 +140,16 @@ fn live_listener_accepts_lineageweave_and_isolates_consumer_idempotency() { let conflict_response = service.handle_http_request(&http_request(LINEAGEWEAVE_CONSUMER_CODE, &conflict)); assert_eq!(conflict_response.status_code, 400); + + let status = service.handle_http_request(&format!( + "GET {NARUON_ANALYSIS_RUN_PATH}/{} HTTP/1.1\r\nHost: 127.0.0.1\r\ncontent-type: application/json\r\ntepp-consumer: {LINEAGEWEAVE_CONSUMER_CODE}\r\ntepp-contract-version: 1\r\nidempotency-key: {}\r\ncontent-length: 0\r\n\r\n", + lineageweave_accepted.run_id, + run.idempotency_key + )); + assert_eq!(status.status_code, 200); + assert!(status.body.contains("\"accepted\"")); + assert!(!status.body.contains("rmse")); + assert!(!status.body.contains("scientific_acceptance")); } #[test] diff --git a/crates/tepp_api/tests/loopback_binary_contract.rs b/crates/tepp_api/tests/loopback_binary_contract.rs index 20e475647..4ccb2ddcd 100644 --- a/crates/tepp_api/tests/loopback_binary_contract.rs +++ b/crates/tepp_api/tests/loopback_binary_contract.rs @@ -29,3 +29,44 @@ fn binary_serves_one_bounded_temporal_context_request() { assert!(response.contains("association_not_causal")); assert!(child.wait().expect("wait").success()); } + +#[test] +fn binary_reads_an_accepted_analysis_run_over_tcp() { + let mut child = Command::new(env!("CARGO_BIN_EXE_tepp-loopback")) + .args(["127.0.0.1:0", "2"]) + .stdout(Stdio::piped()) + .spawn() + .expect("spawn loopback service"); + let mut address = String::new(); + BufReader::new(child.stdout.take().expect("stdout")) + .read_line(&mut address) + .expect("bound address"); + let host = address.trim(); + let body = r#"{"contract_version":1,"idempotency_key":"loopback-status-idem","tenant_workspace_id":"loopback-status-tenant","snapshot_id":"loopback-status-snapshot","knowledge_cutoff":"2026-08-01T00:00:00Z","model_contract_version":"tepp-analysis-run-v1","output_profile":"calibrated_event_measurement"}"#; + let create = format!( + "POST /v1/analysis-runs HTTP/1.1\r\nHost: {host}\r\ncontent-type: application/json\r\ntepp-consumer: lineageweave\r\ntepp-contract-version: 1\r\nidempotency-key: loopback-status-idem\r\ncontent-length: {}\r\n\r\n{body}", + body.len() + ); + let mut stream = TcpStream::connect(host).expect("connect create"); + stream.write_all(create.as_bytes()).expect("create"); + let mut created = String::new(); + stream.read_to_string(&mut created).expect("created"); + assert!(created.starts_with("HTTP/1.1 202 Accepted")); + let json_start = created.find("{\"contract_version\"").expect("json"); + let accepted: serde_json::Value = + serde_json::from_str(&created[json_start..]).expect("accepted json"); + let run_id = accepted["run_id"].as_str().expect("run_id"); + assert!(!created[json_start..].contains("rmse")); + let get = format!( + "GET /v1/analysis-runs/{run_id} HTTP/1.1\r\nHost: {host}\r\ncontent-type: application/json\r\ntepp-consumer: lineageweave\r\ntepp-contract-version: 1\r\nidempotency-key: loopback-status-idem\r\ncontent-length: 0\r\n\r\n" + ); + let mut stream = TcpStream::connect(host).expect("connect status"); + stream.write_all(get.as_bytes()).expect("status"); + let mut status = String::new(); + stream.read_to_string(&mut status).expect("status body"); + assert!(status.starts_with("HTTP/1.1 200 OK")); + assert!(status.contains("\"run_state\":\"accepted\"")); + assert!(!status.contains("rmse")); + assert!(!status.contains("scientific_acceptance")); + assert!(child.wait().expect("wait").success()); +} diff --git a/docs/API_CONTRACT.md b/docs/API_CONTRACT.md index 70d557174..b1655688f 100644 --- a/docs/API_CONTRACT.md +++ b/docs/API_CONTRACT.md @@ -8,7 +8,7 @@ TEPP must work both as a standalone product and as a modular CWL component. Integrations with `naruon`, `contextual-orchestrator`, `.github`, or other repositories use explicit versioned API/artifact contracts. Cross-service direct table access is prohibited. -Current protected main exposes Rust library/domain contracts. The active stack adds a loopback HTTP/1.1 listener for naruon analysis-run, LineageWeave temporal-context, and export POSTs, including `POST /v1/project-histories` on the `AnalysisRunLiveService` contract boundary. `tepp-loopback` runs the shared consumer listener on `127.0.0.1:18081` by default; a caller may pass another loopback socket address and an optional maximum request count as its two arguments. The container is intended for a trusted same-host or shared-network-namespace sidecar, checks readiness through a synthetic bounded temporal-context request, and deliberately cannot bind a public or bridge address. It is not a production TLS/`$PORT` service. Endpoint examples below that are not covered by `NaruonLiveService` or `AnalysisRunLiveService` remain target interface shapes; export retrieval stays a target shape until an executable export route ships. +Current protected main exposes Rust library/domain contracts. The active stack adds a loopback HTTP/1.1 listener for naruon analysis-run, LineageWeave temporal-context, and export POSTs, including `POST /v1/project-histories` on the `AnalysisRunLiveService` contract boundary and `GET /v1/analysis-runs/{run_id}` for metric-free status. `lineageweave_analysis_run_status_exchange` is the published LineageWeave status builder. `NaruonLiveService` stays POST-only. `tepp-loopback` runs the shared consumer listener on `127.0.0.1:18081` by default; a caller may pass another loopback socket address and an optional maximum request count as its two arguments. The container is intended for a trusted same-host or shared-network-namespace sidecar, checks readiness through a synthetic bounded temporal-context request, and deliberately cannot bind a public or bridge address. It is not a production TLS/`$PORT` service. Endpoint examples below that are not covered by `NaruonLiveService` or `AnalysisRunLiveService` remain target interface shapes; export retrieval stays a target shape until an executable export route ships. ## 2. Contract families diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index 1a8294910..5564fca6b 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -54,6 +54,7 @@ The full APA 7th standards/literature register remains `docs/research/standards- | known-truth temporal/event simulation manifests | PRD; TRD; Test Strategy | `tepp_simulation` on protected main; recovery metrics in `validation_core` | implemented-main | | versioned service/API contracts and exports | PRD; API contract; ADR 0011/0013 | `tepp_api` analysis-run/export/JSON-LD/GraphML contracts on protected main (PR #21); request-bound terminal result active in PR #157; HTTP service remains accepted-target; the `orchestrator_live` loopback interpretation listener is on this PR | partial | | loopback analysis-run scientific-acceptance GET | ADR 0027; API contract; RFC 9110; FIPS 180-4 | `tepp_api` `GET /v1/analysis-runs/{run_id}` on `AnalysisRunLiveService` (this PR): accepted/running stay metric-free; `tepp.scientific_acceptance.v1` only on succeeded `scientific_acceptance_v1`; not implemented-main | active-PR | +| analysis-run status consumer parity | ADR 0028; ADR 0027; RFC 9110 | `lineageweave_analysis_run_status_exchange` and `tepp-loopback` TCP GET proof; `NaruonLiveService` stays POST-only | active-PR | | executable cutoff-safe analysis-run readiness | ADR 0021; temporal research; API terminal-result contract | stacked `analysis_engine` PR on #157: availability cutoff, snapshot binding, multiple-membership aggregation, digest-bound artifact, realistic end-to-end tests | active-PR | | delayed-reporting cutoff eligibility in truth corpora | ADR 0002; research | `tepp_simulation` eligible-at-cutoff filter on the active PR | active-PR | | versioned service/API contracts and exports | PRD; API contract; ADR 0011/0013 | `tepp_api` analysis-run/export/JSON-LD/GraphML contracts on protected main (PR #21); HTTP service remaining accepted-target | partial | diff --git a/docs/adr/0028-analysis-run-status-consumer-parity.md b/docs/adr/0028-analysis-run-status-consumer-parity.md new file mode 100644 index 000000000..53754d874 --- /dev/null +++ b/docs/adr/0028-analysis-run-status-consumer-parity.md @@ -0,0 +1,73 @@ +# ADR 0028 — Analysis-run status consumer parity + +**Decision status:** Accepted +**Implementation maturity:** active-PR +**Date:** 2026-08-31 +**Supersedes:** None; complements ADR 0027 and ADR 0018. Does not supersede ADR 0014. ADR 0026 remains on other GAP-003A slices. This ADR number is unique on the GET-status lineage; other live PRs may reuse 0028 on unrelated stacks. + +## Context + +ADR 0027 added `GET /v1/analysis-runs/{run_id}` on `AnalysisRunLiveService` and a Naruon status-exchange builder. `LineageWeave` had a create-exchange builder but no status-exchange, so a published consumer would have to mint a Naruon-labelled GET. The packaged `tepp-loopback` binary had no TCP proof that status GET works on the shared listener. + +Duplicating the GET listener, lifecycle POST, cancel HTTP, collection GET, retry, or engine-library slices would not close this consumer-parity gap. Opening GET on `NaruonLiveService` would violate that listener's POST-only Naruon compatibility contract. + +## Decision + +- `lineageweave_analysis_run_status_exchange` reuses the Naruon status builder and replaces only `tepp-consumer`. +- `AnalysisRunLiveService` remains the shared GET listener. LineageWeave consumers poll their own runs there; Naruon runs stay isolated. +- `NaruonLiveService` stays POST-only. It does not serve GET status. +- `tepp-loopback` proves create-then-GET over loopback TCP with the LineageWeave consumer. +- Accepted GET bodies stay metric-free. Scientific-acceptance attachment remains the ADR 0027 succeeded-profile gate. + +## Non-goals + +- A second GET listener, running/terminal POST, collection GET, retry, persistence, or production TLS. +- Opening `NaruonLiveService` to GET or to LineageWeave. +- An ADR 0014 scientific claim. + +## Alternatives considered + +1. **Leave status GET only on the Naruon builder** — rejected because a published LineageWeave consumer would have to mint a Naruon-labelled GET. +2. **Admit GET on `NaruonLiveService`** — rejected because that listener is Naruon-only POST (ADR 0011/0018). +3. **Mint a second status DTO** — rejected as a duplicate of ADR 0027. +4. **Consumer-parity status GET on the existing typed request** — accepted. + +## Consequences + +- Both published consumers can build a credential-free status GET exchange. +- Operators can observe LineageWeave status through `tepp-loopback` without a second HTTP stack. +- Naruon compatibility remains POST-only. + +## Failure and recovery + +Non-`https` origins, empty or oversized run identities, credential headers, and consumer/idempotency mismatch fail closed. The in-memory registry is not durable. HTTP `200` on an accepted GET is not an ADR 0014 claim. + +## Security, privacy, scientific-integrity, and governance impact + +- No credential headers cross the consumer boundary. +- GET remains loopback-only, size-bounded, and content-redacting. +- Metric-free accepted/running status is unchanged from ADR 0027. + +## Compatibility and migration + +The existing POST analysis-run, temporal-context, project-history, and GET status paths are unchanged. Production adapters may replace loopback while preserving the LineageWeave consumer header on status GET. + +## Verification + +Falsifiable evidence: + +- LineageWeave status exchange sets only the published consumer header and uses GET; +- `tepp-loopback` create-then-GET over TCP returns `200` accepted without RMSE/scientific-acceptance keys; +- LineageWeave GET of its own run succeeds; Naruon GET of that run fails closed; +- Clippy `-D warnings`, `tepp_api` tests, rustdoc, and exact-head review remain required. + +## Rollback and supersession + +Rollback removes the LineageWeave status builder and the TCP GET proof; the ADR 0027 GET listener remains valid. A superseding ADR is required to persist status, bind a public address, or treat HTTP success as an ADR 0014 claim. + +## Related authority + +- ADR 0027 owns the loopback GET listener and Naruon status builder. +- ADR 0018 owns consumer-scoped ingress. +- ADR 0011 owns standalone/modular HTTP boundaries. +- ADR 0014 owns scientific claim promotion. diff --git a/docs/adr/README.md b/docs/adr/README.md index 2246e011e..044c236c1 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -31,6 +31,7 @@ Read [`ADR_POLICY.md`](ADR_POLICY.md) first. **Decision status and implementatio | [0024](0024-lineage-pair-criterion-and-project-journey-posterior.md) | Independent Event Lineage pair criterion and posterior Project Journey | Proposed | active-PR | Strict artifacts preserve criterion/event-time draws, branches, ties, and CPU/GPU receipts without claiming the scientific estimator is complete. | | [0025](0025-macos-native-rust-mlx-metal-boundary.md) | macOS-native Rust-owned MLX Metal execution | Accepted | accepted-target | Compose authenticates to a native host service; Linux never claims Metal, and actual backend/parity receipts fail closed. | | [0027](0027-scientific-acceptance-http-status.md) | Scientific-acceptance loopback HTTP status path | Accepted | active-PR | GET `/v1/analysis-runs/{run_id}` stays metric-free on accepted/running; `tepp.scientific_acceptance.v1` only on succeeded `scientific_acceptance_v1`. | +| [0028](0028-analysis-run-status-consumer-parity.md) | LineageWeave status-exchange and loopback TCP GET proof | Accepted | active-PR | Complements ADR 0027; does not open `NaruonLiveService` to GET. | | [0023](0023-lineage-criterion-anchor-contract.md) | TEPP-owned Event Lineage criterion anchor | Accepted | active-PR | PR #237 publishes the strict accepted/rejected artifact and identities; estimator execution remains fail-closed future work. | | [0024](0024-independent-topic-importance-anchor.md) | Posterior topic-context producer contract | Accepted | contract-only active-PR | Strict DTO/schema only; the current estimator does not emit it. fast-mlsirm owns case-deletion influence. | | [0001](0001-rust-first-modular-msa.md) | Rust-first numerical core and CPU `f64` reference | Accepted | partial | ADR 0011 owns cross-service/MSA authority; 0001 retains numerical/backend authority. | @@ -141,6 +142,8 @@ Use the narrowest owning ADR when decisions overlap: - **accepted-run execution and terminal artifact production:** ADR 0022. - **independent lineage criterion and posterior Project Journey:** ADR 0023. - **macOS-native Rust-owned MLX Metal execution:** ADR 0024. +- **analysis-run scientific-acceptance GET:** ADR 0027. +- **analysis-run status consumer parity:** ADR 0028. ## Change and supersession rule diff --git a/docs/connectors/naruon-artifact-consumer.md b/docs/connectors/naruon-artifact-consumer.md index 9c6f6d185..399ce9726 100644 --- a/docs/connectors/naruon-artifact-consumer.md +++ b/docs/connectors/naruon-artifact-consumer.md @@ -26,6 +26,8 @@ TEPP remains the scientific authority for estimation, recovery metrics, temporal | GraphML relation export | `tepp_api` `GraphMlExport` | TEPP → naruon | | purpose-bound export auth | `tepp_api` `authorize_export` with `ModularServiceConsumer` | TEPP gate | | HTTP analysis-run create | `tepp_api` `naruon_analysis_run_exchange` → `POST /v1/analysis-runs` | naruon → TEPP | +| HTTP analysis-run status | `tepp_api` `naruon_analysis_run_status_exchange` → `GET /v1/analysis-runs/{run_id}` | naruon → TEPP | +| HTTP analysis-run status (LineageWeave) | `tepp_api` `lineageweave_analysis_run_status_exchange` → `GET /v1/analysis-runs/{run_id}` | lineageweave → TEPP | | HTTP export authorize | `tepp_api` `naruon_export_exchange` → `POST /v1/exports` | naruon → TEPP | | Live loopback POST | `tepp_api` `NaruonLiveService` → `POST /v1/analysis-runs` and `/v1/exports` | naruon → TEPP | diff --git a/docs/research/analysis-run-status-consumer-parity.md b/docs/research/analysis-run-status-consumer-parity.md new file mode 100644 index 000000000..eef85d272 --- /dev/null +++ b/docs/research/analysis-run-status-consumer-parity.md @@ -0,0 +1,41 @@ +# Analysis-run status consumer parity (doctoring) + +## Scope + +`LineageWeave` must be able to poll an accepted analysis run without inventing +a second DTO or minting a Naruon-labelled GET. HTTP method, path, and `Host` +semantics follow current HTTP semantics (Fielding, Nottingham, & Reschke, +2022). Fail-closed refusal of non-loopback binds, table-access hosts, +review/Copilot/GitHub credential headers, and scientific-authority promotion +is repository contract authority (ADR 0011; ADR 0018; ADR 0027; ADR 0028), +not an RFC inference rule. + +This slice does not serve GET on `NaruonLiveService`, running/terminal POST, +collection GET, retry, or persistence. + +## Authority + +### External standards (HTTP only) + +Fielding, R., Nottingham, M., & Reschke, J. (Eds.). (2022). *HTTP semantics* +(RFC 9110). IETF. https://doi.org/10.17487/RFC9110 + +### Internal contract evidence + +- `docs/adr/0027-scientific-acceptance-http-status.md` — shared-listener GET +- `docs/adr/0028-analysis-run-status-consumer-parity.md` — consumer parity +- `docs/adr/0018-consumer-scoped-analysis-run-ingress.md` — closed consumers +- `crates/tepp_api/tests/lineageweave_http_contract.rs` — LineageWeave builder +- `crates/tepp_api/tests/loopback_binary_contract.rs` — `tepp-loopback` TCP + +## Verification + +- LineageWeave status exchange sets only the published consumer header; +- LineageWeave GET of its own accepted run is metric-free `200`; +- cancelled JSON is out of scope; accepted GET has no RMSE/scientific-acceptance keys; +- `tepp-loopback` create-then-GET over TCP returns `200` accepted. + +## Non-claims + +This slice does not implement GET on `NaruonLiveService`, lifecycle POST, +persistence, production TLS, or an ADR 0014 scientific claim.