From ebfb564e59c65fd2f331d934c4d715af46edf5b3 Mon Sep 17 00:00:00 2001 From: piaro <7055807+piaro@users.noreply.github.com> Date: Fri, 28 Aug 2026 12:06:50 +0900 Subject: [PATCH] Improve contract health performance and compact results --- README.md | 9 + docs/concepts.ja.md | 4 + docs/implementation.md | 4 + src/contract_health.rs | 264 ++++++++++++++---- src/explain.rs | 2 +- src/kernel.rs | 13 +- src/submission.rs | 155 +++++++++- testdata/golden/v1/application-lifecycle.json | 30 +- testdata/golden/v1/explain-report.json | 48 ++-- testdata/golden/v1/filesystem-project.json | 4 +- testdata/golden/v1/result-submission.json | 14 +- 11 files changed, 425 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index ad69dae..60ff81d 100644 --- a/README.md +++ b/README.md @@ -283,6 +283,15 @@ While impact assessment is still pending, `next` and `explain` select that action before deriving repository-wide Contract health. Unrelated Result and Evidence history is not loaded for this first step. +When Contract health is required, ADF indexes Evidence and verification Results +once, then validates and hashes only records that can affect a Contract clause. +It does not repeatedly scan every Result for every clause. + +New Results store shared input and freshness references once at the Result +level. An outcome carries its own references only when they differ from those +shared values. Existing Results remain readable and are not rewritten, so their +identities and downstream freshness checks remain stable. + Each action also carries advisory execution guidance. Impact assessment normally recommends an economy model, while challenge recommends a high-accuracy model. The listed escalation conditions tell an orchestrator when diff --git a/docs/concepts.ja.md b/docs/concepts.ja.md index cb42835..20bb3e6 100644 --- a/docs/concepts.ja.md +++ b/docs/concepts.ja.md @@ -97,6 +97,10 @@ Contractまたは条項の`evidence_mode`で、検証に掛ける費用を選べ 影響評価が済んでいない間、`adf next`と`adf explain`は、リポジトリ全体のContract検証状態を計算する前に影響評価を次の作業として選びます。この最初の段階では、無関係なChangeのResultとEvidenceを読み込みません。 +Contract検証状態が必要な場合は、Evidenceと検証Resultを一度索引化し、Contract条項へ影響する記録だけを検証してハッシュを計算します。条項ごとに全Resultを繰り返し検索しません。 + +新しいResultでは、すべての判定結果に共通する入力参照と鮮度参照をResult全体へ一度だけ保存します。判定結果ごとの参照が共通値と異なる場合だけ、その判定結果にも保存します。既存Resultは引き続き読み込めます。識別子と後続Resultの鮮度判定を保つため、既存ファイルは自動で書き換えません。 + 各作業には、実行環境へ向けたモデルの推奨も含まれます。影響評価には通常、軽量なモデルを推奨します。ただし、影響なしと結論付ける場合、根拠が矛盾する場合、セキュリティ、プライバシー、決済、元に戻せないデータ変更の可能性がある場合は、精度の高いモデルへの切り替えを勧めます。ADF自体はモデルを選ばず、LLMも実行しません。 実行環境がすでに把握している処理時間、モデル名、入出力Token数、ツール呼び出し数、再試行回数は、`adf_submit`で任意に記録できます。外部Runnerは`adf_begin_execution`と`adf_complete_execution`を使い、Result提出後に確定したToken数や、失敗・中断した実行も追記できます。外部実行では、キャッシュ作成Token、キャッシュ読取Token、推論Token、実行環境が報告した米ドル費用も記録できます。この実行RecordはADFの状態、Result ID、鮮度、Evidence検証には影響しません。同じResultに提出時の計測値とRunnerの完了Recordがある場合は、Runnerの値だけを集計します。 diff --git a/docs/implementation.md b/docs/implementation.md index 17ce496..ec51012 100644 --- a/docs/implementation.md +++ b/docs/implementation.md @@ -122,6 +122,10 @@ Explainでは、確認前を`applicability-pending`、支持された後を`not- `contract-health`は、全ChangeのResult・Evidenceと現在のRepository観測から、Contract条項ごとの実装準拠状態を再生成します。 +計算時には、条項ごとのEvidenceと、Evidenceを参照する検証Resultを一度索引化します。Schema検証と現在値のハッシュ計算は、Contract Healthへ影響するContract、Evidence、検証Result、参照先だけに限定します。Result全件を条項ごとに走査せず、無関係なResultの内容全体も検証しません。ただし、Filesystem StoreはすべてのResultファイルを列挙してJSONとして読み込むため、壊れたJSONは引き続きエラーになります。 + +Result提出時は、判定結果の`input_refs`と`freshness_refs`がResult全体の値と同じなら省略します。判定結果だけが追加または異なる参照を持つ場合は、その値を判定結果へ保存します。Kernelは判定結果に値がなければResult全体の値を使うため、既存形式と軽量形式を同じ意味で扱えます。Schema上で両項目はもともと省略可能なので、Schema versionは変更しません。既存Resultを変換するとResult IDと、それを参照する後続Resultの鮮度が変わるため、自動移行は行いません。 + - `verified`: 成功Evidenceを参照するBuilder outcomeの入力digestがすべて現在値と一致する - `stale`: 検証履歴はあるが、Contract・コード・設定・Evidence等の入力が変わった、または失われた - `unverified`: 検証履歴がない diff --git a/src/contract_health.rs b/src/contract_health.rs index f807ba1..5375c12 100644 --- a/src/contract_health.rs +++ b/src/contract_health.rs @@ -88,29 +88,18 @@ pub fn build_contract_health_report( let project = project .as_object() .ok_or_else(|| health_error("project must be an object"))?; - for (collection, kind) in [ - ("changes", "change"), - ("contracts", "contract"), - ("decisions", "decision"), - ("results", "result"), - ("evidence", "evidence"), - ] { + for collection in ["changes", "contracts", "decisions", "results", "evidence"] { let records = record_array(project.get(collection), collection)?; validate_unique_ids(records, collection)?; - for record in records { - schema_registry - .validate(kind, record) - .map_err(|error| health_error(format!("invalid {kind} record: {error}")))?; - } } - let current_digests = current_artifact_digests(project)?; let results = record_array(project.get("results"), "results")?; let evidence = record_array(project.get("evidence"), "evidence")?; - let evidence_by_id = evidence - .iter() - .filter_map(|record| record["id"].as_str().map(|id| (id.to_owned(), record))) - .collect::>(); + let verification_outcomes_by_evidence = verification_outcomes_by_evidence(results); + let required_current_refs = verification_freshness_refs(results); + validate_health_records(project, schema_registry, &required_current_refs)?; + let current_digests = current_artifact_digests(project, &required_current_refs)?; + let evidence_by_clause = evidence_by_clause(evidence); let mut clauses = Vec::new(); let mut seen_clause_refs = BTreeSet::new(); @@ -137,8 +126,11 @@ pub fn build_contract_health_report( clause, &clause_applies_to, evidence_mode, - results, - &evidence_by_id, + evidence_by_clause + .get(clause_ref.as_str()) + .map(Vec::as_slice) + .unwrap_or(&[]), + &verification_outcomes_by_evidence, ¤t_digests, )); } @@ -164,6 +156,117 @@ pub fn build_contract_health_report( }) } +fn validate_health_records( + project: &serde_json::Map, + schema_registry: &SchemaRegistry, + required_refs: &BTreeSet<&str>, +) -> Result<(), ContractHealthError> { + for (collection, kind) in [ + ("changes", "change"), + ("contracts", "contract"), + ("decisions", "decision"), + ("results", "result"), + ("evidence", "evidence"), + ] { + for record in record_array(project.get(collection), collection)? { + let record_id = required_string(record, "id", kind)?; + let required_for_health = matches!(collection, "contracts" | "evidence") + || required_refs.contains(record_id) + || (collection == "results" + && record["result_schema"].as_str() == Some("result.evidence") + && record["role"].as_str() == Some("Builder")); + if required_for_health { + schema_registry + .validate(kind, record) + .map_err(|error| health_error(format!("invalid {kind} record: {error}")))?; + } + } + } + Ok(()) +} + +#[derive(Clone, Copy)] +struct VerificationOutcome<'a> { + result: &'a Value, + outcome: &'a Value, +} + +fn evidence_by_clause(evidence: &[Value]) -> BTreeMap<&str, Vec<(&str, &Value)>> { + let mut by_clause = BTreeMap::new(); + for record in evidence { + let Some(evidence_id) = record["id"].as_str() else { + continue; + }; + for clause_ref in record["contract_clause_refs"] + .as_array() + .into_iter() + .flatten() + .filter_map(Value::as_str) + { + by_clause + .entry(clause_ref) + .or_insert_with(Vec::new) + .push((evidence_id, record)); + } + } + by_clause +} + +fn verification_outcomes_by_evidence( + results: &[Value], +) -> BTreeMap<&str, Vec>> { + let mut by_evidence = BTreeMap::new(); + for result in results.iter().filter(|result| { + result["result_schema"].as_str() == Some("result.evidence") + && result["role"].as_str() == Some("Builder") + }) { + for outcome in result["payload"]["outcomes"] + .as_array() + .into_iter() + .flatten() + { + for evidence_ref in outcome["basis_refs"] + .as_array() + .into_iter() + .flatten() + .filter_map(Value::as_str) + { + by_evidence + .entry(evidence_ref) + .or_insert_with(Vec::new) + .push(VerificationOutcome { result, outcome }); + } + } + } + by_evidence +} + +fn verification_freshness_refs(results: &[Value]) -> BTreeSet<&str> { + let mut references = BTreeSet::new(); + for result in results.iter().filter(|result| { + result["result_schema"].as_str() == Some("result.evidence") + && result["role"].as_str() == Some("Builder") + }) { + for outcome in result["payload"]["outcomes"] + .as_array() + .into_iter() + .flatten() + { + let manifest = outcome + .get("freshness_refs") + .or_else(|| result.get("freshness_refs")) + .or_else(|| result.get("input_refs")); + references.extend( + manifest + .and_then(Value::as_object) + .into_iter() + .flat_map(|manifest| manifest.keys().map(String::as_str)), + ); + } + } + references +} + #[allow(clippy::too_many_arguments)] fn clause_health( contract_id: &str, @@ -172,8 +275,8 @@ fn clause_health( clause: &Value, applies_to: &[String], evidence_mode: ClauseEvidenceMode, - results: &[Value], - evidence_by_id: &BTreeMap, + evidence_for_clause: &[(&str, &Value)], + verification_outcomes_by_evidence: &BTreeMap<&str, Vec>>, current_digests: &BTreeMap, ) -> ClauseHealth { let mut evidence_refs = BTreeSet::new(); @@ -183,47 +286,42 @@ fn clause_health( let mut current_failure = false; let mut historical_verification = false; - for (evidence_id, evidence) in evidence_by_id { + for (evidence_id, evidence) in evidence_for_clause { if !evidence_covers_clause(evidence, clause_ref, evidence_mode) { continue; } - evidence_refs.insert(evidence_id.clone()); - for result in results.iter().filter(|result| { - result["result_schema"].as_str() == Some("result.evidence") - && result["role"].as_str() == Some("Builder") - && result["change_id"] == evidence["change_id"] - }) { - for outcome in result["payload"]["outcomes"] - .as_array() - .map(Vec::as_slice) - .unwrap_or(&[]) - .iter() - .filter(|outcome| string_array(&outcome["basis_refs"]).contains(evidence_id)) - { - let Some(result_id) = result["id"].as_str() else { - continue; - }; - verification_result_ids.insert(result_id.to_owned()); - let mismatches = stale_refs_for_outcome( - outcome, - result, - current_digests, - &[contract_id, evidence_id], - ); - if mismatches.is_empty() { - match (evidence["outcome"].as_str(), outcome["status"].as_str()) { - (Some("passed"), Some("satisfied")) - if evidence_verifies_outcome(evidence, outcome) => - { - current_success = true; - } - (Some("failed" | "inconclusive"), _) => current_failure = true, - _ => {} + evidence_refs.insert((*evidence_id).to_owned()); + for verification in verification_outcomes_by_evidence + .get(evidence_id) + .into_iter() + .flatten() + .filter(|verification| verification.result["change_id"] == evidence["change_id"]) + { + let result = verification.result; + let outcome = verification.outcome; + let Some(result_id) = result["id"].as_str() else { + continue; + }; + verification_result_ids.insert(result_id.to_owned()); + let mismatches = stale_refs_for_outcome( + outcome, + result, + current_digests, + &[contract_id, evidence_id], + ); + if mismatches.is_empty() { + match (evidence["outcome"].as_str(), outcome["status"].as_str()) { + (Some("passed"), Some("satisfied")) + if evidence_verifies_outcome(evidence, outcome) => + { + current_success = true; } - } else { - historical_verification = true; - stale_refs.extend(mismatches); + (Some("failed" | "inconclusive"), _) => current_failure = true, + _ => {} } + } else { + historical_verification = true; + stale_refs.extend(mismatches); } } } @@ -310,12 +408,15 @@ fn is_sha256_digest(value: &str) -> bool { fn current_artifact_digests( project: &serde_json::Map, + required_refs: &BTreeSet<&str>, ) -> Result, ContractHealthError> { let mut digests = BTreeMap::new(); for collection in ["changes", "contracts", "decisions", "results", "evidence"] { for record in record_array(project.get(collection), collection)? { let id = required_string(record, "id", "Project record")?; - digests.insert(id.to_owned(), digest_value(record)?); + if required_refs.contains(id) { + digests.insert(id.to_owned(), digest_value(record)?); + } if collection == "contracts" { for clause in record["clauses"] .as_array() @@ -323,7 +424,10 @@ fn current_artifact_digests( .unwrap_or(&[]) { let clause_id = required_string(clause, "id", "Contract clause")?; - digests.insert(format!("{id}#{clause_id}"), digest_value(clause)?); + let clause_ref = format!("{id}#{clause_id}"); + if required_refs.contains(clause_ref.as_str()) { + digests.insert(clause_ref, digest_value(clause)?); + } } } } @@ -335,6 +439,9 @@ fn current_artifact_digests( .unwrap_or(&[]) { let reference = required_string(artifact, "ref", "Repository artifact")?; + if !required_refs.contains(reference) { + continue; + } let digest = artifact["digest"] .as_str() .filter(|value| !value.is_empty()) @@ -713,4 +820,43 @@ mod tests { ["result.retry", "result.test"] ); } + + #[test] + fn current_digests_skip_records_unrelated_to_contract_verification() { + let project = json!({ + "changes": [], + "contracts": [], + "decisions": [], + "results": [{"id": "result.unrelated", "unsupported": 1.5}], + "evidence": [], + "repository": {"artifacts": []} + }); + let project = project.as_object().unwrap(); + + assert!(current_artifact_digests(project, &BTreeSet::new()).is_ok()); + let required = BTreeSet::from(["result.unrelated"]); + assert!(current_artifact_digests(project, &required).is_err()); + } + + #[test] + fn report_validates_only_records_that_can_affect_contract_health() { + let project = json!({ + "changes": [], + "contracts": [], + "decisions": [], + "results": [{ + "id": "result.unrelated", + "result_schema": "result.challenge" + }], + "evidence": [], + "repository": {"artifacts": []} + }); + let report = build_contract_health_report(&project, ®istry()).unwrap(); + assert_eq!(report.summary.total, 0); + + let mut relevant = project; + relevant["results"][0]["result_schema"] = json!("result.evidence"); + relevant["results"][0]["role"] = json!("Builder"); + assert!(build_contract_health_report(&relevant, ®istry()).is_err()); + } } diff --git a/src/explain.rs b/src/explain.rs index abf993e..f74d802 100644 --- a/src/explain.rs +++ b/src/explain.rs @@ -384,7 +384,7 @@ fn requirement_trace( && role_matches && stale_refs.is_empty() && (instance.assurance == Assurance::Attestation - || outcome_has_current_evidence(instance, outcome, snapshot)); + || outcome_has_current_evidence(instance, result, outcome, snapshot)); result_checks.push(ResultCheck { result_id: string_field(result, "id").unwrap_or("").to_owned(), outcome_status, diff --git a/src/kernel.rs b/src/kernel.rs index 66b7d26..d673444 100644 --- a/src/kernel.rs +++ b/src/kernel.rs @@ -936,7 +936,7 @@ fn is_satisfied(instance: &RequirementInstance, snapshot: &ProjectSnapshot) -> b && match instance.assurance { Assurance::Attestation => true, Assurance::EvidenceBacked => { - outcome_has_current_evidence(instance, outcome, snapshot) + outcome_has_current_evidence(instance, result, outcome, snapshot) } } }) @@ -945,6 +945,7 @@ fn is_satisfied(instance: &RequirementInstance, snapshot: &ProjectSnapshot) -> b pub(crate) fn outcome_has_current_evidence( instance: &RequirementInstance, + result: &Value, outcome: &Value, snapshot: &ProjectSnapshot, ) -> bool { @@ -960,7 +961,15 @@ pub(crate) fn outcome_has_current_evidence( }; if !basis_refs.contains(evidence_id) || string_field(evidence, "change_id") != Some(snapshot.change_id.as_str()) - || !evidence_matches_inputs(evidence, &string_map(outcome.get("input_refs")), snapshot) + || !evidence_matches_inputs( + evidence, + &string_map( + outcome + .get("input_refs") + .or_else(|| result.get("input_refs")), + ), + snapshot, + ) || string_field(evidence, "outcome") != Some("passed") || !array_contains( &evidence["requirement_instances"], diff --git a/src/submission.rs b/src/submission.rs index 67c05d0..2c53407 100644 --- a/src/submission.rs +++ b/src/submission.rs @@ -124,7 +124,14 @@ pub fn prepare_result( if submission.result_schema == "result.risk-signal-review" { validate_candidate_reviews(context, &payload)?; } - enrich_outcomes(context, current, &mut payload, &submission.output_refs)?; + enrich_outcomes( + context, + current, + &mut payload, + &submission.output_refs, + &input_refs, + &freshness_refs, + )?; let mut output_refs = submission.output_refs.clone(); output_refs.sort(); @@ -387,6 +394,8 @@ fn enrich_outcomes( current: &ProjectSnapshot, payload: &mut Value, output_refs: &[String], + result_inputs: &BTreeMap, + result_freshness: &BTreeMap, ) -> Result<(), ResultSubmissionError> { let requirements = array_field(&context.payload, "requirement_instances"); let selectors = requirements @@ -467,11 +476,17 @@ fn enrich_outcomes( let object = outcome .as_object_mut() .expect("Result payload Schema guarantees an outcome object"); - object.insert("input_refs".to_owned(), string_map_value(instance_inputs)); - object.insert( - "freshness_refs".to_owned(), - string_map_value(&instance_freshness), - ); + object.remove("input_refs"); + object.remove("freshness_refs"); + if instance_inputs != result_inputs { + object.insert("input_refs".to_owned(), string_map_value(instance_inputs)); + } + if instance_freshness != *result_freshness { + object.insert( + "freshness_refs".to_owned(), + string_map_value(&instance_freshness), + ); + } } Ok(()) } @@ -723,9 +738,135 @@ mod tests { ) .unwrap(); assert_eq!( - result["payload"]["outcomes"][0]["freshness_refs"]["evidence.test"], + result["freshness_refs"]["evidence.test"], format!("sha256:{}", "b".repeat(64)) ); + assert!(result["payload"]["outcomes"][0].get("input_refs").is_none()); + assert!( + result["payload"]["outcomes"][0] + .get("freshness_refs") + .is_none() + ); + } + + #[test] + fn outcome_keeps_refs_when_the_result_manifest_is_broader() { + let mut context = context(); + let unrelated_digest = format!("sha256:{}", "f".repeat(64)); + context + .source_digests + .insert("code.unrelated".to_owned(), unrelated_digest.clone()); + let mut snapshot = snapshot(); + snapshot + .artifact_digests + .insert("code.unrelated".to_owned(), unrelated_digest); + + let result = prepare_result( + &context, + &snapshot, + &submission(json!(["evidence.test"]), vec!["evidence.test".to_owned()]), + ®istry(), + ) + .unwrap(); + + let outcome = &result["payload"]["outcomes"][0]; + assert!(outcome["input_refs"].get("change.test").is_some()); + assert!(outcome["input_refs"].get("code.unrelated").is_none()); + assert!(outcome["freshness_refs"].get("evidence.test").is_some()); + assert!(outcome["freshness_refs"].get("code.unrelated").is_none()); + } + + #[test] + fn shared_outcome_refs_are_stored_once_per_result() { + let digest = format!("sha256:{}", "a".repeat(64)); + let shared_refs = (0..512) + .map(|index| (format!("code.file-{index}"), digest.clone())) + .collect::>(); + let requirements = (0..70) + .map(|index| { + json!({ + "instance_key": format!("requirement-{index}|operation.test"), + "subject_refs": ["operation.test"], + "context_selectors": [], + "assurance": "attestation" + }) + }) + .collect::>(); + let instance_source_digests = (0..70) + .map(|index| { + ( + format!("requirement-{index}|operation.test"), + shared_refs.clone(), + ) + }) + .collect(); + let context = GeneratedContext { + action_id: "action.compact".to_owned(), + role: "Analyst".to_owned(), + source_refs: shared_refs.keys().cloned().collect(), + source_digests: shared_refs.clone(), + instance_source_digests, + contract_clause_projection_version: "1".to_owned(), + contract_clauses: Vec::new(), + contract_clauses_digest: canonical_digest(&json!([])).unwrap(), + payload: json!({ + "action": {"expected_result_schema": "result.analysis"}, + "requirement_instances": requirements, + "signal_candidates": [] + }), + digest: format!("sha256:{}", "c".repeat(64)), + }; + let snapshot = ProjectSnapshot { + change_id: "change.test".to_owned(), + change: json!({}), + contracts: Vec::new(), + decisions: Vec::new(), + results: Vec::new(), + evidence: Vec::new(), + repository: json!({"revision": "revision-1"}), + artifact_digests: shared_refs.clone(), + digest: String::new(), + }; + let outcomes = (0..70) + .map(|index| { + json!({ + "instance_key": format!("requirement-{index}|operation.test"), + "definition_digest": format!("sha256:{}", "e".repeat(64)), + "status": "satisfied", + "summary": "Reviewed the requirement.", + "basis_refs": ["code.file-0"] + }) + }) + .collect::>(); + let submission = ResultSubmission { + change_id: "change.test".to_owned(), + action_id: "action.compact".to_owned(), + context_digest: context.digest.clone(), + role: "Analyst".to_owned(), + result_schema: "result.analysis".to_owned(), + payload: json!({"outcomes": outcomes}), + output_refs: Vec::new(), + execution: None, + }; + + let result = prepare_result(&context, &snapshot, &submission, ®istry()).unwrap(); + assert!( + result["payload"]["outcomes"] + .as_array() + .unwrap() + .iter() + .all(|outcome| outcome.get("input_refs").is_none() + && outcome.get("freshness_refs").is_none()) + ); + + let compact_bytes = serde_json::to_vec(&result).unwrap().len(); + let mut expanded = result.clone(); + for outcome in expanded["payload"]["outcomes"].as_array_mut().unwrap() { + outcome["input_refs"] = string_map_value(&shared_refs); + outcome["freshness_refs"] = string_map_value(&shared_refs); + } + let expanded_bytes = serde_json::to_vec(&expanded).unwrap().len(); + assert!(compact_bytes * 20 < expanded_bytes); } #[test] diff --git a/testdata/golden/v1/application-lifecycle.json b/testdata/golden/v1/application-lifecycle.json index 148f837..fd47be6 100644 --- a/testdata/golden/v1/application-lifecycle.json +++ b/testdata/golden/v1/application-lifecycle.json @@ -45,7 +45,7 @@ }, "expected": { "state": "needs-analysis", - "snapshot_digest": "sha256:66774998bdeae6452e45bdf849a2fbc4af118343b7bf006c441447685b9c7127", + "snapshot_digest": "sha256:1e28e7b4cddd6ed627a5d1d46c07ace87973f029c1cfd4bc4bfe415927d6dadd", "decision_digest": "sha256:2814ecde0e19b165a898fbb193405ba702ceb802b01a66609f616e26e174598a", "context_digest": "sha256:75e91d7eb73e0f8691f3913d8360c9c93b6097e1e30bc54d4b89d5792f4292d9", "action": { @@ -82,7 +82,7 @@ }, "expected": { "state": "needs-analysis", - "snapshot_digest": "sha256:625bc8f0e802f7818b658f8b9500cb0b9627d52b4991fbfc35cb4c93e2d1c07d", + "snapshot_digest": "sha256:2c7d9d84beb9a17b30e23560de829035653f4663dea3dee681b8b8bb1aa67e10", "decision_digest": "sha256:c636e6c901e7d03d4ecfc15466b20d42915c94ed6a25bcff51397c951ddabdef", "context_digest": "sha256:70098bacdfff3dedc08b45ee047a45dae7a4408492109b4f565e463384af5760", "action": { @@ -130,9 +130,9 @@ }, "expected": { "state": "needs-human-decision", - "snapshot_digest": "sha256:e1268b670bb4d8635b605f502560f773e4eb67acd2d23ed5a2e7228ac2731acf", + "snapshot_digest": "sha256:16ebb64f6dcd54e7d009aa75af9cd8d0646e89ebfa4f177ab3313de04b5cf6b3", "decision_digest": "sha256:24e7524bd51513fbf2fb7847872bebfdc53644f09cf3eed08cfc493b33357c17", - "context_digest": "sha256:476bbbb14788c348877a6ddb1d2c29b4bde24bbbd8f8c45ada4825ba8335023c", + "context_digest": "sha256:8387eddb8793766b3ffd55250c736f8b3f8bafc52265ce987bf6a83b6001009b", "action": { "id": "action.a5ce67b153296089", "role": "Human", @@ -158,7 +158,7 @@ }, "expected": { "state": "needs-decision-recording", - "snapshot_digest": "sha256:07c92cba81d720a1d1d6ff92a1433ef6ef5f64193544510e955b75c187c440b3", + "snapshot_digest": "sha256:54699b9dced62afc8fc4bb29ce17b80d538fb2e05aa7cc7bb0c2e022d8b4aab2", "decision_digest": "sha256:02e2b277ab04a943475bf08deb7c0b315b18031f0031b3b6a00fa97193170a56", "context_digest": "sha256:d05a49c64e7aa6a3abd48f9a6bf0a06944486baf47b792a969b2f520b5a81156", "action": { @@ -232,7 +232,7 @@ }, "expected": { "state": "needs-analysis", - "snapshot_digest": "sha256:bd46cb99f41db5a5b38e173e3efec3900854b60396d4c93156994ea75e411fef", + "snapshot_digest": "sha256:342856dd9ba81d252c47431f9d6fb18250c64813062606e1094c295272888052", "decision_digest": "sha256:364a72f2e5dbb25d916363e634c17b93f9c9492db631cb8909779f8d3b767ec6", "context_digest": "sha256:3a208e7520fef34f088147d6102dae626d59061d2317d4a46c0828533dea3e90", "action": { @@ -270,9 +270,9 @@ }, "expected": { "state": "needs-pre-build-challenge", - "snapshot_digest": "sha256:78a06f3334587a3e3b6709bb51824641ef9621455a44fb3f4be6f6076def9443", + "snapshot_digest": "sha256:76a53476329876594ac1e57bdf3fa269d02cb4745ab34c38efc646da47a881f4", "decision_digest": "sha256:9bc33fdf3e13253e90b76b088d90f0a29b410f0c22087f40fc460ae099c1478b", - "context_digest": "sha256:ec4789c7172c705e022e2f0d56edc180efb12ffa92876ae8cb18eb2231a7f026", + "context_digest": "sha256:23939b1d9e1d4873f462bb4a56e46a15d600cd34c8929aa62277cb56aa93b2ce", "action": { "id": "action.921c56f3ffbf5770", "role": "Challenger", @@ -300,7 +300,7 @@ "contract.order-lifecycle", "decision.order-model", "decision.submission-result", - "result.3cfe35a1e43a1d7a775a" + "result.e3e118c2ab0b5cd05f2c" ] } ] @@ -309,9 +309,9 @@ }, "expected": { "state": "ready-to-build", - "snapshot_digest": "sha256:6d5f2aacf1cf6c90ab445ffc437cac8cbc9fbe7b1a7e45f716991574ee607d69", + "snapshot_digest": "sha256:befdc5803c17aef78a59959d3a4be8fc5916f39e25f42166a0f7b1772d52635d", "decision_digest": "sha256:3862850773dbcd7802438b962d6de98fb5a5d22b8f711910a016508e4b887b37", - "context_digest": "sha256:7a641c88833beb3d489a04aeaf31ef7e1af9a69a53ef80e961b57fd432be8d3f", + "context_digest": "sha256:9a4472a4f0ba86548d370da57a454b21bebab5213b685811ec6e17ff89338cc2", "action": { "id": "action.7298bff69acdb1bb", "role": "Builder", @@ -378,7 +378,7 @@ }, "expected": { "state": "needs-evidence", - "snapshot_digest": "sha256:d146847cb379da47d246667dbece3ab2a6fa047dadc3cce9cc3639364f3897c9", + "snapshot_digest": "sha256:80159d0b5747a3f9cbf97060151a204a1c024ceb3766d9d94c5147295ab0a28d", "decision_digest": "sha256:ef016dcf47bdeaf98f359cc2710a53029d61afef4b82d094db8d3a4de6fb0c9c", "context_digest": "sha256:fc3e1266bab751b975ffc8a94f16d420e4a908e758179aef536b5eba446e6a23", "action": { @@ -414,9 +414,9 @@ }, "expected": { "state": "needs-post-build-challenge", - "snapshot_digest": "sha256:7c9c246cb3522b80068f52e21d022ced1676820138d7b92338da5dad02657102", + "snapshot_digest": "sha256:fba65f54886a31c85b97b1cd66a797d5ae927a5604745f576fe13459444eaa80", "decision_digest": "sha256:fbfcbb20f73c0a40e2bfe55b47d206f65080782498f00b6d72f4fe3324488158", - "context_digest": "sha256:5fea6c0ac61f8422506f2c111c618580ee1f7dd7e97ca45b5d8c37fff839ecba", + "context_digest": "sha256:1aaae51fbc10eb65f658c5acc29135c1e5902402d47bc3f098645dd1efc4dce2", "action": { "id": "action.d6bb346ee05b334a", "role": "Challenger", @@ -450,7 +450,7 @@ }, "expected": { "state": "ready-to-merge", - "snapshot_digest": "sha256:c56600597231c8df333e9ce95c948ec832707bfb0873613f730cb3d32ce92bff", + "snapshot_digest": "sha256:c93bf566a3b4ed0b4a06b9a515dd943e501393fae7ec04278b505ea0e57cdd0c", "decision_digest": "sha256:54fb70bae7c14cdabf787da5f34f7083a9d323c76ac735cd2294e1eaa03e0864", "context_digest": null, "action": null diff --git a/testdata/golden/v1/explain-report.json b/testdata/golden/v1/explain-report.json index 73c2c0a..8b6ac13 100644 --- a/testdata/golden/v1/explain-report.json +++ b/testdata/golden/v1/explain-report.json @@ -22,8 +22,8 @@ "candidate_count": 3, "requirement_count": 4, "authority_statuses": [], - "report_digest": "sha256:ab476b3471f5bcf2f58fd5d4977f88c02330dd8ac8620dea9195402c91193095", - "text_digest": "sha256:fed2ba599a3a0620da9b0170040cb01d8b4f6440ab8f6c8022c834826800b616" + "report_digest": "sha256:2c8d010c5cfaf874e1c7cf4e42fe521aa032ff61b3d1f0dbc926d20877162364", + "text_digest": "sha256:a79cf9e52491d4980ad715abd795b930528dd81c9499d5f71c59b85fd095c397" }, { "label": "after-step-1", @@ -32,8 +32,8 @@ "candidate_count": 3, "requirement_count": 4, "authority_statuses": [], - "report_digest": "sha256:104aca64be8ec3f8471ad1c6a7b844b696e3a15ebf889e28370b6ae918110298", - "text_digest": "sha256:dcb1c08593867074e5ad0bbe0b6d98ce239968de2aaac64c2d50e63fe5327763" + "report_digest": "sha256:56cb320ae89f8af637c0e39ab07333a4dbdc3df72723a09e3a22974f9a9e5df0", + "text_digest": "sha256:c642612a61b686b61d14f7ab5f56ddcdaced7ccfb17cf306e9a371f3b5ad284f" }, { "label": "after-step-2", @@ -44,8 +44,8 @@ "authority_statuses": [ "open" ], - "report_digest": "sha256:ed5b2ed299c1747aaea8e891fc25f7c0bacc300ea14d41ff5e2c639e05b1b9a3", - "text_digest": "sha256:acd286f676a8a1f839f207ddb3c9d27f86bdf26aa73a0dbdd76423974a6055e9" + "report_digest": "sha256:300e02c486ad79dc7690e5b9486c3d34bf8f7573528410acfdb29c809c7d3e2a", + "text_digest": "sha256:28064a6af6cb8fb6db698e3baeac0c55f3dc62cff31d3e8b0b7d3c2181641134" }, { "label": "after-step-3", @@ -56,8 +56,8 @@ "authority_statuses": [ "answered-not-recorded" ], - "report_digest": "sha256:e6ad89ae87eb5beb9e5eee3a2cbe80486ac41e9619a03e89252a7086fed19502", - "text_digest": "sha256:022377e2392eefe1698b130804dd92aa2d11e0dc94a1561325aec24586b49c94" + "report_digest": "sha256:baff42c6316f921807fe2cfcf7cd06c920d88aa0ce158f73b0571657ae41c4bf", + "text_digest": "sha256:a879637efa53d1e80134929138b1d3823592c0e4e3079fcdba79ee874dc18f79" }, { "label": "after-step-4", @@ -68,8 +68,8 @@ "authority_statuses": [ "answered-not-recorded" ], - "report_digest": "sha256:3e39f4b0c8938fff8ca916349968c1b95fd22631dbfd11c8f2885fed68abd53f", - "text_digest": "sha256:5a425163f8f529c6f4ef537349acf8ed4dd443cbd8eb3eba8fff87be3f677067" + "report_digest": "sha256:ff6f67a91cb488aefd92cedc45d55dc00f48b71523b015ca20ca00ad25e92031", + "text_digest": "sha256:86105acc27cd0f034a5dc88384d2dfe28eee2a6cc667e4a5aca9d425d7ee8642" }, { "label": "after-step-5", @@ -80,8 +80,8 @@ "authority_statuses": [ "recorded" ], - "report_digest": "sha256:0470d3b4964b693339e05be63cd0f62f6e7bafa34e3037b01aa6771118baf841", - "text_digest": "sha256:7cd6bd40e18bfb9b2ceedfe52030397eba5c125119f022fc6a05eab7981df0b2" + "report_digest": "sha256:6b418308be8a602299c9990c4d68ddb38c449c431a6abdb983b89b385686782b", + "text_digest": "sha256:608602b95e8af467890ba45d3c7a004bef77139345b183f5d5c77a015b69f7be" }, { "label": "after-step-6", @@ -92,8 +92,8 @@ "authority_statuses": [ "recorded" ], - "report_digest": "sha256:a9bf4b3169d47939808fafe06ec35959ecd445b60724c0077742dd62bff35b81", - "text_digest": "sha256:429f01038d336b41594d5936844dfa95859b4b405a147e796831d5e6cd5f0e05" + "report_digest": "sha256:936a2027b1f3bc11d86ffdb6094b9d675dcd929889f91ff7bc6e08670daee967", + "text_digest": "sha256:b6e71d1c865d815e0a3f0d9e308ff73fd58a7bbfb9c38add072ea02a10b241c6" }, { "label": "after-step-7", @@ -104,8 +104,8 @@ "authority_statuses": [ "recorded" ], - "report_digest": "sha256:32f1212115d4ced0777302d6ef8f28073359489bbb9b539a41f27ae2de73ac12", - "text_digest": "sha256:7dba2f45ef1704d8cf8541b6b2ad135ceceb8a7c0a2e67e53f6f23895ee28258" + "report_digest": "sha256:7be3d5822e9d5c552d4fc22100234dd6fb94c1c6cf1569b00ad88e6421c910ec", + "text_digest": "sha256:22379dcbe60bf24c363cc81865ae62126f7d1b020c7f04ec063cf2bcb640e7af" }, { "label": "after-step-8", @@ -116,8 +116,8 @@ "authority_statuses": [ "recorded" ], - "report_digest": "sha256:917fe62532a71c7b53917270841ebd3ab922fb19fe53ec3162392f9ff45efbf8", - "text_digest": "sha256:464da44f7ebff994dce403b562d80f506602242bdf00f3e2d9b969ace301fd84" + "report_digest": "sha256:63056c38690b901b1f4e27ae53a2838617556516ac9d6f8b8656e90e50e4552c", + "text_digest": "sha256:e2e23e59e842e81c23f4139587203077d09229fdbc0203a902c4ff677af08112" }, { "label": "after-step-9", @@ -128,8 +128,8 @@ "authority_statuses": [ "recorded" ], - "report_digest": "sha256:8b48fb6678fe8bbfc1007de74c1d8114c3ad6be5052aa5f075eacc67e4874e39", - "text_digest": "sha256:3248a6ea7adbdb57d85243d67f7f704c2d5bea09d8bbbf544bd3c626633565bd" + "report_digest": "sha256:55c5e3524c808b42e0e8e5ce51a796c357c1fef2e31ce74c64a4b538de0b1390", + "text_digest": "sha256:2bbd34247988864dbeccbf477c413403373c7c0357b7f46ffe4f56fbcaceb8eb" }, { "label": "after-step-10", @@ -140,8 +140,8 @@ "authority_statuses": [ "recorded" ], - "report_digest": "sha256:7d363be7f6a5e6afc4bc7476146c005b18ec6e002900501422ab0bae701fb9e5", - "text_digest": "sha256:4d99802c8594bdb6caea3dc8c26ba68f756c4615f29c5d240ac3f53fbbde4cb0" + "report_digest": "sha256:bb58d7e117e1bea6f93cfd350ae880c09c4d19ac8f22bb543c729474268762eb", + "text_digest": "sha256:c27ef9676a0c64d1514aa187662a11db3d2ced5e6d491d8b28151b83b6131f92" }, { "label": "after-step-11", @@ -152,8 +152,8 @@ "authority_statuses": [ "recorded" ], - "report_digest": "sha256:3d448b40db66dfcb3bb2110b0fe72cccc694a514c9c3ff97d4d1d4478c03ad33", - "text_digest": "sha256:ff79bbfb55c49390daf64f57b45c553abbf53dfb8f2f5622098f2134891eb884" + "report_digest": "sha256:80a72960095078fcd76629ab7833c9deaab85b057cdd26aa0b942f9c8d93a922", + "text_digest": "sha256:4bccde6c8dbe8fbbaacd046fd906e54ed800e102a6c7accdf976dad62f489af0" } ] } diff --git a/testdata/golden/v1/filesystem-project.json b/testdata/golden/v1/filesystem-project.json index df39515..0c34a48 100644 --- a/testdata/golden/v1/filesystem-project.json +++ b/testdata/golden/v1/filesystem-project.json @@ -8,8 +8,8 @@ "decision_step": 4, "contract_step": 5, "expected_initial_snapshot_digest": "sha256:3ae18b6dc5493c557b0737acc55d0cddb85e2f45d098ef053a68a85f445d3360", - "expected_result_snapshot_digest": "sha256:66774998bdeae6452e45bdf849a2fbc4af118343b7bf006c441447685b9c7127", - "expected_updated_snapshot_digest": "sha256:5f4f1c91730b17591b7215e2faf1083b9a3e15da676c050187c590f07b15fb35", + "expected_result_snapshot_digest": "sha256:1e28e7b4cddd6ed627a5d1d46c07ace87973f029c1cfd4bc4bfe415927d6dadd", + "expected_updated_snapshot_digest": "sha256:b95e4ba416267389fe5251005c177f2c277d8637b0f592d2dcd630a23bcaf477", "markdown_prose": "## Operational rationale\n\nThe orders table remains authoritative while event delivery is retried.", "shared_contract_concurrency": { "initial": { diff --git a/testdata/golden/v1/result-submission.json b/testdata/golden/v1/result-submission.json index 5576574..9cba316 100644 --- a/testdata/golden/v1/result-submission.json +++ b/testdata/golden/v1/result-submission.json @@ -6,7 +6,7 @@ "scenario_case": "application-lifecycle.json", "step_index": 0, "expected": { - "id": "result.a9264e02b9de0b0dba0f", + "id": "result.32804d7e11b978973228", "schema_version": "1", "change_id": "change.place-order", "action_id": "action.76c79e6f916c6355", @@ -64,17 +64,7 @@ "change.place-order", "code.order-events-publisher", "code.place-order-handler" - ], - "input_refs": { - "change.place-order": "sha256:c4bb2ccce8564e5ad8db5678d2a7a80d7898a6399ac81591284836b8c1bb42a6", - "code.order-events-publisher": "sha256:2222222222222222222222222222222222222222222222222222222222222222", - "code.place-order-handler": "sha256:1111111111111111111111111111111111111111111111111111111111111111" - }, - "freshness_refs": { - "change.place-order": "sha256:c4bb2ccce8564e5ad8db5678d2a7a80d7898a6399ac81591284836b8c1bb42a6", - "code.order-events-publisher": "sha256:2222222222222222222222222222222222222222222222222222222222222222", - "code.place-order-handler": "sha256:1111111111111111111111111111111111111111111111111111111111111111" - } + ] } ] }