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/analysis-run-retry-consumer-parity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- `tepp_api` adds `lineageweave_analysis_run_retry_exchange`, Naruon compatibility-listener retry, and a `tepp-loopback` TCP retry proof (ADR 0033). Metric-free child `202 Accepted` is unchanged from ADR 0032. Not GET status, not lifecycle POST, 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_analysis_run_retry_exchange`, Naruon compatibility-listener `POST /v1/analysis-runs/{run_id}/retry`, and a `tepp-loopback` TCP retry proof (ADR 0033). Failed and cancelled Naruon runs clone into a metric-free child `202 Accepted`. LineageWeave remains refused on `NaruonLiveService`. GET remains refused there. Not GET-by-id, not lifecycle POST, not an ADR 0014 claim.

- `tepp_api` serves `GET /v1/analysis-runs` on the shared loopback listener (ADR 0031). Operators enumerate accepted, running, cancelled, and terminal runs as metric-free collection rows. Collection bodies refuse RMSE/bias/coverage/SE-gate/scientific-acceptance/`terminal_result` keys. GET-by-id and running/terminal POST remain later GAP-003A slices; this is not an ADR 0014 claim.

- `tepp_api` serves `POST /v1/analysis-runs/{run_id}/cancel` on the shared loopback listener (ADR 0029). Accepted and running runs become metric-free `cancelled` status. Succeeded, failed, and unknown runs cannot be cancelled. Cancel bodies refuse RMSE/bias/coverage/SE-gate/scientific-acceptance keys. GET status and running/terminal POST remain later GAP-003A slices; this is not an ADR 0014 claim.
Expand Down
1 change: 1 addition & 0 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ TEPP's approved PRD v0.4 and implementation plan are the primary product baselin
| Analysis-run cancel HTTP doctoring | [`docs/research/analysis-run-cancel-http.md`](docs/research/analysis-run-cancel-http.md) |
| Analysis-run collection HTTP doctoring | [`docs/research/analysis-run-collection-http.md`](docs/research/analysis-run-collection-http.md) |
| Analysis-run retry HTTP doctoring | [`docs/research/analysis-run-retry-http.md`](docs/research/analysis-run-retry-http.md) |
| Analysis-run retry consumer-parity doctoring | [`docs/research/analysis-run-retry-consumer-parity.md`](docs/research/analysis-run-retry-consumer-parity.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
2 changes: 2 additions & 0 deletions crates/tepp_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,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 retry exchange without provider credentials.
pub use lineageweave_http::lineageweave_analysis_run_retry_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
81 changes: 70 additions & 11 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,
AnalysisRunRequest, AnalysisRunRetryRequest, ApiError, NaruonHttpExchange,
ProjectHistoryHttpExchange, ProjectHistoryRequest, TEMPORAL_CONTEXT_CONTRACT_VERSION,
TEMPORAL_CONTEXT_PATH, TemporalContextRequest, naruon_analysis_run_exchange,
naruon_analysis_run_retry_exchange,
};

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

/// Build a `LineageWeave` → TEPP analysis-run retry exchange without credentials.
///
/// The function reuses TEPP's existing origin, body, and header validation,
/// then replaces only the published modular-consumer identity. Retry remains a
/// metric-free clone of a failed or cancelled run, not a measurement result.
///
/// # Errors
///
/// Returns the same fail-closed errors as [`naruon_analysis_run_retry_exchange`].
pub fn lineageweave_analysis_run_retry_exchange(
origin: &str,
request: &AnalysisRunRetryRequest,
) -> Result<NaruonHttpExchange, ApiError> {
let mut exchange = naruon_analysis_run_retry_exchange(origin, request)?;
swap_consumer_header(&mut exchange)?;
Ok(exchange)
}

Expand Down Expand Up @@ -90,13 +104,25 @@ pub(crate) fn consumer_is_supported(consumer_code: &str) -> bool {
)
}

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(())
}

#[cfg(test)]
mod tests {
use super::{
LINEAGEWEAVE_CONSUMER_CODE, NARUON_CONSUMER_CODE, consumer_is_supported,
lineageweave_analysis_run_exchange,
lineageweave_analysis_run_exchange, lineageweave_analysis_run_retry_exchange,
};
use crate::{
ANALYSIS_RUN_CONTRACT_VERSION, AnalysisRunRequest, AnalysisRunRetryRequest, ApiError,
};
use crate::{ANALYSIS_RUN_CONTRACT_VERSION, AnalysisRunRequest, ApiError};

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

#[test]
fn lineageweave_retry_exchange_swaps_only_the_consumer_header() {
let request = AnalysisRunRetryRequest::new("tepp-run-1", "idem-retry-1").expect("retry");
let exchange =
lineageweave_analysis_run_retry_exchange("https://tepp.example.test", &request)
.expect("retry exchange");
assert_eq!(exchange.method, "POST");
assert_eq!(
exchange.target_url,
"https://tepp.example.test/v1/analysis-runs/tepp-run-1/retry"
);
assert!(
exchange
.headers
.contains(&("tepp-consumer".into(), LINEAGEWEAVE_CONSUMER_CODE.into()))
);
assert!(
exchange
.headers
.contains(&("idempotency-key".into(), "idem-retry-1".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_retry_exchange("http://tepp.example.test", &request),
Err(ApiError::InvalidWirePayload)
);
}
}
Loading
Loading