Trust-reconcile and delivery reconciliation#
Retired as a CI check (2026-10-11)#
Decision. The CI trust anchor is retired. The Trust Reconcile workflow, the trust-verify
composite action, CI attestation minting (scripts/ci/mint-attestation.js), the flow-agents verify subcommand, the delivery/DECLARED exemption marker, and the unstarted-delivery Stop
advisory are removed. Agents are no longer asked to keep a delivery bundle or an exemption entry
current so that a CI reconcile step passes. A repository's own required checks, re-run on the
exact pull request head, are the external boundary.
Rationale. In the owner's measurement across the 16 repositories that ran the anchor, it consumed roughly 90 runner-hours per ~100 runs per repository with 30–83% failure rates, and those failures were overwhelmingly exemption-scope bookkeeping or verify failures the required lanes already caught. In about 200 sampled failures exactly one was a claimed-versus-observed divergence, and it was a flake. No consuming repository's default-branch protection listed it as a required check (checked against each repository's branch protection on 2026-10-11). Its only unique step reconciled claims that the required lanes re-run anyway.
What stays, and why. The Hachure trust.bundle format, the session writer, the Stop gate's
local evidence checks, checkpoint sealing, and flow-agents workflow publish-delivery remain:
flow-agents merge-change still requires the delivering session's terminal companions committed
at the exact head. scripts/ci/trust-reconcile.js remains as the home of the manifest helpers the
runtime loads for evidence admission and the local reconcile-preflight. Retiring the
delivery-commit step from the Builder lifecycle (and with it the remaining reconcile helpers and
the provisional-delivery verbs) is a separate lifecycle decision, not made here.
Everything below records the decision as it stood before the retirement.
Before 2026-10-11#
Decision. Delivery is fail-closed with governed exemptions. publishDelivery() passes a
bundle through three distinct, type-discriminated tiers — fail-soft (absent bundle tolerated) →
shape gate (InvalidBundleShapeError, #356) → hold gate (NotFreshHolderError, #293) — and CI's
reconcile is fail-closed by default (onUnderivable: 'fail'). A local reconcile-preflight
mirrors CI's shape check via the shared scripts/lib/reconcile-shape.js so the two cannot
drift. Concurrent deliveries are isolated by per-session paths (delivery/<slug>/, #379), and
a checkpoint's commit_sha must be an ancestor of HEAD (seal-at-parent) so a stale bundle can't be
replayed onto a moved branch. See the coordination guide.
Rationale. The default must be safe: an underivable status fails the run rather than passing it, and a hard block on the shared publish path is applied only on high-confidence signals so it never false-blocks legitimate work. The frozen ADRs (0020, 0022) hold the immutable rationale.
Delivery provenance is an attestation, not a claim (2026-07-29, #1094)#
Decision. A delivery's provenance is established by a signed attestation whose subject digest
matches this commit's trust bundle and whose predicate records reconciled — not by anyone
saying the gate passed. The gate's own signature is the evidence.
Scope, deliberately. This decides the core primitive: the attestation, the digest binding,
and the tier below. It does not decide what any particular outcome means. "Merge-ready" is a
Builder Kit notion — the composition of gates that kit considers sufficient to merge — not a
generic Flow concept, and it is defined by the kit, in terms of these primitives. A different kit
composes the same evidence into a different outcome. Core supplies provable provenance; kits
interpret it. (The core-vs-kit boundary itself remains a needs-decision stub with provenance in
frozen ADR 0014; this record does not resolve it, and
deliberately stays on the core side of it.)
The machinery for this already existed and was going to waste. scripts/ci/mint-attestation.js
signs an in-toto v1 statement over delivery/trust.bundle's sha256 via Sigstore keyless (Fulcio +
Rekor), with predicate {commit_sha, canonical_commands, reconciled, built_at} — then uploads it
as a build artifact that nothing consumes. The unfakeable object expired while the fakeable one
(a self-asserted claim) was what travelled onward. Making the attestation the gate's output
rather than its byproduct closes that, and costs no new cryptography.
Provenance tiers. Every delivery sits at exactly one, and the tier is a fact about evidence, never a description of intent:
| tier | means | available |
|---|---|---|
self-asserted |
a claim with no signature and no binding to content | always, and worth little |
self-attested |
signed locally over the bundle digest, carrying commit_sha + canonical_commands |
now, at zero cost — a local signing identity already exists |
independently-verified |
a second party re-ran canonical verification and signed the result | wherever CI runs |
derive-claim-status.mjs already refuses to trust a bundle's self-reported claim.status and
re-derives it from the bundle's own evidence. The tiers extend that posture from a claim's status
to a delivery's provenance: in both cases the assertion is not the evidence.
Exemptions become the explicitly-unattested path. delivery/DECLARED stays — a fail-closed
gate with no relief valve gets bypassed in worse ways — but an exemption is now understood as
recording self-asserted, not as an equivalent to passing. It should be countable and visible
after merge rather than invisible once merged, which is the property it lacks today.
Rationale. A self-asserted approval is the same defect as a fabricated receipt, one layer up: unverifiable by anyone, indistinguishable from an earned one. The tier does not make dishonesty impossible — anyone holding a token can still assert — but it makes an assertion unable to wear an authority trace it did not earn.
Repo-dependent, deliberately. Requiring a given tier is a kit/repo policy choice, not a core
rule — requiring independently-verified everywhere would block private repos whose CI is off for
cost reasons. The tier is the design that survives both states: a repo
with free CI (public) can require the top tier today; one without sits at self-attested and its
records say so. Nothing about the model changes when CI resumes — the top tier simply starts being
reachable. Console renders the same tier the gate enforces, so the read surface is a view of the
gate's evidence rather than a second, softer judgement
(see console-record-delivery).
Implementation note (#356, iteration 1)#
A local, pre-push reconcile-preflight now exists (workflow-sidecar reconcile-preflight <artifact-dir>), reusing — never forking — the shape-classification logic CI's
scripts/ci/trust-reconcile.js enforces via the shared scripts/lib/reconcile-shape.js
module. publishDelivery() is itself fail-closed on shape-invalidity: it calls the same
preflight before copying anything into delivery/, and refuses to publish a bundle that
fails the shape check.
The reduced-coverage degradation (trusting a session-local claim's self-reported status when
CI-side re-derivation is unavailable) is a LOCAL-preflight-only opt-in
(sessionLocalShapeIssues(..., { onUnderivable: 'reduce' })). CI's trust-reconcile.js
always calls the shared function with { onUnderivable: 'fail' } (also the function's
default when no mode is given) and remains fail-closed: when status re-derivation is
unavailable, every session-local pass-asserting claim becomes a status-underivable
divergence and the run fails, exactly as before the shape logic was extracted.
Implementation note (#379, per-session delivery paths)#
Concurrent deliveries no longer contend on a shared delivery/trust.bundle. Each session writes
its bundle under delivery/<slug>/; CI selects prefer-newest. Publishing must restore sibling
delivery/<slug>/ directories from origin/main before committing — after a soft-reset,
git add -A would otherwise stage the deletion of other sessions' delivery dirs that the branch
predates. This is the standard step in the publish sequence documented in the coordination guide.
Command observation provenance is item-level (#1081)#
commit_sha on a delivery checkpoint or attestation is aggregate publication provenance: it binds
the checkpoint/bundle transport as a whole at seal time. It is not a statement that any individual
command ran at that commit, and it must not be used as one. Individual command provenance lives in
the runtime-authoritative trust.bundle: a confirming command observation carries
observed_at_commit, worktree_clean, and the exact observation-time
verification_workspace_snapshot.
At gate evaluation, every item offered as passing proof must have captured a well-formed commit,
a true cleanliness value, and a clean Git-worktree snapshot. Trusted Git must resolve that
commit and establish it as an ancestor of trusted current HEAD; then the item's snapshot must
equal the current canonical snapshot. This is intentionally a conjunction: ancestry alone does
not prove the bytes are unchanged, and exact snapshot comparison does not replace history
ancestry. Dirty, missing, unavailable, malformed, unrelated, or unresolved/shallow provenance is
not_verified and cannot be upgraded by a checkpoint, waiver, aggregate commit_sha, or a
version 1 record. Version 1 records are rejected and require re-recording under v2.
The workflow-evidence schema v2 projects the same per-command fields for
consumers. It is not a runtime reconciliation authority. Version 1 is rejected; runtime decisions
continue to derive from trust.bundle.
Implementation note (#1422, status function versions)#
Surface 5 moved the default status function from "2" to "3" (omission fails closed) and keeps
"2" selectable for re-deriving records made under it. The writer stamps the version it derived
with into every bundle's source (flow-agents/workflow-sidecar;statusFunctionVersion=<v>), and
re-derivation of a stored bundle — derive-claim-status.mjs for CI and reconcile-preflight, and
workflow-sidecar claim — uses that stamp, through the shared
scripts/lib/status-function-version.js, so a committed delivery reconciles to the verdict it was
written with.
No stamp derives under the installed Surface's current version. No flow-agents writer has emitted an unstamped bundle, and leaving the stamp out must never select the more lenient rules.
A stamp the installed Surface cannot evaluate, or a malformed one (empty, repeated), is refused: every claim is reported underivable and the reason goes to stderr. CI then refuses each session-local claim that asserts a pass or a failure; command-reconciled claims do not consult the derived status. A status derived with an algorithm the producer did not use is not that bundle's status.
"1"is the one exact exception. Surface < 1.2.0 implemented status function"1", and the first writers stamped it for about a day; test fixtures still carry it."1"and"2"differ in three inputs only: arevokedevent status, aninvalidationevent, and a claim-intrinsicexpiresAt/ttlSecondswindow (compared on the Surface 1.1.0 and 1.2.0 sources). A"1"bundle with none of them is re-derived under"2", which is what every reader did before this change; one with any of them is refused.Accepted residual. The stamp is producer-written, so any bundle can claim
"2". Honouring it is unavoidable while installed writers on Surface 2.x keep producing honest"2"bundles; a floor on the accepted version is a separate, owner-level policy decision.Readers that stay on the installed default, deliberately. The Stop hook's gate re-derivation (
stop-goal-fit.js), checkpoint sealing,render-trust-paneland gate-review inquiries read the live session bundle, which the current writer rebuilds and restamps on every write; they are not reconciling a past delivery. The hook acts only ondisputed/rejected, which"3"reaches in strictly more cases, never fewer.
Critique claims carry the reviewer's verdict as attestation evidence, linked from the verdict
event, and their policy requires it. Under "3" a policy that requires nothing cannot verify a
claim, so this is what lets a passing review derive verified from the bundle's own data. One
status changes already under "2": a critique with no verdict event (not_verified) and
superseded critique history now re-derive unknown rather than proposed, because their policy
now requires evidence they do not have. Neither asserts a pass, so neither is reconciled or gated.