From 58222b240621c0d9afd33895f9afee6f2938c4f1 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Wed, 29 Jul 2026 02:15:40 -0500 Subject: [PATCH 01/28] measure true idle from worker completion --- .../scenarios/artifact/runner/measurements.rs | 10 +- .../scenarios/artifact/runner/process.rs | 23 +- .../scenarios/artifact/runner/tests.rs | 29 +- .../src/embedding_qualification/worker.rs | 6 +- .../worker/operations/measure.rs | 364 ++++++++++++++++-- ...er-user-embedding-server-constant-set.json | 22 +- 6 files changed, 383 insertions(+), 71 deletions(-) diff --git a/crates/codestory-bench/src/bin/codestory_embedding_qualification/scenarios/artifact/runner/measurements.rs b/crates/codestory-bench/src/bin/codestory_embedding_qualification/scenarios/artifact/runner/measurements.rs index 579082006..c61e542fe 100644 --- a/crates/codestory-bench/src/bin/codestory_embedding_qualification/scenarios/artifact/runner/measurements.rs +++ b/crates/codestory-bench/src/bin/codestory_embedding_qualification/scenarios/artifact/runner/measurements.rs @@ -9,7 +9,7 @@ use super::analysis::{ }; use super::process::{ busy_retry_marker_timeout, busy_retry_worker_timeout, measurement_worker_timeout, - query_parameters, require_worker_success, + query_parameters, }; use super::{RunningWorker, ScenarioRunner, WorkerOutput, push_metric}; use crate::qualification::request::REQUIRED_METRICS; @@ -294,14 +294,6 @@ impl<'a> ScenarioRunner<'a> { )?; } - let idle_worker = self.spawn_worker("query", query_parameters(1), None)?; - let idle_output = self.finish_worker(idle_worker, measurement_worker_timeout("query"))?; - require_worker_success(&idle_output, "true_idle_owner")?; - let idle_owner = - self.record_worker_snapshot("measurement_true_idle_owner", &idle_output)?; - if !snapshot_has_resident_generation(&idle_owner) { - bail!("embedding_qualification_true_idle_owner_not_resident"); - } let measured = self.run_measure_worker( "measure_true_idle", "true_idle_exit", diff --git a/crates/codestory-bench/src/bin/codestory_embedding_qualification/scenarios/artifact/runner/process.rs b/crates/codestory-bench/src/bin/codestory_embedding_qualification/scenarios/artifact/runner/process.rs index d03f47e40..666504c6d 100644 --- a/crates/codestory-bench/src/bin/codestory_embedding_qualification/scenarios/artifact/runner/process.rs +++ b/crates/codestory-bench/src/bin/codestory_embedding_qualification/scenarios/artifact/runner/process.rs @@ -1,6 +1,4 @@ -use super::super::{ - CONTROL_TIMEOUT, ControlEvent, IDLE_EXIT_GRACE, POLL, QUEUE_SETUP_TIMEOUT, SNAPSHOT_TIMEOUT, -}; +use super::super::{CONTROL_TIMEOUT, ControlEvent, POLL, QUEUE_SETUP_TIMEOUT, SNAPSHOT_TIMEOUT}; use super::analysis::elapsed; use super::{ EMBEDDING_QUALIFICATION_WORKER_SCHEMA_VERSION, ProcessInvocation, RunningWorker, WorkerOutput, @@ -17,6 +15,8 @@ use std::path::{Path, PathBuf}; use std::process::{Child, ExitStatus}; use std::time::Duration; +pub(super) const MEASUREMENT_OWNER_ABSENCE_GRACE: Duration = Duration::from_secs(30); + pub(super) fn existing_control_events(directory: &Path) -> Result> { published_control_events(&directory.join(format!("{}.events.jsonl", qualification_nonce()?))) } @@ -284,12 +284,17 @@ pub(super) fn stall_worker_timeout() -> Duration { pub(super) fn measurement_worker_timeout(operation: &str) -> Duration { let budgets = EmbeddingClientBudgets::current(); if operation == "measure_true_idle" { - // The idle worker first proves the resident owner quiescent (bounded - // by the snapshot allowance), then waits out the server's own idle - // deadline plus the exit grace before the absence observation. - return Duration::from_millis(PER_USER_EMBEDDING_SERVER_IDLE_TIMEOUT_MS) - .saturating_add(IDLE_EXIT_GRACE) - .saturating_add(SNAPSHOT_TIMEOUT) + // The idle worker runs the product request that starts the measured + // idle epoch itself, then waits out the server's idle deadline plus + // the exit grace before the absence observation. + return budgets + .connect + .saturating_add(budgets.spawn) + .saturating_add(budgets.query_request) + .saturating_add(Duration::from_millis( + PER_USER_EMBEDDING_SERVER_IDLE_TIMEOUT_MS, + )) + .saturating_add(MEASUREMENT_OWNER_ABSENCE_GRACE) .saturating_add(SNAPSHOT_TIMEOUT) .saturating_add(CONTROL_TIMEOUT); } diff --git a/crates/codestory-bench/src/bin/codestory_embedding_qualification/scenarios/artifact/runner/tests.rs b/crates/codestory-bench/src/bin/codestory_embedding_qualification/scenarios/artifact/runner/tests.rs index 04664b8c1..bcc41d72c 100644 --- a/crates/codestory-bench/src/bin/codestory_embedding_qualification/scenarios/artifact/runner/tests.rs +++ b/crates/codestory-bench/src/bin/codestory_embedding_qualification/scenarios/artifact/runner/tests.rs @@ -7,9 +7,9 @@ use super::measurements::{ declared_phase_boundaries, declared_workload_id, measurement_span_interval, }; use super::process::{ - LOAD_ESTABLISHMENT_WAITS, busy_retry_worker_timeout, dead_client_setup_timeout, - load_establishment_budget, load_establishment_timeout, measurement_worker_timeout, - published_control_events, + LOAD_ESTABLISHMENT_WAITS, MEASUREMENT_OWNER_ABSENCE_GRACE, busy_retry_worker_timeout, + dead_client_setup_timeout, load_establishment_budget, load_establishment_timeout, + measurement_worker_timeout, published_control_events, }; use super::{ScenarioEvidence, WorkerOutput, opaque_measurement_sample_id}; use crate::qualification::request::{QualificationContracts, REQUIRED_METRICS, REQUIRED_SCENARIOS}; @@ -75,15 +75,26 @@ fn measurement_worker_budgets_dominate_the_deadlines_workers_honor() { measurement_worker_timeout("measure_resident_identity"), "the scenario residency probe must carry the same bulk-deadline budget as the residency measurement" ); - // The true-idle measurement worker waits out the server's own idle - // deadline before the absence observation; its watchdog must dominate - // that self-enforced wait plus its quiescence and absence-grace waits. + // The true-idle measurement worker now performs the product request that + // starts the idle epoch itself; its watchdog must cover that whole client + // chain before the server idle deadline and absence-grace wait. + assert_eq!( + MEASUREMENT_OWNER_ABSENCE_GRACE, + Duration::from_secs(30), + "the coordinator must mirror the measurement worker's owner-absence grace" + ); assert!( measurement_worker_timeout("measure_true_idle") - >= Duration::from_millis(PER_USER_EMBEDDING_SERVER_IDLE_TIMEOUT_MS) - .saturating_add(Duration::from_secs(30)) + >= budgets + .connect + .saturating_add(budgets.spawn) + .saturating_add(budgets.query_request) + .saturating_add(Duration::from_millis( + PER_USER_EMBEDDING_SERVER_IDLE_TIMEOUT_MS, + )) + .saturating_add(MEASUREMENT_OWNER_ABSENCE_GRACE) .saturating_add(SNAPSHOT_TIMEOUT), - "true-idle measurement budget must dominate the server idle deadline plus the worker's own waits" + "true-idle measurement budget must dominate its product request, server idle deadline, and worker waits" ); // The busy-retry worker seeds the held queues (queue-setup phase), then // after release drains queries bounded by its own 120s per-request diff --git a/crates/codestory-cli/src/embedding_qualification/worker.rs b/crates/codestory-cli/src/embedding_qualification/worker.rs index ec538f9a3..9acc431e9 100644 --- a/crates/codestory-cli/src/embedding_qualification/worker.rs +++ b/crates/codestory-cli/src/embedding_qualification/worker.rs @@ -228,7 +228,11 @@ fn run_measure_operation( &request.parameters, ), "measure_resident_identity" => run_measure_resident_identity(runtime, clock.as_ref()), - "measure_true_idle" => run_measure_true_idle(runtime, clock.as_ref()), + "measure_true_idle" => run_measure_true_idle( + &PerUserEmbeddingClient::for_runtime(runtime)?, + clock.as_ref(), + request.parameters.input_bytes, + ), "measure_busy_retry" => { let marker = request .retry_marker diff --git a/crates/codestory-cli/src/embedding_qualification/worker/operations/measure.rs b/crates/codestory-cli/src/embedding_qualification/worker/operations/measure.rs index 2433aeb43..2fea96180 100644 --- a/crates/codestory-cli/src/embedding_qualification/worker/operations/measure.rs +++ b/crates/codestory-cli/src/embedding_qualification/worker/operations/measure.rs @@ -19,7 +19,7 @@ use super::super::protocol::{ run_raw_protocol_exchange_with_input, validated_hello, write_protocol_frame, }; use super::ANTI_IDLE_PROTOCOL_DEADLINE_MS; -use super::owner_exit::{observe_owner_exit, wait_for_owner_exit}; +use super::owner_exit::{OwnerExitObservation, observe_owner_exit, wait_for_owner_exit}; use super::queue::{QueueOperation, run_queue_operation}; use anyhow::{Context, Result, bail}; use codestory_retrieval::{ @@ -350,30 +350,96 @@ pub(in crate::embedding_qualification::worker) fn run_measure_resident_identity( }) } -/// `true_idle_exit`: span start at the observation proving the resident owner -/// carries zero queued, active, or leased work -/// (`last_queued_active_or_leased_work_ended`), span end at the observation -/// that returned no owner (`engine_and_server_absent`). +fn true_idle_scheduler_is_drained( + active_request_count: u64, + query_depth: u64, + bulk_depth: u64, + lease_count: u64, +) -> bool { + active_request_count == 0 && query_depth == 0 && bulk_depth == 0 && lease_count == 0 +} + +fn snapshot_is_true_idle_boundary(snapshot: &EmbeddingServerSnapshot) -> bool { + snapshot.lifecycle == "resident" + && resident_engine_generation(snapshot) + && true_idle_scheduler_is_drained( + snapshot.scheduler.active_request_count, + snapshot.scheduler.query_depth, + snapshot.scheduler.bulk_depth, + snapshot.scheduler.lease_count, + ) +} + +fn same_true_idle_owner( + expected_server_instance_id: &str, + observed_server_instance_id: &str, +) -> bool { + expected_server_instance_id == observed_server_instance_id +} + +fn validate_true_idle_boundary( + expected_server_instance_id: &str, + snapshot: &EmbeddingServerSnapshot, +) -> Result<()> { + if !same_true_idle_owner( + expected_server_instance_id, + &snapshot.process.server_instance_id, + ) { + bail!("embedding_qualification_true_idle_owner_changed"); + } + if !snapshot_is_true_idle_boundary(snapshot) { + bail!("embedding_qualification_true_idle_not_quiescent"); + } + Ok(()) +} + +/// The product and observation operations used by the true-idle measurement. +/// +/// The worker implements this interface with the real per-user embedding +/// client. Tests use a scripted implementation to execute this same +/// `run_measure_true_idle` algorithm rather than a helper that production can +/// bypass. +pub(in crate::embedding_qualification::worker) trait TrueIdleMeasurementClient { + fn observe_snapshot(&self) -> Result>; + fn complete_product_query(&self, input: &str) -> Result<()>; + fn observe_owner_exit(&self) -> Result; +} + +impl TrueIdleMeasurementClient for PerUserEmbeddingClient { + fn observe_snapshot(&self) -> Result> { + self.observe() + } + + fn complete_product_query(&self, input: &str) -> Result<()> { + let _ = self.embed_query(input)?; + Ok(()) + } + + fn observe_owner_exit(&self) -> Result { + observe_owner_exit(self) + } +} + +/// `true_idle_exit`: this worker completes the last product request itself, +/// stamps its return as `last_queued_active_or_leased_work_ended`, proves the +/// resident scheduler is drained, then ends at the observation that returned +/// no owner (`engine_and_server_absent`). pub(in crate::embedding_qualification::worker) fn run_measure_true_idle( - runtime: &SidecarRuntimeConfig, + client: &dyn TrueIdleMeasurementClient, clock: &dyn AwakeMonotonicClock, + input_bytes: u32, ) -> Result { - let client = PerUserEmbeddingClient::for_runtime(runtime)?; - let idle_owner = wait_for_observed_snapshot( - &client, - clock, - SNAPSHOT_TIMEOUT, - "embedding_qualification_true_idle_not_quiescent", - |snapshot| { - snapshot.lifecycle == "resident" - && resident_engine_generation(snapshot) - && snapshot.scheduler.active_request_count == 0 - && snapshot.scheduler.query_depth == 0 - && snapshot.scheduler.bulk_depth == 0 - && snapshot.scheduler.lease_count == 0 - }, - )?; + let expected_owner = client + .observe_snapshot()? + .filter(resident_engine_generation) + .ok_or_else(|| anyhow::anyhow!("embedding_qualification_true_idle_owner_missing"))?; + let input = "q".repeat(input_bytes.max(1) as usize); + client.complete_product_query(&input)?; let start = begin_span(clock)?; + let idle_owner = client + .observe_snapshot()? + .ok_or_else(|| anyhow::anyhow!("embedding_qualification_true_idle_owner_missing"))?; + validate_true_idle_boundary(&expected_owner.process.server_instance_id, &idle_owner)?; let timeout = Duration::from_millis(PER_USER_EMBEDDING_SERVER_IDLE_TIMEOUT_MS) .saturating_add(OWNER_ABSENCE_GRACE); let wait_started = clock.now_ns(); @@ -382,7 +448,7 @@ pub(in crate::embedding_qualification::worker) fn run_measure_true_idle( wait_started, timeout, &idle_owner.process.server_instance_id, - || observe_owner_exit(&client), + || client.observe_owner_exit(), )?; let span = finish_span(clock, start)?; Ok(measurement(span, idle_owner)) @@ -652,7 +718,176 @@ fn validate_vector_response( #[cfg(test)] mod tests { - use super::workload_input; + use super::super::owner_exit::OwnerExitObservation; + use super::{ + TrueIdleMeasurementClient, run_measure_true_idle, true_idle_scheduler_is_drained, + validate_true_idle_boundary, workload_input, + }; + use anyhow::Result; + use codestory_retrieval::{ + AwakeMonotonicClock, EmbeddingServerAuthoritySnapshot, EmbeddingServerClockSnapshot, + EmbeddingServerEngineSnapshot, EmbeddingServerProcessSnapshot, + EmbeddingServerProtocolSnapshot, EmbeddingServerSchedulerSnapshot, EmbeddingServerSnapshot, + PER_USER_EMBEDDING_SERVER_SNAPSHOT_SCHEMA_VERSION, + }; + use std::collections::VecDeque; + use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; + use std::sync::{Arc, Mutex}; + use std::time::Duration; + + struct ScriptClock { + now_ns: AtomicU64, + events: Arc>>, + } + + impl ScriptClock { + fn new(events: Arc>>) -> Self { + Self { + now_ns: AtomicU64::new(0), + events, + } + } + + fn set(&self, now_ns: u64) { + self.now_ns.store(now_ns, Ordering::Release); + } + } + + impl AwakeMonotonicClock for ScriptClock { + fn now_ns(&self) -> u64 { + let now_ns = self.now_ns.load(Ordering::Acquire); + if now_ns == 100 { + self.events + .lock() + .expect("lock events") + .push("span_started"); + } + now_ns + } + + fn sleep(&self, duration: Duration) { + self.now_ns.fetch_add( + u64::try_from(duration.as_nanos()).expect("test duration fits u64"), + Ordering::AcqRel, + ); + } + + fn snapshot(&self) -> EmbeddingServerClockSnapshot { + EmbeddingServerClockSnapshot { + domain: "awake_monotonic".into(), + api: "script_clock".into(), + boot_id: "test-boot".into(), + resolution_ns: 1, + } + } + } + + struct ScriptedTrueIdleClient { + clock: Arc, + events: Arc>>, + snapshots: Mutex>, + exit_observations: Mutex>, + product_calls: AtomicUsize, + } + + impl TrueIdleMeasurementClient for ScriptedTrueIdleClient { + fn observe_snapshot(&self) -> Result> { + let (snapshot, remaining) = { + let mut snapshots = self.snapshots.lock().expect("lock snapshots"); + let snapshot = snapshots + .pop_front() + .expect("the algorithm observed past its snapshot script"); + (snapshot, snapshots.len()) + }; + let event = if remaining == 1 { + "expected_owner_observed" + } else { + self.clock.set(145); + "same_owner_drained_observed" + }; + self.events.lock().expect("lock events").push(event); + Ok(Some(snapshot)) + } + + fn complete_product_query(&self, input: &str) -> Result<()> { + assert_eq!(input, "q".repeat(256)); + self.product_calls.fetch_add(1, Ordering::AcqRel); + self.clock.set(100); + self.events + .lock() + .expect("lock events") + .push("product_completed"); + Ok(()) + } + + fn observe_owner_exit(&self) -> Result { + let observation = self + .exit_observations + .lock() + .expect("lock exit observations") + .pop_front() + .expect("the algorithm observed past its exit script"); + let event = match &observation { + OwnerExitObservation::Present(_) => "same_owner_still_present", + OwnerExitObservation::Lost => "owner_connection_lost", + OwnerExitObservation::Absent => "owner_absence_observed", + }; + self.events.lock().expect("lock events").push(event); + Ok(observation) + } + } + + fn true_idle_snapshot( + owner: &str, + active_request_count: u64, + query_depth: u64, + bulk_depth: u64, + lease_count: u64, + ) -> EmbeddingServerSnapshot { + EmbeddingServerSnapshot { + schema_version: PER_USER_EMBEDDING_SERVER_SNAPSHOT_SCHEMA_VERSION, + event_sequence: 1, + lifecycle: "resident".into(), + clock: EmbeddingServerClockSnapshot { + domain: "awake_monotonic".into(), + api: "script_clock".into(), + boot_id: "test-boot".into(), + resolution_ns: 1, + }, + protocol: EmbeddingServerProtocolSnapshot::current(), + authority: EmbeddingServerAuthoritySnapshot { + endpoint_namespace_id: "endpoint".into(), + lifetime_authority_id: "authority".into(), + listener_id: "listener".into(), + peer_verified: true, + }, + process: EmbeddingServerProcessSnapshot { + server_instance_id: owner.into(), + pid: 42, + process_start_id: "server-start".into(), + executable_sha256: "a".repeat(64), + executable_version: "0.16.3".into(), + }, + scheduler: EmbeddingServerSchedulerSnapshot { + query_capacity: 64, + query_depth, + bulk_capacity: 64, + bulk_depth, + connection_count: 1, + active_request_count, + lease_count, + active_request: None, + }, + engine: Some(EmbeddingServerEngineSnapshot { + engine_owner_id: owner.into(), + native_worker_id: "native-worker".into(), + load_generation: 1, + model_load_count: 1, + successful_encode_count: 1, + }), + failure: None, + } + } #[test] fn workload_inputs_are_deterministic_ascii_and_distinct_per_ordinal() { @@ -666,4 +901,87 @@ mod tests { assert_ne!(first, other_repeat); assert_ne!(first, other_ordinal); } + + #[test] + fn true_idle_start_is_stamped_at_product_completion_before_observation_lag() { + let events = Arc::new(Mutex::new(Vec::new())); + let clock = Arc::new(ScriptClock::new(Arc::clone(&events))); + let client = ScriptedTrueIdleClient { + clock: Arc::clone(&clock), + events: Arc::clone(&events), + snapshots: Mutex::new(VecDeque::from([ + true_idle_snapshot("measured-owner", 0, 0, 0, 0), + true_idle_snapshot("measured-owner", 0, 0, 0, 0), + ])), + exit_observations: Mutex::new(VecDeque::from([ + OwnerExitObservation::Present("measured-owner".into()), + OwnerExitObservation::Absent, + ])), + product_calls: AtomicUsize::new(0), + }; + + let measurement = + run_measure_true_idle(&client, clock.as_ref(), 256).expect("measure true idle"); + + assert_eq!(client.product_calls.load(Ordering::Acquire), 1); + assert_eq!(measurement.span.awake_started_ns, 100); + assert!(measurement.span.awake_finished_ns >= 145); + assert_eq!( + measurement.snapshot.process.server_instance_id, + "measured-owner" + ); + assert_eq!( + *events.lock().expect("lock events"), + [ + "expected_owner_observed", + "product_completed", + "span_started", + "same_owner_drained_observed", + "same_owner_still_present", + "owner_absence_observed", + ] + ); + } + + #[test] + fn true_idle_boundary_rejects_each_queued_active_or_leased_shape() { + let occupied = [ + ("active", (1, 0, 0, 0)), + ("query", (0, 1, 0, 0)), + ("bulk", (0, 0, 1, 0)), + ("lease", (0, 0, 0, 1)), + ]; + assert!(true_idle_scheduler_is_drained(0, 0, 0, 0)); + for (label, state) in occupied { + assert!( + !true_idle_scheduler_is_drained(state.0, state.1, state.2, state.3), + "{label} work must keep the true-idle boundary open" + ); + let result = validate_true_idle_boundary( + "measured-owner", + &true_idle_snapshot("measured-owner", state.0, state.1, state.2, state.3), + ); + assert_eq!( + result + .expect_err("occupied scheduler must reject the sample") + .to_string(), + "embedding_qualification_true_idle_not_quiescent", + "{label} work must fail closed rather than shift the boundary" + ); + } + } + + #[test] + fn true_idle_boundary_rejects_a_different_owner_after_product_completion() { + let result = validate_true_idle_boundary( + "measured-owner", + &true_idle_snapshot("replacement-owner", 0, 0, 0, 0), + ); + assert_eq!( + result + .expect_err("replacement owner must reject the sample") + .to_string(), + "embedding_qualification_true_idle_owner_changed" + ); + } } diff --git a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json index 4aa4b19bb..dfd170e76 100644 --- a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json +++ b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json @@ -42,25 +42,7 @@ "pure_embedding_rpc_replay_limit": 1, "query_queue_capacity": 64 }, - "freeze_record": { - "calibration_bundle_sha256": "bdaf0d80a19af41cb1acdcc4d0ee7dbca4b90481d4e5616f72416b65923f5b0e", - "calibration_freeze_digest": "67e0af9456ddc1afc7ba2298b546024f36b1f92034b9b9e7363936ffbedae0b1", - "input_constant_set_sha256": "fb0326d180cc5e2e6cae46d70e5a7706d5ed423c30fb95b4b983ed61dcdc2e2c", - "measurement_protocol_sha256": "6d29a6a92dc6f9552d004806f0bb0785c040e57a9de25e3877d26bb76e78f04b", - "protocol_sha256": "f4a3fa4afb4d5bcd8e707a5e21b687cdd023dc3398b28ff6891a2318e89c5ec7", - "run_artifact_sha256s": [ - "184d5aa079369f8057acb71576a6a1e4dfd2f4e2f56ec20d17a9aab147c9f8dc", - "2fd6767d1195f9852ca56dfffb8511eb3c001e7f65450aed33c77e27a5b60d44", - "5ef180010acdcb6f518fefc9dac00a9da8277b4c4d397c0006efd9e1424d2c42", - "6e5e26f19b6028d87bd19ffca8d4dbec5d3ebaa0fd2037003723f7eef9d9f557", - "943449b5c7c187c397f2530011d08d5f56b9f41509bd182fbd7b03f970678116", - "fcad9b4c59792c1493ccc4ae96c9d3377efced89460b561cd1c4a2c7017377d7" - ], - "selected_at": "github-actions-run:30329118684:1", - "selection_rule": "all_preregistered_clean_runs_no_outlier_removal+slow_host_floors_v1", - "selection_source_commit": "6b0f4b2edb7d99fe43b63cfb0bd3a85ca9d0032c", - "selection_source_tree": "c759c770b6de9b0b5051a145d5862444052d5cdd" - }, + "freeze_record": null, "qualification_thresholds": { "backend_observed_accelerator_residency": 1, "bulk_documents_per_second": 2, @@ -78,5 +60,5 @@ }, "schema_version": 1, "selection_protocol": "codestory-per-user-embedding-server-v1", - "status": "frozen" + "status": "unfrozen" } From e5d029520d04c0ccd5af68cefc9e79294f9ffcb2 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Wed, 29 Jul 2026 10:35:11 -0500 Subject: [PATCH 02/28] use a fresh calibration live query --- .../runtime_bootstrap_continuity.py | 14 +++- .../self_test_runtime_bootstrap_scope.py | 82 +++++++++++++++---- 2 files changed, 81 insertions(+), 15 deletions(-) diff --git a/.github/scripts/packaged_agent_proof/runtime_bootstrap_continuity.py b/.github/scripts/packaged_agent_proof/runtime_bootstrap_continuity.py index 25087c94c..d9f5f0d28 100644 --- a/.github/scripts/packaged_agent_proof/runtime_bootstrap_continuity.py +++ b/.github/scripts/packaged_agent_proof/runtime_bootstrap_continuity.py @@ -22,6 +22,17 @@ from .server_identity import server_snapshot from .subprocess_control import McpProcess +_CALIBRATION_LIVE_QUERY_SUFFIX = " calibration live encode verification" + + +def _live_project_b_query(args: argparse.Namespace, setup: RuntimeSetup) -> str: + if args.proof_tier == "calibration": + # The cold phase already queried setup.query_b. Reusing it here can hit + # both the retrieval-result and embedding caches without exercising the + # resident native encoder whose counter this phase verifies. + return f"{setup.query_b}{_CALIBRATION_LIVE_QUERY_SUFFIX}" + return setup.query_b + def _managed_runtime( args: argparse.Namespace, @@ -108,8 +119,9 @@ def _live_retrieval( }, "packet-a", ) + project_b_query = _live_project_b_query(args, setup) live_tasks["search-b-live"] = lambda: hosts.host_b.search_until_ready( - {"project": str(setup.project_b), "query": setup.query_b, "why": True}, + {"project": str(setup.project_b), "query": project_b_query, "why": True}, "search-b-live", ) run_parallel(live_tasks) diff --git a/.github/scripts/packaged_agent_proof/self_test_runtime_bootstrap_scope.py b/.github/scripts/packaged_agent_proof/self_test_runtime_bootstrap_scope.py index fdc72d7fe..1ab3a4ac8 100644 --- a/.github/scripts/packaged_agent_proof/self_test_runtime_bootstrap_scope.py +++ b/.github/scripts/packaged_agent_proof/self_test_runtime_bootstrap_scope.py @@ -16,6 +16,9 @@ _QUESTION_A = "How does the large project activate?" _QUERY_A = "large_project_probe" _QUERY_B = "small_project_probe" +_CALIBRATION_LIVE_QUERY = ( + "small_project_probe calibration live encode verification" +) _MANIFEST = {"asset_target": "linux-x64"} @@ -56,39 +59,64 @@ def _run_live_retrieval_case( proof_tier: str, *, trap_project_a: bool = False, -) -> Mock: +) -> tuple[Mock, dict]: setup = _setup() cold = _cold() args = _args(proof_tier) host_a = Mock() host_b = Mock() + cache_state = { + "successful_encode_count": cold.snapshot_a["engine"][ + "successful_encode_count" + ], + "seen_queries": {_QUERY_B}, + } + + def advance_for_packet(*_arguments: object) -> tuple[dict, int]: + cache_state["successful_encode_count"] += 1 + return {"self_test": "packet"}, 1 + if trap_project_a: host_a.tool_until_ready.side_effect = ProofFailure( "calibration scheduled a second broad project-A request" ) else: - host_a.tool_until_ready.return_value = ({"self_test": "packet"}, 1) + host_a.tool_until_ready.side_effect = advance_for_packet host_a.search_until_ready.side_effect = ProofFailure( f"{proof_tier} moved its broad project-A request from packet to search" ) - host_b.search_until_ready.return_value = ({"self_test": "search"}, 1) - host_b.engine_diagnostics.return_value = {"self_test": "diagnostics"} + + def search_project_b(arguments: dict, _label: str) -> tuple[dict, int]: + query = arguments["query"] + if query not in cache_state["seen_queries"]: + cache_state["seen_queries"].add(query) + cache_state["successful_encode_count"] += 1 + return {"self_test": "search"}, 1 + + def engine_diagnostics(*_arguments: object) -> dict: + return { + "engine": { + "successful_encode_count": cache_state[ + "successful_encode_count" + ] + }, + "process": {"server_instance_id": "server-self-test"}, + } + + host_b.search_until_ready.side_effect = search_project_b + host_b.engine_diagnostics.side_effect = engine_diagnostics hosts = SimpleNamespace( host_a=host_a, host_b=host_b, start_a="host-a-start", start_b="host-b-start", ) - after = { - "engine": {"successful_encode_count": 8}, - "process": {"server_instance_id": "server-self-test"}, - } memory = {"self_test": "five-process-memory"} with ( patch.object( runtime_bootstrap_continuity, "server_snapshot", - return_value=after, + side_effect=lambda diagnostics, _manifest, *, require_resident: diagnostics, ) as snapshot_check, patch.object( runtime_bootstrap_continuity, @@ -107,13 +135,23 @@ def _run_live_retrieval_case( observed == memory, f"{proof_tier} live retrieval omitted five-process memory evidence", ) + expected_query = ( + _CALIBRATION_LIVE_QUERY + if proof_tier == "calibration" + else _QUERY_B + ) + if proof_tier == "calibration": + require( + expected_query.strip().lower() != _QUERY_B.strip().lower(), + "calibration live query reused the cold-phase embedding cache key", + ) require( host_b.method_calls == [ call.search_until_ready( { "project": str(_PROJECT_B), - "query": _QUERY_B, + "query": expected_query, "why": True, }, "search-b-live", @@ -122,8 +160,13 @@ def _run_live_retrieval_case( ], f"{proof_tier} live retrieval changed the bounded project-B path", ) + after = memory_check.call_args.kwargs["snapshot"] + require( + after["engine"]["successful_encode_count"] == 8, + f"{proof_tier} live retrieval did not model one fresh native encode", + ) snapshot_check.assert_called_once_with( - host_b.engine_diagnostics.return_value, + after, _MANIFEST, require_resident=True, ) @@ -140,17 +183,24 @@ def _run_live_retrieval_case( manifest=_MANIFEST, expected_backend="CPU", ) - return host_a + return host_a, cache_state def _live_retrieval_scope_tests() -> None: - host_a = _run_live_retrieval_case("calibration", trap_project_a=True) + host_a, cache_state = _run_live_retrieval_case( + "calibration", + trap_project_a=True, + ) require( host_a.method_calls == [], "calibration scheduled a second broad operation on project A", ) + require( + cache_state["seen_queries"] == {_QUERY_B, _CALIBRATION_LIVE_QUERY}, + "calibration did not prove a cache-distinct project-B query", + ) for proof_tier in ("hosted_package", "protected_hardware", "installed_runtime"): - host_a = _run_live_retrieval_case(proof_tier) + host_a, cache_state = _run_live_retrieval_case(proof_tier) require( host_a.method_calls == [ @@ -166,6 +216,10 @@ def _live_retrieval_scope_tests() -> None: ], f"{proof_tier} no longer requires the project-A packet path", ) + require( + cache_state["seen_queries"] == {_QUERY_B}, + f"{proof_tier} changed the established project-B query", + ) def _run_continuity_case(proof_tier: str, expected_project: Path) -> None: From 5cd73eb57e76ab26813a4a35782f026f8d39f2c5 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Wed, 29 Jul 2026 14:33:45 -0500 Subject: [PATCH 03/28] accept native gpu backend families --- .../scenarios/artifact/runner/measurements.rs | 66 +++++++++++++++++-- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/crates/codestory-bench/src/bin/codestory_embedding_qualification/scenarios/artifact/runner/measurements.rs b/crates/codestory-bench/src/bin/codestory_embedding_qualification/scenarios/artifact/runner/measurements.rs index 2da99ecad..06f6dc2d8 100644 --- a/crates/codestory-bench/src/bin/codestory_embedding_qualification/scenarios/artifact/runner/measurements.rs +++ b/crates/codestory-bench/src/bin/codestory_embedding_qualification/scenarios/artifact/runner/measurements.rs @@ -841,7 +841,7 @@ fn validate_constant_engine_evidence( || identity.load_error.is_some() || identity.policy != "accelerated" || identity.backend.eq_ignore_ascii_case("cpu") - || !identity.backend.eq_ignore_ascii_case(expected_backend) + || !constant_backend_matches_expected(&identity.backend, expected_backend) || identity.model_digest != expected_model_sha256 || identity.materialized_model_sha256 != expected_model_sha256 || !identity.embedded_model @@ -852,6 +852,29 @@ fn validate_constant_engine_evidence( Ok(()) } +fn constant_backend_matches_expected(observed: &str, expected: &str) -> bool { + let Some(expected_family) = constant_backend_family(expected) else { + return false; + }; + constant_backend_family(observed) == Some(expected_family) +} + +fn constant_backend_family(value: &str) -> Option<&'static str> { + let normalized = value.trim().to_ascii_lowercase(); + match normalized.as_str() { + "metal" | "mtl" => Some("metal"), + "vulkan" => Some("vulkan"), + value + if value.strip_prefix("vulkan").is_some_and(|suffix| { + !suffix.is_empty() && suffix.bytes().all(|byte| byte.is_ascii_digit()) + }) => + { + Some("vulkan") + } + _ => None, + } +} + fn expected_materialization_reuse(run_index: u32) -> Result { match run_index { 1 => Ok(false), @@ -1088,11 +1111,42 @@ mod constant_calibration_tests { fn engine_precondition_binds_accelerated_backend_model_and_reuse_only() { let server = server_identity("server-a"); let metal = engine_identity("server-a", "Metal", false); - validate_constant_engine_evidence(&metal, &server, "metal", &"a".repeat(64), false) - .expect("Metal identity"); - let vulkan = engine_identity("server-a", "Vulkan", false); - validate_constant_engine_evidence(&vulkan, &server, "vulkan", &"a".repeat(64), false) - .expect("Vulkan identity"); + for (observed, expected) in [ + ("Metal", "metal"), + ("MTL", "metal"), + ("Vulkan", "vulkan"), + ("Vulkan0", "vulkan"), + ] { + let identity = engine_identity("server-a", observed, false); + validate_constant_engine_evidence(&identity, &server, expected, &"a".repeat(64), false) + .unwrap_or_else(|error| { + panic!("{observed} must satisfy expected GPU family {expected}: {error}") + }); + } + for (observed, expected) in [ + ("CPU", "metal"), + ("cpu_explicit", "metal"), + ("", "metal"), + ("unknown", "metal"), + ("metal-cpu", "metal"), + ("mtl0", "metal"), + ("MTL", "vulkan"), + ("Vulkan", "metal"), + ("vulkan-cpu", "vulkan"), + ] { + let identity = engine_identity("server-a", observed, false); + assert!( + validate_constant_engine_evidence( + &identity, + &server, + expected, + &"a".repeat(64), + false, + ) + .is_err(), + "{observed} must not satisfy expected GPU family {expected}" + ); + } let mut invalid = metal.clone(); invalid.policy = "cpu_explicit".into(); From 7c245279a3356bd5ed1841c1d29b453f2b02f99f Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Wed, 29 Jul 2026 15:01:40 -0500 Subject: [PATCH 04/28] freeze embedding server constants --- ...er-user-embedding-server-constant-set.json | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json index 70b89b877..7d8ac092d 100644 --- a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json +++ b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json @@ -1,15 +1,15 @@ { "calibration_required_values": { "capacity_retry_policy": { - "retry_after_ms": 40, + "retry_after_ms": 59, "retry_class": "after_capacity_change", "retry_condition_source": "named_condition_from_typed_capacity_response" }, "connect_timeout_ms": 2000, "election_backoff_policy": { - "initial_backoff_ms": 7, + "initial_backoff_ms": 8, "jitter": "sha256(process_start_id||attempt) modulo inclusive [initial_backoff_ms,maximum_backoff_ms]", - "maximum_backoff_ms": 102 + "maximum_backoff_ms": 104 }, "hard_native_no_progress_ms": 385431, "request_deadlines_ms": { @@ -43,7 +43,22 @@ "query_queue_capacity": 64, "true_idle_observation_grace_ms": 2500 }, - "freeze_record": null, + "freeze_record": { + "calibration_bundle_sha256": "aeb9b86a0129a5d5dae870ccbc3e75959b92cc96e058b8c48975c21c3846aae5", + "calibration_freeze_digest": "7f227a1a30dca7c6ae279ea5bd1982d96a2f39cf11ca441463199047318b92b3", + "input_constant_set_sha256": "c67187b461e07bc09352cbb2b3eaaf1760780e32b284e6d127de9d49adb3c51b", + "measurement_protocol_sha256": "ce4bc4911c13d3e52438638e3abb5383e23cd9b400893805f589cdf6350e16e1", + "protocol_sha256": "f4a3fa4afb4d5bcd8e707a5e21b687cdd023dc3398b28ff6891a2318e89c5ec7", + "run_artifact_sha256s": [ + "427c0db697cb9ff82f8291055701fb4060369699c48c45957b24cb9957ca60af", + "93cc3597879a1e0a207738eab8185867354799ddab2563fc5a62dedb2129451a", + "cff89ed425a94d7e2fe31b72706e3ee91df4882c584730fda98e39f0c0264789" + ], + "selected_at": "github-actions-run:30485668299:1", + "selection_rule": "constant_only_three_fresh_generations_one_sample_each+slow_host_floors_v2", + "selection_source_commit": "5cd73eb57e76ab26813a4a35782f026f8d39f2c5", + "selection_source_tree": "6f608347ebc721a1407902947c4563bf1b1e764d" + }, "qualification_thresholds": { "backend_observed_accelerator_residency": 1, "bulk_documents_per_second": 2, @@ -61,5 +76,5 @@ }, "schema_version": 1, "selection_protocol": "codestory-per-user-embedding-server-v1", - "status": "unfrozen" + "status": "frozen" } From 4f425deebae2071f71caa898c2020c68a387d1cd Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Wed, 29 Jul 2026 16:33:05 -0500 Subject: [PATCH 05/28] unfreeze embedding constants for repaired candidate --- ...er-user-embedding-server-constant-set.json | 25 ++++--------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json index 7d8ac092d..70b89b877 100644 --- a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json +++ b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json @@ -1,15 +1,15 @@ { "calibration_required_values": { "capacity_retry_policy": { - "retry_after_ms": 59, + "retry_after_ms": 40, "retry_class": "after_capacity_change", "retry_condition_source": "named_condition_from_typed_capacity_response" }, "connect_timeout_ms": 2000, "election_backoff_policy": { - "initial_backoff_ms": 8, + "initial_backoff_ms": 7, "jitter": "sha256(process_start_id||attempt) modulo inclusive [initial_backoff_ms,maximum_backoff_ms]", - "maximum_backoff_ms": 104 + "maximum_backoff_ms": 102 }, "hard_native_no_progress_ms": 385431, "request_deadlines_ms": { @@ -43,22 +43,7 @@ "query_queue_capacity": 64, "true_idle_observation_grace_ms": 2500 }, - "freeze_record": { - "calibration_bundle_sha256": "aeb9b86a0129a5d5dae870ccbc3e75959b92cc96e058b8c48975c21c3846aae5", - "calibration_freeze_digest": "7f227a1a30dca7c6ae279ea5bd1982d96a2f39cf11ca441463199047318b92b3", - "input_constant_set_sha256": "c67187b461e07bc09352cbb2b3eaaf1760780e32b284e6d127de9d49adb3c51b", - "measurement_protocol_sha256": "ce4bc4911c13d3e52438638e3abb5383e23cd9b400893805f589cdf6350e16e1", - "protocol_sha256": "f4a3fa4afb4d5bcd8e707a5e21b687cdd023dc3398b28ff6891a2318e89c5ec7", - "run_artifact_sha256s": [ - "427c0db697cb9ff82f8291055701fb4060369699c48c45957b24cb9957ca60af", - "93cc3597879a1e0a207738eab8185867354799ddab2563fc5a62dedb2129451a", - "cff89ed425a94d7e2fe31b72706e3ee91df4882c584730fda98e39f0c0264789" - ], - "selected_at": "github-actions-run:30485668299:1", - "selection_rule": "constant_only_three_fresh_generations_one_sample_each+slow_host_floors_v2", - "selection_source_commit": "5cd73eb57e76ab26813a4a35782f026f8d39f2c5", - "selection_source_tree": "6f608347ebc721a1407902947c4563bf1b1e764d" - }, + "freeze_record": null, "qualification_thresholds": { "backend_observed_accelerator_residency": 1, "bulk_documents_per_second": 2, @@ -76,5 +61,5 @@ }, "schema_version": 1, "selection_protocol": "codestory-per-user-embedding-server-v1", - "status": "frozen" + "status": "unfrozen" } From f511689e0a7164ee11f3a453c736276cdf89ee5c Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Wed, 29 Jul 2026 16:59:52 -0500 Subject: [PATCH 06/28] freeze embedding server constants --- ...er-user-embedding-server-constant-set.json | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json index 70b89b877..26777e0b8 100644 --- a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json +++ b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json @@ -1,13 +1,13 @@ { "calibration_required_values": { "capacity_retry_policy": { - "retry_after_ms": 40, + "retry_after_ms": 48, "retry_class": "after_capacity_change", "retry_condition_source": "named_condition_from_typed_capacity_response" }, "connect_timeout_ms": 2000, "election_backoff_policy": { - "initial_backoff_ms": 7, + "initial_backoff_ms": 8, "jitter": "sha256(process_start_id||attempt) modulo inclusive [initial_backoff_ms,maximum_backoff_ms]", "maximum_backoff_ms": 102 }, @@ -43,7 +43,22 @@ "query_queue_capacity": 64, "true_idle_observation_grace_ms": 2500 }, - "freeze_record": null, + "freeze_record": { + "calibration_bundle_sha256": "6c8a2d449adeb763851e4b54024ce43149a8fc4875d0439edd5e9b0199179a4d", + "calibration_freeze_digest": "afe12bc64d9ec09d3085c8622198d4692b2b1f86c9d514448c0d02319e2f75db", + "input_constant_set_sha256": "c67187b461e07bc09352cbb2b3eaaf1760780e32b284e6d127de9d49adb3c51b", + "measurement_protocol_sha256": "ce4bc4911c13d3e52438638e3abb5383e23cd9b400893805f589cdf6350e16e1", + "protocol_sha256": "f4a3fa4afb4d5bcd8e707a5e21b687cdd023dc3398b28ff6891a2318e89c5ec7", + "run_artifact_sha256s": [ + "18a61ecbbbecb689dc32c6e50ef2930be710776e16c994fa7445128f3f0fa78f", + "b1603c475f1ae54cbe1207d2ce3842638c3ee4f9c598234fbf4cfe5f032cac21", + "cdeed297e9f098f6c542cec901e4a5c3292edf0b0654f841520e31e555d6712c" + ], + "selected_at": "github-actions-run:30493877118:1", + "selection_rule": "constant_only_three_fresh_generations_one_sample_each+slow_host_floors_v2", + "selection_source_commit": "4f425deebae2071f71caa898c2020c68a387d1cd", + "selection_source_tree": "0a003c8b13b1ff2947f88a3f088285f29dbd944c" + }, "qualification_thresholds": { "backend_observed_accelerator_residency": 1, "bulk_documents_per_second": 2, @@ -61,5 +76,5 @@ }, "schema_version": 1, "selection_protocol": "codestory-per-user-embedding-server-v1", - "status": "unfrozen" + "status": "frozen" } From 08451fa2cfc6665821248bebc60bd8b5636c1258 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Wed, 29 Jul 2026 17:56:22 -0500 Subject: [PATCH 07/28] unfreeze constants after workflow repair --- ...er-user-embedding-server-constant-set.json | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json index 26777e0b8..70b89b877 100644 --- a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json +++ b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json @@ -1,13 +1,13 @@ { "calibration_required_values": { "capacity_retry_policy": { - "retry_after_ms": 48, + "retry_after_ms": 40, "retry_class": "after_capacity_change", "retry_condition_source": "named_condition_from_typed_capacity_response" }, "connect_timeout_ms": 2000, "election_backoff_policy": { - "initial_backoff_ms": 8, + "initial_backoff_ms": 7, "jitter": "sha256(process_start_id||attempt) modulo inclusive [initial_backoff_ms,maximum_backoff_ms]", "maximum_backoff_ms": 102 }, @@ -43,22 +43,7 @@ "query_queue_capacity": 64, "true_idle_observation_grace_ms": 2500 }, - "freeze_record": { - "calibration_bundle_sha256": "6c8a2d449adeb763851e4b54024ce43149a8fc4875d0439edd5e9b0199179a4d", - "calibration_freeze_digest": "afe12bc64d9ec09d3085c8622198d4692b2b1f86c9d514448c0d02319e2f75db", - "input_constant_set_sha256": "c67187b461e07bc09352cbb2b3eaaf1760780e32b284e6d127de9d49adb3c51b", - "measurement_protocol_sha256": "ce4bc4911c13d3e52438638e3abb5383e23cd9b400893805f589cdf6350e16e1", - "protocol_sha256": "f4a3fa4afb4d5bcd8e707a5e21b687cdd023dc3398b28ff6891a2318e89c5ec7", - "run_artifact_sha256s": [ - "18a61ecbbbecb689dc32c6e50ef2930be710776e16c994fa7445128f3f0fa78f", - "b1603c475f1ae54cbe1207d2ce3842638c3ee4f9c598234fbf4cfe5f032cac21", - "cdeed297e9f098f6c542cec901e4a5c3292edf0b0654f841520e31e555d6712c" - ], - "selected_at": "github-actions-run:30493877118:1", - "selection_rule": "constant_only_three_fresh_generations_one_sample_each+slow_host_floors_v2", - "selection_source_commit": "4f425deebae2071f71caa898c2020c68a387d1cd", - "selection_source_tree": "0a003c8b13b1ff2947f88a3f088285f29dbd944c" - }, + "freeze_record": null, "qualification_thresholds": { "backend_observed_accelerator_residency": 1, "bulk_documents_per_second": 2, @@ -76,5 +61,5 @@ }, "schema_version": 1, "selection_protocol": "codestory-per-user-embedding-server-v1", - "status": "frozen" + "status": "unfrozen" } From e38fbe5ba15b7b284d99665bc3bff005e765ac9a Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Wed, 29 Jul 2026 18:17:57 -0500 Subject: [PATCH 08/28] freeze constants from repaired calibration --- ...er-user-embedding-server-constant-set.json | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json index 70b89b877..7d2d23f20 100644 --- a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json +++ b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json @@ -1,13 +1,13 @@ { "calibration_required_values": { "capacity_retry_policy": { - "retry_after_ms": 40, + "retry_after_ms": 57, "retry_class": "after_capacity_change", "retry_condition_source": "named_condition_from_typed_capacity_response" }, "connect_timeout_ms": 2000, "election_backoff_policy": { - "initial_backoff_ms": 7, + "initial_backoff_ms": 8, "jitter": "sha256(process_start_id||attempt) modulo inclusive [initial_backoff_ms,maximum_backoff_ms]", "maximum_backoff_ms": 102 }, @@ -43,7 +43,22 @@ "query_queue_capacity": 64, "true_idle_observation_grace_ms": 2500 }, - "freeze_record": null, + "freeze_record": { + "calibration_bundle_sha256": "650c558b108c6473f0f244aaeb8680938f45cb51ad30cba3cc42ccaf330de004", + "calibration_freeze_digest": "14f02f80a8bf0f68d02e820b5d16d8b171839cf60ecbf643ddc6191c9f9bb40e", + "input_constant_set_sha256": "c67187b461e07bc09352cbb2b3eaaf1760780e32b284e6d127de9d49adb3c51b", + "measurement_protocol_sha256": "ce4bc4911c13d3e52438638e3abb5383e23cd9b400893805f589cdf6350e16e1", + "protocol_sha256": "f4a3fa4afb4d5bcd8e707a5e21b687cdd023dc3398b28ff6891a2318e89c5ec7", + "run_artifact_sha256s": [ + "65789b6090a68a45ab770513fc2d20ed3a228a371f1dbdd76aec6ce568dfd8e8", + "b74a95d375f6e3e37b182d91a120b2d05071f2016bf7d49dc3355bb8a2b42dec", + "bdd9340b0020be257fa2af59cb7231eed5270ee2bf3509b04bdc30cc4861da11" + ], + "selected_at": "github-actions-run:30498515242:1", + "selection_rule": "constant_only_three_fresh_generations_one_sample_each+slow_host_floors_v2", + "selection_source_commit": "08451fa2cfc6665821248bebc60bd8b5636c1258", + "selection_source_tree": "2215e54a1ef49f1b1bb8f0b2dab5bcdaaec19706" + }, "qualification_thresholds": { "backend_observed_accelerator_residency": 1, "bulk_documents_per_second": 2, @@ -61,5 +76,5 @@ }, "schema_version": 1, "selection_protocol": "codestory-per-user-embedding-server-v1", - "status": "unfrozen" + "status": "frozen" } From 34154f213a411bb0c89b2fe103ea641f454ffb79 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Wed, 29 Jul 2026 20:47:32 -0500 Subject: [PATCH 09/28] unfreeze embedding constants --- ...er-user-embedding-server-constant-set.json | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json index 7d2d23f20..70b89b877 100644 --- a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json +++ b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json @@ -1,13 +1,13 @@ { "calibration_required_values": { "capacity_retry_policy": { - "retry_after_ms": 57, + "retry_after_ms": 40, "retry_class": "after_capacity_change", "retry_condition_source": "named_condition_from_typed_capacity_response" }, "connect_timeout_ms": 2000, "election_backoff_policy": { - "initial_backoff_ms": 8, + "initial_backoff_ms": 7, "jitter": "sha256(process_start_id||attempt) modulo inclusive [initial_backoff_ms,maximum_backoff_ms]", "maximum_backoff_ms": 102 }, @@ -43,22 +43,7 @@ "query_queue_capacity": 64, "true_idle_observation_grace_ms": 2500 }, - "freeze_record": { - "calibration_bundle_sha256": "650c558b108c6473f0f244aaeb8680938f45cb51ad30cba3cc42ccaf330de004", - "calibration_freeze_digest": "14f02f80a8bf0f68d02e820b5d16d8b171839cf60ecbf643ddc6191c9f9bb40e", - "input_constant_set_sha256": "c67187b461e07bc09352cbb2b3eaaf1760780e32b284e6d127de9d49adb3c51b", - "measurement_protocol_sha256": "ce4bc4911c13d3e52438638e3abb5383e23cd9b400893805f589cdf6350e16e1", - "protocol_sha256": "f4a3fa4afb4d5bcd8e707a5e21b687cdd023dc3398b28ff6891a2318e89c5ec7", - "run_artifact_sha256s": [ - "65789b6090a68a45ab770513fc2d20ed3a228a371f1dbdd76aec6ce568dfd8e8", - "b74a95d375f6e3e37b182d91a120b2d05071f2016bf7d49dc3355bb8a2b42dec", - "bdd9340b0020be257fa2af59cb7231eed5270ee2bf3509b04bdc30cc4861da11" - ], - "selected_at": "github-actions-run:30498515242:1", - "selection_rule": "constant_only_three_fresh_generations_one_sample_each+slow_host_floors_v2", - "selection_source_commit": "08451fa2cfc6665821248bebc60bd8b5636c1258", - "selection_source_tree": "2215e54a1ef49f1b1bb8f0b2dab5bcdaaec19706" - }, + "freeze_record": null, "qualification_thresholds": { "backend_observed_accelerator_residency": 1, "bulk_documents_per_second": 2, @@ -76,5 +61,5 @@ }, "schema_version": 1, "selection_protocol": "codestory-per-user-embedding-server-v1", - "status": "frozen" + "status": "unfrozen" } From c2318eb2405e16ae3a14673325af0e1918bc4efd Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Wed, 29 Jul 2026 21:18:18 -0500 Subject: [PATCH 10/28] freeze embedding server constants --- ...er-user-embedding-server-constant-set.json | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json index 70b89b877..1801527ec 100644 --- a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json +++ b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json @@ -1,7 +1,7 @@ { "calibration_required_values": { "capacity_retry_policy": { - "retry_after_ms": 40, + "retry_after_ms": 55, "retry_class": "after_capacity_change", "retry_condition_source": "named_condition_from_typed_capacity_response" }, @@ -43,7 +43,22 @@ "query_queue_capacity": 64, "true_idle_observation_grace_ms": 2500 }, - "freeze_record": null, + "freeze_record": { + "calibration_bundle_sha256": "1fddcb3e67ae5af4a09022dcbd973cc9e58f2b24ab1485783bbf51d6b8ab222d", + "calibration_freeze_digest": "3e66f41f4dbc7642512c8edfbc5230e9bc7ea2fc2073283f30ce2102832c3d70", + "input_constant_set_sha256": "c67187b461e07bc09352cbb2b3eaaf1760780e32b284e6d127de9d49adb3c51b", + "measurement_protocol_sha256": "ce4bc4911c13d3e52438638e3abb5383e23cd9b400893805f589cdf6350e16e1", + "protocol_sha256": "f4a3fa4afb4d5bcd8e707a5e21b687cdd023dc3398b28ff6891a2318e89c5ec7", + "run_artifact_sha256s": [ + "290295c5d27d513285c7ec22f4f6622b65630975a3b69e5410aef6ace9843c50", + "6123b83da3a72124a88065c9c9a5ac01d9899e746676bd4443e2f6a439837536", + "655967a0dd9ad382b01418410d9b0f9ed6ff9695c2d8105ac3343e1ec66a56e1" + ], + "selected_at": "github-actions-run:30507598793:1", + "selection_rule": "constant_only_three_fresh_generations_one_sample_each+slow_host_floors_v2", + "selection_source_commit": "34154f213a411bb0c89b2fe103ea641f454ffb79", + "selection_source_tree": "5c4fa7bf948410928bd82bf7c020153c2a579abc" + }, "qualification_thresholds": { "backend_observed_accelerator_residency": 1, "bulk_documents_per_second": 2, @@ -61,5 +76,5 @@ }, "schema_version": 1, "selection_protocol": "codestory-per-user-embedding-server-v1", - "status": "unfrozen" + "status": "frozen" } From f1fb7af2458ef16129c1bca71b122f0cc9b3ef05 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Wed, 29 Jul 2026 22:24:33 -0500 Subject: [PATCH 11/28] stage cargo-linked qualification drivers --- .github/scripts/check-workflow-policy.mjs | 18 ++-- .../scripts/check-workflow-policy.test.mjs | 84 ++++++++++++++++++- .../scripts/qualification-driver-artifact.mjs | 18 +++- ...er-user-embedding-server-constant-set.json | 21 +---- 4 files changed, 115 insertions(+), 26 deletions(-) diff --git a/.github/scripts/check-workflow-policy.mjs b/.github/scripts/check-workflow-policy.mjs index 635e0abd5..540f0b12b 100644 --- a/.github/scripts/check-workflow-policy.mjs +++ b/.github/scripts/check-workflow-policy.mjs @@ -602,6 +602,8 @@ export function qualificationDriverArtifactViolations( 'binary: "codestory_embedding_qualification.exe"', 'rustTarget: "x86_64-pc-windows-msvc"', "metadata.isSymbolicLink()\n || !metadata.isFile()\n || metadata.nlink !== 1", + "function regularBuildOutput(file, label)", + "!Number.isSafeInteger(metadata.nlink)\n || metadata.nlink < 1", "metadata.isSymbolicLink() || !metadata.isDirectory()", 'fail("qualification driver helper arguments changed")', "containedRelativePath(root, candidate, label)", @@ -610,7 +612,10 @@ export function qualificationDriverArtifactViolations( 'fail(`${label} must not traverse symbolic links`)', "`codestory-cli-v${version}-${assetTarget}.${contract.archiveExtension}`", 'targetDir,\n contract.rustTarget,\n "release",\n contract.binary', + 'const sourceMetadata = regularBuildOutput(', 'fail("qualification driver artifact directory must start empty")', + "copyFileSync(source, staged)", + 'const stagedMetadata = regularFile(staged, "staged qualification driver")', "archiveBytes: archiveMetadata.size", "archiveDigest: sha256(archivePath)", "archiveFile: expectedArchiveFile", @@ -691,13 +696,14 @@ const packagedHostCompilerFinalizerDigest = "b77d8bb12c2748bfe016ab65ccb2f4581356f3ccf1d666e747306caffd6c0c46"; // The companion qualification driver is intentionally retained only inside // the private Actions package artifact. This digest pins both sides of that -// contract: the producer copies only the selected target binary and binds it -// to the exact candidate archive, while the consumer rejects symlinks, extra -// files, identity drift, and byte drift before restoring execute permission. -// Any helper edit therefore requires a policy and mutation-test review in the -// same PR as the workflow change. +// contract: the producer may read Cargo's trusted hard-linked build output, +// but retains only a new singly linked copy bound to the exact candidate +// archive. The consumer rejects symlinks, retained hardlinks, extra files, +// identity drift, and byte drift before restoring execute permission. +// Any helper edit therefore requires policy and mutation-test review in the +// same PR. const qualificationDriverArtifactDigest = - "f7946e03fa6e272ca17f12616b82579da041de4d7c30b19d24ddbc5f1c7f0063"; + "efc5126e24162d52f9da8bac38c3414b3a7492fb17eed5ff19867fadad69623e"; const draftProofCommands = [ "cargo test --locked -p codestory-llama-sys --test native_staging", "cargo test --locked -p codestory-llama-sys --test model_staging", diff --git a/.github/scripts/check-workflow-policy.test.mjs b/.github/scripts/check-workflow-policy.test.mjs index 4609f6e82..607b99ef7 100644 --- a/.github/scripts/check-workflow-policy.test.mjs +++ b/.github/scripts/check-workflow-policy.test.mjs @@ -3,6 +3,8 @@ import { spawnSync } from "node:child_process"; import { createHash } from "node:crypto"; import { chmodSync, + linkSync, + lstatSync, mkdirSync, mkdtempSync, readFileSync, @@ -44,6 +46,10 @@ import { validateWorkflows, windowsManifestProofPolicyViolations, } from "./check-workflow-policy.mjs"; +import { + produceQualificationDriverArtifact, + verifyQualificationDriverArtifact, +} from "./qualification-driver-artifact.mjs"; const fullSha = "0123456789abcdef0123456789abcdef01234567"; const proofTopology = "proof5-v1-64015a841a2f69f33f7c9ce284f671ad27b3923a58db865fd4806d86230df6c5"; @@ -1453,7 +1459,7 @@ test("qualification driver is built once, retained privately, authenticated, and source.replace("sha256(archivePath) !== identity.archive.sha256", "false")], ["helper follows linked path ancestors", source => source.replace("lstatSync(cursor).isSymbolicLink()", "false")], - ["helper accepts hardlinked drivers", source => + ["helper accepts hardlinked retained drivers", source => source.replace("metadata.nlink !== 1", "false")], ["helper accepts extra identity fields", source => source.replace('fail(`${label} keys changed`)', "return")], @@ -1498,6 +1504,82 @@ test("qualification driver is built once, retained privately, authenticated, and } }); +test("qualification driver retention breaks a Cargo source hardlink and rejects retained hardlinks", () => { + const directory = mkdtempSync( + path.join(os.tmpdir(), "codestory-qualification-driver-"), + ); + try { + const targetDirectory = path.join(directory, "target"); + const releaseDirectory = path.join( + targetDirectory, + "x86_64-pc-windows-msvc", + "release", + ); + const depsDirectory = path.join(releaseDirectory, "deps"); + mkdirSync(depsDirectory, { recursive: true }); + const originalDriver = path.join( + depsDirectory, + "codestory_embedding_qualification-hash.exe", + ); + const cargoDriver = path.join( + releaseDirectory, + "codestory_embedding_qualification.exe", + ); + writeFileSync(originalDriver, "qualification-driver-v1"); + chmodSync(originalDriver, 0o755); + linkSync(originalDriver, cargoDriver); + assert.equal(lstatSync(cargoDriver).nlink, 2); + + const archive = path.join( + directory, + "codestory-cli-v0.16.3-windows-x64.zip", + ); + writeFileSync(archive, "candidate-archive"); + const artifactDirectory = path.join(directory, "artifact"); + const produced = produceQualificationDriverArtifact({ + archive, + assetTarget: "windows-x64", + outDir: artifactDirectory, + sourceSha: "a".repeat(40), + sourceTree: "b".repeat(40), + targetDir: targetDirectory, + trustedRoot: directory, + version: "0.16.3", + }); + assert.equal(lstatSync(produced.driver).nlink, 1); + assert.equal(readFileSync(produced.driver, "utf8"), "qualification-driver-v1"); + + writeFileSync(originalDriver, "qualification-driver-v2"); + assert.equal(readFileSync(produced.driver, "utf8"), "qualification-driver-v1"); + const verified = verifyQualificationDriverArtifact({ + archive, + artifactDir: artifactDirectory, + assetTarget: "windows-x64", + sourceSha: "a".repeat(40), + sourceTree: "b".repeat(40), + trustedRoot: directory, + version: "0.16.3", + }); + assert.equal(verified.identity.driver.sha256, produced.identity.driver.sha256); + + linkSync(produced.driver, path.join(directory, "retained-driver-alias.exe")); + assert.throws( + () => verifyQualificationDriverArtifact({ + archive, + artifactDir: artifactDirectory, + assetTarget: "windows-x64", + sourceSha: "a".repeat(40), + sourceTree: "b".repeat(40), + trustedRoot: directory, + version: "0.16.3", + }), + /qualification driver artifact must be a regular, non-symlink, singly linked file/u, + ); + } finally { + rmSync(directory, { recursive: true, force: true }); + } +}); + test("release workflows retain the closeout coordinator contract test", () => { assert.deepEqual(validateWorkflows(loadWorkflows()), []); for (const [file, jobName] of [ diff --git a/.github/scripts/qualification-driver-artifact.mjs b/.github/scripts/qualification-driver-artifact.mjs index 5d8cfaa90..d9f973550 100644 --- a/.github/scripts/qualification-driver-artifact.mjs +++ b/.github/scripts/qualification-driver-artifact.mjs @@ -119,6 +119,19 @@ function regularFile(file, label) { return metadata; } +function regularBuildOutput(file, label) { + const metadata = lstatSync(file); + if ( + metadata.isSymbolicLink() + || !metadata.isFile() + || !Number.isSafeInteger(metadata.nlink) + || metadata.nlink < 1 + ) { + fail(`${label} must be a regular, non-symlink build output`); + } + return metadata; +} + function regularDirectory(directory, label) { const metadata = lstatSync(directory); if (metadata.isSymbolicLink() || !metadata.isDirectory()) { @@ -270,7 +283,10 @@ export function produceQualificationDriverArtifact({ label: "qualification driver source", root: targetDir, }); - const sourceMetadata = regularFile(source, "qualification driver"); + const sourceMetadata = regularBuildOutput( + source, + "qualification driver source", + ); if (process.platform !== "win32" && (sourceMetadata.mode & 0o111) === 0) { fail("qualification driver must be executable"); } diff --git a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json index 1801527ec..70b89b877 100644 --- a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json +++ b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json @@ -1,7 +1,7 @@ { "calibration_required_values": { "capacity_retry_policy": { - "retry_after_ms": 55, + "retry_after_ms": 40, "retry_class": "after_capacity_change", "retry_condition_source": "named_condition_from_typed_capacity_response" }, @@ -43,22 +43,7 @@ "query_queue_capacity": 64, "true_idle_observation_grace_ms": 2500 }, - "freeze_record": { - "calibration_bundle_sha256": "1fddcb3e67ae5af4a09022dcbd973cc9e58f2b24ab1485783bbf51d6b8ab222d", - "calibration_freeze_digest": "3e66f41f4dbc7642512c8edfbc5230e9bc7ea2fc2073283f30ce2102832c3d70", - "input_constant_set_sha256": "c67187b461e07bc09352cbb2b3eaaf1760780e32b284e6d127de9d49adb3c51b", - "measurement_protocol_sha256": "ce4bc4911c13d3e52438638e3abb5383e23cd9b400893805f589cdf6350e16e1", - "protocol_sha256": "f4a3fa4afb4d5bcd8e707a5e21b687cdd023dc3398b28ff6891a2318e89c5ec7", - "run_artifact_sha256s": [ - "290295c5d27d513285c7ec22f4f6622b65630975a3b69e5410aef6ace9843c50", - "6123b83da3a72124a88065c9c9a5ac01d9899e746676bd4443e2f6a439837536", - "655967a0dd9ad382b01418410d9b0f9ed6ff9695c2d8105ac3343e1ec66a56e1" - ], - "selected_at": "github-actions-run:30507598793:1", - "selection_rule": "constant_only_three_fresh_generations_one_sample_each+slow_host_floors_v2", - "selection_source_commit": "34154f213a411bb0c89b2fe103ea641f454ffb79", - "selection_source_tree": "5c4fa7bf948410928bd82bf7c020153c2a579abc" - }, + "freeze_record": null, "qualification_thresholds": { "backend_observed_accelerator_residency": 1, "bulk_documents_per_second": 2, @@ -76,5 +61,5 @@ }, "schema_version": 1, "selection_protocol": "codestory-per-user-embedding-server-v1", - "status": "frozen" + "status": "unfrozen" } From 150611891aebca0c9ceafbfb9f591b69b552c148 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Wed, 29 Jul 2026 22:43:56 -0500 Subject: [PATCH 12/28] freeze calibrated embedding constants --- ...er-user-embedding-server-constant-set.json | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json index 70b89b877..db3b9976d 100644 --- a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json +++ b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json @@ -1,7 +1,7 @@ { "calibration_required_values": { "capacity_retry_policy": { - "retry_after_ms": 40, + "retry_after_ms": 45, "retry_class": "after_capacity_change", "retry_condition_source": "named_condition_from_typed_capacity_response" }, @@ -9,7 +9,7 @@ "election_backoff_policy": { "initial_backoff_ms": 7, "jitter": "sha256(process_start_id||attempt) modulo inclusive [initial_backoff_ms,maximum_backoff_ms]", - "maximum_backoff_ms": 102 + "maximum_backoff_ms": 104 }, "hard_native_no_progress_ms": 385431, "request_deadlines_ms": { @@ -43,7 +43,22 @@ "query_queue_capacity": 64, "true_idle_observation_grace_ms": 2500 }, - "freeze_record": null, + "freeze_record": { + "calibration_bundle_sha256": "e0861864455fd60dc6d51f5b627f615e1d8a16825a91d029ea5940029dd0c5ea", + "calibration_freeze_digest": "0ae241b2f151c7c2232677e84097f0b4f79766115204d8410d3d5ec15fd10a15", + "input_constant_set_sha256": "c67187b461e07bc09352cbb2b3eaaf1760780e32b284e6d127de9d49adb3c51b", + "measurement_protocol_sha256": "ce4bc4911c13d3e52438638e3abb5383e23cd9b400893805f589cdf6350e16e1", + "protocol_sha256": "f4a3fa4afb4d5bcd8e707a5e21b687cdd023dc3398b28ff6891a2318e89c5ec7", + "run_artifact_sha256s": [ + "739c711ca14cd9110da279fdbb735ee6adf18629fe3fa33d4f693fb7bd4d5dea", + "c6e468a1570fc5fb4f2620df046914b0b39d54123b3ef18f5254425dc58649e1", + "dc9dc2a857e2400d3662ea9f7a111c31776296439b83da696c9aa1f982a2395f" + ], + "selected_at": "github-actions-run:30511516861:1", + "selection_rule": "constant_only_three_fresh_generations_one_sample_each+slow_host_floors_v2", + "selection_source_commit": "f1fb7af2458ef16129c1bca71b122f0cc9b3ef05", + "selection_source_tree": "b084dcda7507969035353a3fc1a9ee906ce2e73d" + }, "qualification_thresholds": { "backend_observed_accelerator_residency": 1, "bulk_documents_per_second": 2, @@ -61,5 +76,5 @@ }, "schema_version": 1, "selection_protocol": "codestory-per-user-embedding-server-v1", - "status": "unfrozen" + "status": "frozen" } From 74dd8b023481ccf5e7d6036aeb5a29cdc1ee4da6 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Thu, 30 Jul 2026 04:01:06 -0500 Subject: [PATCH 13/28] bind true idle to product completion --- .../measurement_protocol_validation.py | 12 ++- .../self_test_full_stack_calibration.py | 32 ++++++++ .../scenarios/artifact/runner/measurements.rs | 4 +- .../worker/operations/measure.rs | 81 ++++++++++++++----- ...embedding-server-measurement-protocol.json | 4 +- 5 files changed, 109 insertions(+), 24 deletions(-) diff --git a/.github/scripts/packaged_agent_proof/measurement_protocol_validation.py b/.github/scripts/packaged_agent_proof/measurement_protocol_validation.py index 812b2c861..424f1f9c1 100644 --- a/.github/scripts/packaged_agent_proof/measurement_protocol_validation.py +++ b/.github/scripts/packaged_agent_proof/measurement_protocol_validation.py @@ -35,7 +35,7 @@ "calibration_workload_state_overrides", ) EXPECTED_QUALIFICATION_MEASUREMENT_SHAPE_SHA256 = ( - "1c065562adc34d0d9978187857807e491c4e6d4aa233fdd94f5636931a7b730e" + "bc78e8c0277062f1274b0ed97e9bafbef2574b2d1934cb6ab89e7f514900fef8" ) @@ -117,6 +117,11 @@ def _verify_scenario_and_metric_contracts(protocol: dict) -> tuple[set[str], dic and all(isinstance(event, str) and event for event in boundaries), f"measurement metric {metric} must have exact start and end events", ) + require( + phase_boundaries["true_idle_exit"] + == ["final_product_request_completed", "engine_and_server_absent"], + "true-idle qualification must start at final product completion", + ) metric_contracts = protocol.get("metric_contracts") require( isinstance(metric_contracts, dict) @@ -419,6 +424,11 @@ def _verify_measurement_sampling( workload.get("input_generator"), f"measurement workload {metric}.input_generator", ) + require( + workloads["true_idle_exit"].get("workload_id") + == "true_idle_after_product_completion_60000_awake_ms_v2", + "true-idle qualification workload changed its product-completion boundary", + ) sampling = protocol.get("metric_sampling") require( isinstance(sampling, dict) and set(sampling) == required_metrics, diff --git a/.github/scripts/packaged_agent_proof/self_test_full_stack_calibration.py b/.github/scripts/packaged_agent_proof/self_test_full_stack_calibration.py index 433dae637..c881c0a1e 100644 --- a/.github/scripts/packaged_agent_proof/self_test_full_stack_calibration.py +++ b/.github/scripts/packaged_agent_proof/self_test_full_stack_calibration.py @@ -33,6 +33,38 @@ def _qualification_matrix_tests(fixture: FullStackFixture) -> dict: self_measurement_protocol, require_frozen=False, ) + for label, field, value in ( + ( + "server idle epoch", + "phase", + [ + "last_queued_active_or_leased_work_ended", + "engine_and_server_absent", + ], + ), + ( + "pre-completion workload", + "workload", + "true_idle_60000_awake_ms_v1", + ), + ): + regressed_true_idle = json.loads( + json.dumps(measurement_contract["measurement_protocol"]) + ) + if field == "phase": + regressed_true_idle["phase_boundaries"]["true_idle_exit"] = value + else: + regressed_true_idle["workloads"]["true_idle_exit"]["workload_id"] = value + regressed_true_idle_path = ( + fixture.root / f"true-idle-{field}-regression.json" + ) + write_json(regressed_true_idle_path, regressed_true_idle) + try: + load_measurement_protocol(regressed_true_idle_path) + except ProofFailure: + pass + else: + raise ProofFailure(f"true-idle qualification accepted {label}") for quality_metric in ( "answer_quality", "packet_quality", diff --git a/crates/codestory-bench/src/bin/codestory_embedding_qualification/scenarios/artifact/runner/measurements.rs b/crates/codestory-bench/src/bin/codestory_embedding_qualification/scenarios/artifact/runner/measurements.rs index 1dff013ad..551b09332 100644 --- a/crates/codestory-bench/src/bin/codestory_embedding_qualification/scenarios/artifact/runner/measurements.rs +++ b/crates/codestory-bench/src/bin/codestory_embedding_qualification/scenarios/artifact/runner/measurements.rs @@ -71,7 +71,7 @@ pub(super) fn declared_phase_boundaries(metric: &str) -> Result<[&'static str; 2 ], "busy_retry_usefulness" => ["typed_retry_emitted", "named_retry_condition_became_true"], "true_idle_exit" => [ - "last_queued_active_or_leased_work_ended", + "final_product_request_completed", "engine_and_server_absent", ], "backend_observed_accelerator_residency" => [ @@ -94,7 +94,7 @@ pub(super) fn declared_workload_id(metric: &str) -> Result<&'static str> { "warm_bulk_ipc" => "warm_bulk_64x256b_v1", "bulk_documents_per_second" | "bulk_tokens_per_second" => "bulk_throughput_256x256b_v1", "busy_retry_usefulness" => "saturated_query_65th_retry_v1", - "true_idle_exit" => "true_idle_60000_awake_ms_v1", + "true_idle_exit" => "true_idle_after_product_completion_60000_awake_ms_v2", "backend_observed_accelerator_residency" => "resident_policy_identity_v1", _ => bail!("embedding_qualification_metric_workload_unknown:{metric}"), }) diff --git a/crates/codestory-cli/src/embedding_qualification/worker/operations/measure.rs b/crates/codestory-cli/src/embedding_qualification/worker/operations/measure.rs index 55b6f0c98..2f7b6927b 100644 --- a/crates/codestory-cli/src/embedding_qualification/worker/operations/measure.rs +++ b/crates/codestory-cli/src/embedding_qualification/worker/operations/measure.rs @@ -68,7 +68,7 @@ fn workload_documents(workload_id: &str, repeat: u32, count: usize, bytes: usize .collect() } -struct MeasurementSpanStart { +pub(in crate::embedding_qualification::worker) struct MeasurementSpanStart { awake_started_ns: u64, inclusive_started_ns: u64, boot_id_started: String, @@ -88,6 +88,26 @@ fn begin_span(clock: &dyn AwakeMonotonicClock) -> Result { }) } +/// Stamp product completion for `true_idle_exit`. +/// +/// The awake reading is deliberately first: once the product operation has +/// stamped completion, a delay while returning to the measurement coordinator +/// must remain inside the measured idle interval, never move its start +/// forward. The suspend-inclusive and boot witnesses follow immediately and +/// the downstream tolerance fails closed if sampling them is delayed. +fn begin_true_idle_span_at_product_completion( + clock: &dyn AwakeMonotonicClock, +) -> Result { + let awake_started_ns = clock.now_ns(); + let inclusive_started_ns = crate::embedding_server_transport::inclusive_now_ns()?; + let boot_id_started = crate::embedding_server_transport::boot_id()?; + Ok(MeasurementSpanStart { + awake_started_ns, + inclusive_started_ns, + boot_id_started, + }) +} + /// Stamp the declared end instant: awake reading first, then the /// suspend-inclusive reading and boot id. fn finish_span( @@ -467,7 +487,11 @@ fn validate_true_idle_boundary( /// bypass. pub(in crate::embedding_qualification::worker) trait TrueIdleMeasurementClient { fn observe_snapshot(&self) -> Result>; - fn complete_product_query(&self, input: &str) -> Result<()>; + fn complete_product_query_and_stamp( + &self, + input: &str, + clock: &dyn AwakeMonotonicClock, + ) -> Result; fn observe_owner_exit(&self) -> Result; } @@ -476,9 +500,13 @@ impl TrueIdleMeasurementClient for PerUserEmbeddingClient { self.observe() } - fn complete_product_query(&self, input: &str) -> Result<()> { + fn complete_product_query_and_stamp( + &self, + input: &str, + clock: &dyn AwakeMonotonicClock, + ) -> Result { let _ = self.embed_query(input)?; - Ok(()) + begin_true_idle_span_at_product_completion(clock) } fn observe_owner_exit(&self) -> Result { @@ -487,9 +515,9 @@ impl TrueIdleMeasurementClient for PerUserEmbeddingClient { } /// `true_idle_exit`: this worker completes the last product request itself, -/// stamps its return as `last_queued_active_or_leased_work_ended`, proves the -/// resident scheduler is drained, then ends at the observation that returned -/// no owner (`engine_and_server_absent`). +/// stamps `final_product_request_completed` before returning to its caller, +/// proves the resident scheduler is drained, then ends at the observation that +/// returned no owner (`engine_and_server_absent`). pub(in crate::embedding_qualification::worker) fn run_measure_true_idle( client: &dyn TrueIdleMeasurementClient, clock: &dyn AwakeMonotonicClock, @@ -500,8 +528,7 @@ pub(in crate::embedding_qualification::worker) fn run_measure_true_idle( .filter(resident_engine_generation) .ok_or_else(|| anyhow::anyhow!("embedding_qualification_true_idle_owner_missing"))?; let input = "q".repeat(input_bytes.max(1) as usize); - client.complete_product_query(&input)?; - let start = begin_span(clock)?; + let start = client.complete_product_query_and_stamp(&input, clock)?; let idle_owner = client .observe_snapshot()? .ok_or_else(|| anyhow::anyhow!("embedding_qualification_true_idle_owner_missing"))?; @@ -786,8 +813,9 @@ fn validate_vector_response( mod tests { use super::super::owner_exit::OwnerExitObservation; use super::{ - TrueIdleMeasurementClient, run_measure_true_idle, true_idle_scheduler_is_drained, - validate_true_idle_boundary, workload_input, + MeasurementSpanStart, TrueIdleMeasurementClient, + begin_true_idle_span_at_product_completion, run_measure_true_idle, + true_idle_scheduler_is_drained, validate_true_idle_boundary, workload_input, }; use anyhow::Result; use codestory_retrieval::{ @@ -801,6 +829,10 @@ mod tests { use std::sync::{Arc, Mutex}; use std::time::Duration; + const PRODUCT_COMPLETED_NS: u64 = 100; + const PRODUCT_RETURNED_NS: u64 = 10_000_000_100; + const DRAINED_OWNER_OBSERVED_NS: u64 = 10_000_000_145; + struct ScriptClock { now_ns: AtomicU64, events: Arc>>, @@ -822,7 +854,7 @@ mod tests { impl AwakeMonotonicClock for ScriptClock { fn now_ns(&self) -> u64 { let now_ns = self.now_ns.load(Ordering::Acquire); - if now_ns == 100 { + if now_ns == PRODUCT_COMPLETED_NS { self.events .lock() .expect("lock events") @@ -868,22 +900,32 @@ mod tests { let event = if remaining == 1 { "expected_owner_observed" } else { - self.clock.set(145); + self.clock.set(DRAINED_OWNER_OBSERVED_NS); "same_owner_drained_observed" }; self.events.lock().expect("lock events").push(event); Ok(Some(snapshot)) } - fn complete_product_query(&self, input: &str) -> Result<()> { + fn complete_product_query_and_stamp( + &self, + input: &str, + clock: &dyn AwakeMonotonicClock, + ) -> Result { assert_eq!(input, "q".repeat(256)); self.product_calls.fetch_add(1, Ordering::AcqRel); - self.clock.set(100); + self.clock.set(PRODUCT_COMPLETED_NS); self.events .lock() .expect("lock events") .push("product_completed"); - Ok(()) + let start = begin_true_idle_span_at_product_completion(clock)?; + self.clock.set(PRODUCT_RETURNED_NS); + self.events + .lock() + .expect("lock events") + .push("product_returned_after_delay"); + Ok(start) } fn observe_owner_exit(&self) -> Result { @@ -969,7 +1011,7 @@ mod tests { } #[test] - fn true_idle_start_is_stamped_at_product_completion_before_observation_lag() { + fn true_idle_start_is_stamped_at_product_completion_before_return_and_observation_lag() { let events = Arc::new(Mutex::new(Vec::new())); let clock = Arc::new(ScriptClock::new(Arc::clone(&events))); let client = ScriptedTrueIdleClient { @@ -990,8 +1032,8 @@ mod tests { run_measure_true_idle(&client, clock.as_ref(), 256).expect("measure true idle"); assert_eq!(client.product_calls.load(Ordering::Acquire), 1); - assert_eq!(measurement.span.awake_started_ns, 100); - assert!(measurement.span.awake_finished_ns >= 145); + assert_eq!(measurement.span.awake_started_ns, PRODUCT_COMPLETED_NS); + assert!(measurement.span.awake_finished_ns >= DRAINED_OWNER_OBSERVED_NS); assert_eq!( measurement.snapshot.process.server_instance_id, "measured-owner" @@ -1002,6 +1044,7 @@ mod tests { "expected_owner_observed", "product_completed", "span_started", + "product_returned_after_delay", "same_owner_drained_observed", "same_owner_still_present", "owner_absence_observed", diff --git a/crates/codestory-llama-sys/per-user-embedding-server-measurement-protocol.json b/crates/codestory-llama-sys/per-user-embedding-server-measurement-protocol.json index deb9c385b..d8ddd7f61 100644 --- a/crates/codestory-llama-sys/per-user-embedding-server-measurement-protocol.json +++ b/crates/codestory-llama-sys/per-user-embedding-server-measurement-protocol.json @@ -157,7 +157,7 @@ "named_retry_condition_became_true" ], "true_idle_exit": [ - "last_queued_active_or_leased_work_ended", + "final_product_request_completed", "engine_and_server_absent" ], "total_codestory_process_memory": [ @@ -279,7 +279,7 @@ "measured_request_ordinal": 65 }, "true_idle_exit": { - "workload_id": "true_idle_60000_awake_ms_v1", + "workload_id": "true_idle_after_product_completion_60000_awake_ms_v2", "owner_state": "resident_then_absent", "operation": "observe", "input_generator": "none", From 76dfa93e6780030595789b877d292d12fc069583 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Thu, 30 Jul 2026 04:48:11 -0500 Subject: [PATCH 14/28] freeze calibrated embedding constants --- ...er-user-embedding-server-constant-set.json | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json index 36a5e49fc..f3a1c167e 100644 --- a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json +++ b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json @@ -1,21 +1,21 @@ { "calibration_required_values": { "capacity_retry_policy": { - "retry_after_ms": 40, + "retry_after_ms": 50, "retry_class": "after_capacity_change", "retry_condition_source": "named_condition_from_typed_capacity_response" }, "connect_timeout_ms": 2000, "election_backoff_policy": { - "initial_backoff_ms": 7, + "initial_backoff_ms": 9, "jitter": "sha256(process_start_id||attempt) modulo inclusive [initial_backoff_ms,maximum_backoff_ms]", - "maximum_backoff_ms": 102 + "maximum_backoff_ms": 108 }, "hard_native_no_progress_ms": 385431, "request_deadlines_ms": { "bulk_replay_success_budget_ms": 144537, "bulk_request_deadline_ms": 564239, - "query_request_deadline_ms": 10000 + "query_request_deadline_ms": 10160 }, "spawn_convergence_timeout_ms": 15000, "watchdog_cadence_ms": 19271 @@ -43,7 +43,22 @@ "query_queue_capacity": 64, "true_idle_observation_grace_ms": 2500 }, - "freeze_record": null, + "freeze_record": { + "calibration_bundle_sha256": "2d02991184e58a28440ffee38f0df50840b57b6b13e57736ec1a273e614b3f97", + "calibration_freeze_digest": "9002c6b680c8b45e4ff8b076ec73cf0d314703a359ea27d60ed3392d9c1ddb01", + "input_constant_set_sha256": "ea58d298473ddf320469d15dc7c32176a1109d615a689c15ff76b67fb337e109", + "measurement_protocol_sha256": "d1bb9b2c7eb354fe98990aa32eedc0e165b0cf804212966e0a2ee362a2f5bf8e", + "protocol_sha256": "f4a3fa4afb4d5bcd8e707a5e21b687cdd023dc3398b28ff6891a2318e89c5ec7", + "run_artifact_sha256s": [ + "65a4c4d5429ce9b83905505d6778c917c6a86d61fbd904c8950db1e0eac83ffc", + "cc5fe78225514333c8741564425c2c6f92db95e88d58df4e78ec03ac6985d6ca", + "e78409ef1925c467c178ed0b90b55f8d88269584a938e56393ead122ea835bb9" + ], + "selected_at": "github-actions-run:30531101324:1", + "selection_rule": "constant_only_three_fresh_generations_one_sample_each+slow_host_floors_v2", + "selection_source_commit": "74dd8b023481ccf5e7d6036aeb5a29cdc1ee4da6", + "selection_source_tree": "1eddbce0062d298868049a0c6f4603c419195100" + }, "qualification_thresholds": { "backend_observed_accelerator_residency": 1, "bulk_documents_per_second": 2, @@ -60,5 +75,5 @@ }, "schema_version": 1, "selection_protocol": "codestory-per-user-embedding-server-v1", - "status": "unfrozen" + "status": "frozen" } From 04605eb8321dc46c7950c998b6a8eabe8c7ccb59 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Thu, 30 Jul 2026 06:06:27 -0500 Subject: [PATCH 15/28] freeze calibrated embedding constants --- ...er-user-embedding-server-constant-set.json | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json index 36a5e49fc..05a9f8a13 100644 --- a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json +++ b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json @@ -1,7 +1,7 @@ { "calibration_required_values": { "capacity_retry_policy": { - "retry_after_ms": 40, + "retry_after_ms": 45, "retry_class": "after_capacity_change", "retry_condition_source": "named_condition_from_typed_capacity_response" }, @@ -9,7 +9,7 @@ "election_backoff_policy": { "initial_backoff_ms": 7, "jitter": "sha256(process_start_id||attempt) modulo inclusive [initial_backoff_ms,maximum_backoff_ms]", - "maximum_backoff_ms": 102 + "maximum_backoff_ms": 107 }, "hard_native_no_progress_ms": 385431, "request_deadlines_ms": { @@ -43,7 +43,22 @@ "query_queue_capacity": 64, "true_idle_observation_grace_ms": 2500 }, - "freeze_record": null, + "freeze_record": { + "calibration_bundle_sha256": "f27a0dfdc256c332daa0c405b388fca2cf88cdaee0ef7fd9f56b8b08a62135e6", + "calibration_freeze_digest": "afc407e83e3192b6095f29e7083b92e2cd8f1d9590bec0345ec5a164b66cccc6", + "input_constant_set_sha256": "ea58d298473ddf320469d15dc7c32176a1109d615a689c15ff76b67fb337e109", + "measurement_protocol_sha256": "d1bb9b2c7eb354fe98990aa32eedc0e165b0cf804212966e0a2ee362a2f5bf8e", + "protocol_sha256": "f4a3fa4afb4d5bcd8e707a5e21b687cdd023dc3398b28ff6891a2318e89c5ec7", + "run_artifact_sha256s": [ + "1ffcf00054d38dadf22e4554edeb61bf9af38822513729685fb7da0e9aa46a7d", + "35dc5bd2f926ef0c791514962fc21d0a91186862ca7c6019b452bc9b81c6297b", + "ef5ec53eaee5cee7fadb657afd379820f4c1f722c72001bf5401f4284b852317" + ], + "selected_at": "github-actions-run:30536808468:1", + "selection_rule": "constant_only_three_fresh_generations_one_sample_each+slow_host_floors_v2", + "selection_source_commit": "d318b485e1d1dfd3753711afa5d29f441500ce5a", + "selection_source_tree": "a9c6d4490589f1f8afcc721b476f937395fd483c" + }, "qualification_thresholds": { "backend_observed_accelerator_residency": 1, "bulk_documents_per_second": 2, @@ -60,5 +75,5 @@ }, "schema_version": 1, "selection_protocol": "codestory-per-user-embedding-server-v1", - "status": "unfrozen" + "status": "frozen" } From f265f76bf731f03ef716f0c09b0852dbd467ab45 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Thu, 30 Jul 2026 06:45:31 -0500 Subject: [PATCH 16/28] enforce atomic candidate archive admission --- .github/scripts/candidate-archive-store.mjs | 10 +++++ .../scripts/candidate-archive-store.test.mjs | 41 ++++++++++++++++++- ...er-user-embedding-server-constant-set.json | 23 ++--------- 3 files changed, 54 insertions(+), 20 deletions(-) diff --git a/.github/scripts/candidate-archive-store.mjs b/.github/scripts/candidate-archive-store.mjs index 0febeed00..f76284c18 100644 --- a/.github/scripts/candidate-archive-store.mjs +++ b/.github/scripts/candidate-archive-store.mjs @@ -874,6 +874,7 @@ function publishStoreEntry(storeRoot, inputRoot, record) { removeOwnedTemporary(temporary, paths.parent, path.basename(paths.entry)); return { admitted: false, ...concurrent }; } + const prepared = lstatSync(temporary, { bigint: true }); try { renameSync(temporary, paths.entry); } catch (error) { @@ -884,6 +885,15 @@ function publishStoreEntry(storeRoot, inputRoot, record) { removeOwnedTemporary(temporary, paths.parent, path.basename(paths.entry)); return { admitted: false, ...concurrent }; } + const published = lstatSync(paths.entry, { bigint: true }); + if ( + !published.isDirectory() + || published.isSymbolicLink() + || published.dev !== prepared.dev + || published.ino !== prepared.ino + ) { + fail("candidate archive store entry was not published by atomic directory rename"); + } return { admitted: true, ...verifyStoreEntry(storeRoot, expected) }; } catch (error) { if (existsSync(temporary)) { diff --git a/.github/scripts/candidate-archive-store.test.mjs b/.github/scripts/candidate-archive-store.test.mjs index 41548c3ee..277c853cc 100644 --- a/.github/scripts/candidate-archive-store.test.mjs +++ b/.github/scripts/candidate-archive-store.test.mjs @@ -17,7 +17,7 @@ import os from "node:os"; import path from "node:path"; import { spawnSync } from "node:child_process"; import test from "node:test"; -import { fileURLToPath } from "node:url"; +import { fileURLToPath, pathToFileURL } from "node:url"; import { admitCandidateArchive, @@ -291,6 +291,45 @@ test("admission and a later hit materialize the complete exact payload as fresh } }); +test("admission rejects sequential publication beneath the final store key", async () => { + const fixture = createFixture(); + try { + const source = readFileSync(SCRIPT, "utf8"); + const atomicPublication = " renameSync(temporary, paths.entry);"; + assert.equal( + source.split(atomicPublication).length - 1, + 1, + "atomic store publication must have one mutation target", + ); + const sequentialPublication = [ + " mkdirSync(paths.entry, { mode: 0o700 });", + " renameSync(temporaryPayload, paths.payload);", + " renameSync(path.join(temporary, RECORD_FILE), paths.recordFile);", + " rmSync(temporary, { recursive: true });", + ].join("\n"); + const mutantFile = path.join(fixture.root, "candidate-archive-store-mutant.mjs"); + writeFileSync( + mutantFile, + source.replace(atomicPublication, sequentialPublication), + { flag: "wx" }, + ); + const mutant = await import(pathToFileURL(mutantFile).href); + assert.throws( + () => mutant.admitCandidateArchive({ + inputRoot: fixture.inputRoot, + outputDir: outputDir(fixture), + outputRoot: fixture.outputRoot, + record: fixture.record, + storeRoot: fixture.storeRoot, + }), + /not published by atomic directory rename/u, + ); + assert.equal(statExists(outputDir(fixture)), false); + } finally { + cleanup(fixture); + } +}); + test("the public checksum companion pair is mandatory", () => { const fixture = createFixture(); try { diff --git a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json index 05a9f8a13..36a5e49fc 100644 --- a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json +++ b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json @@ -1,7 +1,7 @@ { "calibration_required_values": { "capacity_retry_policy": { - "retry_after_ms": 45, + "retry_after_ms": 40, "retry_class": "after_capacity_change", "retry_condition_source": "named_condition_from_typed_capacity_response" }, @@ -9,7 +9,7 @@ "election_backoff_policy": { "initial_backoff_ms": 7, "jitter": "sha256(process_start_id||attempt) modulo inclusive [initial_backoff_ms,maximum_backoff_ms]", - "maximum_backoff_ms": 107 + "maximum_backoff_ms": 102 }, "hard_native_no_progress_ms": 385431, "request_deadlines_ms": { @@ -43,22 +43,7 @@ "query_queue_capacity": 64, "true_idle_observation_grace_ms": 2500 }, - "freeze_record": { - "calibration_bundle_sha256": "f27a0dfdc256c332daa0c405b388fca2cf88cdaee0ef7fd9f56b8b08a62135e6", - "calibration_freeze_digest": "afc407e83e3192b6095f29e7083b92e2cd8f1d9590bec0345ec5a164b66cccc6", - "input_constant_set_sha256": "ea58d298473ddf320469d15dc7c32176a1109d615a689c15ff76b67fb337e109", - "measurement_protocol_sha256": "d1bb9b2c7eb354fe98990aa32eedc0e165b0cf804212966e0a2ee362a2f5bf8e", - "protocol_sha256": "f4a3fa4afb4d5bcd8e707a5e21b687cdd023dc3398b28ff6891a2318e89c5ec7", - "run_artifact_sha256s": [ - "1ffcf00054d38dadf22e4554edeb61bf9af38822513729685fb7da0e9aa46a7d", - "35dc5bd2f926ef0c791514962fc21d0a91186862ca7c6019b452bc9b81c6297b", - "ef5ec53eaee5cee7fadb657afd379820f4c1f722c72001bf5401f4284b852317" - ], - "selected_at": "github-actions-run:30536808468:1", - "selection_rule": "constant_only_three_fresh_generations_one_sample_each+slow_host_floors_v2", - "selection_source_commit": "d318b485e1d1dfd3753711afa5d29f441500ce5a", - "selection_source_tree": "a9c6d4490589f1f8afcc721b476f937395fd483c" - }, + "freeze_record": null, "qualification_thresholds": { "backend_observed_accelerator_residency": 1, "bulk_documents_per_second": 2, @@ -75,5 +60,5 @@ }, "schema_version": 1, "selection_protocol": "codestory-per-user-embedding-server-v1", - "status": "frozen" + "status": "unfrozen" } From c6c1379eb734eed38654a92058e8707d42d0f538 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Thu, 30 Jul 2026 08:06:54 -0500 Subject: [PATCH 17/28] enforce release freeze barrier --- .../check-calibration-release-lineage.py | 9 + .github/scripts/check-workflow-policy.mjs | 376 ++++++++++--- .../scripts/check-workflow-policy.test.mjs | 187 +++++-- .../fixtures/workflow-policy-invalid.json | 14 +- .../calibration_lineage.py | 44 ++ .../self_test_calibration_lineage.py | 30 ++ .github/scripts/release-freeze-barrier.mjs | 501 ++++++++++++++++++ .../scripts/release-freeze-barrier.test.mjs | 237 +++++++++ .github/workflows/auto-release.yml | 4 +- .github/workflows/linux-vulkan-proof.yml | 46 +- .github/workflows/macos-metal-proof.yml | 45 -- .github/workflows/packaged-platform-pr.yml | 123 +++-- .github/workflows/release.yml | 121 ++++- .github/workflows/source-proof.yml | 71 ++- .github/workflows/windows-vulkan-proof.yml | 35 -- AGENTS.md | 46 +- .../release-evidence/fixtures/candidate.json | 6 +- .../release-evidence/fixtures/report.json | 6 +- release-claims.json | 56 +- scripts/codestory-release-claims.mjs | 136 ++++- .../tests/codestory-release-claims.test.mjs | 106 +++- .../tests/codestory-release-closeout.test.mjs | 63 ++- .../fixtures/release-claims/positive.json | 2 +- 23 files changed, 1892 insertions(+), 372 deletions(-) create mode 100644 .github/scripts/release-freeze-barrier.mjs create mode 100644 .github/scripts/release-freeze-barrier.test.mjs diff --git a/.github/scripts/check-calibration-release-lineage.py b/.github/scripts/check-calibration-release-lineage.py index 8339f2867..149676663 100644 --- a/.github/scripts/check-calibration-release-lineage.py +++ b/.github/scripts/check-calibration-release-lineage.py @@ -24,12 +24,21 @@ def main() -> int: ) parser.add_argument("--repo", required=True, type=Path) parser.add_argument("--expected-sha", required=True) + parser.add_argument( + "--allow-promotion-commit", + action="store_true", + help=( + "Permit one tree-preserving main promotion commit whose release " + "parent is the direct constant-freeze child." + ), + ) arguments = parser.parse_args() repository_root = arguments.repo.resolve(strict=True) result = verify_release_head_calibration_lineage( repository_root, arguments.expected_sha, + allow_promotion_commit=arguments.allow_promotion_commit, ) print(json.dumps({"status": "passed", **result}, sort_keys=True)) return 0 diff --git a/.github/scripts/check-workflow-policy.mjs b/.github/scripts/check-workflow-policy.mjs index beb943c0f..e18cbc4b2 100644 --- a/.github/scripts/check-workflow-policy.mjs +++ b/.github/scripts/check-workflow-policy.mjs @@ -913,15 +913,15 @@ const packagedPlatformWorkflowDigest = // made advisory, parked in dead code, or followed by a payload substitution // while leaving the expected tokens in place. const packagedPlatformCoordinatorWorkflowDigest = - "464906e3cd7ec0e2f7e9195d60de035fdba76172c25d8b0861d0982f9d7dcc3e"; + "83ff8876fbf87a2e35eebe0e16d8f025a1b2377ceb0a31d8c9bcacea856f9bde"; const frozenCandidateQualityWorkflowDigest = "92d0a7ab0e0df63dacd5cc3ef0b58500a6578036494c329aa35279048734f173"; const macosMetalWorkflowDigest = - "e1c4a59b412ba3f2041e89177e2af5a6533cf5ef0371dc2fa18c0309fba5b1ca"; + "05b69d48238284b47b40c13bf15eb1f31370dea55bb77169553d41b46fda1a7f"; const windowsVulkanWorkflowDigest = - "f1123f11d430591380ed433d751c6fc110adfb13225106f50deecf232ee89f9b"; + "c2272dbf4c550ba4a21372e772a87f6df3307f5f4f709b216473f85958157ffe"; const linuxVulkanWorkflowDigest = - "935c5df20a2d57ed18824cca11dc9b8590d72a207e3d1677bc2ceea3048b3dff"; + "b2efe3dec20a466cb798752c714f50e64e265856e80ffafc15b28ea2390367d3"; // Linux owns its compiler server inside Docker, while macOS and Windows own one // in the host shell. Pin both executable programs so a swallowed stop or a // dead-code copy cannot satisfy the ownership fragments below. @@ -2095,13 +2095,12 @@ function validatePluginAndDraftWorkflows(workflows, violations, graph) { const sourceConcurrency = [ "source-proof-", promotion.proof_run_sha_expression, - "-${{ inputs.proof_key || inputs.pr_number || github.event.pull_request.number || github.ref }}-", - "${{ github.event.action == 'labeled' && github.event.label.name || 'dispatch' }}", + "-${{ inputs.proof_key || inputs.pr_number || github.ref }}", ].join(""); add( violations, - sameMembers(at(source, "on", "pull_request", "types"), promotion.required_events), - `${sourceFile} pull request trigger must be label-only`, + trigger(source, "pull_request") === undefined, + `${sourceFile} support PR labels must not trigger broad source proof`, ); add( violations, @@ -2118,8 +2117,8 @@ function validatePluginAndDraftWorkflows(workflows, violations, graph) { const resolve = requireJob(violations, sourceFile, source, "resolve"); add( violations, - resolve.if === "github.event_name != 'pull_request' || (github.event.action == 'labeled' && github.event.label.name == 'review-accepted')", - `${sourceFile} resolve job must execute dispatch/call runs and only review-accepted labeled PR runs`, + resolve.if === undefined, + `${sourceFile} resolve job must execute only explicit dispatch and reusable calls`, ); requireStepRun(violations, sourceFile, resolve, "Resolve trusted exact head", [ 'test "$EVENT_HEAD_REPO" = "$GITHUB_REPOSITORY"', @@ -2133,9 +2132,15 @@ function validatePluginAndDraftWorkflows(workflows, violations, graph) { requireExactResolverContract(violations, sourceFile, resolve, sourceResolverContractDigest); requireStepRun(violations, sourceFile, resolve, "Reuse a completed gate for this exact head", [ '.path == ".github/workflows/source-proof.yml"', - '(.event == "pull_request" or .event == "workflow_dispatch") and .conclusion == "success"', + '.event == "workflow_dispatch" and .conclusion == "success"', '.name == "full-source-gate" and .conclusion == "success"', ]); + requireStepRun(violations, sourceFile, resolve, "Require executable release freeze", [ + "codestory/release-freeze/$FREEZE_RECEIPT_DIGEST", + "repos/$GITHUB_REPOSITORY/git/commits/$HEAD_SHA", + ".state == \"success\"", + ".description == $description", + ]); const full = requireJob(violations, sourceFile, source, "full-source-gate"); add(violations, sameMembers(needs(full), ["resolve"]), `${sourceFile} full source gate must need resolve`); add( @@ -2510,7 +2515,11 @@ function validateReleaseCoordinator(workflows, violations, graph) { JSON.stringify(releaseCallers) === JSON.stringify(["auto-release.yml"]), `${releaseFile} publication authority must have only the trusted auto-release.yml caller`, ); - add(violations, object(release.permissions).actions === "read", `${releaseFile} must read prior-run evidence`); + add( + violations, + object(release.permissions).actions === "write", + `${releaseFile} must cancel superseded proof runs before starting release work`, + ); add( violations, object(release.permissions)["pull-requests"] === "read", @@ -2528,6 +2537,14 @@ function validateReleaseCoordinator(workflows, violations, graph) { callPublish.required === false && callPublish.type === "boolean" && callPublish.default === false, `${releaseFile} workflow_call publish_release must be a fail-closed boolean`, ); + for (const input of ["calibration_bundle_artifact", "calibration_bundle_run_id"]) { + add( + violations, + at(release, "on", "workflow_call", "inputs", input) === undefined + && at(release, "on", "workflow_dispatch", "inputs", input) === undefined, + `${releaseFile} must not accept calibration bundle inputs; lineage comes from the frozen constant set`, + ); + } const dispatchExpectedHead = object(at(release, "on", "workflow_dispatch", "inputs", "expected_head_sha")); add( violations, @@ -2545,12 +2562,6 @@ function validateReleaseCoordinator(workflows, violations, graph) { release.env === undefined && release.defaults === undefined, `${releaseFile} release workflow must not override the release-head calibration execution environment`, ); - requireNoCalibrationReferences( - violations, - releaseFile, - release, - [["preflight", releaseLineageStepName]], - ); const policy = requireJob(violations, releaseFile, release, "workflow-policy"); // The reuse-binding contracts resolve real release commits, which a depth-1 // clone does not carry: it answered only while the referenced commit happened @@ -2616,26 +2627,25 @@ function validateReleaseCoordinator(workflows, violations, graph) { && preflight["continue-on-error"] === undefined && hasExactKeys( releaseLineage, - ["name", "env", "shell", "working-directory", "run"], + ["name", "id", "env", "shell", "working-directory", "run"], ) - && hasExactKeys(object(releaseLineage?.env), ["BASH_ENV"]) + && releaseLineage?.id === "lineage" + && hasExactKeys(object(releaseLineage?.env), ["BASH_ENV", "PUBLISH_RELEASE"]) && object(releaseLineage?.env).BASH_ENV === "/dev/null" + && object(releaseLineage?.env).PUBLISH_RELEASE === "${{ inputs.publish_release }}" && releaseLineage?.shell === "/bin/bash --noprofile --norc -e -o pipefail {0}" && releaseLineage?.["working-directory"] === "${{ github.workspace }}", `${releaseFile} release-head calibration lineage must be unconditional and fail closed`, ); - add( - violations, - sameStrings( - nonCommentLines(releaseLineage?.run), - [ - "/usr/bin/python3 -E -s " - + '"$GITHUB_WORKSPACE/.github/scripts/check-calibration-release-lineage.py" ' - + '--repo "$GITHUB_WORKSPACE" --expected-sha "$GITHUB_SHA"', - ], - ), - `${releaseFile} release-head calibration lineage must use the pinned interpreter on the exact release checkout`, - ); + requireStepRun(violations, releaseFile, preflight, releaseLineageStepName, [ + "/usr/bin/python3 -E -s", + '"$GITHUB_WORKSPACE/.github/scripts/check-calibration-release-lineage.py"', + '--repo "$GITHUB_WORKSPACE"', + '--expected-sha "$GITHUB_SHA"', + "--allow-promotion-commit", + "selection_commit", + "selection_tree", + ]); const preflightCheckout = namedStep(preflight, "Checkout"); add( violations, @@ -2648,7 +2658,8 @@ function validateReleaseCoordinator(workflows, violations, graph) { add( violations, stepIndex(preflight, "Checkout") === 0 - && stepIndex(preflight, releaseLineageStepName) === 1 + && stepIndex(preflight, "Cancel superseded proof runs") === 1 + && stepIndex(preflight, releaseLineageStepName) === 2 && stepIndex(preflight, releaseLineageStepName) < stepIndex(preflight, "Validate release authority") && stepIndex(preflight, releaseLineageStepName) @@ -2716,11 +2727,12 @@ function validateReleaseCoordinator(workflows, violations, graph) { `${releaseFile} source proof may be skipped only when preflight resolved reusable evidence`, ); requireStepRun(violations, releaseFile, requireJob(violations, releaseFile, release, "preflight"), "Resolve reusable prior evidence", [ - 'git rev-parse "$GITHUB_SHA^{tree}"', - "merge-base --is-ancestor", + "actions/runs?head_sha=$SOURCE_SHA", "full-source-gate", '.path == ".github/workflows/source-proof.yml"', - '.head_repository.full_name == $repo and .conclusion == "success"', + '.event == "workflow_dispatch" and .conclusion == "success"', + "The release workflow will not start a second proof after calibration", + "codestory/release-freeze/$freeze_digest", ]); const closeout = requireJob(violations, releaseFile, release, "pre-publish-closeout"); requireStepRun(violations, releaseFile, closeout, "Authenticate pre-publish Actions provenance", [ @@ -2732,7 +2744,14 @@ function validateReleaseCoordinator(workflows, violations, graph) { && String(closeout.if ?? "").includes("needs.preflight.result == 'success'"), `${releaseFile} closeout must accept a skipped source gate only alongside a successful preflight`, ); - add(violations, object(source.with).version === "${{ needs.preflight.outputs.version }}" && object(source.with).emit_release_cells === true, `${releaseFile} source proof must emit its authenticated release cell`); + add( + violations, + object(source.with).version === "${{ needs.preflight.outputs.version }}" + && object(source.with).emit_release_cells === true + && object(source.with).freeze_receipt_digest + === "${{ needs.preflight.outputs.freeze_receipt_digest }}", + `${releaseFile} unreachable source fallback must retain the accepted freeze identity`, + ); const packaged = requireJob(violations, releaseFile, release, "packaged-proof"); add(violations, packaged.uses === "./.github/workflows/packaged-platform-proof.yml", `${releaseFile} packaged-proof must call the package workflow`); @@ -4912,13 +4931,12 @@ function validatePackagedCoordinator(workflows, violations, graph) { const expectedConcurrency = [ "proof-", promotion.proof_run_sha_expression, - "-${{ inputs.mode || 'platform' }}-${{ inputs.pr_number || github.event.pull_request.number || 'dev' }}-", - "${{ github.event.action == 'labeled' && github.event.label.name || 'dispatch' }}", + "-${{ inputs.mode || 'platform' }}-${{ inputs.pr_number || 'dev' }}", ].join(""); add( violations, - sameMembers(at(workflow, "on", "pull_request", "types"), promotion.required_events), - `${file} pull request trigger must be label-only`, + trigger(workflow, "pull_request") === undefined, + `${file} support PR labels must not trigger package or hardware proof`, ); add( violations, @@ -4948,13 +4966,17 @@ function validatePackagedCoordinator(workflows, violations, graph) { `${file} dispatch scopes changed`, ); add(violations, trigger(workflow, "pull_request_target") === undefined, `${file} must not use pull_request_target`); - add(violations, object(workflow.permissions).actions === "read", `${file} must read source-proof runs`); + add( + violations, + object(workflow.permissions).actions === "write", + `${file} must cancel superseded proof runs before package or hardware work`, + ); add(violations, object(workflow.permissions).contents === "read", `${file} must use read-only contents permission`); const route = requireJob(violations, file, workflow, "route"); add( violations, - route.if === "github.event_name != 'pull_request' || (github.event.action == 'labeled' && github.event.label.name == 'platform-proof')", - `${file} route job must execute dispatch runs and only platform-proof labeled PR runs`, + route.if === undefined, + `${file} route job must execute only explicit dispatches`, ); requireStepRun(violations, file, route, "Resolve trusted exact head", [ 'test "$head_repo" = "$GITHUB_REPOSITORY"', @@ -4978,10 +5000,16 @@ function validatePackagedCoordinator(workflows, violations, graph) { INPUT_CALIBRATION_RUN_ID: "${{ inputs.calibration_bundle_run_id }}", }); requireExactResolverContract(violations, file, route, platformResolverContractDigest); - requireStepRun(violations, file, route, "Require successful exact-head source proof", [ - "actions/runs?head_sha=$HEAD_SHA", + requireStepRun(violations, file, route, "Require executable release freeze", [ + "codestory/release-freeze/$FREEZE_RECEIPT_DIGEST", + "repos/$GITHUB_REPOSITORY/git/commits/$SOURCE_SHA", + ".state == \"success\"", + ".description == $description", + ]); + requireStepRun(violations, file, route, "Require successful accepted-head source proof", [ + "actions/runs?head_sha=$SOURCE_SHA", '.path == ".github/workflows/source-proof.yml"', - '(.event == "pull_request" or .event == "workflow_dispatch") and .conclusion == "success"', + '.event == "workflow_dispatch" and .conclusion == "success"', '.name == "full-source-gate" and .conclusion == "success"', ]); requireStepRun(violations, file, route, "Select change-aware proof scope", [ @@ -5687,7 +5715,11 @@ function validateRemainingWorkflows(workflows, violations) { add(violations, release.uses === "./.github/workflows/release.yml", `${autoFile} must call the release workflow`); add(violations, sameMembers(needs(release), ["detect-version"]), `${autoFile} release must need version detection`); add(violations, object(release.permissions).contents === "write", `${autoFile} release caller must grant contents write`); - add(violations, object(release.permissions).actions === "read", `${autoFile} release caller must grant actions read`); + add( + violations, + object(release.permissions).actions === "write", + `${autoFile} release caller must grant actions write for superseded-run cancellation`, + ); add( violations, object(release.permissions)["pull-requests"] === "read", @@ -5759,8 +5791,13 @@ function validateRemainingWorkflows(workflows, violations) { === macosMetalWorkflowDigest, `${metalFile} must match the reviewed protected Metal workflow structure`, ); - add(violations, trigger(metal, "workflow_call") !== undefined && trigger(metal, "workflow_dispatch") !== undefined, `${metalFile} must support reusable and manual proof`); - for (const event of ["workflow_call", "workflow_dispatch"]) { + add( + violations, + trigger(metal, "workflow_call") !== undefined + && trigger(metal, "workflow_dispatch") === undefined, + `${metalFile} must be coordinator-only and not directly dispatchable`, + ); + for (const event of ["workflow_call"]) { for (const key of ["calibration_bundle_artifact", "calibration_bundle_run_id"]) { requireOptionalStringInput(violations, metalFile, metal, event, key); } @@ -6402,8 +6439,13 @@ function validateRemainingWorkflows(workflows, violations) { === windowsVulkanWorkflowDigest, `${vulkanFile} must match the reviewed protected Windows Vulkan workflow structure`, ); - add(violations, trigger(vulkan, "workflow_call") !== undefined && trigger(vulkan, "workflow_dispatch") !== undefined, `${vulkanFile} must support reusable and manual proof`); - for (const event of ["workflow_call", "workflow_dispatch"]) { + add( + violations, + trigger(vulkan, "workflow_call") !== undefined + && trigger(vulkan, "workflow_dispatch") === undefined, + `${vulkanFile} must be coordinator-only and not directly dispatchable`, + ); + for (const event of ["workflow_call"]) { for (const key of ["calibration_bundle_artifact", "calibration_bundle_run_id"]) { requireOptionalStringInput(violations, vulkanFile, vulkan, event, key); } @@ -7033,10 +7075,10 @@ function validateRemainingWorkflows(workflows, violations) { add( violations, trigger(linuxVulkan, "workflow_call") !== undefined - && trigger(linuxVulkan, "workflow_dispatch") !== undefined, - `${linuxVulkanFile} must support reusable and manual proof`, + && trigger(linuxVulkan, "workflow_dispatch") === undefined, + `${linuxVulkanFile} must be coordinator-only and not directly dispatchable`, ); - for (const event of ["workflow_call", "workflow_dispatch"]) { + for (const event of ["workflow_call"]) { for (const key of ["calibration_bundle_artifact", "calibration_bundle_run_id"]) { requireOptionalStringInput(violations, linuxVulkanFile, linuxVulkan, event, key); } @@ -7070,44 +7112,21 @@ function validateRemainingWorkflows(workflows, violations) { const optionalCalibrationInput = object(at( linuxVulkan, "on", - "workflow_dispatch", + "workflow_call", "inputs", "constant_calibration_mode", )); add( violations, - at(linuxVulkan, "on", "workflow_call", "inputs", "constant_calibration_mode") - === undefined - && optionalCalibrationInput.required === false + optionalCalibrationInput.required === false && optionalCalibrationInput.type === "boolean" && optionalCalibrationInput.default === false, - `${linuxVulkanFile} optional constant calibration must be manual-only and off by default`, - ); - const manualPackageRunInput = object(at( - linuxVulkan, - "on", - "workflow_dispatch", - "inputs", - "package_run_id", - )); - add( - violations, - manualPackageRunInput.required === false - && manualPackageRunInput.type === "string" - && manualPackageRunInput.default === "", - `${linuxVulkanFile} standalone constant calibration must not require an upstream package run`, + `${linuxVulkanFile} optional constant calibration must be coordinator-only and off by default`, ); add( violations, - at( - linuxVulkan, - "on", - "workflow_dispatch", - "inputs", - "candidate_producer_workflow_path", - "default", - ) === ".github/workflows/packaged-platform-pr.yml", - `${linuxVulkanFile} manual candidate proof must trust the package-producing workflow`, + trigger(linuxVulkan, "workflow_dispatch") === undefined, + `${linuxVulkanFile} standalone proof must not bypass the coordinator`, ); const route = requireJob(violations, linuxVulkanFile, linuxVulkan, "route"); add( @@ -7557,11 +7576,11 @@ function validateRemainingWorkflows(workflows, violations) { add( violations, optionalCalibration.if - === "${{ github.event_name == 'workflow_dispatch' && inputs.constant_calibration_mode }}" + === "${{ inputs.constant_calibration_mode }}" && JSON.stringify(optionalCalibration["runs-on"]) === JSON.stringify(["self-hosted", "Linux", "X64", "codestory-linux-vulkan"]) && optionalCalibration.environment === "linux-vulkan-proof", - `${linuxVulkanFile} optional calibration must be a standalone protected manual Vulkan job`, + `${linuxVulkanFile} optional calibration must be a standalone protected coordinator-only Vulkan job`, ); requireStepRun( violations, @@ -7575,7 +7594,7 @@ function validateRemainingWorkflows(workflows, violations) { ); const optionalCollectorName = "Collect optional Linux Vulkan constant calibration"; requireStepRun(violations, linuxVulkanFile, optionalCalibration, optionalCollectorName, [ - 'test "$GITHUB_EVENT_NAME" = workflow_dispatch', + 'test "$CONSTANT_CALIBRATION_MODE" = true', "--engine-policy accelerated", "--expected-backend Vulkan", "--proof-tier calibration", @@ -7928,6 +7947,190 @@ export function releaseProofCpuSelectorViolations( return violations; } +export function releaseFreezeBarrierWorkflowViolations( + workflows, + graph = loadReleaseClaimGraph(repositoryRoot), +) { + const violations = []; + const freeze = object(graph.workflow_policy.release_freeze_barrier); + add( + violations, + freeze.schema === 1 + && freeze.script === ".github/scripts/release-freeze-barrier.mjs" + && freeze.status_context_prefix === "codestory/release-freeze" + && sameMembers(list(freeze.allowed_future_source_changes), [ + "crates/codestory-llama-sys/per-user-embedding-server-constant-set.json", + ]) + && object(freeze.single_source_proof).post_calibration_fallback_allowed === false, + "[freeze_barrier] release claim graph must pin the executable single-proof freeze contract", + ); + + for (const file of ["source-proof.yml", "packaged-platform-pr.yml"]) { + const workflow = workflows.get(file); + add( + violations, + trigger(workflow, "pull_request") === undefined, + `[proof_identity] ${file} must not run broad proof from a support PR event`, + ); + add( + violations, + String(at(workflow, "concurrency", "group") ?? "").includes("${{ github.sha }}"), + `[proof_identity] ${file} concurrency must bind the exact Actions SHA`, + ); + const freezeInput = object(at( + workflow, + "on", + "workflow_dispatch", + "inputs", + "freeze_receipt_digest", + )); + add( + violations, + freezeInput.required === true && freezeInput.type === "string", + `[freeze_barrier] ${file} dispatch must require an exact-head freeze receipt digest`, + ); + if (file === "source-proof.yml") { + const versionInput = object(at( + workflow, + "on", + "workflow_dispatch", + "inputs", + "version", + )); + const emitInput = object(at( + workflow, + "on", + "workflow_dispatch", + "inputs", + "emit_release_cells", + )); + add( + violations, + versionInput.required === true + && versionInput.type === "string" + && emitInput.required === false + && emitInput.type === "boolean" + && emitInput.default === true, + "[freeze_barrier] source-proof.yml dispatch must emit one reusable versioned source cell", + ); + } + add( + violations, + object(workflow.permissions).actions === "write", + `[freeze_barrier] ${file} must be able to cancel superseded runs`, + ); + const coordinatorJob = file === "source-proof.yml" ? "resolve" : "route"; + requireStepRun( + violations, + file, + requireJob(violations, file, workflow, coordinatorJob), + "Cancel superseded proof runs", + [ + "release-freeze-barrier.mjs cancel-superseded", + '--commit "$HEAD_SHA"', + '--broad-workflow "Exact-head source proof"', + '--broad-workflow "Platform and integration proof"', + '--broad-workflow "Release"', + '--broad-workflow "Auto Release"', + ], + ); + } + + for (const file of list(freeze.coordinator_only_workflows)) { + const workflow = workflows.get(file); + add( + violations, + trigger(workflow, "workflow_call") !== undefined + && trigger(workflow, "workflow_dispatch") === undefined, + `[freeze_barrier] ${file} must be callable only through an accepted coordinator`, + ); + } + + const coordinator = workflows.get("packaged-platform-pr.yml"); + const route = requireJob(violations, "packaged-platform-pr.yml", coordinator, "route"); + requireStepRun( + violations, + "packaged-platform-pr.yml", + route, + "Resolve accepted source proof head", + [ + 'if [ "$MODE" = qualification ]; then', + 'test -n "$CALIBRATION_SOURCE_SHA"', + "sha=$CALIBRATION_SOURCE_SHA", + "sha=$HEAD_SHA", + ], + ); + requireStepRun( + violations, + "packaged-platform-pr.yml", + route, + "Require successful accepted-head source proof", + [ + "actions/runs?head_sha=$SOURCE_SHA", + '.event == "workflow_dispatch" and .conclusion == "success"', + '.name == "full-source-gate" and .conclusion == "success"', + ], + ); + + const release = workflows.get("release.yml"); + const auto = workflows.get("auto-release.yml"); + add( + violations, + at(release, "concurrency", "cancel-in-progress") === true + && at(auto, "concurrency", "cancel-in-progress") === true, + "[freeze_barrier] release and auto-release must cancel superseded work", + ); + const preflight = requireJob(violations, "release.yml", release, "preflight"); + requireStepRun( + violations, + "release.yml", + preflight, + "Resolve reusable prior evidence", + [ + "actions/runs?head_sha=$SOURCE_SHA", + "The release workflow will not start a second proof after calibration", + "source_proof_reused=true", + ], + ); + requireStepEnv( + violations, + "release.yml", + preflight, + "Resolve reusable prior evidence", + { + SOURCE_SHA: "${{ steps.lineage.outputs.selection_commit }}", + SOURCE_TREE: "${{ steps.lineage.outputs.selection_tree }}", + }, + ); + const sourceJob = requireJob(violations, "release.yml", release, "source-proof"); + add( + violations, + sourceJob.if === "needs.preflight.outputs.source_proof_reused != 'true'" + && object(preflight.outputs).source_proof_reused + === "${{ steps.reuse.outputs.source_proof_reused }}", + "[freeze_barrier] release must make the post-calibration source-proof fallback unreachable", + ); + + const lineageSource = fs.readFileSync( + path.join( + repositoryRoot, + ".github", + "scripts", + "packaged_agent_proof", + "calibration_lineage.py", + ), + "utf8", + ); + add( + violations, + lineageSource.includes("frozen_parents == [calibration_source[\"commit\"]]") + && lineageSource.includes("Any later commit revokes acceptance") + && lineageSource.includes("allow_promotion_commit"), + "[freeze_barrier] calibration lineage must require one direct constant-only child with an explicit promotion exception", + ); + return violations; +} + export function releaseWorkflowContractViolations( workflows, graph = loadReleaseClaimGraph(repositoryRoot), @@ -8054,6 +8257,7 @@ export function releaseWorkflowContractViolations( `[proof_identity] ${file} must resolve the current head and compare its exact SHA before executing labeled work`, ); } + violations.push(...releaseFreezeBarrierWorkflowViolations(workflows, graph)); return violations; } diff --git a/.github/scripts/check-workflow-policy.test.mjs b/.github/scripts/check-workflow-policy.test.mjs index afde629e5..4a504150e 100644 --- a/.github/scripts/check-workflow-policy.test.mjs +++ b/.github/scripts/check-workflow-policy.test.mjs @@ -197,16 +197,24 @@ function commitCalibrationFixture(repository, message) { }; } -function runCalibrationReleaseCheck(repository, expectedSha) { +function runCalibrationReleaseCheck( + repository, + expectedSha, + { allowPromotionCommit = false } = {}, +) { + const argumentsList = [ + calibrationReleaseChecker, + "--repo", + repository, + "--expected-sha", + expectedSha, + ]; + if (allowPromotionCommit) { + argumentsList.push("--allow-promotion-commit"); + } return spawnSync( "python", - [ - calibrationReleaseChecker, - "--repo", - repository, - "--expected-sha", - expectedSha, - ], + argumentsList, { cwd: root, encoding: "utf8", @@ -845,10 +853,9 @@ test("constant calibration structure rejects qualification, 3x3 sampling, repeat "", ); }, /must upload attempt-scoped non-selecting evidence/u], - ["Linux calibration requires an upstream package run", "linux-vulkan-proof.yml", workflow => { - workflow.on.workflow_dispatch.inputs.package_run_id.required = true; - delete workflow.on.workflow_dispatch.inputs.package_run_id.default; - }, /must not require an upstream package run/u], + ["Linux calibration restores a direct dispatch", "linux-vulkan-proof.yml", workflow => { + workflow.on.workflow_dispatch = { inputs: {} }; + }, /coordinator-only and not directly dispatchable/u], ["Linux calibration downloads an independently built package", "linux-vulkan-proof.yml", workflow => { workflow.jobs["optional-constant-calibration"].steps.splice(5, 0, { name: "Download exact Linux package", @@ -2232,7 +2239,7 @@ test("release-head calibration lineage rejects identities and source shapes arou assert.match(result.stderr, /release checkout does not match the expected release source/u); }); - await t.test("a tree-preserving promotion commit stays bound", () => { + await t.test("a later commit revokes candidate acceptance unless it is the explicit promotion", () => { calibrationGit( repository, "commit", @@ -2243,8 +2250,19 @@ test("release-head calibration lineage rejects identities and source shapes arou "promote frozen tree", ); const promoted = calibrationGit(repository, "rev-parse", "HEAD"); - const result = runCalibrationReleaseCheck(repository, promoted); - assert.equal(result.status, 0, result.stderr || result.stdout); + const rejected = runCalibrationReleaseCheck(repository, promoted); + assert.notEqual(rejected.status, 0); + assert.match(rejected.stderr, /later commit revokes acceptance/u); + const promotedResult = runCalibrationReleaseCheck( + repository, + promoted, + { allowPromotionCommit: true }, + ); + assert.equal( + promotedResult.status, + 0, + promotedResult.stderr || promotedResult.stdout, + ); calibrationGit(repository, "reset", "--hard", frozen.commit); }); @@ -2332,7 +2350,7 @@ test("release policy keeps the release-head lineage check mandatory and exact", ["interpreter uses PATH lookup", workflows => { const step = draftStep(workflows.get("release.yml").jobs.preflight, stepName); step.run = step.run.replace("/usr/bin/python3 -E -s", "python"); - }, /must use the pinned interpreter on the exact release checkout/u], + }, /step Verify release-head calibration lineage must run \/usr\/bin\/python3 -E -s/u], ["lineage shell uses PATH lookup", workflows => { const step = draftStep(workflows.get("release.yml").jobs.preflight, stepName); step.shell = "bash -e {0}"; @@ -2358,7 +2376,7 @@ test("release policy keeps the release-head lineage check mandatory and exact", ["wrong release SHA", workflows => { const step = draftStep(workflows.get("release.yml").jobs.preflight, stepName); step.run = step.run.replace("$GITHUB_SHA", "$EXPECTED_HEAD_SHA"); - }, /must use the pinned interpreter on the exact release checkout/u], + }, /step Verify release-head calibration lineage must run --expected-sha/u], ]; assert.deepEqual(validateWorkflows(loadWorkflows()), []); for (const [name, mutate, expected] of cases) { @@ -2518,12 +2536,12 @@ test("exact proof policy rejects trigger and identity downgrades", async (t) => const packagedResolver = workflow => draftStep(workflow.jobs.route, "Resolve trusted exact head"); const mutations = [ - ["source synchronize trigger", sourceFile, workflow => { - workflow.on.pull_request.types.push("synchronize"); - }, /trigger must be label-only/u], - ["platform synchronize trigger", packagedCoordinatorFile, workflow => { - workflow.on.pull_request.types.push("synchronize"); - }, /trigger must be label-only/u], + ["source PR label trigger returns", sourceFile, workflow => { + workflow.on.pull_request = { types: ["labeled"] }; + }, /support PR labels must not trigger broad source proof/u], + ["platform PR label trigger returns", packagedCoordinatorFile, workflow => { + workflow.on.pull_request = { types: ["labeled"] }; + }, /support PR labels must not trigger package or hardware proof/u], ["source PR-number-only concurrency", sourceFile, workflow => { workflow.concurrency.group = "source-proof-${{ inputs.pr_number || github.event.pull_request.number }}"; }, /concurrency must bind the Actions SHA/u], @@ -2559,10 +2577,10 @@ test("exact proof policy rejects trigger and identity downgrades", async (t) => sourceResolver(workflow).run = sourceResolver(workflow).run .replace("set -euo pipefail\n", "set -euo pipefail\n\n"); }, /exact normalized trusted resolver script contract/u], - ["source labeled job disabled", sourceFile, workflow => { + ["source resolve becomes conditional", sourceFile, workflow => { workflow.jobs.resolve.if = "false && (github.event.action == 'labeled' && github.event.label.name == 'review-accepted')"; - }, /only review-accepted labeled PR runs/u], + }, /execute only explicit dispatch and reusable calls/u], ["source manual ref equality", sourceFile, workflow => { sourceResolver(workflow).run = sourceResolver(workflow).run .replace('test "$GITHUB_REF" = "refs\/heads\/$head_ref"', 'test -n "$GITHUB_REF"'); @@ -2599,10 +2617,10 @@ test("exact proof policy rejects trigger and identity downgrades", async (t) => 'if [ -n "$INPUT_SOURCE_RUN_ID" ] \\\n\n ||', ); }, /exact normalized trusted resolver script contract/u], - ["platform labeled job disabled", packagedCoordinatorFile, workflow => { + ["platform route becomes conditional", packagedCoordinatorFile, workflow => { workflow.jobs.route.if = "false && (github.event.action == 'labeled' && github.event.label.name == 'platform-proof')"; - }, /only platform-proof labeled PR runs/u], + }, /execute only explicit dispatches/u], ["integration live dev SHA equality", packagedCoordinatorFile, workflow => { packagedResolver(workflow).run = packagedResolver(workflow).run .replace('test "$GITHUB_SHA" = "$dev_head"', 'test -n "$GITHUB_SHA"'); @@ -2627,10 +2645,9 @@ test("exact proof policy rejects trigger and identity downgrades", async (t) => ["protected Linux candidate proof disabled", packagedCoordinatorFile, workflow => { workflow.jobs["linux-vulkan-proof"].with.candidate_installed_proof = false; }, /Linux proof must close Vulkan and candidate-installed claims/u], - ["manual Linux candidate trusts a non-producer", linuxVulkanFile, workflow => { - workflow.on.workflow_dispatch.inputs.candidate_producer_workflow_path.default - = ".github/workflows/release.yml"; - }, /manual candidate proof must trust the package-producing workflow/u], + ["Linux direct dispatch returns", linuxVulkanFile, workflow => { + workflow.on.workflow_dispatch = { inputs: {} }; + }, /coordinator-only and not directly dispatchable/u], ["closeout skips protected Linux", packagedCoordinatorFile, workflow => { workflow.jobs.closeout.needs = workflow.jobs.closeout.needs .filter(name => name !== "linux-vulkan-proof"); @@ -2783,8 +2800,8 @@ test("source proof reuse accepts only whole successful workflow runs", async (t) "Reuse a completed gate for this exact head", ); step.run = step.run.replace( - '(.event == "pull_request" or .event == "workflow_dispatch") and .conclusion == "success"', - '(.event == "pull_request" or .event == "workflow_dispatch")', + '.event == "workflow_dispatch" and .conclusion == "success"', + '.event == "workflow_dispatch"', ); }, /source-proof\.yml step Reuse a completed gate.*workflow_dispatch.*conclusion/u], ["release preflight reuse", workflows => { @@ -2793,20 +2810,20 @@ test("source proof reuse accepts only whole successful workflow runs", async (t) "Resolve reusable prior evidence", ); step.run = step.run.replace( - ".head_repository.full_name == $repo and .conclusion == \"success\"", - ".head_repository.full_name == $repo", + '.event == "workflow_dispatch" and .conclusion == "success"', + '.event == "workflow_dispatch"', ); }, /release\.yml step Resolve reusable prior evidence.*conclusion/u], ["packaged prior proof lookup", workflows => { const step = draftStep( workflows.get("packaged-platform-pr.yml").jobs.route, - "Require successful exact-head source proof", + "Require successful accepted-head source proof", ); step.run = step.run.replace( - '(.event == "pull_request" or .event == "workflow_dispatch") and .conclusion == "success"', - '(.event == "pull_request" or .event == "workflow_dispatch")', + '.event == "workflow_dispatch" and .conclusion == "success"', + '.event == "workflow_dispatch"', ); - }, /packaged-platform-pr\.yml step Require successful exact-head source proof.*conclusion/u], + }, /packaged-platform-pr\.yml step Require successful accepted-head source proof.*conclusion/u], ]; for (const [name, mutate, expectedReason] of mutations) { @@ -2818,6 +2835,85 @@ test("source proof reuse accepts only whole successful workflow runs", async (t) } }); +test("release freeze barrier rejects every broad-proof bypass", async (t) => { + assert.deepEqual(validateWorkflows(loadWorkflows()), []); + const cases = [ + ["source label trigger", workflows => { + workflows.get("source-proof.yml").on.pull_request = { types: ["labeled"] }; + }, /support PR event/u], + ["platform label trigger", workflows => { + workflows.get("packaged-platform-pr.yml").on.pull_request = { types: ["labeled"] }; + }, /support PR event/u], + ...[ + "macos-metal-proof.yml", + "windows-vulkan-proof.yml", + "linux-vulkan-proof.yml", + ].map(file => [ + `${file} direct dispatch`, + workflows => { + workflows.get(file).on.workflow_dispatch = { inputs: {} }; + }, + /callable only through an accepted coordinator/u, + ]), + ["source dispatch omits receipt", workflows => { + workflows.get("source-proof.yml").on.workflow_dispatch + .inputs.freeze_receipt_digest.required = false; + }, /dispatch must require an exact-head freeze receipt digest/u], + ["source dispatch stops emitting a reusable cell", workflows => { + workflows.get("source-proof.yml").on.workflow_dispatch + .inputs.emit_release_cells.default = false; + }, /dispatch must emit one reusable versioned source cell/u], + ["platform dispatch omits receipt", workflows => { + workflows.get("packaged-platform-pr.yml").on.workflow_dispatch + .inputs.freeze_receipt_digest.required = false; + }, /dispatch must require an exact-head freeze receipt digest/u], + ["qualification proves the frozen descendant again", workflows => { + const step = draftStep( + workflows.get("packaged-platform-pr.yml").jobs.route, + "Resolve accepted source proof head", + ); + step.run = step.run.replace( + 'echo "sha=$CALIBRATION_SOURCE_SHA" >> "$GITHUB_OUTPUT"', + 'echo "sha=$HEAD_SHA" >> "$GITHUB_OUTPUT"', + ); + }, /Resolve accepted source proof head.*sha=\$CALIBRATION_SOURCE_SHA/u], + ["release searches the frozen descendant", workflows => { + const step = draftStep( + workflows.get("release.yml").jobs.preflight, + "Resolve reusable prior evidence", + ); + step.run = step.run.replace( + "actions/runs?head_sha=$SOURCE_SHA", + "actions/runs?head_sha=$GITHUB_SHA", + ); + }, /Resolve reusable prior evidence.*head_sha=\$SOURCE_SHA/u], + ["release restores post-calibration fallback", workflows => { + workflows.get("release.yml").jobs["source-proof"].if = "always()"; + }, /post-calibration source-proof fallback unreachable/u], + ["release stops cancelling superseded work", workflows => { + workflows.get("release.yml").concurrency["cancel-in-progress"] = false; + }, /release and auto-release must cancel superseded work/u], + ["auto-release stops cancelling superseded work", workflows => { + workflows.get("auto-release.yml").concurrency["cancel-in-progress"] = false; + }, /release and auto-release must cancel superseded work/u], + ["source stale-run sweep is removed", workflows => { + const job = workflows.get("source-proof.yml").jobs.resolve; + job.steps = job.steps.filter(({ name }) => name !== "Cancel superseded proof runs"); + }, /Cancel superseded proof runs/u], + ["platform stale-run sweep is removed", workflows => { + const job = workflows.get("packaged-platform-pr.yml").jobs.route; + job.steps = job.steps.filter(({ name }) => name !== "Cancel superseded proof runs"); + }, /Cancel superseded proof runs/u], + ]; + for (const [name, mutate, expected] of cases) { + await t.test(name, () => { + const workflows = loadWorkflows(); + mutate(workflows); + assert.match(validateWorkflows(workflows).join("\n"), expected); + }); + } +}); + test("Windows package proof retains the readable native sccache executable", () => { const directory = mkdtempSync(path.join(os.tmpdir(), "codestory-windows-sccache-")); try { @@ -3402,17 +3498,14 @@ test("standard release paths reject calibration plumbing", async (t) => { required: true, type: "string", }; - }, /release\.yml standard release path must not reference calibration/u], + }, /release\.yml must not accept calibration bundle inputs/u], ["release forwards calibration to package proof", workflows => { workflows.get("release.yml").jobs["packaged-proof"].with.calibration_bundle_run_id = "${{ inputs.calibration_bundle_run_id }}"; - }, /release\.yml standard release path must not reference calibration/u], - ["release hides calibration plumbing in a same-named decoy step", workflows => { - workflows.get("release.yml").jobs["workflow-policy"].steps.push({ - name: "Verify release-head calibration lineage", - run: "echo calibration_bundle_artifact", - }); - }, /release\.yml standard release path must not reference calibration/u], + }, /release\.yml packaged proof must not receive calibration_bundle_run_id/u], + ["release restores a second source proof fallback", workflows => { + workflows.get("release.yml").jobs["source-proof"].if = "always()"; + }, /source proof may be skipped only when preflight resolved reusable evidence/u], ["post-publish proof receives calibration", workflows => { const step = draftStep( workflows.get("post-publish-release-smoke.yml").jobs.smoke, diff --git a/.github/scripts/fixtures/workflow-policy-invalid.json b/.github/scripts/fixtures/workflow-policy-invalid.json index 5226f2543..35685c8ea 100644 --- a/.github/scripts/fixtures/workflow-policy-invalid.json +++ b/.github/scripts/fixtures/workflow-policy-invalid.json @@ -83,18 +83,18 @@ ] }, { - "id": "synchronize-proof-trigger", + "id": "support-proof-trigger", "class_prefix": "[proof_identity]", "workflow": "source-proof.yml", "field": [ "on", - "pull_request", - "types" + "pull_request" ], - "value": [ - "labeled", - "synchronize" - ] + "value": { + "types": [ + "labeled" + ] + } }, { "id": "pr-number-only-proof-concurrency", diff --git a/.github/scripts/packaged_agent_proof/calibration_lineage.py b/.github/scripts/packaged_agent_proof/calibration_lineage.py index f063045df..a40424ac9 100644 --- a/.github/scripts/packaged_agent_proof/calibration_lineage.py +++ b/.github/scripts/packaged_agent_proof/calibration_lineage.py @@ -81,6 +81,8 @@ def _tracked_source_dirty(repository_root: Path) -> bool: def verify_release_head_calibration_lineage( repository_root: Path, expected_release_commit: str, + *, + allow_promotion_commit: bool = False, ) -> dict: """Bind a release checkout to the calibration source in its freeze record. @@ -140,6 +142,7 @@ def verify_release_head_calibration_lineage( calibration_source, release_source, repository_root, + allow_promotion_commit=allow_promotion_commit, ) return { **lineage, @@ -152,6 +155,8 @@ def verify_calibration_source_lineage( calibration_source: dict, frozen_source: dict, repository_root: Path, + *, + allow_promotion_commit: bool = False, ) -> dict: require( frozen_source.get("tracked_dirty") is False, @@ -238,8 +243,47 @@ def verify_calibration_source_lineage( ) + f". The {REQUIRED_RELEASE_ORDERING}.", ) + frozen_parents = _git( + repository_root, + "rev-list", + "--parents", + "-n", + "1", + frozen_source["commit"], + ).split()[1:] + direct_freeze = frozen_parents == [calibration_source["commit"]] + promotion_parent = None + if allow_promotion_commit and not direct_freeze: + candidates = [] + for parent in frozen_parents: + parent_parents = _git( + repository_root, + "rev-list", + "--parents", + "-n", + "1", + parent, + ).split()[1:] + parent_tree = _git(repository_root, "rev-parse", f"{parent}^{{tree}}") + if ( + parent_parents == [calibration_source["commit"]] + and parent_tree == frozen_source["tree"] + ): + candidates.append(parent) + if len(candidates) == 1: + promotion_parent = candidates[0] + require( + direct_freeze or promotion_parent is not None, + "the frozen candidate must be the direct single-parent child of the " + "accepted calibration source. Any later commit revokes acceptance; " + "publication may add only one explicit tree-preserving promotion commit", + ) return { "selection_commit": calibration_source["commit"], "frozen_commit": frozen_source["commit"], + "freeze_commit": promotion_parent or frozen_source["commit"], + "promotion_commit": ( + frozen_source["commit"] if promotion_parent is not None else None + ), "allowed_changed_paths": changed_paths, } diff --git a/.github/scripts/packaged_agent_proof/self_test_calibration_lineage.py b/.github/scripts/packaged_agent_proof/self_test_calibration_lineage.py index 1ab2b2999..00b8850a5 100644 --- a/.github/scripts/packaged_agent_proof/self_test_calibration_lineage.py +++ b/.github/scripts/packaged_agent_proof/self_test_calibration_lineage.py @@ -125,6 +125,8 @@ def _accepts_the_single_freeze_commit(root: Path, calibration: dict) -> dict: == { "selection_commit": calibration["commit"], "frozen_commit": frozen["commit"], + "freeze_commit": frozen["commit"], + "promotion_commit": None, "allowed_changed_paths": [CONSTANT_SET_FREEZE_PATH], }, "the one allowed constant-set freeze commit was not accepted intact", @@ -132,6 +134,33 @@ def _accepts_the_single_freeze_commit(root: Path, calibration: dict) -> dict: return frozen +def _rejects_commit_after_freeze( + root: Path, + calibration: dict, + frozen: dict, +) -> None: + later = _commit(root, "later empty commit", allow_empty=True) + _reject( + "a later tree-preserving commit", + ["direct single-parent child", "later commit revokes acceptance"], + calibration, + later, + root, + ) + accepted_promotion = verify_calibration_source_lineage( + calibration, + later, + root, + allow_promotion_commit=True, + ) + require( + accepted_promotion["freeze_commit"] == frozen["commit"] + and accepted_promotion["promotion_commit"] == later["commit"], + "the explicit tree-preserving promotion exception lost its exact commits", + ) + _git(root, "reset", "-q", "--hard", frozen["commit"]) + + def _rejects_identity_and_checkout_drift( root: Path, calibration: dict, @@ -396,6 +425,7 @@ def run_calibration_lineage_self_tests() -> None: root.mkdir(parents=True) calibration = _build_calibration_history(root) frozen = _accepts_the_single_freeze_commit(root, calibration) + _rejects_commit_after_freeze(root, calibration, frozen) _rejects_identity_and_checkout_drift(root, calibration, frozen) _rejects_calibrate_then_bump(root, calibration) _rejects_missing_freeze_and_unrelated_history(root, frozen) diff --git a/.github/scripts/release-freeze-barrier.mjs b/.github/scripts/release-freeze-barrier.mjs new file mode 100644 index 000000000..ec5960a28 --- /dev/null +++ b/.github/scripts/release-freeze-barrier.mjs @@ -0,0 +1,501 @@ +#!/usr/bin/env node + +import { createHash } from "node:crypto"; +import { readFileSync, writeFileSync } from "node:fs"; +import { execFileSync } from "node:child_process"; +import process from "node:process"; + +const ACTIVE_RUN_STATES = new Set([ + "queued", + "waiting", + "requested", + "pending", + "in_progress", +]); +const ALLOWED_FUTURE_CHANGES = new Set([ + "crates/codestory-llama-sys/per-user-embedding-server-constant-set.json", +]); +const STATUS_PREFIX = "codestory/release-freeze"; + +function fail(message) { + throw new Error(message); +} + +function run(command, args, options = {}) { + return execFileSync(command, args, { + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + ...options, + }).trim(); +} + +function git(args, repo) { + return run("git", ["-C", repo, ...args]); +} + +function gh(args) { + return run("gh", args); +} + +function values(args, name) { + const result = []; + for (let index = 0; index < args.length; index += 1) { + if (args[index] === name) { + const value = args[index + 1]; + if (!value || value.startsWith("--")) { + fail(`${name} requires a value`); + } + result.push(value); + index += 1; + } + } + return result; +} + +function value(args, name, fallback = undefined) { + const found = values(args, name); + if (found.length > 1) { + fail(`${name} may be specified only once`); + } + return found[0] ?? fallback; +} + +function required(args, name) { + const result = value(args, name); + if (!result) { + fail(`${name} is required`); + } + return result; +} + +function has(args, name) { + return args.includes(name); +} + +function parseJsonFile(path, label) { + try { + return JSON.parse(readFileSync(path, "utf8")); + } catch (error) { + fail(`${label} is not valid JSON: ${error.message}`); + } +} + +function stable(value) { + if (Array.isArray(value)) { + return value.map(stable); + } + if (value && typeof value === "object") { + return Object.fromEntries( + Object.entries(value) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, item]) => [key, stable(item)]), + ); + } + return value; +} + +export function receiptDigest(receipt) { + const withoutDigest = { ...receipt }; + delete withoutDigest.digest; + return createHash("sha256") + .update(`${JSON.stringify(stable(withoutDigest))}\n`) + .digest("hex"); +} + +export function validateMutationReceipt(receipt, { commit, tree, requiredIds }) { + if (receipt?.commit !== commit || receipt?.tree !== tree) { + fail("hostile mutation evidence must name the exact frozen commit and tree"); + } + if (!Array.isArray(receipt.cases)) { + fail("hostile mutation evidence must contain cases"); + } + const cases = new Map(receipt.cases.map((entry) => [entry?.id, entry])); + for (const id of requiredIds) { + const entry = cases.get(id); + if (!entry || entry.status !== "passed") { + fail(`hostile mutation ${id} did not pass on the exact frozen head`); + } + } +} + +export function validatePlatformEvidence(evidence, { commit, tree }) { + const failures = evidence?.failures ?? []; + const probes = evidence?.probes ?? []; + if (!Array.isArray(failures) || !Array.isArray(probes)) { + fail("platform evidence must contain failure and probe arrays"); + } + for (const failure of failures) { + const probe = probes.find((candidate) => + candidate?.failure_run_id === failure?.run_id + && candidate?.platform === failure?.platform + && candidate?.commit === commit + && candidate?.tree === tree + && candidate?.status === "passed" + && Number.isFinite(candidate?.duration_seconds) + && candidate.duration_seconds < 90 + && typeof candidate?.mutation === "string" + && candidate.mutation.length > 0 + ); + if (!probe) { + fail( + `platform failure ${failure?.run_id ?? ""} lacks an exact-head native probe under 90 seconds`, + ); + } + } +} + +export function validateReceipt(receipt, { commit, tree, requiredMutationIds = [] }) { + if (receipt?.schema !== 1) { + fail("freeze receipt schema must be 1"); + } + if (receipt.commit !== commit || receipt.tree !== tree) { + fail("freeze receipt does not match the exact commit and tree"); + } + if (receipt.worktree_clean !== true || receipt.remote_head !== commit) { + fail("freeze receipt must prove a clean worktree pushed at the exact commit"); + } + if ( + receipt?.release_pr?.head_commit !== commit + || receipt?.release_pr?.head !== receipt.branch + || receipt?.release_pr?.base !== "dev/codestory-next" + ) { + fail("freeze receipt must bind the open release PR at this exact head"); + } + if (!Array.isArray(receipt.known_future_source_changes)) { + fail("freeze receipt must declare known future source changes"); + } + for (const path of receipt.known_future_source_changes) { + if (!ALLOWED_FUTURE_CHANGES.has(path)) { + fail(`freeze receipt admits an unsupported future source change: ${path}`); + } + } + for (const field of [ + "planned_proof_actions", + "reusable_evidence", + "invalidated_evidence", + "running_workflows", + ]) { + if (!Array.isArray(receipt[field])) { + fail(`freeze receipt must contain ${field}`); + } + } + if (typeof receipt.next_permitted_mutation !== "string" + || receipt.next_permitted_mutation.length === 0) { + fail("freeze receipt must name the next permitted mutation"); + } + validateMutationReceipt(receipt.hostile_mutations, { + commit, + tree, + requiredIds: requiredMutationIds, + }); + validatePlatformEvidence(receipt.platform_evidence, { commit, tree }); + if (receipt.digest !== receiptDigest(receipt)) { + fail("freeze receipt digest does not match its contents"); + } +} + +function currentRuns(repository) { + const raw = gh([ + "run", + "list", + "--repo", + repository, + "--limit", + "100", + "--json", + "databaseId,workflowName,headSha,headBranch,status,event,url", + ]); + const parsed = JSON.parse(raw || "[]"); + return parsed.filter((entry) => ACTIVE_RUN_STATES.has(entry.status)); +} + +function cancelSupersededRuns({ repository, commit, workflows, runs }) { + const allowlist = new Set(workflows); + const cancelled = []; + for (const entry of runs) { + if (!allowlist.has(entry.workflowName) || entry.headSha === commit) { + continue; + } + gh(["run", "cancel", String(entry.databaseId), "--repo", repository]); + cancelled.push({ + database_id: entry.databaseId, + head_sha: entry.headSha, + workflow: entry.workflowName, + }); + } + return cancelled; +} + +function cancelSuperseded(args) { + const repository = required(args, "--repository"); + const commit = required(args, "--commit"); + const workflows = values(args, "--broad-workflow"); + if (workflows.length === 0) { + fail("--broad-workflow is required"); + } + const before = currentRuns(repository); + const duplicate = before.find((entry) => + workflows.includes(entry.workflowName) + && entry.headSha === commit + && String(entry.databaseId) !== String(process.env.GITHUB_RUN_ID ?? "") + ); + if (duplicate) { + fail( + `unchanged head ${commit} already has active ${duplicate.workflowName} run ${duplicate.databaseId}`, + ); + } + const cancelled = cancelSupersededRuns({ + repository, + commit, + workflows, + runs: before, + }); + const cancelledIds = new Set(cancelled.map((entry) => String(entry.database_id))); + const remaining = currentRuns(repository).filter((entry) => + workflows.includes(entry.workflowName) + && entry.headSha !== commit + && !cancelledIds.has(String(entry.databaseId)) + ); + if (remaining.length > 0) { + fail("superseded broad proof remains queued or running after cancellation"); + } + process.stdout.write(`${JSON.stringify({ cancelled })}\n`); +} + +function supportPr(repository, number, commit, repo) { + const pr = JSON.parse(gh([ + "pr", + "view", + String(number), + "--repo", + repository, + "--json", + "number,state,mergedAt,mergeCommit,baseRefName,headRefName", + ])); + const mergeCommit = pr?.mergeCommit?.oid; + if (pr.state !== "MERGED" || !pr.mergedAt || !mergeCommit) { + fail(`support PR #${number} is not merged`); + } + try { + git(["merge-base", "--is-ancestor", mergeCommit, commit], repo); + } catch { + fail(`support PR #${number} merge ${mergeCommit} is not integrated into ${commit}`); + } + return { + number: pr.number, + merge_commit: mergeCommit, + base: pr.baseRefName, + head: pr.headRefName, + }; +} + +function releasePr(repository, number, { branch, commit }) { + const pr = JSON.parse(gh([ + "pr", + "view", + String(number), + "--repo", + repository, + "--json", + "number,state,baseRefName,headRefName,headRefOid,headRepository", + ])); + if ( + pr.state !== "OPEN" + || pr.baseRefName !== "dev/codestory-next" + || pr.headRefName !== branch + || pr.headRefOid !== commit + || pr?.headRepository?.nameWithOwner !== repository + ) { + fail( + `release PR #${number} must be an open same-repository ${branch} -> ` + + `dev/codestory-next PR at exact head ${commit}`, + ); + } + return { + number: pr.number, + base: pr.baseRefName, + head: pr.headRefName, + head_commit: pr.headRefOid, + }; +} + +function declare(args) { + const repo = value(args, "--repo", process.cwd()); + const repository = required(args, "--repository"); + const branch = value(args, "--branch", git(["branch", "--show-current"], repo)); + const output = required(args, "--output"); + const releasePrNumber = required(args, "--release-pr"); + const mutationPath = required(args, "--mutation-receipt"); + const platformPath = required(args, "--platform-evidence"); + const requiredMutationIds = values(args, "--required-mutation"); + const supportPrNumbers = values(args, "--support-pr"); + const knownFutureChanges = values(args, "--known-future-change"); + const plannedProofActions = values(args, "--planned-proof-action"); + const reusableEvidence = values(args, "--reusable-evidence"); + const invalidatedEvidence = values(args, "--invalidated-evidence"); + const nextMutation = required(args, "--next-permitted-mutation"); + const broadWorkflows = values(args, "--broad-workflow"); + if ( + requiredMutationIds.length === 0 + || plannedProofActions.length === 0 + || broadWorkflows.length === 0 + ) { + fail( + "release freeze requires hostile mutations, planned proof actions, and broad workflow names", + ); + } + + if (git(["status", "--porcelain=v1", "--untracked-files=all"], repo) !== "") { + fail("release freeze requires a clean worktree, including untracked files"); + } + const commit = git(["rev-parse", "HEAD"], repo); + const tree = git(["rev-parse", "HEAD^{tree}"], repo); + const remoteLine = git(["ls-remote", "--exit-code", "origin", `refs/heads/${branch}`], repo); + const remoteHead = remoteLine.split(/\s+/u)[0]; + if (remoteHead !== commit) { + fail(`origin/${branch} is ${remoteHead}, not local HEAD ${commit}`); + } + for (const path of knownFutureChanges) { + if (!ALLOWED_FUTURE_CHANGES.has(path)) { + fail(`unsupported future source change: ${path}`); + } + } + + const mutationReceipt = parseJsonFile(mutationPath, "mutation receipt"); + validateMutationReceipt(mutationReceipt, { + commit, + tree, + requiredIds: requiredMutationIds, + }); + const platformEvidence = parseJsonFile(platformPath, "platform evidence"); + validatePlatformEvidence(platformEvidence, { commit, tree }); + const acceptedReleasePr = releasePr(repository, releasePrNumber, { + branch, + commit, + }); + const integratedSupportPrs = supportPrNumbers.map( + (number) => supportPr(repository, number, commit, repo), + ); + + const runs = currentRuns(repository); + const cancelledRuns = has(args, "--cancel-superseded") + ? cancelSupersededRuns({ + repository, + commit, + workflows: broadWorkflows, + runs, + }) + : []; + const cancelledIds = new Set( + cancelledRuns.map((entry) => String(entry.database_id)), + ); + const remainingRuns = currentRuns(repository); + const superseded = remainingRuns.filter( + (entry) => + broadWorkflows.includes(entry.workflowName) + && entry.headSha !== commit + && !cancelledIds.has(String(entry.databaseId)), + ); + if (superseded.length > 0) { + fail("superseded broad proof remains queued or running"); + } + + const receipt = { + schema: 1, + repository, + branch, + commit, + tree, + worktree_clean: true, + remote_head: remoteHead, + release_pr: acceptedReleasePr, + integrated_support_prs: integratedSupportPrs, + known_future_source_changes: knownFutureChanges, + planned_proof_actions: plannedProofActions, + hostile_mutations: mutationReceipt, + platform_evidence: platformEvidence, + reusable_evidence: reusableEvidence, + invalidated_evidence: invalidatedEvidence, + running_workflows: remainingRuns, + cancelled_superseded_runs: cancelledRuns, + next_permitted_mutation: nextMutation, + }; + receipt.digest = receiptDigest(receipt); + validateReceipt(receipt, { commit, tree, requiredMutationIds }); + writeFileSync(output, `${JSON.stringify(receipt, null, 2)}\n`); + + if (!has(args, "--no-publish-status")) { + gh([ + "api", + "--method", + "POST", + `repos/${repository}/statuses/${commit}`, + "-f", + "state=success", + "-f", + `context=${STATUS_PREFIX}/${receipt.digest}`, + "-f", + `description=tree=${tree}`, + ]); + } + process.stdout.write(`${receipt.digest}\n`); +} + +function verifyFile(args) { + const receipt = parseJsonFile(required(args, "--receipt"), "freeze receipt"); + const commit = required(args, "--commit"); + const tree = required(args, "--tree"); + validateReceipt(receipt, { + commit, + tree, + requiredMutationIds: values(args, "--required-mutation"), + }); + process.stdout.write(`${receipt.digest}\n`); +} + +function verifyStatus(args) { + const repository = required(args, "--repository"); + const commit = required(args, "--commit"); + const tree = required(args, "--tree"); + const digest = required(args, "--receipt-digest"); + const statuses = JSON.parse(gh([ + "api", + `repos/${repository}/commits/${commit}/statuses?per_page=100`, + ])); + const accepted = statuses.some((status) => + status?.state === "success" + && status?.context === `${STATUS_PREFIX}/${digest}` + && status?.description === `tree=${tree}` + ); + if (!accepted) { + fail("no successful exact-head release freeze status matches this receipt digest and tree"); + } + process.stdout.write(`${digest}\n`); +} + +function main() { + const [command, ...args] = process.argv.slice(2); + if (command === "declare") { + declare(args); + } else if (command === "verify-file") { + verifyFile(args); + } else if (command === "verify-status") { + verifyStatus(args); + } else if (command === "cancel-superseded") { + cancelSuperseded(args); + } else { + fail( + "usage: release-freeze-barrier.mjs " + + " ...", + ); + } +} + +if (import.meta.url === `file://${process.argv[1]}`) { + try { + main(); + } catch (error) { + process.stderr.write(`release freeze rejected: ${error.message}\n`); + process.exitCode = 1; + } +} diff --git a/.github/scripts/release-freeze-barrier.test.mjs b/.github/scripts/release-freeze-barrier.test.mjs new file mode 100644 index 000000000..084d6d7ab --- /dev/null +++ b/.github/scripts/release-freeze-barrier.test.mjs @@ -0,0 +1,237 @@ +import assert from "node:assert/strict"; +import { execFileSync, spawnSync } from "node:child_process"; +import { mkdtempSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import test from "node:test"; +import { + receiptDigest, + validateMutationReceipt, + validatePlatformEvidence, + validateReceipt, +} from "./release-freeze-barrier.mjs"; + +const COMMIT = "1".repeat(40); +const TREE = "2".repeat(40); +const REQUIRED = ["cpu-reentry", "duplicate-source-proof"]; + +function mutationReceipt(overrides = {}) { + return { + commit: COMMIT, + tree: TREE, + cases: REQUIRED.map((id) => ({ id, status: "passed" })), + ...overrides, + }; +} + +function platformEvidence(overrides = {}) { + return { + failures: [{ + run_id: 77, + platform: "windows", + }], + probes: [{ + failure_run_id: 77, + platform: "windows", + commit: COMMIT, + tree: TREE, + status: "passed", + duration_seconds: 5, + mutation: "junction replacement", + }], + ...overrides, + }; +} + +function receipt(overrides = {}) { + const candidate = { + schema: 1, + repository: "TheGreenCedar/CodeStory", + branch: "codex/release", + commit: COMMIT, + tree: TREE, + worktree_clean: true, + remote_head: COMMIT, + release_pr: { + number: 1597, + base: "dev/codestory-next", + head: "codex/release", + head_commit: COMMIT, + }, + integrated_support_prs: [], + known_future_source_changes: [ + "crates/codestory-llama-sys/per-user-embedding-server-constant-set.json", + ], + planned_proof_actions: ["source-proof", "calibration", "qualification"], + hostile_mutations: mutationReceipt(), + platform_evidence: platformEvidence(), + reusable_evidence: [], + invalidated_evidence: [], + running_workflows: [], + cancelled_superseded_runs: [], + next_permitted_mutation: "generated constant set only", + ...overrides, + }; + candidate.digest = receiptDigest(candidate); + return candidate; +} + +test("an exact clean pushed receipt with hostile and native evidence passes", () => { + validateReceipt(receipt(), { + commit: COMMIT, + tree: TREE, + requiredMutationIds: REQUIRED, + }); +}); + +for (const [name, mutate, pattern] of [ + ["later commit", (value) => { value.commit = "3".repeat(40); }, /exact commit and tree/u], + ["later tree", (value) => { value.tree = "4".repeat(40); }, /exact commit and tree/u], + ["dirty worktree", (value) => { value.worktree_clean = false; }, /clean worktree/u], + ["unpushed head", (value) => { value.remote_head = "5".repeat(40); }, /clean worktree/u], + ["moved release PR", (value) => { + value.release_pr.head_commit = "5".repeat(40); + }, /bind the open release PR/u], + ["undeclared source change", (value) => { + value.known_future_source_changes.push(".github/workflows/release.yml"); + }, /unsupported future source change/u], + ["missing handoff field", (value) => { delete value.running_workflows; }, /running_workflows/u], + ["missing next mutation", (value) => { value.next_permitted_mutation = ""; }, /next permitted mutation/u], + ["mutation from another head", (value) => { + value.hostile_mutations.commit = "6".repeat(40); + }, /exact frozen commit and tree/u], + ["named mutation not run", (value) => { + value.hostile_mutations.cases[0].status = "skipped"; + }, /did not pass/u], + ["native probe from another head", (value) => { + value.platform_evidence.probes[0].commit = "7".repeat(40); + }, /native probe under 90 seconds/u], + ["native probe at 90 seconds", (value) => { + value.platform_evidence.probes[0].duration_seconds = 90; + }, /native probe under 90 seconds/u], + ["tampered receipt", (value) => { + value.planned_proof_actions.push("second-source-proof"); + }, /digest/u], +]) { + test(`freeze barrier rejects ${name}`, () => { + const candidate = receipt(); + mutate(candidate); + if (name !== "tampered receipt") { + candidate.digest = receiptDigest(candidate); + } + assert.throws( + () => validateReceipt(candidate, { + commit: COMMIT, + tree: TREE, + requiredMutationIds: REQUIRED, + }), + pattern, + ); + }); +} + +test("mutation and native evidence validators reject malformed arrays", () => { + assert.throws( + () => validateMutationReceipt({ commit: COMMIT, tree: TREE }, { + commit: COMMIT, + tree: TREE, + requiredIds: REQUIRED, + }), + /contain cases/u, + ); + assert.throws( + () => validatePlatformEvidence({ failures: {}, probes: [] }, { + commit: COMMIT, + tree: TREE, + }), + /failure and probe arrays/u, + ); +}); + +test("verify-file is executable and rejects a later commit", () => { + const root = mkdtempSync(path.join(tmpdir(), "codestory-freeze-")); + const receiptPath = path.join(root, "receipt.json"); + writeFileSync(receiptPath, `${JSON.stringify(receipt(), null, 2)}\n`); + const script = new URL("./release-freeze-barrier.mjs", import.meta.url); + const accepted = spawnSync( + process.execPath, + [ + script.pathname, + "verify-file", + "--receipt", + receiptPath, + "--commit", + COMMIT, + "--tree", + TREE, + "--required-mutation", + REQUIRED[0], + "--required-mutation", + REQUIRED[1], + ], + { encoding: "utf8" }, + ); + assert.equal(accepted.status, 0, accepted.stderr); + assert.equal(accepted.stdout.trim(), receipt().digest); + + const rejected = spawnSync( + process.execPath, + [ + script.pathname, + "verify-file", + "--receipt", + receiptPath, + "--commit", + "8".repeat(40), + "--tree", + TREE, + ], + { encoding: "utf8" }, + ); + assert.notEqual(rejected.status, 0); + assert.match(rejected.stderr, /exact commit and tree/u); +}); + +test("declare rejects a dirty worktree before publishing a status", () => { + const root = mkdtempSync(path.join(tmpdir(), "codestory-freeze-repo-")); + execFileSync("git", ["init", "-q", root]); + execFileSync("git", ["-C", root, "config", "user.email", "test@example.com"]); + execFileSync("git", ["-C", root, "config", "user.name", "Test"]); + writeFileSync(path.join(root, "tracked.txt"), "one\n"); + execFileSync("git", ["-C", root, "add", "tracked.txt"]); + execFileSync("git", ["-C", root, "commit", "-qm", "initial"]); + writeFileSync(path.join(root, "untracked.txt"), "dirty\n"); + + const script = new URL("./release-freeze-barrier.mjs", import.meta.url); + const result = spawnSync( + process.execPath, + [ + script.pathname, + "declare", + "--repo", + root, + "--repository", + "TheGreenCedar/CodeStory", + "--release-pr", + "1", + "--output", + path.join(root, "receipt.json"), + "--mutation-receipt", + path.join(root, "missing-mutations.json"), + "--platform-evidence", + path.join(root, "missing-platform.json"), + "--next-permitted-mutation", + "none", + "--required-mutation", + "cpu-reentry", + "--planned-proof-action", + "source-proof", + "--broad-workflow", + "Exact-head source proof", + "--no-publish-status", + ], + { encoding: "utf8" }, + ); + assert.notEqual(result.status, 0); + assert.match(result.stderr, /clean worktree, including untracked files/u); +}); diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index be1b5528c..d07cdc9d5 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -27,7 +27,7 @@ permissions: concurrency: group: auto-release-${{ github.ref }} - cancel-in-progress: false + cancel-in-progress: true jobs: detect-version: @@ -61,7 +61,7 @@ jobs: needs: detect-version if: needs.detect-version.outputs.should_release == 'true' && needs.detect-version.outputs.release_lane == 'native' permissions: - actions: read + actions: write # This is the lane that actually publishes releases, so it is the lane whose token has to be # able to read the lost-runner annotation. A called workflow cannot widen the caller's grant. checks: read diff --git a/.github/workflows/linux-vulkan-proof.yml b/.github/workflows/linux-vulkan-proof.yml index 156dcdedb..7ea477072 100644 --- a/.github/workflows/linux-vulkan-proof.yml +++ b/.github/workflows/linux-vulkan-proof.yml @@ -45,47 +45,8 @@ on: required: false default: false type: boolean - workflow_dispatch: - inputs: - version: - required: true - type: string - ref: - required: true - type: string - proof_key: - required: false - type: string - package_run_id: - description: Upstream packaged-platform run containing codestory-cli-linux-x64; omitted for standalone constant calibration. - required: false - default: "" - type: string - calibration_bundle_artifact: - required: false - default: "" - type: string - calibration_bundle_run_id: - required: false - default: "" - type: string - candidate_installed_proof: - description: Install and prove the exact package through the candidate-managed launcher boundary. - required: false - default: true - type: boolean - candidate_producer_workflow_path: - description: Top-level workflow path authenticated as the candidate artifact producer. - required: false - default: ".github/workflows/packaged-platform-pr.yml" - type: string - server_behavior_only: - description: Prove bounded package retrieval readiness without answer-quality or performance claims. - required: false - default: true - type: boolean constant_calibration_mode: - description: Collect optional Linux Vulkan constant-calibration evidence without feeding the frozen bundle. + description: Collect optional Linux Vulkan calibration evidence without feeding assembly. required: false default: false type: boolean @@ -662,7 +623,7 @@ jobs: retention-days: 30 optional-constant-calibration: - if: ${{ github.event_name == 'workflow_dispatch' && inputs.constant_calibration_mode }} + if: ${{ inputs.constant_calibration_mode }} needs: route name: Optional Linux Vulkan constant calibration runs-on: [self-hosted, Linux, X64, codestory-linux-vulkan] @@ -728,10 +689,11 @@ jobs: shell: bash env: CODESTORY_EMBED_ALLOW_CPU: "0" + CONSTANT_CALIBRATION_MODE: ${{ inputs.constant_calibration_mode }} INPUT_VERSION: ${{ inputs.version }} run: | set -euo pipefail - test "$GITHUB_EVENT_NAME" = workflow_dispatch + test "$CONSTANT_CALIBRATION_MODE" = true test "$(jq -r .status crates/codestory-llama-sys/per-user-embedding-server-constant-set.json)" = unfrozen version="${INPUT_VERSION#v}" source_sha="$(git rev-parse HEAD)" diff --git a/.github/workflows/macos-metal-proof.yml b/.github/workflows/macos-metal-proof.yml index d7b85a6e4..6a865193b 100644 --- a/.github/workflows/macos-metal-proof.yml +++ b/.github/workflows/macos-metal-proof.yml @@ -56,51 +56,6 @@ on: required: false default: false type: boolean - workflow_dispatch: - inputs: - version: - description: CodeStory version to prove. - required: true - type: string - ref: - description: Git ref to check out. Defaults to the current SHA. - required: false - type: string - proof_key: - description: Stable proof identity for cancellation. - required: false - type: string - calibration_bundle_artifact: - description: Frozen calibration bundle artifact name. - required: false - default: "" - type: string - calibration_bundle_run_id: - description: Workflow run that produced the frozen calibration bundle artifact. - required: false - default: "" - type: string - calibration_mode: - description: Collect three independent pre-freeze Metal calibration runs. - required: false - default: false - type: boolean - candidate_installed_proof: - description: Install and prove the exact package through the candidate-managed launcher boundary. - required: false - default: false - type: boolean - candidate_producer_workflow_path: - description: Top-level workflow path authenticated as the candidate artifact producer. - required: false - default: ".github/workflows/macos-metal-proof.yml" - type: string - server_behavior_only: - description: Prove bounded package retrieval readiness without answer-quality or performance claims. - required: false - default: false - type: boolean - permissions: actions: read contents: read diff --git a/.github/workflows/packaged-platform-pr.yml b/.github/workflows/packaged-platform-pr.yml index 64d322a15..08ebe5643 100644 --- a/.github/workflows/packaged-platform-pr.yml +++ b/.github/workflows/packaged-platform-pr.yml @@ -1,8 +1,6 @@ name: Platform and integration proof on: - pull_request: - types: [labeled] workflow_dispatch: inputs: mode: @@ -37,19 +35,22 @@ on: description: Workflow run that produced the frozen calibration bundle artifact. required: false type: string + freeze_receipt_digest: + description: Digest emitted by release-freeze-barrier.mjs for the accepted source head. + required: true + type: string permissions: - actions: read + actions: write contents: read pull-requests: read concurrency: - group: proof-${{ github.sha }}-${{ inputs.mode || 'platform' }}-${{ inputs.pr_number || github.event.pull_request.number || 'dev' }}-${{ github.event.action == 'labeled' && github.event.label.name || 'dispatch' }} + group: proof-${{ github.sha }}-${{ inputs.mode || 'platform' }}-${{ inputs.pr_number || 'dev' }} cancel-in-progress: true jobs: route: - if: github.event_name != 'pull_request' || (github.event.action == 'labeled' && github.event.label.name == 'platform-proof') runs-on: ubuntu-latest timeout-minutes: 10 outputs: @@ -57,6 +58,7 @@ jobs: base_sha: ${{ steps.resolve.outputs.base_sha }} mode: ${{ steps.resolve.outputs.mode }} proof_key: ${{ steps.resolve.outputs.proof_key }} + source_proof_sha: ${{ steps.source-head.outputs.sha }} scope: ${{ steps.scope.outputs.scope }} version: ${{ steps.version.outputs.version }} steps: @@ -165,36 +167,28 @@ jobs: echo "proof_key=$mode-pr-$pr_number-$current_head" } >> "$GITHUB_OUTPUT" - - name: Require successful exact-head source proof - if: steps.resolve.outputs.mode != 'integration' + - name: Checkout accepted candidate + uses: actions/checkout@v5 + with: + ref: ${{ steps.resolve.outputs.head_sha }} + fetch-depth: 0 + + - name: Cancel superseded proof runs shell: bash env: GH_TOKEN: ${{ github.token }} HEAD_SHA: ${{ steps.resolve.outputs.head_sha }} run: | - set -euo pipefail - accepted=false - while IFS= read -r run_id; do - if gh api "repos/$GITHUB_REPOSITORY/actions/runs/$run_id/jobs?per_page=100" \ - --jq '.jobs[] | select(.name == "full-source-gate" and .conclusion == "success") | .id' \ - | grep -q . - then - accepted=true - echo "Accepted exact-head source proof run $run_id for $HEAD_SHA." - break - fi - done < <( - gh api --paginate \ - "repos/$GITHUB_REPOSITORY/actions/runs?head_sha=$HEAD_SHA&status=completed&per_page=100" \ - | jq -r --arg repo "$GITHUB_REPOSITORY" \ - '.workflow_runs[] | select(.path == ".github/workflows/source-proof.yml" and .head_repository.full_name == $repo and (.event == "pull_request" or .event == "workflow_dispatch") and .conclusion == "success") | .id' - ) - test "$accepted" = true || { - echo "::error::No successful full-source-gate job exists for exact head $HEAD_SHA." - exit 1 - } + node .github/scripts/release-freeze-barrier.mjs cancel-superseded \ + --repository "$GITHUB_REPOSITORY" \ + --commit "$HEAD_SHA" \ + --broad-workflow "Exact-head source proof" \ + --broad-workflow "Platform and integration proof" \ + --broad-workflow "Release" \ + --broad-workflow "Auto Release" - name: Authenticate calibration bundle producer + id: calibration if: inputs.calibration_bundle_artifact != '' || inputs.calibration_bundle_run_id != '' shell: bash env: @@ -211,6 +205,7 @@ jobs: test "$(jq -r '.event' <<<"$run")" = workflow_dispatch test "$(jq -r '.conclusion' <<<"$run")" = success test "$CALIBRATION_ARTIFACT" = "embedding-calibration-bundle-$producer_sha" + echo "source_sha=$producer_sha" >> "$GITHUB_OUTPUT" artifact_count="$( gh api "repos/$GITHUB_REPOSITORY/actions/runs/$CALIBRATION_RUN_ID/artifacts?per_page=100" \ | jq --arg name "$CALIBRATION_ARTIFACT" \ @@ -218,10 +213,73 @@ jobs: )" test "$artifact_count" = 1 - - uses: actions/checkout@v5 - with: - ref: ${{ steps.resolve.outputs.head_sha }} - fetch-depth: 0 + - name: Resolve accepted source proof head + id: source-head + shell: bash + env: + CALIBRATION_SOURCE_SHA: ${{ steps.calibration.outputs.source_sha }} + HEAD_SHA: ${{ steps.resolve.outputs.head_sha }} + MODE: ${{ steps.resolve.outputs.mode }} + run: | + set -euo pipefail + if [ "$MODE" = qualification ]; then + test -n "$CALIBRATION_SOURCE_SHA" + echo "sha=$CALIBRATION_SOURCE_SHA" >> "$GITHUB_OUTPUT" + else + echo "sha=$HEAD_SHA" >> "$GITHUB_OUTPUT" + fi + + - name: Require executable release freeze + shell: bash + env: + GH_TOKEN: ${{ github.token }} + FREEZE_RECEIPT_DIGEST: ${{ inputs.freeze_receipt_digest }} + SOURCE_SHA: ${{ steps.source-head.outputs.sha }} + run: | + set -euo pipefail + printf '%s' "$FREEZE_RECEIPT_DIGEST" | grep -Eq '^[0-9a-f]{64}$' + tree="$( + gh api "repos/$GITHUB_REPOSITORY/git/commits/$SOURCE_SHA" --jq '.tree.sha' + )" + gh api "repos/$GITHUB_REPOSITORY/commits/$SOURCE_SHA/statuses?per_page=100" \ + | jq -e \ + --arg context "codestory/release-freeze/$FREEZE_RECEIPT_DIGEST" \ + --arg description "tree=$tree" \ + 'any(.[]; .state == "success" + and .context == $context + and .description == $description)' >/dev/null || { + echo "::error::No executable release freeze accepts source head $SOURCE_SHA and tree $tree." + exit 1 + } + + - name: Require successful accepted-head source proof + if: steps.resolve.outputs.mode != 'integration' + shell: bash + env: + GH_TOKEN: ${{ github.token }} + SOURCE_SHA: ${{ steps.source-head.outputs.sha }} + run: | + set -euo pipefail + accepted=false + while IFS= read -r run_id; do + if gh api "repos/$GITHUB_REPOSITORY/actions/runs/$run_id/jobs?per_page=100" \ + --jq '.jobs[] | select(.name == "full-source-gate" and .conclusion == "success") | .id' \ + | grep -q . + then + accepted=true + echo "Accepted source proof run $run_id for frozen source head $SOURCE_SHA." + break + fi + done < <( + gh api --paginate \ + "repos/$GITHUB_REPOSITORY/actions/runs?head_sha=$SOURCE_SHA&status=completed&per_page=100" \ + | jq -r --arg repo "$GITHUB_REPOSITORY" \ + '.workflow_runs[] | select(.path == ".github/workflows/source-proof.yml" and .head_repository.full_name == $repo and .event == "workflow_dispatch" and .conclusion == "success") | .id' + ) + test "$accepted" = true || { + echo "::error::No successful full-source-gate job exists for accepted source head $SOURCE_SHA." + exit 1 + } - name: Select change-aware proof scope id: scope @@ -381,6 +439,7 @@ jobs: with: ref: ${{ needs.route.outputs.head_sha }} proof_key: ${{ needs.route.outputs.proof_key }} + freeze_receipt_digest: ${{ inputs.freeze_receipt_digest }} packaged-proof: if: >- diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f6a98e50c..2924a2e40 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,6 +17,11 @@ on: required: false type: boolean default: false + freeze_receipt_digest: + description: "Exact-head release freeze receipt digest; publishing may discover it from the calibrated source" + required: false + type: string + default: "" workflow_dispatch: inputs: version: @@ -27,9 +32,13 @@ on: description: "Exact dev/codestory-next head to authenticate without publishing" required: true type: string + freeze_receipt_digest: + description: "Digest emitted by release-freeze-barrier.mjs for the accepted source head" + required: true + type: string permissions: - actions: read + actions: write # accelerator-non-claim reads the job annotation that identifies a lost runner, which the Actions # annotations endpoint gates on `checks: read`. Without it the collector fails closed and this # workflow stops rather than mistaking an unreadable signature for an ordinary failure. @@ -39,7 +48,7 @@ permissions: concurrency: group: release-${{ inputs.version }} - cancel-in-progress: false + cancel-in-progress: true jobs: workflow-policy: @@ -87,6 +96,7 @@ jobs: version: ${{ steps.version.outputs.version }} reuse: ${{ steps.reuse.outputs.reuse }} source_proof_reused: ${{ steps.reuse.outputs.source_proof_reused }} + freeze_receipt_digest: ${{ steps.reuse.outputs.freeze_receipt_digest }} tag: ${{ steps.version.outputs.tag }} marketplace_revision: ${{ steps.marketplace.outputs.marketplace_revision }} steps: @@ -95,16 +105,47 @@ jobs: with: fetch-depth: 0 + - name: Cancel superseded proof runs + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + node .github/scripts/release-freeze-barrier.mjs cancel-superseded \ + --repository "$GITHUB_REPOSITORY" \ + --commit "$GITHUB_SHA" \ + --broad-workflow "Exact-head source proof" \ + --broad-workflow "Platform and integration proof" \ + --broad-workflow "Release" \ + --broad-workflow "Auto Release" + - name: Verify release-head calibration lineage + id: lineage env: BASH_ENV: /dev/null + PUBLISH_RELEASE: ${{ inputs.publish_release }} shell: /bin/bash --noprofile --norc -e -o pipefail {0} working-directory: ${{ github.workspace }} - run: >- - /usr/bin/python3 -E -s - "$GITHUB_WORKSPACE/.github/scripts/check-calibration-release-lineage.py" - --repo "$GITHUB_WORKSPACE" - --expected-sha "$GITHUB_SHA" + run: | + promotion_args=() + if [ "$PUBLISH_RELEASE" = true ]; then + promotion_args+=(--allow-promotion-commit) + fi + result="$( + /usr/bin/python3 -E -s \ + "$GITHUB_WORKSPACE/.github/scripts/check-calibration-release-lineage.py" \ + --repo "$GITHUB_WORKSPACE" \ + --expected-sha "$GITHUB_SHA" \ + "${promotion_args[@]}" + )" + jq -e '.status == "passed"' <<<"$result" >/dev/null + selection_commit="$(jq -r '.selection_commit' <<<"$result")" + selection_tree="$(jq -r '.selection_tree' <<<"$result")" + printf '%s' "$selection_commit" | grep -Eq '^[0-9a-f]{40}$' + printf '%s' "$selection_tree" | grep -Eq '^[0-9a-f]{40}$' + { + echo "selection_commit=$selection_commit" + echo "selection_tree=$selection_tree" + } >> "$GITHUB_OUTPUT" - name: Validate release authority env: @@ -180,39 +221,68 @@ jobs: id: reuse env: GH_TOKEN: ${{ github.token }} + INPUT_FREEZE_RECEIPT_DIGEST: ${{ inputs.freeze_receipt_digest }} + SOURCE_SHA: ${{ steps.lineage.outputs.selection_commit }} + SOURCE_TREE: ${{ steps.lineage.outputs.selection_tree }} shell: bash run: | set -euo pipefail entries=() - # The source gate proves a tree. When dev was already gated and promoted without - # changing the tree, re-running it for an hour cannot reach a different answer. - release_tree="$(git rev-parse "$GITHUB_SHA^{tree}")" + # The sole generated constant-set commit is validated by deterministic selection, + # exact lineage, and frozen-candidate qualification. Re-running the workspace on + # the descendant would be a second source proof for the same candidate. while IFS= read -r run_id; do - head_sha="$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$run_id" --jq .head_sha)" - git cat-file -e "$head_sha^{commit}" 2>/dev/null || continue - test "$(git rev-parse "$head_sha^{tree}")" = "$release_tree" || continue - git merge-base --is-ancestor "$head_sha" "$GITHUB_SHA" || continue gh api "repos/$GITHUB_REPOSITORY/actions/runs/$run_id/jobs?per_page=100" \ - --jq '.jobs[] | select(.name | endswith("full-source-gate")) | select(.conclusion == "success") | .id' \ + --jq '.jobs[] | select(.name == "full-source-gate" and .conclusion == "success") | .id' \ | grep -q . || continue - entries+=("source_behavior=$run_id:$head_sha") - echo "Reusing source proof from run $run_id (tree $release_tree)." + entries+=("source_behavior=$run_id:$SOURCE_SHA") + echo "Reusing the one source proof from run $run_id at calibration source $SOURCE_SHA." break done < <( gh api --paginate \ - "repos/$GITHUB_REPOSITORY/actions/runs?status=completed&per_page=100" \ + "repos/$GITHUB_REPOSITORY/actions/runs?head_sha=$SOURCE_SHA&status=completed&per_page=100" \ | jq -r --arg repo "$GITHUB_REPOSITORY" \ - '.workflow_runs[] | select(.path == ".github/workflows/source-proof.yml" and .head_repository.full_name == $repo and .conclusion == "success") | .id' + '.workflow_runs[] | select(.path == ".github/workflows/source-proof.yml" and .head_repository.full_name == $repo and .event == "workflow_dispatch" and .conclusion == "success") | .id' ) reuse="$(IFS=,; echo "${entries[*]:-}")" - echo "reuse=${reuse:--}" >> "$GITHUB_OUTPUT" - if [ -n "$reuse" ]; then - echo "source_proof_reused=true" >> "$GITHUB_OUTPUT" + test -n "$reuse" || { + echo "::error::The accepted calibration source $SOURCE_SHA has no successful full-source-gate. The release workflow will not start a second proof after calibration." + exit 1 + } + + statuses="$( + gh api "repos/$GITHUB_REPOSITORY/commits/$SOURCE_SHA/statuses?per_page=100" + )" + if [ -n "$INPUT_FREEZE_RECEIPT_DIGEST" ]; then + freeze_digest="$INPUT_FREEZE_RECEIPT_DIGEST" else - echo "source_proof_reused=false" >> "$GITHUB_OUTPUT" + freeze_digest="$( + jq -r \ + --arg description "tree=$SOURCE_TREE" \ + '[.[] | select( + .state == "success" + and (.context | startswith("codestory/release-freeze/")) + and .description == $description + ) | .context | sub("^codestory/release-freeze/"; "")] | unique | if length == 1 then .[0] else "" end' \ + <<<"$statuses" + )" fi + printf '%s' "$freeze_digest" | grep -Eq '^[0-9a-f]{64}$' + jq -e \ + --arg context "codestory/release-freeze/$freeze_digest" \ + --arg description "tree=$SOURCE_TREE" \ + 'any(.[]; .state == "success" + and .context == $context + and .description == $description)' \ + <<<"$statuses" >/dev/null + + { + echo "reuse=$reuse" + echo "source_proof_reused=true" + echo "freeze_receipt_digest=$freeze_digest" + } >> "$GITHUB_OUTPUT" - name: Prove the public marketplace install path if: inputs.publish_release @@ -267,8 +337,8 @@ jobs: source-proof: needs: preflight - # A completed gate for this exact tree is already authenticated evidence; the closeout - # consumes it through the reuse binding instead of re-running an hour of compilation. + # Preflight fails unless the one pre-calibration source proof is reusable. This job is a + # structural DAG placeholder for closeout compatibility and must remain unreachable. if: needs.preflight.outputs.source_proof_reused != 'true' uses: ./.github/workflows/source-proof.yml with: @@ -276,6 +346,7 @@ jobs: proof_key: release-${{ needs.preflight.outputs.version }} version: ${{ needs.preflight.outputs.version }} emit_release_cells: true + freeze_receipt_digest: ${{ needs.preflight.outputs.freeze_receipt_digest }} packaged-proof: needs: preflight diff --git a/.github/workflows/source-proof.yml b/.github/workflows/source-proof.yml index a231ee2c9..2a5dea4e4 100644 --- a/.github/workflows/source-proof.yml +++ b/.github/workflows/source-proof.yml @@ -1,8 +1,6 @@ name: Exact-head source proof on: - pull_request: - types: [labeled] workflow_call: inputs: ref: @@ -19,6 +17,10 @@ on: required: false default: false type: boolean + freeze_receipt_digest: + description: Digest of the exact-head release freeze status. + required: true + type: string workflow_dispatch: inputs: pr_number: @@ -29,14 +31,27 @@ on: description: Exact reviewed head SHA. The selected --ref, github.sha, and live PR head must match. required: true type: string + freeze_receipt_digest: + description: Digest emitted by release-freeze-barrier.mjs for this exact head. + required: true + type: string + version: + description: Release version whose source cell this accepted proof emits. + required: true + type: string + emit_release_cells: + description: Emit the source cell that qualification and publication reuse. + required: false + default: true + type: boolean permissions: - actions: read + actions: write contents: read pull-requests: read concurrency: - group: source-proof-${{ github.sha }}-${{ inputs.proof_key || inputs.pr_number || github.event.pull_request.number || github.ref }}-${{ github.event.action == 'labeled' && github.event.label.name || 'dispatch' }} + group: source-proof-${{ github.sha }}-${{ inputs.proof_key || inputs.pr_number || github.ref }} cancel-in-progress: true env: @@ -46,7 +61,6 @@ env: jobs: resolve: - if: github.event_name != 'pull_request' || (github.event.action == 'labeled' && github.event.label.name == 'review-accepted') runs-on: ubuntu-latest timeout-minutes: 10 outputs: @@ -116,11 +130,27 @@ jobs: echo "ref=$CALLER_REF" >> "$GITHUB_OUTPUT" fi + - name: Checkout accepted source head + uses: actions/checkout@v5 + with: + ref: ${{ steps.resolve.outputs.ref }} + + - name: Cancel superseded proof runs + shell: bash + env: + GH_TOKEN: ${{ github.token }} + HEAD_SHA: ${{ steps.resolve.outputs.ref }} + run: | + node .github/scripts/release-freeze-barrier.mjs cancel-superseded \ + --repository "$GITHUB_REPOSITORY" \ + --commit "$HEAD_SHA" \ + --broad-workflow "Exact-head source proof" \ + --broad-workflow "Platform and integration proof" \ + --broad-workflow "Release" \ + --broad-workflow "Auto Release" + - name: Reuse a completed gate for this exact head id: reuse - # Only the label and dispatch paths. workflow_call always supplies `ref`, and the release - # chain requires full-source-gate to actually run. - if: inputs.ref == '' shell: bash env: GH_TOKEN: ${{ github.token }} @@ -142,10 +172,33 @@ jobs: gh api --paginate \ "repos/$GITHUB_REPOSITORY/actions/runs?head_sha=$HEAD_SHA&status=completed&per_page=100" \ | jq -r --arg repo "$GITHUB_REPOSITORY" \ - '.workflow_runs[] | select(.path == ".github/workflows/source-proof.yml" and .head_repository.full_name == $repo and (.event == "pull_request" or .event == "workflow_dispatch") and .conclusion == "success") | .id' + '.workflow_runs[] | select(.path == ".github/workflows/source-proof.yml" and .head_repository.full_name == $repo and .event == "workflow_dispatch" and .conclusion == "success") | .id' ) echo "reuse=$reuse" >> "$GITHUB_OUTPUT" + - name: Require executable release freeze + shell: bash + env: + GH_TOKEN: ${{ github.token }} + FREEZE_RECEIPT_DIGEST: ${{ inputs.freeze_receipt_digest }} + HEAD_SHA: ${{ steps.resolve.outputs.ref }} + run: | + set -euo pipefail + printf '%s' "$FREEZE_RECEIPT_DIGEST" | grep -Eq '^[0-9a-f]{64}$' + tree="$( + gh api "repos/$GITHUB_REPOSITORY/git/commits/$HEAD_SHA" --jq '.tree.sha' + )" + gh api "repos/$GITHUB_REPOSITORY/commits/$HEAD_SHA/statuses?per_page=100" \ + | jq -e \ + --arg context "codestory/release-freeze/$FREEZE_RECEIPT_DIGEST" \ + --arg description "tree=$tree" \ + 'any(.[]; .state == "success" + and .context == $context + and .description == $description)' >/dev/null || { + echo "::error::No executable release freeze accepts exact head $HEAD_SHA and tree $tree." + exit 1 + } + full-source-gate: name: full-source-gate needs: resolve diff --git a/.github/workflows/windows-vulkan-proof.yml b/.github/workflows/windows-vulkan-proof.yml index 07198f264..945d124fb 100644 --- a/.github/workflows/windows-vulkan-proof.yml +++ b/.github/workflows/windows-vulkan-proof.yml @@ -44,41 +44,6 @@ on: required: false default: false type: boolean - workflow_dispatch: - inputs: - version: - required: true - type: string - ref: - required: false - type: string - proof_key: - required: false - type: string - calibration_bundle_artifact: - required: false - default: "" - type: string - calibration_bundle_run_id: - required: false - default: "" - type: string - candidate_installed_proof: - description: Install and prove the exact package through the candidate-managed launcher boundary. - required: false - default: false - type: boolean - candidate_producer_workflow_path: - description: Top-level workflow path authenticated as the candidate artifact producer. - required: false - default: ".github/workflows/windows-vulkan-proof.yml" - type: string - server_behavior_only: - description: Prove bounded package retrieval readiness without answer-quality or performance claims. - required: false - default: false - type: boolean - permissions: actions: read contents: read diff --git a/AGENTS.md b/AGENTS.md index 395f8e149..0a6213214 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -120,8 +120,8 @@ adapter to compensate for incorrect upstream state. lanes. - Do not use `cargo test --workspace --all-targets` as the routine broad gate; it expands Criterion targets. Draft work uses focused checks. The full - workspace test and all-target/all-feature clippy gate run once on an - independently accepted exact head. + workspace test and all-target/all-feature clippy gate run once on the source + head accepted by the executable release freeze barrier. - CLI integration tests must launch through `tests/test_support::cli_command` or its supplied-binary variant, use isolated cache/install/plugin state roots. @@ -160,6 +160,9 @@ adapter to compensate for incorrect upstream state. saga label) must close a PR-sized issue with `Closes`, `Fixes`, or `Resolves`. Use `Refs` for broader parents. A partial slice closes only its child issue; keep the parent open until its acceptance criteria are met. +- Before creating an issue, branch, worktree, or PR, search open and closed + issues, merged PRs, and integration history for the requested outcome, then + prove that outcome is absent from the current integration head. - For PRs targeting `dev/codestory-next`, add both the issue and PR to the Project; computed linked-PR fields may not populate before default-branch promotion. @@ -170,6 +173,9 @@ adapter to compensate for incorrect upstream state. - PRs should explain context, what changed, how to review, verification, risk, and follow-up. Include exact SHAs and distinguish completed proof from non-claims. +- Release handoffs must name the final intended source head, known future + source changes, proof-triggering labels or actions, reusable and invalidated + evidence, currently running workflows, and the next permitted mutation. - Public GitHub status comments must use `node scripts/github-status-comment.mjs --issue --body-file ` or stdin; the helper rejects literal `\\n` text. @@ -186,6 +192,42 @@ adapter to compensate for incorrect upstream state. ## Release Rules +### Candidate freeze and proof budget + +- Before any gate expected to exceed five minutes, record the exact commit and + tree, confirm the worktree is clean and pushed, and confirm that every + planned source or workflow change is already merged. Independent acceptance + must execute the required hostile mutations on that exact head; diff review + and existing green tests do not qualify. Any later commit revokes + acceptance. +- Support PRs use focused checks only. Do not add a proof-triggering label or + dispatch a broad source, package, calibration, or hardware gate until all + support PRs are integrated into the release lane. Broad proof belongs to the + final integration head, not every independently mergeable PR. +- Release order is: merge all blockers, run focused checks, run actual-host + microprobes, execute hostile mutation acceptance, push and declare the source + head frozen, run one broad source proof, calibrate, apply the sole generated + constant-set change, then qualify. If another source or workflow change + becomes necessary, immediately invalidate the candidate and cancel every + queued or running proof for it. +- Run the full workspace source proof exactly once per release candidate. For + a sole generated constant-set freeze, use deterministic selection + validation, lineage verification, and frozen-candidate qualification; do not + rerun the full workspace. If policy cannot reuse the pre-calibration proof, + move the one broad proof to the frozen head instead. Never run both. +- Cancel a run whose head is no longer the intended release candidate. Never + let an expensive obsolete run finish for information. Before dispatching, + inspect both in-flight runs and whether any known source change will + invalidate the result. +- After a platform-specific packaging or filesystem failure, do not run a full + rebuild until a sub-90-second native probe reproduces the relevant path, + link, staging, cache, or identity behavior on that operating system. Test the + selector against the probe or captured artifact first. +- Use one implementer and one adversarial verifier. Give the verifier the exact + mutation matrix and only the context needed to execute it. Its output is + limited to counterexamples or acceptance evidence. After two failed + revisions of the same shape, stop patching examples and redesign the seam. + - Freeze the selected release claim before qualification. For the standard v0.16 release described in `CHANGELOG.md`, build one candidate; install its exact archives on Apple Silicon macOS, Windows x64, and Linux x64; complete diff --git a/benchmarks/release-evidence/fixtures/candidate.json b/benchmarks/release-evidence/fixtures/candidate.json index ada3b0f49..dc6a7b1cf 100644 --- a/benchmarks/release-evidence/fixtures/candidate.json +++ b/benchmarks/release-evidence/fixtures/candidate.json @@ -62,7 +62,7 @@ }, "release_claims": { "graph_schema": "codestory.release-claims/v1", - "graph_sha256": "54a9a45c1df1c2a738da405f9bca7120cb75d422bb94e9b05cd8cf4f0c3c247b", + "graph_sha256": "74b22ad4023161484dcd9a5ef141b9825456f95137434a2ea3a0ba8ef515d364", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "requested_claims": [ @@ -85,7 +85,7 @@ "type": "performance", "tier": "live_behavior", "status": "measured", - "graph_sha256": "54a9a45c1df1c2a738da405f9bca7120cb75d422bb94e9b05cd8cf4f0c3c247b", + "graph_sha256": "74b22ad4023161484dcd9a5ef141b9825456f95137434a2ea3a0ba8ef515d364", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { @@ -106,7 +106,7 @@ "type": "answer_quality", "tier": "answer_quality", "status": "pass", - "graph_sha256": "54a9a45c1df1c2a738da405f9bca7120cb75d422bb94e9b05cd8cf4f0c3c247b", + "graph_sha256": "74b22ad4023161484dcd9a5ef141b9825456f95137434a2ea3a0ba8ef515d364", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { diff --git a/benchmarks/release-evidence/fixtures/report.json b/benchmarks/release-evidence/fixtures/report.json index a2644f89d..0a89b8383 100644 --- a/benchmarks/release-evidence/fixtures/report.json +++ b/benchmarks/release-evidence/fixtures/report.json @@ -26,7 +26,7 @@ "type": "performance", "tier": "live_behavior", "status": "pass", - "graph_sha256": "54a9a45c1df1c2a738da405f9bca7120cb75d422bb94e9b05cd8cf4f0c3c247b", + "graph_sha256": "74b22ad4023161484dcd9a5ef141b9825456f95137434a2ea3a0ba8ef515d364", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { @@ -47,7 +47,7 @@ "type": "answer_quality", "tier": "answer_quality", "status": "pass", - "graph_sha256": "54a9a45c1df1c2a738da405f9bca7120cb75d422bb94e9b05cd8cf4f0c3c247b", + "graph_sha256": "74b22ad4023161484dcd9a5ef141b9825456f95137434a2ea3a0ba8ef515d364", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { @@ -69,7 +69,7 @@ "schema": "codestory.release-claim-evaluation/v1", "status": "pass", "graph_schema": "codestory.release-claims/v1", - "graph_sha256": "54a9a45c1df1c2a738da405f9bca7120cb75d422bb94e9b05cd8cf4f0c3c247b", + "graph_sha256": "74b22ad4023161484dcd9a5ef141b9825456f95137434a2ea3a0ba8ef515d364", "evidence_selection": "all_matching_rows_must_pass", "expected_commit": "2222222222222222222222222222222222222222", "evaluated_at": "2026-07-21T02:13:20.738Z", diff --git a/release-claims.json b/release-claims.json index a05466aae..1c399b1c4 100644 --- a/release-claims.json +++ b/release-claims.json @@ -117,8 +117,13 @@ "equation": "Reuse anchors a row to the earlier run and commit it was produced by, so the commit identity is read at the release commit for every binding. Any further identity a reused row would otherwise be held to is checked as written unless the binding declares it below. A binding may declare an identity key only when the binding's own construction determines that key for the evidence being inherited, and an equated key is never dropped: the reused row must still carry the reused commit's own value for it, which the binding is what makes admissible in place of this release's.", "bindings": { "source_tree": { - "admits": "Evidence from a prior run is admissible when its producing commit resolves to the identical source tree and is an ancestor of the release commit on the promotion path.", - "equates": [] + "admits": "Source evidence from the accepted calibration commit is admissible on the frozen candidate only when the checked-in freeze record names that exact commit and tree, the sole changed path is the generated constant set, and the frozen candidate is its direct child or has one explicit tree-preserving promotion commit.", + "equates": [ + { + "identity": "source_tree", + "justification": "The source proof ran on the accepted pre-calibration tree. Deterministic constant selection, direct constant-only lineage, and frozen-candidate qualification prove the only later source transition, so closeout may read that measured source row at the frozen candidate tree without running the workspace a second time." + } + ] }, "native_fingerprint": { "admits": "Accelerator evidence from the previous published release is admissible when the reused commit is an ancestor of the release commit and the version-normalized native fingerprint (scripts/native-fingerprint.mjs) of both commits is identical: the built inputs differ only by the embedded version string. Packaging and signing always rerun; only accelerator behavior is inherited.", @@ -1463,13 +1468,48 @@ "manual_pr_ref_hint": "--ref ", "source_cache_namespace": "source-proof-v2", "packaged_cache_namespace": "codestory-cli-native-v4", - "label_routed_workflows": [ + "label_routed_workflows": [], + "required_events": [] + }, + "release_freeze_barrier": { + "schema": 1, + "script": ".github/scripts/release-freeze-barrier.mjs", + "status_context_prefix": "codestory/release-freeze", + "allowed_future_source_changes": [ + "crates/codestory-llama-sys/per-user-embedding-server-constant-set.json" + ], + "required_hostile_mutations": [ + "cpu_backend_rejected", + "calibration_qualification_rejected", + "calibration_three_by_three_rejected", + "calibration_repeated_setup_rejected", + "missing_linux_nonblocking", + "windows_duplicate_build_rejected", + "windows_debug_release_mix_rejected", + "windows_stale_archive_rejected" + ], + "broad_entry_workflows": [ "source-proof.yml", - "packaged-platform-pr.yml" - ], - "required_events": [ - "labeled" - ] + "packaged-platform-pr.yml", + "release.yml" + ], + "coordinator_only_workflows": [ + "macos-metal-proof.yml", + "windows-vulkan-proof.yml", + "linux-vulkan-proof.yml" + ], + "single_source_proof": { + "producer_workflow": "source-proof.yml", + "producer_job": "full-source-gate", + "accepted_source": "freeze_receipt.commit", + "frozen_descendant": "constant_set.freeze_record.selection_source_commit", + "reuse_validation": [ + "deterministic_constant_selection", + "direct_constant_only_lineage", + "frozen_candidate_qualification" + ], + "post_calibration_fallback_allowed": false + } }, "actionlint": { "version": "1.7.12", diff --git a/scripts/codestory-release-claims.mjs b/scripts/codestory-release-claims.mjs index 7e7808bde..f5b4a3c41 100644 --- a/scripts/codestory-release-claims.mjs +++ b/scripts/codestory-release-claims.mjs @@ -183,16 +183,17 @@ export function deriveTrustedGitIdentity({ repoRoot, expectedSha }) { /// and why; this map is the ceiling, stated next to the proofs that establish it, so a graph edit /// alone can never grant an equation no binding proves. /// -/// * `source_tree` proves the reused commit resolves to this release's own tree. Nothing needs -/// substituting: every tree-derived identity a reused row declares is still checkable directly -/// against this release, and equating one would replace a live check with nothing. Hence []. +/// * `source_tree` proves either an identical tree or the one exact constant-set transition from +/// the accepted calibration source. In the latter case it determines both trees and the direct +/// lineage, so the reused source row may retain its measured source_tree while closeout reads it +/// at the frozen candidate's tree. /// * `native_fingerprint` proves the two commits' native build inputs -- crates/**, Cargo.lock, /// vendor/**, the packaging scripts, the toolchain pins, version-normalized -- hash equal. That /// determines the built accelerator, so accelerator execution evidence transfers across the /// source_tree difference the binding exists to tolerate. It determines nothing about the /// repository, the packaged bytes, the host, or the version, so none of those may be equated. const REUSE_BINDING_EQUATABLE_IDENTITY = Object.freeze({ - source_tree: Object.freeze([]), + source_tree: Object.freeze(["source_tree"]), native_fingerprint: Object.freeze(["source_tree"]), }); @@ -218,8 +219,62 @@ export function verifyReuseBinding({ binding, repository, releaseCommit, reusedC if (binding === "source_tree") { const releaseTree = git(["rev-parse", `${releaseCommit}^{tree}`], repository); const reusedTree = git(["rev-parse", `${reusedCommit}^{tree}`], repository); - if (releaseTree !== reusedTree) { - fail(`reused commit ${reusedCommit} tree ${reusedTree} does not match release tree ${releaseTree}`); + if (releaseTree === reusedTree) { + return releaseTree; + } + const constantPath = + "crates/codestory-llama-sys/per-user-embedding-server-constant-set.json"; + let constantSet; + try { + constantSet = JSON.parse(git( + ["show", `${releaseCommit}:${constantPath}`], + repository, + )); + } catch { + fail( + `reused commit ${reusedCommit} tree ${reusedTree} does not match release tree ` + + `${releaseTree}, and the release has no readable calibration freeze`, + ); + } + const freeze = constantSet?.freeze_record; + if ( + constantSet?.status !== "frozen" + || freeze?.selection_source_commit !== reusedCommit + || freeze?.selection_source_tree !== reusedTree + ) { + fail( + `reused commit ${reusedCommit} tree ${reusedTree} does not match release tree ` + + `${releaseTree} or the release calibration source`, + ); + } + const changed = git( + ["diff", "--name-only", reusedCommit, releaseCommit], + repository, + ).split("\n").filter(Boolean); + if (JSON.stringify(changed) !== JSON.stringify([constantPath])) { + fail( + "source proof reuse crosses changes outside the sole generated constant set: " + + changed.join(", "), + ); + } + const parents = (commit) => git( + ["rev-list", "--parents", "-n", "1", commit], + repository, + ).split(/\s+/u).slice(1); + const releaseParents = parents(releaseCommit); + const direct = releaseParents.length === 1 && releaseParents[0] === reusedCommit; + const promotionParents = direct + ? [] + : releaseParents.filter((parent) => + parents(parent).length === 1 + && parents(parent)[0] === reusedCommit + && git(["rev-parse", `${parent}^{tree}`], repository) === releaseTree + ); + if (!direct && promotionParents.length !== 1) { + fail( + "source proof reuse requires the direct generated constant-set child " + + "or one explicit tree-preserving promotion commit", + ); } return releaseTree; } @@ -1495,8 +1550,73 @@ export function validateReleaseClaimGraph(graph) { nonEmptyText(promotion.manual_pr_ref_hint, "workflow_policy.promotion.manual_pr_ref_hint"); nonEmptyText(promotion.source_cache_namespace, "workflow_policy.promotion.source_cache_namespace"); nonEmptyText(promotion.packaged_cache_namespace, "workflow_policy.promotion.packaged_cache_namespace"); - stringArray(promotion.label_routed_workflows, "workflow_policy.promotion.label_routed_workflows", { nonEmpty: true }); - stringArray(promotion.required_events, "workflow_policy.promotion.required_events", { nonEmpty: true }); + const labelRouted = stringArray( + promotion.label_routed_workflows, + "workflow_policy.promotion.label_routed_workflows", + ); + const requiredEvents = stringArray( + promotion.required_events, + "workflow_policy.promotion.required_events", + ); + if (labelRouted.length !== 0 || requiredEvents.length !== 0) { + fail("workflow_policy.promotion must not admit label-routed proof workflows"); + } + + const freeze = object( + policy.release_freeze_barrier, + "workflow_policy.release_freeze_barrier", + ); + if (freeze.schema !== 1) { + fail("workflow_policy.release_freeze_barrier.schema must be 1"); + } + nonEmptyText(freeze.script, "workflow_policy.release_freeze_barrier.script"); + nonEmptyText( + freeze.status_context_prefix, + "workflow_policy.release_freeze_barrier.status_context_prefix", + ); + stringArray( + freeze.allowed_future_source_changes, + "workflow_policy.release_freeze_barrier.allowed_future_source_changes", + { nonEmpty: true }, + ); + stringArray( + freeze.required_hostile_mutations, + "workflow_policy.release_freeze_barrier.required_hostile_mutations", + { nonEmpty: true }, + ); + stringArray( + freeze.broad_entry_workflows, + "workflow_policy.release_freeze_barrier.broad_entry_workflows", + { nonEmpty: true }, + ); + stringArray( + freeze.coordinator_only_workflows, + "workflow_policy.release_freeze_barrier.coordinator_only_workflows", + { nonEmpty: true }, + ); + const singleSource = object( + freeze.single_source_proof, + "workflow_policy.release_freeze_barrier.single_source_proof", + ); + nonEmptyText( + singleSource.producer_workflow, + "workflow_policy.release_freeze_barrier.single_source_proof.producer_workflow", + ); + nonEmptyText( + singleSource.producer_job, + "workflow_policy.release_freeze_barrier.single_source_proof.producer_job", + ); + stringArray( + singleSource.reuse_validation, + "workflow_policy.release_freeze_barrier.single_source_proof.reuse_validation", + { nonEmpty: true }, + ); + if (singleSource.post_calibration_fallback_allowed !== false) { + fail( + "workflow_policy.release_freeze_barrier.single_source_proof " + + "must prohibit a post-calibration fallback", + ); + } const actionlint = object(policy.actionlint, "workflow_policy.actionlint"); if (actionlint.version !== "1.7.12") fail("workflow_policy.actionlint.version must be 1.7.12"); diff --git a/scripts/tests/codestory-release-claims.test.mjs b/scripts/tests/codestory-release-claims.test.mjs index 197b9cdfd..7f0822643 100644 --- a/scripts/tests/codestory-release-claims.test.mjs +++ b/scripts/tests/codestory-release-claims.test.mjs @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import { createHash } from "node:crypto"; import { spawnSync } from "node:child_process"; -import { mkdtempSync, readFileSync, writeFileSync } from "node:fs"; +import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; import test from "node:test"; @@ -152,11 +152,17 @@ test("versioned claim graph has one deterministic digest and all declared contro ], ); assert.ok(graph.claims.every((claim) => claim.prerequisite_checks.every(({ command }) => command.length > 0))); - assert.deepEqual(graph.workflow_policy.promotion.required_events, ["labeled"]); + assert.deepEqual(graph.workflow_policy.promotion.required_events, []); + assert.deepEqual(graph.workflow_policy.promotion.label_routed_workflows, []); assert.equal(graph.workflow_policy.promotion.proof_run_sha_expression, "${{ github.sha }}"); assert.equal(graph.workflow_policy.promotion.manual_pr_ref_hint, "--ref "); assert.equal(graph.workflow_policy.promotion.source_cache_namespace, "source-proof-v2"); assert.equal(graph.workflow_policy.promotion.packaged_cache_namespace, "codestory-cli-native-v4"); + assert.equal( + graph.workflow_policy.release_freeze_barrier + .single_source_proof.post_calibration_fallback_allowed, + false, + ); }); test("claim graph freezes one exact Windows release graph and protected content-addressed reuse", () => { @@ -1162,6 +1168,83 @@ test("reuse bindings verify tree identity and fingerprint equality against real }), /is not an ancestor of the release commit/u, ); + + const fixture = mkdtempSync(path.join(os.tmpdir(), "codestory-source-reuse-")); + const fixtureGit = (...args) => { + const result = spawnSync("git", args, { + cwd: fixture, + encoding: "utf8", + env: { + ...process.env, + GIT_AUTHOR_NAME: "CodeStory Proof", + GIT_AUTHOR_EMAIL: "proof@codestory.invalid", + GIT_COMMITTER_NAME: "CodeStory Proof", + GIT_COMMITTER_EMAIL: "proof@codestory.invalid", + GIT_CONFIG_GLOBAL: os.devNull, + GIT_CONFIG_SYSTEM: os.devNull, + }, + }); + assert.equal(result.status, 0, result.stderr || result.stdout); + return result.stdout.trim(); + }; + fixtureGit("init", "-q"); + const constantPath = + "crates/codestory-llama-sys/per-user-embedding-server-constant-set.json"; + mkdirSync(path.dirname(path.join(fixture, constantPath)), { recursive: true }); + writeFileSync(path.join(fixture, "README.md"), "source reuse fixture\n"); + writeFileSync( + path.join(fixture, constantPath), + `${JSON.stringify({ status: "unfrozen", freeze_record: null }, null, 2)}\n`, + ); + fixtureGit("add", "-A"); + fixtureGit("commit", "-qm", "accepted source"); + const acceptedSource = fixtureGit("rev-parse", "HEAD"); + const acceptedTree = fixtureGit("rev-parse", "HEAD^{tree}"); + writeFileSync( + path.join(fixture, constantPath), + `${JSON.stringify({ + status: "frozen", + freeze_record: { + selection_source_commit: acceptedSource, + selection_source_tree: acceptedTree, + }, + }, null, 2)}\n`, + ); + fixtureGit("add", constantPath); + fixtureGit("commit", "-qm", "freeze constants"); + const frozenSource = fixtureGit("rev-parse", "HEAD"); + const frozenTree = fixtureGit("rev-parse", "HEAD^{tree}"); + assert.equal( + verifyReuseBinding({ + binding: "source_tree", + repository: fixture, + releaseCommit: frozenSource, + reusedCommit: acceptedSource, + }), + frozenTree, + ); + fixtureGit("commit", "--allow-empty", "-qm", "promote frozen tree"); + const promotedSource = fixtureGit("rev-parse", "HEAD"); + assert.equal( + verifyReuseBinding({ + binding: "source_tree", + repository: fixture, + releaseCommit: promotedSource, + reusedCommit: acceptedSource, + }), + frozenTree, + ); + fixtureGit("commit", "--allow-empty", "-qm", "later source commit"); + const laterSource = fixtureGit("rev-parse", "HEAD"); + assert.throws( + () => verifyReuseBinding({ + binding: "source_tree", + repository: fixture, + releaseCommit: laterSource, + reusedCommit: acceptedSource, + }), + /direct generated constant-set child|tree-preserving promotion/u, + ); }); test("a reuse binding may equate only identities its own construction determines", () => { @@ -1170,7 +1253,10 @@ test("a reuse binding may equate only identities its own construction determines // required_identity, which would drop the check for fresh evidence too (#1567). const declared = graph.evidence_policy.reuse.bindings; assert.deepEqual(Object.keys(declared).sort(), ["native_fingerprint", "source_tree"]); - assert.deepEqual(declared.source_tree.equates, []); + assert.deepEqual( + declared.source_tree.equates.map(({ identity }) => identity), + ["source_tree"], + ); assert.deepEqual(declared.native_fingerprint.equates.map(({ identity }) => identity), ["source_tree"]); assert.ok(declared.native_fingerprint.equates[0].justification.length > 0); @@ -1185,15 +1271,15 @@ test("a reuse binding may equate only identities its own construction determines /native_fingerprint may not equate identity repository, which its construction does not determine/u, ); - // The tree binding proves the reused commit resolves to this release's own tree, so there is - // nothing to substitute: equating the tree there would replace a live check with nothing. - const vacuousEquation = structuredClone(graph); - vacuousEquation.evidence_policy.reuse.bindings.source_tree.equates = [ - { identity: "source_tree", justification: "the trees are equal anyway" }, + // Constant-only lineage lets the tree binding determine the source-tree transition. It still + // says nothing about which repository produced either commit, so it cannot equate repository. + const sourceTreeOverreach = structuredClone(graph); + sourceTreeOverreach.evidence_policy.reuse.bindings.source_tree.equates = [ + { identity: "repository", justification: "the commits were nearby" }, ]; assert.throws( - () => validateReleaseClaimGraph(vacuousEquation), - /source_tree may not equate identity source_tree, which its construction does not determine/u, + () => validateReleaseClaimGraph(sourceTreeOverreach), + /source_tree may not equate identity repository, which its construction does not determine/u, ); // An identity outside the release identity binding has no authoritative release-side value the diff --git a/scripts/tests/codestory-release-closeout.test.mjs b/scripts/tests/codestory-release-closeout.test.mjs index f5cccbb5d..730504fde 100644 --- a/scripts/tests/codestory-release-closeout.test.mjs +++ b/scripts/tests/codestory-release-closeout.test.mjs @@ -303,6 +303,7 @@ function evaluate( const reusedRunId = "777"; const reusedCommit = "3".repeat(40); +const reusedSourceTree = "b".repeat(40); /// Stands in for the git binding proof `main()` runs against the closeout's own checkout. function reuseVerifier({ @@ -320,6 +321,20 @@ function reuseVerifier({ }; } +function sourceTreeReuse({ + ancestors = [reusedCommit, gitIdentity.commit], + value = gitIdentity.source_tree, + tree = reusedSourceTree, +} = {}) { + return { + verify: reuseVerifier({ ancestors, value }), + resolve: (commit) => { + if (commit !== reusedCommit) throw new Error(`git cat-file -e ${commit} failed`); + return { repository: gitIdentity.repository, commit, source_tree: tree }; + }, + }; +} + /// Re-anchor the source cell's producer row onto a prior run, exactly as the producer map does /// once release preflight selects source-proof reuse. function reuseSourceBehavior(trustedProducers, manifests, reusedFrom = {}) { @@ -340,6 +355,7 @@ function reuseSourceBehavior(trustedProducers, manifests, reusedFrom = {}) { const manifest = manifests.find(({ cell_id: cellId }) => cellId === "source_behavior"); manifest.evidence.identity.producer_run_id = reusedRunId; manifest.evidence.identity.commit = reusedCommit; + manifest.evidence.identity.source_tree = reusedSourceTree; return row; } @@ -764,11 +780,11 @@ test("a binding-verified reuse row is anchored to the run and commit it was prod const trusted = trustedProducersFor("pre_publish"); reuseSourceBehavior(trusted, manifests); const calls = []; - const verify = reuseVerifier(); + const proof = sourceTreeReuse(); const accepted = evaluate("pre_publish", manifests, null, trusted, null, null, (request) => { calls.push(request); - return verify(request); - }); + return proof.verify(request); + }, proof.resolve); assert.equal(accepted.decision, "accept"); assert.deepEqual(accepted.summary.input_errors, []); assert.deepEqual(accepted.summary.failed_cells, []); @@ -782,6 +798,7 @@ test("a binding-verified reuse row is anchored to the run and commit it was prod const row = accepted.ledger.cells.find(({ id }) => id === "source_behavior"); assert.equal(row.identity.producer_run_id, reusedRunId); assert.equal(row.identity.commit, reusedCommit); + assert.equal(row.identity.source_tree, reusedSourceTree); // Cells that were not reused stay bound to the publishing run. const packaged = accepted.ledger.cells.find(({ id }) => id === "package_identity:windows-x64"); assert.equal(packaged.identity.producer_run_id, "12345"); @@ -857,7 +874,17 @@ test("a reuse row whose binding the closeout cannot reprove fails closed", () => const manifests = manifestsFor("pre_publish"); const trusted = trustedProducersFor("pre_publish"); mutate(trusted, manifests); - const rejected = evaluate("pre_publish", manifests, null, trusted, null, null, reuseVerifier()); + const proof = sourceTreeReuse(); + const rejected = evaluate( + "pre_publish", + manifests, + null, + trusted, + null, + null, + proof.verify, + proof.resolve, + ); assert.equal(rejected.decision, "reject", label); assert.ok( rejected.summary.input_errors.some((message) => message.includes(expected)), @@ -892,6 +919,7 @@ test("a reused artifact container is still bound by its digest", () => { }); bindings.find(({ cell_id: cellId }) => cellId === "source_behavior") .artifact_digest = `sha256:${"f".repeat(64)}`; + const proof = sourceTreeReuse(); const rejected = evaluate( "pre_publish", manifests, @@ -899,7 +927,8 @@ test("a reused artifact container is still bound by its digest", () => { trusted, null, bindings, - reuseVerifier(), + proof.verify, + proof.resolve, ); assert.equal(rejected.decision, "reject"); assert.ok(rejected.summary.failed_cells.includes("source_behavior")); @@ -915,7 +944,17 @@ test("reuse never lets stale evidence through the checks that do not depend on t reuseSourceBehavior(trusted, manifests); manifests.find(({ cell_id: cellId }) => cellId === "source_behavior") .evidence.identity.source_tree = "e".repeat(40); - const rejected = evaluate("pre_publish", manifests, null, trusted, null, null, reuseVerifier()); + const proof = sourceTreeReuse(); + const rejected = evaluate( + "pre_publish", + manifests, + null, + trusted, + null, + null, + proof.verify, + proof.resolve, + ); assert.equal(rejected.decision, "reject"); assert.ok(rejected.summary.failed_cells.includes("source_behavior")); }); @@ -935,7 +974,17 @@ test("a reused manifest may declare only the commit the closeout proved bound to reuseSourceBehavior(trusted, manifests); manifests.find(({ cell_id: cellId }) => cellId === "source_behavior") .evidence.identity.commit = declared; - const rejected = evaluate("pre_publish", manifests, null, trusted, null, null, reuseVerifier()); + const proof = sourceTreeReuse(); + const rejected = evaluate( + "pre_publish", + manifests, + null, + trusted, + null, + null, + proof.verify, + proof.resolve, + ); assert.equal(rejected.decision, "reject", label); assert.ok(rejected.summary.failed_cells.includes("source_behavior"), label); assert.ok( diff --git a/scripts/tests/fixtures/release-claims/positive.json b/scripts/tests/fixtures/release-claims/positive.json index 70198f7f9..66ec2695f 100644 --- a/scripts/tests/fixtures/release-claims/positive.json +++ b/scripts/tests/fixtures/release-claims/positive.json @@ -17,7 +17,7 @@ "type": "source_behavior", "tier": "source", "status": "pass", - "graph_sha256": "54a9a45c1df1c2a738da405f9bca7120cb75d422bb94e9b05cd8cf4f0c3c247b", + "graph_sha256": "74b22ad4023161484dcd9a5ef141b9825456f95137434a2ea3a0ba8ef515d364", "observed_at": "2026-07-16T11:00:00.000Z", "expires_at": "2026-07-17T11:00:00.000Z", "identity": { From c5c0c2f57fd07430059016c34fd972d32300b9c2 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Thu, 30 Jul 2026 08:45:05 -0500 Subject: [PATCH 18/28] authenticate the release freeze --- .github/scripts/check-workflow-policy.mjs | 302 +++++++++++++++-- .../scripts/check-workflow-policy.test.mjs | 151 ++++++++- .github/scripts/release-freeze-barrier.mjs | 303 ++++++++++++------ .../scripts/release-freeze-barrier.test.mjs | 303 +++++++++++++----- .github/workflows/auto-release.yml | 1 + .github/workflows/packaged-platform-pr.yml | 29 +- .../workflows/release-freeze-invalidation.yml | 58 ++++ .github/workflows/release.yml | 31 +- .github/workflows/source-proof.yml | 193 +++++++++-- .../release-evidence/fixtures/candidate.json | 6 +- .../release-evidence/fixtures/report.json | 8 +- release-claims.json | 22 ++ scripts/codestory-release-claims.mjs | 72 +++++ .../tests/codestory-release-claims.test.mjs | 58 ++++ .../fixtures/release-claims/positive.json | 2 +- 15 files changed, 1281 insertions(+), 258 deletions(-) create mode 100644 .github/workflows/release-freeze-invalidation.yml diff --git a/.github/scripts/check-workflow-policy.mjs b/.github/scripts/check-workflow-policy.mjs index e18cbc4b2..546d3bd80 100644 --- a/.github/scripts/check-workflow-policy.mjs +++ b/.github/scripts/check-workflow-policy.mjs @@ -913,7 +913,7 @@ const packagedPlatformWorkflowDigest = // made advisory, parked in dead code, or followed by a payload substitution // while leaving the expected tokens in place. const packagedPlatformCoordinatorWorkflowDigest = - "83ff8876fbf87a2e35eebe0e16d8f025a1b2377ceb0a31d8c9bcacea856f9bde"; + "26e3e2a92d959a46f8ef6173d0531b331cbd824344400629aa2e5b13c6286d33"; const frozenCandidateQualityWorkflowDigest = "92d0a7ab0e0df63dacd5cc3ef0b58500a6578036494c329aa35279048734f173"; const macosMetalWorkflowDigest = @@ -2134,18 +2134,22 @@ function validatePluginAndDraftWorkflows(workflows, violations, graph) { '.path == ".github/workflows/source-proof.yml"', '.event == "workflow_dispatch" and .conclusion == "success"', '.name == "full-source-gate" and .conclusion == "success"', + 'artifact_name="release-cell-prepublish-source-attempt-$run_attempt"', + ".expired == false", + 'test "$artifact_count" = 1 || continue', ]); requireStepRun(violations, sourceFile, resolve, "Require executable release freeze", [ - "codestory/release-freeze/$FREEZE_RECEIPT_DIGEST", "repos/$GITHUB_REPOSITORY/git/commits/$HEAD_SHA", - ".state == \"success\"", - ".description == $description", + "release-freeze-barrier.mjs", + "verify-pending", + "verify-status", + '--receipt-digest "$FREEZE_RECEIPT_DIGEST"', ]); const full = requireJob(violations, sourceFile, source, "full-source-gate"); add(violations, sameMembers(needs(full), ["resolve"]), `${sourceFile} full source gate must need resolve`); add( violations, - full.if === "needs.resolve.outputs.reuse != 'true'", + full.if === "${{ !inputs.acceptance_only && needs.resolve.outputs.reuse != 'true' }}", `${sourceFile} full source gate may skip only a completed exact-head proof`, ); const generalization = requireJob( @@ -2170,7 +2174,8 @@ function validatePluginAndDraftWorkflows(workflows, violations, graph) { violations, generalization.name === "retrieval-generalization" && sameMembers(needs(generalization), ["resolve"]) - && generalization.if === "needs.resolve.outputs.reuse != 'true'" + && generalization.if + === "${{ !inputs.acceptance_only && needs.resolve.outputs.reuse != 'true' }}" && generalization["runs-on"] === "ubuntu-latest" && generalization["timeout-minutes"] === 5 && generalization["continue-on-error"] === undefined, @@ -2406,9 +2411,9 @@ function validatePluginAndDraftWorkflows(workflows, violations, graph) { add( violations, sourceCellUpload?.uses === "actions/upload-artifact@v7.0.1" - && String(sourceCellUpload?.if ?? "").includes("success()") - && String(sourceCellUpload?.if ?? "").includes("inputs.emit_release_cells"), - `${sourceFile} source release cell must be a success-only retained artifact`, + && sourceCellUpload?.if === "success()" + && !scalarStrings(source).some(value => value.includes("emit_release_cells")), + `${sourceFile} source release cell must be an unconditional success-only retained artifact`, ); } } @@ -2732,7 +2737,11 @@ function validateReleaseCoordinator(workflows, violations, graph) { '.path == ".github/workflows/source-proof.yml"', '.event == "workflow_dispatch" and .conclusion == "success"', "The release workflow will not start a second proof after calibration", - "codestory/release-freeze/$freeze_digest", + 'artifact_name="release-cell-prepublish-source-attempt-$run_attempt"', + ".expired == false", + 'test "$artifact_count" = 1 || continue', + "release-freeze-barrier.mjs verify-status", + '--receipt-digest "$freeze_digest"', ]); const closeout = requireJob(violations, releaseFile, release, "pre-publish-closeout"); requireStepRun(violations, releaseFile, closeout, "Authenticate pre-publish Actions provenance", [ @@ -2747,9 +2756,9 @@ function validateReleaseCoordinator(workflows, violations, graph) { add( violations, object(source.with).version === "${{ needs.preflight.outputs.version }}" - && object(source.with).emit_release_cells === true && object(source.with).freeze_receipt_digest - === "${{ needs.preflight.outputs.freeze_receipt_digest }}", + === "${{ needs.preflight.outputs.freeze_receipt_digest }}" + && object(source.with).emit_release_cells === undefined, `${releaseFile} unreachable source fallback must retain the accepted freeze identity`, ); @@ -5001,16 +5010,19 @@ function validatePackagedCoordinator(workflows, violations, graph) { }); requireExactResolverContract(violations, file, route, platformResolverContractDigest); requireStepRun(violations, file, route, "Require executable release freeze", [ - "codestory/release-freeze/$FREEZE_RECEIPT_DIGEST", "repos/$GITHUB_REPOSITORY/git/commits/$SOURCE_SHA", - ".state == \"success\"", - ".description == $description", + "release-freeze-barrier.mjs verify-status", + '--commit "$SOURCE_SHA"', + '--receipt-digest "$FREEZE_RECEIPT_DIGEST"', ]); requireStepRun(violations, file, route, "Require successful accepted-head source proof", [ "actions/runs?head_sha=$SOURCE_SHA", '.path == ".github/workflows/source-proof.yml"', '.event == "workflow_dispatch" and .conclusion == "success"', '.name == "full-source-gate" and .conclusion == "success"', + 'artifact_name="release-cell-prepublish-source-attempt-$run_attempt"', + ".expired == false", + 'test "$artifact_count" = 1 || continue', ]); requireStepRun(violations, file, route, "Select change-aware proof scope", [ 'if [ "$REQUESTED_SCOPE" = none ] || [ "$REQUESTED_SCOPE" = linux ]; then', @@ -7953,6 +7965,8 @@ export function releaseFreezeBarrierWorkflowViolations( ) { const violations = []; const freeze = object(graph.workflow_policy.release_freeze_barrier); + const acceptance = object(freeze.acceptance); + const singleSource = object(freeze.single_source_proof); add( violations, freeze.schema === 1 @@ -7961,10 +7975,99 @@ export function releaseFreezeBarrierWorkflowViolations( && sameMembers(list(freeze.allowed_future_source_changes), [ "crates/codestory-llama-sys/per-user-embedding-server-constant-set.json", ]) - && object(freeze.single_source_proof).post_calibration_fallback_allowed === false, + && freeze.invalidation_workflow === "release-freeze-invalidation.yml" + && acceptance.producer_workflow === "source-proof.yml" + && acceptance.event === "workflow_dispatch" + && acceptance.hostile_job === "freeze-hostile-mutations" + && acceptance.hostile_step === "Execute exact-head hostile mutation matrix" + && acceptance.windows_job === "freeze-windows-native-probe" + && acceptance.windows_step === "Run exact-head Windows native probe" + && sameMembers(list(acceptance.windows_runner), [ + "self-hosted", + "Windows", + "X64", + "codestory-vulkan", + ]) + && acceptance.windows_probe_max_seconds === 90 + && acceptance.publisher_job === "freeze-acceptance" + && acceptance.publisher_step === "Publish executable release freeze" + && acceptance.status_creator === "github-actions[bot]" + && singleSource.artifact + === "release-cell-prepublish-source-attempt-${{ github.run_attempt }}" + && singleSource.artifact_required_unexpired === true + && singleSource.cell_emission === "unconditional_on_success" + && singleSource.post_calibration_fallback_allowed === false, "[freeze_barrier] release claim graph must pin the executable single-proof freeze contract", ); + const invalidationFile = freeze.invalidation_workflow; + const invalidation = workflows.get(invalidationFile); + add( + violations, + sameMembers(at(invalidation, "on", "pull_request", "branches"), [ + "dev/codestory-next", + ]) + && sameMembers(at(invalidation, "on", "pull_request", "types"), [ + "synchronize", + ]) + && sameMembers(at(invalidation, "on", "push", "branches"), [ + "dev/codestory-next", + ]) + && object(invalidation.permissions).actions === "write" + && object(invalidation.permissions).contents === "read" + && object(invalidation.permissions).statuses === "read" + && at(invalidation, "concurrency", "cancel-in-progress") === true, + "[freeze_barrier] release freeze invalidation must run automatically when a candidate head is superseded", + ); + const invalidationJob = requireJob( + violations, + invalidationFile, + invalidation, + "invalidate", + ); + add( + violations, + invalidationJob["runs-on"] === "ubuntu-latest" + && invalidationJob["timeout-minutes"] === 5 + && sameMembers( + list(invalidationJob.steps).map(step => step?.name ?? step?.uses), + [ + "actions/checkout@v5", + "Invalidate a superseded release freeze", + ], + ), + "[freeze_barrier] release freeze invalidation must remain one bounded cancellation job", + ); + requireStepRun( + violations, + invalidationFile, + invalidationJob, + "Invalidate a superseded release freeze", + [ + 'test "$BEFORE_SHA" != "$AFTER_SHA"', + "commits/$BEFORE_SHA/statuses?per_page=100", + '.state == "pending" or .state == "success"', + 'startswith("codestory/release-freeze/")', + 'if [ "$has_freeze" = 0 ]; then', + "release-freeze-barrier.mjs invalidate-superseded", + '--commit "$AFTER_SHA"', + '--broad-workflow "Exact-head source proof"', + '--broad-workflow "Platform and integration proof"', + '--broad-workflow "Release"', + '--broad-workflow "Auto Release"', + ], + ); + requireStepEnv( + violations, + invalidationFile, + invalidationJob, + "Invalidate a superseded release freeze", + { + AFTER_SHA: "${{ github.event.after || github.sha }}", + BEFORE_SHA: "${{ github.event.before }}", + }, + ); + for (const file of ["source-proof.yml", "packaged-platform-pr.yml"]) { const workflow = workflows.get(file); add( @@ -7990,28 +8093,52 @@ export function releaseFreezeBarrierWorkflowViolations( `[freeze_barrier] ${file} dispatch must require an exact-head freeze receipt digest`, ); if (file === "source-proof.yml") { - const versionInput = object(at( + const dispatchVersionInput = object(at( workflow, "on", "workflow_dispatch", "inputs", "version", )); - const emitInput = object(at( + const callVersionInput = object(at( + workflow, + "on", + "workflow_call", + "inputs", + "version", + )); + const acceptanceInput = object(at( workflow, "on", "workflow_dispatch", "inputs", - "emit_release_cells", + "acceptance_only", )); add( violations, - versionInput.required === true - && versionInput.type === "string" - && emitInput.required === false - && emitInput.type === "boolean" - && emitInput.default === true, - "[freeze_barrier] source-proof.yml dispatch must emit one reusable versioned source cell", + dispatchVersionInput.required === true + && dispatchVersionInput.type === "string" + && callVersionInput.required === true + && callVersionInput.type === "string" + && acceptanceInput.required === false + && acceptanceInput.type === "boolean" + && acceptanceInput.default === false + && at(workflow, "on", "workflow_dispatch", "inputs", "emit_release_cells") + === undefined + && at(workflow, "on", "workflow_call", "inputs", "emit_release_cells") + === undefined, + "[freeze_barrier] source-proof.yml must separate acceptance and emit one reusable source cell after every successful proof", + ); + add( + violations, + object(workflow.permissions).statuses === "write", + "[freeze_barrier] source-proof.yml acceptance must publish an exact-head commit status", + ); + } else { + add( + violations, + object(workflow.permissions).statuses === "read", + "[freeze_barrier] packaged-platform-pr.yml must authenticate the exact-head freeze status", ); } add( @@ -8036,6 +8163,107 @@ export function releaseFreezeBarrierWorkflowViolations( ); } + const sourceWorkflow = workflows.get("source-proof.yml"); + const hostileJob = requireJob( + violations, + "source-proof.yml", + sourceWorkflow, + acceptance.hostile_job, + ); + add( + violations, + hostileJob.if === "inputs.acceptance_only" + && sameMembers(needs(hostileJob), ["resolve"]) + && hostileJob["runs-on"] === "ubuntu-latest" + && hostileJob["timeout-minutes"] === 5 + && namedStep(hostileJob, acceptance.hostile_step)?.["continue-on-error"] !== true, + "[freeze_barrier] source acceptance must execute the exact blocking hostile mutation job", + ); + requireStepRun( + violations, + "source-proof.yml", + hostileJob, + acceptance.hostile_step, + [ + "node --test", + ".github/scripts/check-workflow-policy.test.mjs", + ".github/scripts/release-freeze-barrier.test.mjs", + ".github/scripts/cargo-build-artifacts.test.mjs", + ".github/scripts/candidate-archive-store.test.mjs", + ], + ); + + const windowsJob = requireJob( + violations, + "source-proof.yml", + sourceWorkflow, + acceptance.windows_job, + ); + add( + violations, + windowsJob.if === "inputs.acceptance_only" + && sameMembers(needs(windowsJob), ["resolve"]) + && sameMembers(list(windowsJob["runs-on"]), list(acceptance.windows_runner)) + && windowsJob["timeout-minutes"] === 5 + && namedStep(windowsJob, acceptance.windows_step)?.shell === "pwsh" + && namedStep(windowsJob, acceptance.windows_step)?.["continue-on-error"] !== true, + "[freeze_barrier] source acceptance must execute the protected blocking Windows native probe", + ); + requireStepRun( + violations, + "source-proof.yml", + windowsJob, + acceptance.windows_step, + [ + "cargo new --quiet --bin", + "cargo build --release --quiet", + "node --test .github/scripts/cargo-build-artifacts.test.mjs", + "left.dev !== right.dev", + "left.ino !== right.ino", + "left.nlink !== 2n", + "right.nlink !== 2n", + "Elapsed.TotalSeconds -ge 90", + "Remove-Item -LiteralPath $probeRoot -Recurse -Force", + ], + ); + + const publisherJob = requireJob( + violations, + "source-proof.yml", + sourceWorkflow, + acceptance.publisher_job, + ); + add( + violations, + sameMembers(needs(publisherJob), [ + "resolve", + acceptance.hostile_job, + acceptance.windows_job, + ]) + && publisherJob["runs-on"] === "ubuntu-latest" + && publisherJob["timeout-minutes"] === 5 + && [ + "always()", + "inputs.acceptance_only", + `needs.${acceptance.hostile_job}.result == 'success'`, + `needs.${acceptance.windows_job}.result == 'success'`, + ].every(fragment => String(publisherJob.if ?? "").includes(fragment)), + "[freeze_barrier] acceptance publisher must depend on both exact successful mutation jobs", + ); + requireStepRun( + violations, + "source-proof.yml", + publisherJob, + acceptance.publisher_step, + [ + "repos/$GITHUB_REPOSITORY/statuses/$HEAD_SHA", + "-f state=success", + "-f \"context=codestory/release-freeze/$FREEZE_RECEIPT_DIGEST\"", + "-f \"description=tree=$tree\"", + "actions/runs/$GITHUB_RUN_ID", + ], + ); + for (const file of list(freeze.coordinator_only_workflows)) { const workflow = workflows.get(file); add( @@ -8060,6 +8288,17 @@ export function releaseFreezeBarrierWorkflowViolations( "sha=$HEAD_SHA", ], ); + requireStepRun( + violations, + "packaged-platform-pr.yml", + route, + "Require executable release freeze", + [ + "release-freeze-barrier.mjs verify-status", + '--commit "$SOURCE_SHA"', + '--receipt-digest "$FREEZE_RECEIPT_DIGEST"', + ], + ); requireStepRun( violations, "packaged-platform-pr.yml", @@ -8069,6 +8308,9 @@ export function releaseFreezeBarrierWorkflowViolations( "actions/runs?head_sha=$SOURCE_SHA", '.event == "workflow_dispatch" and .conclusion == "success"', '.name == "full-source-gate" and .conclusion == "success"', + 'artifact_name="release-cell-prepublish-source-attempt-$run_attempt"', + ".expired == false", + 'test "$artifact_count" = 1 || continue', ], ); @@ -8080,6 +8322,12 @@ export function releaseFreezeBarrierWorkflowViolations( && at(auto, "concurrency", "cancel-in-progress") === true, "[freeze_barrier] release and auto-release must cancel superseded work", ); + add( + violations, + object(release.permissions).statuses === "read" + && object(at(auto, "jobs", "release", "permissions")).statuses === "read", + "[freeze_barrier] manual and automatic release must authenticate freeze status provenance", + ); const preflight = requireJob(violations, "release.yml", release, "preflight"); requireStepRun( violations, @@ -8088,6 +8336,10 @@ export function releaseFreezeBarrierWorkflowViolations( "Resolve reusable prior evidence", [ "actions/runs?head_sha=$SOURCE_SHA", + 'artifact_name="release-cell-prepublish-source-attempt-$run_attempt"', + ".expired == false", + 'test "$artifact_count" = 1 || continue', + "release-freeze-barrier.mjs verify-status", "The release workflow will not start a second proof after calibration", "source_proof_reused=true", ], diff --git a/.github/scripts/check-workflow-policy.test.mjs b/.github/scripts/check-workflow-policy.test.mjs index 4a504150e..93d0e3883 100644 --- a/.github/scripts/check-workflow-policy.test.mjs +++ b/.github/scripts/check-workflow-policy.test.mjs @@ -2841,6 +2841,32 @@ test("release freeze barrier rejects every broad-proof bypass", async (t) => { ["source label trigger", workflows => { workflows.get("source-proof.yml").on.pull_request = { types: ["labeled"] }; }, /support PR event/u], + ["superseded PR heads stop invalidating proof", workflows => { + workflows.get("release-freeze-invalidation.yml").on.pull_request.types = ["opened"]; + }, /must run automatically when a candidate head is superseded/u], + ["dev head changes stop invalidating proof", workflows => { + delete workflows.get("release-freeze-invalidation.yml").on.push; + }, /must run automatically when a candidate head is superseded/u], + ["invalidation stops checking the prior freeze", workflows => { + const step = draftStep( + workflows.get("release-freeze-invalidation.yml").jobs.invalidate, + "Invalidate a superseded release freeze", + ); + step.run = step.run.replace( + "commits/$BEFORE_SHA/statuses?per_page=100", + "commits/$AFTER_SHA/statuses?per_page=100", + ); + }, /Invalidate a superseded release freeze/u], + ["invalidation stops cancelling auto-release", workflows => { + const step = draftStep( + workflows.get("release-freeze-invalidation.yml").jobs.invalidate, + "Invalidate a superseded release freeze", + ); + step.run = step.run.replace( + '--broad-workflow "Auto Release"', + "", + ); + }, /Invalidate a superseded release freeze/u], ["platform label trigger", workflows => { workflows.get("packaged-platform-pr.yml").on.pull_request = { types: ["labeled"] }; }, /support PR event/u], @@ -2859,14 +2885,82 @@ test("release freeze barrier rejects every broad-proof bypass", async (t) => { workflows.get("source-proof.yml").on.workflow_dispatch .inputs.freeze_receipt_digest.required = false; }, /dispatch must require an exact-head freeze receipt digest/u], - ["source dispatch stops emitting a reusable cell", workflows => { + ["source acceptance becomes the default", workflows => { workflows.get("source-proof.yml").on.workflow_dispatch - .inputs.emit_release_cells.default = false; - }, /dispatch must emit one reusable versioned source cell/u], + .inputs.acceptance_only.default = true; + }, /separate acceptance and emit one reusable source cell/u], + ["source acceptance cannot publish status", workflows => { + delete workflows.get("source-proof.yml").permissions.statuses; + }, /acceptance must publish an exact-head commit status/u], + ["source restores conditional cell emission", workflows => { + workflows.get("source-proof.yml").on.workflow_dispatch + .inputs.emit_release_cells = { + required: false, + default: false, + type: "boolean", + }; + draftStep( + workflows.get("source-proof.yml").jobs["full-source-gate"], + "Upload authenticated source release cell", + ).if = "success() && inputs.emit_release_cells"; + }, /separate acceptance and emit one reusable source cell/u], + ["hostile mutation job is removed", workflows => { + delete workflows.get("source-proof.yml").jobs["freeze-hostile-mutations"]; + }, /freeze-hostile-mutations/u], + ["hostile mutation matrix is weakened", workflows => { + draftStep( + workflows.get("source-proof.yml").jobs["freeze-hostile-mutations"], + "Execute exact-head hostile mutation matrix", + ).run = "node --test .github/scripts/release-freeze-barrier.test.mjs"; + }, /Execute exact-head hostile mutation matrix/u], + ["hostile mutations become advisory", workflows => { + draftStep( + workflows.get("source-proof.yml").jobs["freeze-hostile-mutations"], + "Execute exact-head hostile mutation matrix", + )["continue-on-error"] = true; + }, /exact blocking hostile mutation job/u], + ["Windows probe leaves the protected runner", workflows => { + workflows.get("source-proof.yml").jobs["freeze-windows-native-probe"]["runs-on"] + = ["self-hosted", "Windows", "X64"]; + }, /protected blocking Windows native probe/u], + ["Windows probe restores a full build", workflows => { + const step = draftStep( + workflows.get("source-proof.yml").jobs["freeze-windows-native-probe"], + "Run exact-head Windows native probe", + ); + step.run = step.run.replace( + "cargo build --release --quiet", + "cargo build --workspace --release", + ); + }, /Run exact-head Windows native probe/u], + ["Windows probe allows 90 seconds", workflows => { + const step = draftStep( + workflows.get("source-proof.yml").jobs["freeze-windows-native-probe"], + "Run exact-head Windows native probe", + ); + step.run = step.run.replace("Elapsed.TotalSeconds -ge 90", "Elapsed.TotalSeconds -gt 90"); + }, /Run exact-head Windows native probe/u], + ["acceptance publisher stops waiting for Windows", workflows => { + workflows.get("source-proof.yml").jobs["freeze-acceptance"].needs + = ["resolve", "freeze-hostile-mutations"]; + }, /publisher must depend on both exact successful mutation jobs/u], + ["acceptance publisher loses Actions provenance", workflows => { + const step = draftStep( + workflows.get("source-proof.yml").jobs["freeze-acceptance"], + "Publish executable release freeze", + ); + step.run = step.run.replace( + "actions/runs/$GITHUB_RUN_ID", + "pull/$GITHUB_RUN_ID", + ); + }, /Publish executable release freeze/u], ["platform dispatch omits receipt", workflows => { workflows.get("packaged-platform-pr.yml").on.workflow_dispatch .inputs.freeze_receipt_digest.required = false; }, /dispatch must require an exact-head freeze receipt digest/u], + ["platform cannot read freeze status", workflows => { + delete workflows.get("packaged-platform-pr.yml").permissions.statuses; + }, /must authenticate the exact-head freeze status/u], ["qualification proves the frozen descendant again", workflows => { const step = draftStep( workflows.get("packaged-platform-pr.yml").jobs.route, @@ -2890,9 +2984,53 @@ test("release freeze barrier rejects every broad-proof bypass", async (t) => { ["release restores post-calibration fallback", workflows => { workflows.get("release.yml").jobs["source-proof"].if = "always()"; }, /post-calibration source-proof fallback unreachable/u], + ["source reuse accepts an expired cell", workflows => { + const step = draftStep( + workflows.get("source-proof.yml").jobs.resolve, + "Reuse a completed gate for this exact head", + ); + step.run = step.run.replace(".expired == false", "true"); + }, /Reuse a completed gate.*expired/u], + ["qualification accepts an expired source cell", workflows => { + const step = draftStep( + workflows.get("packaged-platform-pr.yml").jobs.route, + "Require successful accepted-head source proof", + ); + step.run = step.run.replace(".expired == false", "true"); + }, /Require successful accepted-head source proof.*expired/u], + ["release accepts an expired source cell", workflows => { + const step = draftStep( + workflows.get("release.yml").jobs.preflight, + "Resolve reusable prior evidence", + ); + step.run = step.run.replace(".expired == false", "true"); + }, /Resolve reusable prior evidence.*expired/u], + ["qualification trusts a bare success status", workflows => { + const step = draftStep( + workflows.get("packaged-platform-pr.yml").jobs.route, + "Require executable release freeze", + ); + step.run = step.run.replace( + "release-freeze-barrier.mjs verify-status", + "gh api repos/$GITHUB_REPOSITORY/commits/$SOURCE_SHA/status", + ); + }, /Require executable release freeze.*verify-status/u], + ["release trusts a bare success status", workflows => { + const step = draftStep( + workflows.get("release.yml").jobs.preflight, + "Resolve reusable prior evidence", + ); + step.run = step.run.replace( + "release-freeze-barrier.mjs verify-status", + "gh api repos/$GITHUB_REPOSITORY/commits/$SOURCE_SHA/status", + ); + }, /Resolve reusable prior evidence.*verify-status/u], ["release stops cancelling superseded work", workflows => { workflows.get("release.yml").concurrency["cancel-in-progress"] = false; }, /release and auto-release must cancel superseded work/u], + ["automatic release cannot read freeze status", workflows => { + delete workflows.get("auto-release.yml").jobs.release.permissions.statuses; + }, /must authenticate freeze status provenance/u], ["auto-release stops cancelling superseded work", workflows => { workflows.get("auto-release.yml").concurrency["cancel-in-progress"] = false; }, /release and auto-release must cancel superseded work/u], @@ -4860,7 +4998,12 @@ test("release policy rejects manifest producer, trusted-map, and publication byp uses: "./.github/workflows/release.yml", }; }], - ["source emission", workflows => { delete workflows.get("release.yml").jobs["source-proof"].with.emit_release_cells; }], + ["source emission", workflows => { + draftStep( + workflows.get("source-proof.yml").jobs["full-source-gate"], + "Upload authenticated source release cell", + ).if = "success() && inputs.emit_release_cells"; + }], ["full rerun preflight guard", workflows => { workflows.get("release.yml").jobs.preflight.steps = workflows .get("release.yml").jobs.preflight.steps diff --git a/.github/scripts/release-freeze-barrier.mjs b/.github/scripts/release-freeze-barrier.mjs index ec5960a28..f665fc5c7 100644 --- a/.github/scripts/release-freeze-barrier.mjs +++ b/.github/scripts/release-freeze-barrier.mjs @@ -16,6 +16,14 @@ const ALLOWED_FUTURE_CHANGES = new Set([ "crates/codestory-llama-sys/per-user-embedding-server-constant-set.json", ]); const STATUS_PREFIX = "codestory/release-freeze"; +const CANCEL_POLL_ATTEMPTS = Number.parseInt( + process.env.CODESTORY_FREEZE_CANCEL_POLL_ATTEMPTS ?? "10", + 10, +); +const CANCEL_POLL_MS = Number.parseInt( + process.env.CODESTORY_FREEZE_CANCEL_POLL_MS ?? "1000", + 10, +); function fail(message) { throw new Error(message); @@ -102,49 +110,89 @@ export function receiptDigest(receipt) { .digest("hex"); } -export function validateMutationReceipt(receipt, { commit, tree, requiredIds }) { - if (receipt?.commit !== commit || receipt?.tree !== tree) { - fail("hostile mutation evidence must name the exact frozen commit and tree"); - } - if (!Array.isArray(receipt.cases)) { - fail("hostile mutation evidence must contain cases"); - } - const cases = new Map(receipt.cases.map((entry) => [entry?.id, entry])); - for (const id of requiredIds) { - const entry = cases.get(id); - if (!entry || entry.status !== "passed") { - fail(`hostile mutation ${id} did not pass on the exact frozen head`); - } +function elapsedSeconds(step) { + const started = Date.parse(String(step?.started_at ?? "")); + const completed = Date.parse(String(step?.completed_at ?? "")); + if (!Number.isFinite(started) || !Number.isFinite(completed) || completed < started) { + fail(`acceptance step ${step?.name ?? ""} has invalid Actions timing`); } + return (completed - started) / 1000; } -export function validatePlatformEvidence(evidence, { commit, tree }) { - const failures = evidence?.failures ?? []; - const probes = evidence?.probes ?? []; - if (!Array.isArray(failures) || !Array.isArray(probes)) { - fail("platform evidence must contain failure and probe arrays"); - } - for (const failure of failures) { - const probe = probes.find((candidate) => - candidate?.failure_run_id === failure?.run_id - && candidate?.platform === failure?.platform - && candidate?.commit === commit - && candidate?.tree === tree - && candidate?.status === "passed" - && Number.isFinite(candidate?.duration_seconds) - && candidate.duration_seconds < 90 - && typeof candidate?.mutation === "string" - && candidate.mutation.length > 0 - ); - if (!probe) { - fail( - `platform failure ${failure?.run_id ?? ""} lacks an exact-head native probe under 90 seconds`, - ); +export function validateAcceptanceProvenance({ + status, + run, + jobs, + repository, + commit, + tree, + digest, +}) { + const escapedRepository = repository.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"); + const target = new RegExp( + `^https://github\\.com/${escapedRepository}/actions/runs/([1-9][0-9]*)$`, + "u", + ).exec(String(status?.target_url ?? "")); + if ( + status?.state !== "success" + || status?.context !== `${STATUS_PREFIX}/${digest}` + || status?.description !== `tree=${tree}` + || status?.creator?.login !== "github-actions[bot]" + || status?.creator?.type !== "Bot" + || !target + ) { + fail("release freeze status is not authenticated Actions acceptance"); + } + if ( + String(run?.id) !== target[1] + || run?.head_sha !== commit + || run?.path !== ".github/workflows/source-proof.yml" + || run?.event !== "workflow_dispatch" + || run?.status !== "completed" + || run?.conclusion !== "success" + || run?.head_repository?.full_name !== repository + ) { + fail("release freeze acceptance run provenance changed"); + } + if (!Array.isArray(jobs)) { + fail("release freeze acceptance jobs are missing"); + } + const requiredJobs = new Map([ + ["freeze-hostile-mutations", "Execute exact-head hostile mutation matrix"], + ["freeze-windows-native-probe", "Run exact-head Windows native probe"], + ["freeze-acceptance", "Publish executable release freeze"], + ]); + for (const [jobName, stepName] of requiredJobs) { + const job = jobs.find((candidate) => candidate?.name === jobName); + if ( + job?.status !== "completed" + || job?.conclusion !== "success" + || job?.head_sha !== commit + || String(job?.run_id) !== String(run.id) + || String(job?.run_attempt) !== String(run.run_attempt) + ) { + fail(`release freeze acceptance job ${jobName} is not a successful exact-run job`); + } + const step = job.steps?.find((candidate) => candidate?.name === stepName); + if (step?.status !== "completed" || step?.conclusion !== "success") { + fail(`release freeze acceptance step ${stepName} did not execute successfully`); + } + if (jobName === "freeze-windows-native-probe") { + const labels = new Set(job.labels ?? []); + for (const label of ["self-hosted", "Windows", "X64", "codestory-vulkan"]) { + if (!labels.has(label)) { + fail(`Windows native probe did not run on protected label ${label}`); + } + } + if (elapsedSeconds(step) >= 90) { + fail("Windows native probe must complete in under 90 seconds"); + } } } + return Number(target[1]); } -export function validateReceipt(receipt, { commit, tree, requiredMutationIds = [] }) { +export function validateReceipt(receipt, { commit, tree }) { if (receipt?.schema !== 1) { fail("freeze receipt schema must be 1"); } @@ -183,12 +231,6 @@ export function validateReceipt(receipt, { commit, tree, requiredMutationIds = [ || receipt.next_permitted_mutation.length === 0) { fail("freeze receipt must name the next permitted mutation"); } - validateMutationReceipt(receipt.hostile_mutations, { - commit, - tree, - requiredIds: requiredMutationIds, - }); - validatePlatformEvidence(receipt.platform_evidence, { commit, tree }); if (receipt.digest !== receiptDigest(receipt)) { fail("freeze receipt digest does not match its contents"); } @@ -226,6 +268,29 @@ function cancelSupersededRuns({ repository, commit, workflows, runs }) { return cancelled; } +function waitForSupersededRunsToStop({ repository, commit, workflows }) { + if ( + !Number.isInteger(CANCEL_POLL_ATTEMPTS) + || CANCEL_POLL_ATTEMPTS < 1 + || !Number.isInteger(CANCEL_POLL_MS) + || CANCEL_POLL_MS < 0 + ) { + fail("cancellation polling configuration is invalid"); + } + for (let attempt = 0; attempt < CANCEL_POLL_ATTEMPTS; attempt += 1) { + const remaining = currentRuns(repository).filter((entry) => + workflows.includes(entry.workflowName) && entry.headSha !== commit + ); + if (remaining.length === 0) { + return; + } + if (attempt + 1 < CANCEL_POLL_ATTEMPTS) { + Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, CANCEL_POLL_MS); + } + } + fail("superseded broad proof remains queued or running after cancellation"); +} + function cancelSuperseded(args) { const repository = required(args, "--repository"); const commit = required(args, "--commit"); @@ -250,15 +315,24 @@ function cancelSuperseded(args) { workflows, runs: before, }); - const cancelledIds = new Set(cancelled.map((entry) => String(entry.database_id))); - const remaining = currentRuns(repository).filter((entry) => - workflows.includes(entry.workflowName) - && entry.headSha !== commit - && !cancelledIds.has(String(entry.databaseId)) - ); - if (remaining.length > 0) { - fail("superseded broad proof remains queued or running after cancellation"); + waitForSupersededRunsToStop({ repository, commit, workflows }); + process.stdout.write(`${JSON.stringify({ cancelled })}\n`); +} + +function invalidateSuperseded(args) { + const repository = required(args, "--repository"); + const commit = required(args, "--commit"); + const workflows = values(args, "--broad-workflow"); + if (workflows.length === 0) { + fail("--broad-workflow is required"); } + const cancelled = cancelSupersededRuns({ + repository, + commit, + workflows, + runs: currentRuns(repository), + }); + waitForSupersededRunsToStop({ repository, commit, workflows }); process.stdout.write(`${JSON.stringify({ cancelled })}\n`); } @@ -325,9 +399,6 @@ function declare(args) { const branch = value(args, "--branch", git(["branch", "--show-current"], repo)); const output = required(args, "--output"); const releasePrNumber = required(args, "--release-pr"); - const mutationPath = required(args, "--mutation-receipt"); - const platformPath = required(args, "--platform-evidence"); - const requiredMutationIds = values(args, "--required-mutation"); const supportPrNumbers = values(args, "--support-pr"); const knownFutureChanges = values(args, "--known-future-change"); const plannedProofActions = values(args, "--planned-proof-action"); @@ -336,13 +407,10 @@ function declare(args) { const nextMutation = required(args, "--next-permitted-mutation"); const broadWorkflows = values(args, "--broad-workflow"); if ( - requiredMutationIds.length === 0 - || plannedProofActions.length === 0 + plannedProofActions.length === 0 || broadWorkflows.length === 0 ) { - fail( - "release freeze requires hostile mutations, planned proof actions, and broad workflow names", - ); + fail("release freeze requires planned proof actions and broad workflow names"); } if (git(["status", "--porcelain=v1", "--untracked-files=all"], repo) !== "") { @@ -361,14 +429,6 @@ function declare(args) { } } - const mutationReceipt = parseJsonFile(mutationPath, "mutation receipt"); - validateMutationReceipt(mutationReceipt, { - commit, - tree, - requiredIds: requiredMutationIds, - }); - const platformEvidence = parseJsonFile(platformPath, "platform evidence"); - validatePlatformEvidence(platformEvidence, { commit, tree }); const acceptedReleasePr = releasePr(repository, releasePrNumber, { branch, commit, @@ -378,26 +438,35 @@ function declare(args) { ); const runs = currentRuns(repository); - const cancelledRuns = has(args, "--cancel-superseded") - ? cancelSupersededRuns({ + const duplicate = runs.find((entry) => + broadWorkflows.includes(entry.workflowName) && entry.headSha === commit + ); + if (duplicate) { + fail( + `unchanged head ${commit} already has active ${duplicate.workflowName} run ${duplicate.databaseId}`, + ); + } + const cancelledRuns = cancelSupersededRuns({ + repository, + commit, + workflows: broadWorkflows, + runs, + }); + if (cancelledRuns.length > 0) { + waitForSupersededRunsToStop({ repository, commit, workflows: broadWorkflows, - runs, - }) - : []; - const cancelledIds = new Set( - cancelledRuns.map((entry) => String(entry.database_id)), - ); + }); + } const remainingRuns = currentRuns(repository); - const superseded = remainingRuns.filter( - (entry) => - broadWorkflows.includes(entry.workflowName) - && entry.headSha !== commit - && !cancelledIds.has(String(entry.databaseId)), + const remainingBroadRun = remainingRuns.find((entry) => + broadWorkflows.includes(entry.workflowName) ); - if (superseded.length > 0) { - fail("superseded broad proof remains queued or running"); + if (remainingBroadRun) { + fail( + `broad proof ${remainingBroadRun.databaseId} remains active before freeze declaration`, + ); } const receipt = { @@ -412,8 +481,6 @@ function declare(args) { integrated_support_prs: integratedSupportPrs, known_future_source_changes: knownFutureChanges, planned_proof_actions: plannedProofActions, - hostile_mutations: mutationReceipt, - platform_evidence: platformEvidence, reusable_evidence: reusableEvidence, invalidated_evidence: invalidatedEvidence, running_workflows: remainingRuns, @@ -421,7 +488,7 @@ function declare(args) { next_permitted_mutation: nextMutation, }; receipt.digest = receiptDigest(receipt); - validateReceipt(receipt, { commit, tree, requiredMutationIds }); + validateReceipt(receipt, { commit, tree }); writeFileSync(output, `${JSON.stringify(receipt, null, 2)}\n`); if (!has(args, "--no-publish-status")) { @@ -431,7 +498,7 @@ function declare(args) { "POST", `repos/${repository}/statuses/${commit}`, "-f", - "state=success", + "state=pending", "-f", `context=${STATUS_PREFIX}/${receipt.digest}`, "-f", @@ -448,28 +515,69 @@ function verifyFile(args) { validateReceipt(receipt, { commit, tree, - requiredMutationIds: values(args, "--required-mutation"), }); process.stdout.write(`${receipt.digest}\n`); } -function verifyStatus(args) { - const repository = required(args, "--repository"); - const commit = required(args, "--commit"); - const tree = required(args, "--tree"); - const digest = required(args, "--receipt-digest"); +function matchingStatus({ repository, commit, tree, digest, state }) { const statuses = JSON.parse(gh([ "api", `repos/${repository}/commits/${commit}/statuses?per_page=100`, ])); - const accepted = statuses.some((status) => - status?.state === "success" + return statuses.find((status) => + status?.state === state && status?.context === `${STATUS_PREFIX}/${digest}` && status?.description === `tree=${tree}` ); - if (!accepted) { +} + +function verifyPending(args) { + const repository = required(args, "--repository"); + const commit = required(args, "--commit"); + const tree = required(args, "--tree"); + const digest = required(args, "--receipt-digest"); + if (!matchingStatus({ repository, commit, tree, digest, state: "pending" })) { + fail("no pending local release freeze declaration matches this exact commit and tree"); + } + process.stdout.write(`${digest}\n`); +} + +function verifyStatus(args) { + const repository = required(args, "--repository"); + const commit = required(args, "--commit"); + const tree = required(args, "--tree"); + const digest = required(args, "--receipt-digest"); + const status = matchingStatus({ + repository, + commit, + tree, + digest, + state: "success", + }); + if (!status) { fail("no successful exact-head release freeze status matches this receipt digest and tree"); } + const target = /\/actions\/runs\/([1-9][0-9]*)$/u.exec(String(status.target_url ?? "")); + if (!target) { + fail("release freeze success status has no authenticated Actions run"); + } + const run = JSON.parse(gh([ + "api", + `repos/${repository}/actions/runs/${target[1]}`, + ])); + const jobsPayload = JSON.parse(gh([ + "api", + `repos/${repository}/actions/runs/${target[1]}/jobs?per_page=100`, + ])); + validateAcceptanceProvenance({ + status, + run, + jobs: jobsPayload.jobs, + repository, + commit, + tree, + digest, + }); process.stdout.write(`${digest}\n`); } @@ -479,14 +587,19 @@ function main() { declare(args); } else if (command === "verify-file") { verifyFile(args); + } else if (command === "verify-pending") { + verifyPending(args); } else if (command === "verify-status") { verifyStatus(args); } else if (command === "cancel-superseded") { cancelSuperseded(args); + } else if (command === "invalidate-superseded") { + invalidateSuperseded(args); } else { fail( "usage: release-freeze-barrier.mjs " - + " ...", + + " ...", ); } } diff --git a/.github/scripts/release-freeze-barrier.test.mjs b/.github/scripts/release-freeze-barrier.test.mjs index 084d6d7ab..0bb8bb6fe 100644 --- a/.github/scripts/release-freeze-barrier.test.mjs +++ b/.github/scripts/release-freeze-barrier.test.mjs @@ -1,47 +1,18 @@ import assert from "node:assert/strict"; import { execFileSync, spawnSync } from "node:child_process"; -import { mkdtempSync, writeFileSync } from "node:fs"; +import { chmodSync, mkdtempSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import test from "node:test"; import { receiptDigest, - validateMutationReceipt, - validatePlatformEvidence, + validateAcceptanceProvenance, validateReceipt, } from "./release-freeze-barrier.mjs"; const COMMIT = "1".repeat(40); const TREE = "2".repeat(40); -const REQUIRED = ["cpu-reentry", "duplicate-source-proof"]; - -function mutationReceipt(overrides = {}) { - return { - commit: COMMIT, - tree: TREE, - cases: REQUIRED.map((id) => ({ id, status: "passed" })), - ...overrides, - }; -} - -function platformEvidence(overrides = {}) { - return { - failures: [{ - run_id: 77, - platform: "windows", - }], - probes: [{ - failure_run_id: 77, - platform: "windows", - commit: COMMIT, - tree: TREE, - status: "passed", - duration_seconds: 5, - mutation: "junction replacement", - }], - ...overrides, - }; -} +const DIGEST = "a".repeat(64); function receipt(overrides = {}) { const candidate = { @@ -63,8 +34,6 @@ function receipt(overrides = {}) { "crates/codestory-llama-sys/per-user-embedding-server-constant-set.json", ], planned_proof_actions: ["source-proof", "calibration", "qualification"], - hostile_mutations: mutationReceipt(), - platform_evidence: platformEvidence(), reusable_evidence: [], invalidated_evidence: [], running_workflows: [], @@ -76,12 +45,8 @@ function receipt(overrides = {}) { return candidate; } -test("an exact clean pushed receipt with hostile and native evidence passes", () => { - validateReceipt(receipt(), { - commit: COMMIT, - tree: TREE, - requiredMutationIds: REQUIRED, - }); +test("an exact clean pushed local declaration passes", () => { + validateReceipt(receipt(), { commit: COMMIT, tree: TREE }); }); for (const [name, mutate, pattern] of [ @@ -97,18 +62,6 @@ for (const [name, mutate, pattern] of [ }, /unsupported future source change/u], ["missing handoff field", (value) => { delete value.running_workflows; }, /running_workflows/u], ["missing next mutation", (value) => { value.next_permitted_mutation = ""; }, /next permitted mutation/u], - ["mutation from another head", (value) => { - value.hostile_mutations.commit = "6".repeat(40); - }, /exact frozen commit and tree/u], - ["named mutation not run", (value) => { - value.hostile_mutations.cases[0].status = "skipped"; - }, /did not pass/u], - ["native probe from another head", (value) => { - value.platform_evidence.probes[0].commit = "7".repeat(40); - }, /native probe under 90 seconds/u], - ["native probe at 90 seconds", (value) => { - value.platform_evidence.probes[0].duration_seconds = 90; - }, /native probe under 90 seconds/u], ["tampered receipt", (value) => { value.planned_proof_actions.push("second-source-proof"); }, /digest/u], @@ -120,34 +73,101 @@ for (const [name, mutate, pattern] of [ candidate.digest = receiptDigest(candidate); } assert.throws( - () => validateReceipt(candidate, { - commit: COMMIT, - tree: TREE, - requiredMutationIds: REQUIRED, - }), + () => validateReceipt(candidate, { commit: COMMIT, tree: TREE }), pattern, ); }); } -test("mutation and native evidence validators reject malformed arrays", () => { - assert.throws( - () => validateMutationReceipt({ commit: COMMIT, tree: TREE }, { - commit: COMMIT, - tree: TREE, - requiredIds: REQUIRED, - }), - /contain cases/u, - ); - assert.throws( - () => validatePlatformEvidence({ failures: {}, probes: [] }, { - commit: COMMIT, - tree: TREE, - }), - /failure and probe arrays/u, - ); +function acceptanceProvenance() { + const runId = 77; + const runAttempt = 2; + const startedAt = "2026-07-30T12:00:00Z"; + const completedAt = "2026-07-30T12:00:06Z"; + const job = (name, stepName, labels = ["ubuntu-latest"]) => ({ + name, + status: "completed", + conclusion: "success", + head_sha: COMMIT, + run_id: runId, + run_attempt: runAttempt, + labels, + steps: [{ + name: stepName, + status: "completed", + conclusion: "success", + started_at: startedAt, + completed_at: completedAt, + }], + }); + return { + status: { + state: "success", + context: `codestory/release-freeze/${DIGEST}`, + description: `tree=${TREE}`, + target_url: `https://github.com/TheGreenCedar/CodeStory/actions/runs/${runId}`, + creator: { login: "github-actions[bot]", type: "Bot" }, + }, + run: { + id: runId, + run_attempt: runAttempt, + head_sha: COMMIT, + path: ".github/workflows/source-proof.yml", + event: "workflow_dispatch", + status: "completed", + conclusion: "success", + head_repository: { full_name: "TheGreenCedar/CodeStory" }, + }, + jobs: [ + job("freeze-hostile-mutations", "Execute exact-head hostile mutation matrix"), + job( + "freeze-windows-native-probe", + "Run exact-head Windows native probe", + ["self-hosted", "Windows", "X64", "codestory-vulkan"], + ), + job("freeze-acceptance", "Publish executable release freeze"), + ], + repository: "TheGreenCedar/CodeStory", + commit: COMMIT, + tree: TREE, + digest: DIGEST, + }; +} + +test("acceptance trusts exact Actions run, job, step, host, and duration provenance", () => { + assert.equal(validateAcceptanceProvenance(acceptanceProvenance()), 77); }); +for (const [name, mutate, pattern] of [ + ["caller-authored success", (value) => { + value.status.creator = { login: "TheGreenCedar", type: "User" }; + }, /not authenticated Actions acceptance/u], + ["cross-head run", (value) => { + value.run.head_sha = "3".repeat(40); + }, /run provenance changed/u], + ["wrong workflow", (value) => { + value.run.path = ".github/workflows/release.yml"; + }, /run provenance changed/u], + ["skipped hostile mutations", (value) => { + value.jobs[0].conclusion = "skipped"; + }, /not a successful exact-run job/u], + ["unprotected Windows runner", (value) => { + value.jobs[1].labels = ["self-hosted", "Windows", "X64"]; + }, /protected label codestory-vulkan/u], + ["90-second Windows probe", (value) => { + value.jobs[1].steps[0].completed_at = "2026-07-30T12:01:30Z"; + }, /under 90 seconds/u], + ["fabricated native step", (value) => { + value.jobs[1].steps[0].conclusion = "failure"; + }, /did not execute successfully/u], +]) { + test(`acceptance rejects ${name}`, () => { + const value = acceptanceProvenance(); + mutate(value); + assert.throws(() => validateAcceptanceProvenance(value), pattern); + }); +} + test("verify-file is executable and rejects a later commit", () => { const root = mkdtempSync(path.join(tmpdir(), "codestory-freeze-")); const receiptPath = path.join(root, "receipt.json"); @@ -164,10 +184,6 @@ test("verify-file is executable and rejects a later commit", () => { COMMIT, "--tree", TREE, - "--required-mutation", - REQUIRED[0], - "--required-mutation", - REQUIRED[1], ], { encoding: "utf8" }, ); @@ -216,14 +232,8 @@ test("declare rejects a dirty worktree before publishing a status", () => { "1", "--output", path.join(root, "receipt.json"), - "--mutation-receipt", - path.join(root, "missing-mutations.json"), - "--platform-evidence", - path.join(root, "missing-platform.json"), "--next-permitted-mutation", "none", - "--required-mutation", - "cpu-reentry", "--planned-proof-action", "source-proof", "--broad-workflow", @@ -235,3 +245,130 @@ test("declare rejects a dirty worktree before publishing a status", () => { assert.notEqual(result.status, 0); assert.match(result.stderr, /clean worktree, including untracked files/u); }); + +test("cancel-superseded rejects a cancellation request that leaves the run active", () => { + const root = mkdtempSync(path.join(tmpdir(), "codestory-freeze-gh-")); + const fakeGh = path.join(root, "gh"); + writeFileSync( + fakeGh, + `#!/bin/sh +if [ "$1 $2" = "run list" ]; then + printf '%s\\n' '[{"databaseId":123,"workflowName":"Exact-head source proof","headSha":"${"9".repeat(40)}","headBranch":"old","status":"in_progress","event":"workflow_dispatch","url":"https://example.invalid/123"}]' + exit 0 +fi +if [ "$1 $2" = "run cancel" ]; then + exit 0 +fi +exit 1 +`, + ); + chmodSync(fakeGh, 0o755); + const script = new URL("./release-freeze-barrier.mjs", import.meta.url); + const result = spawnSync( + process.execPath, + [ + script.pathname, + "cancel-superseded", + "--repository", + "TheGreenCedar/CodeStory", + "--commit", + COMMIT, + "--broad-workflow", + "Exact-head source proof", + ], + { + encoding: "utf8", + env: { + ...process.env, + CODESTORY_FREEZE_CANCEL_POLL_ATTEMPTS: "2", + CODESTORY_FREEZE_CANCEL_POLL_MS: "0", + PATH: `${root}${path.delimiter}${process.env.PATH}`, + }, + }, + ); + assert.notEqual(result.status, 0); + assert.match(result.stderr, /remains queued or running after cancellation/u); +}); + +test("cancel-superseded rejects another active broad run on the unchanged head", () => { + const root = mkdtempSync(path.join(tmpdir(), "codestory-freeze-duplicate-gh-")); + const fakeGh = path.join(root, "gh"); + writeFileSync( + fakeGh, + `#!/bin/sh +if [ "$1 $2" = "run list" ]; then + printf '%s\\n' '[{"databaseId":456,"workflowName":"Exact-head source proof","headSha":"${COMMIT}","headBranch":"candidate","status":"in_progress","event":"workflow_dispatch","url":"https://example.invalid/456"}]' + exit 0 +fi +exit 1 +`, + ); + chmodSync(fakeGh, 0o755); + const script = new URL("./release-freeze-barrier.mjs", import.meta.url); + const result = spawnSync( + process.execPath, + [ + script.pathname, + "cancel-superseded", + "--repository", + "TheGreenCedar/CodeStory", + "--commit", + COMMIT, + "--broad-workflow", + "Exact-head source proof", + ], + { + encoding: "utf8", + env: { + ...process.env, + PATH: `${root}${path.delimiter}${process.env.PATH}`, + }, + }, + ); + assert.notEqual(result.status, 0); + assert.match(result.stderr, /unchanged head.*already has active/u); +}); + +test("automatic invalidation preserves an active proof for the new exact head", () => { + const root = mkdtempSync(path.join(tmpdir(), "codestory-freeze-current-gh-")); + const fakeGh = path.join(root, "gh"); + writeFileSync( + fakeGh, + `#!/bin/sh +if [ "$1 $2" = "run list" ]; then + printf '%s\\n' '[{"databaseId":789,"workflowName":"Exact-head source proof","headSha":"${COMMIT}","headBranch":"candidate","status":"in_progress","event":"workflow_dispatch","url":"https://example.invalid/789"}]' + exit 0 +fi +if [ "$1 $2" = "run cancel" ]; then + exit 9 +fi +exit 1 +`, + ); + chmodSync(fakeGh, 0o755); + const script = new URL("./release-freeze-barrier.mjs", import.meta.url); + const result = spawnSync( + process.execPath, + [ + script.pathname, + "invalidate-superseded", + "--repository", + "TheGreenCedar/CodeStory", + "--commit", + COMMIT, + "--broad-workflow", + "Exact-head source proof", + ], + { + encoding: "utf8", + env: { + ...process.env, + CODESTORY_FREEZE_CANCEL_POLL_ATTEMPTS: "2", + CODESTORY_FREEZE_CANCEL_POLL_MS: "0", + PATH: `${root}${path.delimiter}${process.env.PATH}`, + }, + }, + ); + assert.equal(result.status, 0, result.stderr); + assert.deepEqual(JSON.parse(result.stdout), { cancelled: [] }); +}); diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index d07cdc9d5..70a6840d5 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -67,6 +67,7 @@ jobs: checks: read contents: write pull-requests: read + statuses: read uses: ./.github/workflows/release.yml with: version: ${{ needs.detect-version.outputs.version }} diff --git a/.github/workflows/packaged-platform-pr.yml b/.github/workflows/packaged-platform-pr.yml index 08ebe5643..c0344d45b 100644 --- a/.github/workflows/packaged-platform-pr.yml +++ b/.github/workflows/packaged-platform-pr.yml @@ -44,6 +44,7 @@ permissions: actions: write contents: read pull-requests: read + statuses: read concurrency: group: proof-${{ github.sha }}-${{ inputs.mode || 'platform' }}-${{ inputs.pr_number || 'dev' }} @@ -241,16 +242,11 @@ jobs: tree="$( gh api "repos/$GITHUB_REPOSITORY/git/commits/$SOURCE_SHA" --jq '.tree.sha' )" - gh api "repos/$GITHUB_REPOSITORY/commits/$SOURCE_SHA/statuses?per_page=100" \ - | jq -e \ - --arg context "codestory/release-freeze/$FREEZE_RECEIPT_DIGEST" \ - --arg description "tree=$tree" \ - 'any(.[]; .state == "success" - and .context == $context - and .description == $description)' >/dev/null || { - echo "::error::No executable release freeze accepts source head $SOURCE_SHA and tree $tree." - exit 1 - } + node .github/scripts/release-freeze-barrier.mjs verify-status \ + --repository "$GITHUB_REPOSITORY" \ + --commit "$SOURCE_SHA" \ + --tree "$tree" \ + --receipt-digest "$FREEZE_RECEIPT_DIGEST" - name: Require successful accepted-head source proof if: steps.resolve.outputs.mode != 'integration' @@ -266,8 +262,18 @@ jobs: --jq '.jobs[] | select(.name == "full-source-gate" and .conclusion == "success") | .id' \ | grep -q . then + run_attempt="$( + gh api "repos/$GITHUB_REPOSITORY/actions/runs/$run_id" --jq '.run_attempt' + )" + artifact_name="release-cell-prepublish-source-attempt-$run_attempt" + artifact_count="$( + gh api "repos/$GITHUB_REPOSITORY/actions/runs/$run_id/artifacts?per_page=100" \ + | jq --arg name "$artifact_name" \ + '[.artifacts[] | select(.name == $name and .expired == false)] | length' + )" + test "$artifact_count" = 1 || continue accepted=true - echo "Accepted source proof run $run_id for frozen source head $SOURCE_SHA." + echo "Accepted source proof run $run_id and $artifact_name for frozen source head $SOURCE_SHA." break fi done < <( @@ -439,6 +445,7 @@ jobs: with: ref: ${{ needs.route.outputs.head_sha }} proof_key: ${{ needs.route.outputs.proof_key }} + version: ${{ needs.route.outputs.version }} freeze_receipt_digest: ${{ inputs.freeze_receipt_digest }} packaged-proof: diff --git a/.github/workflows/release-freeze-invalidation.yml b/.github/workflows/release-freeze-invalidation.yml new file mode 100644 index 000000000..54abbdc30 --- /dev/null +++ b/.github/workflows/release-freeze-invalidation.yml @@ -0,0 +1,58 @@ +name: Release freeze invalidation + +on: + pull_request: + branches: + - dev/codestory-next + types: [synchronize] + push: + branches: + - dev/codestory-next + +permissions: + actions: write + contents: read + statuses: read + +concurrency: + group: release-freeze-invalidation-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + invalidate: + name: Cancel proof for a superseded frozen head + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v5 + + - name: Invalidate a superseded release freeze + shell: bash + env: + AFTER_SHA: ${{ github.event.after || github.sha }} + BEFORE_SHA: ${{ github.event.before }} + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + printf '%s' "$BEFORE_SHA" | grep -Eq '^[0-9a-f]{40}$' + printf '%s' "$AFTER_SHA" | grep -Eq '^[0-9a-f]{40}$' + test "$BEFORE_SHA" != "$AFTER_SHA" + has_freeze="$( + gh api "repos/$GITHUB_REPOSITORY/commits/$BEFORE_SHA/statuses?per_page=100" \ + | jq \ + '[.[] | select( + (.state == "pending" or .state == "success") + and (.context | startswith("codestory/release-freeze/")) + )] | length' + )" + if [ "$has_freeze" = 0 ]; then + echo "Previous head $BEFORE_SHA was not a declared release candidate." + exit 0 + fi + node .github/scripts/release-freeze-barrier.mjs invalidate-superseded \ + --repository "$GITHUB_REPOSITORY" \ + --commit "$AFTER_SHA" \ + --broad-workflow "Exact-head source proof" \ + --broad-workflow "Platform and integration proof" \ + --broad-workflow "Release" \ + --broad-workflow "Auto Release" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2924a2e40..fe51bea0e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,6 +45,7 @@ permissions: checks: read contents: read pull-requests: read + statuses: read concurrency: group: release-${{ inputs.version }} @@ -236,8 +237,18 @@ jobs: gh api "repos/$GITHUB_REPOSITORY/actions/runs/$run_id/jobs?per_page=100" \ --jq '.jobs[] | select(.name == "full-source-gate" and .conclusion == "success") | .id' \ | grep -q . || continue + run_attempt="$( + gh api "repos/$GITHUB_REPOSITORY/actions/runs/$run_id" --jq '.run_attempt' + )" + artifact_name="release-cell-prepublish-source-attempt-$run_attempt" + artifact_count="$( + gh api "repos/$GITHUB_REPOSITORY/actions/runs/$run_id/artifacts?per_page=100" \ + | jq --arg name "$artifact_name" \ + '[.artifacts[] | select(.name == $name and .expired == false)] | length' + )" + test "$artifact_count" = 1 || continue entries+=("source_behavior=$run_id:$SOURCE_SHA") - echo "Reusing the one source proof from run $run_id at calibration source $SOURCE_SHA." + echo "Reusing the one source proof and $artifact_name from run $run_id at calibration source $SOURCE_SHA." break done < <( gh api --paginate \ @@ -265,18 +276,21 @@ jobs: .state == "success" and (.context | startswith("codestory/release-freeze/")) and .description == $description + and .creator.login == "github-actions[bot]" + and .creator.type == "Bot" + and (.target_url | test( + "^https://github.com/TheGreenCedar/CodeStory/actions/runs/[1-9][0-9]*$" + )) ) | .context | sub("^codestory/release-freeze/"; "")] | unique | if length == 1 then .[0] else "" end' \ <<<"$statuses" )" fi printf '%s' "$freeze_digest" | grep -Eq '^[0-9a-f]{64}$' - jq -e \ - --arg context "codestory/release-freeze/$freeze_digest" \ - --arg description "tree=$SOURCE_TREE" \ - 'any(.[]; .state == "success" - and .context == $context - and .description == $description)' \ - <<<"$statuses" >/dev/null + node .github/scripts/release-freeze-barrier.mjs verify-status \ + --repository "$GITHUB_REPOSITORY" \ + --commit "$SOURCE_SHA" \ + --tree "$SOURCE_TREE" \ + --receipt-digest "$freeze_digest" { echo "reuse=$reuse" @@ -345,7 +359,6 @@ jobs: ref: ${{ github.sha }} proof_key: release-${{ needs.preflight.outputs.version }} version: ${{ needs.preflight.outputs.version }} - emit_release_cells: true freeze_receipt_digest: ${{ needs.preflight.outputs.freeze_receipt_digest }} packaged-proof: diff --git a/.github/workflows/source-proof.yml b/.github/workflows/source-proof.yml index 2a5dea4e4..52753387d 100644 --- a/.github/workflows/source-proof.yml +++ b/.github/workflows/source-proof.yml @@ -10,13 +10,8 @@ on: required: true type: string version: - required: false - default: "" + required: true type: string - emit_release_cells: - required: false - default: false - type: boolean freeze_receipt_digest: description: Digest of the exact-head release freeze status. required: true @@ -39,16 +34,17 @@ on: description: Release version whose source cell this accepted proof emits. required: true type: string - emit_release_cells: - description: Emit the source cell that qualification and publication reuse. + acceptance_only: + description: Execute only the hostile mutation and protected Windows native-probe freeze barrier. required: false - default: true + default: false type: boolean permissions: actions: write contents: read pull-requests: read + statuses: write concurrency: group: source-proof-${{ github.sha }}-${{ inputs.proof_key || inputs.pr_number || github.ref }} @@ -151,6 +147,7 @@ jobs: - name: Reuse a completed gate for this exact head id: reuse + if: ${{ !inputs.acceptance_only }} shell: bash env: GH_TOKEN: ${{ github.token }} @@ -164,8 +161,18 @@ jobs: --jq '.jobs[] | select(.name == "full-source-gate" and .conclusion == "success") | .id' \ | grep -q . then + run_attempt="$( + gh api "repos/$GITHUB_REPOSITORY/actions/runs/$run_id" --jq '.run_attempt' + )" + artifact_name="release-cell-prepublish-source-attempt-$run_attempt" + artifact_count="$( + gh api "repos/$GITHUB_REPOSITORY/actions/runs/$run_id/artifacts?per_page=100" \ + | jq --arg name "$artifact_name" \ + '[.artifacts[] | select(.name == $name and .expired == false)] | length' + )" + test "$artifact_count" = 1 || continue reuse=true - echo "Reusing full-source-gate from run $run_id for exact head $HEAD_SHA." + echo "Reusing full-source-gate and $artifact_name from run $run_id for exact head $HEAD_SHA." break fi done < <( @@ -180,6 +187,7 @@ jobs: shell: bash env: GH_TOKEN: ${{ github.token }} + ACCEPTANCE_ONLY: ${{ inputs.acceptance_only }} FREEZE_RECEIPT_DIGEST: ${{ inputs.freeze_receipt_digest }} HEAD_SHA: ${{ steps.resolve.outputs.ref }} run: | @@ -188,16 +196,156 @@ jobs: tree="$( gh api "repos/$GITHUB_REPOSITORY/git/commits/$HEAD_SHA" --jq '.tree.sha' )" - gh api "repos/$GITHUB_REPOSITORY/commits/$HEAD_SHA/statuses?per_page=100" \ - | jq -e \ - --arg context "codestory/release-freeze/$FREEZE_RECEIPT_DIGEST" \ - --arg description "tree=$tree" \ - 'any(.[]; .state == "success" - and .context == $context - and .description == $description)' >/dev/null || { - echo "::error::No executable release freeze accepts exact head $HEAD_SHA and tree $tree." - exit 1 + command=verify-status + if [ "$ACCEPTANCE_ONLY" = true ]; then + command=verify-pending + fi + node .github/scripts/release-freeze-barrier.mjs "$command" \ + --repository "$GITHUB_REPOSITORY" \ + --commit "$HEAD_SHA" \ + --tree "$tree" \ + --receipt-digest "$FREEZE_RECEIPT_DIGEST" + + freeze-hostile-mutations: + name: freeze-hostile-mutations + if: inputs.acceptance_only + needs: resolve + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v5 + with: + ref: ${{ needs.resolve.outputs.ref }} + + - uses: actions/setup-node@v5 + with: + node-version: "24" + package-manager-cache: false + + - name: Install workflow policy dependencies + run: npm ci --ignore-scripts + + - name: Execute exact-head hostile mutation matrix + run: >- + node --test + .github/scripts/check-workflow-policy.test.mjs + .github/scripts/release-freeze-barrier.test.mjs + .github/scripts/cargo-build-artifacts.test.mjs + .github/scripts/candidate-archive-store.test.mjs + + freeze-windows-native-probe: + name: freeze-windows-native-probe + if: inputs.acceptance_only + needs: resolve + runs-on: [self-hosted, Windows, X64, codestory-vulkan] + timeout-minutes: 5 + steps: + - uses: actions/checkout@v5 + with: + ref: ${{ needs.resolve.outputs.ref }} + + - name: Run exact-head Windows native probe + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + $probeRoot = Join-Path $env:RUNNER_TEMP ( + "codestory-cargo-hardlink-probe-$env:GITHUB_RUN_ID-$env:GITHUB_RUN_ATTEMPT" + ) + if (Test-Path -LiteralPath $probeRoot) { + throw "native probe root already exists: $probeRoot" + } + try { + cargo new --quiet --bin --name cargo-hardlink-probe $probeRoot + if ($LASTEXITCODE -ne 0) { + throw "cargo new failed" + } + $clock = [Diagnostics.Stopwatch]::StartNew() + $vswhere = Join-Path ${env:ProgramFiles(x86)} ( + "Microsoft Visual Studio/Installer/vswhere.exe" + ) + $visualStudio = & $vswhere -latest -products "*" ` + -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 ` + -property installationPath + $vsDevCmd = Join-Path $visualStudio "Common7/Tools/VsDevCmd.bat" + $build = ( + "`"$vsDevCmd`" -arch=x64 -host_arch=x64 >nul " + + "&& cd /d `"$probeRoot`" && cargo build --release --quiet" + ) + & cmd.exe /d /s /c $build + if ($LASTEXITCODE -ne 0) { + throw "tiny Cargo release probe failed" + } + node --test .github/scripts/cargo-build-artifacts.test.mjs + if ($LASTEXITCODE -ne 0) { + throw "exact-head Windows artifact selector mutations failed" + } + $rootExe = Join-Path $probeRoot "target/release/cargo-hardlink-probe.exe" + $depsExe = Join-Path $probeRoot "target/release/deps/cargo_hardlink_probe.exe" + $identityScript = @' + const fs = require("node:fs"); + const [root, deps] = process.argv.slice(1); + const left = fs.statSync(root, { bigint: true }); + const right = fs.statSync(deps, { bigint: true }); + if ( + left.dev !== right.dev + || left.ino !== right.ino + || left.nlink !== 2n + || right.nlink !== 2n + ) { + throw new Error("Cargo release root/deps outputs are not one native two-link file"); + } + console.log(JSON.stringify({ + device: String(left.dev), + inode: String(left.ino), + nlink: String(left.nlink), + })); + '@ + node -e $identityScript $rootExe $depsExe + if ($LASTEXITCODE -ne 0) { + throw "Cargo native hardlink identity probe failed" + } + $clock.Stop() + if ($clock.Elapsed.TotalSeconds -ge 90) { + throw "native probe took $($clock.Elapsed.TotalSeconds) seconds" } + "native_probe_seconds=$([Math]::Round($clock.Elapsed.TotalSeconds, 3))" + } finally { + Remove-Item -LiteralPath $probeRoot -Recurse -Force -ErrorAction SilentlyContinue + } + + freeze-acceptance: + name: freeze-acceptance + if: >- + always() && + inputs.acceptance_only && + needs.resolve.result == 'success' && + needs.freeze-hostile-mutations.result == 'success' && + needs.freeze-windows-native-probe.result == 'success' + needs: + - resolve + - freeze-hostile-mutations + - freeze-windows-native-probe + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Publish executable release freeze + shell: bash + env: + GH_TOKEN: ${{ github.token }} + FREEZE_RECEIPT_DIGEST: ${{ inputs.freeze_receipt_digest }} + HEAD_SHA: ${{ needs.resolve.outputs.ref }} + run: | + set -euo pipefail + tree="$( + gh api "repos/$GITHUB_REPOSITORY/git/commits/$HEAD_SHA" --jq '.tree.sha' + )" + gh api \ + --method POST \ + "repos/$GITHUB_REPOSITORY/statuses/$HEAD_SHA" \ + -f state=success \ + -f "context=codestory/release-freeze/$FREEZE_RECEIPT_DIGEST" \ + -f "description=tree=$tree" \ + -f "target_url=$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" full-source-gate: name: full-source-gate @@ -206,7 +354,7 @@ jobs: # and cannot reach a different answer, which is what made re-labelling a PR expensive. Release # runs (workflow_call, which always supplies `ref`) never take this path: their chain requires # the job to execute. - if: needs.resolve.outputs.reuse != 'true' + if: ${{ !inputs.acceptance_only && needs.resolve.outputs.reuse != 'true' }} runs-on: ubuntu-latest timeout-minutes: 60 steps: @@ -452,7 +600,6 @@ jobs: cargo test --workspace --doc --locked - name: Emit authenticated source release cell - if: inputs.emit_release_cells shell: bash env: INPUT_VERSION: ${{ inputs.version }} @@ -472,7 +619,7 @@ jobs: --out target/release-cells/source_behavior.json - name: Upload authenticated source release cell - if: success() && inputs.emit_release_cells + if: success() uses: actions/upload-artifact@v7.0.1 with: name: release-cell-prepublish-source-attempt-${{ github.run_attempt }} @@ -483,7 +630,7 @@ jobs: retrieval-generalization: name: retrieval-generalization needs: resolve - if: needs.resolve.outputs.reuse != 'true' + if: ${{ !inputs.acceptance_only && needs.resolve.outputs.reuse != 'true' }} runs-on: ubuntu-latest timeout-minutes: 5 steps: diff --git a/benchmarks/release-evidence/fixtures/candidate.json b/benchmarks/release-evidence/fixtures/candidate.json index dc6a7b1cf..1d0fdbcb1 100644 --- a/benchmarks/release-evidence/fixtures/candidate.json +++ b/benchmarks/release-evidence/fixtures/candidate.json @@ -62,7 +62,7 @@ }, "release_claims": { "graph_schema": "codestory.release-claims/v1", - "graph_sha256": "74b22ad4023161484dcd9a5ef141b9825456f95137434a2ea3a0ba8ef515d364", + "graph_sha256": "2d4012d77d9ca94e22d6a77c24e54e0d0cbfdc941e288f1b53a6da63fce89526", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "requested_claims": [ @@ -85,7 +85,7 @@ "type": "performance", "tier": "live_behavior", "status": "measured", - "graph_sha256": "74b22ad4023161484dcd9a5ef141b9825456f95137434a2ea3a0ba8ef515d364", + "graph_sha256": "2d4012d77d9ca94e22d6a77c24e54e0d0cbfdc941e288f1b53a6da63fce89526", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { @@ -106,7 +106,7 @@ "type": "answer_quality", "tier": "answer_quality", "status": "pass", - "graph_sha256": "74b22ad4023161484dcd9a5ef141b9825456f95137434a2ea3a0ba8ef515d364", + "graph_sha256": "2d4012d77d9ca94e22d6a77c24e54e0d0cbfdc941e288f1b53a6da63fce89526", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { diff --git a/benchmarks/release-evidence/fixtures/report.json b/benchmarks/release-evidence/fixtures/report.json index 0a89b8383..604cccc32 100644 --- a/benchmarks/release-evidence/fixtures/report.json +++ b/benchmarks/release-evidence/fixtures/report.json @@ -7,7 +7,7 @@ "baseline_id": "ci-contract-v1@1111111111111111111111111111111111111111", "baseline_sha256": "0bbbe6dd8b4000151edf7b1270959d08e94e08db876e7f2372b25613e0f237c1", "candidate_path": "benchmarks/release-evidence/fixtures/candidate.json", - "candidate_sha256": "dd96364938407289c45793d74de63084ad57619229c242096233129a7c6ff83c", + "candidate_sha256": "b35b494fe9ec9854100c8e1772606a74a0c613721f6552f51558f5f575450230", "artifact_paths": [ { "path": "candidate-stats.json", @@ -26,7 +26,7 @@ "type": "performance", "tier": "live_behavior", "status": "pass", - "graph_sha256": "74b22ad4023161484dcd9a5ef141b9825456f95137434a2ea3a0ba8ef515d364", + "graph_sha256": "2d4012d77d9ca94e22d6a77c24e54e0d0cbfdc941e288f1b53a6da63fce89526", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { @@ -47,7 +47,7 @@ "type": "answer_quality", "tier": "answer_quality", "status": "pass", - "graph_sha256": "74b22ad4023161484dcd9a5ef141b9825456f95137434a2ea3a0ba8ef515d364", + "graph_sha256": "2d4012d77d9ca94e22d6a77c24e54e0d0cbfdc941e288f1b53a6da63fce89526", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { @@ -69,7 +69,7 @@ "schema": "codestory.release-claim-evaluation/v1", "status": "pass", "graph_schema": "codestory.release-claims/v1", - "graph_sha256": "74b22ad4023161484dcd9a5ef141b9825456f95137434a2ea3a0ba8ef515d364", + "graph_sha256": "2d4012d77d9ca94e22d6a77c24e54e0d0cbfdc941e288f1b53a6da63fce89526", "evidence_selection": "all_matching_rows_must_pass", "expected_commit": "2222222222222222222222222222222222222222", "evaluated_at": "2026-07-21T02:13:20.738Z", diff --git a/release-claims.json b/release-claims.json index 1c399b1c4..cd5e5ab81 100644 --- a/release-claims.json +++ b/release-claims.json @@ -1493,14 +1493,36 @@ "packaged-platform-pr.yml", "release.yml" ], + "invalidation_workflow": "release-freeze-invalidation.yml", "coordinator_only_workflows": [ "macos-metal-proof.yml", "windows-vulkan-proof.yml", "linux-vulkan-proof.yml" ], + "acceptance": { + "producer_workflow": "source-proof.yml", + "event": "workflow_dispatch", + "hostile_job": "freeze-hostile-mutations", + "hostile_step": "Execute exact-head hostile mutation matrix", + "windows_job": "freeze-windows-native-probe", + "windows_step": "Run exact-head Windows native probe", + "windows_runner": [ + "self-hosted", + "Windows", + "X64", + "codestory-vulkan" + ], + "windows_probe_max_seconds": 90, + "publisher_job": "freeze-acceptance", + "publisher_step": "Publish executable release freeze", + "status_creator": "github-actions[bot]" + }, "single_source_proof": { "producer_workflow": "source-proof.yml", "producer_job": "full-source-gate", + "artifact": "release-cell-prepublish-source-attempt-${{ github.run_attempt }}", + "artifact_required_unexpired": true, + "cell_emission": "unconditional_on_success", "accepted_source": "freeze_receipt.commit", "frozen_descendant": "constant_set.freeze_record.selection_source_commit", "reuse_validation": [ diff --git a/scripts/codestory-release-claims.mjs b/scripts/codestory-release-claims.mjs index f5b4a3c41..54b0b7f2b 100644 --- a/scripts/codestory-release-claims.mjs +++ b/scripts/codestory-release-claims.mjs @@ -1589,11 +1589,67 @@ export function validateReleaseClaimGraph(graph) { "workflow_policy.release_freeze_barrier.broad_entry_workflows", { nonEmpty: true }, ); + if (freeze.invalidation_workflow !== "release-freeze-invalidation.yml") { + fail( + "workflow_policy.release_freeze_barrier.invalidation_workflow must name " + + "release-freeze-invalidation.yml", + ); + } stringArray( freeze.coordinator_only_workflows, "workflow_policy.release_freeze_barrier.coordinator_only_workflows", { nonEmpty: true }, ); + const acceptance = object( + freeze.acceptance, + "workflow_policy.release_freeze_barrier.acceptance", + ); + for (const field of [ + "producer_workflow", + "event", + "hostile_job", + "hostile_step", + "windows_job", + "windows_step", + "publisher_job", + "publisher_step", + "status_creator", + ]) { + nonEmptyText( + acceptance[field], + `workflow_policy.release_freeze_barrier.acceptance.${field}`, + ); + } + const windowsRunner = stringArray( + acceptance.windows_runner, + "workflow_policy.release_freeze_barrier.acceptance.windows_runner", + { nonEmpty: true }, + ); + if ( + JSON.stringify([...windowsRunner].sort()) + !== JSON.stringify([ + "self-hosted", + "Windows", + "X64", + "codestory-vulkan", + ].sort()) + ) { + fail( + "workflow_policy.release_freeze_barrier.acceptance.windows_runner " + + "must name the protected Windows Vulkan runner", + ); + } + if ( + acceptance.producer_workflow !== "source-proof.yml" + || acceptance.event !== "workflow_dispatch" + || acceptance.windows_probe_max_seconds !== 90 + || acceptance.status_creator !== "github-actions[bot]" + ) { + fail( + "workflow_policy.release_freeze_barrier.acceptance must bind the exact " + + "Actions producer, event, protected probe budget, and status creator", + ); + } const singleSource = object( freeze.single_source_proof, "workflow_policy.release_freeze_barrier.single_source_proof", @@ -1606,6 +1662,22 @@ export function validateReleaseClaimGraph(graph) { singleSource.producer_job, "workflow_policy.release_freeze_barrier.single_source_proof.producer_job", ); + nonEmptyText( + singleSource.artifact, + "workflow_policy.release_freeze_barrier.single_source_proof.artifact", + ); + if (singleSource.artifact_required_unexpired !== true) { + fail( + "workflow_policy.release_freeze_barrier.single_source_proof " + + "must require an unexpired source-cell artifact", + ); + } + if (singleSource.cell_emission !== "unconditional_on_success") { + fail( + "workflow_policy.release_freeze_barrier.single_source_proof " + + "must emit its source cell unconditionally after successful proof", + ); + } stringArray( singleSource.reuse_validation, "workflow_policy.release_freeze_barrier.single_source_proof.reuse_validation", diff --git a/scripts/tests/codestory-release-claims.test.mjs b/scripts/tests/codestory-release-claims.test.mjs index 7f0822643..935d7f666 100644 --- a/scripts/tests/codestory-release-claims.test.mjs +++ b/scripts/tests/codestory-release-claims.test.mjs @@ -163,6 +163,40 @@ test("versioned claim graph has one deterministic digest and all declared contro .single_source_proof.post_calibration_fallback_allowed, false, ); + assert.deepEqual( + graph.workflow_policy.release_freeze_barrier.acceptance, + { + producer_workflow: "source-proof.yml", + event: "workflow_dispatch", + hostile_job: "freeze-hostile-mutations", + hostile_step: "Execute exact-head hostile mutation matrix", + windows_job: "freeze-windows-native-probe", + windows_step: "Run exact-head Windows native probe", + windows_runner: ["self-hosted", "Windows", "X64", "codestory-vulkan"], + windows_probe_max_seconds: 90, + publisher_job: "freeze-acceptance", + publisher_step: "Publish executable release freeze", + status_creator: "github-actions[bot]", + }, + ); + assert.equal( + graph.workflow_policy.release_freeze_barrier.invalidation_workflow, + "release-freeze-invalidation.yml", + ); + assert.deepEqual( + { + artifact: graph.workflow_policy.release_freeze_barrier.single_source_proof.artifact, + artifact_required_unexpired: graph.workflow_policy.release_freeze_barrier + .single_source_proof.artifact_required_unexpired, + cell_emission: graph.workflow_policy.release_freeze_barrier + .single_source_proof.cell_emission, + }, + { + artifact: "release-cell-prepublish-source-attempt-${{ github.run_attempt }}", + artifact_required_unexpired: true, + cell_emission: "unconditional_on_success", + }, + ); }); test("claim graph freezes one exact Windows release graph and protected content-addressed reuse", () => { @@ -616,6 +650,30 @@ test("graph rejects ambiguous dependencies and unstructured proof lanes", () => /identity undeclared_identity must declare a format/u, ); + const unprotectedFreezeProbe = structuredClone(graph); + unprotectedFreezeProbe.workflow_policy.release_freeze_barrier + .acceptance.windows_runner = ["windows-latest"]; + assert.throws( + () => validateReleaseClaimGraph(unprotectedFreezeProbe), + /release_freeze_barrier\.acceptance\.windows_runner/u, + ); + + const missingInvalidation = structuredClone(graph); + delete missingInvalidation.workflow_policy.release_freeze_barrier + .invalidation_workflow; + assert.throws( + () => validateReleaseClaimGraph(missingInvalidation), + /release_freeze_barrier\.invalidation_workflow/u, + ); + + const conditionalSourceCell = structuredClone(graph); + conditionalSourceCell.workflow_policy.release_freeze_barrier + .single_source_proof.cell_emission = "caller_opt_in"; + assert.throws( + () => validateReleaseClaimGraph(conditionalSourceCell), + /must emit its source cell unconditionally/u, + ); + // A non-claim that withholds less than the lost host actually produced would leave a live claim // resting on a proof that never ran, so the withheld set is checked against the graph itself. const partialNonClaim = structuredClone(graph); diff --git a/scripts/tests/fixtures/release-claims/positive.json b/scripts/tests/fixtures/release-claims/positive.json index 66ec2695f..3237bd17e 100644 --- a/scripts/tests/fixtures/release-claims/positive.json +++ b/scripts/tests/fixtures/release-claims/positive.json @@ -17,7 +17,7 @@ "type": "source_behavior", "tier": "source", "status": "pass", - "graph_sha256": "74b22ad4023161484dcd9a5ef141b9825456f95137434a2ea3a0ba8ef515d364", + "graph_sha256": "2d4012d77d9ca94e22d6a77c24e54e0d0cbfdc941e288f1b53a6da63fce89526", "observed_at": "2026-07-16T11:00:00.000Z", "expires_at": "2026-07-17T11:00:00.000Z", "identity": { From ca16670ab3585dbeed9f0158016556ab62472a39 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Thu, 30 Jul 2026 09:11:18 -0500 Subject: [PATCH 19/28] move freeze authority into actions --- .github/scripts/check-workflow-policy.mjs | 292 ++++++++++++- .../scripts/check-workflow-policy.test.mjs | 187 +++++++- .github/scripts/release-freeze-barrier.mjs | 403 ++++++++++++------ .../scripts/release-freeze-barrier.test.mjs | 207 +++++++-- .github/workflows/auto-release.yml | 1 - .github/workflows/packaged-platform-pr.yml | 6 +- .../workflows/release-freeze-invalidation.yml | 32 +- .github/workflows/release.yml | 48 +-- .github/workflows/source-proof.yml | 114 ++++- .../release-evidence/fixtures/candidate.json | 6 +- .../release-evidence/fixtures/report.json | 8 +- release-claims.json | 9 +- scripts/codestory-release-claims.mjs | 26 +- .../tests/codestory-release-claims.test.mjs | 41 ++ .../fixtures/release-claims/positive.json | 2 +- 15 files changed, 1103 insertions(+), 279 deletions(-) diff --git a/.github/scripts/check-workflow-policy.mjs b/.github/scripts/check-workflow-policy.mjs index 546d3bd80..9ea9c562b 100644 --- a/.github/scripts/check-workflow-policy.mjs +++ b/.github/scripts/check-workflow-policy.mjs @@ -913,7 +913,7 @@ const packagedPlatformWorkflowDigest = // made advisory, parked in dead code, or followed by a payload substitution // while leaving the expected tokens in place. const packagedPlatformCoordinatorWorkflowDigest = - "26e3e2a92d959a46f8ef6173d0531b331cbd824344400629aa2e5b13c6286d33"; + "24916c12c5242695460cddf8999518b053ae80a2286fb6706f6cf4c71e49a3e1"; const frozenCandidateQualityWorkflowDigest = "92d0a7ab0e0df63dacd5cc3ef0b58500a6578036494c329aa35279048734f173"; const macosMetalWorkflowDigest = @@ -2141,7 +2141,6 @@ function validatePluginAndDraftWorkflows(workflows, violations, graph) { requireStepRun(violations, sourceFile, resolve, "Require executable release freeze", [ "repos/$GITHUB_REPOSITORY/git/commits/$HEAD_SHA", "release-freeze-barrier.mjs", - "verify-pending", "verify-status", '--receipt-digest "$FREEZE_RECEIPT_DIGEST"', ]); @@ -2740,9 +2739,17 @@ function validateReleaseCoordinator(workflows, violations, graph) { 'artifact_name="release-cell-prepublish-source-attempt-$run_attempt"', ".expired == false", 'test "$artifact_count" = 1 || continue', - "release-freeze-barrier.mjs verify-status", - '--receipt-digest "$freeze_digest"', ]); + forbidStepRun( + violations, + releaseFile, + requireJob(violations, releaseFile, release, "preflight"), + "Resolve reusable prior evidence", + [ + "release-freeze-barrier.mjs verify-status", + "freeze_receipt_digest", + ], + ); const closeout = requireJob(violations, releaseFile, release, "pre-publish-closeout"); requireStepRun(violations, releaseFile, closeout, "Authenticate pre-publish Actions provenance", [ '--reuse "$REUSE_SELECTION"', @@ -2756,10 +2763,9 @@ function validateReleaseCoordinator(workflows, violations, graph) { add( violations, object(source.with).version === "${{ needs.preflight.outputs.version }}" - && object(source.with).freeze_receipt_digest - === "${{ needs.preflight.outputs.freeze_receipt_digest }}" + && object(source.with).freeze_receipt_digest === "" && object(source.with).emit_release_cells === undefined, - `${releaseFile} unreachable source fallback must retain the accepted freeze identity`, + `${releaseFile} unreachable source fallback must fail closed without a post-calibration freeze`, ); const packaged = requireJob(violations, releaseFile, release, "packaged-proof"); @@ -5009,6 +5015,12 @@ function validatePackagedCoordinator(workflows, violations, graph) { INPUT_CALIBRATION_RUN_ID: "${{ inputs.calibration_bundle_run_id }}", }); requireExactResolverContract(violations, file, route, platformResolverContractDigest); + add( + violations, + namedStep(route, "Require executable release freeze")?.if + === "steps.resolve.outputs.mode != 'qualification'", + `${file} active freeze status must gate only pre-calibration proof modes`, + ); requireStepRun(violations, file, route, "Require executable release freeze", [ "repos/$GITHUB_REPOSITORY/git/commits/$SOURCE_SHA", "release-freeze-barrier.mjs verify-status", @@ -7962,14 +7974,25 @@ export function releaseProofCpuSelectorViolations( export function releaseFreezeBarrierWorkflowViolations( workflows, graph = loadReleaseClaimGraph(repositoryRoot), + barrierSource = fs.readFileSync( + path.join(repositoryRoot, ".github", "scripts", "release-freeze-barrier.mjs"), + "utf8", + ), ) { const violations = []; + for (const [file, workflow] of workflows) { + add( + violations, + !scalarStrings(workflow).some(value => value.includes("verify-pending")), + `[freeze_barrier] ${file} must never trust a caller-authored pending freeze`, + ); + } const freeze = object(graph.workflow_policy.release_freeze_barrier); const acceptance = object(freeze.acceptance); const singleSource = object(freeze.single_source_proof); add( violations, - freeze.schema === 1 + freeze.schema === 2 && freeze.script === ".github/scripts/release-freeze-barrier.mjs" && freeze.status_context_prefix === "codestory/release-freeze" && sameMembers(list(freeze.allowed_future_source_changes), [ @@ -7977,6 +8000,13 @@ export function releaseFreezeBarrierWorkflowViolations( ]) && freeze.invalidation_workflow === "release-freeze-invalidation.yml" && acceptance.producer_workflow === "source-proof.yml" + && acceptance.receipt_authority === "github_actions" + && acceptance.receipt_artifact + === "release-freeze-receipt-attempt-${{ github.run_attempt }}" + && acceptance.receipt_file === "release-freeze-receipt.json" + && acceptance.receipt_producer_job === "resolve" + && acceptance.status_scope === "pre_calibration_source_head" + && acceptance.later_commit_revokes === true && acceptance.event === "workflow_dispatch" && acceptance.hostile_job === "freeze-hostile-mutations" && acceptance.hostile_step === "Execute exact-head hostile mutation matrix" @@ -7996,9 +8026,24 @@ export function releaseFreezeBarrierWorkflowViolations( === "release-cell-prepublish-source-attempt-${{ github.run_attempt }}" && singleSource.artifact_required_unexpired === true && singleSource.cell_emission === "unconditional_on_success" + && singleSource.post_calibration_status_required === false && singleSource.post_calibration_fallback_allowed === false, "[freeze_barrier] release claim graph must pin the executable single-proof freeze contract", ); + add( + violations, + barrierSource.includes('gh(["api", `repos/${repository}/pulls/${number}`])') + && barrierSource.includes("`repos/${repository}/compare/${pr.base.sha}...${commit}`") + && barrierSource.includes("base_commit: pr.base.sha") + && barrierSource.includes("const currentReleasePr = releasePr(") + && barrierSource.includes( + "currentReleasePr.base_commit !== receipt?.release_pr?.base_commit", + ) + && barrierSource.includes("release PR base advanced after freeze acceptance") + && barrierSource.includes("git([\"merge-base\", \"--is-ancestor\", mergeCommit, commit]") + && barrierSource.includes("support PR #${number} is not merged"), + "[freeze_barrier] Actions receipt authority must recheck the live release PR base and integrated support PR ancestry", + ); const invalidationFile = freeze.invalidation_workflow; const invalidation = workflows.get(invalidationFile); @@ -8015,7 +8060,7 @@ export function releaseFreezeBarrierWorkflowViolations( ]) && object(invalidation.permissions).actions === "write" && object(invalidation.permissions).contents === "read" - && object(invalidation.permissions).statuses === "read" + && object(invalidation.permissions).statuses === "write" && at(invalidation, "concurrency", "cancel-in-progress") === true, "[freeze_barrier] release freeze invalidation must run automatically when a candidate head is superseded", ); @@ -8046,9 +8091,13 @@ export function releaseFreezeBarrierWorkflowViolations( [ 'test "$BEFORE_SHA" != "$AFTER_SHA"', "commits/$BEFORE_SHA/statuses?per_page=100", - '.state == "pending" or .state == "success"', + '.state == "success"', 'startswith("codestory/release-freeze/")', - 'if [ "$has_freeze" = 0 ]; then', + 'if [ -z "$freeze_contexts" ]; then', + '"repos/$GITHUB_REPOSITORY/statuses/$BEFORE_SHA"', + "-f state=error", + '-f "context=$context"', + '-f "description=superseded-by=$AFTER_SHA"', "release-freeze-barrier.mjs invalidate-superseded", '--commit "$AFTER_SHA"', '--broad-workflow "Exact-head source proof"', @@ -8057,6 +8106,16 @@ export function releaseFreezeBarrierWorkflowViolations( '--broad-workflow "Auto Release"', ], ); + forbidStepRun( + violations, + invalidationFile, + invalidationJob, + "Invalidate a superseded release freeze", + [ + '.state == "pending"', + ".state == 'pending'", + ], + ); requireStepEnv( violations, invalidationFile, @@ -8065,8 +8124,24 @@ export function releaseFreezeBarrierWorkflowViolations( { AFTER_SHA: "${{ github.event.after || github.sha }}", BEFORE_SHA: "${{ github.event.before }}", + EVENT_NAME: "${{ github.event_name }}", }, ); + const invalidationRun = executableRunText(stepRun( + invalidationJob, + "Invalidate a superseded release freeze", + )); + add( + violations, + occurrenceCount(invalidationRun, "release-freeze-barrier.mjs invalidate-superseded") + === 2 + && occurrenceCount(invalidationRun, '--broad-workflow "Auto Release"') === 2 + && invalidationRun.indexOf('if [ "$EVENT_NAME" = push ]; then') + < invalidationRun.indexOf("commits/$BEFORE_SHA/statuses?per_page=100") + && invalidationRun.indexOf("release-freeze-barrier.mjs invalidate-superseded") + < invalidationRun.indexOf("commits/$BEFORE_SHA/statuses?per_page=100"), + "[freeze_barrier] every dev push must cancel obsolete proof before PR-status revocation logic", + ); for (const file of ["source-proof.yml", "packaged-platform-pr.yml"]) { const workflow = workflows.get(file); @@ -8089,8 +8164,12 @@ export function releaseFreezeBarrierWorkflowViolations( )); add( violations, - freezeInput.required === true && freezeInput.type === "string", - `[freeze_barrier] ${file} dispatch must require an exact-head freeze receipt digest`, + freezeInput.required === false + && freezeInput.default === "" + && freezeInput.type === "string", + file === "source-proof.yml" + ? "[freeze_barrier] source acceptance must mint its own receipt digest" + : "[freeze_barrier] qualification must reuse source-cell lineage without an active freeze digest", ); if (file === "source-proof.yml") { const dispatchVersionInput = object(at( @@ -8114,12 +8193,21 @@ export function releaseFreezeBarrierWorkflowViolations( "inputs", "acceptance_only", )); + const callFreezeInput = object(at( + workflow, + "on", + "workflow_call", + "inputs", + "freeze_receipt_digest", + )); add( violations, dispatchVersionInput.required === true && dispatchVersionInput.type === "string" && callVersionInput.required === true && callVersionInput.type === "string" + && callFreezeInput.required === true + && callFreezeInput.type === "string" && acceptanceInput.required === false && acceptanceInput.type === "boolean" && acceptanceInput.default === false @@ -8164,6 +8252,118 @@ export function releaseFreezeBarrierWorkflowViolations( } const sourceWorkflow = workflows.get("source-proof.yml"); + const sourceResolve = requireJob( + violations, + "source-proof.yml", + sourceWorkflow, + acceptance.receipt_producer_job, + ); + const acceptedCheckout = namedStep(sourceResolve, "Checkout accepted source head"); + add( + violations, + acceptedCheckout?.uses === "actions/checkout@v5" + && object(acceptedCheckout.with).ref === "${{ steps.resolve.outputs.ref }}" + && object(acceptedCheckout.with)["fetch-depth"] === 0, + "[freeze_barrier] Actions receipt generation must have complete history for support PR ancestry", + ); + const recordReceipt = namedStep(sourceResolve, "Record executable release freeze"); + add( + violations, + recordReceipt?.if === "${{ inputs.acceptance_only }}", + "[freeze_barrier] Actions may generate a release freeze receipt only in acceptance mode", + ); + requireStepRun( + violations, + "source-proof.yml", + sourceResolve, + "Record executable release freeze", + [ + 'test -z "$CALLER_FREEZE_RECEIPT_DIGEST"', + "release-freeze-barrier.mjs record-actions-receipt", + '--repository "$GITHUB_REPOSITORY"', + '--repo "$GITHUB_WORKSPACE"', + '--branch "$GITHUB_REF_NAME"', + '--commit "$HEAD_SHA"', + '--tree "$tree"', + '--release-pr "$PR_NUMBER"', + '--support-prs-json "$SUPPORT_PRS_JSON"', + '--reusable-evidence-json "$REUSABLE_EVIDENCE_JSON"', + '--invalidated-evidence-json "$INVALIDATED_EVIDENCE_JSON"', + '--cancelled-runs-json "$CANCELLED_RUNS_JSON"', + '--run-id "$GITHUB_RUN_ID"', + '--run-attempt "$GITHUB_RUN_ATTEMPT"', + '--output "$RUNNER_TEMP/release-freeze-receipt.json"', + '--github-output "$GITHUB_OUTPUT"', + ], + ); + requireStepEnv( + violations, + "source-proof.yml", + sourceResolve, + "Record executable release freeze", + { + CALLER_FREEZE_RECEIPT_DIGEST: "${{ inputs.freeze_receipt_digest }}", + CANCELLED_RUNS_JSON: "${{ steps.cancel.outputs.cancelled }}", + HEAD_SHA: "${{ steps.resolve.outputs.ref }}", + INVALIDATED_EVIDENCE_JSON: "${{ inputs.invalidated_evidence_json }}", + PR_NUMBER: "${{ inputs.pr_number }}", + REUSABLE_EVIDENCE_JSON: "${{ inputs.reusable_evidence_json }}", + SUPPORT_PRS_JSON: "${{ inputs.support_prs_json }}", + }, + ); + const receiptUpload = namedStep( + sourceResolve, + "Upload executable release freeze receipt", + ); + add( + violations, + receiptUpload?.if === "${{ inputs.acceptance_only }}" + && receiptUpload?.uses === "actions/upload-artifact@v7.0.1" + && object(receiptUpload.with).name + === "${{ steps.receipt.outputs.artifact_name }}" + && object(receiptUpload.with).path + === "${{ runner.temp }}/release-freeze-receipt.json" + && object(receiptUpload.with)["if-no-files-found"] === "error" + && object(receiptUpload.with)["retention-days"] === 30 + && object(sourceResolve.outputs).freeze_digest + === "${{ steps.receipt.outputs.digest }}" + && object(sourceResolve.outputs).freeze_artifact_name + === "${{ steps.receipt.outputs.artifact_name }}", + "[freeze_barrier] source acceptance must retain one immutable attempt-qualified Actions receipt", + ); + const broadFreeze = namedStep(sourceResolve, "Require executable release freeze"); + add( + violations, + broadFreeze?.if === "${{ !inputs.acceptance_only }}", + "[freeze_barrier] broad source proof must authenticate the accepted freeze", + ); + requireStepRun( + violations, + "source-proof.yml", + sourceResolve, + "Require executable release freeze", + [ + "release-freeze-barrier.mjs verify-status", + '--commit "$HEAD_SHA"', + '--tree "$tree"', + '--receipt-digest "$FREEZE_RECEIPT_DIGEST"', + ], + ); + requireStepEnv( + violations, + "source-proof.yml", + sourceResolve, + "Require executable release freeze", + { + FREEZE_RECEIPT_DIGEST: "${{ inputs.freeze_receipt_digest }}", + HEAD_SHA: "${{ steps.resolve.outputs.ref }}", + }, + ); + add( + violations, + !scalarStrings(sourceWorkflow).some(value => value.includes("verify-pending")), + "[freeze_barrier] source proof must never accept a caller-authored pending status", + ); const hostileJob = requireJob( violations, "source-proof.yml", @@ -8250,12 +8450,35 @@ export function releaseFreezeBarrierWorkflowViolations( ].every(fragment => String(publisherJob.if ?? "").includes(fragment)), "[freeze_barrier] acceptance publisher must depend on both exact successful mutation jobs", ); + const receiptDownload = namedStep( + publisherJob, + "Download executable release freeze receipt", + ); + add( + violations, + receiptDownload?.uses === "actions/download-artifact@v8.0.1" + && object(receiptDownload.with).name + === "${{ needs.resolve.outputs.freeze_artifact_name }}" + && object(receiptDownload.with).path + === "${{ runner.temp }}/release-freeze-receipt" + && stepIndex(publisherJob, "Download executable release freeze receipt") + < stepIndex(publisherJob, acceptance.publisher_step), + "[freeze_barrier] acceptance publisher must download the exact Actions receipt before publication", + ); requireStepRun( violations, "source-proof.yml", publisherJob, acceptance.publisher_step, [ + "release-freeze-barrier.mjs verify-file", + '--receipt "$RUNNER_TEMP/release-freeze-receipt/release-freeze-receipt.json"', + '--repository "$GITHUB_REPOSITORY"', + '--commit "$HEAD_SHA"', + '--tree "$tree"', + '--run-id "$GITHUB_RUN_ID"', + '--run-attempt "$GITHUB_RUN_ATTEMPT"', + 'test "$verified_digest" = "$FREEZE_RECEIPT_DIGEST"', "repos/$GITHUB_REPOSITORY/statuses/$HEAD_SHA", "-f state=success", "-f \"context=codestory/release-freeze/$FREEZE_RECEIPT_DIGEST\"", @@ -8263,6 +8486,16 @@ export function releaseFreezeBarrierWorkflowViolations( "actions/runs/$GITHUB_RUN_ID", ], ); + requireStepEnv( + violations, + "source-proof.yml", + publisherJob, + acceptance.publisher_step, + { + FREEZE_RECEIPT_DIGEST: "${{ needs.resolve.outputs.freeze_digest }}", + HEAD_SHA: "${{ needs.resolve.outputs.ref }}", + }, + ); for (const file of list(freeze.coordinator_only_workflows)) { const workflow = workflows.get(file); @@ -8288,6 +8521,12 @@ export function releaseFreezeBarrierWorkflowViolations( "sha=$HEAD_SHA", ], ); + add( + violations, + namedStep(route, "Require executable release freeze")?.if + === "steps.resolve.outputs.mode != 'qualification'", + "[freeze_barrier] qualification must reuse source-cell lineage after the constant-only commit", + ); requireStepRun( violations, "packaged-platform-pr.yml", @@ -8324,9 +8563,9 @@ export function releaseFreezeBarrierWorkflowViolations( ); add( violations, - object(release.permissions).statuses === "read" - && object(at(auto, "jobs", "release", "permissions")).statuses === "read", - "[freeze_barrier] manual and automatic release must authenticate freeze status provenance", + object(release.permissions).statuses === undefined + && object(at(auto, "jobs", "release", "permissions")).statuses === undefined, + "[freeze_barrier] post-calibration release must not rely on an active freeze status", ); const preflight = requireJob(violations, "release.yml", release, "preflight"); requireStepRun( @@ -8339,11 +8578,20 @@ export function releaseFreezeBarrierWorkflowViolations( 'artifact_name="release-cell-prepublish-source-attempt-$run_attempt"', ".expired == false", 'test "$artifact_count" = 1 || continue', - "release-freeze-barrier.mjs verify-status", "The release workflow will not start a second proof after calibration", "source_proof_reused=true", ], ); + forbidStepRun( + violations, + "release.yml", + preflight, + "Resolve reusable prior evidence", + [ + "release-freeze-barrier.mjs verify-status", + "freeze_receipt_digest", + ], + ); requireStepEnv( violations, "release.yml", @@ -8351,7 +8599,6 @@ export function releaseFreezeBarrierWorkflowViolations( "Resolve reusable prior evidence", { SOURCE_SHA: "${{ steps.lineage.outputs.selection_commit }}", - SOURCE_TREE: "${{ steps.lineage.outputs.selection_tree }}", }, ); const sourceJob = requireJob(violations, "release.yml", release, "source-proof"); @@ -8359,7 +8606,8 @@ export function releaseFreezeBarrierWorkflowViolations( violations, sourceJob.if === "needs.preflight.outputs.source_proof_reused != 'true'" && object(preflight.outputs).source_proof_reused - === "${{ steps.reuse.outputs.source_proof_reused }}", + === "${{ steps.reuse.outputs.source_proof_reused }}" + && object(sourceJob.with).freeze_receipt_digest === "", "[freeze_barrier] release must make the post-calibration source-proof fallback unreachable", ); @@ -9097,7 +9345,11 @@ function validateReleaseArtifactRerunSafety(workflows, violations) { const upload = object(step.with); const artifactName = String(upload.name ?? ""); const uploadKey = `${file}/${jobId}/${step.name ?? ""}`; - const attemptQualified = artifactName.includes("${{ github.run_attempt }}"); + const attemptQualified = artifactName.includes("${{ github.run_attempt }}") + || ( + uploadKey === "source-proof.yml/resolve/Upload executable release freeze receipt" + && artifactName === "${{ steps.receipt.outputs.artifact_name }}" + ); const expectedStable = replaceableStableIntermediates.get(uploadKey); const stableIntermediateMatches = expectedStable !== undefined && !observedStableIntermediates.has(uploadKey) diff --git a/.github/scripts/check-workflow-policy.test.mjs b/.github/scripts/check-workflow-policy.test.mjs index 93d0e3883..4db5a9cf8 100644 --- a/.github/scripts/check-workflow-policy.test.mjs +++ b/.github/scripts/check-workflow-policy.test.mjs @@ -36,6 +36,7 @@ import { releaseEvidenceApprovalViolations, releaseProofCpuSelectorViolations, releaseEvidenceWorkflowRef, + releaseFreezeBarrierWorkflowViolations, releaseWorkflowContractViolations, retrievalGeneralizationSuitePolicyViolations, retrievalFile, @@ -2866,6 +2867,43 @@ test("release freeze barrier rejects every broad-proof bypass", async (t) => { '--broad-workflow "Auto Release"', "", ); + }, /every dev push must cancel obsolete proof/u], + ["dev push no longer cancels before status lookup", workflows => { + const step = draftStep( + workflows.get("release-freeze-invalidation.yml").jobs.invalidate, + "Invalidate a superseded release freeze", + ); + step.run = step.run.replace( + "release-freeze-barrier.mjs invalidate-superseded", + "release-freeze-barrier.mjs cancelled-too-late", + ); + }, /every dev push must cancel obsolete proof/u], + ["invalidation loses event identity", workflows => { + const step = draftStep( + workflows.get("release-freeze-invalidation.yml").jobs.invalidate, + "Invalidate a superseded release freeze", + ); + delete step.env.EVENT_NAME; + }, /must bind EVENT_NAME/u], + ["invalidation accepts a pending freeze", workflows => { + const step = draftStep( + workflows.get("release-freeze-invalidation.yml").jobs.invalidate, + "Invalidate a superseded release freeze", + ); + step.run = step.run.replace( + '.state == "success"', + '(.state == "pending" or .state == "success")', + ); + }, /Invalidate a superseded release freeze/u], + ["invalidation cannot revoke the old status", workflows => { + workflows.get("release-freeze-invalidation.yml").permissions.statuses = "read"; + }, /must run automatically when a candidate head is superseded/u], + ["invalidation stops publishing the revocation", workflows => { + const step = draftStep( + workflows.get("release-freeze-invalidation.yml").jobs.invalidate, + "Invalidate a superseded release freeze", + ); + step.run = step.run.replace("-f state=error", "-f state=success"); }, /Invalidate a superseded release freeze/u], ["platform label trigger", workflows => { workflows.get("packaged-platform-pr.yml").on.pull_request = { types: ["labeled"] }; @@ -2881,10 +2919,10 @@ test("release freeze barrier rejects every broad-proof bypass", async (t) => { }, /callable only through an accepted coordinator/u, ]), - ["source dispatch omits receipt", workflows => { + ["source acceptance requires a caller receipt", workflows => { workflows.get("source-proof.yml").on.workflow_dispatch - .inputs.freeze_receipt_digest.required = false; - }, /dispatch must require an exact-head freeze receipt digest/u], + .inputs.freeze_receipt_digest.required = true; + }, /acceptance must mint its own receipt digest/u], ["source acceptance becomes the default", workflows => { workflows.get("source-proof.yml").on.workflow_dispatch .inputs.acceptance_only.default = true; @@ -2892,6 +2930,44 @@ test("release freeze barrier rejects every broad-proof bypass", async (t) => { ["source acceptance cannot publish status", workflows => { delete workflows.get("source-proof.yml").permissions.statuses; }, /acceptance must publish an exact-head commit status/u], + ["Actions receipt generation is removed", workflows => { + const job = workflows.get("source-proof.yml").jobs.resolve; + job.steps = job.steps.filter(({ name }) => + name !== "Record executable release freeze"); + }, /Record executable release freeze/u], + ["Actions receipt generation loses live release PR authentication", workflows => { + const step = draftStep( + workflows.get("source-proof.yml").jobs.resolve, + "Record executable release freeze", + ); + step.run = step.run.replace('--release-pr "$PR_NUMBER"', ""); + }, /Record executable release freeze.*--release-pr/u], + ["Actions receipt generation loses merged support PR authentication", workflows => { + const step = draftStep( + workflows.get("source-proof.yml").jobs.resolve, + "Record executable release freeze", + ); + step.run = step.run.replace('--support-prs-json "$SUPPORT_PRS_JSON"', ""); + }, /Record executable release freeze.*--support-prs-json/u], + ["Actions receipt generation loses support PR history", workflows => { + const step = draftStep( + workflows.get("source-proof.yml").jobs.resolve, + "Checkout accepted source head", + ); + delete step.with["fetch-depth"]; + }, /complete history for support PR ancestry/u], + ["Actions receipt artifact is removed", workflows => { + const job = workflows.get("source-proof.yml").jobs.resolve; + job.steps = job.steps.filter(({ name }) => + name !== "Upload executable release freeze receipt"); + }, /immutable attempt-qualified Actions receipt/u], + ["Actions receipt artifact is substituted", workflows => { + const step = draftStep( + workflows.get("source-proof.yml").jobs.resolve, + "Upload executable release freeze receipt", + ); + step.with.name = "release-freeze-receipt"; + }, /immutable attempt-qualified Actions receipt/u], ["source restores conditional cell emission", workflows => { workflows.get("source-proof.yml").on.workflow_dispatch .inputs.emit_release_cells = { @@ -2944,6 +3020,35 @@ test("release freeze barrier rejects every broad-proof bypass", async (t) => { workflows.get("source-proof.yml").jobs["freeze-acceptance"].needs = ["resolve", "freeze-hostile-mutations"]; }, /publisher must depend on both exact successful mutation jobs/u], + ["acceptance publisher stops downloading the Actions receipt", workflows => { + const job = workflows.get("source-proof.yml").jobs["freeze-acceptance"]; + job.steps = job.steps.filter(({ name }) => + name !== "Download executable release freeze receipt"); + }, /download the exact Actions receipt before publication/u], + ["acceptance publisher trusts the caller digest", workflows => { + const step = draftStep( + workflows.get("source-proof.yml").jobs["freeze-acceptance"], + "Publish executable release freeze", + ); + step.env.FREEZE_RECEIPT_DIGEST = "${{ inputs.freeze_receipt_digest }}"; + }, /FREEZE_RECEIPT_DIGEST/u], + ["acceptance publisher skips receipt verification", workflows => { + const step = draftStep( + workflows.get("source-proof.yml").jobs["freeze-acceptance"], + "Publish executable release freeze", + ); + step.run = step.run.replace( + "release-freeze-barrier.mjs verify-file", + "printf '%s' \"$FREEZE_RECEIPT_DIGEST\"", + ); + }, /Publish executable release freeze.*verify-file/u], + ["source acceptance restores pending status trust", workflows => { + const step = draftStep( + workflows.get("source-proof.yml").jobs.resolve, + "Require executable release freeze", + ); + step.run = step.run.replace("verify-status", "verify-pending"); + }, /caller-authored pending status/u], ["acceptance publisher loses Actions provenance", workflows => { const step = draftStep( workflows.get("source-proof.yml").jobs["freeze-acceptance"], @@ -2954,10 +3059,10 @@ test("release freeze barrier rejects every broad-proof bypass", async (t) => { "pull/$GITHUB_RUN_ID", ); }, /Publish executable release freeze/u], - ["platform dispatch omits receipt", workflows => { + ["qualification requires an active receipt", workflows => { workflows.get("packaged-platform-pr.yml").on.workflow_dispatch - .inputs.freeze_receipt_digest.required = false; - }, /dispatch must require an exact-head freeze receipt digest/u], + .inputs.freeze_receipt_digest.required = true; + }, /qualification must reuse source-cell lineage without an active freeze digest/u], ["platform cannot read freeze status", workflows => { delete workflows.get("packaged-platform-pr.yml").permissions.statuses; }, /must authenticate the exact-head freeze status/u], @@ -2971,6 +3076,12 @@ test("release freeze barrier rejects every broad-proof bypass", async (t) => { 'echo "sha=$HEAD_SHA" >> "$GITHUB_OUTPUT"', ); }, /Resolve accepted source proof head.*sha=\$CALIBRATION_SOURCE_SHA/u], + ["qualification requires an active freeze status", workflows => { + draftStep( + workflows.get("packaged-platform-pr.yml").jobs.route, + "Require executable release freeze", + ).if = "always()"; + }, /qualification must reuse source-cell lineage after the constant-only commit/u], ["release searches the frozen descendant", workflows => { const step = draftStep( workflows.get("release.yml").jobs.preflight, @@ -3015,22 +3126,26 @@ test("release freeze barrier rejects every broad-proof bypass", async (t) => { "gh api repos/$GITHUB_REPOSITORY/commits/$SOURCE_SHA/status", ); }, /Require executable release freeze.*verify-status/u], - ["release trusts a bare success status", workflows => { + ["release restores active freeze status authentication", workflows => { const step = draftStep( workflows.get("release.yml").jobs.preflight, "Resolve reusable prior evidence", ); - step.run = step.run.replace( - "release-freeze-barrier.mjs verify-status", - "gh api repos/$GITHUB_REPOSITORY/commits/$SOURCE_SHA/status", - ); - }, /Resolve reusable prior evidence.*verify-status/u], + step.run += "\nnode .github/scripts/release-freeze-barrier.mjs verify-status\n"; + }, /Resolve reusable prior evidence must not run release-freeze-barrier\.mjs verify-status/u], + ["release placeholder propagates a post-calibration receipt", workflows => { + workflows.get("release.yml").jobs["source-proof"].with.freeze_receipt_digest + = "${{ inputs.freeze_receipt_digest }}"; + }, /unreachable source fallback must fail closed without a post-calibration freeze/u], ["release stops cancelling superseded work", workflows => { workflows.get("release.yml").concurrency["cancel-in-progress"] = false; }, /release and auto-release must cancel superseded work/u], - ["automatic release cannot read freeze status", workflows => { - delete workflows.get("auto-release.yml").jobs.release.permissions.statuses; - }, /must authenticate freeze status provenance/u], + ["automatic release restores freeze status authority", workflows => { + workflows.get("auto-release.yml").jobs.release.permissions.statuses = "read"; + }, /post-calibration release must not rely on an active freeze status/u], + ["manual release restores freeze status authority", workflows => { + workflows.get("release.yml").permissions.statuses = "read"; + }, /post-calibration release must not rely on an active freeze status/u], ["auto-release stops cancelling superseded work", workflows => { workflows.get("auto-release.yml").concurrency["cancel-in-progress"] = false; }, /release and auto-release must cancel superseded work/u], @@ -3052,6 +3167,48 @@ test("release freeze barrier rejects every broad-proof bypass", async (t) => { } }); +test("release freeze policy pins live PR base and support ancestry revalidation", async (t) => { + const source = readFileSync( + path.join(root, ".github", "scripts", "release-freeze-barrier.mjs"), + "utf8", + ); + const cases = [ + ["release PR lookup stops using live REST state", value => + value.replace( + 'gh(["api", `repos/${repository}/pulls/${number}`])', + "JSON.parse('{}')", + )], + ["release PR head stops proving it contains the current dev base", value => + value.replace( + "`repos/${repository}/compare/${pr.base.sha}...${commit}`", + "`repos/${repository}/commits/${commit}`", + )], + ["verification stops detecting a base advance", value => + value.replace( + "currentReleasePr.base_commit !== receipt?.release_pr?.base_commit", + "false", + )], + ["support PR ancestry becomes advisory", value => + value.replace( + 'git(["merge-base", "--is-ancestor", mergeCommit, commit]', + 'git(["rev-parse", commit]', + )], + ]; + for (const [name, mutate] of cases) { + await t.test(name, () => { + const violations = releaseFreezeBarrierWorkflowViolations( + loadWorkflows(), + loadReleaseClaimGraph(root), + mutate(source), + ); + assert.match( + violations.join("\n"), + /recheck the live release PR base and integrated support PR ancestry/u, + ); + }); + } +}); + test("Windows package proof retains the readable native sccache executable", () => { const directory = mkdtempSync(path.join(os.tmpdir(), "codestory-windows-sccache-")); try { diff --git a/.github/scripts/release-freeze-barrier.mjs b/.github/scripts/release-freeze-barrier.mjs index f665fc5c7..be3655364 100644 --- a/.github/scripts/release-freeze-barrier.mjs +++ b/.github/scripts/release-freeze-barrier.mjs @@ -1,8 +1,17 @@ #!/usr/bin/env node import { createHash } from "node:crypto"; -import { readFileSync, writeFileSync } from "node:fs"; +import { + lstatSync, + mkdtempSync, + readFileSync, + readdirSync, + rmSync, + writeFileSync, +} from "node:fs"; import { execFileSync } from "node:child_process"; +import { tmpdir } from "node:os"; +import path from "node:path"; import process from "node:process"; const ACTIVE_RUN_STATES = new Set([ @@ -15,6 +24,17 @@ const ACTIVE_RUN_STATES = new Set([ const ALLOWED_FUTURE_CHANGES = new Set([ "crates/codestory-llama-sys/per-user-embedding-server-constant-set.json", ]); +const NEXT_PERMITTED_MUTATION = + "crates/codestory-llama-sys/per-user-embedding-server-constant-set.json"; +const PLANNED_PROOF_ACTIONS = [ + "acceptance", + "source-proof", + "calibration", + "qualification", + "release", +]; +const RECEIPT_ARTIFACT_PREFIX = "release-freeze-receipt-attempt-"; +const RECEIPT_FILE = "release-freeze-receipt.json"; const STATUS_PREFIX = "codestory/release-freeze"; const CANCEL_POLL_ATTEMPTS = Number.parseInt( process.env.CODESTORY_FREEZE_CANCEL_POLL_ATTEMPTS ?? "10", @@ -76,10 +96,6 @@ function required(args, name) { return result; } -function has(args, name) { - return args.includes(name); -} - function parseJsonFile(path, label) { try { return JSON.parse(readFileSync(path, "utf8")); @@ -123,6 +139,8 @@ export function validateAcceptanceProvenance({ status, run, jobs, + artifact, + receipt, repository, commit, tree, @@ -154,6 +172,22 @@ export function validateAcceptanceProvenance({ ) { fail("release freeze acceptance run provenance changed"); } + const artifactName = `${RECEIPT_ARTIFACT_PREFIX}${run.run_attempt}`; + if ( + artifact?.name !== artifactName + || artifact?.expired !== false + || String(artifact?.workflow_run?.id) !== String(run.id) + || receipt?.digest !== digest + ) { + fail("release freeze receipt artifact provenance changed"); + } + validateReceipt(receipt, { + repository, + commit, + tree, + runId: String(run.id), + runAttempt: String(run.run_attempt), + }); if (!Array.isArray(jobs)) { fail("release freeze acceptance jobs are missing"); } @@ -192,44 +226,76 @@ export function validateAcceptanceProvenance({ return Number(target[1]); } -export function validateReceipt(receipt, { commit, tree }) { - if (receipt?.schema !== 1) { - fail("freeze receipt schema must be 1"); +export function validateReceipt( + receipt, + { repository, commit, tree, runId, runAttempt }, +) { + if (receipt?.schema !== 2 || receipt?.authority !== "github_actions") { + fail("freeze receipt must use the GitHub Actions authority schema"); } - if (receipt.commit !== commit || receipt.tree !== tree) { + if ( + receipt.repository !== repository + || receipt.commit !== commit + || receipt.tree !== tree + ) { fail("freeze receipt does not match the exact commit and tree"); } if (receipt.worktree_clean !== true || receipt.remote_head !== commit) { fail("freeze receipt must prove a clean worktree pushed at the exact commit"); } if ( - receipt?.release_pr?.head_commit !== commit + !Number.isInteger(receipt?.release_pr?.number) + || receipt.release_pr.number <= 0 + || receipt?.release_pr?.head_commit !== commit || receipt?.release_pr?.head !== receipt.branch || receipt?.release_pr?.base !== "dev/codestory-next" + || !/^[0-9a-f]{40}$/u.test(String(receipt?.release_pr?.base_commit ?? "")) ) { fail("freeze receipt must bind the open release PR at this exact head"); } - if (!Array.isArray(receipt.known_future_source_changes)) { - fail("freeze receipt must declare known future source changes"); + if ( + !Array.isArray(receipt.integrated_support_prs) + || new Set(receipt.integrated_support_prs.map(entry => entry?.number)).size + !== receipt.integrated_support_prs.length + ) { + fail("freeze receipt must contain unique integrated support PRs"); + } + if ( + !Array.isArray(receipt.known_future_source_changes) + || receipt.known_future_source_changes.length !== 1 + || !ALLOWED_FUTURE_CHANGES.has(receipt.known_future_source_changes[0]) + ) { + fail("freeze receipt must declare only the generated constant-set change"); } - for (const path of receipt.known_future_source_changes) { - if (!ALLOWED_FUTURE_CHANGES.has(path)) { - fail(`freeze receipt admits an unsupported future source change: ${path}`); - } + if ( + JSON.stringify(receipt.planned_proof_actions) !== JSON.stringify(PLANNED_PROOF_ACTIONS) + || JSON.stringify(receipt.proof_triggering_labels) !== "[]" + || JSON.stringify(receipt.proof_triggering_actions) !== JSON.stringify( + PLANNED_PROOF_ACTIONS, + ) + ) { + fail("freeze receipt must record the exact proof-triggering actions and no labels"); } for (const field of [ - "planned_proof_actions", "reusable_evidence", "invalidated_evidence", "running_workflows", + "cancelled_superseded_runs", ]) { if (!Array.isArray(receipt[field])) { fail(`freeze receipt must contain ${field}`); } } - if (typeof receipt.next_permitted_mutation !== "string" - || receipt.next_permitted_mutation.length === 0) { - fail("freeze receipt must name the next permitted mutation"); + if (receipt.next_permitted_mutation !== NEXT_PERMITTED_MUTATION) { + fail("freeze receipt must name the generated constant set as the next mutation"); + } + if ( + String(receipt?.acceptance_run?.id) !== String(runId) + || String(receipt?.acceptance_run?.attempt) !== String(runAttempt) + || receipt?.acceptance_run?.workflow !== ".github/workflows/source-proof.yml" + || receipt?.acceptance_run?.event !== "workflow_dispatch" + ) { + fail("freeze receipt must bind its exact Actions run and attempt"); } if (receipt.digest !== receiptDigest(receipt)) { fail("freeze receipt digest does not match its contents"); @@ -364,69 +430,110 @@ function supportPr(repository, number, commit, repo) { } function releasePr(repository, number, { branch, commit }) { - const pr = JSON.parse(gh([ - "pr", - "view", - String(number), - "--repo", - repository, - "--json", - "number,state,baseRefName,headRefName,headRefOid,headRepository", - ])); + const pr = JSON.parse(gh(["api", `repos/${repository}/pulls/${number}`])); if ( - pr.state !== "OPEN" - || pr.baseRefName !== "dev/codestory-next" - || pr.headRefName !== branch - || pr.headRefOid !== commit - || pr?.headRepository?.nameWithOwner !== repository + pr.state !== "open" + || pr?.base?.ref !== "dev/codestory-next" + || pr?.head?.ref !== branch + || pr?.head?.sha !== commit + || pr?.head?.repo?.full_name !== repository + || !/^[0-9a-f]{40}$/u.test(String(pr?.base?.sha ?? "")) ) { fail( `release PR #${number} must be an open same-repository ${branch} -> ` + `dev/codestory-next PR at exact head ${commit}`, ); } + const comparison = JSON.parse(gh([ + "api", + `repos/${repository}/compare/${pr.base.sha}...${commit}`, + ])); + if (!["ahead", "identical"].includes(comparison?.status)) { + fail( + `release PR #${number} head ${commit} does not contain current dev base ${pr.base.sha}`, + ); + } return { number: pr.number, - base: pr.baseRefName, - head: pr.headRefName, - head_commit: pr.headRefOid, + base: pr.base.ref, + base_commit: pr.base.sha, + head: pr.head.ref, + head_commit: pr.head.sha, }; } -function declare(args) { +function jsonArray(args, name, label) { + let parsed; + try { + parsed = JSON.parse(value(args, name, "[]")); + } catch (error) { + fail(`${label} must be valid JSON: ${error.message}`); + } + if (!Array.isArray(parsed)) { + fail(`${label} must be a JSON array`); + } + return parsed; +} + +function stringArray(args, name, label) { + const parsed = jsonArray(args, name, label); + if (!parsed.every(entry => typeof entry === "string" && entry.length > 0)) { + fail(`${label} must contain only non-empty strings`); + } + return parsed; +} + +function recordActionsReceipt(args) { const repo = value(args, "--repo", process.cwd()); const repository = required(args, "--repository"); - const branch = value(args, "--branch", git(["branch", "--show-current"], repo)); + const branch = required(args, "--branch"); + const commit = required(args, "--commit"); + const tree = required(args, "--tree"); const output = required(args, "--output"); const releasePrNumber = required(args, "--release-pr"); - const supportPrNumbers = values(args, "--support-pr"); - const knownFutureChanges = values(args, "--known-future-change"); - const plannedProofActions = values(args, "--planned-proof-action"); - const reusableEvidence = values(args, "--reusable-evidence"); - const invalidatedEvidence = values(args, "--invalidated-evidence"); - const nextMutation = required(args, "--next-permitted-mutation"); + const runId = required(args, "--run-id"); + const runAttempt = required(args, "--run-attempt"); + const supportPrNumbers = jsonArray(args, "--support-prs-json", "support PRs"); + if ( + !supportPrNumbers.every(number => Number.isInteger(number) && number > 0) + || new Set(supportPrNumbers).size !== supportPrNumbers.length + ) { + fail("support PRs must contain unique positive integers"); + } + const reusableEvidence = stringArray( + args, + "--reusable-evidence-json", + "reusable evidence", + ); + const invalidatedEvidence = stringArray( + args, + "--invalidated-evidence-json", + "invalidated evidence", + ); + const cancelledRuns = jsonArray( + args, + "--cancelled-runs-json", + "cancelled superseded runs", + ); const broadWorkflows = values(args, "--broad-workflow"); + if (broadWorkflows.length === 0) { + fail("release freeze requires broad workflow names"); + } if ( - plannedProofActions.length === 0 - || broadWorkflows.length === 0 + process.env.GITHUB_ACTIONS !== "true" + || process.env.GITHUB_EVENT_NAME !== "workflow_dispatch" ) { - fail("release freeze requires planned proof actions and broad workflow names"); + fail("the canonical release freeze receipt may be produced only by workflow_dispatch"); } if (git(["status", "--porcelain=v1", "--untracked-files=all"], repo) !== "") { fail("release freeze requires a clean worktree, including untracked files"); } - const commit = git(["rev-parse", "HEAD"], repo); - const tree = git(["rev-parse", "HEAD^{tree}"], repo); - const remoteLine = git(["ls-remote", "--exit-code", "origin", `refs/heads/${branch}`], repo); - const remoteHead = remoteLine.split(/\s+/u)[0]; - if (remoteHead !== commit) { - fail(`origin/${branch} is ${remoteHead}, not local HEAD ${commit}`); - } - for (const path of knownFutureChanges) { - if (!ALLOWED_FUTURE_CHANGES.has(path)) { - fail(`unsupported future source change: ${path}`); - } + if ( + git(["rev-parse", "HEAD"], repo) !== commit + || git(["rev-parse", "HEAD^{tree}"], repo) !== tree + ) { + fail("checked-out Actions source does not match the declared commit and tree"); } const acceptedReleasePr = releasePr(repository, releasePrNumber, { @@ -437,29 +544,9 @@ function declare(args) { (number) => supportPr(repository, number, commit, repo), ); - const runs = currentRuns(repository); - const duplicate = runs.find((entry) => - broadWorkflows.includes(entry.workflowName) && entry.headSha === commit + const remainingRuns = currentRuns(repository).filter( + entry => String(entry.databaseId) !== String(runId), ); - if (duplicate) { - fail( - `unchanged head ${commit} already has active ${duplicate.workflowName} run ${duplicate.databaseId}`, - ); - } - const cancelledRuns = cancelSupersededRuns({ - repository, - commit, - workflows: broadWorkflows, - runs, - }); - if (cancelledRuns.length > 0) { - waitForSupersededRunsToStop({ - repository, - commit, - workflows: broadWorkflows, - }); - } - const remainingRuns = currentRuns(repository); const remainingBroadRun = remainingRuns.find((entry) => broadWorkflows.includes(entry.workflowName) ); @@ -470,76 +557,140 @@ function declare(args) { } const receipt = { - schema: 1, + schema: 2, + authority: "github_actions", repository, branch, commit, tree, worktree_clean: true, - remote_head: remoteHead, + remote_head: commit, release_pr: acceptedReleasePr, integrated_support_prs: integratedSupportPrs, - known_future_source_changes: knownFutureChanges, - planned_proof_actions: plannedProofActions, + known_future_source_changes: [NEXT_PERMITTED_MUTATION], + planned_proof_actions: PLANNED_PROOF_ACTIONS, + proof_triggering_labels: [], + proof_triggering_actions: PLANNED_PROOF_ACTIONS, reusable_evidence: reusableEvidence, invalidated_evidence: invalidatedEvidence, running_workflows: remainingRuns, cancelled_superseded_runs: cancelledRuns, - next_permitted_mutation: nextMutation, + next_permitted_mutation: NEXT_PERMITTED_MUTATION, + acceptance_run: { + id: Number(runId), + attempt: Number(runAttempt), + workflow: ".github/workflows/source-proof.yml", + event: "workflow_dispatch", + }, }; receipt.digest = receiptDigest(receipt); - validateReceipt(receipt, { commit, tree }); + validateReceipt(receipt, { + repository, + commit, + tree, + runId, + runAttempt, + }); writeFileSync(output, `${JSON.stringify(receipt, null, 2)}\n`); - - if (!has(args, "--no-publish-status")) { - gh([ - "api", - "--method", - "POST", - `repos/${repository}/statuses/${commit}`, - "-f", - "state=pending", - "-f", - `context=${STATUS_PREFIX}/${receipt.digest}`, - "-f", - `description=tree=${tree}`, - ]); + const githubOutput = value(args, "--github-output"); + if (githubOutput) { + writeFileSync( + githubOutput, + `digest=${receipt.digest}\nartifact_name=${RECEIPT_ARTIFACT_PREFIX}${runAttempt}\n`, + { flag: "a" }, + ); } process.stdout.write(`${receipt.digest}\n`); } function verifyFile(args) { const receipt = parseJsonFile(required(args, "--receipt"), "freeze receipt"); + const repository = required(args, "--repository"); const commit = required(args, "--commit"); const tree = required(args, "--tree"); validateReceipt(receipt, { + repository, commit, tree, + runId: required(args, "--run-id"), + runAttempt: required(args, "--run-attempt"), }); process.stdout.write(`${receipt.digest}\n`); } -function matchingStatus({ repository, commit, tree, digest, state }) { +export function acceptedFreezeStatus(statuses, { tree, digest }) { + if (!Array.isArray(statuses)) { + fail("release freeze statuses are missing"); + } + const context = `${STATUS_PREFIX}/${digest}`; + const newest = statuses + .filter(status => status?.context === context) + .reduce((latest, status) => { + if (!latest) { + return status; + } + const latestId = BigInt(String(latest.id ?? "0")); + const statusId = BigInt(String(status.id ?? "0")); + return statusId > latestId ? status : latest; + }, undefined); + if (newest?.state !== "success" || newest?.description !== `tree=${tree}`) { + return undefined; + } + return newest; +} + +function matchingStatus({ repository, commit, tree, digest }) { const statuses = JSON.parse(gh([ "api", `repos/${repository}/commits/${commit}/statuses?per_page=100`, ])); - return statuses.find((status) => - status?.state === state - && status?.context === `${STATUS_PREFIX}/${digest}` - && status?.description === `tree=${tree}` - ); + return acceptedFreezeStatus(statuses, { tree, digest }); } -function verifyPending(args) { - const repository = required(args, "--repository"); - const commit = required(args, "--commit"); - const tree = required(args, "--tree"); - const digest = required(args, "--receipt-digest"); - if (!matchingStatus({ repository, commit, tree, digest, state: "pending" })) { - fail("no pending local release freeze declaration matches this exact commit and tree"); +function downloadAuthenticatedReceipt({ repository, run }) { + const artifactName = `${RECEIPT_ARTIFACT_PREFIX}${run.run_attempt}`; + const payload = JSON.parse(gh([ + "api", + `repos/${repository}/actions/runs/${run.id}/artifacts?per_page=100`, + ])); + const matches = (payload.artifacts ?? []).filter( + artifact => artifact?.name === artifactName && artifact?.expired === false, + ); + if (matches.length !== 1) { + fail(`acceptance run must retain exactly one unexpired ${artifactName}`); + } + const directory = mkdtempSync(path.join(tmpdir(), "codestory-freeze-receipt-")); + try { + gh([ + "run", + "download", + String(run.id), + "--repo", + repository, + "--name", + artifactName, + "--dir", + directory, + ]); + const entries = readdirSync(directory); + if ( + entries.length !== 1 + || entries[0] !== RECEIPT_FILE + || !lstatSync(path.join(directory, RECEIPT_FILE)).isFile() + || lstatSync(path.join(directory, RECEIPT_FILE)).nlink !== 1 + ) { + fail("release freeze artifact must contain one singly linked canonical receipt"); + } + return { + artifact: matches[0], + receipt: parseJsonFile( + path.join(directory, RECEIPT_FILE), + "authenticated freeze receipt", + ), + }; + } finally { + rmSync(directory, { recursive: true, force: true }); } - process.stdout.write(`${digest}\n`); } function verifyStatus(args) { @@ -552,7 +703,6 @@ function verifyStatus(args) { commit, tree, digest, - state: "success", }); if (!status) { fail("no successful exact-head release freeze status matches this receipt digest and tree"); @@ -565,6 +715,17 @@ function verifyStatus(args) { "api", `repos/${repository}/actions/runs/${target[1]}`, ])); + const { artifact, receipt } = downloadAuthenticatedReceipt({ + repository, + run, + }); + const currentReleasePr = releasePr(repository, receipt?.release_pr?.number, { + branch: receipt?.branch, + commit, + }); + if (currentReleasePr.base_commit !== receipt?.release_pr?.base_commit) { + fail("release PR base advanced after freeze acceptance"); + } const jobsPayload = JSON.parse(gh([ "api", `repos/${repository}/actions/runs/${target[1]}/jobs?per_page=100`, @@ -573,6 +734,8 @@ function verifyStatus(args) { status, run, jobs: jobsPayload.jobs, + artifact, + receipt, repository, commit, tree, @@ -583,12 +746,10 @@ function verifyStatus(args) { function main() { const [command, ...args] = process.argv.slice(2); - if (command === "declare") { - declare(args); + if (command === "record-actions-receipt") { + recordActionsReceipt(args); } else if (command === "verify-file") { verifyFile(args); - } else if (command === "verify-pending") { - verifyPending(args); } else if (command === "verify-status") { verifyStatus(args); } else if (command === "cancel-superseded") { @@ -598,7 +759,7 @@ function main() { } else { fail( "usage: release-freeze-barrier.mjs " - + " ...", ); } diff --git a/.github/scripts/release-freeze-barrier.test.mjs b/.github/scripts/release-freeze-barrier.test.mjs index 0bb8bb6fe..d0824fe10 100644 --- a/.github/scripts/release-freeze-barrier.test.mjs +++ b/.github/scripts/release-freeze-barrier.test.mjs @@ -1,23 +1,36 @@ import assert from "node:assert/strict"; -import { execFileSync, spawnSync } from "node:child_process"; +import { spawnSync } from "node:child_process"; import { chmodSync, mkdtempSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import test from "node:test"; import { + acceptedFreezeStatus, receiptDigest, validateAcceptanceProvenance, validateReceipt, } from "./release-freeze-barrier.mjs"; +const REPOSITORY = "TheGreenCedar/CodeStory"; const COMMIT = "1".repeat(40); const TREE = "2".repeat(40); -const DIGEST = "a".repeat(64); +const RUN_ID = 77; +const RUN_ATTEMPT = 2; +const NEXT_PERMITTED_MUTATION = + "crates/codestory-llama-sys/per-user-embedding-server-constant-set.json"; +const PLANNED_PROOF_ACTIONS = [ + "acceptance", + "source-proof", + "calibration", + "qualification", + "release", +]; function receipt(overrides = {}) { const candidate = { - schema: 1, - repository: "TheGreenCedar/CodeStory", + schema: 2, + authority: "github_actions", + repository: REPOSITORY, branch: "codex/release", commit: COMMIT, tree: TREE, @@ -26,27 +39,75 @@ function receipt(overrides = {}) { release_pr: { number: 1597, base: "dev/codestory-next", + base_commit: "0".repeat(40), head: "codex/release", head_commit: COMMIT, }, integrated_support_prs: [], - known_future_source_changes: [ - "crates/codestory-llama-sys/per-user-embedding-server-constant-set.json", - ], - planned_proof_actions: ["source-proof", "calibration", "qualification"], + known_future_source_changes: [NEXT_PERMITTED_MUTATION], + planned_proof_actions: [...PLANNED_PROOF_ACTIONS], + proof_triggering_labels: [], + proof_triggering_actions: [...PLANNED_PROOF_ACTIONS], reusable_evidence: [], invalidated_evidence: [], running_workflows: [], cancelled_superseded_runs: [], - next_permitted_mutation: "generated constant set only", + next_permitted_mutation: NEXT_PERMITTED_MUTATION, + acceptance_run: { + id: RUN_ID, + attempt: RUN_ATTEMPT, + workflow: ".github/workflows/source-proof.yml", + event: "workflow_dispatch", + }, ...overrides, }; candidate.digest = receiptDigest(candidate); return candidate; } -test("an exact clean pushed local declaration passes", () => { - validateReceipt(receipt(), { commit: COMMIT, tree: TREE }); +const RECEIPT_CONTEXT = { + repository: REPOSITORY, + commit: COMMIT, + tree: TREE, + runId: String(RUN_ID), + runAttempt: String(RUN_ATTEMPT), +}; + +test("an exact clean pushed Actions receipt passes", () => { + validateReceipt(receipt(), RECEIPT_CONTEXT); +}); + +test("a newer invalidation status revokes an older accepted freeze", () => { + const acceptedReceipt = receipt(); + const context = `codestory/release-freeze/${acceptedReceipt.digest}`; + const accepted = { + id: "9007199254740993", + state: "success", + context, + description: `tree=${TREE}`, + }; + assert.equal( + acceptedFreezeStatus([accepted], { + tree: TREE, + digest: acceptedReceipt.digest, + }), + accepted, + ); + assert.equal( + acceptedFreezeStatus([ + accepted, + { + id: "9007199254740994", + state: "error", + context, + description: `superseded-by=${"3".repeat(40)}`, + }, + ], { + tree: TREE, + digest: acceptedReceipt.digest, + }), + undefined, + ); }); for (const [name, mutate, pattern] of [ @@ -57,13 +118,27 @@ for (const [name, mutate, pattern] of [ ["moved release PR", (value) => { value.release_pr.head_commit = "5".repeat(40); }, /bind the open release PR/u], + ["unbound release base", (value) => { + value.release_pr.base_commit = ""; + }, /bind the open release PR/u], ["undeclared source change", (value) => { value.known_future_source_changes.push(".github/workflows/release.yml"); - }, /unsupported future source change/u], + }, /only the generated constant-set change/u], + ["caller-selected proof actions", (value) => { + value.planned_proof_actions = ["source-proof"]; + }, /exact proof-triggering actions/u], + ["proof-triggering label", (value) => { + value.proof_triggering_labels = ["source-proof"]; + }, /exact proof-triggering actions/u], + ["cross-attempt receipt", (value) => { + value.acceptance_run.attempt = RUN_ATTEMPT + 1; + }, /exact Actions run and attempt/u], ["missing handoff field", (value) => { delete value.running_workflows; }, /running_workflows/u], - ["missing next mutation", (value) => { value.next_permitted_mutation = ""; }, /next permitted mutation/u], + ["missing next mutation", (value) => { + value.next_permitted_mutation = ""; + }, /generated constant set as the next mutation/u], ["tampered receipt", (value) => { - value.planned_proof_actions.push("second-source-proof"); + value.reusable_evidence.push("unauthenticated evidence"); }, /digest/u], ]) { test(`freeze barrier rejects ${name}`, () => { @@ -73,15 +148,15 @@ for (const [name, mutate, pattern] of [ candidate.digest = receiptDigest(candidate); } assert.throws( - () => validateReceipt(candidate, { commit: COMMIT, tree: TREE }), + () => validateReceipt(candidate, RECEIPT_CONTEXT), pattern, ); }); } function acceptanceProvenance() { - const runId = 77; - const runAttempt = 2; + const acceptedReceipt = receipt(); + const digest = acceptedReceipt.digest; const startedAt = "2026-07-30T12:00:00Z"; const completedAt = "2026-07-30T12:00:06Z"; const job = (name, stepName, labels = ["ubuntu-latest"]) => ({ @@ -89,8 +164,8 @@ function acceptanceProvenance() { status: "completed", conclusion: "success", head_sha: COMMIT, - run_id: runId, - run_attempt: runAttempt, + run_id: RUN_ID, + run_attempt: RUN_ATTEMPT, labels, steps: [{ name: stepName, @@ -103,20 +178,20 @@ function acceptanceProvenance() { return { status: { state: "success", - context: `codestory/release-freeze/${DIGEST}`, + context: `codestory/release-freeze/${digest}`, description: `tree=${TREE}`, - target_url: `https://github.com/TheGreenCedar/CodeStory/actions/runs/${runId}`, + target_url: `https://github.com/${REPOSITORY}/actions/runs/${RUN_ID}`, creator: { login: "github-actions[bot]", type: "Bot" }, }, run: { - id: runId, - run_attempt: runAttempt, + id: RUN_ID, + run_attempt: RUN_ATTEMPT, head_sha: COMMIT, path: ".github/workflows/source-proof.yml", event: "workflow_dispatch", status: "completed", conclusion: "success", - head_repository: { full_name: "TheGreenCedar/CodeStory" }, + head_repository: { full_name: REPOSITORY }, }, jobs: [ job("freeze-hostile-mutations", "Execute exact-head hostile mutation matrix"), @@ -127,10 +202,16 @@ function acceptanceProvenance() { ), job("freeze-acceptance", "Publish executable release freeze"), ], - repository: "TheGreenCedar/CodeStory", + artifact: { + name: `release-freeze-receipt-attempt-${RUN_ATTEMPT}`, + expired: false, + workflow_run: { id: RUN_ID }, + }, + receipt: acceptedReceipt, + repository: REPOSITORY, commit: COMMIT, tree: TREE, - digest: DIGEST, + digest, }; } @@ -160,6 +241,21 @@ for (const [name, mutate, pattern] of [ ["fabricated native step", (value) => { value.jobs[1].steps[0].conclusion = "failure"; }, /did not execute successfully/u], + ["wrong receipt artifact", (value) => { + value.artifact.name = "release-freeze-receipt-attempt-999"; + }, /receipt artifact provenance changed/u], + ["expired receipt artifact", (value) => { + value.artifact.expired = true; + }, /receipt artifact provenance changed/u], + ["cross-run receipt artifact", (value) => { + value.artifact.workflow_run.id = RUN_ID + 1; + }, /receipt artifact provenance changed/u], + ["cross-attempt receipt artifact", (value) => { + value.artifact.name = `release-freeze-receipt-attempt-${RUN_ATTEMPT + 1}`; + }, /receipt artifact provenance changed/u], + ["tampered receipt artifact", (value) => { + value.receipt.running_workflows.push({ id: 123 }); + }, /digest/u], ]) { test(`acceptance rejects ${name}`, () => { const value = acceptanceProvenance(); @@ -180,10 +276,16 @@ test("verify-file is executable and rejects a later commit", () => { "verify-file", "--receipt", receiptPath, + "--repository", + REPOSITORY, "--commit", COMMIT, "--tree", TREE, + "--run-id", + String(RUN_ID), + "--run-attempt", + String(RUN_ATTEMPT), ], { encoding: "utf8" }, ); @@ -197,10 +299,16 @@ test("verify-file is executable and rejects a later commit", () => { "verify-file", "--receipt", receiptPath, + "--repository", + REPOSITORY, "--commit", "8".repeat(40), "--tree", TREE, + "--run-id", + String(RUN_ID), + "--run-attempt", + String(RUN_ATTEMPT), ], { encoding: "utf8" }, ); @@ -208,42 +316,51 @@ test("verify-file is executable and rejects a later commit", () => { assert.match(rejected.stderr, /exact commit and tree/u); }); -test("declare rejects a dirty worktree before publishing a status", () => { - const root = mkdtempSync(path.join(tmpdir(), "codestory-freeze-repo-")); - execFileSync("git", ["init", "-q", root]); - execFileSync("git", ["-C", root, "config", "user.email", "test@example.com"]); - execFileSync("git", ["-C", root, "config", "user.name", "Test"]); - writeFileSync(path.join(root, "tracked.txt"), "one\n"); - execFileSync("git", ["-C", root, "add", "tracked.txt"]); - execFileSync("git", ["-C", root, "commit", "-qm", "initial"]); - writeFileSync(path.join(root, "untracked.txt"), "dirty\n"); - +test("record-actions-receipt refuses to mint authority outside GitHub Actions", () => { + const root = mkdtempSync(path.join(tmpdir(), "codestory-freeze-outside-actions-")); const script = new URL("./release-freeze-barrier.mjs", import.meta.url); const result = spawnSync( process.execPath, [ script.pathname, - "declare", + "record-actions-receipt", "--repo", root, "--repository", - "TheGreenCedar/CodeStory", + REPOSITORY, + "--branch", + "codex/release", + "--commit", + COMMIT, + "--tree", + TREE, "--release-pr", "1", "--output", path.join(root, "receipt.json"), - "--next-permitted-mutation", - "none", - "--planned-proof-action", - "source-proof", + "--run-id", + String(RUN_ID), + "--run-attempt", + String(RUN_ATTEMPT), + "--support-prs-json", + "[]", "--broad-workflow", "Exact-head source proof", - "--no-publish-status", ], - { encoding: "utf8" }, + { + encoding: "utf8", + env: { + ...process.env, + GITHUB_ACTIONS: "", + GITHUB_EVENT_NAME: "", + }, + }, ); assert.notEqual(result.status, 0); - assert.match(result.stderr, /clean worktree, including untracked files/u); + assert.match( + result.stderr, + /canonical release freeze receipt may be produced only by workflow_dispatch/u, + ); }); test("cancel-superseded rejects a cancellation request that leaves the run active", () => { diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index 70a6840d5..d07cdc9d5 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -67,7 +67,6 @@ jobs: checks: read contents: write pull-requests: read - statuses: read uses: ./.github/workflows/release.yml with: version: ${{ needs.detect-version.outputs.version }} diff --git a/.github/workflows/packaged-platform-pr.yml b/.github/workflows/packaged-platform-pr.yml index c0344d45b..832ea7d6d 100644 --- a/.github/workflows/packaged-platform-pr.yml +++ b/.github/workflows/packaged-platform-pr.yml @@ -36,8 +36,9 @@ on: required: false type: string freeze_receipt_digest: - description: Digest emitted by release-freeze-barrier.mjs for the accepted source head. - required: true + description: Active freeze digest required before pre-calibration proof; qualification reuses the source cell under constant-only lineage. + required: false + default: "" type: string permissions: @@ -231,6 +232,7 @@ jobs: fi - name: Require executable release freeze + if: steps.resolve.outputs.mode != 'qualification' shell: bash env: GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/release-freeze-invalidation.yml b/.github/workflows/release-freeze-invalidation.yml index 54abbdc30..21e4dda82 100644 --- a/.github/workflows/release-freeze-invalidation.yml +++ b/.github/workflows/release-freeze-invalidation.yml @@ -12,7 +12,7 @@ on: permissions: actions: write contents: read - statuses: read + statuses: write concurrency: group: release-freeze-invalidation-${{ github.event.pull_request.number || github.ref }} @@ -31,24 +31,44 @@ jobs: env: AFTER_SHA: ${{ github.event.after || github.sha }} BEFORE_SHA: ${{ github.event.before }} + EVENT_NAME: ${{ github.event_name }} GH_TOKEN: ${{ github.token }} run: | set -euo pipefail printf '%s' "$BEFORE_SHA" | grep -Eq '^[0-9a-f]{40}$' printf '%s' "$AFTER_SHA" | grep -Eq '^[0-9a-f]{40}$' test "$BEFORE_SHA" != "$AFTER_SHA" - has_freeze="$( + if [ "$EVENT_NAME" = push ]; then + node .github/scripts/release-freeze-barrier.mjs invalidate-superseded \ + --repository "$GITHUB_REPOSITORY" \ + --commit "$AFTER_SHA" \ + --broad-workflow "Exact-head source proof" \ + --broad-workflow "Platform and integration proof" \ + --broad-workflow "Release" \ + --broad-workflow "Auto Release" + exit 0 + fi + freeze_contexts="$( gh api "repos/$GITHUB_REPOSITORY/commits/$BEFORE_SHA/statuses?per_page=100" \ - | jq \ + | jq -r \ '[.[] | select( - (.state == "pending" or .state == "success") + .state == "success" and (.context | startswith("codestory/release-freeze/")) - )] | length' + ) | .context] | unique[]' )" - if [ "$has_freeze" = 0 ]; then + if [ -z "$freeze_contexts" ]; then echo "Previous head $BEFORE_SHA was not a declared release candidate." exit 0 fi + while IFS= read -r context; do + gh api \ + --method POST \ + "repos/$GITHUB_REPOSITORY/statuses/$BEFORE_SHA" \ + -f state=error \ + -f "context=$context" \ + -f "description=superseded-by=$AFTER_SHA" \ + -f "target_url=$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" + done <<<"$freeze_contexts" node .github/scripts/release-freeze-barrier.mjs invalidate-superseded \ --repository "$GITHUB_REPOSITORY" \ --commit "$AFTER_SHA" \ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fe51bea0e..0834e88ce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,11 +17,6 @@ on: required: false type: boolean default: false - freeze_receipt_digest: - description: "Exact-head release freeze receipt digest; publishing may discover it from the calibrated source" - required: false - type: string - default: "" workflow_dispatch: inputs: version: @@ -32,11 +27,6 @@ on: description: "Exact dev/codestory-next head to authenticate without publishing" required: true type: string - freeze_receipt_digest: - description: "Digest emitted by release-freeze-barrier.mjs for the accepted source head" - required: true - type: string - permissions: actions: write # accelerator-non-claim reads the job annotation that identifies a lost runner, which the Actions @@ -45,7 +35,6 @@ permissions: checks: read contents: read pull-requests: read - statuses: read concurrency: group: release-${{ inputs.version }} @@ -97,7 +86,6 @@ jobs: version: ${{ steps.version.outputs.version }} reuse: ${{ steps.reuse.outputs.reuse }} source_proof_reused: ${{ steps.reuse.outputs.source_proof_reused }} - freeze_receipt_digest: ${{ steps.reuse.outputs.freeze_receipt_digest }} tag: ${{ steps.version.outputs.tag }} marketplace_revision: ${{ steps.marketplace.outputs.marketplace_revision }} steps: @@ -222,9 +210,7 @@ jobs: id: reuse env: GH_TOKEN: ${{ github.token }} - INPUT_FREEZE_RECEIPT_DIGEST: ${{ inputs.freeze_receipt_digest }} SOURCE_SHA: ${{ steps.lineage.outputs.selection_commit }} - SOURCE_TREE: ${{ steps.lineage.outputs.selection_tree }} shell: bash run: | set -euo pipefail @@ -263,39 +249,9 @@ jobs: exit 1 } - statuses="$( - gh api "repos/$GITHUB_REPOSITORY/commits/$SOURCE_SHA/statuses?per_page=100" - )" - if [ -n "$INPUT_FREEZE_RECEIPT_DIGEST" ]; then - freeze_digest="$INPUT_FREEZE_RECEIPT_DIGEST" - else - freeze_digest="$( - jq -r \ - --arg description "tree=$SOURCE_TREE" \ - '[.[] | select( - .state == "success" - and (.context | startswith("codestory/release-freeze/")) - and .description == $description - and .creator.login == "github-actions[bot]" - and .creator.type == "Bot" - and (.target_url | test( - "^https://github.com/TheGreenCedar/CodeStory/actions/runs/[1-9][0-9]*$" - )) - ) | .context | sub("^codestory/release-freeze/"; "")] | unique | if length == 1 then .[0] else "" end' \ - <<<"$statuses" - )" - fi - printf '%s' "$freeze_digest" | grep -Eq '^[0-9a-f]{64}$' - node .github/scripts/release-freeze-barrier.mjs verify-status \ - --repository "$GITHUB_REPOSITORY" \ - --commit "$SOURCE_SHA" \ - --tree "$SOURCE_TREE" \ - --receipt-digest "$freeze_digest" - { echo "reuse=$reuse" echo "source_proof_reused=true" - echo "freeze_receipt_digest=$freeze_digest" } >> "$GITHUB_OUTPUT" - name: Prove the public marketplace install path @@ -359,7 +315,9 @@ jobs: ref: ${{ github.sha }} proof_key: release-${{ needs.preflight.outputs.version }} version: ${{ needs.preflight.outputs.version }} - freeze_receipt_digest: ${{ needs.preflight.outputs.freeze_receipt_digest }} + # This job is deliberately unreachable. An empty digest makes any policy + # regression that reaches it fail before starting a second broad proof. + freeze_receipt_digest: "" packaged-proof: needs: preflight diff --git a/.github/workflows/source-proof.yml b/.github/workflows/source-proof.yml index 52753387d..da7f6fb61 100644 --- a/.github/workflows/source-proof.yml +++ b/.github/workflows/source-proof.yml @@ -27,8 +27,9 @@ on: required: true type: string freeze_receipt_digest: - description: Digest emitted by release-freeze-barrier.mjs for this exact head. - required: true + description: Existing successful freeze receipt digest. Leave empty when acceptance_only is true. + required: false + default: "" type: string version: description: Release version whose source cell this accepted proof emits. @@ -39,6 +40,21 @@ on: required: false default: false type: boolean + support_prs_json: + description: JSON array of support PR numbers already merged into the release head. + required: false + default: "[]" + type: string + reusable_evidence_json: + description: JSON array naming evidence reusable by this exact head. + required: false + default: "[]" + type: string + invalidated_evidence_json: + description: JSON array naming evidence invalidated before this exact head. + required: false + default: "[]" + type: string permissions: actions: write @@ -62,6 +78,8 @@ jobs: outputs: ref: ${{ steps.resolve.outputs.ref }} reuse: ${{ steps.reuse.outputs.reuse }} + freeze_digest: ${{ steps.receipt.outputs.digest }} + freeze_artifact_name: ${{ steps.receipt.outputs.artifact_name }} steps: - name: Resolve trusted exact head id: resolve @@ -130,20 +148,75 @@ jobs: uses: actions/checkout@v5 with: ref: ${{ steps.resolve.outputs.ref }} + fetch-depth: 0 - name: Cancel superseded proof runs + id: cancel shell: bash env: GH_TOKEN: ${{ github.token }} HEAD_SHA: ${{ steps.resolve.outputs.ref }} run: | - node .github/scripts/release-freeze-barrier.mjs cancel-superseded \ + set -euo pipefail + result="$( + node .github/scripts/release-freeze-barrier.mjs cancel-superseded \ --repository "$GITHUB_REPOSITORY" \ --commit "$HEAD_SHA" \ --broad-workflow "Exact-head source proof" \ --broad-workflow "Platform and integration proof" \ --broad-workflow "Release" \ --broad-workflow "Auto Release" + )" + echo "cancelled=$(jq -c '.cancelled' <<<"$result")" >> "$GITHUB_OUTPUT" + + - name: Record executable release freeze + id: receipt + if: ${{ inputs.acceptance_only }} + shell: bash + env: + CALLER_FREEZE_RECEIPT_DIGEST: ${{ inputs.freeze_receipt_digest }} + CANCELLED_RUNS_JSON: ${{ steps.cancel.outputs.cancelled }} + GH_TOKEN: ${{ github.token }} + HEAD_SHA: ${{ steps.resolve.outputs.ref }} + INVALIDATED_EVIDENCE_JSON: ${{ inputs.invalidated_evidence_json }} + PR_NUMBER: ${{ inputs.pr_number }} + REUSABLE_EVIDENCE_JSON: ${{ inputs.reusable_evidence_json }} + SUPPORT_PRS_JSON: ${{ inputs.support_prs_json }} + run: | + set -euo pipefail + test -z "$CALLER_FREEZE_RECEIPT_DIGEST" || { + echo "::error::acceptance_only mints its receipt digest; callers must leave freeze_receipt_digest empty." + exit 1 + } + tree="$(git rev-parse 'HEAD^{tree}')" + node .github/scripts/release-freeze-barrier.mjs record-actions-receipt \ + --repository "$GITHUB_REPOSITORY" \ + --repo "$GITHUB_WORKSPACE" \ + --branch "$GITHUB_REF_NAME" \ + --commit "$HEAD_SHA" \ + --tree "$tree" \ + --release-pr "$PR_NUMBER" \ + --support-prs-json "$SUPPORT_PRS_JSON" \ + --reusable-evidence-json "$REUSABLE_EVIDENCE_JSON" \ + --invalidated-evidence-json "$INVALIDATED_EVIDENCE_JSON" \ + --cancelled-runs-json "$CANCELLED_RUNS_JSON" \ + --run-id "$GITHUB_RUN_ID" \ + --run-attempt "$GITHUB_RUN_ATTEMPT" \ + --broad-workflow "Exact-head source proof" \ + --broad-workflow "Platform and integration proof" \ + --broad-workflow "Release" \ + --broad-workflow "Auto Release" \ + --output "$RUNNER_TEMP/release-freeze-receipt.json" \ + --github-output "$GITHUB_OUTPUT" + + - name: Upload executable release freeze receipt + if: ${{ inputs.acceptance_only }} + uses: actions/upload-artifact@v7.0.1 + with: + name: ${{ steps.receipt.outputs.artifact_name }} + path: ${{ runner.temp }}/release-freeze-receipt.json + if-no-files-found: error + retention-days: 30 - name: Reuse a completed gate for this exact head id: reuse @@ -184,10 +257,10 @@ jobs: echo "reuse=$reuse" >> "$GITHUB_OUTPUT" - name: Require executable release freeze + if: ${{ !inputs.acceptance_only }} shell: bash env: GH_TOKEN: ${{ github.token }} - ACCEPTANCE_ONLY: ${{ inputs.acceptance_only }} FREEZE_RECEIPT_DIGEST: ${{ inputs.freeze_receipt_digest }} HEAD_SHA: ${{ steps.resolve.outputs.ref }} run: | @@ -196,11 +269,7 @@ jobs: tree="$( gh api "repos/$GITHUB_REPOSITORY/git/commits/$HEAD_SHA" --jq '.tree.sha' )" - command=verify-status - if [ "$ACCEPTANCE_ONLY" = true ]; then - command=verify-pending - fi - node .github/scripts/release-freeze-barrier.mjs "$command" \ + node .github/scripts/release-freeze-barrier.mjs verify-status \ --repository "$GITHUB_REPOSITORY" \ --commit "$HEAD_SHA" \ --tree "$tree" \ @@ -328,17 +397,38 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: + - uses: actions/checkout@v5 + with: + ref: ${{ needs.resolve.outputs.ref }} + + - name: Download executable release freeze receipt + uses: actions/download-artifact@v8.0.1 + with: + name: ${{ needs.resolve.outputs.freeze_artifact_name }} + path: ${{ runner.temp }}/release-freeze-receipt + - name: Publish executable release freeze shell: bash env: GH_TOKEN: ${{ github.token }} - FREEZE_RECEIPT_DIGEST: ${{ inputs.freeze_receipt_digest }} + FREEZE_RECEIPT_DIGEST: ${{ needs.resolve.outputs.freeze_digest }} HEAD_SHA: ${{ needs.resolve.outputs.ref }} run: | set -euo pipefail - tree="$( - gh api "repos/$GITHUB_REPOSITORY/git/commits/$HEAD_SHA" --jq '.tree.sha' + tree="$(git rev-parse 'HEAD^{tree}')" + verified_digest="$( + node .github/scripts/release-freeze-barrier.mjs verify-file \ + --receipt "$RUNNER_TEMP/release-freeze-receipt/release-freeze-receipt.json" \ + --repository "$GITHUB_REPOSITORY" \ + --commit "$HEAD_SHA" \ + --tree "$tree" \ + --run-id "$GITHUB_RUN_ID" \ + --run-attempt "$GITHUB_RUN_ATTEMPT" )" + test "$verified_digest" = "$FREEZE_RECEIPT_DIGEST" || { + echo "::error::Downloaded acceptance receipt digest differs from the Actions-generated resolve output." + exit 1 + } gh api \ --method POST \ "repos/$GITHUB_REPOSITORY/statuses/$HEAD_SHA" \ diff --git a/benchmarks/release-evidence/fixtures/candidate.json b/benchmarks/release-evidence/fixtures/candidate.json index 1d0fdbcb1..6807f5dc4 100644 --- a/benchmarks/release-evidence/fixtures/candidate.json +++ b/benchmarks/release-evidence/fixtures/candidate.json @@ -62,7 +62,7 @@ }, "release_claims": { "graph_schema": "codestory.release-claims/v1", - "graph_sha256": "2d4012d77d9ca94e22d6a77c24e54e0d0cbfdc941e288f1b53a6da63fce89526", + "graph_sha256": "63c4f7d047ab277566f73a8f413a765ac86a238b5bc994376c4a8dd41173a59e", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "requested_claims": [ @@ -85,7 +85,7 @@ "type": "performance", "tier": "live_behavior", "status": "measured", - "graph_sha256": "2d4012d77d9ca94e22d6a77c24e54e0d0cbfdc941e288f1b53a6da63fce89526", + "graph_sha256": "63c4f7d047ab277566f73a8f413a765ac86a238b5bc994376c4a8dd41173a59e", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { @@ -106,7 +106,7 @@ "type": "answer_quality", "tier": "answer_quality", "status": "pass", - "graph_sha256": "2d4012d77d9ca94e22d6a77c24e54e0d0cbfdc941e288f1b53a6da63fce89526", + "graph_sha256": "63c4f7d047ab277566f73a8f413a765ac86a238b5bc994376c4a8dd41173a59e", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { diff --git a/benchmarks/release-evidence/fixtures/report.json b/benchmarks/release-evidence/fixtures/report.json index 604cccc32..d5e6892e7 100644 --- a/benchmarks/release-evidence/fixtures/report.json +++ b/benchmarks/release-evidence/fixtures/report.json @@ -7,7 +7,7 @@ "baseline_id": "ci-contract-v1@1111111111111111111111111111111111111111", "baseline_sha256": "0bbbe6dd8b4000151edf7b1270959d08e94e08db876e7f2372b25613e0f237c1", "candidate_path": "benchmarks/release-evidence/fixtures/candidate.json", - "candidate_sha256": "b35b494fe9ec9854100c8e1772606a74a0c613721f6552f51558f5f575450230", + "candidate_sha256": "a3e3899ed89e41dc3839653b5d0d2da86f527797ff1eac39f83e162b0675a6fc", "artifact_paths": [ { "path": "candidate-stats.json", @@ -26,7 +26,7 @@ "type": "performance", "tier": "live_behavior", "status": "pass", - "graph_sha256": "2d4012d77d9ca94e22d6a77c24e54e0d0cbfdc941e288f1b53a6da63fce89526", + "graph_sha256": "63c4f7d047ab277566f73a8f413a765ac86a238b5bc994376c4a8dd41173a59e", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { @@ -47,7 +47,7 @@ "type": "answer_quality", "tier": "answer_quality", "status": "pass", - "graph_sha256": "2d4012d77d9ca94e22d6a77c24e54e0d0cbfdc941e288f1b53a6da63fce89526", + "graph_sha256": "63c4f7d047ab277566f73a8f413a765ac86a238b5bc994376c4a8dd41173a59e", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { @@ -69,7 +69,7 @@ "schema": "codestory.release-claim-evaluation/v1", "status": "pass", "graph_schema": "codestory.release-claims/v1", - "graph_sha256": "2d4012d77d9ca94e22d6a77c24e54e0d0cbfdc941e288f1b53a6da63fce89526", + "graph_sha256": "63c4f7d047ab277566f73a8f413a765ac86a238b5bc994376c4a8dd41173a59e", "evidence_selection": "all_matching_rows_must_pass", "expected_commit": "2222222222222222222222222222222222222222", "evaluated_at": "2026-07-21T02:13:20.738Z", diff --git a/release-claims.json b/release-claims.json index cd5e5ab81..8a390b64e 100644 --- a/release-claims.json +++ b/release-claims.json @@ -1472,7 +1472,7 @@ "required_events": [] }, "release_freeze_barrier": { - "schema": 1, + "schema": 2, "script": ".github/scripts/release-freeze-barrier.mjs", "status_context_prefix": "codestory/release-freeze", "allowed_future_source_changes": [ @@ -1501,6 +1501,12 @@ ], "acceptance": { "producer_workflow": "source-proof.yml", + "receipt_authority": "github_actions", + "receipt_artifact": "release-freeze-receipt-attempt-${{ github.run_attempt }}", + "receipt_file": "release-freeze-receipt.json", + "receipt_producer_job": "resolve", + "status_scope": "pre_calibration_source_head", + "later_commit_revokes": true, "event": "workflow_dispatch", "hostile_job": "freeze-hostile-mutations", "hostile_step": "Execute exact-head hostile mutation matrix", @@ -1523,6 +1529,7 @@ "artifact": "release-cell-prepublish-source-attempt-${{ github.run_attempt }}", "artifact_required_unexpired": true, "cell_emission": "unconditional_on_success", + "post_calibration_status_required": false, "accepted_source": "freeze_receipt.commit", "frozen_descendant": "constant_set.freeze_record.selection_source_commit", "reuse_validation": [ diff --git a/scripts/codestory-release-claims.mjs b/scripts/codestory-release-claims.mjs index 54b0b7f2b..04eac3409 100644 --- a/scripts/codestory-release-claims.mjs +++ b/scripts/codestory-release-claims.mjs @@ -1566,8 +1566,8 @@ export function validateReleaseClaimGraph(graph) { policy.release_freeze_barrier, "workflow_policy.release_freeze_barrier", ); - if (freeze.schema !== 1) { - fail("workflow_policy.release_freeze_barrier.schema must be 1"); + if (freeze.schema !== 2) { + fail("workflow_policy.release_freeze_barrier.schema must be 2"); } nonEmptyText(freeze.script, "workflow_policy.release_freeze_barrier.script"); nonEmptyText( @@ -1606,6 +1606,11 @@ export function validateReleaseClaimGraph(graph) { ); for (const field of [ "producer_workflow", + "receipt_authority", + "receipt_artifact", + "receipt_file", + "receipt_producer_job", + "status_scope", "event", "hostile_job", "hostile_step", @@ -1641,13 +1646,21 @@ export function validateReleaseClaimGraph(graph) { } if ( acceptance.producer_workflow !== "source-proof.yml" + || acceptance.receipt_authority !== "github_actions" + || acceptance.receipt_artifact + !== "release-freeze-receipt-attempt-${{ github.run_attempt }}" + || acceptance.receipt_file !== "release-freeze-receipt.json" + || acceptance.receipt_producer_job !== "resolve" + || acceptance.status_scope !== "pre_calibration_source_head" + || acceptance.later_commit_revokes !== true || acceptance.event !== "workflow_dispatch" || acceptance.windows_probe_max_seconds !== 90 || acceptance.status_creator !== "github-actions[bot]" ) { fail( "workflow_policy.release_freeze_barrier.acceptance must bind the exact " - + "Actions producer, event, protected probe budget, and status creator", + + "Actions receipt authority, immutable artifact, producer, event, protected " + + "probe budget, status scope, revocation, and status creator", ); } const singleSource = object( @@ -1678,6 +1691,13 @@ export function validateReleaseClaimGraph(graph) { + "must emit its source cell unconditionally after successful proof", ); } + if (singleSource.post_calibration_status_required !== false) { + fail( + "workflow_policy.release_freeze_barrier.single_source_proof " + + "must reuse the source cell and constant-only lineage after calibration, " + + "not an active freeze status", + ); + } stringArray( singleSource.reuse_validation, "workflow_policy.release_freeze_barrier.single_source_proof.reuse_validation", diff --git a/scripts/tests/codestory-release-claims.test.mjs b/scripts/tests/codestory-release-claims.test.mjs index 935d7f666..56d1df6d1 100644 --- a/scripts/tests/codestory-release-claims.test.mjs +++ b/scripts/tests/codestory-release-claims.test.mjs @@ -167,6 +167,12 @@ test("versioned claim graph has one deterministic digest and all declared contro graph.workflow_policy.release_freeze_barrier.acceptance, { producer_workflow: "source-proof.yml", + receipt_authority: "github_actions", + receipt_artifact: "release-freeze-receipt-attempt-${{ github.run_attempt }}", + receipt_file: "release-freeze-receipt.json", + receipt_producer_job: "resolve", + status_scope: "pre_calibration_source_head", + later_commit_revokes: true, event: "workflow_dispatch", hostile_job: "freeze-hostile-mutations", hostile_step: "Execute exact-head hostile mutation matrix", @@ -190,11 +196,14 @@ test("versioned claim graph has one deterministic digest and all declared contro .single_source_proof.artifact_required_unexpired, cell_emission: graph.workflow_policy.release_freeze_barrier .single_source_proof.cell_emission, + post_calibration_status_required: graph.workflow_policy.release_freeze_barrier + .single_source_proof.post_calibration_status_required, }, { artifact: "release-cell-prepublish-source-attempt-${{ github.run_attempt }}", artifact_required_unexpired: true, cell_emission: "unconditional_on_success", + post_calibration_status_required: false, }, ); }); @@ -658,6 +667,38 @@ test("graph rejects ambiguous dependencies and unstructured proof lanes", () => /release_freeze_barrier\.acceptance\.windows_runner/u, ); + const callerAuthoredFreeze = structuredClone(graph); + callerAuthoredFreeze.workflow_policy.release_freeze_barrier + .acceptance.receipt_authority = "caller"; + assert.throws( + () => validateReleaseClaimGraph(callerAuthoredFreeze), + /release_freeze_barrier\.acceptance/u, + ); + + const mutableFreezeReceipt = structuredClone(graph); + mutableFreezeReceipt.workflow_policy.release_freeze_barrier + .acceptance.receipt_artifact = "release-freeze-receipt"; + assert.throws( + () => validateReleaseClaimGraph(mutableFreezeReceipt), + /release_freeze_barrier\.acceptance/u, + ); + + const persistentFreezeStatus = structuredClone(graph); + persistentFreezeStatus.workflow_policy.release_freeze_barrier + .acceptance.later_commit_revokes = false; + assert.throws( + () => validateReleaseClaimGraph(persistentFreezeStatus), + /release_freeze_barrier\.acceptance/u, + ); + + const postCalibrationStatus = structuredClone(graph); + postCalibrationStatus.workflow_policy.release_freeze_barrier + .single_source_proof.post_calibration_status_required = true; + assert.throws( + () => validateReleaseClaimGraph(postCalibrationStatus), + /source cell and constant-only lineage after calibration/u, + ); + const missingInvalidation = structuredClone(graph); delete missingInvalidation.workflow_policy.release_freeze_barrier .invalidation_workflow; diff --git a/scripts/tests/fixtures/release-claims/positive.json b/scripts/tests/fixtures/release-claims/positive.json index 3237bd17e..060863c8b 100644 --- a/scripts/tests/fixtures/release-claims/positive.json +++ b/scripts/tests/fixtures/release-claims/positive.json @@ -17,7 +17,7 @@ "type": "source_behavior", "tier": "source", "status": "pass", - "graph_sha256": "2d4012d77d9ca94e22d6a77c24e54e0d0cbfdc941e288f1b53a6da63fce89526", + "graph_sha256": "63c4f7d047ab277566f73a8f413a765ac86a238b5bc994376c4a8dd41173a59e", "observed_at": "2026-07-16T11:00:00.000Z", "expires_at": "2026-07-17T11:00:00.000Z", "identity": { From ab962dd0a29f80c1497e90f4823227ebcee92ed6 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Thu, 30 Jul 2026 09:18:09 -0500 Subject: [PATCH 20/28] bind freeze to live dev head --- .github/scripts/check-workflow-policy.mjs | 9 +- .../scripts/check-workflow-policy.test.mjs | 7 +- .github/scripts/release-freeze-barrier.mjs | 13 ++- .../scripts/release-freeze-barrier.test.mjs | 99 ++++++++++++++++++- 4 files changed, 119 insertions(+), 9 deletions(-) diff --git a/.github/scripts/check-workflow-policy.mjs b/.github/scripts/check-workflow-policy.mjs index 9ea9c562b..2abcd89e8 100644 --- a/.github/scripts/check-workflow-policy.mjs +++ b/.github/scripts/check-workflow-policy.mjs @@ -8033,8 +8033,13 @@ export function releaseFreezeBarrierWorkflowViolations( add( violations, barrierSource.includes('gh(["api", `repos/${repository}/pulls/${number}`])') - && barrierSource.includes("`repos/${repository}/compare/${pr.base.sha}...${commit}`") - && barrierSource.includes("base_commit: pr.base.sha") + && barrierSource.includes( + "`repos/${repository}/git/ref/heads/dev/codestory-next`", + ) + && barrierSource.includes( + "`repos/${repository}/compare/${liveBaseCommit}...${commit}`", + ) + && barrierSource.includes("base_commit: liveBaseCommit") && barrierSource.includes("const currentReleasePr = releasePr(") && barrierSource.includes( "currentReleasePr.base_commit !== receipt?.release_pr?.base_commit", diff --git a/.github/scripts/check-workflow-policy.test.mjs b/.github/scripts/check-workflow-policy.test.mjs index 4db5a9cf8..b77ff6632 100644 --- a/.github/scripts/check-workflow-policy.test.mjs +++ b/.github/scripts/check-workflow-policy.test.mjs @@ -3178,9 +3178,14 @@ test("release freeze policy pins live PR base and support ancestry revalidation" 'gh(["api", `repos/${repository}/pulls/${number}`])', "JSON.parse('{}')", )], + ["release base lookup stops using the live integration ref", value => + value.replace( + "`repos/${repository}/git/ref/heads/dev/codestory-next`", + "`repos/${repository}/git/commits/${pr.base.sha}`", + )], ["release PR head stops proving it contains the current dev base", value => value.replace( - "`repos/${repository}/compare/${pr.base.sha}...${commit}`", + "`repos/${repository}/compare/${liveBaseCommit}...${commit}`", "`repos/${repository}/commits/${commit}`", )], ["verification stops detecting a base advance", value => diff --git a/.github/scripts/release-freeze-barrier.mjs b/.github/scripts/release-freeze-barrier.mjs index be3655364..b5e15c98e 100644 --- a/.github/scripts/release-freeze-barrier.mjs +++ b/.github/scripts/release-freeze-barrier.mjs @@ -431,13 +431,18 @@ function supportPr(repository, number, commit, repo) { function releasePr(repository, number, { branch, commit }) { const pr = JSON.parse(gh(["api", `repos/${repository}/pulls/${number}`])); + const liveBaseRef = JSON.parse(gh([ + "api", + `repos/${repository}/git/ref/heads/dev/codestory-next`, + ])); + const liveBaseCommit = liveBaseRef?.object?.sha; if ( pr.state !== "open" || pr?.base?.ref !== "dev/codestory-next" || pr?.head?.ref !== branch || pr?.head?.sha !== commit || pr?.head?.repo?.full_name !== repository - || !/^[0-9a-f]{40}$/u.test(String(pr?.base?.sha ?? "")) + || !/^[0-9a-f]{40}$/u.test(String(liveBaseCommit ?? "")) ) { fail( `release PR #${number} must be an open same-repository ${branch} -> ` @@ -446,17 +451,17 @@ function releasePr(repository, number, { branch, commit }) { } const comparison = JSON.parse(gh([ "api", - `repos/${repository}/compare/${pr.base.sha}...${commit}`, + `repos/${repository}/compare/${liveBaseCommit}...${commit}`, ])); if (!["ahead", "identical"].includes(comparison?.status)) { fail( - `release PR #${number} head ${commit} does not contain current dev base ${pr.base.sha}`, + `release PR #${number} head ${commit} does not contain current dev base ${liveBaseCommit}`, ); } return { number: pr.number, base: pr.base.ref, - base_commit: pr.base.sha, + base_commit: liveBaseCommit, head: pr.head.ref, head_commit: pr.head.sha, }; diff --git a/.github/scripts/release-freeze-barrier.test.mjs b/.github/scripts/release-freeze-barrier.test.mjs index d0824fe10..5fda647b9 100644 --- a/.github/scripts/release-freeze-barrier.test.mjs +++ b/.github/scripts/release-freeze-barrier.test.mjs @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; -import { spawnSync } from "node:child_process"; -import { chmodSync, mkdtempSync, writeFileSync } from "node:fs"; +import { execFileSync, spawnSync } from "node:child_process"; +import { chmodSync, mkdirSync, mkdtempSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import test from "node:test"; @@ -363,6 +363,101 @@ test("record-actions-receipt refuses to mint authority outside GitHub Actions", ); }); +test("record-actions-receipt rejects a PR whose snapshot omits the live dev head", () => { + const sandbox = mkdtempSync(path.join(tmpdir(), "codestory-freeze-stale-base-")); + const root = path.join(sandbox, "repo"); + mkdirSync(root); + execFileSync("git", ["init", "-q", "-b", "codex/release", root]); + execFileSync("git", ["-C", root, "config", "user.email", "test@example.com"]); + execFileSync("git", ["-C", root, "config", "user.name", "Test"]); + writeFileSync(path.join(root, "tracked.txt"), "candidate\n"); + execFileSync("git", ["-C", root, "add", "tracked.txt"]); + execFileSync("git", ["-C", root, "commit", "-qm", "candidate"]); + const commit = execFileSync("git", ["-C", root, "rev-parse", "HEAD"], { + encoding: "utf8", + }).trim(); + const tree = execFileSync("git", ["-C", root, "rev-parse", "HEAD^{tree}"], { + encoding: "utf8", + }).trim(); + const staleBase = "a".repeat(40); + const liveBase = "b".repeat(40); + const fakeGh = path.join(sandbox, "gh"); + writeFileSync( + fakeGh, + `#!/bin/sh +if [ "$1" = "api" ] && [ "$2" = "repos/${REPOSITORY}/pulls/1597" ]; then + printf '%s\\n' '{"number":1597,"state":"open","base":{"ref":"dev/codestory-next","sha":"${staleBase}"},"head":{"ref":"codex/release","sha":"${commit}","repo":{"full_name":"${REPOSITORY}"}}}' + exit 0 +fi +if [ "$1" = "api" ] && [ "$2" = "repos/${REPOSITORY}/git/ref/heads/dev/codestory-next" ]; then + printf '%s\\n' '{"object":{"sha":"${liveBase}"}}' + exit 0 +fi +if [ "$1" = "api" ] && [ "$2" = "repos/${REPOSITORY}/compare/${liveBase}...${commit}" ]; then + printf '%s\\n' '{"status":"diverged"}' + exit 0 +fi +if [ "$1" = "api" ] && [ "$2" = "repos/${REPOSITORY}/compare/${staleBase}...${commit}" ]; then + printf '%s\\n' '{"status":"ahead"}' + exit 0 +fi +if [ "$1 $2" = "run list" ]; then + printf '%s\\n' '[]' + exit 0 +fi +exit 9 +`, + ); + chmodSync(fakeGh, 0o755); + const script = new URL("./release-freeze-barrier.mjs", import.meta.url); + const result = spawnSync( + process.execPath, + [ + script.pathname, + "record-actions-receipt", + "--repo", + root, + "--repository", + REPOSITORY, + "--branch", + "codex/release", + "--commit", + commit, + "--tree", + tree, + "--release-pr", + "1597", + "--output", + path.join(root, "receipt.json"), + "--run-id", + String(RUN_ID), + "--run-attempt", + String(RUN_ATTEMPT), + "--support-prs-json", + "[]", + "--reusable-evidence-json", + "[]", + "--invalidated-evidence-json", + "[]", + "--cancelled-runs-json", + "[]", + "--broad-workflow", + "Exact-head source proof", + ], + { + encoding: "utf8", + env: { + ...process.env, + GITHUB_ACTIONS: "true", + GITHUB_EVENT_NAME: "workflow_dispatch", + PATH: `${sandbox}${path.delimiter}${process.env.PATH}`, + }, + }, + ); + assert.notEqual(result.status, 0); + assert.match(result.stderr, /does not contain current dev base/u); +}); + test("cancel-superseded rejects a cancellation request that leaves the run active", () => { const root = mkdtempSync(path.join(tmpdir(), "codestory-freeze-gh-")); const fakeGh = path.join(root, "gh"); From e980b8205499545a61d3caa0c9937b2cf54cab48 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Thu, 30 Jul 2026 09:20:51 -0500 Subject: [PATCH 21/28] freeze embedding server constants --- ...er-user-embedding-server-constant-set.json | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json index 36a5e49fc..967aa2776 100644 --- a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json +++ b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json @@ -1,7 +1,7 @@ { "calibration_required_values": { "capacity_retry_policy": { - "retry_after_ms": 40, + "retry_after_ms": 45, "retry_class": "after_capacity_change", "retry_condition_source": "named_condition_from_typed_capacity_response" }, @@ -9,7 +9,7 @@ "election_backoff_policy": { "initial_backoff_ms": 7, "jitter": "sha256(process_start_id||attempt) modulo inclusive [initial_backoff_ms,maximum_backoff_ms]", - "maximum_backoff_ms": 102 + "maximum_backoff_ms": 109 }, "hard_native_no_progress_ms": 385431, "request_deadlines_ms": { @@ -43,7 +43,22 @@ "query_queue_capacity": 64, "true_idle_observation_grace_ms": 2500 }, - "freeze_record": null, + "freeze_record": { + "calibration_bundle_sha256": "40813f9d5fac21274301a41a70aa446aaacb3d816ceb926fcb4201ff1b5bd27b", + "calibration_freeze_digest": "d2dd1d32ed8699980f90e6c14806d3e86b0c68d7791c14dbacb57a4ee25fb975", + "input_constant_set_sha256": "ea58d298473ddf320469d15dc7c32176a1109d615a689c15ff76b67fb337e109", + "measurement_protocol_sha256": "d1bb9b2c7eb354fe98990aa32eedc0e165b0cf804212966e0a2ee362a2f5bf8e", + "protocol_sha256": "f4a3fa4afb4d5bcd8e707a5e21b687cdd023dc3398b28ff6891a2318e89c5ec7", + "run_artifact_sha256s": [ + "6083cf27e86367eec7ff5b31365e0475641df950309d41b48f1db3365c1452e6", + "a807d48bfa1b72bdb6e5eead4f0666fca49de879d8f89a4e7a49a84ae250c9d3", + "e576fdd9d2f8a1b7498edc64f78b224b8a81407183e204d011a0e750d868df18" + ], + "selected_at": "github-actions-run:30550654213:1", + "selection_rule": "constant_only_three_fresh_generations_one_sample_each+slow_host_floors_v2", + "selection_source_commit": "196fd369b267e8e182c91b970fbbea40f1a679a5", + "selection_source_tree": "e555fe17511fc7bc6a6dad1545d33693bb32839f" + }, "qualification_thresholds": { "backend_observed_accelerator_residency": 1, "bulk_documents_per_second": 2, @@ -60,5 +75,5 @@ }, "schema_version": 1, "selection_protocol": "codestory-per-user-embedding-server-v1", - "status": "unfrozen" + "status": "frozen" } From 9b2a26de7b4e5fc6e624d86d6848cd096da32f02 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Thu, 30 Jul 2026 09:26:07 -0500 Subject: [PATCH 22/28] paginate active proof cancellation --- .github/scripts/check-workflow-policy.mjs | 10 ++ .../scripts/check-workflow-policy.test.mjs | 16 +++ .github/scripts/release-freeze-barrier.mjs | 40 +++++--- .../scripts/release-freeze-barrier.test.mjs | 97 +++++++++++++++++-- 4 files changed, 145 insertions(+), 18 deletions(-) diff --git a/.github/scripts/check-workflow-policy.mjs b/.github/scripts/check-workflow-policy.mjs index 2abcd89e8..e4bb32778 100644 --- a/.github/scripts/check-workflow-policy.mjs +++ b/.github/scripts/check-workflow-policy.mjs @@ -8049,6 +8049,16 @@ export function releaseFreezeBarrierWorkflowViolations( && barrierSource.includes("support PR #${number} is not merged"), "[freeze_barrier] Actions receipt authority must recheck the live release PR base and integrated support PR ancestry", ); + add( + violations, + barrierSource.includes("for (const status of ACTIVE_RUN_STATES)") + && barrierSource.includes('"api",\n "--paginate",\n "--slurp",') + && barrierSource.includes( + "`repos/${repository}/actions/runs?status=${status}&per_page=100`", + ) + && !barrierSource.includes('"run",\n "list",'), + "[freeze_barrier] obsolete-run discovery must paginate every active Actions state", + ); const invalidationFile = freeze.invalidation_workflow; const invalidation = workflows.get(invalidationFile); diff --git a/.github/scripts/check-workflow-policy.test.mjs b/.github/scripts/check-workflow-policy.test.mjs index b77ff6632..afeed349c 100644 --- a/.github/scripts/check-workflow-policy.test.mjs +++ b/.github/scripts/check-workflow-policy.test.mjs @@ -3212,6 +3212,22 @@ test("release freeze policy pins live PR base and support ancestry revalidation" ); }); } + + await t.test("active workflow discovery becomes bounded", () => { + const bounded = source.replace( + '"api",\n "--paginate",\n "--slurp",', + '"run",\n "list",\n "--limit",', + ); + const violations = releaseFreezeBarrierWorkflowViolations( + loadWorkflows(), + loadReleaseClaimGraph(root), + bounded, + ); + assert.match( + violations.join("\n"), + /obsolete-run discovery must paginate every active Actions state/u, + ); + }); }); test("Windows package proof retains the readable native sccache executable", () => { diff --git a/.github/scripts/release-freeze-barrier.mjs b/.github/scripts/release-freeze-barrier.mjs index b5e15c98e..03ca33ddb 100644 --- a/.github/scripts/release-freeze-barrier.mjs +++ b/.github/scripts/release-freeze-barrier.mjs @@ -303,18 +303,34 @@ export function validateReceipt( } function currentRuns(repository) { - const raw = gh([ - "run", - "list", - "--repo", - repository, - "--limit", - "100", - "--json", - "databaseId,workflowName,headSha,headBranch,status,event,url", - ]); - const parsed = JSON.parse(raw || "[]"); - return parsed.filter((entry) => ACTIVE_RUN_STATES.has(entry.status)); + const runs = []; + for (const status of ACTIVE_RUN_STATES) { + const raw = gh([ + "api", + "--paginate", + "--slurp", + `repos/${repository}/actions/runs?status=${status}&per_page=100`, + ]); + const pages = JSON.parse(raw || "[]"); + if (!Array.isArray(pages)) { + fail(`active workflow query for ${status} did not return paginated pages`); + } + for (const page of pages) { + for (const entry of page?.workflow_runs ?? []) { + runs.push({ + databaseId: entry.id, + workflowName: entry.name, + headSha: entry.head_sha, + headBranch: entry.head_branch, + status: entry.status, + event: entry.event, + url: entry.html_url, + }); + } + } + } + const unique = new Map(runs.map(entry => [String(entry.databaseId), entry])); + return [...unique.values()].filter((entry) => ACTIVE_RUN_STATES.has(entry.status)); } function cancelSupersededRuns({ repository, commit, workflows, runs }) { diff --git a/.github/scripts/release-freeze-barrier.test.mjs b/.github/scripts/release-freeze-barrier.test.mjs index 5fda647b9..ef837a3bc 100644 --- a/.github/scripts/release-freeze-barrier.test.mjs +++ b/.github/scripts/release-freeze-barrier.test.mjs @@ -464,8 +464,15 @@ test("cancel-superseded rejects a cancellation request that leaves the run activ writeFileSync( fakeGh, `#!/bin/sh -if [ "$1 $2" = "run list" ]; then - printf '%s\\n' '[{"databaseId":123,"workflowName":"Exact-head source proof","headSha":"${"9".repeat(40)}","headBranch":"old","status":"in_progress","event":"workflow_dispatch","url":"https://example.invalid/123"}]' +if [ "$1 $2 $3" = "api --paginate --slurp" ]; then + case "$4" in + *status=in_progress*) + printf '%s\\n' '[ + {"workflow_runs":[{"id":123,"name":"Exact-head source proof","head_sha":"${"9".repeat(40)}","head_branch":"old","status":"in_progress","event":"workflow_dispatch","html_url":"https://example.invalid/123"}]} + ]' + ;; + *) printf '%s\\n' '[{"workflow_runs":[]}]' ;; + esac exit 0 fi if [ "$1 $2" = "run cancel" ]; then @@ -502,14 +509,85 @@ exit 1 assert.match(result.stderr, /remains queued or running after cancellation/u); }); +test("cancel-superseded finds an obsolete proof on a later active-run page", () => { + const root = mkdtempSync(path.join(tmpdir(), "codestory-freeze-paginated-gh-")); + const fakeGh = path.join(root, "gh"); + const cancelledMarker = path.join(root, "cancelled"); + writeFileSync( + fakeGh, + `#!/bin/sh +if [ "$1 $2 $3" = "api --paginate --slurp" ]; then + case "$4" in + *status=in_progress*) + if [ -f "${cancelledMarker}" ]; then + printf '%s\\n' '[{"workflow_runs":[]}]' + else + printf '%s\\n' '[ + {"workflow_runs":[{"id":1,"name":"Draft source checks","head_sha":"${COMMIT}","head_branch":"candidate","status":"in_progress","event":"pull_request","html_url":"https://example.invalid/1"}]}, + {"workflow_runs":[{"id":999,"name":"Exact-head source proof","head_sha":"${"9".repeat(40)}","head_branch":"obsolete","status":"in_progress","event":"workflow_dispatch","html_url":"https://example.invalid/999"}]} + ]' + fi + ;; + *) printf '%s\\n' '[{"workflow_runs":[]}]' ;; + esac + exit 0 +fi +if [ "$1 $2 $3" = "run cancel 999" ]; then + : > "${cancelledMarker}" + exit 0 +fi +exit 9 +`, + ); + chmodSync(fakeGh, 0o755); + const script = new URL("./release-freeze-barrier.mjs", import.meta.url); + const result = spawnSync( + process.execPath, + [ + script.pathname, + "cancel-superseded", + "--repository", + REPOSITORY, + "--commit", + COMMIT, + "--broad-workflow", + "Exact-head source proof", + ], + { + encoding: "utf8", + env: { + ...process.env, + CODESTORY_FREEZE_CANCEL_POLL_ATTEMPTS: "2", + CODESTORY_FREEZE_CANCEL_POLL_MS: "0", + PATH: `${root}${path.delimiter}${process.env.PATH}`, + }, + }, + ); + assert.equal(result.status, 0, result.stderr); + assert.deepEqual(JSON.parse(result.stdout), { + cancelled: [{ + database_id: 999, + head_sha: "9".repeat(40), + workflow: "Exact-head source proof", + }], + }); +}); + test("cancel-superseded rejects another active broad run on the unchanged head", () => { const root = mkdtempSync(path.join(tmpdir(), "codestory-freeze-duplicate-gh-")); const fakeGh = path.join(root, "gh"); writeFileSync( fakeGh, `#!/bin/sh -if [ "$1 $2" = "run list" ]; then - printf '%s\\n' '[{"databaseId":456,"workflowName":"Exact-head source proof","headSha":"${COMMIT}","headBranch":"candidate","status":"in_progress","event":"workflow_dispatch","url":"https://example.invalid/456"}]' +if [ "$1 $2 $3" = "api --paginate --slurp" ]; then + case "$4" in + *status=in_progress*) + printf '%s\\n' '[ + {"workflow_runs":[{"id":456,"name":"Exact-head source proof","head_sha":"${COMMIT}","head_branch":"candidate","status":"in_progress","event":"workflow_dispatch","html_url":"https://example.invalid/456"}]} + ]' + ;; + *) printf '%s\\n' '[{"workflow_runs":[]}]' ;; + esac exit 0 fi exit 1 @@ -547,8 +625,15 @@ test("automatic invalidation preserves an active proof for the new exact head", writeFileSync( fakeGh, `#!/bin/sh -if [ "$1 $2" = "run list" ]; then - printf '%s\\n' '[{"databaseId":789,"workflowName":"Exact-head source proof","headSha":"${COMMIT}","headBranch":"candidate","status":"in_progress","event":"workflow_dispatch","url":"https://example.invalid/789"}]' +if [ "$1 $2 $3" = "api --paginate --slurp" ]; then + case "$4" in + *status=in_progress*) + printf '%s\\n' '[ + {"workflow_runs":[{"id":789,"name":"Exact-head source proof","head_sha":"${COMMIT}","head_branch":"candidate","status":"in_progress","event":"workflow_dispatch","html_url":"https://example.invalid/789"}]} + ]' + ;; + *) printf '%s\\n' '[{"workflow_runs":[]}]' ;; + esac exit 0 fi if [ "$1 $2" = "run cancel" ]; then From 6c28714ded34b28be6f560fdb765e195f28268d9 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Thu, 30 Jul 2026 11:27:08 -0500 Subject: [PATCH 23/28] restore unfrozen calibration input --- ...er-user-embedding-server-constant-set.json | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json index 967aa2776..36a5e49fc 100644 --- a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json +++ b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json @@ -1,7 +1,7 @@ { "calibration_required_values": { "capacity_retry_policy": { - "retry_after_ms": 45, + "retry_after_ms": 40, "retry_class": "after_capacity_change", "retry_condition_source": "named_condition_from_typed_capacity_response" }, @@ -9,7 +9,7 @@ "election_backoff_policy": { "initial_backoff_ms": 7, "jitter": "sha256(process_start_id||attempt) modulo inclusive [initial_backoff_ms,maximum_backoff_ms]", - "maximum_backoff_ms": 109 + "maximum_backoff_ms": 102 }, "hard_native_no_progress_ms": 385431, "request_deadlines_ms": { @@ -43,22 +43,7 @@ "query_queue_capacity": 64, "true_idle_observation_grace_ms": 2500 }, - "freeze_record": { - "calibration_bundle_sha256": "40813f9d5fac21274301a41a70aa446aaacb3d816ceb926fcb4201ff1b5bd27b", - "calibration_freeze_digest": "d2dd1d32ed8699980f90e6c14806d3e86b0c68d7791c14dbacb57a4ee25fb975", - "input_constant_set_sha256": "ea58d298473ddf320469d15dc7c32176a1109d615a689c15ff76b67fb337e109", - "measurement_protocol_sha256": "d1bb9b2c7eb354fe98990aa32eedc0e165b0cf804212966e0a2ee362a2f5bf8e", - "protocol_sha256": "f4a3fa4afb4d5bcd8e707a5e21b687cdd023dc3398b28ff6891a2318e89c5ec7", - "run_artifact_sha256s": [ - "6083cf27e86367eec7ff5b31365e0475641df950309d41b48f1db3365c1452e6", - "a807d48bfa1b72bdb6e5eead4f0666fca49de879d8f89a4e7a49a84ae250c9d3", - "e576fdd9d2f8a1b7498edc64f78b224b8a81407183e204d011a0e750d868df18" - ], - "selected_at": "github-actions-run:30550654213:1", - "selection_rule": "constant_only_three_fresh_generations_one_sample_each+slow_host_floors_v2", - "selection_source_commit": "196fd369b267e8e182c91b970fbbea40f1a679a5", - "selection_source_tree": "e555fe17511fc7bc6a6dad1545d33693bb32839f" - }, + "freeze_record": null, "qualification_thresholds": { "backend_observed_accelerator_residency": 1, "bulk_documents_per_second": 2, @@ -75,5 +60,5 @@ }, "schema_version": 1, "selection_protocol": "codestory-per-user-embedding-server-v1", - "status": "frozen" + "status": "unfrozen" } From 681ca99098b86006e7dc1f51c27158c757ac05c9 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Thu, 30 Jul 2026 11:44:27 -0500 Subject: [PATCH 24/28] prove only the frozen candidate once --- .github/scripts/check-workflow-policy.mjs | 12 +++++- .../scripts/check-workflow-policy.test.mjs | 37 +++++++++++++++++++ .github/workflows/packaged-platform-pr.yml | 2 +- .../release-evidence/fixtures/candidate.json | 6 +-- .../release-evidence/fixtures/report.json | 6 +-- release-claims.json | 2 + scripts/codestory-release-claims.mjs | 4 +- .../tests/codestory-release-claims.test.mjs | 11 ++++++ .../fixtures/release-claims/positive.json | 2 +- 9 files changed, 72 insertions(+), 10 deletions(-) diff --git a/.github/scripts/check-workflow-policy.mjs b/.github/scripts/check-workflow-policy.mjs index 37c943205..18cef670f 100644 --- a/.github/scripts/check-workflow-policy.mjs +++ b/.github/scripts/check-workflow-policy.mjs @@ -970,7 +970,7 @@ const packagedPlatformWorkflowDigest = // made advisory, parked in dead code, or followed by a payload substitution // while leaving the expected tokens in place. const packagedPlatformCoordinatorWorkflowDigest = - "464906e3cd7ec0e2f7e9195d60de035fdba76172c25d8b0861d0982f9d7dcc3e"; + "5017abab05e80355daf4618795d5ec7f09c07b4bc33cc1d52dca968a96b056bb"; const frozenCandidateQualityWorkflowDigest = "92d0a7ab0e0df63dacd5cc3ef0b58500a6578036494c329aa35279048734f173"; const macosMetalWorkflowDigest = @@ -5145,6 +5145,16 @@ function validatePackagedCoordinator(workflows, violations, graph) { INPUT_CALIBRATION_RUN_ID: "${{ inputs.calibration_bundle_run_id }}", }); requireExactResolverContract(violations, file, route, platformResolverContractDigest); + const sourceProofRequirement = namedStep( + route, + "Require successful exact-head source proof", + ); + add( + violations, + sourceProofRequirement?.if + === "steps.resolve.outputs.mode != 'integration' && steps.resolve.outputs.mode != 'calibration'", + `${file} calibration alone must skip pre-freeze source proof while every frozen-candidate mode requires it`, + ); requireStepRun(violations, file, route, "Require successful exact-head source proof", [ "actions/runs?head_sha=$HEAD_SHA", '.path == ".github/workflows/source-proof.yml"', diff --git a/.github/scripts/check-workflow-policy.test.mjs b/.github/scripts/check-workflow-policy.test.mjs index 45f48a142..f2a6fb61d 100644 --- a/.github/scripts/check-workflow-policy.test.mjs +++ b/.github/scripts/check-workflow-policy.test.mjs @@ -2941,6 +2941,43 @@ test("source proof reuse accepts only whole successful workflow runs", async (t) } }); +test("calibration precedes the sole frozen-candidate source proof", async (t) => { + assert.deepEqual(validateWorkflows(loadWorkflows()), []); + const coordinatorFile = "packaged-platform-pr.yml"; + const mutations = [ + ["calibration regains a pre-freeze source proof", workflow => { + draftStep( + workflow.jobs.route, + "Require successful exact-head source proof", + ).if = "steps.resolve.outputs.mode != 'integration'"; + }], + ["qualification loses the frozen-head source proof", workflow => { + draftStep( + workflow.jobs.route, + "Require successful exact-head source proof", + ).if + = "steps.resolve.outputs.mode != 'integration' && steps.resolve.outputs.mode != 'calibration' && steps.resolve.outputs.mode != 'qualification'"; + }], + ["every mode loses the exact-head source proof", workflow => { + draftStep( + workflow.jobs.route, + "Require successful exact-head source proof", + ).if = "false"; + }], + ]; + + for (const [name, mutate] of mutations) { + await t.test(name, () => { + const workflows = loadWorkflows(); + mutate(workflows.get(coordinatorFile)); + assert.match( + validateWorkflows(workflows).join("\n"), + /calibration alone must skip pre-freeze source proof while every frozen-candidate mode requires it/u, + ); + }); + } +}); + test("Windows package proof retains the readable native sccache executable", () => { const directory = mkdtempSync(path.join(os.tmpdir(), "codestory-windows-sccache-")); try { diff --git a/.github/workflows/packaged-platform-pr.yml b/.github/workflows/packaged-platform-pr.yml index 64d322a15..17852d590 100644 --- a/.github/workflows/packaged-platform-pr.yml +++ b/.github/workflows/packaged-platform-pr.yml @@ -166,7 +166,7 @@ jobs: } >> "$GITHUB_OUTPUT" - name: Require successful exact-head source proof - if: steps.resolve.outputs.mode != 'integration' + if: steps.resolve.outputs.mode != 'integration' && steps.resolve.outputs.mode != 'calibration' shell: bash env: GH_TOKEN: ${{ github.token }} diff --git a/benchmarks/release-evidence/fixtures/candidate.json b/benchmarks/release-evidence/fixtures/candidate.json index c8c6a5b49..d0fd4c0e9 100644 --- a/benchmarks/release-evidence/fixtures/candidate.json +++ b/benchmarks/release-evidence/fixtures/candidate.json @@ -62,7 +62,7 @@ }, "release_claims": { "graph_schema": "codestory.release-claims/v1", - "graph_sha256": "7385b32924d42e94c14b8d1f41fcec50bded200fc0e65fec05f43036e2896087", + "graph_sha256": "b08ec2429ec708519b2b67532217379d7bb6a64dd1afbbfa2501652411cb1b4b", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "requested_claims": [ @@ -85,7 +85,7 @@ "type": "performance", "tier": "live_behavior", "status": "measured", - "graph_sha256": "7385b32924d42e94c14b8d1f41fcec50bded200fc0e65fec05f43036e2896087", + "graph_sha256": "b08ec2429ec708519b2b67532217379d7bb6a64dd1afbbfa2501652411cb1b4b", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { @@ -106,7 +106,7 @@ "type": "answer_quality", "tier": "answer_quality", "status": "pass", - "graph_sha256": "7385b32924d42e94c14b8d1f41fcec50bded200fc0e65fec05f43036e2896087", + "graph_sha256": "b08ec2429ec708519b2b67532217379d7bb6a64dd1afbbfa2501652411cb1b4b", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { diff --git a/benchmarks/release-evidence/fixtures/report.json b/benchmarks/release-evidence/fixtures/report.json index 7a0d10f04..f203f4e35 100644 --- a/benchmarks/release-evidence/fixtures/report.json +++ b/benchmarks/release-evidence/fixtures/report.json @@ -26,7 +26,7 @@ "type": "performance", "tier": "live_behavior", "status": "pass", - "graph_sha256": "7385b32924d42e94c14b8d1f41fcec50bded200fc0e65fec05f43036e2896087", + "graph_sha256": "b08ec2429ec708519b2b67532217379d7bb6a64dd1afbbfa2501652411cb1b4b", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { @@ -47,7 +47,7 @@ "type": "answer_quality", "tier": "answer_quality", "status": "pass", - "graph_sha256": "7385b32924d42e94c14b8d1f41fcec50bded200fc0e65fec05f43036e2896087", + "graph_sha256": "b08ec2429ec708519b2b67532217379d7bb6a64dd1afbbfa2501652411cb1b4b", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { @@ -69,7 +69,7 @@ "schema": "codestory.release-claim-evaluation/v1", "status": "pass", "graph_schema": "codestory.release-claims/v1", - "graph_sha256": "7385b32924d42e94c14b8d1f41fcec50bded200fc0e65fec05f43036e2896087", + "graph_sha256": "b08ec2429ec708519b2b67532217379d7bb6a64dd1afbbfa2501652411cb1b4b", "evidence_selection": "all_matching_rows_must_pass", "expected_commit": "2222222222222222222222222222222222222222", "evaluated_at": "2026-07-21T02:13:20.738Z", diff --git a/release-claims.json b/release-claims.json index e9e07b945..bf62003d8 100644 --- a/release-claims.json +++ b/release-claims.json @@ -1133,6 +1133,8 @@ "coordinator_workflow": "packaged-platform-pr.yml", "mode": "calibration", "assembly_job": "calibration-assemble", + "pre_collection_source_proof_required": false, + "source_proof_stage": "frozen_candidate_before_qualification", "required_cells": [ { "id": "protected_macos_arm64_metal", diff --git a/scripts/codestory-release-claims.mjs b/scripts/codestory-release-claims.mjs index 7ee478bd3..e0a348fb3 100644 --- a/scripts/codestory-release-claims.mjs +++ b/scripts/codestory-release-claims.mjs @@ -541,8 +541,10 @@ function validateCalibrationPolicy(value) { calibration.coordinator_workflow !== "packaged-platform-pr.yml" || calibration.mode !== "calibration" || calibration.assembly_job !== "calibration-assemble" + || calibration.pre_collection_source_proof_required !== false + || calibration.source_proof_stage !== "frozen_candidate_before_qualification" ) { - fail("workflow_policy.calibration must name the canonical calibration coordinator and assembly job"); + fail("workflow_policy.calibration must collect before the sole frozen-candidate source proof"); } if (calibration.runs_per_required_cell !== 3) { fail("workflow_policy.calibration must require exactly three clean runs per required cell"); diff --git a/scripts/tests/codestory-release-claims.test.mjs b/scripts/tests/codestory-release-claims.test.mjs index b4cbd4a3f..3e3f7a225 100644 --- a/scripts/tests/codestory-release-claims.test.mjs +++ b/scripts/tests/codestory-release-claims.test.mjs @@ -289,6 +289,11 @@ test("claim graph freezes Mac-only accelerated 3x1 constant calibration", () => assert.equal(calibration.optional_cells[0].feeds_constant_selection, false); assert.equal(calibration.runs_per_required_cell, 3); assert.equal(calibration.samples_per_metric_per_run, 1); + assert.equal(calibration.pre_collection_source_proof_required, false); + assert.equal( + calibration.source_proof_stage, + "frozen_candidate_before_qualification", + ); assert.deepEqual(calibration.forbidden_environment, [ "CODESTORY_EMBED_ALLOW_CPU=1", ]); @@ -312,6 +317,12 @@ test("claim graph freezes Mac-only accelerated 3x1 constant calibration", () => [draft => { draft.workflow_policy.calibration.samples_per_metric_per_run = 3; }, /exactly one sample per metric per run/u], + [draft => { + draft.workflow_policy.calibration.pre_collection_source_proof_required = true; + }, /sole frozen-candidate source proof/u], + [draft => { + draft.workflow_policy.calibration.source_proof_stage = "before_calibration"; + }, /sole frozen-candidate source proof/u], [draft => { draft.workflow_policy.calibration.forbidden_environment = [ "CODESTORY_EMBED_ALLOW_CPU=0", diff --git a/scripts/tests/fixtures/release-claims/positive.json b/scripts/tests/fixtures/release-claims/positive.json index 902934188..3ddc9f8f4 100644 --- a/scripts/tests/fixtures/release-claims/positive.json +++ b/scripts/tests/fixtures/release-claims/positive.json @@ -17,7 +17,7 @@ "type": "source_behavior", "tier": "source", "status": "pass", - "graph_sha256": "7385b32924d42e94c14b8d1f41fcec50bded200fc0e65fec05f43036e2896087", + "graph_sha256": "b08ec2429ec708519b2b67532217379d7bb6a64dd1afbbfa2501652411cb1b4b", "observed_at": "2026-07-16T11:00:00.000Z", "expires_at": "2026-07-17T11:00:00.000Z", "identity": { From c72c87a963b81c626530958078b9f54f8f47efc5 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Thu, 30 Jul 2026 11:51:36 -0500 Subject: [PATCH 25/28] freeze embedding server constants --- ...er-user-embedding-server-constant-set.json | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json index 36a5e49fc..33c1aed5a 100644 --- a/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json +++ b/crates/codestory-llama-sys/per-user-embedding-server-constant-set.json @@ -1,7 +1,7 @@ { "calibration_required_values": { "capacity_retry_policy": { - "retry_after_ms": 40, + "retry_after_ms": 42, "retry_class": "after_capacity_change", "retry_condition_source": "named_condition_from_typed_capacity_response" }, @@ -9,7 +9,7 @@ "election_backoff_policy": { "initial_backoff_ms": 7, "jitter": "sha256(process_start_id||attempt) modulo inclusive [initial_backoff_ms,maximum_backoff_ms]", - "maximum_backoff_ms": 102 + "maximum_backoff_ms": 104 }, "hard_native_no_progress_ms": 385431, "request_deadlines_ms": { @@ -43,7 +43,22 @@ "query_queue_capacity": 64, "true_idle_observation_grace_ms": 2500 }, - "freeze_record": null, + "freeze_record": { + "calibration_bundle_sha256": "2adaaab974814cf890609bac0f1b6be54fb04cda4812aea33f06dff63a954ed0", + "calibration_freeze_digest": "511ec0e9018d73c1cfb4669de2e1b4bd722ef650cd9e6d38d7179d568913f2d5", + "input_constant_set_sha256": "ea58d298473ddf320469d15dc7c32176a1109d615a689c15ff76b67fb337e109", + "measurement_protocol_sha256": "d1bb9b2c7eb354fe98990aa32eedc0e165b0cf804212966e0a2ee362a2f5bf8e", + "protocol_sha256": "f4a3fa4afb4d5bcd8e707a5e21b687cdd023dc3398b28ff6891a2318e89c5ec7", + "run_artifact_sha256s": [ + "2332db8dfdf81057263a7db45d184e0847f576d1cb2047f2704998d0f20848c8", + "7a0a63f344b0ba0f88cc8e3a7118d8c8d3ec044ee1ea0b71ad3fbc75421ee44c", + "7bb038f4503c333d200f5f532fded590ce74acdb2cabc99cfaf7643b6930767f" + ], + "selected_at": "github-actions-run:30562970311:1", + "selection_rule": "constant_only_three_fresh_generations_one_sample_each+slow_host_floors_v2", + "selection_source_commit": "681ca99098b86006e7dc1f51c27158c757ac05c9", + "selection_source_tree": "1a5494c9b82bcddc030438452285154e9d5b3e2b" + }, "qualification_thresholds": { "backend_observed_accelerator_residency": 1, "bulk_documents_per_second": 2, @@ -60,5 +75,5 @@ }, "schema_version": 1, "selection_protocol": "codestory-per-user-embedding-server-v1", - "status": "unfrozen" + "status": "frozen" } From d94e8e6c55a970aab72bf31aee76e6be3df72a73 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Thu, 30 Jul 2026 12:47:48 -0500 Subject: [PATCH 26/28] seal acceptance job bodies --- .github/scripts/check-workflow-policy.mjs | 94 +++++++++------ .../scripts/check-workflow-policy.test.mjs | 107 +++++++++++++++++- .../release-freeze-acceptance-jobs.json | 10 ++ .../release-evidence/fixtures/candidate.json | 6 +- .../release-evidence/fixtures/report.json | 8 +- release-claims.json | 2 + scripts/codestory-release-claims.mjs | 5 + .../tests/codestory-release-claims.test.mjs | 19 ++++ .../fixtures/release-claims/positive.json | 2 +- 9 files changed, 210 insertions(+), 43 deletions(-) create mode 100644 .github/scripts/release-freeze-acceptance-jobs.json diff --git a/.github/scripts/check-workflow-policy.mjs b/.github/scripts/check-workflow-policy.mjs index ed3f57430..90fac4b6b 100644 --- a/.github/scripts/check-workflow-policy.mjs +++ b/.github/scripts/check-workflow-policy.mjs @@ -336,6 +336,19 @@ function at(value, ...keys) { return current; } +function canonicalJson(value) { + if (Array.isArray(value)) { + return `[${value.map(canonicalJson).join(",")}]`; + } + if (value !== null && typeof value === "object") { + return `{${Object.keys(value) + .sort() + .map(key => `${JSON.stringify(key)}:${canonicalJson(value[key])}`) + .join(",")}}`; + } + return JSON.stringify(value); +} + function scalarStrings(value, found = []) { if (typeof value === "string") { found.push(value); @@ -8158,6 +8171,15 @@ export function releaseFreezeBarrierWorkflowViolations( path.join(repositoryRoot, ".github", "scripts", "release-freeze-barrier.mjs"), "utf8", ), + acceptanceManifestSource = fs.readFileSync( + path.join( + repositoryRoot, + ".github", + "scripts", + "release-freeze-acceptance-jobs.json", + ), + "utf8", + ), ) { const violations = []; for (const [file, workflow] of workflows) { @@ -8172,6 +8194,24 @@ export function releaseFreezeBarrierWorkflowViolations( const acceptancePhases = object(acceptance.phases); const calibrationSourcePhase = object(acceptancePhases.calibration_source); const frozenCandidatePhase = object(acceptancePhases.frozen_candidate); + let acceptanceManifest = {}; + try { + acceptanceManifest = object(JSON.parse(acceptanceManifestSource)); + } catch { + violations.push( + "[freeze_barrier] canonical acceptance job manifest must be valid JSON", + ); + } + const acceptanceManifestJobs = object(acceptanceManifest.jobs); + const acceptanceJobNames = [ + "resolve", + "freeze-hostile-mutations", + "freeze-windows-native-probe", + "freeze-acceptance", + ]; + const acceptanceManifestDigest = createHash("sha256") + .update(acceptanceManifestSource) + .digest("hex"); add( violations, freeze.schema === 3 @@ -8204,6 +8244,10 @@ export function releaseFreezeBarrierWorkflowViolations( && acceptance.publisher_job === "freeze-acceptance" && acceptance.publisher_step === "Publish executable release freeze" && acceptance.status_creator === "github-actions[bot]" + && acceptance.job_manifest + === ".github/scripts/release-freeze-acceptance-jobs.json" + && /^[0-9a-f]{64}$/u.test(String(acceptance.job_manifest_sha256 ?? "")) + && acceptance.job_manifest_sha256 === acceptanceManifestDigest && sameMembers(list(calibrationSourcePhase.known_future_source_changes), [ "crates/codestory-llama-sys/per-user-embedding-server-constant-set.json", ]) @@ -8228,6 +8272,16 @@ export function releaseFreezeBarrierWorkflowViolations( && frozenCandidatePhase.next_permitted_mutation === null, "[freeze_barrier] release claim graph must pin the executable exact-head freeze contract", ); + add( + violations, + acceptanceManifest.schema === "codestory.release-freeze-acceptance-jobs/v1" + && acceptanceManifest.workflow === ".github/workflows/source-proof.yml" + && sameMembers(Object.keys(acceptanceManifestJobs), acceptanceJobNames) + && acceptanceJobNames.every(jobName => + /^[0-9a-f]{64}$/u.test(String(acceptanceManifestJobs[jobName] ?? "")) + ), + "[freeze_barrier] canonical acceptance job manifest must pin exactly the executable acceptance jobs", + ); add( violations, barrierSource.includes('gh(["api", `repos/${repository}/pulls/${number}`])') @@ -8494,42 +8548,14 @@ export function releaseFreezeBarrierWorkflowViolations( sameMembers(Object.keys(object(sourceWorkflow.jobs)), sourceJobNames), "[freeze_barrier] source-proof.yml must use the closed source and acceptance job contract", ); - const acceptanceStepContracts = new Map([ - ["resolve", [ - "Resolve trusted exact head", - "Checkout accepted source head", - "Cancel superseded proof runs", - "Record executable release freeze", - "Upload executable release freeze receipt", - "Reuse a completed gate for this exact head", - "Require executable release freeze", - ]], - ["freeze-hostile-mutations", [ - "actions/checkout@v5", - "actions/setup-node@v5", - "Install workflow policy dependencies", - "Execute exact-head hostile mutation matrix", - ]], - ["freeze-windows-native-probe", [ - "actions/checkout@v5", - "Run exact-head Windows native probe", - ]], - ["freeze-acceptance", [ - "actions/checkout@v5", - "Download executable release freeze receipt", - "Publish executable release freeze", - ]], - ]); - for (const [jobName, expectedSteps] of acceptanceStepContracts) { - const job = object(at(sourceWorkflow, "jobs", jobName)); - const stepNames = list(job.steps).map(step => step?.name ?? step?.uses); + for (const jobName of acceptanceJobNames) { + const actualDigest = createHash("sha256") + .update(canonicalJson(object(at(sourceWorkflow, "jobs", jobName)))) + .digest("hex"); add( violations, - sameMembers(stepNames, expectedSteps) - && !scalarStrings(job).some(value => - /\bcargo\s+(?:test|nextest|build|clippy)\b[^\n]*--workspace\b/iu.test(value) - ), - `[freeze_barrier] source-proof.yml ${jobName} must use the closed cheap acceptance step contract`, + actualDigest === acceptanceManifestJobs[jobName], + `[freeze_barrier] source-proof.yml ${jobName} must match the canonical acceptance job manifest`, ); } const sourceResolve = requireJob( diff --git a/.github/scripts/check-workflow-policy.test.mjs b/.github/scripts/check-workflow-policy.test.mjs index 99e78ebc6..fe9b216b9 100644 --- a/.github/scripts/check-workflow-policy.test.mjs +++ b/.github/scripts/check-workflow-policy.test.mjs @@ -3078,7 +3078,56 @@ test("release freeze barrier rejects every broad-proof bypass", async (t) => { name: "Unexpected broad source proof", run: "cargo test --workspace --locked", }); - }, /closed cheap acceptance step contract/u], + }, /canonical acceptance job manifest/u], + ["acceptance hides an Ubuntu workspace test behind a variable", workflows => { + const step = draftStep( + workflows.get("source-proof.yml").jobs["freeze-hostile-mutations"], + "Execute exact-head hostile mutation matrix", + ); + step.run += '\nbroad_scope=--workspace\ncargo test "$broad_scope" --locked\n'; + }, /canonical acceptance job manifest/u], + ["acceptance hides a Windows workspace test behind a variable", workflows => { + const step = draftStep( + workflows.get("source-proof.yml").jobs["freeze-windows-native-probe"], + "Run exact-head Windows native probe", + ); + step.run += '\n$scope = "--workspace"\ncargo test --release $scope --locked\n'; + }, /canonical acceptance job manifest/u], + ["acceptance hides a workspace test behind an alias", workflows => { + const step = draftStep( + workflows.get("source-proof.yml").jobs["freeze-hostile-mutations"], + "Execute exact-head hostile mutation matrix", + ); + step.run += "\nalias broad='cargo test --workspace --locked'\nbroad\n"; + }, /canonical acceptance job manifest/u], + ["acceptance hides a workspace test behind a shell function", workflows => { + const step = draftStep( + workflows.get("source-proof.yml").jobs["freeze-hostile-mutations"], + "Execute exact-head hostile mutation matrix", + ); + step.run += "\nrun_broad() { cargo test --workspace --locked; }\nrun_broad\n"; + }, /canonical acceptance job manifest/u], + ["acceptance delegates to an unreviewed script", workflows => { + const step = draftStep( + workflows.get("source-proof.yml").jobs["freeze-hostile-mutations"], + "Execute exact-head hostile mutation matrix", + ); + step.run += "\nbash scripts/run-broad-source.sh\n"; + }, /canonical acceptance job manifest/u], + ["acceptance chains a workspace test after an approved command", workflows => { + const step = draftStep( + workflows.get("source-proof.yml").jobs["freeze-hostile-mutations"], + "Execute exact-head hostile mutation matrix", + ); + step.run += "\ntrue && cargo test --workspace --locked\n"; + }, /canonical acceptance job manifest/u], + ["acceptance substitutes an alternate shell", workflows => { + const step = draftStep( + workflows.get("source-proof.yml").jobs["freeze-hostile-mutations"], + "Execute exact-head hostile mutation matrix", + ); + step.shell = "python"; + }, /canonical acceptance job manifest/u], ["source acceptance cannot publish status", workflows => { delete workflows.get("source-proof.yml").permissions.statuses; }, /acceptance must publish an exact-head commit status/u], @@ -3411,6 +3460,62 @@ test("release freeze policy pins live PR base and support ancestry revalidation" }); }); +test("release freeze policy authenticates the complete acceptance job manifest", async (t) => { + const barrierSource = readFileSync( + path.join(root, ".github", "scripts", "release-freeze-barrier.mjs"), + "utf8", + ); + const manifestPath = path.join( + root, + ".github", + "scripts", + "release-freeze-acceptance-jobs.json", + ); + const manifest = JSON.parse(readFileSync(manifestPath, "utf8")); + const cases = [ + ["manifest substitutes an approved job body", value => { + value.jobs["freeze-hostile-mutations"] = "0".repeat(64); + }, /freeze-hostile-mutations must match the canonical acceptance job manifest/u], + ["manifest admits an extra executable job", value => { + value.jobs["acceptance-extra"] = "0".repeat(64); + }, /must pin exactly the executable acceptance jobs/u], + ]; + + for (const [name, mutate, expected] of cases) { + await t.test(name, () => { + const changedManifest = structuredClone(manifest); + mutate(changedManifest); + const changedSource = `${JSON.stringify(changedManifest, null, 2)}\n`; + const graph = structuredClone(loadReleaseClaimGraph(root)); + graph.workflow_policy.release_freeze_barrier.acceptance.job_manifest_sha256 + = createHash("sha256").update(changedSource).digest("hex"); + const violations = releaseFreezeBarrierWorkflowViolations( + loadWorkflows(), + graph, + barrierSource, + changedSource, + ); + assert.match(violations.join("\n"), expected); + }); + } + + await t.test("claim graph substitutes the manifest digest", () => { + const graph = structuredClone(loadReleaseClaimGraph(root)); + graph.workflow_policy.release_freeze_barrier.acceptance.job_manifest_sha256 + = "0".repeat(64); + const violations = releaseFreezeBarrierWorkflowViolations( + loadWorkflows(), + graph, + barrierSource, + readFileSync(manifestPath, "utf8"), + ); + assert.match( + violations.join("\n"), + /release claim graph must pin the executable exact-head freeze contract/u, + ); + }); +}); + test("calibration precedes the sole frozen-candidate source proof", async (t) => { assert.deepEqual(validateWorkflows(loadWorkflows()), []); const coordinatorFile = "packaged-platform-pr.yml"; diff --git a/.github/scripts/release-freeze-acceptance-jobs.json b/.github/scripts/release-freeze-acceptance-jobs.json new file mode 100644 index 000000000..e7d9e5c1a --- /dev/null +++ b/.github/scripts/release-freeze-acceptance-jobs.json @@ -0,0 +1,10 @@ +{ + "schema": "codestory.release-freeze-acceptance-jobs/v1", + "workflow": ".github/workflows/source-proof.yml", + "jobs": { + "resolve": "da6c955c944644cd714728bf67d43aabd4ad049d5fde943ce7b1739f3d7cd8e5", + "freeze-hostile-mutations": "ebc27d28a1c087f848be090d2a2a458acee0177f06048c4d357e0724cf38be1a", + "freeze-windows-native-probe": "252f90a48322275128f47c58f48895a0be25909e323ae7e049ddaff015bf2299", + "freeze-acceptance": "544688894a77c9f95ef5799e3070ec3bea4e199b5ed650f9aa62d35a54e83ca4" + } +} diff --git a/benchmarks/release-evidence/fixtures/candidate.json b/benchmarks/release-evidence/fixtures/candidate.json index fc2234058..0a56625f5 100644 --- a/benchmarks/release-evidence/fixtures/candidate.json +++ b/benchmarks/release-evidence/fixtures/candidate.json @@ -62,7 +62,7 @@ }, "release_claims": { "graph_schema": "codestory.release-claims/v1", - "graph_sha256": "35728c879c314b59ed7606e491fee5fdbda40e8a853dc1cf2c68defabcf30df1", + "graph_sha256": "fd94b7fd1c7e946b9cfa86e805e526bf4f5a978ac4da9ea5b16e5318a2605489", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "requested_claims": [ @@ -85,7 +85,7 @@ "type": "performance", "tier": "live_behavior", "status": "measured", - "graph_sha256": "35728c879c314b59ed7606e491fee5fdbda40e8a853dc1cf2c68defabcf30df1", + "graph_sha256": "fd94b7fd1c7e946b9cfa86e805e526bf4f5a978ac4da9ea5b16e5318a2605489", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { @@ -106,7 +106,7 @@ "type": "answer_quality", "tier": "answer_quality", "status": "pass", - "graph_sha256": "35728c879c314b59ed7606e491fee5fdbda40e8a853dc1cf2c68defabcf30df1", + "graph_sha256": "fd94b7fd1c7e946b9cfa86e805e526bf4f5a978ac4da9ea5b16e5318a2605489", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { diff --git a/benchmarks/release-evidence/fixtures/report.json b/benchmarks/release-evidence/fixtures/report.json index 0b038af94..bb01dd8ac 100644 --- a/benchmarks/release-evidence/fixtures/report.json +++ b/benchmarks/release-evidence/fixtures/report.json @@ -7,7 +7,7 @@ "baseline_id": "ci-contract-v1@1111111111111111111111111111111111111111", "baseline_sha256": "0bbbe6dd8b4000151edf7b1270959d08e94e08db876e7f2372b25613e0f237c1", "candidate_path": "benchmarks/release-evidence/fixtures/candidate.json", - "candidate_sha256": "06aacf127fc14eeb49c689160c8c9076b3e9cdde21eb73d934da4d08e4bd0042", + "candidate_sha256": "81625f51cac5313bf23184f3853940a83149e1bbbb99e4a5823d046dc0d362e8", "artifact_paths": [ { "path": "candidate-stats.json", @@ -26,7 +26,7 @@ "type": "performance", "tier": "live_behavior", "status": "pass", - "graph_sha256": "35728c879c314b59ed7606e491fee5fdbda40e8a853dc1cf2c68defabcf30df1", + "graph_sha256": "fd94b7fd1c7e946b9cfa86e805e526bf4f5a978ac4da9ea5b16e5318a2605489", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { @@ -47,7 +47,7 @@ "type": "answer_quality", "tier": "answer_quality", "status": "pass", - "graph_sha256": "35728c879c314b59ed7606e491fee5fdbda40e8a853dc1cf2c68defabcf30df1", + "graph_sha256": "fd94b7fd1c7e946b9cfa86e805e526bf4f5a978ac4da9ea5b16e5318a2605489", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { @@ -69,7 +69,7 @@ "schema": "codestory.release-claim-evaluation/v1", "status": "pass", "graph_schema": "codestory.release-claims/v1", - "graph_sha256": "35728c879c314b59ed7606e491fee5fdbda40e8a853dc1cf2c68defabcf30df1", + "graph_sha256": "fd94b7fd1c7e946b9cfa86e805e526bf4f5a978ac4da9ea5b16e5318a2605489", "evidence_selection": "all_matching_rows_must_pass", "expected_commit": "2222222222222222222222222222222222222222", "evaluated_at": "2026-07-21T02:13:20.738Z", diff --git a/release-claims.json b/release-claims.json index 06ad7b0c5..cec1420c0 100644 --- a/release-claims.json +++ b/release-claims.json @@ -1522,6 +1522,8 @@ "publisher_job": "freeze-acceptance", "publisher_step": "Publish executable release freeze", "status_creator": "github-actions[bot]", + "job_manifest": ".github/scripts/release-freeze-acceptance-jobs.json", + "job_manifest_sha256": "e523d997b26828b8333014afe96835deb31dfa4f3c3bf476fffc43e2907a1cc6", "phases": { "calibration_source": { "known_future_source_changes": [ diff --git a/scripts/codestory-release-claims.mjs b/scripts/codestory-release-claims.mjs index aa8979fe7..7a096d8f1 100644 --- a/scripts/codestory-release-claims.mjs +++ b/scripts/codestory-release-claims.mjs @@ -1577,6 +1577,8 @@ export function validateReleaseClaimGraph(graph) { "publisher_job", "publisher_step", "status_creator", + "job_manifest", + "job_manifest_sha256", ]) { nonEmptyText( acceptance[field], @@ -1614,6 +1616,9 @@ export function validateReleaseClaimGraph(graph) { || acceptance.event !== "workflow_dispatch" || acceptance.windows_probe_max_seconds !== 90 || acceptance.status_creator !== "github-actions[bot]" + || acceptance.job_manifest + !== ".github/scripts/release-freeze-acceptance-jobs.json" + || !SHA256.test(acceptance.job_manifest_sha256) ) { fail( "workflow_policy.release_freeze_barrier.acceptance must bind the exact " diff --git a/scripts/tests/codestory-release-claims.test.mjs b/scripts/tests/codestory-release-claims.test.mjs index 43c8506b9..24f537434 100644 --- a/scripts/tests/codestory-release-claims.test.mjs +++ b/scripts/tests/codestory-release-claims.test.mjs @@ -178,6 +178,9 @@ test("versioned claim graph has one deterministic digest and all declared contro publisher_job: "freeze-acceptance", publisher_step: "Publish executable release freeze", status_creator: "github-actions[bot]", + job_manifest: ".github/scripts/release-freeze-acceptance-jobs.json", + job_manifest_sha256: + "e523d997b26828b8333014afe96835deb31dfa4f3c3bf476fffc43e2907a1cc6", phases: { calibration_source: { known_future_source_changes: [ @@ -717,6 +720,22 @@ test("graph rejects ambiguous dependencies and unstructured proof lanes", () => /release_freeze_barrier\.acceptance/u, ); + const unpinnedAcceptanceManifest = structuredClone(graph); + unpinnedAcceptanceManifest.workflow_policy.release_freeze_barrier + .acceptance.job_manifest_sha256 = "not-a-digest"; + assert.throws( + () => validateReleaseClaimGraph(unpinnedAcceptanceManifest), + /release_freeze_barrier\.acceptance/u, + ); + + const substitutedAcceptanceManifest = structuredClone(graph); + substitutedAcceptanceManifest.workflow_policy.release_freeze_barrier + .acceptance.job_manifest = ".github/workflows/source-proof.yml"; + assert.throws( + () => validateReleaseClaimGraph(substitutedAcceptanceManifest), + /release_freeze_barrier\.acceptance/u, + ); + const preCalibrationSourceProof = structuredClone(graph); preCalibrationSourceProof.workflow_policy.release_freeze_barrier .acceptance.phases.calibration_source.planned_actions = [ diff --git a/scripts/tests/fixtures/release-claims/positive.json b/scripts/tests/fixtures/release-claims/positive.json index b0625b150..9f5cc08c7 100644 --- a/scripts/tests/fixtures/release-claims/positive.json +++ b/scripts/tests/fixtures/release-claims/positive.json @@ -17,7 +17,7 @@ "type": "source_behavior", "tier": "source", "status": "pass", - "graph_sha256": "35728c879c314b59ed7606e491fee5fdbda40e8a853dc1cf2c68defabcf30df1", + "graph_sha256": "fd94b7fd1c7e946b9cfa86e805e526bf4f5a978ac4da9ea5b16e5318a2605489", "observed_at": "2026-07-16T11:00:00.000Z", "expires_at": "2026-07-17T11:00:00.000Z", "identity": { From 1a7c8a4af7816da13f886799fe7589ae52d6bbd1 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Thu, 30 Jul 2026 13:06:51 -0500 Subject: [PATCH 27/28] authenticate workflow execution context --- .github/scripts/check-workflow-policy.mjs | 29 +++++++++- .../scripts/check-workflow-policy.test.mjs | 57 +++++++++++++++++++ .../release-freeze-acceptance-jobs.json | 3 +- .../release-evidence/fixtures/candidate.json | 6 +- .../release-evidence/fixtures/report.json | 6 +- release-claims.json | 2 +- .../tests/codestory-release-claims.test.mjs | 2 +- .../fixtures/release-claims/positive.json | 2 +- 8 files changed, 96 insertions(+), 11 deletions(-) diff --git a/.github/scripts/check-workflow-policy.mjs b/.github/scripts/check-workflow-policy.mjs index 90fac4b6b..ceded0134 100644 --- a/.github/scripts/check-workflow-policy.mjs +++ b/.github/scripts/check-workflow-policy.mjs @@ -349,6 +349,16 @@ function canonicalJson(value) { return JSON.stringify(value); } +// A parsed job is not its complete execution contract. Workflow-level environment and run +// defaults execute inside every job, while triggers, permissions, concurrency, and future +// top-level fields can change when or with what authority it runs. Hash the entire parsed +// workflow except `jobs`; the acceptance manifest hashes those bodies separately. +function workflowExecutionContext(workflowValue) { + return Object.fromEntries( + Object.entries(object(workflowValue)).filter(([key]) => key !== "jobs"), + ); +} + function scalarStrings(value, found = []) { if (typeof value === "string") { found.push(value); @@ -8274,8 +8284,17 @@ export function releaseFreezeBarrierWorkflowViolations( ); add( violations, - acceptanceManifest.schema === "codestory.release-freeze-acceptance-jobs/v1" + hasExactKeys(acceptanceManifest, [ + "schema", + "workflow", + "workflow_context_sha256", + "jobs", + ]) + && acceptanceManifest.schema === "codestory.release-freeze-acceptance-jobs/v2" && acceptanceManifest.workflow === ".github/workflows/source-proof.yml" + && /^[0-9a-f]{64}$/u.test( + String(acceptanceManifest.workflow_context_sha256 ?? ""), + ) && sameMembers(Object.keys(acceptanceManifestJobs), acceptanceJobNames) && acceptanceJobNames.every(jobName => /^[0-9a-f]{64}$/u.test(String(acceptanceManifestJobs[jobName] ?? "")) @@ -8548,6 +8567,14 @@ export function releaseFreezeBarrierWorkflowViolations( sameMembers(Object.keys(object(sourceWorkflow.jobs)), sourceJobNames), "[freeze_barrier] source-proof.yml must use the closed source and acceptance job contract", ); + const actualWorkflowContextDigest = createHash("sha256") + .update(canonicalJson(workflowExecutionContext(sourceWorkflow))) + .digest("hex"); + add( + violations, + actualWorkflowContextDigest === acceptanceManifest.workflow_context_sha256, + "[freeze_barrier] source-proof.yml workflow execution context must match the canonical acceptance manifest", + ); for (const jobName of acceptanceJobNames) { const actualDigest = createHash("sha256") .update(canonicalJson(object(at(sourceWorkflow, "jobs", jobName)))) diff --git a/.github/scripts/check-workflow-policy.test.mjs b/.github/scripts/check-workflow-policy.test.mjs index fe9b216b9..aee6de8ff 100644 --- a/.github/scripts/check-workflow-policy.test.mjs +++ b/.github/scripts/check-workflow-policy.test.mjs @@ -3473,6 +3473,9 @@ test("release freeze policy authenticates the complete acceptance job manifest", ); const manifest = JSON.parse(readFileSync(manifestPath, "utf8")); const cases = [ + ["manifest substitutes the workflow execution context", value => { + value.workflow_context_sha256 = "0".repeat(64); + }, /workflow execution context must match the canonical acceptance manifest/u], ["manifest substitutes an approved job body", value => { value.jobs["freeze-hostile-mutations"] = "0".repeat(64); }, /freeze-hostile-mutations must match the canonical acceptance job manifest/u], @@ -3514,6 +3517,60 @@ test("release freeze policy authenticates the complete acceptance job manifest", /release claim graph must pin the executable exact-head freeze contract/u, ); }); + + const workflowContextCases = [ + ["repository BASH_ENV preload", workflow => { + workflow.env = { + ...workflow.env, + BASH_ENV: "${{ github.workspace }}/scripts/run-broad-source.sh", + }; + }], + ["repository NODE_OPTIONS preload", workflow => { + workflow.env = { + ...workflow.env, + NODE_OPTIONS: "--require ${{ github.workspace }}/scripts/run-broad-source.js", + }; + }], + ["repository shell wrapper", workflow => { + workflow.defaults = { + run: { + shell: "bash scripts/run-broad-source.sh {0}", + }, + }; + }], + ["workflow trigger context", workflow => { + workflow.on.workflow_dispatch.inputs.acceptance_only.default = true; + }], + ["workflow token permissions", workflow => { + workflow.permissions.contents = "write"; + }], + ["workflow cancellation context", workflow => { + workflow.concurrency.group = "unscoped-acceptance"; + }], + ["workflow display identity", workflow => { + workflow.name = "Unreviewed acceptance wrapper"; + }], + ["new workflow-level field", workflow => { + workflow["run-name"] = "unreviewed-${{ github.run_id }}"; + }], + ]; + + for (const [name, mutate] of workflowContextCases) { + await t.test(`workflow context rejects ${name}`, () => { + const workflows = loadWorkflows(); + mutate(workflows.get("source-proof.yml")); + const violations = releaseFreezeBarrierWorkflowViolations( + workflows, + loadReleaseClaimGraph(root), + barrierSource, + readFileSync(manifestPath, "utf8"), + ); + assert.match( + violations.join("\n"), + /source-proof\.yml workflow execution context must match the canonical acceptance manifest/u, + ); + }); + } }); test("calibration precedes the sole frozen-candidate source proof", async (t) => { diff --git a/.github/scripts/release-freeze-acceptance-jobs.json b/.github/scripts/release-freeze-acceptance-jobs.json index e7d9e5c1a..e99d402a7 100644 --- a/.github/scripts/release-freeze-acceptance-jobs.json +++ b/.github/scripts/release-freeze-acceptance-jobs.json @@ -1,6 +1,7 @@ { - "schema": "codestory.release-freeze-acceptance-jobs/v1", + "schema": "codestory.release-freeze-acceptance-jobs/v2", "workflow": ".github/workflows/source-proof.yml", + "workflow_context_sha256": "c4fc041ddabf8ac44f4966e13e0c351b4f0d70bf3a94878490aff7030f912f66", "jobs": { "resolve": "da6c955c944644cd714728bf67d43aabd4ad049d5fde943ce7b1739f3d7cd8e5", "freeze-hostile-mutations": "ebc27d28a1c087f848be090d2a2a458acee0177f06048c4d357e0724cf38be1a", diff --git a/benchmarks/release-evidence/fixtures/candidate.json b/benchmarks/release-evidence/fixtures/candidate.json index 0a56625f5..28f6da4e3 100644 --- a/benchmarks/release-evidence/fixtures/candidate.json +++ b/benchmarks/release-evidence/fixtures/candidate.json @@ -62,7 +62,7 @@ }, "release_claims": { "graph_schema": "codestory.release-claims/v1", - "graph_sha256": "fd94b7fd1c7e946b9cfa86e805e526bf4f5a978ac4da9ea5b16e5318a2605489", + "graph_sha256": "9df8f932d66a60baa104bf61252ab318b0c1325740a0edf193463618e0946348", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "requested_claims": [ @@ -85,7 +85,7 @@ "type": "performance", "tier": "live_behavior", "status": "measured", - "graph_sha256": "fd94b7fd1c7e946b9cfa86e805e526bf4f5a978ac4da9ea5b16e5318a2605489", + "graph_sha256": "9df8f932d66a60baa104bf61252ab318b0c1325740a0edf193463618e0946348", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { @@ -106,7 +106,7 @@ "type": "answer_quality", "tier": "answer_quality", "status": "pass", - "graph_sha256": "fd94b7fd1c7e946b9cfa86e805e526bf4f5a978ac4da9ea5b16e5318a2605489", + "graph_sha256": "9df8f932d66a60baa104bf61252ab318b0c1325740a0edf193463618e0946348", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { diff --git a/benchmarks/release-evidence/fixtures/report.json b/benchmarks/release-evidence/fixtures/report.json index bb01dd8ac..2463699c3 100644 --- a/benchmarks/release-evidence/fixtures/report.json +++ b/benchmarks/release-evidence/fixtures/report.json @@ -26,7 +26,7 @@ "type": "performance", "tier": "live_behavior", "status": "pass", - "graph_sha256": "fd94b7fd1c7e946b9cfa86e805e526bf4f5a978ac4da9ea5b16e5318a2605489", + "graph_sha256": "9df8f932d66a60baa104bf61252ab318b0c1325740a0edf193463618e0946348", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { @@ -47,7 +47,7 @@ "type": "answer_quality", "tier": "answer_quality", "status": "pass", - "graph_sha256": "fd94b7fd1c7e946b9cfa86e805e526bf4f5a978ac4da9ea5b16e5318a2605489", + "graph_sha256": "9df8f932d66a60baa104bf61252ab318b0c1325740a0edf193463618e0946348", "observed_at": "2026-07-21T02:13:20.738Z", "expires_at": "2026-07-22T02:13:20.738Z", "identity": { @@ -69,7 +69,7 @@ "schema": "codestory.release-claim-evaluation/v1", "status": "pass", "graph_schema": "codestory.release-claims/v1", - "graph_sha256": "fd94b7fd1c7e946b9cfa86e805e526bf4f5a978ac4da9ea5b16e5318a2605489", + "graph_sha256": "9df8f932d66a60baa104bf61252ab318b0c1325740a0edf193463618e0946348", "evidence_selection": "all_matching_rows_must_pass", "expected_commit": "2222222222222222222222222222222222222222", "evaluated_at": "2026-07-21T02:13:20.738Z", diff --git a/release-claims.json b/release-claims.json index cec1420c0..068d2e6f0 100644 --- a/release-claims.json +++ b/release-claims.json @@ -1523,7 +1523,7 @@ "publisher_step": "Publish executable release freeze", "status_creator": "github-actions[bot]", "job_manifest": ".github/scripts/release-freeze-acceptance-jobs.json", - "job_manifest_sha256": "e523d997b26828b8333014afe96835deb31dfa4f3c3bf476fffc43e2907a1cc6", + "job_manifest_sha256": "2df6fb76f1ac19acb98e530381ef456f38d517ded6356e61892b75b5fe6f3c79", "phases": { "calibration_source": { "known_future_source_changes": [ diff --git a/scripts/tests/codestory-release-claims.test.mjs b/scripts/tests/codestory-release-claims.test.mjs index 24f537434..147772278 100644 --- a/scripts/tests/codestory-release-claims.test.mjs +++ b/scripts/tests/codestory-release-claims.test.mjs @@ -180,7 +180,7 @@ test("versioned claim graph has one deterministic digest and all declared contro status_creator: "github-actions[bot]", job_manifest: ".github/scripts/release-freeze-acceptance-jobs.json", job_manifest_sha256: - "e523d997b26828b8333014afe96835deb31dfa4f3c3bf476fffc43e2907a1cc6", + "2df6fb76f1ac19acb98e530381ef456f38d517ded6356e61892b75b5fe6f3c79", phases: { calibration_source: { known_future_source_changes: [ diff --git a/scripts/tests/fixtures/release-claims/positive.json b/scripts/tests/fixtures/release-claims/positive.json index 9f5cc08c7..57f4a96fe 100644 --- a/scripts/tests/fixtures/release-claims/positive.json +++ b/scripts/tests/fixtures/release-claims/positive.json @@ -17,7 +17,7 @@ "type": "source_behavior", "tier": "source", "status": "pass", - "graph_sha256": "fd94b7fd1c7e946b9cfa86e805e526bf4f5a978ac4da9ea5b16e5318a2605489", + "graph_sha256": "9df8f932d66a60baa104bf61252ab318b0c1325740a0edf193463618e0946348", "observed_at": "2026-07-16T11:00:00.000Z", "expires_at": "2026-07-17T11:00:00.000Z", "identity": { From 47e41374eeb286d38da38794068ec505031d2f3b Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Thu, 30 Jul 2026 13:18:15 -0500 Subject: [PATCH 28/28] refresh release evidence fixture binding --- benchmarks/release-evidence/fixtures/report.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/release-evidence/fixtures/report.json b/benchmarks/release-evidence/fixtures/report.json index 2463699c3..ccd2f3014 100644 --- a/benchmarks/release-evidence/fixtures/report.json +++ b/benchmarks/release-evidence/fixtures/report.json @@ -7,7 +7,7 @@ "baseline_id": "ci-contract-v1@1111111111111111111111111111111111111111", "baseline_sha256": "0bbbe6dd8b4000151edf7b1270959d08e94e08db876e7f2372b25613e0f237c1", "candidate_path": "benchmarks/release-evidence/fixtures/candidate.json", - "candidate_sha256": "81625f51cac5313bf23184f3853940a83149e1bbbb99e4a5823d046dc0d362e8", + "candidate_sha256": "5302ac5856891447183a0c0e7c7df09cfc5ad425efb1b834b2b439364870f438", "artifact_paths": [ { "path": "candidate-stats.json",