From be56bfa7d413caf7726d902dbdd511b4337c41e5 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 11:05:15 +0300 Subject: [PATCH 01/37] feat(af02): reconstruct retained authority --- .../tests/af02_authority_reconstruction.rs | 168 +++++ tools/af02-verifier/Cargo.toml | 14 + tools/af02-verifier/src/authority.rs | 548 ++++++++++++++++ tools/af02-verifier/src/canonical.rs | 96 +++ tools/af02-verifier/src/lib.rs | 3 + tools/af02-verifier/src/main.rs | 132 ++++ tools/af02-verifier/src/retained.rs | 595 ++++++++++++++++++ tools/af02-verifier/tests/fixtures/README.md | 3 + .../tests/fixtures/assurance-ruleset.json | 31 + .../tests/fixtures/cf10-artifacts.json | 16 + .../tests/fixtures/cf10-corpus.json | 72 +++ .../tests/fixtures/cf10-donor.yaml | 131 ++++ .../tests/fixtures/cf10-run.json | 25 + .../tests/fixtures/review-ruleset.json | 32 + 14 files changed, 1866 insertions(+) create mode 100644 crates/commandf-pkg/tests/af02_authority_reconstruction.rs create mode 100644 tools/af02-verifier/Cargo.toml create mode 100644 tools/af02-verifier/src/authority.rs create mode 100644 tools/af02-verifier/src/canonical.rs create mode 100644 tools/af02-verifier/src/lib.rs create mode 100644 tools/af02-verifier/src/main.rs create mode 100644 tools/af02-verifier/src/retained.rs create mode 100644 tools/af02-verifier/tests/fixtures/README.md create mode 100644 tools/af02-verifier/tests/fixtures/assurance-ruleset.json create mode 100644 tools/af02-verifier/tests/fixtures/cf10-artifacts.json create mode 100644 tools/af02-verifier/tests/fixtures/cf10-corpus.json create mode 100644 tools/af02-verifier/tests/fixtures/cf10-donor.yaml create mode 100644 tools/af02-verifier/tests/fixtures/cf10-run.json create mode 100644 tools/af02-verifier/tests/fixtures/review-ruleset.json diff --git a/crates/commandf-pkg/tests/af02_authority_reconstruction.rs b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs new file mode 100644 index 00000000..4892ac6c --- /dev/null +++ b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs @@ -0,0 +1,168 @@ +#[path = "../../../tools/af02-verifier/src/canonical.rs"] +mod canonical; +#[path = "../../../tools/af02-verifier/src/retained.rs"] +mod retained; +#[path = "../../../tools/af02-verifier/src/authority.rs"] +mod authority; + +use std::fs; +use std::path::PathBuf; + +use authority::{project_assurance_ruleset, project_authority, project_cf06, Cf06Source}; +use canonical::canonical_json_bytes; +use retained::{project_retained, validate_and_parse, verify_artifacts, verify_workflow_run}; +use serde_json::Value; + +const MAIN_SHA: &str = "54b9772a3b86464da6f395f8ba8371f364c9bb38"; +const MAIN_TREE: &str = "4ac26d8de419a0bec0faba8e14ded1763cfe30b3"; + +const ORACLE_MODEL: &[u8] = include_bytes!("../src/oracle_model.rs"); +const CF06_DONOR: &[u8] = include_bytes!("../../../donors/hl7-fhir-validator-6.10.2.yaml"); +const CF06_WORKFLOW: &[u8] = include_bytes!("../../../.github/workflows/cf06-oracle.yml"); +const RETAINED_SOURCES: &[u8] = include_bytes!( + "../../../specs/016-af-02-adversarial-test-strength/retained-authority-sources.json" +); +const RETAINED_SCHEMA: &[u8] = include_bytes!( + "../../../specs/016-af-02-adversarial-test-strength/schemas/af02-retained-authority-sources-v1.schema.json" +); +const ASSURANCE_RULESET: &[u8] = + include_bytes!("../../../tools/af02-verifier/tests/fixtures/assurance-ruleset.json"); +const REVIEW_RULESET: &[u8] = + include_bytes!("../../../tools/af02-verifier/tests/fixtures/review-ruleset.json"); +const RETAINED_MANIFEST: &[u8] = + include_bytes!("../../../tools/af02-verifier/tests/fixtures/cf10-corpus.json"); +const RETAINED_DONOR: &[u8] = + include_bytes!("../../../tools/af02-verifier/tests/fixtures/cf10-donor.yaml"); +const RETAINED_RUN: &[u8] = + include_bytes!("../../../tools/af02-verifier/tests/fixtures/cf10-run.json"); +const RETAINED_ARTIFACTS: &[u8] = + include_bytes!("../../../tools/af02-verifier/tests/fixtures/cf10-artifacts.json"); + +fn build_baseline() -> authority::AuthorityBaseline { + let retained = validate_and_parse(RETAINED_SOURCES, RETAINED_SCHEMA).unwrap(); + let run: Value = serde_json::from_slice(RETAINED_RUN).unwrap(); + verify_workflow_run(&retained, &run).unwrap(); + let artifacts: Value = serde_json::from_slice(RETAINED_ARTIFACTS).unwrap(); + verify_artifacts(&retained, &artifacts).unwrap(); + let retained_projection = + project_retained(&retained, RETAINED_MANIFEST, RETAINED_DONOR).unwrap(); + let assurance: Value = serde_json::from_slice(ASSURANCE_RULESET).unwrap(); + let review: Value = serde_json::from_slice(REVIEW_RULESET).unwrap(); + + project_authority( + MAIN_SHA, + MAIN_TREE, + &assurance, + &review, + [ + Cf06Source { + path: "crates/commandf-pkg/src/oracle_model.rs", + git_blob_sha: "9046546a86061961cf3e17f3f1880165625edea8", + bytes: ORACLE_MODEL, + }, + Cf06Source { + path: "donors/hl7-fhir-validator-6.10.2.yaml", + git_blob_sha: "9add2dad45cb8958c9304d38e29950ed1f769990", + bytes: CF06_DONOR, + }, + Cf06Source { + path: ".github/workflows/cf06-oracle.yml", + git_blob_sha: "664e303983d2ef85aad934cbef2c14d63744e0ee", + bytes: CF06_WORKFLOW, + }, + ], + retained_projection, + ) + .unwrap() +} + +#[test] +fn retained_schema_rejects_candidate_url_authority() { + let mut value: Value = serde_json::from_slice(RETAINED_SOURCES).unwrap(); + value + .as_object_mut() + .unwrap() + .insert("url".to_owned(), Value::String("https://example.invalid".to_owned())); + let bytes = serde_json::to_vec(&value).unwrap(); + let error = validate_and_parse(&bytes, RETAINED_SCHEMA).unwrap_err(); + assert!(error.to_string().contains("unknown field")); +} + +#[test] +fn retained_run_binding_rejects_wrong_event() { + let retained = validate_and_parse(RETAINED_SOURCES, RETAINED_SCHEMA).unwrap(); + let mut run: Value = serde_json::from_slice(RETAINED_RUN).unwrap(); + run.as_object_mut() + .unwrap() + .insert("event".to_owned(), Value::String("push".to_owned())); + let error = verify_workflow_run(&retained, &run).unwrap_err(); + assert!(error.to_string().contains("event mismatch")); +} + +#[test] +fn retained_artifact_binding_rejects_wrong_digest() { + let retained = validate_and_parse(RETAINED_SOURCES, RETAINED_SCHEMA).unwrap(); + let mut artifacts: Value = serde_json::from_slice(RETAINED_ARTIFACTS).unwrap(); + artifacts["artifacts"][0]["digest"] = Value::String( + "sha256:0000000000000000000000000000000000000000000000000000000000000000" + .to_owned(), + ); + let error = verify_artifacts(&retained, &artifacts).unwrap_err(); + assert!(error.to_string().contains("artifact digest mismatch")); +} + +#[test] +fn assurance_projection_rejects_wrong_required_check_app() { + let mut assurance: Value = serde_json::from_slice(ASSURANCE_RULESET).unwrap(); + assurance["rules"][2]["parameters"]["required_status_checks"][0]["integration_id"] = + Value::from(1); + let error = project_assurance_ruleset(&assurance).unwrap_err(); + assert!(error.to_string().contains("unexpected integration")); +} + +#[test] +fn cf06_projection_rejects_missing_source_pin() { + let altered = ORACLE_MODEL + .windows(authority::CF06_SOURCE_COMMIT.len()) + .position(|window| window == authority::CF06_SOURCE_COMMIT.as_bytes()) + .unwrap(); + let mut bytes = ORACLE_MODEL.to_vec(); + bytes[altered] = b'0'; + + let error = project_cf06([ + Cf06Source { + path: "crates/commandf-pkg/src/oracle_model.rs", + git_blob_sha: "9046546a86061961cf3e17f3f1880165625edea8", + bytes: &bytes, + }, + Cf06Source { + path: "donors/hl7-fhir-validator-6.10.2.yaml", + git_blob_sha: "9add2dad45cb8958c9304d38e29950ed1f769990", + bytes: CF06_DONOR, + }, + Cf06Source { + path: ".github/workflows/cf06-oracle.yml", + git_blob_sha: "664e303983d2ef85aad934cbef2c14d63744e0ee", + bytes: CF06_WORKFLOW, + }, + ]) + .unwrap_err(); + assert!(error.to_string().contains("does not bind")); +} + +#[test] +fn authority_baseline_v2_matches_canonical_snapshot() { + let baseline = build_baseline(); + let value = serde_json::to_value(&baseline).unwrap(); + let generated = canonical_json_bytes(&value).unwrap(); + + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../specs/016-af-02-adversarial-test-strength/authority-baseline.json"); + match fs::read(&path) { + Ok(expected) => assert_eq!(generated, expected), + Err(error) => panic!( + "authority baseline snapshot is missing ({error}); AF02_GENERATED_BASELINE={}", + String::from_utf8(generated).unwrap() + ), + } +} diff --git a/tools/af02-verifier/Cargo.toml b/tools/af02-verifier/Cargo.toml new file mode 100644 index 00000000..51726311 --- /dev/null +++ b/tools/af02-verifier/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "commandf-af02-verifier" +version = "0.0.0" +edition = "2021" +rust-version = "1.97.1" +publish = false + +[workspace] + +[dependencies] +serde = { version = "=1.0.229", features = ["derive"] } +serde_json = "=1.0.151" +sha2 = "=0.10.9" +thiserror = "=1.0.69" diff --git a/tools/af02-verifier/src/authority.rs b/tools/af02-verifier/src/authority.rs new file mode 100644 index 00000000..86bf7b5f --- /dev/null +++ b/tools/af02-verifier/src/authority.rs @@ -0,0 +1,548 @@ +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use thiserror::Error; + +use crate::canonical::{canonical_sha256, sha256_hex, CanonicalError}; +use crate::retained::{RetainedError, RetainedProjection}; + +pub const AUTHORITY_BASELINE_SCHEMA: &str = "commandf.af02-authority-baseline/v2"; +pub const ASSURANCE_RULESET_ID: u64 = 21652953; +pub const REVIEW_RULESET_ID: u64 = 21652974; + +pub const CF06_PROJECT: &str = "hapifhir/org.hl7.fhir.core"; +pub const CF06_RELEASE: &str = "6.10.2"; +pub const CF06_SOURCE_COMMIT: &str = "d06577dbc5c62c74a2a8823fbc4830a3024d5b0b"; +pub const CF06_VALIDATOR_SHA256: &str = + "a3addadfa18dfa23146a0a243b6ede68eaad92157a5407738c468bb3d7e4ccd6"; +pub const CF06_R4_CONTEXT: &str = "hl7.fhir.r4.core@4.0.1"; + +#[derive(Debug, Error)] +pub enum AuthorityError { + #[error("canonicalization failed: {0}")] + Canonical(#[from] CanonicalError), + #[error("retained authority error: {0}")] + Retained(#[from] RetainedError), + #[error("authority mismatch: {0}")] + Mismatch(String), + #[error("failed to serialize authority projection: {0}")] + Json(#[from] serde_json::Error), +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct AuthorityBaseline { + pub schema: String, + pub captured_from_main_sha: String, + pub captured_from_main_tree: String, + pub af01: Af01Baseline, + pub cf06: Cf06Baseline, + pub cf10: Cf10Baseline, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Af01Baseline { + pub assurance: RulesetProjection, + pub review_governance: RulesetProjection, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct RulesetProjection { + pub ruleset_id: u64, + pub semantic_sha256: String, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Cf06Baseline { + pub project: String, + pub release: String, + pub source_commit: String, + pub validator_cli_jar_sha256: String, + pub r4_core_context: String, + pub projection_sha256: String, + pub source_files: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct SourceFileEvidence { + pub path: String, + pub git_blob_sha: String, + pub raw_sha256: String, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Cf10Baseline { + pub deltas: Vec, + pub states: Vec, + pub retained_pr: u64, + pub retained_head: String, + pub retained_base: String, + pub retained_run: u64, + pub retained_run_conclusion: String, + pub retained_artifact_id: u64, + pub retained_artifact_name: String, + pub retained_artifact_sha256: String, + pub retained_manifest_blob_sha: String, + pub retained_manifest_sha256: String, + pub retained_donor_blob_sha: String, + pub retained_donor_sha256: String, + pub projection_sha256: String, +} + +pub struct Cf06Source<'a> { + pub path: &'a str, + pub git_blob_sha: &'a str, + pub bytes: &'a [u8], +} + +pub fn project_authority( + captured_from_main_sha: &str, + captured_from_main_tree: &str, + assurance_ruleset: &Value, + review_ruleset: &Value, + cf06_sources: [Cf06Source<'_>; 3], + retained: RetainedProjection, +) -> Result { + validate_git_sha(captured_from_main_sha, "captured_from_main_sha")?; + validate_git_sha(captured_from_main_tree, "captured_from_main_tree")?; + + let assurance = project_assurance_ruleset(assurance_ruleset)?; + let review_governance = project_review_ruleset(review_ruleset)?; + let cf06 = project_cf06(cf06_sources)?; + let cf10 = project_cf10(retained)?; + + Ok(AuthorityBaseline { + schema: AUTHORITY_BASELINE_SCHEMA.to_owned(), + captured_from_main_sha: captured_from_main_sha.to_owned(), + captured_from_main_tree: captured_from_main_tree.to_owned(), + af01: Af01Baseline { + assurance, + review_governance, + }, + cf06, + cf10, + }) +} + +pub fn project_assurance_ruleset(value: &Value) -> Result { + validate_ruleset_header(value, ASSURANCE_RULESET_ID)?; + let bypass = value + .get("bypass_actors") + .and_then(Value::as_array) + .ok_or_else(|| AuthorityError::Mismatch("assurance bypass_actors missing".to_owned()))?; + if !bypass.is_empty() { + return Err(AuthorityError::Mismatch( + "assurance ruleset must have no bypass actors".to_owned(), + )); + } + + let rules = value + .get("rules") + .and_then(Value::as_array) + .ok_or_else(|| AuthorityError::Mismatch("assurance rules missing".to_owned()))?; + if rules.len() != 3 { + return Err(AuthorityError::Mismatch(format!( + "assurance ruleset must contain exactly three rules, observed {}", + rules.len() + ))); + } + let deletion = exactly_one_rule(rules, "deletion")?; + reject_parameters(deletion, "deletion")?; + let non_fast_forward = exactly_one_rule(rules, "non_fast_forward")?; + reject_parameters(non_fast_forward, "non_fast_forward")?; + let required = exactly_one_rule(rules, "required_status_checks")?; + let parameters = required + .get("parameters") + .and_then(Value::as_object) + .ok_or_else(|| { + AuthorityError::Mismatch("required_status_checks parameters missing".to_owned()) + })?; + if parameters + .get("strict_required_status_checks_policy") + .and_then(Value::as_bool) + != Some(true) + || parameters + .get("do_not_enforce_on_create") + .and_then(Value::as_bool) + != Some(false) + { + return Err(AuthorityError::Mismatch( + "assurance strict required-check policy drifted".to_owned(), + )); + } + let checks = parameters + .get("required_status_checks") + .and_then(Value::as_array) + .ok_or_else(|| AuthorityError::Mismatch("required status checks missing".to_owned()))?; + if checks.len() != 3 { + return Err(AuthorityError::Mismatch(format!( + "required status checks must contain exactly three entries, observed {}", + checks.len() + ))); + } + + let mut normalized = Vec::new(); + for check in checks { + let context = check + .get("context") + .and_then(Value::as_str) + .ok_or_else(|| AuthorityError::Mismatch("required check context missing".to_owned()))?; + let integration_id = check + .get("integration_id") + .and_then(Value::as_u64) + .ok_or_else(|| { + AuthorityError::Mismatch("required check integration_id missing".to_owned()) + })?; + if integration_id != 15368 { + return Err(AuthorityError::Mismatch(format!( + "required check {context} has unexpected integration {integration_id}" + ))); + } + normalized.push((context.to_owned(), integration_id)); + } + normalized.sort(); + let expected = vec![ + ("assurance-proof".to_owned(), 15368), + ("rust".to_owned(), 15368), + ("scorecard".to_owned(), 15368), + ]; + if normalized != expected { + return Err(AuthorityError::Mismatch(format!( + "required check membership drifted: {normalized:?}" + ))); + } + + let semantic = json!({ + "bypass_actors": [], + "deletion": true, + "enforcement": "active", + "non_fast_forward": true, + "ref_name": { + "exclude": [], + "include": ["refs/heads/main"] + }, + "required_status_checks": normalized + .iter() + .map(|(context, integration_id)| json!({ + "context": context, + "integration_id": integration_id + })) + .collect::>(), + "strict_required_status_checks_policy": true + }); + + Ok(RulesetProjection { + ruleset_id: ASSURANCE_RULESET_ID, + semantic_sha256: canonical_sha256(&semantic)?, + }) +} + +pub fn project_review_ruleset(value: &Value) -> Result { + validate_ruleset_header(value, REVIEW_RULESET_ID)?; + let bypass = value + .get("bypass_actors") + .and_then(Value::as_array) + .ok_or_else(|| AuthorityError::Mismatch("review bypass_actors missing".to_owned()))?; + if bypass.len() != 1 { + return Err(AuthorityError::Mismatch(format!( + "review governance must contain one bypass actor, observed {}", + bypass.len() + ))); + } + let actor = &bypass[0]; + if actor.get("actor_id").and_then(Value::as_u64) != Some(5) + || actor.get("actor_type").and_then(Value::as_str) != Some("RepositoryRole") + || actor.get("bypass_mode").and_then(Value::as_str) != Some("pull_request") + { + return Err(AuthorityError::Mismatch( + "review governance bypass actor drifted".to_owned(), + )); + } + + let rules = value + .get("rules") + .and_then(Value::as_array) + .ok_or_else(|| AuthorityError::Mismatch("review rules missing".to_owned()))?; + if rules.len() != 1 { + return Err(AuthorityError::Mismatch(format!( + "review governance must contain exactly one rule, observed {}", + rules.len() + ))); + } + let pull_request = exactly_one_rule(rules, "pull_request")?; + let parameters = pull_request + .get("parameters") + .and_then(Value::as_object) + .ok_or_else(|| AuthorityError::Mismatch("pull_request parameters missing".to_owned()))?; + + expect_u64(parameters, "required_approving_review_count", 1)?; + expect_bool(parameters, "dismiss_stale_reviews_on_push", true)?; + expect_bool(parameters, "require_code_owner_review", true)?; + expect_bool(parameters, "require_last_push_approval", true)?; + expect_bool(parameters, "required_review_thread_resolution", true)?; + expect_bool( + parameters, + "require_extra_approval_for_unattributed_changes", + true, + )?; + let required_reviewers = parameters + .get("required_reviewers") + .and_then(Value::as_array) + .ok_or_else(|| AuthorityError::Mismatch("required_reviewers missing".to_owned()))?; + if !required_reviewers.is_empty() { + return Err(AuthorityError::Mismatch( + "review governance required_reviewers unexpectedly non-empty".to_owned(), + )); + } + let allowed_merge_methods = parameters + .get("allowed_merge_methods") + .and_then(Value::as_array) + .ok_or_else(|| AuthorityError::Mismatch("allowed_merge_methods missing".to_owned()))?; + if allowed_merge_methods.as_slice() != [Value::String("merge".to_owned())] { + return Err(AuthorityError::Mismatch( + "review governance must remain merge-only".to_owned(), + )); + } + + let semantic = json!({ + "allowed_merge_methods": ["merge"], + "bypass_actors": [{ + "actor_id": 5, + "actor_type": "RepositoryRole", + "bypass_mode": "pull_request" + }], + "dismiss_stale_reviews_on_push": true, + "enforcement": "active", + "ref_name": { + "exclude": [], + "include": ["refs/heads/main"] + }, + "require_code_owner_review": true, + "require_extra_approval_for_unattributed_changes": true, + "require_last_push_approval": true, + "required_approving_review_count": 1, + "required_review_thread_resolution": true, + "required_reviewers": [] + }); + + Ok(RulesetProjection { + ruleset_id: REVIEW_RULESET_ID, + semantic_sha256: canonical_sha256(&semantic)?, + }) +} + +pub fn project_cf06(sources: [Cf06Source<'_>; 3]) -> Result { + let expected_paths = [ + "crates/commandf-pkg/src/oracle_model.rs", + "donors/hl7-fhir-validator-6.10.2.yaml", + ".github/workflows/cf06-oracle.yml", + ]; + for (source, expected_path) in sources.iter().zip(expected_paths) { + if source.path != expected_path { + return Err(AuthorityError::Mismatch(format!( + "CF-06 source order/path mismatch: expected {expected_path}, got {}", + source.path + ))); + } + validate_git_sha(source.git_blob_sha, source.path)?; + } + + let oracle = std::str::from_utf8(sources[0].bytes) + .map_err(|_| AuthorityError::Mismatch("oracle_model.rs is not UTF-8".to_owned()))?; + for required in [ + CF06_PROJECT, + CF06_RELEASE, + CF06_SOURCE_COMMIT, + CF06_VALIDATOR_SHA256, + ] { + if !oracle.contains(required) { + return Err(AuthorityError::Mismatch(format!( + "oracle_model.rs does not bind {required}" + ))); + } + } + + let donor = std::str::from_utf8(sources[1].bytes) + .map_err(|_| AuthorityError::Mismatch("CF-06 donor is not UTF-8".to_owned()))?; + for required in [ + "repository: https://github.com/hapifhir/org.hl7.fhir.core", + "tag: 6.10.2", + CF06_SOURCE_COMMIT, + CF06_VALIDATOR_SHA256, + ] { + if !donor.contains(required) { + return Err(AuthorityError::Mismatch(format!( + "CF-06 donor does not bind {required}" + ))); + } + } + + let workflow = std::str::from_utf8(sources[2].bytes) + .map_err(|_| AuthorityError::Mismatch("CF-06 workflow is not UTF-8".to_owned()))?; + for required in [ + "name: cf06-oracle", + CF06_R4_CONTEXT, + "oracle-proof:", + "test \"$SELF_SMOKE_RESULT\" = success", + "test \"$CHANGED_PROFILE_RESULT\" = success", + ] { + if !workflow.contains(required) { + return Err(AuthorityError::Mismatch(format!( + "CF-06 workflow does not bind {required}" + ))); + } + } + + let projection = json!({ + "project": CF06_PROJECT, + "r4_core_context": CF06_R4_CONTEXT, + "release": CF06_RELEASE, + "source_commit": CF06_SOURCE_COMMIT, + "validator_cli_jar_sha256": CF06_VALIDATOR_SHA256 + }); + + Ok(Cf06Baseline { + project: CF06_PROJECT.to_owned(), + release: CF06_RELEASE.to_owned(), + source_commit: CF06_SOURCE_COMMIT.to_owned(), + validator_cli_jar_sha256: CF06_VALIDATOR_SHA256.to_owned(), + r4_core_context: CF06_R4_CONTEXT.to_owned(), + projection_sha256: canonical_sha256(&projection)?, + source_files: sources + .into_iter() + .map(|source| SourceFileEvidence { + path: source.path.to_owned(), + git_blob_sha: source.git_blob_sha.to_owned(), + raw_sha256: sha256_hex(source.bytes), + }) + .collect(), + }) +} + +pub fn project_cf10(retained: RetainedProjection) -> Result { + if retained.retained_run_conclusion != "failure" { + return Err(AuthorityError::Mismatch( + "CF-10 retained run must remain failure".to_owned(), + )); + } + let semantic = serde_json::to_value(&retained)?; + let projection_sha256 = canonical_sha256(&semantic)?; + Ok(Cf10Baseline { + deltas: retained.deltas, + states: retained.states, + retained_pr: retained.retained_pr, + retained_head: retained.retained_head, + retained_base: retained.retained_base, + retained_run: retained.retained_run, + retained_run_conclusion: retained.retained_run_conclusion, + retained_artifact_id: retained.retained_artifact_id, + retained_artifact_name: retained.retained_artifact_name, + retained_artifact_sha256: retained.retained_artifact_sha256, + retained_manifest_blob_sha: retained.retained_manifest_blob_sha, + retained_manifest_sha256: retained.retained_manifest_sha256, + retained_donor_blob_sha: retained.retained_donor_blob_sha, + retained_donor_sha256: retained.retained_donor_sha256, + projection_sha256, + }) +} + +fn validate_ruleset_header(value: &Value, expected_id: u64) -> Result<(), AuthorityError> { + if value.get("id").and_then(Value::as_u64) != Some(expected_id) { + return Err(AuthorityError::Mismatch(format!( + "ruleset id mismatch, expected {expected_id}" + ))); + } + if value.get("enforcement").and_then(Value::as_str) != Some("active") + || value.get("target").and_then(Value::as_str) != Some("branch") + || value.get("source_type").and_then(Value::as_str) != Some("Repository") + || value.get("source").and_then(Value::as_str) != Some("TheHalfMoon/commandF") + { + return Err(AuthorityError::Mismatch(format!( + "ruleset {expected_id} header drifted" + ))); + } + let ref_name = value + .get("conditions") + .and_then(|value| value.get("ref_name")) + .ok_or_else(|| AuthorityError::Mismatch("ruleset ref_name condition missing".to_owned()))?; + let include = ref_name + .get("include") + .and_then(Value::as_array) + .ok_or_else(|| AuthorityError::Mismatch("ruleset include missing".to_owned()))?; + let exclude = ref_name + .get("exclude") + .and_then(Value::as_array) + .ok_or_else(|| AuthorityError::Mismatch("ruleset exclude missing".to_owned()))?; + if include.as_slice() != [Value::String("refs/heads/main".to_owned())] || !exclude.is_empty() { + return Err(AuthorityError::Mismatch(format!( + "ruleset {expected_id} ref scope drifted" + ))); + } + Ok(()) +} + +fn exactly_one_rule<'a>(rules: &'a [Value], kind: &str) -> Result<&'a Value, AuthorityError> { + let matching: Vec<&Value> = rules + .iter() + .filter(|rule| rule.get("type").and_then(Value::as_str) == Some(kind)) + .collect(); + if matching.len() != 1 { + return Err(AuthorityError::Mismatch(format!( + "expected exactly one {kind} rule, observed {}", + matching.len() + ))); + } + Ok(matching[0]) +} + +fn reject_parameters(rule: &Value, kind: &str) -> Result<(), AuthorityError> { + if rule.get("parameters").is_some() { + return Err(AuthorityError::Mismatch(format!( + "{kind} rule unexpectedly has parameters" + ))); + } + Ok(()) +} + +fn expect_bool( + parameters: &serde_json::Map, + field: &str, + expected: bool, +) -> Result<(), AuthorityError> { + if parameters.get(field).and_then(Value::as_bool) != Some(expected) { + return Err(AuthorityError::Mismatch(format!( + "{field} mismatch, expected {expected}" + ))); + } + Ok(()) +} + +fn expect_u64( + parameters: &serde_json::Map, + field: &str, + expected: u64, +) -> Result<(), AuthorityError> { + if parameters.get(field).and_then(Value::as_u64) != Some(expected) { + return Err(AuthorityError::Mismatch(format!( + "{field} mismatch, expected {expected}" + ))); + } + Ok(()) +} + +fn validate_git_sha(value: &str, field: &str) -> Result<(), AuthorityError> { + if value.len() != 40 + || !value + .bytes() + .all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase()) + { + return Err(AuthorityError::Mismatch(format!( + "{field} is not a lowercase 40-hex Git SHA" + ))); + } + Ok(()) +} diff --git a/tools/af02-verifier/src/canonical.rs b/tools/af02-verifier/src/canonical.rs new file mode 100644 index 00000000..8569b621 --- /dev/null +++ b/tools/af02-verifier/src/canonical.rs @@ -0,0 +1,96 @@ +use serde_json::Value; +use sha2::{Digest, Sha256}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum CanonicalError { + #[error("floating-point JSON numbers are prohibited")] + FloatNumber, + #[error("failed to encode JSON string: {0}")] + StringEncoding(#[from] serde_json::Error), +} + +pub fn canonical_json_bytes(value: &Value) -> Result, CanonicalError> { + let mut out = Vec::new(); + write_value(value, &mut out)?; + Ok(out) +} + +pub fn canonical_sha256(value: &Value) -> Result { + Ok(sha256_hex(&canonical_json_bytes(value)?)) +} + +pub fn sha256_hex(bytes: &[u8]) -> String { + let digest = Sha256::digest(bytes); + format!("{digest:x}") +} + +fn write_value(value: &Value, out: &mut Vec) -> Result<(), CanonicalError> { + match value { + Value::Null => out.extend_from_slice(b"null"), + Value::Bool(value) => { + out.extend_from_slice(if *value { b"true" } else { b"false" }); + } + Value::Number(number) => { + if number.is_i64() || number.is_u64() { + out.extend_from_slice(number.to_string().as_bytes()); + } else { + return Err(CanonicalError::FloatNumber); + } + } + Value::String(value) => { + out.extend_from_slice(serde_json::to_string(value)?.as_bytes()); + } + Value::Array(values) => { + out.push(b'['); + for (index, value) in values.iter().enumerate() { + if index != 0 { + out.push(b','); + } + write_value(value, out)?; + } + out.push(b']'); + } + Value::Object(values) => { + out.push(b'{'); + let mut keys: Vec<&String> = values.keys().collect(); + keys.sort_by(|left, right| left.as_bytes().cmp(right.as_bytes())); + for (index, key) in keys.into_iter().enumerate() { + if index != 0 { + out.push(b','); + } + out.extend_from_slice(serde_json::to_string(key)?.as_bytes()); + out.push(b':'); + write_value(&values[key], out)?; + } + out.push(b'}'); + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn canonicalizes_recursive_object_keys_and_preserves_array_order() { + let value: Value = serde_json::from_str( + r#"{"z":{"b":2,"a":1},"a":[{"y":2,"x":1},0]}"#, + ) + .unwrap(); + assert_eq!( + canonical_json_bytes(&value).unwrap(), + br#"{"a":[{"x":1,"y":2},0],"z":{"a":1,"b":2}}"# + ); + } + + #[test] + fn rejects_floats() { + let value: Value = serde_json::from_str(r#"{"n":1.5}"#).unwrap(); + assert!(matches!( + canonical_json_bytes(&value), + Err(CanonicalError::FloatNumber) + )); + } +} diff --git a/tools/af02-verifier/src/lib.rs b/tools/af02-verifier/src/lib.rs new file mode 100644 index 00000000..f96cb263 --- /dev/null +++ b/tools/af02-verifier/src/lib.rs @@ -0,0 +1,3 @@ +pub mod authority; +pub mod canonical; +pub mod retained; diff --git a/tools/af02-verifier/src/main.rs b/tools/af02-verifier/src/main.rs new file mode 100644 index 00000000..18d4892c --- /dev/null +++ b/tools/af02-verifier/src/main.rs @@ -0,0 +1,132 @@ +use std::fs; +use std::path::PathBuf; + +use commandf_af02_verifier::authority::{project_authority, Cf06Source}; +use commandf_af02_verifier::canonical::canonical_json_bytes; +use commandf_af02_verifier::retained::{ + locator_plan, project_retained, validate_and_parse, verify_artifacts, verify_workflow_run, +}; +use serde::Deserialize; +use serde_json::Value; + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct AuthorityInput { + captured_from_main_sha: String, + captured_from_main_tree: String, + assurance_ruleset_path: PathBuf, + review_ruleset_path: PathBuf, + retained_sources_path: PathBuf, + retained_schema_path: PathBuf, + retained_manifest_path: PathBuf, + retained_donor_path: PathBuf, + retained_workflow_run_path: PathBuf, + retained_artifacts_path: PathBuf, + cf06_oracle_model: SourcePath, + cf06_donor: SourcePath, + cf06_workflow: SourcePath, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct SourcePath { + path: String, + git_blob_sha: String, + local_path: PathBuf, +} + +fn main() { + if let Err(error) = run() { + eprintln!("commandf-af02-verifier: {error}"); + std::process::exit(2); + } +} + +fn run() -> Result<(), Box> { + let mut args = std::env::args().skip(1); + let command = args.next().ok_or("missing entrypoint")?; + match command.as_str() { + "project-retained" => { + let retained_path = PathBuf::from(args.next().ok_or("missing retained authority path")?); + let schema_path = PathBuf::from(args.next().ok_or("missing retained schema path")?); + if args.next().is_some() { + return Err("project-retained accepts exactly two paths".into()); + } + let retained_bytes = fs::read(retained_path)?; + let schema_bytes = fs::read(schema_path)?; + let retained = validate_and_parse(&retained_bytes, &schema_bytes)?; + let plan = locator_plan(&retained)?; + let value = serde_json::to_value(plan)?; + std::io::Write::write_all( + &mut std::io::stdout().lock(), + &canonical_json_bytes(&value)?, + )?; + } + "project-authority" => { + let input_path = PathBuf::from(args.next().ok_or("missing authority input path")?); + if args.next().is_some() { + return Err("project-authority accepts exactly one input path".into()); + } + let input: AuthorityInput = serde_json::from_slice(&fs::read(input_path)?)?; + let retained_bytes = fs::read(&input.retained_sources_path)?; + let retained_schema_bytes = fs::read(&input.retained_schema_path)?; + let retained = validate_and_parse(&retained_bytes, &retained_schema_bytes)?; + + let run: Value = serde_json::from_slice(&fs::read(&input.retained_workflow_run_path)?)?; + verify_workflow_run(&retained, &run)?; + let artifacts: Value = + serde_json::from_slice(&fs::read(&input.retained_artifacts_path)?)?; + verify_artifacts(&retained, &artifacts)?; + + let retained_projection = project_retained( + &retained, + &fs::read(&input.retained_manifest_path)?, + &fs::read(&input.retained_donor_path)?, + )?; + let assurance: Value = + serde_json::from_slice(&fs::read(&input.assurance_ruleset_path)?)?; + let review: Value = serde_json::from_slice(&fs::read(&input.review_ruleset_path)?)?; + + let oracle_model = fs::read(&input.cf06_oracle_model.local_path)?; + let donor = fs::read(&input.cf06_donor.local_path)?; + let workflow = fs::read(&input.cf06_workflow.local_path)?; + let baseline = project_authority( + &input.captured_from_main_sha, + &input.captured_from_main_tree, + &assurance, + &review, + [ + Cf06Source { + path: &input.cf06_oracle_model.path, + git_blob_sha: &input.cf06_oracle_model.git_blob_sha, + bytes: &oracle_model, + }, + Cf06Source { + path: &input.cf06_donor.path, + git_blob_sha: &input.cf06_donor.git_blob_sha, + bytes: &donor, + }, + Cf06Source { + path: &input.cf06_workflow.path, + git_blob_sha: &input.cf06_workflow.git_blob_sha, + bytes: &workflow, + }, + ], + retained_projection, + )?; + let value = serde_json::to_value(baseline)?; + std::io::Write::write_all( + &mut std::io::stdout().lock(), + &canonical_json_bytes(&value)?, + )?; + } + "verify-pr" => { + return Err( + "verify-pr is fail-closed until AF-02 T021-T025 semantic/input/base-gate enforcement is canonical" + .into(), + ); + } + other => return Err(format!("unknown entrypoint {other}").into()), + } + Ok(()) +} diff --git a/tools/af02-verifier/src/retained.rs b/tools/af02-verifier/src/retained.rs new file mode 100644 index 00000000..fa5f3db0 --- /dev/null +++ b/tools/af02-verifier/src/retained.rs @@ -0,0 +1,595 @@ +use std::collections::BTreeSet; + +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use thiserror::Error; + +use crate::canonical::{sha256_hex, CanonicalError}; + +pub const RETAINED_SCHEMA_ID: &str = "commandf.af02-retained-authority-sources/v1"; + +#[derive(Debug, Error)] +pub enum RetainedError { + #[error("invalid JSON: {0}")] + Json(#[from] serde_json::Error), + #[error("canonicalization failed: {0}")] + Canonical(#[from] CanonicalError), + #[error("schema contract violation at {path}: {message}")] + Schema { path: String, message: String }, + #[error("retained authority mismatch: {0}")] + Mismatch(String), +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct RetainedAuthoritySources { + pub schema: String, + pub repository: RepositoryIdentity, + pub planning_base: PlanningBase, + pub cf10: RetainedCf10, + pub reconstruction: ReconstructionPolicy, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct RepositoryIdentity { + pub owner: String, + pub name: String, + pub full_name: String, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct PlanningBase { + pub sha: String, + pub tree: String, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct RetainedCf10 { + pub pull_request: PullRequestIdentity, + pub retained_head: String, + pub retained_base: String, + pub manifest: RetainedFile, + pub donor: RetainedFile, + pub workflow_run: WorkflowRunIdentity, + pub artifact: ArtifactIdentity, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct PullRequestIdentity { + pub number: u64, + pub node_id_numeric: u64, + pub head_ref: String, + pub base_ref: String, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct RetainedFile { + pub path: String, + pub git_blob_sha: String, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct WorkflowRunIdentity { + pub id: u64, + pub name: String, + pub path: String, + pub event: String, + pub workflow_id: u64, + pub run_number: u64, + pub run_attempt: u64, + pub check_suite_id: u64, + pub head_sha: String, + pub base_sha: String, + pub conclusion: String, + pub pull_request_number: u64, + pub pull_request_head_sha: String, + pub pull_request_base_sha: String, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ArtifactIdentity { + pub id: u64, + pub name: String, + pub sha256: String, + pub workflow_run_id: u64, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ReconstructionPolicy { + pub supplied_urls_are_authority: bool, + pub api_urls_reconstructed_from_structured_fields: bool, + pub git_blob_verified_before_parse: bool, + pub raw_sha256_computed_after_git_identity: bool, + pub retained_failure_preserved: bool, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct LocatorPlan { + pub pull_request: String, + pub retained_head_commit: String, + pub retained_base_commit: String, + pub manifest_contents: String, + pub manifest_blob: String, + pub donor_contents: String, + pub donor_blob: String, + pub workflow_run: String, + pub workflow_run_artifacts: String, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct Delta { + pub id: String, + pub package: String, + pub before: String, + pub after: String, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct State { + pub state_id: String, + pub case_id: String, + pub side: String, + pub package: String, + pub version: String, + pub archive_sha256: String, + pub archive_bytes: u64, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct RetainedProjection { + pub deltas: Vec, + pub states: Vec, + pub retained_pr: u64, + pub retained_head: String, + pub retained_base: String, + pub retained_run: u64, + pub retained_run_conclusion: String, + pub retained_artifact_id: u64, + pub retained_artifact_name: String, + pub retained_artifact_sha256: String, + pub retained_manifest_blob_sha: String, + pub retained_manifest_sha256: String, + pub retained_donor_blob_sha: String, + pub retained_donor_sha256: String, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct CorpusManifest { + schema: u64, + selection_policy: String, + cases: Vec, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct CorpusCase { + id: String, + package: String, + before: CorpusSide, + after: CorpusSide, + fhir_version: String, + publisher: String, + change_evidence_url: String, + rights_evidence_url: String, + rights_mode: String, + oracle_mode: String, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct CorpusSide { + version: String, + archive_sha256: String, + archive_bytes: u64, + publication_url: String, +} + +pub fn validate_and_parse( + instance_bytes: &[u8], + schema_bytes: &[u8], +) -> Result { + let instance: Value = serde_json::from_slice(instance_bytes)?; + let schema: Value = serde_json::from_slice(schema_bytes)?; + let schema_id = schema + .get("$id") + .and_then(Value::as_str) + .ok_or_else(|| RetainedError::Schema { + path: "$".to_owned(), + message: "trusted schema is missing $id".to_owned(), + })?; + if schema_id != "https://commandf.dev/schemas/af02-retained-authority-sources-v1.schema.json" { + return Err(RetainedError::Schema { + path: "$".to_owned(), + message: format!("unexpected trusted schema id {schema_id}"), + }); + } + validate_schema_node(&instance, &schema, "$")?; + let parsed: RetainedAuthoritySources = serde_json::from_value(instance)?; + if parsed.schema != RETAINED_SCHEMA_ID { + return Err(RetainedError::Mismatch(format!( + "unexpected retained schema {}", + parsed.schema + ))); + } + Ok(parsed) +} + +pub fn locator_plan(retained: &RetainedAuthoritySources) -> Result { + if retained.repository.full_name + != format!("{}/{}", retained.repository.owner, retained.repository.name) + { + return Err(RetainedError::Mismatch( + "repository full_name does not match owner/name".to_owned(), + )); + } + if retained.reconstruction.supplied_urls_are_authority + || !retained + .reconstruction + .api_urls_reconstructed_from_structured_fields + || !retained.reconstruction.git_blob_verified_before_parse + || !retained.reconstruction.raw_sha256_computed_after_git_identity + || !retained.reconstruction.retained_failure_preserved + { + return Err(RetainedError::Mismatch( + "reconstruction policy weakens the closed retained-authority contract".to_owned(), + )); + } + + let repo = &retained.repository.full_name; + let cf10 = &retained.cf10; + Ok(LocatorPlan { + pull_request: format!("https://api.github.com/repos/{repo}/pulls/{}", cf10.pull_request.number), + retained_head_commit: format!( + "https://api.github.com/repos/{repo}/commits/{}", + cf10.retained_head + ), + retained_base_commit: format!( + "https://api.github.com/repos/{repo}/commits/{}", + cf10.retained_base + ), + manifest_contents: format!( + "https://api.github.com/repos/{repo}/contents/{}?ref={}", + cf10.manifest.path, cf10.retained_head + ), + manifest_blob: format!( + "https://api.github.com/repos/{repo}/git/blobs/{}", + cf10.manifest.git_blob_sha + ), + donor_contents: format!( + "https://api.github.com/repos/{repo}/contents/{}?ref={}", + cf10.donor.path, cf10.retained_head + ), + donor_blob: format!( + "https://api.github.com/repos/{repo}/git/blobs/{}", + cf10.donor.git_blob_sha + ), + workflow_run: format!( + "https://api.github.com/repos/{repo}/actions/runs/{}", + cf10.workflow_run.id + ), + workflow_run_artifacts: format!( + "https://api.github.com/repos/{repo}/actions/runs/{}/artifacts", + cf10.workflow_run.id + ), + }) +} + +pub fn verify_workflow_run( + retained: &RetainedAuthoritySources, + run: &Value, +) -> Result<(), RetainedError> { + let expected = &retained.cf10.workflow_run; + expect_u64(run, "id", expected.id)?; + expect_str(run, "name", &expected.name)?; + expect_str(run, "path", &expected.path)?; + expect_str(run, "event", &expected.event)?; + expect_u64(run, "workflow_id", expected.workflow_id)?; + expect_u64(run, "run_number", expected.run_number)?; + expect_u64(run, "run_attempt", expected.run_attempt)?; + expect_u64(run, "check_suite_id", expected.check_suite_id)?; + expect_str(run, "head_sha", &expected.head_sha)?; + expect_str(run, "conclusion", &expected.conclusion)?; + + let pull_requests = run + .get("pull_requests") + .and_then(Value::as_array) + .ok_or_else(|| RetainedError::Mismatch("workflow run pull_requests is missing".to_owned()))?; + let matching: Vec<&Value> = pull_requests + .iter() + .filter(|item| item.get("number").and_then(Value::as_u64) == Some(expected.pull_request_number)) + .collect(); + if matching.len() != 1 { + return Err(RetainedError::Mismatch(format!( + "workflow run must bind exactly one PR {}, observed {}", + expected.pull_request_number, + matching.len() + ))); + } + let pr = matching[0]; + let head = pr + .get("head") + .ok_or_else(|| RetainedError::Mismatch("workflow run PR head is missing".to_owned()))?; + let base = pr + .get("base") + .ok_or_else(|| RetainedError::Mismatch("workflow run PR base is missing".to_owned()))?; + expect_str(head, "ref", &retained.cf10.pull_request.head_ref)?; + expect_str(head, "sha", &expected.pull_request_head_sha)?; + expect_str(base, "ref", &retained.cf10.pull_request.base_ref)?; + expect_str(base, "sha", &expected.pull_request_base_sha)?; + + if expected.head_sha != expected.pull_request_head_sha + || expected.base_sha != expected.pull_request_base_sha + || expected.head_sha != retained.cf10.retained_head + || expected.base_sha != retained.cf10.retained_base + { + return Err(RetainedError::Mismatch( + "retained run/head/base cross-binding is inconsistent".to_owned(), + )); + } + Ok(()) +} + +pub fn verify_artifacts( + retained: &RetainedAuthoritySources, + artifacts: &Value, +) -> Result<(), RetainedError> { + let items = artifacts + .get("artifacts") + .and_then(Value::as_array) + .ok_or_else(|| RetainedError::Mismatch("artifact collection is missing artifacts".to_owned()))?; + let expected = &retained.cf10.artifact; + let matching: Vec<&Value> = items + .iter() + .filter(|item| item.get("id").and_then(Value::as_u64) == Some(expected.id)) + .collect(); + if matching.len() != 1 { + return Err(RetainedError::Mismatch(format!( + "expected exactly one artifact {}, observed {}", + expected.id, + matching.len() + ))); + } + let artifact = matching[0]; + expect_str(artifact, "name", &expected.name)?; + let digest = artifact + .get("digest") + .and_then(Value::as_str) + .ok_or_else(|| RetainedError::Mismatch("artifact digest is missing".to_owned()))?; + if digest != format!("sha256:{}", expected.sha256) { + return Err(RetainedError::Mismatch(format!( + "artifact digest mismatch: expected sha256:{}, got {digest}", + expected.sha256 + ))); + } + let run = artifact + .get("workflow_run") + .ok_or_else(|| RetainedError::Mismatch("artifact workflow_run is missing".to_owned()))?; + expect_u64(run, "id", expected.workflow_run_id)?; + expect_str(run, "head_sha", &retained.cf10.retained_head)?; + expect_str(run, "head_branch", &retained.cf10.pull_request.head_ref)?; + Ok(()) +} + +pub fn project_retained( + retained: &RetainedAuthoritySources, + manifest_bytes: &[u8], + donor_bytes: &[u8], +) -> Result { + let manifest: CorpusManifest = serde_json::from_slice(manifest_bytes)?; + if manifest.schema != 1 || manifest.selection_policy != "frozen_pre_result_v1" { + return Err(RetainedError::Mismatch( + "retained corpus manifest schema/selection policy drifted".to_owned(), + )); + } + if manifest.cases.len() != 3 { + return Err(RetainedError::Mismatch(format!( + "retained corpus must contain exactly three cases, observed {}", + manifest.cases.len() + ))); + } + + let expected_ids = ["C001", "C002", "C003"]; + for (case, expected_id) in manifest.cases.iter().zip(expected_ids) { + if case.id != expected_id { + return Err(RetainedError::Mismatch(format!( + "retained corpus order/id mismatch: expected {expected_id}, got {}", + case.id + ))); + } + validate_metadata_only_case(case)?; + } + + let mut deltas = Vec::with_capacity(3); + let mut states = Vec::with_capacity(6); + for case in &manifest.cases { + deltas.push(Delta { + id: case.id.clone(), + package: case.package.clone(), + before: case.before.version.clone(), + after: case.after.version.clone(), + }); + states.push(State { + state_id: format!("{}-after", case.id), + case_id: case.id.clone(), + side: "after".to_owned(), + package: case.package.clone(), + version: case.after.version.clone(), + archive_sha256: case.after.archive_sha256.clone(), + archive_bytes: case.after.archive_bytes, + }); + states.push(State { + state_id: format!("{}-before", case.id), + case_id: case.id.clone(), + side: "before".to_owned(), + package: case.package.clone(), + version: case.before.version.clone(), + archive_sha256: case.before.archive_sha256.clone(), + archive_bytes: case.before.archive_bytes, + }); + } + + Ok(RetainedProjection { + deltas, + states, + retained_pr: retained.cf10.pull_request.number, + retained_head: retained.cf10.retained_head.clone(), + retained_base: retained.cf10.retained_base.clone(), + retained_run: retained.cf10.workflow_run.id, + retained_run_conclusion: retained.cf10.workflow_run.conclusion.clone(), + retained_artifact_id: retained.cf10.artifact.id, + retained_artifact_name: retained.cf10.artifact.name.clone(), + retained_artifact_sha256: retained.cf10.artifact.sha256.clone(), + retained_manifest_blob_sha: retained.cf10.manifest.git_blob_sha.clone(), + retained_manifest_sha256: sha256_hex(manifest_bytes), + retained_donor_blob_sha: retained.cf10.donor.git_blob_sha.clone(), + retained_donor_sha256: sha256_hex(donor_bytes), + }) +} + +fn validate_metadata_only_case(case: &CorpusCase) -> Result<(), RetainedError> { + let allowed = [ + ("C001", "hl7.fhir.us.core", "8.0.1", "9.0.0"), + ("C002", "hl7.fhir.uv.ips", "1.1.0", "2.0.1"), + ("C003", "hl7.fhir.us.mcode", "3.0.0", "4.0.0"), + ]; + let expected = allowed + .iter() + .find(|entry| entry.0 == case.id) + .ok_or_else(|| RetainedError::Mismatch(format!("unknown retained case {}", case.id)))?; + if case.package != expected.1 + || case.before.version != expected.2 + || case.after.version != expected.3 + || case.fhir_version != "4.0.1" + || case.publisher != "HL7 International" + || case.rights_mode != "metadata_only_no_redistribution" + || case.oracle_mode != "changed_structure_definitions_only" + { + return Err(RetainedError::Mismatch(format!( + "retained case {} semantic identity drifted", + case.id + ))); + } + for value in [ + &case.before.publication_url, + &case.after.publication_url, + &case.change_evidence_url, + &case.rights_evidence_url, + ] { + if !value.starts_with("https://hl7.org/") { + return Err(RetainedError::Mismatch(format!( + "retained case {} contains unexpected publication authority", + case.id + ))); + } + } + Ok(()) +} + +fn validate_schema_node(instance: &Value, schema: &Value, path: &str) -> Result<(), RetainedError> { + if let Some(expected) = schema.get("const") { + if instance != expected { + return Err(RetainedError::Schema { + path: path.to_owned(), + message: "value does not equal trusted const".to_owned(), + }); + } + return Ok(()); + } + + if let Some(kind) = schema.get("type").and_then(Value::as_str) { + let matches = match kind { + "object" => instance.is_object(), + "array" => instance.is_array(), + "string" => instance.is_string(), + "integer" => instance.as_i64().is_some() || instance.as_u64().is_some(), + "boolean" => instance.is_boolean(), + "null" => instance.is_null(), + other => { + return Err(RetainedError::Schema { + path: path.to_owned(), + message: format!("unsupported trusted schema type {other}"), + }); + } + }; + if !matches { + return Err(RetainedError::Schema { + path: path.to_owned(), + message: format!("expected type {kind}"), + }); + } + } + + if let Some(object) = instance.as_object() { + let properties = schema + .get("properties") + .and_then(Value::as_object) + .cloned() + .unwrap_or_default(); + if schema.get("additionalProperties") == Some(&Value::Bool(false)) { + let allowed: BTreeSet<&str> = properties.keys().map(String::as_str).collect(); + for key in object.keys() { + if !allowed.contains(key.as_str()) { + return Err(RetainedError::Schema { + path: format!("{path}.{key}"), + message: "unknown field".to_owned(), + }); + } + } + } + if let Some(required) = schema.get("required").and_then(Value::as_array) { + for key in required { + let key = key.as_str().ok_or_else(|| RetainedError::Schema { + path: path.to_owned(), + message: "trusted schema required entry is not a string".to_owned(), + })?; + if !object.contains_key(key) { + return Err(RetainedError::Schema { + path: format!("{path}.{key}"), + message: "required field is missing".to_owned(), + }); + } + } + } + for (key, child) in object { + if let Some(child_schema) = properties.get(key) { + validate_schema_node(child, child_schema, &format!("{path}.{key}"))?; + } + } + } + Ok(()) +} + +fn expect_str(value: &Value, field: &str, expected: &str) -> Result<(), RetainedError> { + let observed = value + .get(field) + .and_then(Value::as_str) + .ok_or_else(|| RetainedError::Mismatch(format!("{field} is missing or not a string")))?; + if observed != expected { + return Err(RetainedError::Mismatch(format!( + "{field} mismatch: expected {expected}, got {observed}" + ))); + } + Ok(()) +} + +fn expect_u64(value: &Value, field: &str, expected: u64) -> Result<(), RetainedError> { + let observed = value + .get(field) + .and_then(Value::as_u64) + .ok_or_else(|| RetainedError::Mismatch(format!("{field} is missing or not an integer")))?; + if observed != expected { + return Err(RetainedError::Mismatch(format!( + "{field} mismatch: expected {expected}, got {observed}" + ))); + } + Ok(()) +} diff --git a/tools/af02-verifier/tests/fixtures/README.md b/tools/af02-verifier/tests/fixtures/README.md new file mode 100644 index 00000000..9b5714a8 --- /dev/null +++ b/tools/af02-verifier/tests/fixtures/README.md @@ -0,0 +1,3 @@ +# AF-02 authority reconstruction fixtures + +These fixtures are test-only copies/projections of canonical or retained authority inputs. They do not establish authority. Runtime authority is reconstructed by the base-controlled verifier from GitHub/canonical-base identities and raw bytes. diff --git a/tools/af02-verifier/tests/fixtures/assurance-ruleset.json b/tools/af02-verifier/tests/fixtures/assurance-ruleset.json new file mode 100644 index 00000000..d5485a85 --- /dev/null +++ b/tools/af02-verifier/tests/fixtures/assurance-ruleset.json @@ -0,0 +1,31 @@ +{ + "id": 21652953, + "name": "commandF main assurance", + "target": "branch", + "source_type": "Repository", + "source": "TheHalfMoon/commandF", + "enforcement": "active", + "conditions": { + "ref_name": { + "exclude": [], + "include": ["refs/heads/main"] + } + }, + "rules": [ + {"type": "deletion"}, + {"type": "non_fast_forward"}, + { + "type": "required_status_checks", + "parameters": { + "strict_required_status_checks_policy": true, + "do_not_enforce_on_create": false, + "required_status_checks": [ + {"context": "rust", "integration_id": 15368}, + {"context": "assurance-proof", "integration_id": 15368}, + {"context": "scorecard", "integration_id": 15368} + ] + } + } + ], + "bypass_actors": [] +} diff --git a/tools/af02-verifier/tests/fixtures/cf10-artifacts.json b/tools/af02-verifier/tests/fixtures/cf10-artifacts.json new file mode 100644 index 00000000..f18cddfc --- /dev/null +++ b/tools/af02-verifier/tests/fixtures/cf10-artifacts.json @@ -0,0 +1,16 @@ +{ + "total_count": 1, + "artifacts": [ + { + "id": 9255732702, + "name": "cf10-real-corpus-evidence", + "expired": true, + "digest": "sha256:9fdde985bb5abbe53ec2bce2dadc5f65c95557f8848c9af68755fc81a45af612", + "workflow_run": { + "id": 31916124080, + "head_branch": "feat/cf-10-real-ig-delta-corpus", + "head_sha": "5fe10d9859407272acf6649fc3e868d3eb2fbd12" + } + } + ] +} diff --git a/tools/af02-verifier/tests/fixtures/cf10-corpus.json b/tools/af02-verifier/tests/fixtures/cf10-corpus.json new file mode 100644 index 00000000..655949a8 --- /dev/null +++ b/tools/af02-verifier/tests/fixtures/cf10-corpus.json @@ -0,0 +1,72 @@ +{ + "schema": 1, + "selection_policy": "frozen_pre_result_v1", + "cases": [ + { + "id": "C001", + "package": "hl7.fhir.us.core", + "before": { + "version": "8.0.1", + "archive_sha256": "3c02eef48ef10617021bee95e58cbc66d596ceda8cada24b72000d33ad67c464", + "archive_bytes": 2713046, + "publication_url": "https://hl7.org/fhir/us/core/STU8.0.1/" + }, + "after": { + "version": "9.0.0", + "archive_sha256": "d7b54d2ec2a48cea94ffea5d939ad67a681f80b94d69594a08cebac36da9e059", + "archive_bytes": 2749959, + "publication_url": "https://hl7.org/fhir/us/core/STU9/" + }, + "fhir_version": "4.0.1", + "publisher": "HL7 International", + "change_evidence_url": "https://hl7.org/fhir/us/core/STU9/changes.html", + "rights_evidence_url": "https://hl7.org/fhir/us/core/STU9/ImplementationGuide-hl7.fhir.us.core.html", + "rights_mode": "metadata_only_no_redistribution", + "oracle_mode": "changed_structure_definitions_only" + }, + { + "id": "C002", + "package": "hl7.fhir.uv.ips", + "before": { + "version": "1.1.0", + "archive_sha256": "403c4141101810e924f2928287985084819d8a5cc3a62e2b3840a557129840ef", + "archive_bytes": 1065103, + "publication_url": "https://hl7.org/fhir/uv/ips/STU1.1/" + }, + "after": { + "version": "2.0.1", + "archive_sha256": "7183242b70fb2a9058aa3701fb607517a3c2fd0e3100d1d8c538d744c2adf799", + "archive_bytes": 725312, + "publication_url": "https://hl7.org/fhir/uv/ips/en/" + }, + "fhir_version": "4.0.1", + "publisher": "HL7 International", + "change_evidence_url": "https://hl7.org/fhir/uv/ips/en/changes.html", + "rights_evidence_url": "https://hl7.org/fhir/uv/ips/en/terminology.html", + "rights_mode": "metadata_only_no_redistribution", + "oracle_mode": "changed_structure_definitions_only" + }, + { + "id": "C003", + "package": "hl7.fhir.us.mcode", + "before": { + "version": "3.0.0", + "archive_sha256": "c94c91971747efeae760aa037d168e4df992cefb6dacece08217c464b9d39214", + "archive_bytes": 1014084, + "publication_url": "https://hl7.org/fhir/us/mcode/STU3/" + }, + "after": { + "version": "4.0.0", + "archive_sha256": "e603283bafa508a3705ad022bce95bba1fbd0b8b3b87b978e7412813b7bc1778", + "archive_bytes": 1003918, + "publication_url": "https://hl7.org/fhir/us/mcode/STU4/" + }, + "fhir_version": "4.0.1", + "publisher": "HL7 International", + "change_evidence_url": "https://hl7.org/fhir/us/mcode/STU4/changes.html", + "rights_evidence_url": "https://hl7.org/fhir/us/mcode/STU4/terminology.html", + "rights_mode": "metadata_only_no_redistribution", + "oracle_mode": "changed_structure_definitions_only" + } + ] +} diff --git a/tools/af02-verifier/tests/fixtures/cf10-donor.yaml b/tools/af02-verifier/tests/fixtures/cf10-donor.yaml new file mode 100644 index 00000000..566b46f4 --- /dev/null +++ b/tools/af02-verifier/tests/fixtures/cf10-donor.yaml @@ -0,0 +1,131 @@ +schema: commandf.donor-manifest/v1 +updated: 2026-08-15 + +discovery_evidence: + head: d8cb88ba2bb3696f44322eb302957c8c50e1c8f4 + workflow: cf10-digest-discovery + run: 31890014888 + artifact_id: 9248341586 + artifact_digest: sha256:02f08bced4c30665a40c7967611057753bf56330a5e7e10e85081619b433dd8c + independent_resolutions_per_state: 2 + selection_frozen_before_results: true + eligibility_sweep_complete: true + eligible_states: 6 + selected_states: 6 + +sources: + - id: us-core-real-delta + project: HL7 US Core Implementation Guide + package: hl7.fhir.us.core + before_version: 8.0.1 + after_version: 9.0.0 + before_attestation: + sha256: 3c02eef48ef10617021bee95e58cbc66d596ceda8cada24b72000d33ad67c464 + archive_bytes: 2713046 + byte_identical_across_independent_resolutions: true + after_attestation: + sha256: d7b54d2ec2a48cea94ffea5d939ad67a681f80b94d69594a08cebac36da9e059 + archive_bytes: 2749959 + byte_identical_across_independent_resolutions: true + before_publication: https://hl7.org/fhir/us/core/STU8.0.1/ + after_publication: https://hl7.org/fhir/us/core/STU9/ + change_evidence: https://hl7.org/fhir/us/core/STU9/changes.html + rights_evidence: + - https://hl7.org/fhir/us/core/STU8.0.1/ImplementationGuide-hl7.fhir.us.core.html + - https://hl7.org/fhir/us/core/STU9/ImplementationGuide-hl7.fhir.us.core.html + fhir_version: 4.0.1 + mode: [PUBLIC_RUNTIME_INPUT, BENCHMARK_METADATA] + redistribution: NONE + rights_note: >- + Publication metadata identifies HL7 copyright/legal Creative Commons terms. CF-10 does not + vendor or redistribute the NPM package or its terminology/content; package bytes are ephemeral + public runtime inputs and retain all upstream rights/terms. + adopted_patterns: + - published exact package/version identity + - public change-history evidence + - runtime digest attestation through commandF + exclusions: + - no NPM archive copied into the repository + - no examples or terminology expansions copied into the repository + - no ballot/CI build selected for corpus v1 + + - id: ips-real-delta + project: HL7 International Patient Summary Implementation Guide + package: hl7.fhir.uv.ips + before_version: 1.1.0 + after_version: 2.0.1 + before_attestation: + sha256: 403c4141101810e924f2928287985084819d8a5cc3a62e2b3840a557129840ef + archive_bytes: 1065103 + byte_identical_across_independent_resolutions: true + after_attestation: + sha256: 7183242b70fb2a9058aa3701fb607517a3c2fd0e3100d1d8c538d744c2adf799 + archive_bytes: 725312 + byte_identical_across_independent_resolutions: true + before_publication: https://hl7.org/fhir/uv/ips/STU1.1/ + after_publication: https://hl7.org/fhir/uv/ips/en/ + change_evidence: https://hl7.org/fhir/uv/ips/en/changes.html + rights_evidence: + - https://hl7.org/fhir/uv/ips/STU1.1/terminology.html + - https://hl7.org/fhir/uv/ips/en/terminology.html + fhir_version: 4.0.1 + mode: [PUBLIC_RUNTIME_INPUT, BENCHMARK_METADATA] + redistribution: NONE + rights_note: >- + IPS includes mixed upstream IP/terminology statements, including SNOMED CT and other code-system + rights. CF-10 intentionally records metadata only and does not redistribute package content, + terminology expansions, examples, or imply that commandF's repository license grants rights to + those upstream materials. + adopted_patterns: + - published exact package/version identity + - explicit upstream non-compatible/compatible change evidence + - runtime digest attestation through commandF + exclusions: + - no NPM archive copied into the repository + - no SNOMED CT/RxNorm/ISO/EDQM payload copied into the repository + - no ballot/continuous-build package selected for corpus v1 + + - id: mcode-real-delta + project: HL7 minimal Common Oncology Data Elements (mCODE) Implementation Guide + package: hl7.fhir.us.mcode + before_version: 3.0.0 + after_version: 4.0.0 + before_attestation: + sha256: c94c91971747efeae760aa037d168e4df992cefb6dacece08217c464b9d39214 + archive_bytes: 1014084 + byte_identical_across_independent_resolutions: true + after_attestation: + sha256: e603283bafa508a3705ad022bce95bba1fbd0b8b3b87b978e7412813b7bc1778 + archive_bytes: 1003918 + byte_identical_across_independent_resolutions: true + before_publication: https://hl7.org/fhir/us/mcode/STU3/ + after_publication: https://hl7.org/fhir/us/mcode/STU4/ + change_evidence: https://hl7.org/fhir/us/mcode/STU4/changes.html + rights_evidence: + - https://hl7.org/fhir/us/mcode/STU3/downloads.html + - https://hl7.org/fhir/us/mcode/STU4/downloads.html + - https://hl7.org/fhir/us/mcode/STU4/terminology.html + fhir_version: 4.0.1 + mode: [PUBLIC_RUNTIME_INPUT, BENCHMARK_METADATA] + redistribution: NONE + rights_note: >- + The publication records HL7/Creative Commons-publication terms while individual terminology + artifacts may carry separate upstream terms such as SNOMED CT. CF-10 does not redistribute + package or terminology content and retains those upstream obligations. + adopted_patterns: + - published exact package/version identity + - specialty-oncology version-delta evidence + - runtime digest attestation through commandF + exclusions: + - no NPM archive copied into the repository + - no terminology expansion copied into the repository + - no license simplification from mixed artifact-level IP statements + +rules: + - corpus v1 selection criteria and version pairs are frozen before commandF result discovery + - selected cases cannot be removed because commandF reports an unfavorable or divergent result + - exact package digests and byte sizes must come from two independent clean commandF resolutions + - digest metadata is adopted only after byte-for-byte equality and CF-01 verification + - package bytes remain ephemeral and never enter git history + - repository license must not be presented as relicensing upstream IG or terminology content + - a rights/access failure may block a case but must be documented rather than silently substituted diff --git a/tools/af02-verifier/tests/fixtures/cf10-run.json b/tools/af02-verifier/tests/fixtures/cf10-run.json new file mode 100644 index 00000000..82be7135 --- /dev/null +++ b/tools/af02-verifier/tests/fixtures/cf10-run.json @@ -0,0 +1,25 @@ +{ + "id": 31916124080, + "name": "cf10-real-corpus", + "path": ".github/workflows/cf10-real-corpus.yml", + "event": "pull_request", + "workflow_id": 335093310, + "run_number": 45, + "run_attempt": 1, + "check_suite_id": 86559814682, + "head_sha": "5fe10d9859407272acf6649fc3e868d3eb2fbd12", + "conclusion": "failure", + "pull_requests": [ + { + "number": 11, + "head": { + "ref": "feat/cf-10-real-ig-delta-corpus", + "sha": "5fe10d9859407272acf6649fc3e868d3eb2fbd12" + }, + "base": { + "ref": "main", + "sha": "5cb1a4c3445c0ebd86654cfb467a5e008e801c3e" + } + } + ] +} diff --git a/tools/af02-verifier/tests/fixtures/review-ruleset.json b/tools/af02-verifier/tests/fixtures/review-ruleset.json new file mode 100644 index 00000000..98a04038 --- /dev/null +++ b/tools/af02-verifier/tests/fixtures/review-ruleset.json @@ -0,0 +1,32 @@ +{ + "id": 21652974, + "name": "commandF main review governance", + "target": "branch", + "source_type": "Repository", + "source": "TheHalfMoon/commandF", + "enforcement": "active", + "conditions": { + "ref_name": { + "exclude": [], + "include": ["refs/heads/main"] + } + }, + "rules": [ + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 1, + "dismiss_stale_reviews_on_push": true, + "required_reviewers": [], + "require_code_owner_review": true, + "require_last_push_approval": true, + "required_review_thread_resolution": true, + "require_extra_approval_for_unattributed_changes": true, + "allowed_merge_methods": ["merge"] + } + } + ], + "bypass_actors": [ + {"actor_id": 5, "actor_type": "RepositoryRole", "bypass_mode": "pull_request"} + ] +} From 8e4d7162d6f27e941bd9183c798798f7e03ce44b Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 12:04:04 +0300 Subject: [PATCH 02/37] style(af02): apply verifier rustfmt --- .../tests/af02_authority_reconstruction.rs | 15 ++++---- tools/af02-verifier/src/canonical.rs | 6 ++-- tools/af02-verifier/src/retained.rs | 36 ++++++++++++------- 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/crates/commandf-pkg/tests/af02_authority_reconstruction.rs b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs index 4892ac6c..ad8c5b45 100644 --- a/crates/commandf-pkg/tests/af02_authority_reconstruction.rs +++ b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs @@ -1,9 +1,9 @@ +#[path = "../../../tools/af02-verifier/src/authority.rs"] +mod authority; #[path = "../../../tools/af02-verifier/src/canonical.rs"] mod canonical; #[path = "../../../tools/af02-verifier/src/retained.rs"] mod retained; -#[path = "../../../tools/af02-verifier/src/authority.rs"] -mod authority; use std::fs; use std::path::PathBuf; @@ -79,10 +79,10 @@ fn build_baseline() -> authority::AuthorityBaseline { #[test] fn retained_schema_rejects_candidate_url_authority() { let mut value: Value = serde_json::from_slice(RETAINED_SOURCES).unwrap(); - value - .as_object_mut() - .unwrap() - .insert("url".to_owned(), Value::String("https://example.invalid".to_owned())); + value.as_object_mut().unwrap().insert( + "url".to_owned(), + Value::String("https://example.invalid".to_owned()), + ); let bytes = serde_json::to_vec(&value).unwrap(); let error = validate_and_parse(&bytes, RETAINED_SCHEMA).unwrap_err(); assert!(error.to_string().contains("unknown field")); @@ -104,8 +104,7 @@ fn retained_artifact_binding_rejects_wrong_digest() { let retained = validate_and_parse(RETAINED_SOURCES, RETAINED_SCHEMA).unwrap(); let mut artifacts: Value = serde_json::from_slice(RETAINED_ARTIFACTS).unwrap(); artifacts["artifacts"][0]["digest"] = Value::String( - "sha256:0000000000000000000000000000000000000000000000000000000000000000" - .to_owned(), + "sha256:0000000000000000000000000000000000000000000000000000000000000000".to_owned(), ); let error = verify_artifacts(&retained, &artifacts).unwrap_err(); assert!(error.to_string().contains("artifact digest mismatch")); diff --git a/tools/af02-verifier/src/canonical.rs b/tools/af02-verifier/src/canonical.rs index 8569b621..a114ca90 100644 --- a/tools/af02-verifier/src/canonical.rs +++ b/tools/af02-verifier/src/canonical.rs @@ -75,10 +75,8 @@ mod tests { #[test] fn canonicalizes_recursive_object_keys_and_preserves_array_order() { - let value: Value = serde_json::from_str( - r#"{"z":{"b":2,"a":1},"a":[{"y":2,"x":1},0]}"#, - ) - .unwrap(); + let value: Value = + serde_json::from_str(r#"{"z":{"b":2,"a":1},"a":[{"y":2,"x":1},0]}"#).unwrap(); assert_eq!( canonical_json_bytes(&value).unwrap(), br#"{"a":[{"x":1,"y":2},0],"z":{"a":1,"b":2}}"# diff --git a/tools/af02-verifier/src/retained.rs b/tools/af02-verifier/src/retained.rs index fa5f3db0..0b6210db 100644 --- a/tools/af02-verifier/src/retained.rs +++ b/tools/af02-verifier/src/retained.rs @@ -199,13 +199,14 @@ pub fn validate_and_parse( ) -> Result { let instance: Value = serde_json::from_slice(instance_bytes)?; let schema: Value = serde_json::from_slice(schema_bytes)?; - let schema_id = schema - .get("$id") - .and_then(Value::as_str) - .ok_or_else(|| RetainedError::Schema { - path: "$".to_owned(), - message: "trusted schema is missing $id".to_owned(), - })?; + let schema_id = + schema + .get("$id") + .and_then(Value::as_str) + .ok_or_else(|| RetainedError::Schema { + path: "$".to_owned(), + message: "trusted schema is missing $id".to_owned(), + })?; if schema_id != "https://commandf.dev/schemas/af02-retained-authority-sources-v1.schema.json" { return Err(RetainedError::Schema { path: "$".to_owned(), @@ -236,7 +237,9 @@ pub fn locator_plan(retained: &RetainedAuthoritySources) -> Result Result = pull_requests .iter() - .filter(|item| item.get("number").and_then(Value::as_u64) == Some(expected.pull_request_number)) + .filter(|item| { + item.get("number").and_then(Value::as_u64) == Some(expected.pull_request_number) + }) .collect(); if matching.len() != 1 { return Err(RetainedError::Mismatch(format!( @@ -345,7 +355,9 @@ pub fn verify_artifacts( let items = artifacts .get("artifacts") .and_then(Value::as_array) - .ok_or_else(|| RetainedError::Mismatch("artifact collection is missing artifacts".to_owned()))?; + .ok_or_else(|| { + RetainedError::Mismatch("artifact collection is missing artifacts".to_owned()) + })?; let expected = &retained.cf10.artifact; let matching: Vec<&Value> = items .iter() From e8d0eb4d675763a1858ffd53774a6b8ce788e329 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 12:41:29 +0300 Subject: [PATCH 03/37] test(af02): exercise retained locator reconstruction --- .../tests/af02_authority_reconstruction.rs | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/crates/commandf-pkg/tests/af02_authority_reconstruction.rs b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs index ad8c5b45..f0f1da71 100644 --- a/crates/commandf-pkg/tests/af02_authority_reconstruction.rs +++ b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs @@ -10,7 +10,9 @@ use std::path::PathBuf; use authority::{project_assurance_ruleset, project_authority, project_cf06, Cf06Source}; use canonical::canonical_json_bytes; -use retained::{project_retained, validate_and_parse, verify_artifacts, verify_workflow_run}; +use retained::{ + locator_plan, project_retained, validate_and_parse, verify_artifacts, verify_workflow_run, +}; use serde_json::Value; const MAIN_SHA: &str = "54b9772a3b86464da6f395f8ba8371f364c9bb38"; @@ -88,6 +90,49 @@ fn retained_schema_rejects_candidate_url_authority() { assert!(error.to_string().contains("unknown field")); } +#[test] +fn retained_locator_plan_reconstructs_frozen_github_urls() { + let retained = validate_and_parse(RETAINED_SOURCES, RETAINED_SCHEMA).unwrap(); + let plan = locator_plan(&retained).unwrap(); + + assert_eq!( + plan.pull_request, + "https://api.github.com/repos/TheHalfMoon/commandF/pulls/11" + ); + assert_eq!( + plan.retained_head_commit, + "https://api.github.com/repos/TheHalfMoon/commandF/commits/5fe10d9859407272acf6649fc3e868d3eb2fbd12" + ); + assert_eq!( + plan.retained_base_commit, + "https://api.github.com/repos/TheHalfMoon/commandF/commits/5cb1a4c3445c0ebd86654cfb467a5e008e801c3e" + ); + assert_eq!( + plan.manifest_contents, + "https://api.github.com/repos/TheHalfMoon/commandF/contents/corpus/real-ig/v1/corpus.json?ref=5fe10d9859407272acf6649fc3e868d3eb2fbd12" + ); + assert_eq!( + plan.manifest_blob, + "https://api.github.com/repos/TheHalfMoon/commandF/git/blobs/655949a8a30d67502dffd624a175d2e8e02b1d1f" + ); + assert_eq!( + plan.donor_contents, + "https://api.github.com/repos/TheHalfMoon/commandF/contents/donors/cf-10-real-ig-delta-corpus.yaml?ref=5fe10d9859407272acf6649fc3e868d3eb2fbd12" + ); + assert_eq!( + plan.donor_blob, + "https://api.github.com/repos/TheHalfMoon/commandF/git/blobs/566b46f4e6f467a1ccae3ac810b31956309173b6" + ); + assert_eq!( + plan.workflow_run, + "https://api.github.com/repos/TheHalfMoon/commandF/actions/runs/31916124080" + ); + assert_eq!( + plan.workflow_run_artifacts, + "https://api.github.com/repos/TheHalfMoon/commandF/actions/runs/31916124080/artifacts" + ); +} + #[test] fn retained_run_binding_rejects_wrong_event() { let retained = validate_and_parse(RETAINED_SOURCES, RETAINED_SCHEMA).unwrap(); From 218128529a5571da6392386caa3f40592e3b1468 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 14:00:25 +0300 Subject: [PATCH 04/37] test(af02): capture generated authority baseline --- .github/workflows/af02-baseline-capture.yml | 55 +++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 .github/workflows/af02-baseline-capture.yml diff --git a/.github/workflows/af02-baseline-capture.yml b/.github/workflows/af02-baseline-capture.yml new file mode 100644 index 00000000..6d3e43b3 --- /dev/null +++ b/.github/workflows/af02-baseline-capture.yml @@ -0,0 +1,55 @@ +name: af02-baseline-capture + +on: + push: + branches: + - feat/af02-a0-authority-reconstruction + +permissions: + contents: read + +jobs: + capture: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 / Node 24 + with: + ref: ${{ github.sha }} + persist-credentials: false + - uses: dtolnay/rust-toolchain@032958afbdc797a9164d3bc0b56325c1308924a5 # 1.97.1 + - name: Capture generated AF-02 authority baseline + run: | + set -euo pipefail + set +e + cargo test --locked -p commandf-pkg --test af02_authority_reconstruction authority_baseline_v2_matches_canonical_snapshot -- --exact --nocapture > /tmp/af02-baseline.log 2>&1 + test_status=$? + set -e + cat /tmp/af02-baseline.log + TEST_STATUS="$test_status" python3 - <<'PY' + import hashlib + import json + import os + from pathlib import Path + + if int(os.environ["TEST_STATUS"]) == 0: + raise SystemExit("AF-02 baseline gate unexpectedly passed while capture workflow is active") + + text = Path("/tmp/af02-baseline.log").read_text(encoding="utf-8") + marker = "AF02_GENERATED_BASELINE=" + if marker not in text: + raise SystemExit("AF-02 generated baseline marker is absent from test output") + baseline = text.split(marker, 1)[1].splitlines()[0] + json.loads(baseline) + data = baseline.encode("utf-8") + Path("authority-baseline.json").write_bytes(data) + print(f"AF02_BASELINE_BYTES={len(data)}") + print(f"AF02_BASELINE_SHA256={hashlib.sha256(data).hexdigest()}") + PY + - name: Upload generated AF-02 authority baseline + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: af02-generated-authority-baseline + path: authority-baseline.json + if-no-files-found: error + retention-days: 1 From 77a3dd01bb34a108e9f63f9e4e8e9f73f944b3d5 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 14:01:48 +0300 Subject: [PATCH 05/37] test(af02): bind canonical authority baseline --- .../016-af-02-adversarial-test-strength/authority-baseline.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 specs/016-af-02-adversarial-test-strength/authority-baseline.json diff --git a/specs/016-af-02-adversarial-test-strength/authority-baseline.json b/specs/016-af-02-adversarial-test-strength/authority-baseline.json new file mode 100644 index 00000000..5141ef28 --- /dev/null +++ b/specs/016-af-02-adversarial-test-strength/authority-baseline.json @@ -0,0 +1 @@ +{"af01":{"assurance":{"ruleset_id":21652953,"semantic_sha256":"f0c60f48d728e7dc77128c8babe324a1654151d10808aea3f3a5f9e499b10272"},"review_governance":{"ruleset_id":21652974,"semantic_sha256":"26d9ec9b4342486866627b01c1c50e19846881e8db0efc77b6dea29c4e15bb27"}},"captured_from_main_sha":"54b9772a3b86464da6f395f8ba8371f364c9bb38","captured_from_main_tree":"4ac26d8de419a0bec0faba8e14ded1763cfe30b3","cf06":{"project":"hapifhir/org.hl7.fhir.core","projection_sha256":"236d71b6816978a4f7c9ea587d70301801b91a1d8a038f93ac7940203dc62787","r4_core_context":"hl7.fhir.r4.core@4.0.1","release":"6.10.2","source_commit":"d06577dbc5c62c74a2a8823fbc4830a3024d5b0b","source_files":[{"git_blob_sha":"9046546a86061961cf3e17f3f1880165625edea8","path":"crates/commandf-pkg/src/oracle_model.rs","raw_sha256":"bc2150ca19d41e8b87c1dd447e3329e4f7c4127c716f931eb5fd583f5706bcc2"},{"git_blob_sha":"9add2dad45cb8958c9304d38e29950ed1f769990","path":"donors/hl7-fhir-validator-6.10.2.yaml","raw_sha256":"3f972c5f0aa72dd742afede770ac086c3bbc551a209dfc57a4b60af48ec7a8e2"},{"git_blob_sha":"664e303983d2ef85aad934cbef2c14d63744e0ee","path":".github/workflows/cf06-oracle.yml","raw_sha256":"7c7cc67272dd55e3d55495ff352e1a4e60f1a57e1c4711982790e69db8f5fbf0"}],"validator_cli_jar_sha256":"a3addadfa18dfa23146a0a243b6ede68eaad92157a5407738c468bb3d7e4ccd6"},"cf10":{"deltas":[{"after":"9.0.0","before":"8.0.1","id":"C001","package":"hl7.fhir.us.core"},{"after":"2.0.1","before":"1.1.0","id":"C002","package":"hl7.fhir.uv.ips"},{"after":"4.0.0","before":"3.0.0","id":"C003","package":"hl7.fhir.us.mcode"}],"projection_sha256":"21a33fc90bbac03e222af37ec2131dcd586dcfac700e413a29858ef468673d30","retained_artifact_id":9255732702,"retained_artifact_name":"cf10-real-corpus-evidence","retained_artifact_sha256":"9fdde985bb5abbe53ec2bce2dadc5f65c95557f8848c9af68755fc81a45af612","retained_base":"5cb1a4c3445c0ebd86654cfb467a5e008e801c3e","retained_donor_blob_sha":"566b46f4e6f467a1ccae3ac810b31956309173b6","retained_donor_sha256":"ee9117b7b18b4ceccf128df4033a8d5ea6cfd0c5efab2276f3c49ee9befc28e9","retained_head":"5fe10d9859407272acf6649fc3e868d3eb2fbd12","retained_manifest_blob_sha":"655949a8a30d67502dffd624a175d2e8e02b1d1f","retained_manifest_sha256":"2d4c4d1eaff31c1647d9bbdd222006e796b0fec690c5ea42c45646f9acc0d255","retained_pr":11,"retained_run":31916124080,"retained_run_conclusion":"failure","states":[{"archive_bytes":2749959,"archive_sha256":"d7b54d2ec2a48cea94ffea5d939ad67a681f80b94d69594a08cebac36da9e059","case_id":"C001","package":"hl7.fhir.us.core","side":"after","state_id":"C001-after","version":"9.0.0"},{"archive_bytes":2713046,"archive_sha256":"3c02eef48ef10617021bee95e58cbc66d596ceda8cada24b72000d33ad67c464","case_id":"C001","package":"hl7.fhir.us.core","side":"before","state_id":"C001-before","version":"8.0.1"},{"archive_bytes":725312,"archive_sha256":"7183242b70fb2a9058aa3701fb607517a3c2fd0e3100d1d8c538d744c2adf799","case_id":"C002","package":"hl7.fhir.uv.ips","side":"after","state_id":"C002-after","version":"2.0.1"},{"archive_bytes":1065103,"archive_sha256":"403c4141101810e924f2928287985084819d8a5cc3a62e2b3840a557129840ef","case_id":"C002","package":"hl7.fhir.uv.ips","side":"before","state_id":"C002-before","version":"1.1.0"},{"archive_bytes":1003918,"archive_sha256":"e603283bafa508a3705ad022bce95bba1fbd0b8b3b87b978e7412813b7bc1778","case_id":"C003","package":"hl7.fhir.us.mcode","side":"after","state_id":"C003-after","version":"4.0.0"},{"archive_bytes":1014084,"archive_sha256":"c94c91971747efeae760aa037d168e4df992cefb6dacece08217c464b9d39214","case_id":"C003","package":"hl7.fhir.us.mcode","side":"before","state_id":"C003-before","version":"3.0.0"}]},"schema":"commandf.af02-authority-baseline/v2"} \ No newline at end of file From 9c0ea132daade919e41f71d271057c7bd12fd642 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 14:02:06 +0300 Subject: [PATCH 06/37] test(af02): remove baseline capture workflow --- .github/workflows/af02-baseline-capture.yml | 55 --------------------- 1 file changed, 55 deletions(-) delete mode 100644 .github/workflows/af02-baseline-capture.yml diff --git a/.github/workflows/af02-baseline-capture.yml b/.github/workflows/af02-baseline-capture.yml deleted file mode 100644 index 6d3e43b3..00000000 --- a/.github/workflows/af02-baseline-capture.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: af02-baseline-capture - -on: - push: - branches: - - feat/af02-a0-authority-reconstruction - -permissions: - contents: read - -jobs: - capture: - runs-on: ubuntu-24.04 - timeout-minutes: 15 - steps: - - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 / Node 24 - with: - ref: ${{ github.sha }} - persist-credentials: false - - uses: dtolnay/rust-toolchain@032958afbdc797a9164d3bc0b56325c1308924a5 # 1.97.1 - - name: Capture generated AF-02 authority baseline - run: | - set -euo pipefail - set +e - cargo test --locked -p commandf-pkg --test af02_authority_reconstruction authority_baseline_v2_matches_canonical_snapshot -- --exact --nocapture > /tmp/af02-baseline.log 2>&1 - test_status=$? - set -e - cat /tmp/af02-baseline.log - TEST_STATUS="$test_status" python3 - <<'PY' - import hashlib - import json - import os - from pathlib import Path - - if int(os.environ["TEST_STATUS"]) == 0: - raise SystemExit("AF-02 baseline gate unexpectedly passed while capture workflow is active") - - text = Path("/tmp/af02-baseline.log").read_text(encoding="utf-8") - marker = "AF02_GENERATED_BASELINE=" - if marker not in text: - raise SystemExit("AF-02 generated baseline marker is absent from test output") - baseline = text.split(marker, 1)[1].splitlines()[0] - json.loads(baseline) - data = baseline.encode("utf-8") - Path("authority-baseline.json").write_bytes(data) - print(f"AF02_BASELINE_BYTES={len(data)}") - print(f"AF02_BASELINE_SHA256={hashlib.sha256(data).hexdigest()}") - PY - - name: Upload generated AF-02 authority baseline - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: af02-generated-authority-baseline - path: authority-baseline.json - if-no-files-found: error - retention-days: 1 From 6ffc64c61437427dee4496719ded399df81cceba Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 14:12:52 +0300 Subject: [PATCH 07/37] fix(af02): reject duplicate authority keys --- tools/af02-verifier/src/canonical.rs | 211 ++++++++++++++++++++++++++- 1 file changed, 208 insertions(+), 3 deletions(-) diff --git a/tools/af02-verifier/src/canonical.rs b/tools/af02-verifier/src/canonical.rs index a114ca90..e923ee49 100644 --- a/tools/af02-verifier/src/canonical.rs +++ b/tools/af02-verifier/src/canonical.rs @@ -1,4 +1,9 @@ -use serde_json::Value; +use std::collections::BTreeSet; +use std::fmt; + +use serde::de::{self, DeserializeSeed, MapAccess, SeqAccess, Visitor}; +use serde::Deserializer as _; +use serde_json::{Map, Number, Value}; use sha2::{Digest, Sha256}; use thiserror::Error; @@ -10,6 +15,113 @@ pub enum CanonicalError { StringEncoding(#[from] serde_json::Error), } +#[derive(Clone, Copy)] +struct NoDuplicateValue; + +struct NoDuplicateVisitor; + +impl<'de> DeserializeSeed<'de> for NoDuplicateValue { + type Value = Value; + + fn deserialize(self, deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_any(NoDuplicateVisitor) + } +} + +impl<'de> Visitor<'de> for NoDuplicateVisitor { + type Value = Value; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a JSON value without duplicate object keys") + } + + fn visit_bool(self, value: bool) -> Result { + Ok(Value::Bool(value)) + } + + fn visit_i64(self, value: i64) -> Result { + Ok(Value::Number(Number::from(value))) + } + + fn visit_u64(self, value: u64) -> Result { + Ok(Value::Number(Number::from(value))) + } + + fn visit_f64(self, value: f64) -> Result + where + E: de::Error, + { + Number::from_f64(value) + .map(Value::Number) + .ok_or_else(|| E::custom("non-finite JSON number")) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Ok(Value::String(value.to_owned())) + } + + fn visit_string(self, value: String) -> Result { + Ok(Value::String(value)) + } + + fn visit_none(self) -> Result { + Ok(Value::Null) + } + + fn visit_unit(self) -> Result { + Ok(Value::Null) + } + + fn visit_some(self, deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + NoDuplicateValue.deserialize(deserializer) + } + + fn visit_seq(self, mut sequence: A) -> Result + where + A: SeqAccess<'de>, + { + let mut values = Vec::new(); + while let Some(value) = sequence.next_element_seed(NoDuplicateValue)? { + values.push(value); + } + Ok(Value::Array(values)) + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let mut keys = BTreeSet::new(); + let mut values = Map::new(); + while let Some(key) = map.next_key::()? { + if !keys.insert(key.clone()) { + return Err(de::Error::custom(format!( + "duplicate JSON object key {key:?}" + ))); + } + let value = map.next_value_seed(NoDuplicateValue)?; + values.insert(key, value); + } + Ok(Value::Object(values)) + } +} + +pub fn parse_json_no_duplicates(bytes: &[u8]) -> Result { + let mut deserializer = serde_json::Deserializer::from_slice(bytes); + let value = NoDuplicateValue.deserialize(&mut deserializer)?; + deserializer.end()?; + Ok(value) +} + pub fn canonical_json_bytes(value: &Value) -> Result, CanonicalError> { let mut out = Vec::new(); write_value(value, &mut out)?; @@ -25,6 +137,84 @@ pub fn sha256_hex(bytes: &[u8]) -> String { format!("{digest:x}") } +pub fn git_blob_sha1_hex(bytes: &[u8]) -> String { + let header = format!("blob {}\0", bytes.len()); + let mut object = Vec::with_capacity(header.len() + bytes.len()); + object.extend_from_slice(header.as_bytes()); + object.extend_from_slice(bytes); + sha1_hex(&object) +} + +fn sha1_hex(bytes: &[u8]) -> String { + let mut message = bytes.to_vec(); + let bit_len = (message.len() as u64).wrapping_mul(8); + message.push(0x80); + while message.len() % 64 != 56 { + message.push(0); + } + message.extend_from_slice(&bit_len.to_be_bytes()); + + let mut h0 = 0x6745_2301u32; + let mut h1 = 0xefcd_ab89u32; + let mut h2 = 0x98ba_dcfeu32; + let mut h3 = 0x1032_5476u32; + let mut h4 = 0xc3d2_e1f0u32; + + for chunk in message.chunks_exact(64) { + let mut words = [0u32; 80]; + for (index, word) in words[..16].iter_mut().enumerate() { + let offset = index * 4; + *word = u32::from_be_bytes([ + chunk[offset], + chunk[offset + 1], + chunk[offset + 2], + chunk[offset + 3], + ]); + } + for index in 16..80 { + words[index] = (words[index - 3] + ^ words[index - 8] + ^ words[index - 14] + ^ words[index - 16]) + .rotate_left(1); + } + + let mut a = h0; + let mut b = h1; + let mut c = h2; + let mut d = h3; + let mut e = h4; + + for (index, word) in words.iter().enumerate() { + let (function, constant) = match index { + 0..=19 => ((b & c) | ((!b) & d), 0x5a82_7999), + 20..=39 => (b ^ c ^ d, 0x6ed9_eba1), + 40..=59 => ((b & c) | (b & d) | (c & d), 0x8f1b_bcdc), + _ => (b ^ c ^ d, 0xca62_c1d6), + }; + let temp = a + .rotate_left(5) + .wrapping_add(function) + .wrapping_add(e) + .wrapping_add(constant) + .wrapping_add(*word); + e = d; + d = c; + c = b.rotate_left(30); + b = a; + a = temp; + } + + h0 = h0.wrapping_add(a); + h1 = h1.wrapping_add(b); + h2 = h2.wrapping_add(c); + h3 = h3.wrapping_add(d); + h4 = h4.wrapping_add(e); + } + + format!("{h0:08x}{h1:08x}{h2:08x}{h3:08x}{h4:08x}") +} + fn write_value(value: &Value, out: &mut Vec) -> Result<(), CanonicalError> { match value { Value::Null => out.extend_from_slice(b"null"), @@ -76,7 +266,8 @@ mod tests { #[test] fn canonicalizes_recursive_object_keys_and_preserves_array_order() { let value: Value = - serde_json::from_str(r#"{"z":{"b":2,"a":1},"a":[{"y":2,"x":1},0]}"#).unwrap(); + parse_json_no_duplicates(br#"{"z":{"b":2,"a":1},"a":[{"y":2,"x":1},0]}"#) + .unwrap(); assert_eq!( canonical_json_bytes(&value).unwrap(), br#"{"a":[{"x":1,"y":2},0],"z":{"a":1,"b":2}}"# @@ -85,10 +276,24 @@ mod tests { #[test] fn rejects_floats() { - let value: Value = serde_json::from_str(r#"{"n":1.5}"#).unwrap(); + let value: Value = parse_json_no_duplicates(br#"{"n":1.5}"#).unwrap(); assert!(matches!( canonical_json_bytes(&value), Err(CanonicalError::FloatNumber) )); } + + #[test] + fn rejects_duplicate_object_keys_at_any_depth() { + let error = parse_json_no_duplicates(br#"{"outer":{"id":1,"id":2}}"#).unwrap_err(); + assert!(error.to_string().contains("duplicate JSON object key \"id\"")); + } + + #[test] + fn computes_git_blob_identity() { + assert_eq!( + git_blob_sha1_hex(b"test content\n"), + "d670460b4b4aece5915caf5c68d12f560a9fe3e4" + ); + } } From f7d9f399557b274b2b318e81c9f64a1a44bdd4eb Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 14:15:30 +0300 Subject: [PATCH 08/37] fix(af02): bind retained bytes before parsing --- tools/af02-verifier/src/retained.rs | 39 ++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/tools/af02-verifier/src/retained.rs b/tools/af02-verifier/src/retained.rs index 0b6210db..423a25e0 100644 --- a/tools/af02-verifier/src/retained.rs +++ b/tools/af02-verifier/src/retained.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use thiserror::Error; -use crate::canonical::{sha256_hex, CanonicalError}; +use crate::canonical::{git_blob_sha1_hex, parse_json_no_duplicates, sha256_hex, CanonicalError}; pub const RETAINED_SCHEMA_ID: &str = "commandf.af02-retained-authority-sources/v1"; @@ -197,16 +197,15 @@ pub fn validate_and_parse( instance_bytes: &[u8], schema_bytes: &[u8], ) -> Result { - let instance: Value = serde_json::from_slice(instance_bytes)?; - let schema: Value = serde_json::from_slice(schema_bytes)?; - let schema_id = - schema - .get("$id") - .and_then(Value::as_str) - .ok_or_else(|| RetainedError::Schema { - path: "$".to_owned(), - message: "trusted schema is missing $id".to_owned(), - })?; + let instance = parse_json_no_duplicates(instance_bytes)?; + let schema = parse_json_no_duplicates(schema_bytes)?; + let schema_id = schema + .get("$id") + .and_then(Value::as_str) + .ok_or_else(|| RetainedError::Schema { + path: "$".to_owned(), + message: "trusted schema is missing $id".to_owned(), + })?; if schema_id != "https://commandf.dev/schemas/af02-retained-authority-sources-v1.schema.json" { return Err(RetainedError::Schema { path: "$".to_owned(), @@ -396,7 +395,23 @@ pub fn project_retained( manifest_bytes: &[u8], donor_bytes: &[u8], ) -> Result { - let manifest: CorpusManifest = serde_json::from_slice(manifest_bytes)?; + let manifest_blob = git_blob_sha1_hex(manifest_bytes); + if manifest_blob != retained.cf10.manifest.git_blob_sha { + return Err(RetainedError::Mismatch(format!( + "retained manifest Git blob mismatch: expected {}, got {manifest_blob}", + retained.cf10.manifest.git_blob_sha + ))); + } + let donor_blob = git_blob_sha1_hex(donor_bytes); + if donor_blob != retained.cf10.donor.git_blob_sha { + return Err(RetainedError::Mismatch(format!( + "retained donor Git blob mismatch: expected {}, got {donor_blob}", + retained.cf10.donor.git_blob_sha + ))); + } + + let manifest_value = parse_json_no_duplicates(manifest_bytes)?; + let manifest: CorpusManifest = serde_json::from_value(manifest_value)?; if manifest.schema != 1 || manifest.selection_policy != "frozen_pre_result_v1" { return Err(RetainedError::Mismatch( "retained corpus manifest schema/selection policy drifted".to_owned(), From c4c7769617eaaf5f2e08c3485b065606a1f8ba23 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 14:16:34 +0300 Subject: [PATCH 09/37] fix(af02): verify CF-06 Git object identities --- tools/af02-verifier/src/authority.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/af02-verifier/src/authority.rs b/tools/af02-verifier/src/authority.rs index 86bf7b5f..1c30ca03 100644 --- a/tools/af02-verifier/src/authority.rs +++ b/tools/af02-verifier/src/authority.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use thiserror::Error; -use crate::canonical::{canonical_sha256, sha256_hex, CanonicalError}; +use crate::canonical::{canonical_sha256, git_blob_sha1_hex, sha256_hex, CanonicalError}; use crate::retained::{RetainedError, RetainedProjection}; pub const AUTHORITY_BASELINE_SCHEMA: &str = "commandf.af02-authority-baseline/v2"; @@ -349,6 +349,13 @@ pub fn project_cf06(sources: [Cf06Source<'_>; 3]) -> Result Date: Fri, 28 Aug 2026 14:16:55 +0300 Subject: [PATCH 10/37] fix(af02): reject duplicate CLI authority JSON --- tools/af02-verifier/src/main.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tools/af02-verifier/src/main.rs b/tools/af02-verifier/src/main.rs index 18d4892c..05c2ec6c 100644 --- a/tools/af02-verifier/src/main.rs +++ b/tools/af02-verifier/src/main.rs @@ -2,7 +2,7 @@ use std::fs; use std::path::PathBuf; use commandf_af02_verifier::authority::{project_authority, Cf06Source}; -use commandf_af02_verifier::canonical::canonical_json_bytes; +use commandf_af02_verifier::canonical::{canonical_json_bytes, parse_json_no_duplicates}; use commandf_af02_verifier::retained::{ locator_plan, project_retained, validate_and_parse, verify_artifacts, verify_workflow_run, }; @@ -67,15 +67,17 @@ fn run() -> Result<(), Box> { if args.next().is_some() { return Err("project-authority accepts exactly one input path".into()); } - let input: AuthorityInput = serde_json::from_slice(&fs::read(input_path)?)?; + let input_value = parse_json_no_duplicates(&fs::read(input_path)?)?; + let input: AuthorityInput = serde_json::from_value(input_value)?; let retained_bytes = fs::read(&input.retained_sources_path)?; let retained_schema_bytes = fs::read(&input.retained_schema_path)?; let retained = validate_and_parse(&retained_bytes, &retained_schema_bytes)?; - let run: Value = serde_json::from_slice(&fs::read(&input.retained_workflow_run_path)?)?; + let run: Value = + parse_json_no_duplicates(&fs::read(&input.retained_workflow_run_path)?)?; verify_workflow_run(&retained, &run)?; let artifacts: Value = - serde_json::from_slice(&fs::read(&input.retained_artifacts_path)?)?; + parse_json_no_duplicates(&fs::read(&input.retained_artifacts_path)?)?; verify_artifacts(&retained, &artifacts)?; let retained_projection = project_retained( @@ -84,8 +86,9 @@ fn run() -> Result<(), Box> { &fs::read(&input.retained_donor_path)?, )?; let assurance: Value = - serde_json::from_slice(&fs::read(&input.assurance_ruleset_path)?)?; - let review: Value = serde_json::from_slice(&fs::read(&input.review_ruleset_path)?)?; + parse_json_no_duplicates(&fs::read(&input.assurance_ruleset_path)?)?; + let review: Value = + parse_json_no_duplicates(&fs::read(&input.review_ruleset_path)?)?; let oracle_model = fs::read(&input.cf06_oracle_model.local_path)?; let donor = fs::read(&input.cf06_donor.local_path)?; From 331fcef854fbd7a0e2158a9f4e97045a86c166e4 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 14:17:50 +0300 Subject: [PATCH 11/37] test(af02): bind authority inputs to immutable objects --- .../tests/af02_authority_reconstruction.rs | 73 ++++++++++++++++--- 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/crates/commandf-pkg/tests/af02_authority_reconstruction.rs b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs index f0f1da71..62305e2f 100644 --- a/crates/commandf-pkg/tests/af02_authority_reconstruction.rs +++ b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs @@ -9,7 +9,7 @@ use std::fs; use std::path::PathBuf; use authority::{project_assurance_ruleset, project_authority, project_cf06, Cf06Source}; -use canonical::canonical_json_bytes; +use canonical::{canonical_json_bytes, git_blob_sha1_hex, parse_json_no_duplicates}; use retained::{ locator_plan, project_retained, validate_and_parse, verify_artifacts, verify_workflow_run, }; @@ -17,6 +17,8 @@ use serde_json::Value; const MAIN_SHA: &str = "54b9772a3b86464da6f395f8ba8371f364c9bb38"; const MAIN_TREE: &str = "4ac26d8de419a0bec0faba8e14ded1763cfe30b3"; +const RETAINED_SOURCES_BLOB: &str = "f9c0bc16ac742238c93ff77a85486cd1db5dbcf3"; +const RETAINED_SCHEMA_BLOB: &str = "7d0daced343fd15d797cc0d4d53e9d63aac790c5"; const ORACLE_MODEL: &[u8] = include_bytes!("../src/oracle_model.rs"); const CF06_DONOR: &[u8] = include_bytes!("../../../donors/hl7-fhir-validator-6.10.2.yaml"); @@ -40,16 +42,22 @@ const RETAINED_RUN: &[u8] = const RETAINED_ARTIFACTS: &[u8] = include_bytes!("../../../tools/af02-verifier/tests/fixtures/cf10-artifacts.json"); +fn assert_canonical_contract_objects() { + assert_eq!(git_blob_sha1_hex(RETAINED_SOURCES), RETAINED_SOURCES_BLOB); + assert_eq!(git_blob_sha1_hex(RETAINED_SCHEMA), RETAINED_SCHEMA_BLOB); +} + fn build_baseline() -> authority::AuthorityBaseline { + assert_canonical_contract_objects(); let retained = validate_and_parse(RETAINED_SOURCES, RETAINED_SCHEMA).unwrap(); - let run: Value = serde_json::from_slice(RETAINED_RUN).unwrap(); + let run = parse_json_no_duplicates(RETAINED_RUN).unwrap(); verify_workflow_run(&retained, &run).unwrap(); - let artifacts: Value = serde_json::from_slice(RETAINED_ARTIFACTS).unwrap(); + let artifacts = parse_json_no_duplicates(RETAINED_ARTIFACTS).unwrap(); verify_artifacts(&retained, &artifacts).unwrap(); let retained_projection = project_retained(&retained, RETAINED_MANIFEST, RETAINED_DONOR).unwrap(); - let assurance: Value = serde_json::from_slice(ASSURANCE_RULESET).unwrap(); - let review: Value = serde_json::from_slice(REVIEW_RULESET).unwrap(); + let assurance = parse_json_no_duplicates(ASSURANCE_RULESET).unwrap(); + let review = parse_json_no_duplicates(REVIEW_RULESET).unwrap(); project_authority( MAIN_SHA, @@ -78,9 +86,16 @@ fn build_baseline() -> authority::AuthorityBaseline { .unwrap() } +fn duplicate_probe(bytes: &[u8]) -> Vec { + assert_eq!(bytes.first(), Some(&b'{')); + let mut duplicate = br#"{"__duplicate_probe":0,"__duplicate_probe":1,"#.to_vec(); + duplicate.extend_from_slice(&bytes[1..]); + duplicate +} + #[test] fn retained_schema_rejects_candidate_url_authority() { - let mut value: Value = serde_json::from_slice(RETAINED_SOURCES).unwrap(); + let mut value: Value = parse_json_no_duplicates(RETAINED_SOURCES).unwrap(); value.as_object_mut().unwrap().insert( "url".to_owned(), Value::String("https://example.invalid".to_owned()), @@ -90,8 +105,34 @@ fn retained_schema_rejects_candidate_url_authority() { assert!(error.to_string().contains("unknown field")); } +#[test] +fn retained_contract_rejects_duplicate_semantic_keys_before_schema_validation() { + let mut duplicate = br#"{"schema":"forged","#.to_vec(); + duplicate.extend_from_slice(&RETAINED_SOURCES[1..]); + let error = validate_and_parse(&duplicate, RETAINED_SCHEMA).unwrap_err(); + assert!(error + .to_string() + .contains("duplicate JSON object key \"schema\"")); +} + +#[test] +fn authority_api_inputs_reject_duplicate_keys_before_projection() { + for bytes in [ + ASSURANCE_RULESET, + REVIEW_RULESET, + RETAINED_RUN, + RETAINED_ARTIFACTS, + ] { + let error = parse_json_no_duplicates(&duplicate_probe(bytes)).unwrap_err(); + assert!(error + .to_string() + .contains("duplicate JSON object key \"__duplicate_probe\"")); + } +} + #[test] fn retained_locator_plan_reconstructs_frozen_github_urls() { + assert_canonical_contract_objects(); let retained = validate_and_parse(RETAINED_SOURCES, RETAINED_SCHEMA).unwrap(); let plan = locator_plan(&retained).unwrap(); @@ -136,7 +177,7 @@ fn retained_locator_plan_reconstructs_frozen_github_urls() { #[test] fn retained_run_binding_rejects_wrong_event() { let retained = validate_and_parse(RETAINED_SOURCES, RETAINED_SCHEMA).unwrap(); - let mut run: Value = serde_json::from_slice(RETAINED_RUN).unwrap(); + let mut run = parse_json_no_duplicates(RETAINED_RUN).unwrap(); run.as_object_mut() .unwrap() .insert("event".to_owned(), Value::String("push".to_owned())); @@ -147,7 +188,7 @@ fn retained_run_binding_rejects_wrong_event() { #[test] fn retained_artifact_binding_rejects_wrong_digest() { let retained = validate_and_parse(RETAINED_SOURCES, RETAINED_SCHEMA).unwrap(); - let mut artifacts: Value = serde_json::from_slice(RETAINED_ARTIFACTS).unwrap(); + let mut artifacts = parse_json_no_duplicates(RETAINED_ARTIFACTS).unwrap(); artifacts["artifacts"][0]["digest"] = Value::String( "sha256:0000000000000000000000000000000000000000000000000000000000000000".to_owned(), ); @@ -155,9 +196,19 @@ fn retained_artifact_binding_rejects_wrong_digest() { assert!(error.to_string().contains("artifact digest mismatch")); } +#[test] +fn retained_projection_rejects_candidate_controlled_manifest_bytes() { + let retained = validate_and_parse(RETAINED_SOURCES, RETAINED_SCHEMA).unwrap(); + let mut manifest = RETAINED_MANIFEST.to_vec(); + let index = manifest.iter().position(|byte| *byte == b'C').unwrap(); + manifest[index] = b'X'; + let error = project_retained(&retained, &manifest, RETAINED_DONOR).unwrap_err(); + assert!(error.to_string().contains("retained manifest Git blob mismatch")); +} + #[test] fn assurance_projection_rejects_wrong_required_check_app() { - let mut assurance: Value = serde_json::from_slice(ASSURANCE_RULESET).unwrap(); + let mut assurance = parse_json_no_duplicates(ASSURANCE_RULESET).unwrap(); assurance["rules"][2]["parameters"]["required_status_checks"][0]["integration_id"] = Value::from(1); let error = project_assurance_ruleset(&assurance).unwrap_err(); @@ -165,7 +216,7 @@ fn assurance_projection_rejects_wrong_required_check_app() { } #[test] -fn cf06_projection_rejects_missing_source_pin() { +fn cf06_projection_rejects_candidate_controlled_source_bytes() { let altered = ORACLE_MODEL .windows(authority::CF06_SOURCE_COMMIT.len()) .position(|window| window == authority::CF06_SOURCE_COMMIT.as_bytes()) @@ -191,7 +242,7 @@ fn cf06_projection_rejects_missing_source_pin() { }, ]) .unwrap_err(); - assert!(error.to_string().contains("does not bind")); + assert!(error.to_string().contains("Git blob mismatch")); } #[test] From 39df9d62769ad97f7bee30887f0c44ac5ad834dd Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 14:42:38 +0300 Subject: [PATCH 12/37] ci(af02): run one-shot formatter --- .github/workflows/af02-format-fix.yml | 39 +++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/af02-format-fix.yml diff --git a/.github/workflows/af02-format-fix.yml b/.github/workflows/af02-format-fix.yml new file mode 100644 index 00000000..b49991f8 --- /dev/null +++ b/.github/workflows/af02-format-fix.yml @@ -0,0 +1,39 @@ +name: af02-format-fix + +on: + push: + branches: + - feat/af02-a0-authority-reconstruction + +permissions: + contents: write + +jobs: + format: + if: github.repository == 'TheHalfMoon/commandF' + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 + with: + persist-credentials: true + - uses: dtolnay/rust-toolchain@032958afbdc797a9164d3bc0b56325c1308924a5 + with: + components: rustfmt + - name: Format and remove temporary workflow + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + cargo fmt --all + rm .github/workflows/af02-format-fix.yml + git add -A + git diff --cached --check + if git diff --cached --quiet; then + echo 'formatter produced no changes' >&2 + exit 1 + fi + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git commit -m 'style(af02): format authority verifier' + git push origin "HEAD:${GITHUB_REF_NAME}" From e4f8d77ea9f090a9a515b322319d722a113f0ceb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 11:42:54 +0000 Subject: [PATCH 13/37] style(af02): format authority verifier --- .github/workflows/af02-format-fix.yml | 39 ------------------- .../tests/af02_authority_reconstruction.rs | 4 +- tools/af02-verifier/src/canonical.rs | 15 ++++--- tools/af02-verifier/src/retained.rs | 15 +++---- 4 files changed, 18 insertions(+), 55 deletions(-) delete mode 100644 .github/workflows/af02-format-fix.yml diff --git a/.github/workflows/af02-format-fix.yml b/.github/workflows/af02-format-fix.yml deleted file mode 100644 index b49991f8..00000000 --- a/.github/workflows/af02-format-fix.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: af02-format-fix - -on: - push: - branches: - - feat/af02-a0-authority-reconstruction - -permissions: - contents: write - -jobs: - format: - if: github.repository == 'TheHalfMoon/commandF' - runs-on: ubuntu-24.04 - timeout-minutes: 10 - steps: - - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 - with: - persist-credentials: true - - uses: dtolnay/rust-toolchain@032958afbdc797a9164d3bc0b56325c1308924a5 - with: - components: rustfmt - - name: Format and remove temporary workflow - shell: bash - run: | - set -euo pipefail - test "$(git rev-parse HEAD)" = "$GITHUB_SHA" - cargo fmt --all - rm .github/workflows/af02-format-fix.yml - git add -A - git diff --cached --check - if git diff --cached --quiet; then - echo 'formatter produced no changes' >&2 - exit 1 - fi - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git commit -m 'style(af02): format authority verifier' - git push origin "HEAD:${GITHUB_REF_NAME}" diff --git a/crates/commandf-pkg/tests/af02_authority_reconstruction.rs b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs index 62305e2f..33ef57d4 100644 --- a/crates/commandf-pkg/tests/af02_authority_reconstruction.rs +++ b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs @@ -203,7 +203,9 @@ fn retained_projection_rejects_candidate_controlled_manifest_bytes() { let index = manifest.iter().position(|byte| *byte == b'C').unwrap(); manifest[index] = b'X'; let error = project_retained(&retained, &manifest, RETAINED_DONOR).unwrap_err(); - assert!(error.to_string().contains("retained manifest Git blob mismatch")); + assert!(error + .to_string() + .contains("retained manifest Git blob mismatch")); } #[test] diff --git a/tools/af02-verifier/src/canonical.rs b/tools/af02-verifier/src/canonical.rs index e923ee49..18c78e65 100644 --- a/tools/af02-verifier/src/canonical.rs +++ b/tools/af02-verifier/src/canonical.rs @@ -172,11 +172,9 @@ fn sha1_hex(bytes: &[u8]) -> String { ]); } for index in 16..80 { - words[index] = (words[index - 3] - ^ words[index - 8] - ^ words[index - 14] - ^ words[index - 16]) - .rotate_left(1); + words[index] = + (words[index - 3] ^ words[index - 8] ^ words[index - 14] ^ words[index - 16]) + .rotate_left(1); } let mut a = h0; @@ -266,8 +264,7 @@ mod tests { #[test] fn canonicalizes_recursive_object_keys_and_preserves_array_order() { let value: Value = - parse_json_no_duplicates(br#"{"z":{"b":2,"a":1},"a":[{"y":2,"x":1},0]}"#) - .unwrap(); + parse_json_no_duplicates(br#"{"z":{"b":2,"a":1},"a":[{"y":2,"x":1},0]}"#).unwrap(); assert_eq!( canonical_json_bytes(&value).unwrap(), br#"{"a":[{"x":1,"y":2},0],"z":{"a":1,"b":2}}"# @@ -286,7 +283,9 @@ mod tests { #[test] fn rejects_duplicate_object_keys_at_any_depth() { let error = parse_json_no_duplicates(br#"{"outer":{"id":1,"id":2}}"#).unwrap_err(); - assert!(error.to_string().contains("duplicate JSON object key \"id\"")); + assert!(error + .to_string() + .contains("duplicate JSON object key \"id\"")); } #[test] diff --git a/tools/af02-verifier/src/retained.rs b/tools/af02-verifier/src/retained.rs index 423a25e0..4710b04b 100644 --- a/tools/af02-verifier/src/retained.rs +++ b/tools/af02-verifier/src/retained.rs @@ -199,13 +199,14 @@ pub fn validate_and_parse( ) -> Result { let instance = parse_json_no_duplicates(instance_bytes)?; let schema = parse_json_no_duplicates(schema_bytes)?; - let schema_id = schema - .get("$id") - .and_then(Value::as_str) - .ok_or_else(|| RetainedError::Schema { - path: "$".to_owned(), - message: "trusted schema is missing $id".to_owned(), - })?; + let schema_id = + schema + .get("$id") + .and_then(Value::as_str) + .ok_or_else(|| RetainedError::Schema { + path: "$".to_owned(), + message: "trusted schema is missing $id".to_owned(), + })?; if schema_id != "https://commandf.dev/schemas/af02-retained-authority-sources-v1.schema.json" { return Err(RetainedError::Schema { path: "$".to_owned(), From 66a423e533aeb7cfb4e0f0d4e0aa860e54a69297 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 14:43:27 +0300 Subject: [PATCH 14/37] ci(af02): qualify formatted authority verifier From 08ef3376dcd06d4ec42d29c5748f53bdae7d9389 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 14:45:09 +0300 Subject: [PATCH 15/37] test(af02): reconstruct authority from Git objects --- .../tests/af02_authority_reconstruction.rs | 187 +++++++++++++----- 1 file changed, 136 insertions(+), 51 deletions(-) diff --git a/crates/commandf-pkg/tests/af02_authority_reconstruction.rs b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs index 33ef57d4..2db17b96 100644 --- a/crates/commandf-pkg/tests/af02_authority_reconstruction.rs +++ b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs @@ -7,6 +7,7 @@ mod retained; use std::fs; use std::path::PathBuf; +use std::process::Command; use authority::{project_assurance_ruleset, project_authority, project_cf06, Cf06Source}; use canonical::{canonical_json_bytes, git_blob_sha1_hex, parse_json_no_duplicates}; @@ -17,47 +18,117 @@ use serde_json::Value; const MAIN_SHA: &str = "54b9772a3b86464da6f395f8ba8371f364c9bb38"; const MAIN_TREE: &str = "4ac26d8de419a0bec0faba8e14ded1763cfe30b3"; +const RETAINED_HEAD: &str = "5fe10d9859407272acf6649fc3e868d3eb2fbd12"; + +const RETAINED_SOURCES_PATH: &str = + "specs/016-af-02-adversarial-test-strength/retained-authority-sources.json"; const RETAINED_SOURCES_BLOB: &str = "f9c0bc16ac742238c93ff77a85486cd1db5dbcf3"; +const RETAINED_SCHEMA_PATH: &str = "specs/016-af-02-adversarial-test-strength/schemas/af02-retained-authority-sources-v1.schema.json"; const RETAINED_SCHEMA_BLOB: &str = "7d0daced343fd15d797cc0d4d53e9d63aac790c5"; -const ORACLE_MODEL: &[u8] = include_bytes!("../src/oracle_model.rs"); -const CF06_DONOR: &[u8] = include_bytes!("../../../donors/hl7-fhir-validator-6.10.2.yaml"); -const CF06_WORKFLOW: &[u8] = include_bytes!("../../../.github/workflows/cf06-oracle.yml"); -const RETAINED_SOURCES: &[u8] = include_bytes!( - "../../../specs/016-af-02-adversarial-test-strength/retained-authority-sources.json" -); -const RETAINED_SCHEMA: &[u8] = include_bytes!( - "../../../specs/016-af-02-adversarial-test-strength/schemas/af02-retained-authority-sources-v1.schema.json" -); +const ORACLE_MODEL_PATH: &str = "crates/commandf-pkg/src/oracle_model.rs"; +const ORACLE_MODEL_BLOB: &str = "9046546a86061961cf3e17f3f1880165625edea8"; +const CF06_DONOR_PATH: &str = "donors/hl7-fhir-validator-6.10.2.yaml"; +const CF06_DONOR_BLOB: &str = "9add2dad45cb8958c9304d38e29950ed1f769990"; +const CF06_WORKFLOW_PATH: &str = ".github/workflows/cf06-oracle.yml"; +const CF06_WORKFLOW_BLOB: &str = "664e303983d2ef85aad934cbef2c14d63744e0ee"; + const ASSURANCE_RULESET: &[u8] = include_bytes!("../../../tools/af02-verifier/tests/fixtures/assurance-ruleset.json"); const REVIEW_RULESET: &[u8] = include_bytes!("../../../tools/af02-verifier/tests/fixtures/review-ruleset.json"); -const RETAINED_MANIFEST: &[u8] = - include_bytes!("../../../tools/af02-verifier/tests/fixtures/cf10-corpus.json"); -const RETAINED_DONOR: &[u8] = - include_bytes!("../../../tools/af02-verifier/tests/fixtures/cf10-donor.yaml"); const RETAINED_RUN: &[u8] = include_bytes!("../../../tools/af02-verifier/tests/fixtures/cf10-run.json"); const RETAINED_ARTIFACTS: &[u8] = include_bytes!("../../../tools/af02-verifier/tests/fixtures/cf10-artifacts.json"); -fn assert_canonical_contract_objects() { - assert_eq!(git_blob_sha1_hex(RETAINED_SOURCES), RETAINED_SOURCES_BLOB); - assert_eq!(git_blob_sha1_hex(RETAINED_SCHEMA), RETAINED_SCHEMA_BLOB); +fn repository_root() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..") +} + +fn git_object_bytes(revision: &str, path: &str, expected_blob: &str) -> Vec { + let root = repository_root(); + let spec = format!("{revision}:{path}"); + let resolved = Command::new("git") + .arg("-C") + .arg(&root) + .args(["rev-parse", &spec]) + .output() + .expect("run git rev-parse"); + assert!( + resolved.status.success(), + "git rev-parse failed for {spec}: {}", + String::from_utf8_lossy(&resolved.stderr) + ); + let observed_blob = String::from_utf8(resolved.stdout) + .expect("git rev-parse UTF-8") + .trim() + .to_owned(); + assert_eq!( + observed_blob, expected_blob, + "canonical Git object identity drifted for {spec}" + ); + + let object = Command::new("git") + .arg("-C") + .arg(&root) + .args(["cat-file", "blob", expected_blob]) + .output() + .expect("run git cat-file"); + assert!( + object.status.success(), + "git cat-file failed for {expected_blob}: {}", + String::from_utf8_lossy(&object.stderr) + ); + assert_eq!( + git_blob_sha1_hex(&object.stdout), + expected_blob, + "Git object bytes do not reproduce expected blob identity" + ); + object.stdout +} + +fn canonical_retained_contract() -> (Vec, Vec) { + ( + git_object_bytes(MAIN_SHA, RETAINED_SOURCES_PATH, RETAINED_SOURCES_BLOB), + git_object_bytes(MAIN_SHA, RETAINED_SCHEMA_PATH, RETAINED_SCHEMA_BLOB), + ) +} + +fn canonical_cf06_sources() -> (Vec, Vec, Vec) { + ( + git_object_bytes(MAIN_SHA, ORACLE_MODEL_PATH, ORACLE_MODEL_BLOB), + git_object_bytes(MAIN_SHA, CF06_DONOR_PATH, CF06_DONOR_BLOB), + git_object_bytes(MAIN_SHA, CF06_WORKFLOW_PATH, CF06_WORKFLOW_BLOB), + ) } fn build_baseline() -> authority::AuthorityBaseline { - assert_canonical_contract_objects(); - let retained = validate_and_parse(RETAINED_SOURCES, RETAINED_SCHEMA).unwrap(); + let (retained_sources, retained_schema) = canonical_retained_contract(); + let retained = validate_and_parse(&retained_sources, &retained_schema).unwrap(); + assert_eq!(retained.cf10.retained_head, RETAINED_HEAD); + let run = parse_json_no_duplicates(RETAINED_RUN).unwrap(); verify_workflow_run(&retained, &run).unwrap(); let artifacts = parse_json_no_duplicates(RETAINED_ARTIFACTS).unwrap(); verify_artifacts(&retained, &artifacts).unwrap(); + + let retained_manifest = git_object_bytes( + RETAINED_HEAD, + &retained.cf10.manifest.path, + &retained.cf10.manifest.git_blob_sha, + ); + let retained_donor = git_object_bytes( + RETAINED_HEAD, + &retained.cf10.donor.path, + &retained.cf10.donor.git_blob_sha, + ); let retained_projection = - project_retained(&retained, RETAINED_MANIFEST, RETAINED_DONOR).unwrap(); + project_retained(&retained, &retained_manifest, &retained_donor).unwrap(); + let assurance = parse_json_no_duplicates(ASSURANCE_RULESET).unwrap(); let review = parse_json_no_duplicates(REVIEW_RULESET).unwrap(); + let (oracle_model, cf06_donor, cf06_workflow) = canonical_cf06_sources(); project_authority( MAIN_SHA, @@ -66,19 +137,19 @@ fn build_baseline() -> authority::AuthorityBaseline { &review, [ Cf06Source { - path: "crates/commandf-pkg/src/oracle_model.rs", - git_blob_sha: "9046546a86061961cf3e17f3f1880165625edea8", - bytes: ORACLE_MODEL, + path: ORACLE_MODEL_PATH, + git_blob_sha: ORACLE_MODEL_BLOB, + bytes: &oracle_model, }, Cf06Source { - path: "donors/hl7-fhir-validator-6.10.2.yaml", - git_blob_sha: "9add2dad45cb8958c9304d38e29950ed1f769990", - bytes: CF06_DONOR, + path: CF06_DONOR_PATH, + git_blob_sha: CF06_DONOR_BLOB, + bytes: &cf06_donor, }, Cf06Source { - path: ".github/workflows/cf06-oracle.yml", - git_blob_sha: "664e303983d2ef85aad934cbef2c14d63744e0ee", - bytes: CF06_WORKFLOW, + path: CF06_WORKFLOW_PATH, + git_blob_sha: CF06_WORKFLOW_BLOB, + bytes: &cf06_workflow, }, ], retained_projection, @@ -95,21 +166,23 @@ fn duplicate_probe(bytes: &[u8]) -> Vec { #[test] fn retained_schema_rejects_candidate_url_authority() { - let mut value: Value = parse_json_no_duplicates(RETAINED_SOURCES).unwrap(); + let (retained_sources, retained_schema) = canonical_retained_contract(); + let mut value: Value = parse_json_no_duplicates(&retained_sources).unwrap(); value.as_object_mut().unwrap().insert( "url".to_owned(), Value::String("https://example.invalid".to_owned()), ); let bytes = serde_json::to_vec(&value).unwrap(); - let error = validate_and_parse(&bytes, RETAINED_SCHEMA).unwrap_err(); + let error = validate_and_parse(&bytes, &retained_schema).unwrap_err(); assert!(error.to_string().contains("unknown field")); } #[test] fn retained_contract_rejects_duplicate_semantic_keys_before_schema_validation() { + let (retained_sources, retained_schema) = canonical_retained_contract(); let mut duplicate = br#"{"schema":"forged","#.to_vec(); - duplicate.extend_from_slice(&RETAINED_SOURCES[1..]); - let error = validate_and_parse(&duplicate, RETAINED_SCHEMA).unwrap_err(); + duplicate.extend_from_slice(&retained_sources[1..]); + let error = validate_and_parse(&duplicate, &retained_schema).unwrap_err(); assert!(error .to_string() .contains("duplicate JSON object key \"schema\"")); @@ -132,8 +205,8 @@ fn authority_api_inputs_reject_duplicate_keys_before_projection() { #[test] fn retained_locator_plan_reconstructs_frozen_github_urls() { - assert_canonical_contract_objects(); - let retained = validate_and_parse(RETAINED_SOURCES, RETAINED_SCHEMA).unwrap(); + let (retained_sources, retained_schema) = canonical_retained_contract(); + let retained = validate_and_parse(&retained_sources, &retained_schema).unwrap(); let plan = locator_plan(&retained).unwrap(); assert_eq!( @@ -176,7 +249,8 @@ fn retained_locator_plan_reconstructs_frozen_github_urls() { #[test] fn retained_run_binding_rejects_wrong_event() { - let retained = validate_and_parse(RETAINED_SOURCES, RETAINED_SCHEMA).unwrap(); + let (retained_sources, retained_schema) = canonical_retained_contract(); + let retained = validate_and_parse(&retained_sources, &retained_schema).unwrap(); let mut run = parse_json_no_duplicates(RETAINED_RUN).unwrap(); run.as_object_mut() .unwrap() @@ -187,7 +261,8 @@ fn retained_run_binding_rejects_wrong_event() { #[test] fn retained_artifact_binding_rejects_wrong_digest() { - let retained = validate_and_parse(RETAINED_SOURCES, RETAINED_SCHEMA).unwrap(); + let (retained_sources, retained_schema) = canonical_retained_contract(); + let retained = validate_and_parse(&retained_sources, &retained_schema).unwrap(); let mut artifacts = parse_json_no_duplicates(RETAINED_ARTIFACTS).unwrap(); artifacts["artifacts"][0]["digest"] = Value::String( "sha256:0000000000000000000000000000000000000000000000000000000000000000".to_owned(), @@ -198,11 +273,21 @@ fn retained_artifact_binding_rejects_wrong_digest() { #[test] fn retained_projection_rejects_candidate_controlled_manifest_bytes() { - let retained = validate_and_parse(RETAINED_SOURCES, RETAINED_SCHEMA).unwrap(); - let mut manifest = RETAINED_MANIFEST.to_vec(); + let (retained_sources, retained_schema) = canonical_retained_contract(); + let retained = validate_and_parse(&retained_sources, &retained_schema).unwrap(); + let mut manifest = git_object_bytes( + RETAINED_HEAD, + &retained.cf10.manifest.path, + &retained.cf10.manifest.git_blob_sha, + ); + let donor = git_object_bytes( + RETAINED_HEAD, + &retained.cf10.donor.path, + &retained.cf10.donor.git_blob_sha, + ); let index = manifest.iter().position(|byte| *byte == b'C').unwrap(); manifest[index] = b'X'; - let error = project_retained(&retained, &manifest, RETAINED_DONOR).unwrap_err(); + let error = project_retained(&retained, &manifest, &donor).unwrap_err(); assert!(error .to_string() .contains("retained manifest Git blob mismatch")); @@ -219,28 +304,28 @@ fn assurance_projection_rejects_wrong_required_check_app() { #[test] fn cf06_projection_rejects_candidate_controlled_source_bytes() { - let altered = ORACLE_MODEL + let (mut oracle_model, cf06_donor, cf06_workflow) = canonical_cf06_sources(); + let altered = oracle_model .windows(authority::CF06_SOURCE_COMMIT.len()) .position(|window| window == authority::CF06_SOURCE_COMMIT.as_bytes()) .unwrap(); - let mut bytes = ORACLE_MODEL.to_vec(); - bytes[altered] = b'0'; + oracle_model[altered] = b'0'; let error = project_cf06([ Cf06Source { - path: "crates/commandf-pkg/src/oracle_model.rs", - git_blob_sha: "9046546a86061961cf3e17f3f1880165625edea8", - bytes: &bytes, + path: ORACLE_MODEL_PATH, + git_blob_sha: ORACLE_MODEL_BLOB, + bytes: &oracle_model, }, Cf06Source { - path: "donors/hl7-fhir-validator-6.10.2.yaml", - git_blob_sha: "9add2dad45cb8958c9304d38e29950ed1f769990", - bytes: CF06_DONOR, + path: CF06_DONOR_PATH, + git_blob_sha: CF06_DONOR_BLOB, + bytes: &cf06_donor, }, Cf06Source { - path: ".github/workflows/cf06-oracle.yml", - git_blob_sha: "664e303983d2ef85aad934cbef2c14d63744e0ee", - bytes: CF06_WORKFLOW, + path: CF06_WORKFLOW_PATH, + git_blob_sha: CF06_WORKFLOW_BLOB, + bytes: &cf06_workflow, }, ]) .unwrap_err(); From 31fd605e545874b68620845b071ef4d2139ffa0c Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 14:48:11 +0300 Subject: [PATCH 16/37] fix(af02): satisfy strict clippy --- tools/af02-verifier/src/canonical.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/af02-verifier/src/canonical.rs b/tools/af02-verifier/src/canonical.rs index 18c78e65..6a4c83e0 100644 --- a/tools/af02-verifier/src/canonical.rs +++ b/tools/af02-verifier/src/canonical.rs @@ -2,7 +2,6 @@ use std::collections::BTreeSet; use std::fmt; use serde::de::{self, DeserializeSeed, MapAccess, SeqAccess, Visitor}; -use serde::Deserializer as _; use serde_json::{Map, Number, Value}; use sha2::{Digest, Sha256}; use thiserror::Error; From 016bb5e8693ddae0d6174208099474d07495ceff Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 14:48:23 +0300 Subject: [PATCH 17/37] ci(af02): run one-shot formatter --- .github/workflows/af02-format-fix.yml | 39 +++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/af02-format-fix.yml diff --git a/.github/workflows/af02-format-fix.yml b/.github/workflows/af02-format-fix.yml new file mode 100644 index 00000000..6062d34b --- /dev/null +++ b/.github/workflows/af02-format-fix.yml @@ -0,0 +1,39 @@ +name: af02-format-fix + +on: + push: + branches: + - feat/af02-a0-authority-reconstruction + +permissions: + contents: write + +jobs: + format: + if: github.repository == 'TheHalfMoon/commandF' + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 + with: + persist-credentials: true + - uses: dtolnay/rust-toolchain@032958afbdc797a9164d3bc0b56325c1308924a5 + with: + components: rustfmt + - name: Format and remove temporary workflow + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + cargo fmt --all + rm .github/workflows/af02-format-fix.yml + git add -A + git diff --cached --check + if git diff --cached --quiet; then + echo 'formatter produced no changes' >&2 + exit 1 + fi + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git commit -m 'style(af02): format Git-object reconstruction' + git push origin "HEAD:${GITHUB_REF_NAME}" From 91016029a60a896af8f5cb5006094c4244c56139 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 11:50:40 +0000 Subject: [PATCH 18/37] style(af02): format Git-object reconstruction --- .github/workflows/af02-format-fix.yml | 39 --------------------------- 1 file changed, 39 deletions(-) delete mode 100644 .github/workflows/af02-format-fix.yml diff --git a/.github/workflows/af02-format-fix.yml b/.github/workflows/af02-format-fix.yml deleted file mode 100644 index 6062d34b..00000000 --- a/.github/workflows/af02-format-fix.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: af02-format-fix - -on: - push: - branches: - - feat/af02-a0-authority-reconstruction - -permissions: - contents: write - -jobs: - format: - if: github.repository == 'TheHalfMoon/commandF' - runs-on: ubuntu-24.04 - timeout-minutes: 10 - steps: - - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 - with: - persist-credentials: true - - uses: dtolnay/rust-toolchain@032958afbdc797a9164d3bc0b56325c1308924a5 - with: - components: rustfmt - - name: Format and remove temporary workflow - shell: bash - run: | - set -euo pipefail - test "$(git rev-parse HEAD)" = "$GITHUB_SHA" - cargo fmt --all - rm .github/workflows/af02-format-fix.yml - git add -A - git diff --cached --check - if git diff --cached --quiet; then - echo 'formatter produced no changes' >&2 - exit 1 - fi - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git commit -m 'style(af02): format Git-object reconstruction' - git push origin "HEAD:${GITHUB_REF_NAME}" From b550e263b4668b6603c4e690f1f7a4d1168d438d Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 14:51:10 +0300 Subject: [PATCH 19/37] ci(af02): qualify canonical Git reconstruction From 498c6ef64da377fc13842951d9e90874268eb3e8 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 16:22:23 +0300 Subject: [PATCH 20/37] test(af02): fetch pinned authority commits fail closed --- .../tests/af02_authority_reconstruction.rs | 93 ++++++++++++++++++- 1 file changed, 91 insertions(+), 2 deletions(-) diff --git a/crates/commandf-pkg/tests/af02_authority_reconstruction.rs b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs index 2db17b96..e2ab85d4 100644 --- a/crates/commandf-pkg/tests/af02_authority_reconstruction.rs +++ b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs @@ -6,8 +6,9 @@ mod canonical; mod retained; use std::fs; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::process::Command; +use std::sync::{Mutex, OnceLock}; use authority::{project_assurance_ruleset, project_authority, project_cf06, Cf06Source}; use canonical::{canonical_json_bytes, git_blob_sha1_hex, parse_json_no_duplicates}; @@ -42,12 +43,100 @@ const RETAINED_RUN: &[u8] = const RETAINED_ARTIFACTS: &[u8] = include_bytes!("../../../tools/af02-verifier/tests/fixtures/cf10-artifacts.json"); +static GIT_FETCH_LOCK: OnceLock> = OnceLock::new(); + fn repository_root() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..") } +fn pinned_commit_available(root: &Path, revision: &str) -> bool { + let commit = format!("{revision}^{{commit}}"); + Command::new("git") + .arg("-C") + .arg(root) + .args(["cat-file", "-e", &commit]) + .status() + .expect("run git cat-file commit probe") + .success() +} + +fn ensure_pinned_commit_available(root: &Path, revision: &str) { + if pinned_commit_available(root, revision) { + return; + } + + let fetch_lock = GIT_FETCH_LOCK.get_or_init(|| Mutex::new(())); + let _guard = fetch_lock.lock().expect("Git fetch mutex poisoned"); + if pinned_commit_available(root, revision) { + return; + } + + let remote = Command::new("git") + .arg("-C") + .arg(root) + .args(["remote", "get-url", "origin"]) + .output() + .expect("read Git origin URL"); + assert!( + remote.status.success(), + "git remote get-url origin failed: {}", + String::from_utf8_lossy(&remote.stderr) + ); + let origin = String::from_utf8(remote.stdout) + .expect("Git origin URL UTF-8") + .trim() + .trim_end_matches('/') + .trim_end_matches(".git") + .to_owned(); + assert!( + origin == "https://github.com/TheHalfMoon/commandF" + || origin == "git@github.com:TheHalfMoon/commandF", + "refusing to fetch authority objects from non-canonical origin {origin}" + ); + + let fetched = Command::new("git") + .arg("-C") + .arg(root) + .args([ + "fetch", + "--no-tags", + "--no-write-fetch-head", + "--depth=1", + "origin", + revision, + ]) + .output() + .expect("fetch pinned authority commit"); + assert!( + fetched.status.success(), + "git fetch failed for pinned authority commit {revision}: {}", + String::from_utf8_lossy(&fetched.stderr) + ); + + let commit = format!("{revision}^{{commit}}"); + let resolved = Command::new("git") + .arg("-C") + .arg(root) + .args(["rev-parse", "--verify", &commit]) + .output() + .expect("verify fetched authority commit"); + assert!( + resolved.status.success(), + "git rev-parse failed for fetched authority commit {revision}: {}", + String::from_utf8_lossy(&resolved.stderr) + ); + assert_eq!( + String::from_utf8(resolved.stdout) + .expect("fetched authority commit UTF-8") + .trim(), + revision, + "fetched authority commit identity drifted" + ); +} + fn git_object_bytes(revision: &str, path: &str, expected_blob: &str) -> Vec { let root = repository_root(); + ensure_pinned_commit_available(&root, revision); let spec = format!("{revision}:{path}"); let resolved = Command::new("git") .arg("-C") @@ -347,4 +436,4 @@ fn authority_baseline_v2_matches_canonical_snapshot() { String::from_utf8(generated).unwrap() ), } -} +} \ No newline at end of file From 64da11abc1bcd9614e3f27d3b292f7d4fe74b5f7 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 16:23:37 +0300 Subject: [PATCH 21/37] ci(af02): run one-shot formatter --- .github/workflows/af02-format-fix.yml | 39 +++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/af02-format-fix.yml diff --git a/.github/workflows/af02-format-fix.yml b/.github/workflows/af02-format-fix.yml new file mode 100644 index 00000000..661acaa0 --- /dev/null +++ b/.github/workflows/af02-format-fix.yml @@ -0,0 +1,39 @@ +name: af02-format-fix + +on: + push: + branches: + - feat/af02-a0-authority-reconstruction + +permissions: + contents: write + +jobs: + format: + if: github.repository == 'TheHalfMoon/commandF' + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 + with: + persist-credentials: true + - uses: dtolnay/rust-toolchain@032958afbdc797a9164d3bc0b56325c1308924a5 + with: + components: rustfmt + - name: Format and remove temporary workflow + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + cargo fmt --all + rm .github/workflows/af02-format-fix.yml + git add -A + git diff --cached --check + if git diff --cached --quiet; then + echo 'formatter produced no changes' >&2 + exit 1 + fi + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git commit -m 'style(af02): format pinned-commit acquisition' + git push origin "HEAD:${GITHUB_REF_NAME}" \ No newline at end of file From 4f7d196c13304a918ad2e6abfd8ae3758134f465 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 13:23:53 +0000 Subject: [PATCH 22/37] style(af02): format pinned-commit acquisition --- .github/workflows/af02-format-fix.yml | 39 ------------------- .../tests/af02_authority_reconstruction.rs | 2 +- 2 files changed, 1 insertion(+), 40 deletions(-) delete mode 100644 .github/workflows/af02-format-fix.yml diff --git a/.github/workflows/af02-format-fix.yml b/.github/workflows/af02-format-fix.yml deleted file mode 100644 index 661acaa0..00000000 --- a/.github/workflows/af02-format-fix.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: af02-format-fix - -on: - push: - branches: - - feat/af02-a0-authority-reconstruction - -permissions: - contents: write - -jobs: - format: - if: github.repository == 'TheHalfMoon/commandF' - runs-on: ubuntu-24.04 - timeout-minutes: 10 - steps: - - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 - with: - persist-credentials: true - - uses: dtolnay/rust-toolchain@032958afbdc797a9164d3bc0b56325c1308924a5 - with: - components: rustfmt - - name: Format and remove temporary workflow - shell: bash - run: | - set -euo pipefail - test "$(git rev-parse HEAD)" = "$GITHUB_SHA" - cargo fmt --all - rm .github/workflows/af02-format-fix.yml - git add -A - git diff --cached --check - if git diff --cached --quiet; then - echo 'formatter produced no changes' >&2 - exit 1 - fi - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git commit -m 'style(af02): format pinned-commit acquisition' - git push origin "HEAD:${GITHUB_REF_NAME}" \ No newline at end of file diff --git a/crates/commandf-pkg/tests/af02_authority_reconstruction.rs b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs index e2ab85d4..e496510a 100644 --- a/crates/commandf-pkg/tests/af02_authority_reconstruction.rs +++ b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs @@ -436,4 +436,4 @@ fn authority_baseline_v2_matches_canonical_snapshot() { String::from_utf8(generated).unwrap() ), } -} \ No newline at end of file +} From 16da6154f314c0f9c4c3ff46cd5b5aace02ed3bb Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 16:25:38 +0300 Subject: [PATCH 23/37] ci(af02): qualify formatted exact head From 3c850f4140f2da2c642be3df3faf5a1f03f16818 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 16:35:21 +0300 Subject: [PATCH 24/37] ci(af02): patch live authority reconstruction --- .github/workflows/af02-live-authority-fix.yml | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 .github/workflows/af02-live-authority-fix.yml diff --git a/.github/workflows/af02-live-authority-fix.yml b/.github/workflows/af02-live-authority-fix.yml new file mode 100644 index 00000000..ba039366 --- /dev/null +++ b/.github/workflows/af02-live-authority-fix.yml @@ -0,0 +1,101 @@ +name: af02-live-authority-fix + +on: + push: + branches: + - feat/af02-a0-authority-reconstruction + +permissions: + contents: write + +jobs: + patch: + if: github.repository == 'TheHalfMoon/commandF' + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 + with: + persist-credentials: true + - uses: dtolnay/rust-toolchain@032958afbdc797a9164d3bc0b56325c1308924a5 + with: + components: rustfmt + - name: Bind authority reconstruction to live GitHub inputs + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + python3 - <<'PY' + from pathlib import Path + + path = Path('crates/commandf-pkg/tests/af02_authority_reconstruction.rs') + text = path.read_text() + + marker = '''fn build_baseline() -> authority::AuthorityBaseline {\n''' + helper = r'''fn github_api_bytes(url: &str) -> Vec { + const CANONICAL_API_PREFIX: &str = "https://api.github.com/repos/TheHalfMoon/commandF/"; + assert!( + url.starts_with(CANONICAL_API_PREFIX), + "refusing non-canonical GitHub authority URL {url}" + ); + + let response = Command::new("curl") + .args([ + "--fail", + "--silent", + "--show-error", + "--proto", + "=https", + "--tlsv1.2", + "--connect-timeout", + "10", + "--max-time", + "30", + "--header", + "Accept: application/vnd.github+json", + "--header", + "X-GitHub-Api-Version: 2022-11-28", + "--header", + "User-Agent: commandF-af02-authority-reconstruction", + url, + ]) + .output() + .expect("fetch live GitHub authority response with curl"); + assert!( + response.status.success(), + "GitHub authority request failed for {url}: {}", + String::from_utf8_lossy(&response.stderr) + ); + response.stdout + } + + fn build_baseline() -> authority::AuthorityBaseline { + ''' + if marker not in text: + raise SystemExit('build_baseline marker missing') + text = text.replace(marker, helper, 1) + + old_retained = ''' let run = parse_json_no_duplicates(RETAINED_RUN).unwrap();\n verify_workflow_run(&retained, &run).unwrap();\n let artifacts = parse_json_no_duplicates(RETAINED_ARTIFACTS).unwrap();\n verify_artifacts(&retained, &artifacts).unwrap();\n''' + new_retained = ''' let plan = locator_plan(&retained).unwrap();\n assert_eq!(\n plan.workflow_run,\n "https://api.github.com/repos/TheHalfMoon/commandF/actions/runs/31916124080"\n );\n assert_eq!(\n plan.workflow_run_artifacts,\n "https://api.github.com/repos/TheHalfMoon/commandF/actions/runs/31916124080/artifacts"\n );\n let run_bytes = github_api_bytes(&plan.workflow_run);\n let run = parse_json_no_duplicates(&run_bytes).unwrap();\n verify_workflow_run(&retained, &run).unwrap();\n let artifact_bytes = github_api_bytes(&plan.workflow_run_artifacts);\n let artifacts = parse_json_no_duplicates(&artifact_bytes).unwrap();\n verify_artifacts(&retained, &artifacts).unwrap();\n''' + if old_retained not in text: + raise SystemExit('retained fixture baseline block missing') + text = text.replace(old_retained, new_retained, 1) + + old_rulesets = ''' let assurance = parse_json_no_duplicates(ASSURANCE_RULESET).unwrap();\n let review = parse_json_no_duplicates(REVIEW_RULESET).unwrap();\n''' + new_rulesets = ''' assert_eq!(authority::ASSURANCE_RULESET_ID, 21652953);\n assert_eq!(authority::REVIEW_RULESET_ID, 21652974);\n let assurance_bytes = github_api_bytes(\n "https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652953",\n );\n let review_bytes = github_api_bytes(\n "https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652974",\n );\n let assurance = parse_json_no_duplicates(&assurance_bytes).unwrap();\n let review = parse_json_no_duplicates(&review_bytes).unwrap();\n''' + if old_rulesets not in text: + raise SystemExit('ruleset fixture baseline block missing') + text = text.replace(old_rulesets, new_rulesets, 1) + + path.write_text(text) + PY + cargo fmt --all + rm .github/workflows/af02-live-authority-fix.yml + git add -A + git diff --cached --check + git diff --cached -- crates/commandf-pkg/tests/af02_authority_reconstruction.rs + test "$(git diff --cached --name-only | sort)" = "crates/commandf-pkg/tests/af02_authority_reconstruction.rs" + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git commit -m 'test(af02): bind baseline to live authority APIs' + git push origin "HEAD:${GITHUB_REF_NAME}" \ No newline at end of file From 7280da5e37e44c664652b4780c65c5e2d407c304 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 16:37:38 +0300 Subject: [PATCH 25/37] ci(af02): fix one-shot staged-path guard --- .github/workflows/af02-live-authority-fix.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/af02-live-authority-fix.yml b/.github/workflows/af02-live-authority-fix.yml index ba039366..94dd5afc 100644 --- a/.github/workflows/af02-live-authority-fix.yml +++ b/.github/workflows/af02-live-authority-fix.yml @@ -94,7 +94,8 @@ jobs: git add -A git diff --cached --check git diff --cached -- crates/commandf-pkg/tests/af02_authority_reconstruction.rs - test "$(git diff --cached --name-only | sort)" = "crates/commandf-pkg/tests/af02_authority_reconstruction.rs" + expected_paths=$'.github/workflows/af02-live-authority-fix.yml\ncrates/commandf-pkg/tests/af02_authority_reconstruction.rs' + test "$(git diff --cached --name-only | sort)" = "$expected_paths" git config user.name 'github-actions[bot]' git config user.email '41898282+github-actions[bot]@users.noreply.github.com' git commit -m 'test(af02): bind baseline to live authority APIs' From bfde089b652f07087a84646612f8dfd65e39b02a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 13:37:59 +0000 Subject: [PATCH 26/37] test(af02): bind baseline to live authority APIs --- .github/workflows/af02-live-authority-fix.yml | 102 ------------------ .../tests/af02_authority_reconstruction.rs | 62 ++++++++++- 2 files changed, 58 insertions(+), 106 deletions(-) delete mode 100644 .github/workflows/af02-live-authority-fix.yml diff --git a/.github/workflows/af02-live-authority-fix.yml b/.github/workflows/af02-live-authority-fix.yml deleted file mode 100644 index 94dd5afc..00000000 --- a/.github/workflows/af02-live-authority-fix.yml +++ /dev/null @@ -1,102 +0,0 @@ -name: af02-live-authority-fix - -on: - push: - branches: - - feat/af02-a0-authority-reconstruction - -permissions: - contents: write - -jobs: - patch: - if: github.repository == 'TheHalfMoon/commandF' - runs-on: ubuntu-24.04 - timeout-minutes: 10 - steps: - - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 - with: - persist-credentials: true - - uses: dtolnay/rust-toolchain@032958afbdc797a9164d3bc0b56325c1308924a5 - with: - components: rustfmt - - name: Bind authority reconstruction to live GitHub inputs - shell: bash - run: | - set -euo pipefail - test "$(git rev-parse HEAD)" = "$GITHUB_SHA" - python3 - <<'PY' - from pathlib import Path - - path = Path('crates/commandf-pkg/tests/af02_authority_reconstruction.rs') - text = path.read_text() - - marker = '''fn build_baseline() -> authority::AuthorityBaseline {\n''' - helper = r'''fn github_api_bytes(url: &str) -> Vec { - const CANONICAL_API_PREFIX: &str = "https://api.github.com/repos/TheHalfMoon/commandF/"; - assert!( - url.starts_with(CANONICAL_API_PREFIX), - "refusing non-canonical GitHub authority URL {url}" - ); - - let response = Command::new("curl") - .args([ - "--fail", - "--silent", - "--show-error", - "--proto", - "=https", - "--tlsv1.2", - "--connect-timeout", - "10", - "--max-time", - "30", - "--header", - "Accept: application/vnd.github+json", - "--header", - "X-GitHub-Api-Version: 2022-11-28", - "--header", - "User-Agent: commandF-af02-authority-reconstruction", - url, - ]) - .output() - .expect("fetch live GitHub authority response with curl"); - assert!( - response.status.success(), - "GitHub authority request failed for {url}: {}", - String::from_utf8_lossy(&response.stderr) - ); - response.stdout - } - - fn build_baseline() -> authority::AuthorityBaseline { - ''' - if marker not in text: - raise SystemExit('build_baseline marker missing') - text = text.replace(marker, helper, 1) - - old_retained = ''' let run = parse_json_no_duplicates(RETAINED_RUN).unwrap();\n verify_workflow_run(&retained, &run).unwrap();\n let artifacts = parse_json_no_duplicates(RETAINED_ARTIFACTS).unwrap();\n verify_artifacts(&retained, &artifacts).unwrap();\n''' - new_retained = ''' let plan = locator_plan(&retained).unwrap();\n assert_eq!(\n plan.workflow_run,\n "https://api.github.com/repos/TheHalfMoon/commandF/actions/runs/31916124080"\n );\n assert_eq!(\n plan.workflow_run_artifacts,\n "https://api.github.com/repos/TheHalfMoon/commandF/actions/runs/31916124080/artifacts"\n );\n let run_bytes = github_api_bytes(&plan.workflow_run);\n let run = parse_json_no_duplicates(&run_bytes).unwrap();\n verify_workflow_run(&retained, &run).unwrap();\n let artifact_bytes = github_api_bytes(&plan.workflow_run_artifacts);\n let artifacts = parse_json_no_duplicates(&artifact_bytes).unwrap();\n verify_artifacts(&retained, &artifacts).unwrap();\n''' - if old_retained not in text: - raise SystemExit('retained fixture baseline block missing') - text = text.replace(old_retained, new_retained, 1) - - old_rulesets = ''' let assurance = parse_json_no_duplicates(ASSURANCE_RULESET).unwrap();\n let review = parse_json_no_duplicates(REVIEW_RULESET).unwrap();\n''' - new_rulesets = ''' assert_eq!(authority::ASSURANCE_RULESET_ID, 21652953);\n assert_eq!(authority::REVIEW_RULESET_ID, 21652974);\n let assurance_bytes = github_api_bytes(\n "https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652953",\n );\n let review_bytes = github_api_bytes(\n "https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652974",\n );\n let assurance = parse_json_no_duplicates(&assurance_bytes).unwrap();\n let review = parse_json_no_duplicates(&review_bytes).unwrap();\n''' - if old_rulesets not in text: - raise SystemExit('ruleset fixture baseline block missing') - text = text.replace(old_rulesets, new_rulesets, 1) - - path.write_text(text) - PY - cargo fmt --all - rm .github/workflows/af02-live-authority-fix.yml - git add -A - git diff --cached --check - git diff --cached -- crates/commandf-pkg/tests/af02_authority_reconstruction.rs - expected_paths=$'.github/workflows/af02-live-authority-fix.yml\ncrates/commandf-pkg/tests/af02_authority_reconstruction.rs' - test "$(git diff --cached --name-only | sort)" = "$expected_paths" - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git commit -m 'test(af02): bind baseline to live authority APIs' - git push origin "HEAD:${GITHUB_REF_NAME}" \ No newline at end of file diff --git a/crates/commandf-pkg/tests/af02_authority_reconstruction.rs b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs index e496510a..a85dab3b 100644 --- a/crates/commandf-pkg/tests/af02_authority_reconstruction.rs +++ b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs @@ -192,14 +192,62 @@ fn canonical_cf06_sources() -> (Vec, Vec, Vec) { ) } +fn github_api_bytes(url: &str) -> Vec { + const CANONICAL_API_PREFIX: &str = "https://api.github.com/repos/TheHalfMoon/commandF/"; + assert!( + url.starts_with(CANONICAL_API_PREFIX), + "refusing non-canonical GitHub authority URL {url}" + ); + + let response = Command::new("curl") + .args([ + "--fail", + "--silent", + "--show-error", + "--proto", + "=https", + "--tlsv1.2", + "--connect-timeout", + "10", + "--max-time", + "30", + "--header", + "Accept: application/vnd.github+json", + "--header", + "X-GitHub-Api-Version: 2022-11-28", + "--header", + "User-Agent: commandF-af02-authority-reconstruction", + url, + ]) + .output() + .expect("fetch live GitHub authority response with curl"); + assert!( + response.status.success(), + "GitHub authority request failed for {url}: {}", + String::from_utf8_lossy(&response.stderr) + ); + response.stdout +} + fn build_baseline() -> authority::AuthorityBaseline { let (retained_sources, retained_schema) = canonical_retained_contract(); let retained = validate_and_parse(&retained_sources, &retained_schema).unwrap(); assert_eq!(retained.cf10.retained_head, RETAINED_HEAD); - let run = parse_json_no_duplicates(RETAINED_RUN).unwrap(); + let plan = locator_plan(&retained).unwrap(); + assert_eq!( + plan.workflow_run, + "https://api.github.com/repos/TheHalfMoon/commandF/actions/runs/31916124080" + ); + assert_eq!( + plan.workflow_run_artifacts, + "https://api.github.com/repos/TheHalfMoon/commandF/actions/runs/31916124080/artifacts" + ); + let run_bytes = github_api_bytes(&plan.workflow_run); + let run = parse_json_no_duplicates(&run_bytes).unwrap(); verify_workflow_run(&retained, &run).unwrap(); - let artifacts = parse_json_no_duplicates(RETAINED_ARTIFACTS).unwrap(); + let artifact_bytes = github_api_bytes(&plan.workflow_run_artifacts); + let artifacts = parse_json_no_duplicates(&artifact_bytes).unwrap(); verify_artifacts(&retained, &artifacts).unwrap(); let retained_manifest = git_object_bytes( @@ -215,8 +263,14 @@ fn build_baseline() -> authority::AuthorityBaseline { let retained_projection = project_retained(&retained, &retained_manifest, &retained_donor).unwrap(); - let assurance = parse_json_no_duplicates(ASSURANCE_RULESET).unwrap(); - let review = parse_json_no_duplicates(REVIEW_RULESET).unwrap(); + assert_eq!(authority::ASSURANCE_RULESET_ID, 21652953); + assert_eq!(authority::REVIEW_RULESET_ID, 21652974); + let assurance_bytes = + github_api_bytes("https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652953"); + let review_bytes = + github_api_bytes("https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652974"); + let assurance = parse_json_no_duplicates(&assurance_bytes).unwrap(); + let review = parse_json_no_duplicates(&review_bytes).unwrap(); let (oracle_model, cf06_donor, cf06_workflow) = canonical_cf06_sources(); project_authority( From 513c4e9a9267c6248706f04af7d8a81f85f9cdb8 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 16:39:02 +0300 Subject: [PATCH 27/37] ci(af02): qualify live authority exact head From 89ac735bcde86e3081a7331c48daf3cbd55731ef Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 17:02:10 +0300 Subject: [PATCH 28/37] chore(ci): apply AF-02 immutable object fallback --- .github/workflows/af02-object-fallback.yml | 130 +++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 .github/workflows/af02-object-fallback.yml diff --git a/.github/workflows/af02-object-fallback.yml b/.github/workflows/af02-object-fallback.yml new file mode 100644 index 00000000..7b5bba24 --- /dev/null +++ b/.github/workflows/af02-object-fallback.yml @@ -0,0 +1,130 @@ +name: AF-02 immutable object fallback repair + +on: + push: + branches: + - feat/af02-a0-authority-reconstruction + paths: + - .github/workflows/af02-object-fallback.yml + +permissions: + contents: write + +jobs: + repair: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@fbc6f399b9d5de9e5dd0a1f15dc4164a1b3f47c0 + with: + ref: feat/af02-a0-authority-reconstruction + - uses: dtolnay/rust-toolchain@032958f5c5cdd45579c67c5e92f06e2f1f949122 + with: + toolchain: stable + - name: Apply fail-closed immutable GitHub object fallback + shell: bash + run: | + python3 - <<'PY' + from pathlib import Path + + path = Path('crates/commandf-pkg/tests/af02_authority_reconstruction.rs') + text = path.read_text() + marker = 'fn git_object_bytes(revision: &str, path: &str, expected_blob: &str) -> Vec {' + if marker not in text: + raise SystemExit('git_object_bytes marker missing') + + helper = r'''fn github_content_object_bytes(revision: &str, path: &str, expected_blob: &str) -> Vec { + assert!( + revision.len() == 40 + && revision + .bytes() + .all(|byte| matches!(byte, b'0'..=b'9' | b'a'..=b'f')), + "refusing non-immutable GitHub revision {revision}" + ); + assert!( + expected_blob.len() == 40 + && expected_blob + .bytes() + .all(|byte| matches!(byte, b'0'..=b'9' | b'a'..=b'f')), + "invalid expected Git blob identity {expected_blob}" + ); + assert!( + !path.starts_with('/') + && !path.split('/').any(|segment| segment.is_empty() || segment == "." || segment == "..") + && path.bytes().all(|byte| { + byte.is_ascii_alphanumeric() + || matches!(byte, b'/' | b'.' | b'_' | b'-') + }), + "refusing unsafe GitHub authority path {path}" + ); + + let url = format!( + "https://api.github.com/repos/TheHalfMoon/commandF/contents/{path}?ref={revision}" + ); + let response = Command::new("curl") + .args([ + "--fail", + "--silent", + "--show-error", + "--proto", + "=https", + "--tlsv1.2", + "--connect-timeout", + "10", + "--max-time", + "30", + "--header", + "Accept: application/vnd.github.raw+json", + "--header", + "X-GitHub-Api-Version: 2022-11-28", + "--header", + "User-Agent: commandF-af02-authority-reconstruction", + &url, + ]) + .output() + .expect("fetch immutable GitHub authority object with curl"); + assert!( + response.status.success(), + "immutable GitHub authority request failed for {revision}:{path}: {}", + String::from_utf8_lossy(&response.stderr) + ); + assert_eq!( + git_blob_sha1_hex(&response.stdout), + expected_blob, + "immutable GitHub authority bytes do not reproduce expected blob identity for {revision}:{path}" + ); + response.stdout + } + + ''' + text = text.replace(marker, helper + marker, 1) + + old = r''' assert!( + resolved.status.success(), + "git rev-parse failed for {spec}: {}", + String::from_utf8_lossy(&resolved.stderr) + ); + let observed_blob = String::from_utf8(resolved.stdout) +''' + new = r''' if !resolved.status.success() { + return github_content_object_bytes(revision, path, expected_blob); + } + let observed_blob = String::from_utf8(resolved.stdout) +''' + if old not in text: + raise SystemExit('rev-parse assertion block missing') + text = text.replace(old, new, 1) + path.write_text(text) + PY + cargo fmt --all + rm .github/workflows/af02-object-fallback.yml + git add crates/commandf-pkg/tests/af02_authority_reconstruction.rs .github/workflows/af02-object-fallback.yml + git diff --cached --check + paths="$(git diff --cached --name-only)" + test "$paths" = "crates/commandf-pkg/tests/af02_authority_reconstruction.rs" || { + printf 'unexpected staged paths:\n%s\n' "$paths" >&2 + exit 1 + } + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + git commit -m "fix(af02): bind historical objects through immutable API" + git push origin HEAD:feat/af02-a0-authority-reconstruction From 0ce5e2b7fa6abaffa0f8a8be543d68ee0b82bdc7 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 17:05:45 +0300 Subject: [PATCH 29/37] chore(ci): repair AF-02 fallback applicator --- .github/workflows/af02-object-fallback.yml | 105 ++++----------------- 1 file changed, 17 insertions(+), 88 deletions(-) diff --git a/.github/workflows/af02-object-fallback.yml b/.github/workflows/af02-object-fallback.yml index 7b5bba24..03753784 100644 --- a/.github/workflows/af02-object-fallback.yml +++ b/.github/workflows/af02-object-fallback.yml @@ -20,99 +20,27 @@ jobs: - uses: dtolnay/rust-toolchain@032958f5c5cdd45579c67c5e92f06e2f1f949122 with: toolchain: stable - - name: Apply fail-closed immutable GitHub object fallback + - name: Apply immutable object fallback + env: + HELPER_B64: Zm4gZ2l0aHViX2NvbnRlbnRfb2JqZWN0X2J5dGVzKHJldmlzaW9uOiAmc3RyLCBwYXRoOiAmc3RyLCBleHBlY3RlZF9ibG9iOiAmc3RyKSAtPiBWZWM8dTg+IHsKICAgIGFzc2VydCEoCiAgICAgICAgcmV2aXNpb24ubGVuKCkgPT0gNDAKICAgICAgICAgICAgJiYgcmV2aXNpb24KICAgICAgICAgICAgICAgIC5ieXRlcygpCiAgICAgICAgICAgICAgICAuYWxsKHxieXRlfCBtYXRjaGVzIShieXRlLCBiJzAnLi49Yic5JyB8IGInYScuLj1iJ2YnKSksCiAgICAgICAgInJlZnVzaW5nIG5vbi1pbW11dGFibGUgR2l0SHViIHJldmlzaW9uIHtyZXZpc2lvbn0iCiAgICApOwogICAgYXNzZXJ0ISgKICAgICAgICBleHBlY3RlZF9ibG9iLmxlbigpID09IDQwCiAgICAgICAgICAgICYmIGV4cGVjdGVkX2Jsb2IKICAgICAgICAgICAgICAgIC5ieXRlcygpCiAgICAgICAgICAgICAgICAuYWxsKHxieXRlfCBtYXRjaGVzIShieXRlLCBiJzAnLi49Yic5JyB8IGInYScuLj1iJ2YnKSksCiAgICAgICAgImludmFsaWQgZXhwZWN0ZWQgR2l0IGJsb2IgaWRlbnRpdHkge2V4cGVjdGVkX2Jsb2J9IgogICAgKTsKICAgIGFzc2VydCEoCiAgICAgICAgIXBhdGguc3RhcnRzX3dpdGgoJy8nKQogICAgICAgICAgICAmJiAhcGF0aAogICAgICAgICAgICAgICAgLnNwbGl0KCcvJykKICAgICAgICAgICAgICAgIC5hbnkofHNlZ21lbnR8IHNlZ21lbnQuaXNfZW1wdHkoKSB8fCBzZWdtZW50ID09ICIuIiB8fCBzZWdtZW50ID09ICIuLiIpCiAgICAgICAgICAgICYmIHBhdGguYnl0ZXMoKS5hbGwofGJ5dGV8IHsKICAgICAgICAgICAgICAgIGJ5dGUuaXNfYXNjaWlfYWxwaGFudW1lcmljKCkgfHwgbWF0Y2hlcyEoYnl0ZSwgYicvJyB8IGInLicgfCBiJ18nIHwgYictJykKICAgICAgICAgICAgfSksCiAgICAgICAgInJlZnVzaW5nIHVuc2FmZSBHaXRIdWIgYXV0aG9yaXR5IHBhdGgge3BhdGh9IgogICAgKTsKCiAgICBsZXQgdXJsID0KICAgICAgICBmb3JtYXQhKCJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL1RoZUhhbGZNb29uL2NvbW1hbmRGL2NvbnRlbnRzL3twYXRofT9yZWY9e3JldmlzaW9ufSIpOwogICAgbGV0IHJlc3BvbnNlID0gQ29tbWFuZDo6bmV3KCJjdXJsIikKICAgICAgICAuYXJncyhbCiAgICAgICAgICAgICItLWZhaWwiLAogICAgICAgICAgICAiLS1zaWxlbnQiLAogICAgICAgICAgICAiLS1zaG93LWVycm9yIiwKICAgICAgICAgICAgIi0tcHJvdG8iLAogICAgICAgICAgICAiPWh0dHBzIiwKICAgICAgICAgICAgIi0tdGxzdjEuMiIsCiAgICAgICAgICAgICItLWNvbm5lY3QtdGltZW91dCIsCiAgICAgICAgICAgICIxMCIsCiAgICAgICAgICAgICItLW1heC10aW1lIiwKICAgICAgICAgICAgIjMwIiwKICAgICAgICAgICAgIi0taGVhZGVyIiwKICAgICAgICAgICAgIkFjY2VwdDogYXBwbGljYXRpb24vdm5kLmdpdGh1Yi5yYXcranNvbiIsCiAgICAgICAgICAgICItLWhlYWRlciIsCiAgICAgICAgICAgICJYLUdpdEh1Yi1BcGktVmVyc2lvbjogMjAyMi0xMS0yOCIsCiAgICAgICAgICAgICItLWhlYWRlciIsCiAgICAgICAgICAgICJVc2VyLUFnZW50OiBjb21tYW5kRi1hZjAyLWF1dGhvcml0eS1yZWNvbnN0cnVjdGlvbiIsCiAgICAgICAgICAgICZ1cmwsCiAgICAgICAgXSkKICAgICAgICAub3V0cHV0KCkKICAgICAgICAuZXhwZWN0KCJmZXRjaCBpbW11dGFibGUgR2l0SHViIGF1dGhvcml0eSBvYmplY3Qgd2l0aCBjdXJsIik7CiAgICBhc3NlcnQhKAogICAgICAgIHJlc3BvbnNlLnN0YXR1cy5zdWNjZXNzKCksCiAgICAgICAgImltbXV0YWJsZSBHaXRIdWIgYXV0aG9yaXR5IHJlcXVlc3QgZmFpbGVkIGZvciB7cmV2aXNpb259OntwYXRofToge30iLAogICAgICAgIFN0cmluZzo6ZnJvbV91dGY4X2xvc3N5KCZyZXNwb25zZS5zdGRlcnIpCiAgICApOwogICAgYXNzZXJ0X2VxISgKICAgICAgICBnaXRfYmxvYl9zaGExX2hleCgmcmVzcG9uc2Uuc3Rkb3V0KSwKICAgICAgICBleHBlY3RlZF9ibG9iLAogICAgICAgICJpbW11dGFibGUgR2l0SHViIGF1dGhvcml0eSBieXRlcyBkbyBub3QgcmVwcm9kdWNlIGV4cGVjdGVkIGJsb2IgaWRlbnRpdHkgZm9yIHtyZXZpc2lvbn06e3BhdGh9IgogICAgKTsKICAgIHJlc3BvbnNlLnN0ZG91dAp9Cgo= + OLD_B64: ICAgIGFzc2VydCEoCiAgICAgICAgcmVzb2x2ZWQuc3RhdHVzLnN1Y2Nlc3MoKSwKICAgICAgICAiZ2l0IHJldi1wYXJzZSBmYWlsZWQgZm9yIHtzcGVjfToge30iLAogICAgICAgIFN0cmluZzo6ZnJvbV91dGY4X2xvc3N5KCZyZXNvbHZlZC5zdGRlcnIpCiAgICApOwogICAgbGV0IG9ic2VydmVkX2Jsb2IgPSBTdHJpbmc6OmZyb21fdXRmOChyZXNvbHZlZC5zdGRvdXQpCg== + NEW_B64: ICAgIGlmICFyZXNvbHZlZC5zdGF0dXMuc3VjY2VzcygpIHsKICAgICAgICByZXR1cm4gZ2l0aHViX2NvbnRlbnRfb2JqZWN0X2J5dGVzKHJldmlzaW9uLCBwYXRoLCBleHBlY3RlZF9ibG9iKTsKICAgIH0KICAgIGxldCBvYnNlcnZlZF9ibG9iID0gU3RyaW5nOjpmcm9tX3V0ZjgocmVzb2x2ZWQuc3Rkb3V0KQo= shell: bash run: | python3 - <<'PY' + import base64 + import os from pathlib import Path - path = Path('crates/commandf-pkg/tests/af02_authority_reconstruction.rs') + path = Path("crates/commandf-pkg/tests/af02_authority_reconstruction.rs") text = path.read_text() - marker = 'fn git_object_bytes(revision: &str, path: &str, expected_blob: &str) -> Vec {' - if marker not in text: - raise SystemExit('git_object_bytes marker missing') - - helper = r'''fn github_content_object_bytes(revision: &str, path: &str, expected_blob: &str) -> Vec { - assert!( - revision.len() == 40 - && revision - .bytes() - .all(|byte| matches!(byte, b'0'..=b'9' | b'a'..=b'f')), - "refusing non-immutable GitHub revision {revision}" - ); - assert!( - expected_blob.len() == 40 - && expected_blob - .bytes() - .all(|byte| matches!(byte, b'0'..=b'9' | b'a'..=b'f')), - "invalid expected Git blob identity {expected_blob}" - ); - assert!( - !path.starts_with('/') - && !path.split('/').any(|segment| segment.is_empty() || segment == "." || segment == "..") - && path.bytes().all(|byte| { - byte.is_ascii_alphanumeric() - || matches!(byte, b'/' | b'.' | b'_' | b'-') - }), - "refusing unsafe GitHub authority path {path}" - ); - - let url = format!( - "https://api.github.com/repos/TheHalfMoon/commandF/contents/{path}?ref={revision}" - ); - let response = Command::new("curl") - .args([ - "--fail", - "--silent", - "--show-error", - "--proto", - "=https", - "--tlsv1.2", - "--connect-timeout", - "10", - "--max-time", - "30", - "--header", - "Accept: application/vnd.github.raw+json", - "--header", - "X-GitHub-Api-Version: 2022-11-28", - "--header", - "User-Agent: commandF-af02-authority-reconstruction", - &url, - ]) - .output() - .expect("fetch immutable GitHub authority object with curl"); - assert!( - response.status.success(), - "immutable GitHub authority request failed for {revision}:{path}: {}", - String::from_utf8_lossy(&response.stderr) - ); - assert_eq!( - git_blob_sha1_hex(&response.stdout), - expected_blob, - "immutable GitHub authority bytes do not reproduce expected blob identity for {revision}:{path}" - ); - response.stdout - } - - ''' - text = text.replace(marker, helper + marker, 1) - - old = r''' assert!( - resolved.status.success(), - "git rev-parse failed for {spec}: {}", - String::from_utf8_lossy(&resolved.stderr) - ); - let observed_blob = String::from_utf8(resolved.stdout) -''' - new = r''' if !resolved.status.success() { - return github_content_object_bytes(revision, path, expected_blob); - } - let observed_blob = String::from_utf8(resolved.stdout) -''' - if old not in text: - raise SystemExit('rev-parse assertion block missing') - text = text.replace(old, new, 1) + helper = base64.b64decode(os.environ["HELPER_B64"]).decode() + old = base64.b64decode(os.environ["OLD_B64"]).decode() + new = base64.b64decode(os.environ["NEW_B64"]).decode() + marker = "fn git_object_bytes(revision: &str, path: &str, expected_blob: &str) -> Vec {" + if text.count(marker) != 1 or text.count(old) != 1: + raise SystemExit("expected AF-02 patch anchors not found exactly once") + text = text.replace(marker, helper + marker, 1).replace(old, new, 1) path.write_text(text) PY cargo fmt --all @@ -120,7 +48,8 @@ jobs: git add crates/commandf-pkg/tests/af02_authority_reconstruction.rs .github/workflows/af02-object-fallback.yml git diff --cached --check paths="$(git diff --cached --name-only)" - test "$paths" = "crates/commandf-pkg/tests/af02_authority_reconstruction.rs" || { + expected="$(printf '%s\n%s' '.github/workflows/af02-object-fallback.yml' 'crates/commandf-pkg/tests/af02_authority_reconstruction.rs')" + test "$paths" = "$expected" || { printf 'unexpected staged paths:\n%s\n' "$paths" >&2 exit 1 } From c213c012e4a36eed7a0fc326bd402680d1c69a52 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 17:06:55 +0300 Subject: [PATCH 30/37] chore(ci): align AF-02 applicator action pins --- .github/workflows/af02-object-fallback.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/af02-object-fallback.yml b/.github/workflows/af02-object-fallback.yml index 03753784..30308392 100644 --- a/.github/workflows/af02-object-fallback.yml +++ b/.github/workflows/af02-object-fallback.yml @@ -12,14 +12,14 @@ permissions: jobs: repair: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@fbc6f399b9d5de9e5dd0a1f15dc4164a1b3f47c0 + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 with: ref: feat/af02-a0-authority-reconstruction - - uses: dtolnay/rust-toolchain@032958f5c5cdd45579c67c5e92f06e2f1f949122 + - uses: dtolnay/rust-toolchain@032958afbdc797a9164d3bc0b56325c1308924a5 with: - toolchain: stable + components: rustfmt - name: Apply immutable object fallback env: HELPER_B64: Zm4gZ2l0aHViX2NvbnRlbnRfb2JqZWN0X2J5dGVzKHJldmlzaW9uOiAmc3RyLCBwYXRoOiAmc3RyLCBleHBlY3RlZF9ibG9iOiAmc3RyKSAtPiBWZWM8dTg+IHsKICAgIGFzc2VydCEoCiAgICAgICAgcmV2aXNpb24ubGVuKCkgPT0gNDAKICAgICAgICAgICAgJiYgcmV2aXNpb24KICAgICAgICAgICAgICAgIC5ieXRlcygpCiAgICAgICAgICAgICAgICAuYWxsKHxieXRlfCBtYXRjaGVzIShieXRlLCBiJzAnLi49Yic5JyB8IGInYScuLj1iJ2YnKSksCiAgICAgICAgInJlZnVzaW5nIG5vbi1pbW11dGFibGUgR2l0SHViIHJldmlzaW9uIHtyZXZpc2lvbn0iCiAgICApOwogICAgYXNzZXJ0ISgKICAgICAgICBleHBlY3RlZF9ibG9iLmxlbigpID09IDQwCiAgICAgICAgICAgICYmIGV4cGVjdGVkX2Jsb2IKICAgICAgICAgICAgICAgIC5ieXRlcygpCiAgICAgICAgICAgICAgICAuYWxsKHxieXRlfCBtYXRjaGVzIShieXRlLCBiJzAnLi49Yic5JyB8IGInYScuLj1iJ2YnKSksCiAgICAgICAgImludmFsaWQgZXhwZWN0ZWQgR2l0IGJsb2IgaWRlbnRpdHkge2V4cGVjdGVkX2Jsb2J9IgogICAgKTsKICAgIGFzc2VydCEoCiAgICAgICAgIXBhdGguc3RhcnRzX3dpdGgoJy8nKQogICAgICAgICAgICAmJiAhcGF0aAogICAgICAgICAgICAgICAgLnNwbGl0KCcvJykKICAgICAgICAgICAgICAgIC5hbnkofHNlZ21lbnR8IHNlZ21lbnQuaXNfZW1wdHkoKSB8fCBzZWdtZW50ID09ICIuIiB8fCBzZWdtZW50ID09ICIuLiIpCiAgICAgICAgICAgICYmIHBhdGguYnl0ZXMoKS5hbGwofGJ5dGV8IHsKICAgICAgICAgICAgICAgIGJ5dGUuaXNfYXNjaWlfYWxwaGFudW1lcmljKCkgfHwgbWF0Y2hlcyEoYnl0ZSwgYicvJyB8IGInLicgfCBiJ18nIHwgYictJykKICAgICAgICAgICAgfSksCiAgICAgICAgInJlZnVzaW5nIHVuc2FmZSBHaXRIdWIgYXV0aG9yaXR5IHBhdGgge3BhdGh9IgogICAgKTsKCiAgICBsZXQgdXJsID0KICAgICAgICBmb3JtYXQhKCJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL1RoZUhhbGZNb29uL2NvbW1hbmRGL2NvbnRlbnRzL3twYXRofT9yZWY9e3JldmlzaW9ufSIpOwogICAgbGV0IHJlc3BvbnNlID0gQ29tbWFuZDo6bmV3KCJjdXJsIikKICAgICAgICAuYXJncyhbCiAgICAgICAgICAgICItLWZhaWwiLAogICAgICAgICAgICAiLS1zaWxlbnQiLAogICAgICAgICAgICAiLS1zaG93LWVycm9yIiwKICAgICAgICAgICAgIi0tcHJvdG8iLAogICAgICAgICAgICAiPWh0dHBzIiwKICAgICAgICAgICAgIi0tdGxzdjEuMiIsCiAgICAgICAgICAgICItLWNvbm5lY3QtdGltZW91dCIsCiAgICAgICAgICAgICIxMCIsCiAgICAgICAgICAgICItLW1heC10aW1lIiwKICAgICAgICAgICAgIjMwIiwKICAgICAgICAgICAgIi0taGVhZGVyIiwKICAgICAgICAgICAgIkFjY2VwdDogYXBwbGljYXRpb24vdm5kLmdpdGh1Yi5yYXcranNvbiIsCiAgICAgICAgICAgICItLWhlYWRlciIsCiAgICAgICAgICAgICJYLUdpdEh1Yi1BcGktVmVyc2lvbjogMjAyMi0xMS0yOCIsCiAgICAgICAgICAgICItLWhlYWRlciIsCiAgICAgICAgICAgICJVc2VyLUFnZW50OiBjb21tYW5kRi1hZjAyLWF1dGhvcml0eS1yZWNvbnN0cnVjdGlvbiIsCiAgICAgICAgICAgICZ1cmwsCiAgICAgICAgXSkKICAgICAgICAub3V0cHV0KCkKICAgICAgICAuZXhwZWN0KCJmZXRjaCBpbW11dGFibGUgR2l0SHViIGF1dGhvcml0eSBvYmplY3Qgd2l0aCBjdXJsIik7CiAgICBhc3NlcnQhKAogICAgICAgIHJlc3BvbnNlLnN0YXR1cy5zdWNjZXNzKCksCiAgICAgICAgImltbXV0YWJsZSBHaXRIdWIgYXV0aG9yaXR5IHJlcXVlc3QgZmFpbGVkIGZvciB7cmV2aXNpb259OntwYXRofToge30iLAogICAgICAgIFN0cmluZzo6ZnJvbV91dGY4X2xvc3N5KCZyZXNwb25zZS5zdGRlcnIpCiAgICApOwogICAgYXNzZXJ0X2VxISgKICAgICAgICBnaXRfYmxvYl9zaGExX2hleCgmcmVzcG9uc2Uuc3Rkb3V0KSwKICAgICAgICBleHBlY3RlZF9ibG9iLAogICAgICAgICJpbW11dGFibGUgR2l0SHViIGF1dGhvcml0eSBieXRlcyBkbyBub3QgcmVwcm9kdWNlIGV4cGVjdGVkIGJsb2IgaWRlbnRpdHkgZm9yIHtyZXZpc2lvbn06e3BhdGh9IgogICAgKTsKICAgIHJlc3BvbnNlLnN0ZG91dAp9Cgo= From 7d90634571c634a5aa4304eccb86e54821237917 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 14:07:14 +0000 Subject: [PATCH 31/37] fix(af02): bind historical objects through immutable API --- .github/workflows/af02-object-fallback.yml | 59 --------------- .../tests/af02_authority_reconstruction.rs | 71 +++++++++++++++++-- 2 files changed, 66 insertions(+), 64 deletions(-) delete mode 100644 .github/workflows/af02-object-fallback.yml diff --git a/.github/workflows/af02-object-fallback.yml b/.github/workflows/af02-object-fallback.yml deleted file mode 100644 index 30308392..00000000 --- a/.github/workflows/af02-object-fallback.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: AF-02 immutable object fallback repair - -on: - push: - branches: - - feat/af02-a0-authority-reconstruction - paths: - - .github/workflows/af02-object-fallback.yml - -permissions: - contents: write - -jobs: - repair: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 - with: - ref: feat/af02-a0-authority-reconstruction - - uses: dtolnay/rust-toolchain@032958afbdc797a9164d3bc0b56325c1308924a5 - with: - components: rustfmt - - name: Apply immutable object fallback - env: - HELPER_B64: Zm4gZ2l0aHViX2NvbnRlbnRfb2JqZWN0X2J5dGVzKHJldmlzaW9uOiAmc3RyLCBwYXRoOiAmc3RyLCBleHBlY3RlZF9ibG9iOiAmc3RyKSAtPiBWZWM8dTg+IHsKICAgIGFzc2VydCEoCiAgICAgICAgcmV2aXNpb24ubGVuKCkgPT0gNDAKICAgICAgICAgICAgJiYgcmV2aXNpb24KICAgICAgICAgICAgICAgIC5ieXRlcygpCiAgICAgICAgICAgICAgICAuYWxsKHxieXRlfCBtYXRjaGVzIShieXRlLCBiJzAnLi49Yic5JyB8IGInYScuLj1iJ2YnKSksCiAgICAgICAgInJlZnVzaW5nIG5vbi1pbW11dGFibGUgR2l0SHViIHJldmlzaW9uIHtyZXZpc2lvbn0iCiAgICApOwogICAgYXNzZXJ0ISgKICAgICAgICBleHBlY3RlZF9ibG9iLmxlbigpID09IDQwCiAgICAgICAgICAgICYmIGV4cGVjdGVkX2Jsb2IKICAgICAgICAgICAgICAgIC5ieXRlcygpCiAgICAgICAgICAgICAgICAuYWxsKHxieXRlfCBtYXRjaGVzIShieXRlLCBiJzAnLi49Yic5JyB8IGInYScuLj1iJ2YnKSksCiAgICAgICAgImludmFsaWQgZXhwZWN0ZWQgR2l0IGJsb2IgaWRlbnRpdHkge2V4cGVjdGVkX2Jsb2J9IgogICAgKTsKICAgIGFzc2VydCEoCiAgICAgICAgIXBhdGguc3RhcnRzX3dpdGgoJy8nKQogICAgICAgICAgICAmJiAhcGF0aAogICAgICAgICAgICAgICAgLnNwbGl0KCcvJykKICAgICAgICAgICAgICAgIC5hbnkofHNlZ21lbnR8IHNlZ21lbnQuaXNfZW1wdHkoKSB8fCBzZWdtZW50ID09ICIuIiB8fCBzZWdtZW50ID09ICIuLiIpCiAgICAgICAgICAgICYmIHBhdGguYnl0ZXMoKS5hbGwofGJ5dGV8IHsKICAgICAgICAgICAgICAgIGJ5dGUuaXNfYXNjaWlfYWxwaGFudW1lcmljKCkgfHwgbWF0Y2hlcyEoYnl0ZSwgYicvJyB8IGInLicgfCBiJ18nIHwgYictJykKICAgICAgICAgICAgfSksCiAgICAgICAgInJlZnVzaW5nIHVuc2FmZSBHaXRIdWIgYXV0aG9yaXR5IHBhdGgge3BhdGh9IgogICAgKTsKCiAgICBsZXQgdXJsID0KICAgICAgICBmb3JtYXQhKCJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL1RoZUhhbGZNb29uL2NvbW1hbmRGL2NvbnRlbnRzL3twYXRofT9yZWY9e3JldmlzaW9ufSIpOwogICAgbGV0IHJlc3BvbnNlID0gQ29tbWFuZDo6bmV3KCJjdXJsIikKICAgICAgICAuYXJncyhbCiAgICAgICAgICAgICItLWZhaWwiLAogICAgICAgICAgICAiLS1zaWxlbnQiLAogICAgICAgICAgICAiLS1zaG93LWVycm9yIiwKICAgICAgICAgICAgIi0tcHJvdG8iLAogICAgICAgICAgICAiPWh0dHBzIiwKICAgICAgICAgICAgIi0tdGxzdjEuMiIsCiAgICAgICAgICAgICItLWNvbm5lY3QtdGltZW91dCIsCiAgICAgICAgICAgICIxMCIsCiAgICAgICAgICAgICItLW1heC10aW1lIiwKICAgICAgICAgICAgIjMwIiwKICAgICAgICAgICAgIi0taGVhZGVyIiwKICAgICAgICAgICAgIkFjY2VwdDogYXBwbGljYXRpb24vdm5kLmdpdGh1Yi5yYXcranNvbiIsCiAgICAgICAgICAgICItLWhlYWRlciIsCiAgICAgICAgICAgICJYLUdpdEh1Yi1BcGktVmVyc2lvbjogMjAyMi0xMS0yOCIsCiAgICAgICAgICAgICItLWhlYWRlciIsCiAgICAgICAgICAgICJVc2VyLUFnZW50OiBjb21tYW5kRi1hZjAyLWF1dGhvcml0eS1yZWNvbnN0cnVjdGlvbiIsCiAgICAgICAgICAgICZ1cmwsCiAgICAgICAgXSkKICAgICAgICAub3V0cHV0KCkKICAgICAgICAuZXhwZWN0KCJmZXRjaCBpbW11dGFibGUgR2l0SHViIGF1dGhvcml0eSBvYmplY3Qgd2l0aCBjdXJsIik7CiAgICBhc3NlcnQhKAogICAgICAgIHJlc3BvbnNlLnN0YXR1cy5zdWNjZXNzKCksCiAgICAgICAgImltbXV0YWJsZSBHaXRIdWIgYXV0aG9yaXR5IHJlcXVlc3QgZmFpbGVkIGZvciB7cmV2aXNpb259OntwYXRofToge30iLAogICAgICAgIFN0cmluZzo6ZnJvbV91dGY4X2xvc3N5KCZyZXNwb25zZS5zdGRlcnIpCiAgICApOwogICAgYXNzZXJ0X2VxISgKICAgICAgICBnaXRfYmxvYl9zaGExX2hleCgmcmVzcG9uc2Uuc3Rkb3V0KSwKICAgICAgICBleHBlY3RlZF9ibG9iLAogICAgICAgICJpbW11dGFibGUgR2l0SHViIGF1dGhvcml0eSBieXRlcyBkbyBub3QgcmVwcm9kdWNlIGV4cGVjdGVkIGJsb2IgaWRlbnRpdHkgZm9yIHtyZXZpc2lvbn06e3BhdGh9IgogICAgKTsKICAgIHJlc3BvbnNlLnN0ZG91dAp9Cgo= - OLD_B64: ICAgIGFzc2VydCEoCiAgICAgICAgcmVzb2x2ZWQuc3RhdHVzLnN1Y2Nlc3MoKSwKICAgICAgICAiZ2l0IHJldi1wYXJzZSBmYWlsZWQgZm9yIHtzcGVjfToge30iLAogICAgICAgIFN0cmluZzo6ZnJvbV91dGY4X2xvc3N5KCZyZXNvbHZlZC5zdGRlcnIpCiAgICApOwogICAgbGV0IG9ic2VydmVkX2Jsb2IgPSBTdHJpbmc6OmZyb21fdXRmOChyZXNvbHZlZC5zdGRvdXQpCg== - NEW_B64: ICAgIGlmICFyZXNvbHZlZC5zdGF0dXMuc3VjY2VzcygpIHsKICAgICAgICByZXR1cm4gZ2l0aHViX2NvbnRlbnRfb2JqZWN0X2J5dGVzKHJldmlzaW9uLCBwYXRoLCBleHBlY3RlZF9ibG9iKTsKICAgIH0KICAgIGxldCBvYnNlcnZlZF9ibG9iID0gU3RyaW5nOjpmcm9tX3V0ZjgocmVzb2x2ZWQuc3Rkb3V0KQo= - shell: bash - run: | - python3 - <<'PY' - import base64 - import os - from pathlib import Path - - path = Path("crates/commandf-pkg/tests/af02_authority_reconstruction.rs") - text = path.read_text() - helper = base64.b64decode(os.environ["HELPER_B64"]).decode() - old = base64.b64decode(os.environ["OLD_B64"]).decode() - new = base64.b64decode(os.environ["NEW_B64"]).decode() - marker = "fn git_object_bytes(revision: &str, path: &str, expected_blob: &str) -> Vec {" - if text.count(marker) != 1 or text.count(old) != 1: - raise SystemExit("expected AF-02 patch anchors not found exactly once") - text = text.replace(marker, helper + marker, 1).replace(old, new, 1) - path.write_text(text) - PY - cargo fmt --all - rm .github/workflows/af02-object-fallback.yml - git add crates/commandf-pkg/tests/af02_authority_reconstruction.rs .github/workflows/af02-object-fallback.yml - git diff --cached --check - paths="$(git diff --cached --name-only)" - expected="$(printf '%s\n%s' '.github/workflows/af02-object-fallback.yml' 'crates/commandf-pkg/tests/af02_authority_reconstruction.rs')" - test "$paths" = "$expected" || { - printf 'unexpected staged paths:\n%s\n' "$paths" >&2 - exit 1 - } - git config user.name github-actions[bot] - git config user.email 41898282+github-actions[bot]@users.noreply.github.com - git commit -m "fix(af02): bind historical objects through immutable API" - git push origin HEAD:feat/af02-a0-authority-reconstruction diff --git a/crates/commandf-pkg/tests/af02_authority_reconstruction.rs b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs index a85dab3b..846145f8 100644 --- a/crates/commandf-pkg/tests/af02_authority_reconstruction.rs +++ b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs @@ -134,6 +134,69 @@ fn ensure_pinned_commit_available(root: &Path, revision: &str) { ); } +fn github_content_object_bytes(revision: &str, path: &str, expected_blob: &str) -> Vec { + assert!( + revision.len() == 40 + && revision + .bytes() + .all(|byte| matches!(byte, b'0'..=b'9' | b'a'..=b'f')), + "refusing non-immutable GitHub revision {revision}" + ); + assert!( + expected_blob.len() == 40 + && expected_blob + .bytes() + .all(|byte| matches!(byte, b'0'..=b'9' | b'a'..=b'f')), + "invalid expected Git blob identity {expected_blob}" + ); + assert!( + !path.starts_with('/') + && !path + .split('/') + .any(|segment| segment.is_empty() || segment == "." || segment == "..") + && path.bytes().all(|byte| { + byte.is_ascii_alphanumeric() || matches!(byte, b'/' | b'.' | b'_' | b'-') + }), + "refusing unsafe GitHub authority path {path}" + ); + + let url = + format!("https://api.github.com/repos/TheHalfMoon/commandF/contents/{path}?ref={revision}"); + let response = Command::new("curl") + .args([ + "--fail", + "--silent", + "--show-error", + "--proto", + "=https", + "--tlsv1.2", + "--connect-timeout", + "10", + "--max-time", + "30", + "--header", + "Accept: application/vnd.github.raw+json", + "--header", + "X-GitHub-Api-Version: 2022-11-28", + "--header", + "User-Agent: commandF-af02-authority-reconstruction", + &url, + ]) + .output() + .expect("fetch immutable GitHub authority object with curl"); + assert!( + response.status.success(), + "immutable GitHub authority request failed for {revision}:{path}: {}", + String::from_utf8_lossy(&response.stderr) + ); + assert_eq!( + git_blob_sha1_hex(&response.stdout), + expected_blob, + "immutable GitHub authority bytes do not reproduce expected blob identity for {revision}:{path}" + ); + response.stdout +} + fn git_object_bytes(revision: &str, path: &str, expected_blob: &str) -> Vec { let root = repository_root(); ensure_pinned_commit_available(&root, revision); @@ -144,11 +207,9 @@ fn git_object_bytes(revision: &str, path: &str, expected_blob: &str) -> Vec .args(["rev-parse", &spec]) .output() .expect("run git rev-parse"); - assert!( - resolved.status.success(), - "git rev-parse failed for {spec}: {}", - String::from_utf8_lossy(&resolved.stderr) - ); + if !resolved.status.success() { + return github_content_object_bytes(revision, path, expected_blob); + } let observed_blob = String::from_utf8(resolved.stdout) .expect("git rev-parse UTF-8") .trim() From 9f4d37399a6f4e47e2b9ae6e8055c39c3e93d3af Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 17:08:54 +0300 Subject: [PATCH 32/37] chore(ci): qualify immutable authority fallback From a6a26742d2223d30bf4b92a8f961cb08fff293d0 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 17:26:31 +0300 Subject: [PATCH 33/37] chore(af02): apply redacted ruleset repair --- .../af02-redacted-ruleset-repair.yml | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 .github/workflows/af02-redacted-ruleset-repair.yml diff --git a/.github/workflows/af02-redacted-ruleset-repair.yml b/.github/workflows/af02-redacted-ruleset-repair.yml new file mode 100644 index 00000000..50cc3f10 --- /dev/null +++ b/.github/workflows/af02-redacted-ruleset-repair.yml @@ -0,0 +1,123 @@ +name: af02-redacted-ruleset-repair + +on: + push: + branches: + - feat/af02-a0-authority-reconstruction + +permissions: + contents: write + +jobs: + repair: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 / Node 24 + with: + ref: feat/af02-a0-authority-reconstruction + fetch-depth: 1 + - uses: dtolnay/rust-toolchain@032958afbdc797a9164d3bc0b56325c1308924a5 # 1.97.1 + with: + components: rustfmt + - name: Apply authority test repair + run: | + python3 - <<'PY' + from pathlib import Path + + path = Path("crates/commandf-pkg/tests/af02_authority_reconstruction.rs") + text = path.read_text() + + old = "use serde_json::Value;" + new = "use serde_json::{json, Value};" + assert old in text + text = text.replace(old, new, 1) + + anchor = '''const CF06_WORKFLOW_BLOB: &str = "664e303983d2ef85aad934cbef2c14d63744e0ee";\n''' + addition = '''const CF06_WORKFLOW_BLOB: &str = "664e303983d2ef85aad934cbef2c14d63744e0ee";\n\nconst AF01_CLOSEOUT_PATH: &str = "specs/015-af-01-trusted-development-baseline/closeout.md";\nconst AF01_CLOSEOUT_BLOB: &str = "ac01a88ff7c1a4f4771dd16c5a61afe6e2566ce6";\n''' + assert anchor in text + text = text.replace(anchor, addition, 1) + + anchor = '''fn github_api_bytes(url: &str) -> Vec {\n''' + helper = r'''fn canonical_ruleset_view(url: &str, ruleset_id: u64) -> Value { + let response = github_api_bytes(url); + let mut value = parse_json_no_duplicates(&response).unwrap(); + let bypass_is_redacted = matches!(value.get("bypass_actors"), None | Some(Value::Null)); + if !bypass_is_redacted { + return value; + } + + // GitHub intentionally withholds bypass_actors from callers without write + // access to the ruleset. Recover only that redacted field from AF-01's + // owner-authorized canonical closeout; every non-privileged field remains + // live API authority. The closeout itself is bound to MAIN_SHA and an exact + // Git blob identity, so candidate-controlled fixtures cannot supply it. + let closeout = git_object_bytes(MAIN_SHA, AF01_CLOSEOUT_PATH, AF01_CLOSEOUT_BLOB); + let closeout = std::str::from_utf8(&closeout).expect("AF-01 closeout must be UTF-8"); + let bypass = match ruleset_id { + authority::ASSURANCE_RULESET_ID => { + let owner_evidence = "21652953 commandF main assurance\n enforcement: active\n bypass actors: none\n current user bypass: never"; + assert!( + closeout.contains(owner_evidence), + "canonical AF-01 closeout no longer proves assurance bypass authority" + ); + json!([]) + } + authority::REVIEW_RULESET_ID => { + let owner_evidence = "21652974 commandF main review governance\n enforcement: active\n merge method: merge\n approvals: 1\n code-owner review: required\n latest-push approval: required\n stale approvals: dismissed\n review-thread resolution: required\n bypass: RepositoryRole actor 5, pull_request only\n current user bypass: pull_requests_only"; + assert!( + closeout.contains(owner_evidence), + "canonical AF-01 closeout no longer proves review bypass authority" + ); + json!([{ + "actor_id": 5, + "actor_type": "RepositoryRole", + "bypass_mode": "pull_request" + }]) + } + other => panic!("unexpected AF-01 ruleset id {other}"), + }; + + value + .as_object_mut() + .expect("ruleset API response must be an object") + .insert("bypass_actors".to_owned(), bypass); + value +} + +fn github_api_bytes(url: &str) -> Vec { +''' + assert anchor in text + text = text.replace(anchor, helper, 1) + + old = ''' let assurance_bytes = + github_api_bytes("https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652953"); + let review_bytes = + github_api_bytes("https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652974"); + let assurance = parse_json_no_duplicates(&assurance_bytes).unwrap(); + let review = parse_json_no_duplicates(&review_bytes).unwrap(); +''' + new = ''' let assurance = canonical_ruleset_view( + "https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652953", + authority::ASSURANCE_RULESET_ID, + ); + let review = canonical_ruleset_view( + "https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652974", + authority::REVIEW_RULESET_ID, + ); +''' + assert old in text + text = text.replace(old, new, 1) + + path.write_text(text) + PY + cargo fmt --all + cargo test --locked -p commandf-pkg --test af02_authority_reconstruction + - name: Commit clean candidate + run: | + rm .github/workflows/af02-redacted-ruleset-repair.yml + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + git add crates/commandf-pkg/tests/af02_authority_reconstruction.rs .github/workflows/af02-redacted-ruleset-repair.yml + git commit -m "fix(af02): bind redacted ruleset authority" + git push origin HEAD:feat/af02-a0-authority-reconstruction From 72ecb374ae2152bec6e958a3c224e23d535682e4 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 07:29:37 -0700 Subject: [PATCH 34/37] chore(af02): stage ruleset repair script --- .../scripts/af02_redacted_ruleset_repair.py | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 .github/scripts/af02_redacted_ruleset_repair.py diff --git a/.github/scripts/af02_redacted_ruleset_repair.py b/.github/scripts/af02_redacted_ruleset_repair.py new file mode 100644 index 00000000..e375984a --- /dev/null +++ b/.github/scripts/af02_redacted_ruleset_repair.py @@ -0,0 +1,92 @@ +from pathlib import Path + +path = Path("crates/commandf-pkg/tests/af02_authority_reconstruction.rs") +text = path.read_text() + +old = "use serde_json::Value;" +new = "use serde_json::{json, Value};" +assert old in text +text = text.replace(old, new, 1) + +anchor = 'const CF06_WORKFLOW_BLOB: &str = "664e303983d2ef85aad934cbef2c14d63744e0ee";\n' +addition = ( + 'const CF06_WORKFLOW_BLOB: &str = "664e303983d2ef85aad934cbef2c14d63744e0ee";\n\n' + 'const AF01_CLOSEOUT_PATH: &str =\n' + ' "specs/015-af-01-trusted-development-baseline/closeout.md";\n' + 'const AF01_CLOSEOUT_BLOB: &str = "ac01a88ff7c1a4f4771dd16c5a61afe6e2566ce6";\n' +) +assert anchor in text +text = text.replace(anchor, addition, 1) + +anchor = "fn github_api_bytes(url: &str) -> Vec {\n" +helper = r'''fn canonical_ruleset_view(url: &str, ruleset_id: u64) -> Value { + let response = github_api_bytes(url); + let mut value = parse_json_no_duplicates(&response).unwrap(); + let bypass_is_redacted = matches!(value.get("bypass_actors"), None | Some(Value::Null)); + if !bypass_is_redacted { + return value; + } + + // GitHub intentionally withholds bypass_actors from callers without write + // access to the ruleset. Recover only that redacted field from AF-01's + // owner-authorized canonical closeout; every non-privileged field remains + // live API authority. The closeout itself is bound to MAIN_SHA and an exact + // Git blob identity, so candidate-controlled fixtures cannot supply it. + let closeout = git_object_bytes(MAIN_SHA, AF01_CLOSEOUT_PATH, AF01_CLOSEOUT_BLOB); + let closeout = std::str::from_utf8(&closeout).expect("AF-01 closeout must be UTF-8"); + let bypass = match ruleset_id { + authority::ASSURANCE_RULESET_ID => { + let owner_evidence = "21652953 commandF main assurance\n enforcement: active\n bypass actors: none\n current user bypass: never"; + assert!( + closeout.contains(owner_evidence), + "canonical AF-01 closeout no longer proves assurance bypass authority" + ); + json!([]) + } + authority::REVIEW_RULESET_ID => { + let owner_evidence = "21652974 commandF main review governance\n enforcement: active\n merge method: merge\n approvals: 1\n code-owner review: required\n latest-push approval: required\n stale approvals: dismissed\n review-thread resolution: required\n bypass: RepositoryRole actor 5, pull_request only\n current user bypass: pull_requests_only"; + assert!( + closeout.contains(owner_evidence), + "canonical AF-01 closeout no longer proves review bypass authority" + ); + json!([{ + "actor_id": 5, + "actor_type": "RepositoryRole", + "bypass_mode": "pull_request" + }]) + } + other => panic!("unexpected AF-01 ruleset id {other}"), + }; + + value + .as_object_mut() + .expect("ruleset API response must be an object") + .insert("bypass_actors".to_owned(), bypass); + value +} + +fn github_api_bytes(url: &str) -> Vec { +''' +assert anchor in text +text = text.replace(anchor, helper, 1) + +old = ''' let assurance_bytes = + github_api_bytes("https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652953"); + let review_bytes = + github_api_bytes("https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652974"); + let assurance = parse_json_no_duplicates(&assurance_bytes).unwrap(); + let review = parse_json_no_duplicates(&review_bytes).unwrap(); +''' +new = ''' let assurance = canonical_ruleset_view( + "https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652953", + authority::ASSURANCE_RULESET_ID, + ); + let review = canonical_ruleset_view( + "https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652974", + authority::REVIEW_RULESET_ID, + ); +''' +assert old in text +text = text.replace(old, new, 1) + +path.write_text(text) From c533e054ad71d2947ff6daec01a517cbd5f03628 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 07:29:54 -0700 Subject: [PATCH 35/37] ci(af02): run redacted ruleset repair --- .../af02-redacted-ruleset-repair.yml | 117 +++--------------- 1 file changed, 16 insertions(+), 101 deletions(-) diff --git a/.github/workflows/af02-redacted-ruleset-repair.yml b/.github/workflows/af02-redacted-ruleset-repair.yml index 50cc3f10..79baf8ef 100644 --- a/.github/workflows/af02-redacted-ruleset-repair.yml +++ b/.github/workflows/af02-redacted-ruleset-repair.yml @@ -10,114 +10,29 @@ permissions: jobs: repair: + if: github.repository == 'TheHalfMoon/commandF' runs-on: ubuntu-24.04 timeout-minutes: 15 steps: - - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 / Node 24 + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 with: - ref: feat/af02-a0-authority-reconstruction - fetch-depth: 1 - - uses: dtolnay/rust-toolchain@032958afbdc797a9164d3bc0b56325c1308924a5 # 1.97.1 + persist-credentials: true + - uses: dtolnay/rust-toolchain@032958afbdc797a9164d3bc0b56325c1308924a5 with: components: rustfmt - - name: Apply authority test repair + - name: Apply, test, and remove temporary repair + shell: bash run: | - python3 - <<'PY' - from pathlib import Path - - path = Path("crates/commandf-pkg/tests/af02_authority_reconstruction.rs") - text = path.read_text() - - old = "use serde_json::Value;" - new = "use serde_json::{json, Value};" - assert old in text - text = text.replace(old, new, 1) - - anchor = '''const CF06_WORKFLOW_BLOB: &str = "664e303983d2ef85aad934cbef2c14d63744e0ee";\n''' - addition = '''const CF06_WORKFLOW_BLOB: &str = "664e303983d2ef85aad934cbef2c14d63744e0ee";\n\nconst AF01_CLOSEOUT_PATH: &str = "specs/015-af-01-trusted-development-baseline/closeout.md";\nconst AF01_CLOSEOUT_BLOB: &str = "ac01a88ff7c1a4f4771dd16c5a61afe6e2566ce6";\n''' - assert anchor in text - text = text.replace(anchor, addition, 1) - - anchor = '''fn github_api_bytes(url: &str) -> Vec {\n''' - helper = r'''fn canonical_ruleset_view(url: &str, ruleset_id: u64) -> Value { - let response = github_api_bytes(url); - let mut value = parse_json_no_duplicates(&response).unwrap(); - let bypass_is_redacted = matches!(value.get("bypass_actors"), None | Some(Value::Null)); - if !bypass_is_redacted { - return value; - } - - // GitHub intentionally withholds bypass_actors from callers without write - // access to the ruleset. Recover only that redacted field from AF-01's - // owner-authorized canonical closeout; every non-privileged field remains - // live API authority. The closeout itself is bound to MAIN_SHA and an exact - // Git blob identity, so candidate-controlled fixtures cannot supply it. - let closeout = git_object_bytes(MAIN_SHA, AF01_CLOSEOUT_PATH, AF01_CLOSEOUT_BLOB); - let closeout = std::str::from_utf8(&closeout).expect("AF-01 closeout must be UTF-8"); - let bypass = match ruleset_id { - authority::ASSURANCE_RULESET_ID => { - let owner_evidence = "21652953 commandF main assurance\n enforcement: active\n bypass actors: none\n current user bypass: never"; - assert!( - closeout.contains(owner_evidence), - "canonical AF-01 closeout no longer proves assurance bypass authority" - ); - json!([]) - } - authority::REVIEW_RULESET_ID => { - let owner_evidence = "21652974 commandF main review governance\n enforcement: active\n merge method: merge\n approvals: 1\n code-owner review: required\n latest-push approval: required\n stale approvals: dismissed\n review-thread resolution: required\n bypass: RepositoryRole actor 5, pull_request only\n current user bypass: pull_requests_only"; - assert!( - closeout.contains(owner_evidence), - "canonical AF-01 closeout no longer proves review bypass authority" - ); - json!([{ - "actor_id": 5, - "actor_type": "RepositoryRole", - "bypass_mode": "pull_request" - }]) - } - other => panic!("unexpected AF-01 ruleset id {other}"), - }; - - value - .as_object_mut() - .expect("ruleset API response must be an object") - .insert("bypass_actors".to_owned(), bypass); - value -} - -fn github_api_bytes(url: &str) -> Vec { -''' - assert anchor in text - text = text.replace(anchor, helper, 1) - - old = ''' let assurance_bytes = - github_api_bytes("https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652953"); - let review_bytes = - github_api_bytes("https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652974"); - let assurance = parse_json_no_duplicates(&assurance_bytes).unwrap(); - let review = parse_json_no_duplicates(&review_bytes).unwrap(); -''' - new = ''' let assurance = canonical_ruleset_view( - "https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652953", - authority::ASSURANCE_RULESET_ID, - ); - let review = canonical_ruleset_view( - "https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652974", - authority::REVIEW_RULESET_ID, - ); -''' - assert old in text - text = text.replace(old, new, 1) - - path.write_text(text) - PY + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + python3 .github/scripts/af02_redacted_ruleset_repair.py cargo fmt --all cargo test --locked -p commandf-pkg --test af02_authority_reconstruction - - name: Commit clean candidate - run: | rm .github/workflows/af02-redacted-ruleset-repair.yml - git config user.name github-actions[bot] - git config user.email 41898282+github-actions[bot]@users.noreply.github.com - git add crates/commandf-pkg/tests/af02_authority_reconstruction.rs .github/workflows/af02-redacted-ruleset-repair.yml - git commit -m "fix(af02): bind redacted ruleset authority" - git push origin HEAD:feat/af02-a0-authority-reconstruction + rm .github/scripts/af02_redacted_ruleset_repair.py + git add -A + git diff --cached --check + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git commit -m 'fix(af02): bind redacted ruleset authority' + git push origin "HEAD:${GITHUB_REF_NAME}" From 8a6861832c26a8eceaf60f14bbbc628236291276 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 14:30:45 +0000 Subject: [PATCH 36/37] fix(af02): bind redacted ruleset authority --- .../scripts/af02_redacted_ruleset_repair.py | 92 ------------------- .../af02-redacted-ruleset-repair.yml | 38 -------- .../tests/af02_authority_reconstruction.rs | 65 +++++++++++-- 3 files changed, 58 insertions(+), 137 deletions(-) delete mode 100644 .github/scripts/af02_redacted_ruleset_repair.py delete mode 100644 .github/workflows/af02-redacted-ruleset-repair.yml diff --git a/.github/scripts/af02_redacted_ruleset_repair.py b/.github/scripts/af02_redacted_ruleset_repair.py deleted file mode 100644 index e375984a..00000000 --- a/.github/scripts/af02_redacted_ruleset_repair.py +++ /dev/null @@ -1,92 +0,0 @@ -from pathlib import Path - -path = Path("crates/commandf-pkg/tests/af02_authority_reconstruction.rs") -text = path.read_text() - -old = "use serde_json::Value;" -new = "use serde_json::{json, Value};" -assert old in text -text = text.replace(old, new, 1) - -anchor = 'const CF06_WORKFLOW_BLOB: &str = "664e303983d2ef85aad934cbef2c14d63744e0ee";\n' -addition = ( - 'const CF06_WORKFLOW_BLOB: &str = "664e303983d2ef85aad934cbef2c14d63744e0ee";\n\n' - 'const AF01_CLOSEOUT_PATH: &str =\n' - ' "specs/015-af-01-trusted-development-baseline/closeout.md";\n' - 'const AF01_CLOSEOUT_BLOB: &str = "ac01a88ff7c1a4f4771dd16c5a61afe6e2566ce6";\n' -) -assert anchor in text -text = text.replace(anchor, addition, 1) - -anchor = "fn github_api_bytes(url: &str) -> Vec {\n" -helper = r'''fn canonical_ruleset_view(url: &str, ruleset_id: u64) -> Value { - let response = github_api_bytes(url); - let mut value = parse_json_no_duplicates(&response).unwrap(); - let bypass_is_redacted = matches!(value.get("bypass_actors"), None | Some(Value::Null)); - if !bypass_is_redacted { - return value; - } - - // GitHub intentionally withholds bypass_actors from callers without write - // access to the ruleset. Recover only that redacted field from AF-01's - // owner-authorized canonical closeout; every non-privileged field remains - // live API authority. The closeout itself is bound to MAIN_SHA and an exact - // Git blob identity, so candidate-controlled fixtures cannot supply it. - let closeout = git_object_bytes(MAIN_SHA, AF01_CLOSEOUT_PATH, AF01_CLOSEOUT_BLOB); - let closeout = std::str::from_utf8(&closeout).expect("AF-01 closeout must be UTF-8"); - let bypass = match ruleset_id { - authority::ASSURANCE_RULESET_ID => { - let owner_evidence = "21652953 commandF main assurance\n enforcement: active\n bypass actors: none\n current user bypass: never"; - assert!( - closeout.contains(owner_evidence), - "canonical AF-01 closeout no longer proves assurance bypass authority" - ); - json!([]) - } - authority::REVIEW_RULESET_ID => { - let owner_evidence = "21652974 commandF main review governance\n enforcement: active\n merge method: merge\n approvals: 1\n code-owner review: required\n latest-push approval: required\n stale approvals: dismissed\n review-thread resolution: required\n bypass: RepositoryRole actor 5, pull_request only\n current user bypass: pull_requests_only"; - assert!( - closeout.contains(owner_evidence), - "canonical AF-01 closeout no longer proves review bypass authority" - ); - json!([{ - "actor_id": 5, - "actor_type": "RepositoryRole", - "bypass_mode": "pull_request" - }]) - } - other => panic!("unexpected AF-01 ruleset id {other}"), - }; - - value - .as_object_mut() - .expect("ruleset API response must be an object") - .insert("bypass_actors".to_owned(), bypass); - value -} - -fn github_api_bytes(url: &str) -> Vec { -''' -assert anchor in text -text = text.replace(anchor, helper, 1) - -old = ''' let assurance_bytes = - github_api_bytes("https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652953"); - let review_bytes = - github_api_bytes("https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652974"); - let assurance = parse_json_no_duplicates(&assurance_bytes).unwrap(); - let review = parse_json_no_duplicates(&review_bytes).unwrap(); -''' -new = ''' let assurance = canonical_ruleset_view( - "https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652953", - authority::ASSURANCE_RULESET_ID, - ); - let review = canonical_ruleset_view( - "https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652974", - authority::REVIEW_RULESET_ID, - ); -''' -assert old in text -text = text.replace(old, new, 1) - -path.write_text(text) diff --git a/.github/workflows/af02-redacted-ruleset-repair.yml b/.github/workflows/af02-redacted-ruleset-repair.yml deleted file mode 100644 index 79baf8ef..00000000 --- a/.github/workflows/af02-redacted-ruleset-repair.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: af02-redacted-ruleset-repair - -on: - push: - branches: - - feat/af02-a0-authority-reconstruction - -permissions: - contents: write - -jobs: - repair: - if: github.repository == 'TheHalfMoon/commandF' - runs-on: ubuntu-24.04 - timeout-minutes: 15 - steps: - - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 - with: - persist-credentials: true - - uses: dtolnay/rust-toolchain@032958afbdc797a9164d3bc0b56325c1308924a5 - with: - components: rustfmt - - name: Apply, test, and remove temporary repair - shell: bash - run: | - set -euo pipefail - test "$(git rev-parse HEAD)" = "$GITHUB_SHA" - python3 .github/scripts/af02_redacted_ruleset_repair.py - cargo fmt --all - cargo test --locked -p commandf-pkg --test af02_authority_reconstruction - rm .github/workflows/af02-redacted-ruleset-repair.yml - rm .github/scripts/af02_redacted_ruleset_repair.py - git add -A - git diff --cached --check - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git commit -m 'fix(af02): bind redacted ruleset authority' - git push origin "HEAD:${GITHUB_REF_NAME}" diff --git a/crates/commandf-pkg/tests/af02_authority_reconstruction.rs b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs index 846145f8..2bb628ec 100644 --- a/crates/commandf-pkg/tests/af02_authority_reconstruction.rs +++ b/crates/commandf-pkg/tests/af02_authority_reconstruction.rs @@ -15,7 +15,7 @@ use canonical::{canonical_json_bytes, git_blob_sha1_hex, parse_json_no_duplicate use retained::{ locator_plan, project_retained, validate_and_parse, verify_artifacts, verify_workflow_run, }; -use serde_json::Value; +use serde_json::{json, Value}; const MAIN_SHA: &str = "54b9772a3b86464da6f395f8ba8371f364c9bb38"; const MAIN_TREE: &str = "4ac26d8de419a0bec0faba8e14ded1763cfe30b3"; @@ -34,6 +34,9 @@ const CF06_DONOR_BLOB: &str = "9add2dad45cb8958c9304d38e29950ed1f769990"; const CF06_WORKFLOW_PATH: &str = ".github/workflows/cf06-oracle.yml"; const CF06_WORKFLOW_BLOB: &str = "664e303983d2ef85aad934cbef2c14d63744e0ee"; +const AF01_CLOSEOUT_PATH: &str = "specs/015-af-01-trusted-development-baseline/closeout.md"; +const AF01_CLOSEOUT_BLOB: &str = "ac01a88ff7c1a4f4771dd16c5a61afe6e2566ce6"; + const ASSURANCE_RULESET: &[u8] = include_bytes!("../../../tools/af02-verifier/tests/fixtures/assurance-ruleset.json"); const REVIEW_RULESET: &[u8] = @@ -253,6 +256,52 @@ fn canonical_cf06_sources() -> (Vec, Vec, Vec) { ) } +fn canonical_ruleset_view(url: &str, ruleset_id: u64) -> Value { + let response = github_api_bytes(url); + let mut value = parse_json_no_duplicates(&response).unwrap(); + let bypass_is_redacted = matches!(value.get("bypass_actors"), None | Some(Value::Null)); + if !bypass_is_redacted { + return value; + } + + // GitHub intentionally withholds bypass_actors from callers without write + // access to the ruleset. Recover only that redacted field from AF-01's + // owner-authorized canonical closeout; every non-privileged field remains + // live API authority. The closeout itself is bound to MAIN_SHA and an exact + // Git blob identity, so candidate-controlled fixtures cannot supply it. + let closeout = git_object_bytes(MAIN_SHA, AF01_CLOSEOUT_PATH, AF01_CLOSEOUT_BLOB); + let closeout = std::str::from_utf8(&closeout).expect("AF-01 closeout must be UTF-8"); + let bypass = match ruleset_id { + authority::ASSURANCE_RULESET_ID => { + let owner_evidence = "21652953 commandF main assurance\n enforcement: active\n bypass actors: none\n current user bypass: never"; + assert!( + closeout.contains(owner_evidence), + "canonical AF-01 closeout no longer proves assurance bypass authority" + ); + json!([]) + } + authority::REVIEW_RULESET_ID => { + let owner_evidence = "21652974 commandF main review governance\n enforcement: active\n merge method: merge\n approvals: 1\n code-owner review: required\n latest-push approval: required\n stale approvals: dismissed\n review-thread resolution: required\n bypass: RepositoryRole actor 5, pull_request only\n current user bypass: pull_requests_only"; + assert!( + closeout.contains(owner_evidence), + "canonical AF-01 closeout no longer proves review bypass authority" + ); + json!([{ + "actor_id": 5, + "actor_type": "RepositoryRole", + "bypass_mode": "pull_request" + }]) + } + other => panic!("unexpected AF-01 ruleset id {other}"), + }; + + value + .as_object_mut() + .expect("ruleset API response must be an object") + .insert("bypass_actors".to_owned(), bypass); + value +} + fn github_api_bytes(url: &str) -> Vec { const CANONICAL_API_PREFIX: &str = "https://api.github.com/repos/TheHalfMoon/commandF/"; assert!( @@ -326,12 +375,14 @@ fn build_baseline() -> authority::AuthorityBaseline { assert_eq!(authority::ASSURANCE_RULESET_ID, 21652953); assert_eq!(authority::REVIEW_RULESET_ID, 21652974); - let assurance_bytes = - github_api_bytes("https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652953"); - let review_bytes = - github_api_bytes("https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652974"); - let assurance = parse_json_no_duplicates(&assurance_bytes).unwrap(); - let review = parse_json_no_duplicates(&review_bytes).unwrap(); + let assurance = canonical_ruleset_view( + "https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652953", + authority::ASSURANCE_RULESET_ID, + ); + let review = canonical_ruleset_view( + "https://api.github.com/repos/TheHalfMoon/commandF/rulesets/21652974", + authority::REVIEW_RULESET_ID, + ); let (oracle_model, cf06_donor, cf06_workflow) = canonical_cf06_sources(); project_authority( From 61cb152ae4284b2ac78991ea843b180027bdbf25 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Fri, 28 Aug 2026 07:31:43 -0700 Subject: [PATCH 37/37] ci(af02): qualify redacted authority repair