feat(api): cancel accepted project-histories on loopback HTTP - #447
Conversation
POST /v1/project-histories/{idempotency_key}/cancel removes one metric-free
LineageWeave identity from AnalysisRunLiveService. Receipts stay
cancelled=true with temporal_association_only. Naruon is refused.
NaruonLiveService stays POST-only. ADR 0079.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Team Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| if !body.trim().is_empty() { | ||
| return Err(ApiError::InvalidWirePayload); |
There was a problem hiding this comment.
🟡 Whitespace bodies cancel histories
A whitespace-only body passes trim().is_empty() and cancels the history. The contract requires every nonempty body to fail closed.
| if !body.trim().is_empty() { | |
| return Err(ApiError::InvalidWirePayload); | |
| if !body.is_empty() { | |
| return Err(ApiError::InvalidWirePayload); | |
| } |
Was this helpful? React with 👍 or 👎 to provide feedback.
| let (request, projection) = self | ||
| .accepted_project_histories | ||
| .remove(&replay_key) | ||
| .ok_or(ApiError::InvalidWirePayload)?; | ||
| let cancelled = ProjectHistoryCancelled::from_stored(&request, &projection)?; | ||
| let response_body = cancelled.to_json()?; | ||
| Ok(json_response(200, "OK", response_body)) |
There was a problem hiding this comment.
🔴 Failed receipts still delete histories
A project key containing tepp.scientific_acceptance.v1 makes to_json() fail after removal. The caller receives an error, but the history is gone.
| let (request, projection) = self | |
| .accepted_project_histories | |
| .remove(&replay_key) | |
| .ok_or(ApiError::InvalidWirePayload)?; | |
| let cancelled = ProjectHistoryCancelled::from_stored(&request, &projection)?; | |
| let response_body = cancelled.to_json()?; | |
| Ok(json_response(200, "OK", response_body)) | |
| let (request, projection) = self | |
| .accepted_project_histories | |
| .get(&replay_key) | |
| .ok_or(ApiError::InvalidWirePayload)?; | |
| let cancelled = ProjectHistoryCancelled::from_stored(request, projection)?; | |
| let response_body = cancelled.to_json()?; | |
| self.accepted_project_histories.remove(&replay_key); | |
| Ok(json_response(200, "OK", response_body)) |
Was this helpful? React with 👍 or 👎 to provide feedback.
| pub struct ProjectHistoryCancelled { | ||
| /// Consumer-owned stable project key. | ||
| pub project_key: String, | ||
| /// Exact request idempotency key that minted the stored projection. | ||
| pub idempotency_key: String, | ||
| /// Knowledge cutoff applied to the stored projection. | ||
| pub knowledge_cutoff: String, | ||
| /// Fixed claim boundary: sequence is association, not causation. | ||
| pub inference_status: String, | ||
| /// Always `true` on a successful cancel receipt. | ||
| pub cancelled: bool, |
There was a problem hiding this comment.
🟡 Cancellation receipts lack version identity
ProjectHistoryCancelled omits a semantic contract version. Consumers cannot identify or safely evolve this externally consumable response shape.
Prompt for agents
Add an explicit semantic contract version to the project-history cancellation receipt in crates/tepp_api/src/project_history_cancel_http.rs. Define and export a payload-family version constant, populate it in ProjectHistoryCancelled::from_stored, validate it during serialization and parsing, and update tests and contract documentation. This is required by docs/API_CONTRACT.md section 3 for every externally consumable contract.
Was this helpful? React with 👍 or 👎 to provide feedback.
| if matches!( | ||
| project_history_cancel_path_id(path), | ||
| Ok(_) | Err(ApiError::LimitExceeded) | ||
| ) { | ||
| return self.cancel_project_history(path, &headers, body); |
| let tenant_workspace_id = header_value(headers, PROJECT_HISTORY_RETRIEVAL_TENANT_HEADER)?; | ||
| crate::project_history::validate_project_history_registry_identity(tenant_workspace_id)?; | ||
| let idempotency_key = project_history_cancel_path_id(path)?; | ||
| let replay_key = | ||
| consumer_tenant_idempotency_key(consumer, tenant_workspace_id, &idempotency_key); |
| let tenant_workspace_id = header_value(headers, PROJECT_HISTORY_RETRIEVAL_TENANT_HEADER)?; | ||
| crate::project_history::validate_project_history_registry_identity(tenant_workspace_id)?; | ||
| let idempotency_key = project_history_cancel_path_id(path)?; | ||
| let replay_key = | ||
| consumer_tenant_idempotency_key(consumer, tenant_workspace_id, &idempotency_key); | ||
| let (request, projection) = self | ||
| .accepted_project_histories | ||
| .remove(&replay_key) | ||
| .ok_or(ApiError::InvalidWirePayload)?; |
There was a problem hiding this comment.
|
Closing as unsafe destructive-operation lineage with replacement mapping to #174. Review proved forged tenant/consumer headers can cancel another stored history, whitespace is incorrectly admitted as an empty body, receipt failure can delete state, and the external receipt lacks semantic version identity. Preserve those RED findings and project-history cancellation tests for the future authenticated Analysis Run/project-history operations vehicle. |
Summary
GAP-003A unique operator-visible slice: loopback
POST /v1/project-histories/{idempotency_key}/cancelonAnalysisRunLiveService/tepp-loopbackremoves one accepted LineageWeave project-history identity.idempotency-key/ extra path segments / slash/NUL identities / credential headers fail closed.cancelled=trueandinference_status=temporal_association_only. Evidence text, findings, RMSE, bias, coverage, SE-gate, causal scores, andtepp.scientific_acceptance.v1never appear.feat/project-history-retrieval-get-gap-003a). Does not unstack onto main.Explicitly not
Another export cancel CLI (#446), export cancel HTTP (#445), export collection CLI (#444), export collection GET (#443), interpretation-run cancel CLI (#442), interpretation-run cancel HTTP (#440), project-history retrieval CLI (#431), project-history GET-by-id (#429), project-history collection CLI (#428), project-history collection GET (#424), project-history POST CLI (#420), analysis-run cancel (#361), Leiden, Driver p.16, GAP-010 Figma/export, persistence/Compose (#287).
Does not add GET to
NaruonLiveService. Does not open naruon on this LineageWeave-owned adapter. Does not weaken fail-closed. Does not infer causality. No Buyer language. HTTP 200 is not an ADR 0014 claim.Test plan
cargo test -p tepp_api --lib project_history_cancelcargo test -p tepp_api --lib project_history_cancel_removescargo test -p tepp_api --test project_history_cancel_http_contractcargo clippy -p tepp_api --all-targets -- -D warningscargo doc -p tepp_api --no-depspython3 scripts/validate_documentation.pypython3 scripts/check_docstrings.py