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
6 changes: 6 additions & 0 deletions CHANGELOG.d/analysis-run-lifecycle-consumer-parity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- `tepp_api` adds `lineageweave_analysis_run_running_exchange` /
`lineageweave_analysis_run_terminal_exchange`, Naruon compatibility-listener
lifecycle POST, and a `tepp-loopback` TCP running proof (ADR 0029). Metric-free
running and request-bound terminal semantics are unchanged from ADR 0028.
`NaruonLiveService` stays POST-only and Naruon-only. Not GET status, not
cancel, not an ADR 0014 claim.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang

## [Unreleased]

- `tepp_api` adds LineageWeave running/terminal exchange builders, Naruon compatibility-listener lifecycle POST, and a `tepp-loopback` TCP create-then-running proof so both published consumers can record metric-free running status without minting a foreign consumer header. `NaruonLiveService` stays POST-only and still refuses LineageWeave. Reverse transitions, mutating a terminal run, GET, receipt RMSE/bias/coverage/SE-gate keys, an unknown run, and consumer mismatch fail closed. This is the GAP-003A lifecycle consumer-parity slice for issue #166 stacked on #360; it does not duplicate the shared-listener lifecycle POST (#360), GET status (#359), status consumer-parity (#383), cancel (#361/#373), the terminal-result DTO (#358), or the `analysis_engine` library bind (#356); persistence remains GAP-003B.

- `tepp_api` loopback `AnalysisRunLiveService` now serves production `POST /v1/analysis-runs/{run_id}/running` and `POST /v1/analysis-runs/{run_id}/terminal` so accepted/running stay metric-free and only a succeeded status with profile `scientific_acceptance_v1` may return `tepp.scientific_acceptance.v1` after a lifecycle POST. Canonical artifact bytes travel as `scientific_acceptance_json`. Reverse transitions, mutating a terminal run, failed-plus-artifact emission, receipt RMSE/bias/coverage/SE-gate keys, an unknown run, and consumer mismatch fail closed. This is the GAP-003A HTTP lifecycle slice for issue #166; it does not duplicate the `analysis_engine` library bind (#356), the terminal-result DTO wire (#358), or the GET status slice (#359); persistence remains GAP-003B.

- `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.
Expand Down
2 changes: 2 additions & 0 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ 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 lifecycle consumer-parity doctoring | [`docs/research/analysis-run-lifecycle-consumer-parity.md`](docs/research/analysis-run-lifecycle-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) |
Expand Down Expand Up @@ -109,6 +110,7 @@ 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 lifecycle consumer-parity doctoring | [`docs/research/analysis-run-lifecycle-consumer-parity.md`](docs/research/analysis-run-lifecycle-consumer-parity.md) |
| contextual-orchestrator interpretation port | [`docs/connectors/contextual-orchestrator-interpretation-port.md`](docs/connectors/contextual-orchestrator-interpretation-port.md) |
| UML/runtime/scientific flows | [`docs/UML.md`](docs/UML.md) |
| Logical/physical ERD | [`docs/ERD.md`](docs/ERD.md) |
Expand Down
4 changes: 4 additions & 0 deletions crates/tepp_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ 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` running-status exchange without provider credentials.
pub use lineageweave_http::lineageweave_analysis_run_running_exchange;
/// Build a `LineageWeave` terminal-status exchange without provider credentials.
pub use lineageweave_http::lineageweave_analysis_run_terminal_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.
Expand Down
116 changes: 110 additions & 6 deletions crates/tepp_api/src/lineageweave_http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
use crate::naruon_http::compose_https_target;
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,
AnalysisRunLifecycleTransition, AnalysisRunRequest, ApiError, NaruonHttpExchange,
ProjectHistoryHttpExchange, ProjectHistoryRequest, TEMPORAL_CONTEXT_CONTRACT_VERSION,
TEMPORAL_CONTEXT_PATH, TemporalContextRequest, naruon_analysis_run_exchange,
naruon_analysis_run_running_exchange, naruon_analysis_run_terminal_exchange,
};

/// Stable consumer identity used by the Naruon adapter.
Expand All @@ -29,13 +30,54 @@ pub fn lineageweave_analysis_run_exchange(
request: &AnalysisRunRequest,
) -> Result<NaruonHttpExchange, ApiError> {
let mut exchange = naruon_analysis_run_exchange(origin, request)?;
swap_consumer_header(&mut exchange)?;
Ok(exchange)
}

