Repository navigation
Make UK build outcomes honest in staging, telemetry and the Logbook - #1147
Draft
juaristi22 wants to merge 5 commits into
Draft
juaristi22 wants to merge 5 commits into
juaristi22 wants to merge 5 commits into
Conversation
…ng contract v3) A build whose gates refuse its candidate used to close its staging run as `completed`, and a build refused at the preflight gates read as a pass on the dashboard. Every failure carried `BUILD_FAILED`, and the Logbook and the telemetry could disagree on how a run ended. - Staging documents move to schema version 3: a `blocked` run status with a `block` record (gate phase, blocking gate ids, count) and a `failure_class` on failures. The delivery summary keeps contract version 2, which publication and the assemblers pin. Version 2 documents stay readable; the v2 fixtures are frozen beside a new v3 set (adds a blocked run). - `microcosm.build.run_outcome` classifies a build's end once: a gate block anywhere in the cause chain is `blocked`; Ctrl-C, out-of-memory, graph-node and other errors get distinct codes and classes; the Logbook disposition is derived from the same classification. - Both UK close-outs close the run `blocked` (terminal battery, national seam battery) or `completed` from that classification; the preflight refusal now records its gates, a `preflight_gates` stage, Logbook verdicts that resolve in the preflight gate document, and a `blocked` run at phase `preflight`. - The spine build closes gate blocks as `blocked` and classifies other errors. - The hosted emitter gains a `blocked` run event and `error_code` on failures. Readers must accept version 3 first: PolicyEngine/calibration-diagnostics#206. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A supervisor's SIGTERM killed a UK build without running its failure path: the staging run stayed `running` and no Logbook row was written. The spine build also let Ctrl-C escape without closing its run. - `microcosm.build.termination.raise_on_sigterm` turns the first SIGTERM into `BuildTerminatedError`, a `KeyboardInterrupt` subclass, so the existing interrupt arms record it and kernels' `except Exception` cannot swallow it. The handler fires once and then restores the default, so a second SIGTERM still kills the process at once. - The spine, dense and national entry points install it and, after the close-out, exit with status 143, so supervisors still see a termination. - The spine build gains an interrupt arm: the run closes `failed` (`INTERRUPTED` or `TERMINATED`) and the attempt records a `discarded` row. Known limits: settlement after the first signal can outlast a supervisor's grace period; SIGKILL and out-of-memory kills are covered only by the hosted emitter's heartbeat and write no Logbook row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A graph-built dense candidate could not be assembled: its `*.local_gates.json` held the graph's unsigned 26-gate document, with no `release_id` and no `shippable`, so the dense contract, the release preflight and the size evaluation all failed to read it. - `replay_uk_dense_gate_battery` projects the six local outcomes from the graph's terminal document (nothing is re-evaluated; the projection checks the declarations match), replays them through the gate battery under `MARKS_ARTIFACT`, grafts the scoped-report trio and signs the report with the Logbook build id as `release_id`. Without the key or an attempt it is written unsigned (`signing_error`) and is never shippable. - The dense build writes it before any refusal, so a blocked candidate leaves it too; the graph's own enforcement still sets the exit status. - The full document keeps its evidence name (`uk.full.gates.calibrated.gate_report.json`), is what the package binds, and is recorded as `outputs.full_gate_report`. Gate receipts now resolve: local gates in the signed report, the rest in the full document. - A build whose `--target-geographies` select no local targets writes no local report (`outputs.local_gate_report: null`, with the reason). - `--release-candidate` refuses to start without a 32-byte signing key or with a filter that drops the local targets; the preflight requires exactly 32 bytes. - The assembler and preflight tests now feed the build's own report; no test signs a report by hand. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The spine build never recorded its output file's digest, so nothing tied
a downstream build to the exact H5 a spine run wrote.
- The digest is measured once the H5 is fully written (smoke marking
included) and recorded in the sidecar (`output {filename, sha256,
size_bytes}`), a `<h5>.sha256` file in `sha256sum` format, the
`spine_h5_creation` stage event and the Logbook `pipeline` verdict
(`artifact_sha256`).
- `load_bound_spine_checkpoint` refuses an input H5 whose measured digest
differs from the sidecar's `output.sha256`; older sidecars without the
key still bind by content identity. Both full-build roles pass the
measured digest.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A resumed build reused stored graph results from earlier attempts, but nothing linked it to them or said how much it reused. - Each attempt's `request.json` now names its Logbook build id and staging run id. - Both roles' rowwise manifests gain an `execution` block: the graph store, the attempt directory, the nodes reused from the store versus computed (the first graph run to reach a node decides; later runs in the same attempt see this attempt's own results as hits), and the earlier attempt directories on the same store with their ids. It sits outside the run parameters, so the candidate identity is unchanged. - The counts reach the staging run as a `graph_execution` stage event before it closes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #1099 (always-on telemetry emitter); retarget to
mainonce it merges. Stacked PRs get no CI here, so CI runs after the retarget.Readers must accept staging contract version 3 first: PolicyEngine/calibration-diagnostics#206 has to be deployed and its collector qualified (a
blockedevent returns 2xx) before this merges. An old collector answersblockedwith 422, and #1099's delivery queue retries it indefinitely.Why
After #1115, a review of how UK builds show up in the calibration dashboard found eight gaps:
error_code: BUILD_FAILED, andfailure_classwas never written.completed; the dashboard inferred "blocked" from counts.*.local_gates.jsonheld the graph's unsigned 26-gate document, with norelease_idorshippable.runningwith no Logbook row. There was no SIGTERM handler, and the spine build ignored Ctrl-C.What changes (one commit per area)
A. Staging contract v3 and one outcome classification (gaps 1, 2, 6, 7).
schema_version3. A run can now endblocked, withblock {phase, blocking_failure_count, blocking_gate_ids}. Failures gainfailure_class.contract_version2, whichpublish_cli, both assemblers and the dashboard pin.microcosm.build.run_outcomeclassifies a build's end once, and the staging bundle, the hosted emitter and the Logbook disposition all take it from there:blocked;INTERRUPTED, SIGTERMTERMINATED,MemoryErrorOUT_OF_MEMORY, a graph-node failureGRAPH_NODE_FAILED, and anything elseBUILD_FAILED.failed;discarded.preflight_gatesstage;blockedrun at phasepreflight.blockedrun event anderror_codeon failures._run_with_telemetry, each attempt's own close-out closes the hosted emitter with this classification first, so the wrapper's generic complete or fail only runs when nothing closed it. An error raised before the attempt takes over (its preflight digest, say) is classified there too. Add always-on build telemetry delivery #1099's test that expected a gate-blocked dense run to readfailednow expectsblocked.B. Termination signals (gap 5).
raise_on_sigterm()turns the first SIGTERM intoBuildTerminatedError. It is aKeyboardInterruptsubclass, so the existing interrupt arms record it and kernels'except Exceptioncan't swallow it.failedwith its own class and writes adiscardedrow.C. Sign the dense local gate report (gap 4).
MARKS_ARTIFACT, grafts the scoped-report trio, and signs the result with the Logbook build id asrelease_id.uk.full.gates.calibrated.gate_report.json. The package binds it, and it is recorded asoutputs.full_gate_report.--release-candidaterefuses to start without a 32-byte signing key, or with--target-geographiesthat select no local targets.outputs.local_gate_reportis null, with the reason recorded. It never fills the missing local gates in as not applicable.D. Spine output checksum (gap 3).
output);<h5>.sha256file;spine_h5_creationstage event;pipelineverdict.output.sha256. Older sidecars still bind by content identity.E. Resume lineage (gap 8).
request.jsonnames its Logbook build id and its staging run id.executionblock, outside the run parameters, so the identity is unchanged. It records:graph_executionstage event.Known limits
unexpected_process_exitevent cover them, and they still write no Logbook row. Area E lets the next attempt on the same store name the killed one.Testing
classify_failure, including the cause chain, wrapped spine blocks and every class;logbook_disposition;TERMINATED,discarded) on the spine, dense and national lines._check_uk_dense_gate_report;--resume requirereplay reports reuse and names the cold attempt. Request ids and the stage event are tested too.tools/generate_staging_contract_fixtures.py --checkpasses, the v2SHA256SUMSis unchanged, andtools/ci_test_plan.py verifypasses.test_engine_is_reported_unavailable_without_the_uk_extrais environmental: the local venv has the UK extra;test_subprocess_exchanges_ambient_token_and_delivers_eventsfailed only under-n 6and passes serially.🤖 Generated with Claude Code