feat(api): record scientific acceptance via loopback lifecycle POST - #360
Conversation
GAP-003A fourth slice for issue #166. POST /v1/analysis-runs stays a metric-free receipt. POST /v1/analysis-runs/{run_id}/running and POST /v1/analysis-runs/{run_id}/terminal are the production status-update path so accepted/running stay metric-free and only a succeeded status with profile scientific_acceptance_v1 may return tepp.scientific_acceptance.v1 on the subsequent GET. Canonical artifact bytes travel as scientific_acceptance_json. Reverse transitions, mutating a terminal run, failed-plus-artifact emission, receipt metric keys, an unknown run, and consumer mismatch fail closed. This does not duplicate the analysis_engine library bind, the terminal-result DTO wire, or the GET status slice.
|
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: Pro Plus 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 let Some(artifact) = self.scientific_acceptance_json.as_deref() { | ||
| require_nonempty(artifact)?; | ||
| } |
There was a problem hiding this comment.
🟡 Lifecycle builders emit rejected requests
For succeeded transitions, validate only requires nonempty artifact text. The exchange builders emit requests the live endpoint rejects for profile, schema, or digest mismatches.
Prompt for agents
Make AnalysisRunLifecycleTransition validation enforce the same succeeded-artifact rules as the live endpoint. Use the terminal result's output profile, schema, and result_sha256 to require or forbid scientific_acceptance_json and validate its JSON shape and exact byte digest. Keep one shared validator so constructors, deserialization, exchange builders, and AnalysisRunLiveService cannot diverge. Add tests proving every transition accepted by either public exchange builder is accepted by the corresponding live route, including missing, malformed, wrong-profile, wrong-schema, and digest-mismatched artifacts.
Was this helpful? React with 👍 or 👎 to provide feedback.
| let encoded_run_id = encode_path_segment(&transition.run_id); | ||
| let target_path = format!("{ANALYSIS_RUN_STATUS_PATH}/{encoded_run_id}/{suffix}"); |
There was a problem hiding this comment.
🟡 Encoded run identifiers always fail
encode_path_segment accepts slash-containing run IDs, but the live decoder rejects their decoded slash. Every resulting lifecycle request receives a 400 response.
Prompt for agents
Align lifecycle exchange construction with live route parsing. Either reject run IDs whose decoded form the server refuses, or define a safe decoding contract that accepts percent-encoded reserved bytes consistently. Apply the same rule to status, running, and terminal builders and add builder-to-live-service round-trip tests for reserved characters, especially slash.
Was this helpful? React with 👍 or 👎 to provide feedback.
| if stored.status == status | ||
| && stored.scientific_acceptance_json == transition.scientific_acceptance_json | ||
| { | ||
| let response_body = status_http_json( | ||
| &stored.status, | ||
| &stored.request, | ||
| stored.scientific_acceptance_json.as_deref(), | ||
| )?; | ||
| return Ok(json_response(200, "OK", response_body)); | ||
| } |
| match stored.status.run_state { | ||
| AnalysisRunStatusState::Accepted => {} | ||
| AnalysisRunStatusState::Running | ||
| if matches!( | ||
| transition.run_state, | ||
| AnalysisRunStatusState::Succeeded | AnalysisRunStatusState::Failed | ||
| ) => {} |
There was a problem hiding this comment.
| headers: &HashMap<String, String>, | ||
| body: &str, | ||
| ) -> Result<NaruonLiveResponse, ApiError> { | ||
| let transition = AnalysisRunLifecycleTransition::from_json(body)?; |
|
Hour-20 exact-head review request. Current head GAP-003A lifecycle POST draft stacked on #359. ADR 0028. Do not duplicate POST running/terminal. Do not self-approve. Do not --admin merge. Checks/reviews are not a reason to weaken fail-closed gates. |
|
Hour-21 exact-head review request. Current head @opencode-agent review |
Consolidation decision — closed after the terminal scientific-acceptance artifact was retired
This PR is not merged. Exact head
99464037d90f7c23ec9213d9b49807be4593e406contains useful generic Analysis Run lifecycle transitions, but its terminal POST body and subsequent GET contract explicitly store/servescientific_acceptance_json/tepp.scientific_acceptance.v1. The #356/#358 authority chain is now retired.Preserve and fold the generic lifecycle invariants into #166/#435: accepted→running→terminal monotonicity, reverse-transition refusal, terminal immutability, run/consumer isolation, exact artifact digest binding, metric-free nonterminal receipts and fail-closed unknown runs. Rebuild the terminal artifact as versioned Validation Evidence produced by the owning Rust validation path. Scientific Claim Promotion must remain a separate aggregate/policy rather than a lifecycle state side effect.
Branch, tests, reviews and doctoring remain available as fold lineage.