/// Build a `LineageWeave` → TEPP running-status POST without credentials.
///
/// The function reuses TEPP's existing origin, body, and header validation,
/// then replaces only the published modular-consumer identity. Running stays
/// a metric-free lifecycle command, not a measurement result.
///
/// # Errors
///
/// Returns the same fail-closed errors as [`naruon_analysis_run_running_exchange`].
pub fn lineageweave_analysis_run_running_exchange(
origin: &str,
transition: &AnalysisRunLifecycleTransition,
) -> Result<NaruonHttpExchange, ApiError> {
let mut exchange = naruon_analysis_run_running_exchange(origin, transition)?;
swap_consumer_header(&mut exchange)?;
Ok(exchange)
}

/// Build a `LineageWeave` → TEPP terminal-status POST without credentials.
///
/// The function reuses TEPP's existing origin, body, and header validation,
/// then replaces only the published modular-consumer identity. Terminal
/// scientific-acceptance bytes remain request-bound and fail closed.
///
/// # Errors
///
/// Returns the same fail-closed errors as [`naruon_analysis_run_terminal_exchange`].
pub fn lineageweave_analysis_run_terminal_exchange(
origin: &str,
transition: &AnalysisRunLifecycleTransition,
) -> Result<NaruonHttpExchange, ApiError> {
let mut exchange = naruon_analysis_run_terminal_exchange(origin, transition)?;
swap_consumer_header(&mut exchange)?;
Ok(exchange)
}

fn swap_consumer_header(exchange: &mut NaruonHttpExchange) -> Result<(), ApiError> {
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)
Ok(())
}

/// Build a credential-free `LineageWeave` temporal-context exchange.
Expand Down Expand Up @@ -94,9 +136,12 @@ 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_running_exchange,
lineageweave_analysis_run_terminal_exchange,
};
use crate::{
ANALYSIS_RUN_CONTRACT_VERSION, AnalysisRunLifecycleTransition, AnalysisRunRequest, ApiError,
};
use crate::{ANALYSIS_RUN_CONTRACT_VERSION, AnalysisRunRequest, ApiError};

fn sample_run() -> AnalysisRunRequest {
AnalysisRunRequest {
Expand Down Expand Up @@ -132,4 +177,63 @@ mod tests {
Err(ApiError::InvalidWirePayload)
);
}

#[test]
fn lineageweave_lifecycle_exchanges_swap_only_the_consumer_header() {
let running =
AnalysisRunLifecycleTransition::running("tepp-run-1", "idem-1").expect("running");
let running_exchange =
lineageweave_analysis_run_running_exchange("https://tepp.example.test", &running)
.expect("running exchange");
assert_eq!(running_exchange.method, "POST");
assert_eq!(
running_exchange.target_url,
"https://tepp.example.test/v1/analysis-runs/tepp-run-1/running"
);
assert!(
running_exchange
.headers
.contains(&("tepp-consumer".into(), LINEAGEWEAVE_CONSUMER_CODE.into()))
);
assert!(
!running_exchange
.headers
.contains(&("tepp-consumer".into(), NARUON_CONSUMER_CODE.into()))
);
assert!(!running_exchange.body.contains("rmse"));
assert_eq!(
lineageweave_analysis_run_running_exchange("http://tepp.example.test", &running),
Err(ApiError::InvalidWirePayload)
);

let failed = crate::AnalysisRunTerminalResult::failed(
&sample_run(),
&crate::AnalysisRunAccepted::new("tepp-run-1", "accepted", "idem-1").expect("accepted"),
"2026-08-02T03:04:05Z",
"estimation_failed",
)
.expect("failed");
let terminal =
AnalysisRunLifecycleTransition::terminal("tepp-run-1", "idem-1", failed, None)
.expect("terminal");
let terminal_exchange =
lineageweave_analysis_run_terminal_exchange("https://tepp.example.test", &terminal)
.expect("terminal exchange");
assert_eq!(terminal_exchange.method, "POST");
assert!(
terminal_exchange
.target_url
.ends_with("/v1/analysis-runs/tepp-run-1/terminal")
);
assert!(
terminal_exchange
.headers
.contains(&("tepp-consumer".into(), LINEAGEWEAVE_CONSUMER_CODE.into()))
);
assert_eq!(
lineageweave_analysis_run_terminal_exchange("https://tepp.example.test", &running)
.expect_err("running on terminal"),
ApiError::InvalidWirePayload
);
}
}
Loading
Loading