Feature design: CI shadow closure and exact-SHA evidence¶
- Status: APPROVED
- Implementation status: IN PROGRESS
- Owner: dpone maintainer
- Issue: #512
- Target release: TBD
- Last verified: 2026-08-30
The status in this file is not self-authenticating approval. It becomes
repository evidence only when the exact PR head carrying this specification is
reviewed by the owner, the Agent PR receipt succeeds, all required contexts
resolved from the canonical active policy succeed without bypass, and the merge commit is recorded in
issue #512. Until that sequence completes, architecture implementation PRs may
be prepared but must not merge.
Executive summary¶
DPONE-CI-SHADOW-CLOSURE introduces a measured, non-authoritative
PR Gate shadow while preserving the required contexts resolved from the
canonical active policy as the only merge authority. The shadow path classifies a pull request, runs
only the relevant product jobs, publishes one data-only evidence file, and is
audited by trusted code from the default branch without executing pull-request
content.
GitHub Actions may create exactly one check context named PR Gate shadow.
No Actions workflow may create PR Gate. That name is reserved for a future,
separately administered GitHub App whose positive App ID is not GitHub Actions
App ID 15368. Provisioning that App, activating union/final protection,
mutating rulesets or classic protection, tags, release workflows, publication,
and release credentials are outside this goal.
The closure is successful when routing, exact-attempt auditing, reconciliation, immutable two-phase compatibility, default-deny readiness, and read-only policy observation are implemented and measured under the unchanged legacy authority. It does not claim or perform production cutover.
Personas and customer journey¶
| Persona | Goal | Current pain | Success signal |
|---|---|---|---|
| Contributor | Understand why a PR ran or skipped each shadow job | Legacy checks do not explain component routing | One exact-head plan and one PR Gate shadow result list every selected and N/A job |
| Merge owner | Merge only with existing protection | A new diagnostic check could be mistaken for authority | All canonical active required contexts remain required and no bypass or live mutation occurs |
| Shadow auditor operator | Verify one producer attempt safely | Producer artifacts and self-described PASS are untrusted |
Default-branch auditor binds API identity, direct JSON bytes, claims, and its own revision |
| Security reviewer | Prove privileged jobs never execute PR-controlled code | workflow_run can receive write tokens or secrets |
Auditor plus verifier preflight/evaluator are data-only; candidate execution is isolated, unprivileged, cacheless, and fail-closed |
| Policy observer | Compare repository intent with live state | Dry-run and apply surfaces can be confused | Policy tooling exposes load, resolve, GET-only snapshot, diff, and receipt; no mutation transport exists |
Journey¶
- Discover. The contributor sees that shadow is diagnostic and the legacy canonical active required contexts remain the only merge authority.
- Prepare. The workflow derives exact repository, PR,
B/H/M, run, and attempt identity without a write credential or secret. Fork approval is fail-closed telemetry until the live canary establishes the platform lifecycle. - Configure. A closed route policy defines every path class. Every evidence upload explicitly declares 90-day retention. No repository-admin credential, protection mutation, or retention-settings prerequisite is required. Before the PR 4C reconciler child is approved, a separately approved read-only calibration probe records the request, byte, and wall-time headroom proof.
- Execute. The PR-head producer publishes
PR Gate shadowand one attempt-specific claims file. Trusted default-branch code audits only bounded data and never executes subject content. - Observe. The contributor sees producer status, audit receipt, selected
and
N/Ajobs, exact identities, and the current reconciliation report. The report is scoped to its provider-observable exact interval. - Diagnose. Stable codes distinguish authenticated failure from missing, malformed, ambiguous, cancelled, stale, API-unavailable, resource-limited, and retention-lost evidence.
- Recover. A source fix creates a new head; a producer or auditor retry
creates immutable attempt evidence; reconciliation rescans the whole declared
interval. Moved merge identity or ambiguous fork lifecycle requires a fresh
eligible event. Historical loss stops
PASSfor every overlapping interval; a fresh auditable attempt establishes only new exact-head evidence, while daily-root recovery waits until a later complete clean window excludes the lost attempt. Neither path relabels the old report. - Operate. Daily stateless reconciliation surfaces shadow/legacy disagreement without cursors, predecessor state, or protection changes.
- Upgrade. Child PRs land independently. PR 6 public readiness waits for authenticated PR 5B evidence, and PR 7 settings observation stays optional and non-authoritative.
Scope¶
In scope¶
- repair the exact baseline and enforce module-size ratchet v2 before CI architecture changes;
- CI hygiene that does not modify release workflows;
- a semantic PR-reachable privilege boundary;
- an always-on, non-required
PR Gate shadow; - a trusted data-only auditor and attempt-aware reconciliation;
- immutable candidate identity and a completed-producer/default-branch verifier;
- exact-subject, default-deny readiness evidence and crash-safe report pairs;
- a dormant, read-only CI shadow policy v2 and live observation receipts;
- route, burst, cancellation, failure/recovery, SLO, and exact-SHA acceptance evidence.
Non-goals¶
- an Actions-produced
PR Gate; - provisioning or implementing the future GitHub App;
- activating
unionorfinalprotection, or changing the canonical active required contexts; - changing ruleset
18806829, classic protection, repository variables, bypass roles, tags, release workflows, release concurrency, release publication, or release credentials; - executing untrusted content in the trusted shadow auditor, verifier
preflight/evaluator, any
pull_request_target, privileged job, or self-hosted runner; the explicitly unprivileged Workflow-B executor is the onlyworkflow_run-reachable execution exception; - reducing the full PR profile before a separate comparative objective;
- changing data-plane manifests, connectors, checkpoint ordering, or runtime idempotency;
- adding a generic CI plugin, event bus, evidence store, or workflow polling framework.
Assumptions and constraints¶
- PR 1 integrated as merge commit
0fa1b35bfd20c35fa0cb2a8c3dfa966d7afc11d4before this specification; PR 1 itself remains the only baseline repair permitted to precede the specification amendment. - The live authority is a time-bound observation. On 2026-08-30, ruleset
18806829, classic protection and.agents/policy/github-branch-protection.ymlall resolved the same 21 contexts from GitHub Actions App ID15368. Operational consumers resolve the current set from canonical active policy bytes rather than copying this observed count. - The full compatibility contract includes Python 3.11 and 3.12, eight Airflow cells, and both runtime-wheel smoke cases.
- PR-scoped concurrency may cancel stale heads. It is not a durable lock and never erases attempt identity.
workflow_runedges are hard-limited to one downstream hop in this architecture: producer depth 0, auditor/verifier depth 1. Cycles and any downstream trigger from depth 1 are rejected. GitHub's additional platform capacity is not an implementation reservation; a future finalizer requires a new approved ADR and architecture test.- PR5B post-upload certification is a separate explicit read-only
workflow_dispatch, not a downstream trigger; architecture tests reject anyworkflow_runtrigger in that workflow, so it does not amend this depth cap.
Public contract¶
Check contexts and authority¶
.github/workflows/pr-gate-shadow.ymlhas apull_requesttrigger withoutpathsorpaths-ignore.- Exactly one always-existing aggregator job has the exact display name
PR Gate shadowand usesif: always(). - Static policy tests reject a second
PR Gate shadowand any Actions job namedPR Gate. PR Gate shadowis never required during this goal.- The PR-head aggregator publishes that context from its own untrusted producer
attempt. The read-only
workflow_runauditor never creates or updates a PR check and cannot retroactively recolor the producer context. - The canonical active required contexts remain unconditional and are the only merge authority.
- Future
PR Gateauthority requires both contextPR Gateand a separately administered trusted App ID greater than zero and different from15368.
Change plan and routing¶
dpone.ci-change-plan.v1 is closed canonical JSON. It binds repository, pull
request, event, base/head SHA, route-policy digest, changed paths, semantic
configuration delta, selected jobs, N/A jobs, reasons, and plan digest.
The classifier uses an exact, rename-disabled, NUL-delimited diff. Duplicate paths are normalized. Unknown paths, invalid identities, policy/schema errors, or diff errors select the full route or fail the plan; they never silently skip work.
Closure routing is:
| Change | Required shadow work |
|---|---|
| docs only | static contracts plus strict docs; no Python/Airflow matrix |
uv.lock only |
static, Python 3.11/3.12 core, package build, applicable wheel smoke; no Airflow scheduler matrix |
root pyproject.toml |
semantic TOML diff: tool/dev-only follows core; runtime/build/workspace changes add builds and both wheel smokes; Airflow full only when Airflow compatibility policy changes |
| core | static, contracts, Python 3.11/3.12, packaging; no Airflow unless the changed dependency surface requires it |
| PostgreSQL | core route plus PostgreSQL XMin |
| Airflow pack/provider policy, constraints, or compatibility workflow | core, Airflow build, full eight-cell matrix, both wheel smokes |
| control, classifier ambiguity, or unknown path | full shadow route |
The activation-dependency probe belongs to wheel smoke rather than every Airflow cell. During this closure every selected Airflow route retains all eight cells. Representative routing is not activated.
The closed job vocabulary is static, contracts, docs, python-3.11,
python-3.12, packaging, postgresql, airflow, and
runtime-wheel-smoke. Matrix cases are children of their owning job, not
additional aggregate contexts. The exact live authority names remain
defined once by .agents/policy/github-branch-protection.yml; this design does
not copy them into a second active policy.
CI hygiene¶
- PR 3A-owned non-release CI project-environment synchronization uses
uv sync --locked; its exact allowlist, preserved options, validation, and release-workflow exclusions are frozen in the PR 3A executable child specification. - Dependabot uses the
uvecosystem every Monday with open-PR limit 3, timezoneEurope/Berlin, and labelsdependenciesandpython:uv. - Dependabot GitHub Actions updates run every Wednesday with open-PR limit 2,
timezone
Europe/Berlin, and labelsdependenciesandgithub-actions. PR 3A verifies that all three labels exist before relying on them. - Pull-request concurrency is PR-scoped and cancels only a stale head of the
same PR. Nightly non-release compatibility uses a stable group,
queue: max, andcancel-in-progress: false. - Legacy and shadow Airflow matrices use
max-parallel: 2; shadow wheel smoke uses1; nightly full compatibility uses4. - Pages on a PR validates only. A stable whole-workflow concurrency group, a
separate read-only current-master freshness job, and SHA/
run_attempt-bound outputs prevent an obsolete or deploy-only rerun from reaching the protected default-branch deploy job. Non-PR build/upload is eligible only on attempt1; every non-PR rerun is unsupported, recovery always creates a new current-master run and artifact namespace, and skipped deploy isNOT_RUN/UNVERIFIED, never deployment PASS. Deploy permissions remain isolated to that job. - Dependency Review remains a native read-only check on both PR test-merge and
exact
masterpush subjects; manual dispatch and synthetic backfill are removed so release exact-commit evidence remains available without write authority. - Release workflow files, release dependency installation, and release concurrency are unchanged.
Producer evidence¶
The producer writes one UTF-8 closed JSON document, at most 1 MiB, with bounded depth, collection counts, and string lengths. Duplicate JSON keys, non-finite numbers, unknown fields, invalid Unicode, or trailing bytes are blockers.
The normative schema path is
docs/schemas/cicd/pr-gate-shadow-evidence-v1.schema.json. JSON Schema enforces
the closed shape, types, local maxProperties/maxItems, character-length
bounds, and numeric ranges it can represent. The streaming parser separately
enforces maximum depth 8, 1,024 aggregate object members, 4,096 aggregate array
items, 256 members in one object, 1,024 items in one array, and 65,536 UTF-8
bytes in one string before materializing a value. Integers are non-Boolean,
unsigned, and at most 64-bit. Parser-limit failures use
JSON_LIMIT_EXCEEDED; representable schema failures use JSON_SCHEMA_INVALID.
Parity tests cover ASCII/multibyte strings, local versus aggregate counts, and
every exact boundary; the schema never claims to enforce global tree budgets.
The filename is immutable and attempt-specific:
It is uploaded with pinned actions/upload-artifact, archive: false,
overwrite: false, and 90-day retention. With direct upload, the filename is
the artifact name and the action's name input is not used.
The payload contains claims only. The following is an abbreviated explanatory excerpt; it is intentionally not schema-valid because the other eight mandatory job keys are elided. Normative fixtures contain all nine keys.
{
"schema_version": "dpone.pr-gate-shadow-evidence.v1",
"pr_number": 123,
"repository_id": 456,
"head_repository_id": 789,
"base_ref": "master",
"head_ref": "feature/example",
"base_sha": "<40-hex>",
"head_sha": "<40-hex>",
"merge_sha": "<40-hex>",
"subject_tree_oid": "<40-hex>",
"workflow_blob_oid": "<40-hex>",
"producer_run_id": 123,
"producer_run_attempt": 2,
"plan_digest": "sha256:<hex>",
"implementation_bundle_digest": "sha256:<hex>",
"jobs": {
"static": {
"selection": "RUN",
"cases": {
"default": {"outcome": "PASS", "command_id": "shadow.static"}
}
}
},
"status": "PASS"
}
It cannot contain its own artifact ID, provider digest, URL, or provider size, because those values exist only after upload. Self-described status is never authority.
head_sha is the authenticated pull-request head H; GitHub REST
workflow_run.head_sha also binds H and must never be relabelled as the
synthetic pull-request merge M. REST run/check_suite.pull_requests[] is
mutable and may become empty; it is diagnostic only and neither its membership
nor its SHA fields are attempt authority.
pr_number, repository IDs, base/head refs, base_sha, and merge_sha are
event-generated producer claims, not API authority. Before accepting them, the
read-only auditor completely enumerates open PRs and defines exactly one
eligible tuple:
(base_repository_id = run.repository.id,
base_ref = protected default branch,
base_sha = B,
head_repository_id = run.head_repository.id,
head_ref = run.head_branch,
head_sha = run.head_sha = H,
pr_number = claimed N,
state = open,
mergeable = true,
merge_commit_sha = refs/pull/N/merge = claimed M)
Pagination must be complete. Another open PR with the same base/head repository
IDs, refs, and SHAs is an ambiguous twin and rejects; PR number alone is not
uniqueness. For the claimed PR, the auditor performs bounded polling of exact
GET /pulls/N until mergeability is non-null, resolves the GitHub-owned
read-only ref through an injected Git-ref port, and requires two consecutive
identical full observations of the tuple and the complete eligible-set digest.
Every observation independently repaginates the complete open-PR set before it
polls the exact PR, resolves the ref, and loads its parents; no observation may
reuse a list captured by an earlier read. At most five observations or 30
seconds are allowed; timeout, stale PR response/ref mismatch, partial
pagination, conflict, or ambiguity is AUDITOR_MERGE_REF_UNVERIFIED.
The auditor loads immutable M, requires exactly two ordered parents (B, H),
and compares them with the stable tuple and claims. Immediately before
persisting the receipt it repeats the complete enumeration, exact-PR polling,
ref resolution, and parent check; the same tuple and OID must still match. This
final read is the audit linearization point. Any final failure is folded into a
schema-valid UNVERIFIED/AUDITOR_MERGE_REF_UNVERIFIED receipt before
persistence; only receipt-storage failure may prevent the receipt. Because the
provider-owned ref still equals the collector's GITHUB_SHA=M claim at that
point, it authenticates the current merge snapshot without giving the PR
workflow OIDC, write permission, or a secret. It does not claim that a mutable
ref proves a historical event M; product execution is independently bound to
H. If the ref moved while the PR remains open, recovery requires a genuinely
new pull_request event/run (normally a new/rebased head), never rerunning the
old event. If the PR was closed/merged or the ref disappeared, the old attempt
is terminal UNVERIFIED; only a new corrective PR can create new evidence.
subject_tree_oid binds the H tree actually checked out for product and
control commands, and workflow_blob_oid is accepted only when the exact
workflow path has the same regular-file mode and blob OID in B, H, and
provider-bound M. A PR that changes the producer workflow is deliberately
UNVERIFIED until that workflow is merged and becomes the common base for a
later attempt. Every producer
product/control job that executes repository bytes checks out the event's exact
head repository at immutable H with a fully
pinned actions/checkout, persist-credentials: false, submodules: false,
and clean checkout semantics. Before any repository command, the job proves a
clean worktree, git rev-parse HEAD == H, and the expected subject tree OID.
No job may rely on checkout's default PR merge ref. A mismatch fails before
product execution and is visible in the provider job conclusion.
Same-repository, fork, and Dependabot producers use the same read-only profile:
no Actions secret, environment, write token, OIDC, or trusted cache. Provider
approval behavior is not promoted to an identity contract. An immutable incoming
workflow_run payload with conclusion=action_required may produce only
receipt-free PENDING_OBSERVER telemetry; the design does not assume GitHub
always emits that delivery or later reuses its run/attempt identity. A current
API action_required observation is
UNVERIFIED/PROVENANCE_APPROVAL_PENDING. Only an independently authenticated
post-start completed attempt enters audit. Unknown, inaccessible, moved,
deleted, closed/reopened, or ambiguous lifecycle forms stay UNVERIFIED, and
recovery uses a fresh eligible PR event/head when no auditable completion
materializes. After PR 4B's safe fallback is merged, the live public-fork
canary required before PR 4C implementation must capture the event and API
sequence. Dependabot keeps its documented fork-equivalent
read-only/no-Actions-secrets boundary.
The auditor's pull-requests: read permission enumerates base-repository open
PRs; it never asks a fork to publish trusted evidence.
jobs contains exactly the nine keys in the closed job vocabulary. Every job
has selection=RUN|N/A and a closed cases object. A non-matrix job has only
default; Airflow has the eight exact (2.10.5|2.11.0|3.2.0|3.3.0) x
(3.11|3.12) case IDs; runtime wheel smoke has 3.11 and 3.12. Each case has
only outcome=PASS|FAIL|N/A and a trusted-policy command_id; arbitrary shell
text is forbidden. N/A is valid only when both the trusted plan and every
case say N/A. The root status is the claimed fold over all cases.
The PR 4A child contract freezes a separate closed provider-workflow job set:
planner, the jobs/cases required by those nine selections, and final claims
aggregator PR Gate shadow. The claims jobs object never hides
those infrastructure jobs; the auditor binds both vocabularies independently.
Product command steps and jobs may not use continue-on-error: each required
command fails its provider job natively. A post-product step on the same mutable runner
is never an enforcement boundary, because untrusted PR code can alter runner
state. Every evidence matrix sets strategy.fail-fast: false, so one failing
cell cannot cancel sibling evidence. A separate global if: always()
claims-collector job sees all needs results,
then uses only actions: read to page its own exact run-attempt Jobs API and
derive every matrix case name/provider conclusion; it never relies on matrix
output merging. It uploads the single claims document even when a product job
fails, and no per-cell machine artifact is introduced. The auditor independently
repeats the API query, so producer aggregation remains an untrusted claim. This
fresh aggregator is the only job named PR Gate shadow; it folds native product
conclusions without running after untrusted code on the same runner.
The claims collector is checkout-free, cache-free, and download-free. Its
identity fields come only from direct GitHub event expressions
(repository_id, PR number, base/head repository IDs and refs, B, H, and
GITHUB_SHA=M) wired by the byte-identical B/H/M workflow into a closed static
encoder; no needs output, product artifact, PR environment, or candidate shell
text can supply or override them. Its fixed encoder and allowed context paths are
TRUST_CORE entries in the approved bundle. Only provider job conclusions come
from the attempt Jobs API. This gives the later read-only audit an
event-generated correlation claim without making producer self-description
authoritative or giving the collector a write capability.
Trusted shadow audit receipt¶
The default-branch audit workflow evaluates only an independently authenticated
post-start completed producer attempt. An immutable incoming event whose own
conclusion=action_required is receipt-free PENDING_OBSERVER telemetry,
not an auditor identity; it downloads no claims and is excluded from duplicate
auditor detection. This is a one-way fail-closed rule, not a promise that the
provider emits an action-required delivery or preserves the same run ID or run
attempt after approval. Current pending API observations belong to stateless
reconciliation. Two independent post-start auditor workflow run IDs remain an
ambiguity blocker. It records two authoritative revisions:
auditor_revision_sha = GITHUB_SHA, the trusted default-branch revision;producer_head_sha = github.event.workflow_run.head_sha, independently matched to RESTrun.head_shaand provider-bound merge parentH.
The auditor never derives B, H, or M from current mutable PR association
SHA fields. It accepts claimed M only when the exact GitHub-owned pull-request
merge ref still resolves to that OID, then requires parents (B, H) plus REST
run.head_sha == H. Missing, moved, ambiguous, conflicted, or expired merge
identity is AUDITOR_MERGE_REF_UNVERIFIED. A workflow blob/mode mismatch across
B/H/M is AUDITOR_WORKFLOW_SOURCE_UNVERIFIED.
Before download it queries GitHub REST metadata and verifies repository and
head repository, run ID/attempt, workflow ID/path, event, branch, subject head
SHA/tree OID, provider merge-ref identity, B/H/M parentage and workflow blob equality,
status, conclusion, timestamps, artifact ID/name/digest/size/expiration, and
the unique eligible match. Producer fields are claims compared with API data.
The auditor also authenticates the exact pull request, base and head commits;
walks both immutable Git trees by object ID; rejects truncated, incomplete,
over-limit, non-blob, or submodule input; and computes a rename-disabled path/OID
diff itself rather than trusting provider rename heuristics. It reads only the
bounded configuration blobs needed for semantic routing. Trusted default-branch
classifier and route policy code independently recompute canonical plan bytes
and digest. It pages
/actions/runs/{run_id}/attempts/{attempt}/jobs, rejects incomplete pagination,
and binds the exact expected workflow job IDs, matrix case names, statuses,
conclusions, and terminal timestamps. Producer outcomes remain claims; provider
job/case conclusions and the independently recomputed plan decide the audit.
Download uses exact repository, run ID, and artifact ID into runner.temp,
with skip-decompress: true and digest-mismatch: error. The auditor never
checks out the PR head, restores a cache, imports producer modules, evaluates
YAML/pickle, sources data, or executes/interpolates downloaded bytes. Rich
diagnostics may be uploaded separately for humans; the auditor never reads
them.
The attempt-specific receipt is:
The auditor stages that exact filename, fsyncs it, and uploads it directly with
archive: false, overwrite: false, and 90-day retention before returning its
decision. A receipt-upload failure is AUDITOR_RECEIPT_PERSIST_FAILED; the run
cannot report verified success.
The normative audit schema is
docs/schemas/cicd/pr-gate-shadow-audit-v1.schema.json with the same parser
budgets. dpone.pr-gate-shadow-audit.v1 contains exactly:
schema_version,decision=PASS|FAIL|UNVERIFIED, andaudit_stage=EVENT_RECEIVED|RUN_AUTHENTICATED|PLAN_RECOMPUTED|JOBS_BOUND|CLAIMS_BOUND;- required
observed_event: repository, workflow-run ID, event head repository and head SHA, event status/conclusion, receipt timestamp, and canonicalobservation_keyover repository, producer run ID/attempt, auditor workflow ID/revision, and immutable event head identity; - nullable
producer_identity, which whenaudit_stage>=RUN_AUTHENTICATEDcontains authenticated base/head repository names and numeric IDs, PR number, base/head refs, base/head/merge SHA, subject tree OID, exact pull-request merge ref/OID plus initial/final resolution timestamps, equalB/H/Mworkflow blob OID, workflow ID/path, event, branch, run ID/attempt, status, conclusion, and created/updated timestamps; - tagged
claims_evidence:MISSINGis allowed only when no unique usable transport artifact can be selected or downloaded and records that exact transport blocker;PRESENTalways binds artifact ID/name/provider digest/size/expiration and local raw-byte SHA-256, then carriesparse_status=VALID|INVALIDplus a nullable canonical claims digest that is non-null only forVALID. Merge-ref, workflow, plan, job, or bundle failure never relabels already downloaded bytes as missing; - nullable phase outputs
recomputed_plan_digest,attempt_jobs_digest, andimplementation_bundle_digest, each with a companion completed-phase flag; auditor_identity: workflow ID/path, trusted revision, run ID/attempt;- sorted closed
blockers, trustedreproduction_command_ids, optionalresolves, and no unknown fields.
It does not contain the identity of its own not-yet-uploaded artifact. Shadow audit requires provider metadata, the exact provider-owned merge ref and immutable parents, locally recomputed digests, semantic revalidation, and the trusted receipt. It does not create or require an attestation.
PASS requires claims_evidence=PRESENT, parse_status=VALID, and every phase output non-null.
Authenticated product/contract failure also requires a unique transport-bound,
PRESENT/VALID subject plus completed merge-ref, plan, bundle, and attempt-job
phases; the claims' self-described status is ignored. Actual transport
absence uses MISSING. Invalid JSON retains PRESENT/INVALID raw transport
identity; later merge/workflow/plan/job/bundle uncertainty retains the existing
PRESENT tag and uses nullable phase results plus exact blockers. Artifact or
merge-ref identity missing/duplicate/expired after any producer conclusion is
always UNVERIFIED, never FAIL.
Stage invariants are monotonic: each stage requires all prior-stage fields;
later-stage fields must be null before that stage. PASS requires
CLAIMS_BOUND; authenticated FAIL requires at least JOBS_BOUND;
UNVERIFIED is valid at any stage with an exact blocker. Thus even an API or
permission failure can persist a schema-valid receipt without inventing an
identity or digest.
Transitive implementation bundle¶
dpone.ci-shadow-bundle.v1 has canonical approval path
.agents/policy/ci-shadow-bundle-v1.yml on the auditor's trusted default-branch
revision. It is a closed manifest over the workflow,
classifier, evaluator, route policy/schema, every local import/read file/called
script in the routing/evidence control plane, and every pinned external action.
It does not recursively absorb subject workload inputs: product source,
packages, tests, uv.lock, build inputs/outputs, and command-ID product programs
remain untrusted PR data exercised by jobs, not approved control-plane bytes.
Manifest roles explicitly distinguish TRUST_CORE from SUBJECT_INPUT; a file
that can affect route selection, evidence encoding, outcome folding, identity,
or audit is always TRUST_CORE and cannot be excluded as workload. A static dependency closure test
rejects dynamic imports, exec, eval, undeclared helpers, local actions,
local reusable workflows, or unknown dependencies. Unknown closure is
UNVERIFIED, not partial approval.
The auditor obtains every producer workflow/local dependency from immutable Git
tree and blob objects, verifies regular-file mode, confined canonical path,
size, blob OID, and SHA-256, then recomputes the manifest digest. The manifest
assigns the producer workflow YAML the role EXECUTION_WORKFLOW; its path,
mode, and blob OID must be identical in exact base B, subject H, and
provider-bound merge M. It
resolves checked-out local control-plane files with role SUBJECT_TRUST_CORE
from authenticated subject tree H. It statically requires every
product/control job to perform the exact H checkout and pre-command identity
proof above. It compares that closed subject/workflow closure with the exact
trusted-revision manifest; the producer's bundle digest is only a claim. Any
extra, missing, symlink, submodule, dynamic, unread, mismatched, changed-between-
parents, or identity-unbound dependency is AUDITOR_BUNDLE_UNAPPROVED.
Focused taint/closure tests prove that changing a classifier/evaluator/helper or
policy byte invalidates approval, while changing only declared product source,
tests, package inputs, or lockfile leaves the control-plane digest stable and is
still bound through exact subject SHA and provider job conclusions.
Stateless attempt-aware reconciliation¶
PR 4C is a bounded diagnostic observer, not a state store. The approved
machine-readable design fixture is
test_artifacts/agent-policy/dpone-ci-shadow-reconciliation-mvp.yml, validated
by the adjacent closed JSON Schema. It is runtime_consumable=false and is
never passed to a production command. The PR 4C reconciler child contract must freeze an
exact canonical runtime object owned by
dpone.contracts.ci_shadow_reconciliation.ReconciliationPolicyV1; the
composition root injects that object into the service. There is no external
--policy argument or mutable operator choice that can shrink the scan.
Observation interval and pagination¶
The source-free default-branch reconciler runs daily at 03:15 UTC. An injected trusted UTC clock is floored to a whole RFC 3339 UTC second:
observation_started_at = floor_to_utc_second(trusted_clock.now_utc())
safe_scan_through = observation_started_at - 30 minutes
scan_from = safe_scan_through - 14 days
scan_interval = [scan_from, safe_scan_through] # closed UTC seconds
Every invocation scans that exact 14-day provider-observable interval from
scratch. The 30-minute grace period excludes runs that may still be appearing
or changing. The report says provider-observable exact interval; it is not
proof that no hidden, manually deleted, or provider-omitted run ever existed.
Producer discovery uses the exact workflow endpoint and
event=pull_request&created={lo}..{hi}&per_page=100&page={page}. Auditor
discovery uses the exact auditor workflow endpoint over
[scan_from, observation_started_at] with event=workflow_run. For both:
- one complete whole-window observation independently constructs and pages the entire deterministic partition tree, enumerates producer and auditor run/attempt records plus exact-attempt Jobs pages and artifact inventories, and downloads every selected bounded artifact byte sequence;
- a slice with
total_count < 1,000is a completely paged leaf; - a slice with
total_count >= 1,000andlo == hiis irreducible andUNVERIFIED; an adjacent-second[lo, hi]splits into[lo, lo]and[hi, hi]; every wider slice splits at its whole-second midpoint into closed children[lo, mid]and[mid, hi]; - a shared midpoint second is deliberately observed twice; byte-identical
stable keys deduplicate, while the same key with changed canonical bytes is
UNVERIFIED; - the service performs two consecutive complete whole-window observations; their partition topology, total counts, canonical run/attempt/Jobs/artifact records, downloaded bytes, and all response/content digests must be identical; and
- repeated/missing pages, changed records, partition-depth overflow, or a
partial response are
UNVERIFIED/API_PAGINATION_INCOMPLETE.
The second matching whole-window observation is the report's
evidence_observed_through boundary. Report persistence follows immediately
without another provider read. Evaluation uses only the bounded bytes retained
from that second observation; a later run/rerun belongs to the next report. The
report claims only the set visible under this exact double-observation
protocol; the 30-minute grace reduces but does not relabel provider omission or
later appearance.
Cross-window auditor classification¶
Auditor discovery deliberately reaches through the 30-minute producer grace so
that every visible auditor record is explained instead of silently discarded.
During each complete whole-window observation, every auditor record therefore
causes an exact producer-run lookup. That lookup, its retries, Jobs and artifact
reads, and all response bytes consume the same global budgets as list discovery.
The only timestamp authority for interval membership is the exact producer root
workflow run's provider-authenticated workflow_run.created_at; an auditor rerun
does not move the producer into a newer interval.
The closed classification is:
Producer created_at |
Current-report outcome | Root effect | Recovery |
|---|---|---|---|
< scan_from |
OUT_OF_SCOPE_OLD_PRODUCER_RERUN |
diagnostic only | CREATE_NEW_PRODUCER_RUN |
scan_from <= created_at <= safe_scan_through |
EVALUATE_NORMALLY |
fold the producer and selected auditor receipt | normal attempt recovery |
safe_scan_through < created_at <= observation_started_at |
DEFER_TO_NEXT_REPORT |
diagnostic only | wait for the next report |
created_at > observation_started_at |
UNVERIFIED / RECONCILIATION_FUTURE_PRODUCER |
block the current report | verify trusted time/provider identity, then rerun |
An exact producer run inside the interval that is absent from the independently
complete producer query is
UNVERIFIED/RECONCILIATION_PRODUCER_QUERY_OMISSION with
RECONCILIATION_RETRY. Missing, foreign, wrong-workflow, wrong-event, or
otherwise unresolvable producer identity is
UNVERIFIED/RECONCILIATION_PRODUCER_IDENTITY_UNRESOLVABLE with
VERIFY_PRODUCER_IDENTITY_OR_CREATE_NEW_RUN. Both block the current report.
Every auditor record receives one of these explicit classifications, stable
codes, and recovery command IDs in report bytes. The reconciler never expands
scan_from backward to make an old producer authoritative, and a rerun of an
old producer never repaints the current daily root.
List order, page order, artifact creation order, and current PR association SHA
fields are never authority. The raw workflow-run stable key is
(repository_id, workflow_id, run_id). After listing, the reconciler
enumerates every attempt 1..run_attempt using attempt-specific run and Jobs
APIs; the producer evidence key is
(repository_id, workflow_id, run_id, run_attempt).
There is no cursor, predecessor state, unresolved ledger, genesis state, DAG, ACI lattice, tombstone, external sort, checkpoint promotion, or manifest/state/finalization transaction. No previous reconciliation report is an input. In particular, there is no “latest artifact wins” rule and a missing older report cannot silently change the current interval.
Auditor receipt selection¶
For each in-window producer key, PR 4C evaluates the matching trusted auditor
workflow runs, attempts, Jobs, inventories, and receipt bytes captured by the
second complete observation. Cross-window auditor records first follow the
classification above. Within one auditor
workflow run, the highest observed attempt must be terminal before its
receipt can be selected. If a higher requested, queued, or in-progress attempt
exists, the producer key is UNVERIFIED; an earlier completed PASS cannot
temporarily win. Lower completed attempts remain diagnostic lineage. Two
independent auditor workflow run IDs, different terminal decisions for the same
authenticated evidence, duplicate artifacts, or foreign identity is
UNVERIFIED.
Coverage, product, and rollout decisions¶
The report separates:
coverage_status=PASS|UNVERIFIED, which describes pagination, transport, identity, retention availability, and resource completeness;- each attempt's
product_status=PASS|FAIL|UNVERIFIED; and - root
decision, folded asUNVERIFIED > FAIL > PASSfor this exact interval.
An authenticated terminal product/auditor failure is FAIL only when no
coverage uncertainty exists. A missing/nonterminal/skipped/cancelled/timed-out/
action-required/stale attempt, higher nonterminal auditor rerun, incomplete
enumeration, missing/ambiguous receipt, identity mismatch, resource overflow,
or unavailable API is UNVERIFIED. PASS requires complete stable
pagination, every observed attempt terminal, exactly one authenticated selected
receipt per attempt, and no uncertainty or resource blocker.
Route canaries, deliberate FAIL/cancellation recovery, and two distinct-SHA
full runs are separate rollout oracles and are not daily PASS prerequisites
outside the interval. If any such attempt is visible inside the exact producer
interval, it is folded exactly like every other producer attempt: an expected
deliberate FAIL makes the daily root FAIL, and an expected cancellation
makes it UNVERIFIED. The acceptance oracle separately verifies that this was
the intended outcome; it never excludes or repaints the attempt. Canary
artifacts outside the interval are not fetched, so expiry cannot make daily
reconciliation permanently UNVERIFIED; a fresh acceptance campaign creates
fresh immutable evidence.
Fork-approval telemetry¶
Fork approval remains fail-closed telemetry. An immutable incoming
workflow_run payload whose own conclusion is action_required may no-op as
receipt-free PENDING_OBSERVER; this does not assert that GitHub always emits
that delivery. A current API action-required run is
UNVERIFIED/PROVENANCE_APPROVAL_PENDING. Only an independently authenticated
post-start completed attempt enters audit. The design does not require the same
run ID or run attempt. Unknown, moved, deleted, closed/reopened, rerun, or
otherwise ambiguous forms remain UNVERIFIED.
PR 4B first lands the safe fallback above on the default branch without relying on lifecycle continuity. Then an authorized public-fork lifecycle canary records its event journal and fully paginated run/Jobs observations before approval, immediately after approval, while running, after completion, after rerun, after close/reopen, and after deletion. That fork evidence is required before the PR 4C reconciler child contract and implementation. Route, deliberate failure/ cancellation, burst, and distinct-SHA campaigns require the implemented PR 4C report and therefore run only after PR 4C, before goal acceptance. Either class may strengthen the lifecycle only through a reviewed amendment; absence never weakens the fallback.
Retention and permissions¶
Each upload binds its own source authority and explicitly sets
retention-days: 90, archive: false, and overwrite: false:
| Evidence source | Upload-setting authority |
|---|---|
| PR-head producer | exact workflow path/mode/blob identical in authenticated B/H/M |
| default-branch auditor | allowlisted default-branch workflow ID/path/revision/ref/event/run/attempt |
| default-branch reconciler | allowlisted default-branch workflow ID/path/revision/ref/event/run/attempt |
PR 4C authenticates each required artifact's created_at, expires_at,
expired, provider digest, size, and bytes through ordinary Actions-read
metadata. GitHub expires_at is an observed availability deadline, not proof
that expires_at - created_at == 90 days; the latter equality is never
required. The source-specific authenticated workflow input proves the declared
retention request. Missing, expired, early-deleted, inconsistent, or unreadable
evidence makes the affected interval immutable
UNVERIFIED/RECONCILIATION_RETENTION_HISTORY_LOST.
Repository-level retention settings are optional PR 7 telemetry and never an
authority input for PR 4C. PR 4C has only contents: read, actions: read,
and pull-requests: read; it has no repository-admin, check-publication,
OIDC, secret, cache, setter, arbitrary-request, or mutation capability.
Reconciliation report and write transaction¶
Each invocation writes one immutable direct-JSON report named
pr-gate-shadow-reconciliation-<observer_run_id>-<observer_run_attempt>.json
with schema dpone.pr-gate-shadow-reconciliation.v1. The observer identity in
the filename is never a producer identity. The closed report contains:
- observer/repository/workflow identity and exact interval;
- literal scope
provider-observable exact interval; coverage_status, per-attempt product records, root decision, blockers, and recovery command IDs;- complete pagination counters for a normal result; or an explicitly incomplete
observed_at_leastlower bound andlast_complete_sliceafter a resource stop; and - provider-query digests only for completely observed slices.
The payload does not contain its own length or digest. Before write, the service checks the 1,048,576-byte limit. After upload, provider artifact ID/digest/size become transport identity for downstream readers.
Before any workflow integration, the PR 4C calibration-probe child must
introduce the exact capability port
dpone.ports.evidence.CreateOnlyEvidenceWriterV1 and adapter
dpone.adapters.filesystem_evidence.DescriptorPinnedCreateOnlyEvidenceWriter.
The later reconciler child reuses that reviewed capability.
The existing dpone.readiness.route_attestation_files.write_create_only is not
authority for the stronger retry semantics and cannot be reused directly. Any
shared extraction or extension must preserve its existing public behavior and
pass the route-attestation compatibility suite before the new adapter is used.
The new confined adapter creates a same-attempt stage, writes bounded bytes,
file-fsyncs, commits without replacement, and parent-fsyncs. A retry that finds
a byte-identical target must descriptor-pin and revalidate the canonical root,
parent, leaf, inode, and bytes, then successfully fsync the file and parent in
the current attempt before returning idempotent success. Different bytes,
symlink, foreign inode, identity mismatch, or any failed durability barrier is
UNVERIFIED. Retry may remove only an authenticated same-attempt partial stage,
fsync the parent, and repeat. Failure before durable commit leaves no certified
target; failure after commit preserves the immutable target but remains
non-PASS until the current retry completes the durability barrier.
Resource limits and recovery¶
The numeric request, byte, and wall-time limits below are parent-owned hard
maxima, not provisional values that a child may expand. A separately approved
PR 4C calibration-probe child first introduces the read-only command
tools/ci/calibrate_pr_gate_shadow_capacity.py, workflow
.github/workflows/pr-gate-shadow-capacity.yml, canonical service/ports, and
closed schema
test_artifacts/agent-policy/dpone-ci-shadow-reconciliation-capacity.schema.json.
The source-free default-branch workflow_dispatch receives only the same
read-only permissions as the reconciler and changes no repository setting.
The probe runs the complete current 14-day double observation under the hard
maxima and publishes one create-only
pr-gate-shadow-reconciliation-capacity-<observer_run_id>-<observer_run_attempt>.json
artifact. Its dpone.ci-shadow-reconciliation-capacity.v1 payload is always
UNVERIFIED; a complete matching within-threshold observation uses
RECONCILIATION_CAPACITY_CALIBRATION_ONLY, while incomplete, mismatched, or
over-threshold output uses RECONCILIATION_PR4C_IMPLEMENTATION_BLOCKED. Neither
certifies a daily root. The payload binds repository/default-branch workflow
path/revision/ref/event/run/attempt, policy SHA-256, exact interval, observation
boundary, matching-observation proof, counters, hard maxima, and thresholds.
The wrapper workflow and calibration CLI are authenticated by that source
identity and are deliberately excluded from the equality-constrained bundle:
the later reconciler has a different, independently authenticated wrapper and
composition root. The shared bundle instead contains the canonical acquisition
policy, service, ports, adapters, their transitive local imports/reads/called
scripts, pyproject.toml, and uv.lock. Traversal from the shared acquisition
service rejects dynamic/unresolved local inputs, absolute/dot-dot/non-UTF-8
paths, and symlinks.
Entries are unique and strictly ascending by UTF-8 repo-relative path bytes;
each path is at most 1,024 UTF-8 bytes;
each entry is u32be(path_len) || path || six ASCII mode bytes || raw 32-byte
blob SHA-256. With at most 4,096 entries, manifest_bytes is
u32be(entry_count) followed by repeated
u32be(entry_len) || entry_bytes. The bundle digest is
SHA256(domain_utf8 || 0x00 || u64be(manifest_len) || manifest_bytes) where the
domain is dpone.ci-shadow-reconciliation-observation-bundle.v1. The reconciler
child must prove its shared acquisition closure byte-identical; changing that
closure invalidates calibration. Calibration and reconciliation also match the
controllable runner label, architecture, and Python version; dependency locks
are already inside the shared bundle. Provider image version is diagnostic
telemetry, not equality authority; the two-times margin and runtime hard caps
remain fail-closed if the hosted image changes. Wrapper source identities remain
separate. The approval adapter selects one external provider artifact by exact
repository/run/attempt/ID/name, rejects provider ZIP archive size above
8,388,608 bytes before allocation, reads at most 8,388,609 archive bytes, and
verifies actual archive length and SHA-256 against the provider size and digest.
GitHub Actions artifact transport is ZIP, so provider metadata does not claim to
authenticate a direct JSON member. Only after archive authentication does the
adapter accept exactly one expected confined regular JSON member; duplicate
names, directories, symlinks, encrypted entries, traversal, unsupported
compression, or extra files are UNVERIFIED. It separately limits and records
the direct payload byte count and SHA-256, then parses those same payload bytes
with the existing duplicate-free/non-finite-rejecting strict JSON primitive. The
parsed tree is additionally capped at depth 32 and 50,000 nodes before schema
validation. Limit approval requires an unexpired,
complete artifact from the same approved policy/shared-bundle/configuration
revision, no crossed limit, and
valid_until == calibration_observed_at + 24h. Trusted approval time must be
greater than or equal to calibration_observed_at and strictly less than
valid_until.
The complete observation is current only when
observation_started_at <= evidence_observed_through <=
calibration_observed_at and
calibration_observed_at - observation_started_at <= 900 seconds. This UTC
ordering complements, but never replaces, the injected monotonic wall counter;
it prevents a recent stamp from qualifying an arbitrarily old 14-day scan.
Request accounting increments before every outbound HTTP dispatch. Closed
classes cover producer/auditor list pages, exact producer and attempt lookups,
Jobs pages, artifact metadata/download endpoints, followed redirects/blob
requests, Git objects, pull-request identity, and polling. Their canonical sum
equals total_http_requests; retry_requests is an intersecting subset, must
not exceed total_http_requests, and is not added again. Response bytes are the response-body octets delivered to the
application before archive decompression. The payload records API response-body
bytes, artifact-download body bytes, and their exact sum
total_response_body_bytes. To preserve a two-times safety factor inside parent
authority, complete observed usage must be at most 400 requests, 67,108,864
response-body bytes, and 450 wall seconds—half the hard maxima. Missing, stale,
partial, inconsistent, over-threshold, or untrusted calibration is
RECONCILIATION_PR4C_IMPLEMENTATION_BLOCKED; the reconciler child cannot be
approved. Exceeding a threshold requires a reviewed parent amendment; a
child cannot raise a hard maximum.
Below a hard limit, counters are exact. limits_crossed uses hard-maximum field
order, and every named counter serializes exactly its saturated hard maximum.
A request whose pre-dispatch increment
would exceed 800 is never sent. Byte streams read at most remaining + 1 octets
to detect overflow; after crossing, the serialized counter saturates at the hard
maximum, limits_crossed names the byte budget, and counter semantics identify
the value as a lower bound. Wall time uses an injected monotonic clock from
immediately before first provider dispatch through final observation
evaluation. Every dispatch timeout is
min(adapter_timeout, remaining_wall_budget) and no dispatch starts with zero
remaining time. A crossed wall budget similarly serializes the saturated
900-second lower bound and cannot qualify capacity evidence.
| Boundary | Limit | Failure |
|---|---|---|
| provider results per time slice | 1,000 | split; irreducible same-second cap is UNVERIFIED |
| time-partition depth | 32 | RECONCILIATION_RESOURCE_LIMIT |
| API requests | 800 | stop immediately with incomplete lower-bound report |
| raw workflow-run / artifact records | 65,536 each | stop immediately with incomplete lower-bound report |
| aggregate API-response plus artifact-download bytes across both observations | 134,217,728 | stop immediately with incomplete lower-bound report |
| wall time | 900 seconds | stop immediately with incomplete lower-bound report |
| producer attempts / audit receipts | 4,096 each | stop immediately with incomplete lower-bound report |
| report bytes | 1,048,576 | compact UNVERIFIED report |
| JSON depth/members/items/string bytes | 32/256/4,096/65,536 | JSON_LIMIT_EXCEEDED |
Once any budget is crossed, enumeration stops. The report sets
complete=false, gives only observed_at_least and the last completely
verified slice, and never claims an exact total, first/last key, or digest for
the unseen set.
A retry is a new observer run and immutable report. Transient API failure uses
RECONCILIATION_RETRY. A lost required artifact makes that exact report and
every overlapping interval immutable UNVERIFIED. A fresh auditable producer
attempt may establish new exact-head evidence, but cannot repair a daily root
whose interval still contains the lost attempt; daily-root recovery waits until
a later completely observed clean window excludes it. A deterministic record,
partition, or output cap likewise waits for a clean window or a separately
approved limit amendment; only transient API/wall-time failure uses immediate
retry. A later PASS describes a different interval and never relabels the
older report. There is no in-band history repair, re-genesis, administrative
relabel, or migration prerequisite for ordinary rolling-window recovery.
Immutable candidate and two-phase compatibility¶
Workflow A builds an immutable candidate and terminates. During this goal the
only authoritative producer is merged path
.github/workflows/exact-sha-candidate.yml, resolved to its API workflow ID,
triggered by push on refs/heads/master; subject SHA is the authenticated
run head SHA. Schedule, workflow dispatch, tag, other branch/path, same-run, and
non-allowlisted producers are diagnostic UNVERIFIED. Tags remain outside this
goal. The allowlist is merged repository policy, not producer input.
The producer exposes repository, workflow ID/path/blob, event/ref, subject SHA,
run ID/attempt, artifact ID/name, provider digest, and a sorted inventory digest.
artifact_url is navigation only. Identity and authorization use the
provider-authenticated repository/workflow/run head SHA, exact allowlisted
workflow and checkout proof, run ID, run attempt, artifact ID, provider digest,
and inventory digest. A SHA copied into candidate-controlled bytes is
self-description and is not authority. The attempt-specific candidate artifact contains exactly the root,
Airflow pack, and provider wheels plus one closed
dpone.compatibility-candidate.v1 manifest. The manifest inventory is sorted by
confined filename and binds distribution, normalized version, SHA-256, and byte
size; duplicates, directories, symlinks, extra archives, invalid wheel names,
or cardinality other than three reject before candidate execution.
Default-branch Workflow B starts only from workflow_run: completed, performs
REST metadata preflight, and downloads by artifact ID. It has three explicit
boundaries: a trusted data-only preflight job; a GitHub-hosted unprivileged
executor job; and a trusted data-only evaluator that parses a closed executor
receipt but never imports, sources, or executes its outputs. Every job has only
the minimum actions: read/contents: read permission and no secret, write,
OIDC, environment, or private-network authority. This verifier is not the
shadow auditor: it intentionally executes candidate wheels only inside its
isolated executor. Same-run and manual or non-allowlisted producers can be
executed only as diagnostic evidence and can never be VERIFIED.
Candidate code executes only on a GitHub-hosted ephemeral runner with no
secrets, inherited secrets, write scopes, OIDC, cache restore/save, VPN,
private/on-prem endpoints, or cloud credentials. actions/cache, setup-python
cache, and uv cache are forbidden; setup-uv enable-cache: false is explicit.
The download token is not available to candidate execution steps. Candidate
outputs remain untrusted data, and no privileged follow-up executes them.
Candidate archives remain acceptable because only the unprivileged executor opens them after metadata preflight. The trusted shadow evidence path remains direct, single-file JSON.
Evidence taxonomy¶
| Situation | Provenance | Compatibility | Evidence |
|---|---|---|---|
| allowlisted producer succeeds; tests pass | VERIFIED |
PASS |
PASS |
| allowlisted producer succeeds; tests fail | VERIFIED |
FAIL |
FAIL |
allowlisted producer concludes failure |
VERIFIED |
NOT_RUN |
FAIL |
| producer cancelled, timed out, action-required, stale, or skipped | UNVERIFIED |
NOT_RUN |
UNVERIFIED |
| same-run or non-allowlisted producer | UNVERIFIED |
actual diagnostic result | UNVERIFIED |
| diagnostic verifier dispatch | UNVERIFIED |
actual diagnostic result | UNVERIFIED |
| missing, malformed, expired, duplicate, or ambiguous artifact | UNVERIFIED |
NOT_RUN |
UNVERIFIED |
For the shadow auditor, producer success plus receipt PASS is expected;
producer failure plus receipt FAIL is expected; producer success plus receipt
FAIL is a blocker; producer failure plus receipt PASS is a critical
false-green. Cancelled or timed-out is never PASS.
Decision precedence is closed. Foreign/ambiguous identity, incomplete API
pagination, permission/outage uncertainty, cancellation, timeout, stale or
missing proof is UNVERIFIED. Matrix folding is deterministic: a missing or
nonterminal required case, or any required case concluded cancelled,
timed_out, action_required, stale, or skipped, makes the attempt
UNVERIFIED and takes precedence over a different case's failure. Only after
the exact case set is complete and none has an uncertainty conclusion does any
authenticated product/job/case or contract failure become FAIL, and only when
the unique transport-bound claims document is present. Missing producer JSON is
missing proof and remains UNVERIFIED. All required cases succeeding is
the only path to PASS. Whole-producer cancellation/timeout/skip uses the same
UNVERIFIED precedence. Auditor crash or receipt-persistence failure is
observed as UNVERIFIED by reconciliation.
Readiness evidence¶
--subject-commit is mandatory for readiness commands. The default provenance
verifier denies authority. Fixture, local, stale, forged, legacy, malformed,
self-described, or unauthenticated producer evidence is UNVERIFIED; verified
failure is FAIL; only a verified exact-subject pass is PASS. A domain with
no authenticated producer cannot produce a production PASS.
Report pairs use staged writes, file fsync, atomic replace, parent-directory
fsync, and a recovery journal. Process crash and retry preserve truthful status;
host power-loss durability remains UNVERIFIED until separately certified.
The pair identity is (output_directory, report_kind, subject_commit_sha) and a
non-blocking repository-confined lock serializes the same output directory.
Different subjects cannot overwrite one another; an existing committed pair for
the same subject is an idempotent no-op only when both digests match, otherwise
it is READINESS_OUTPUT_CONFLICT. The closed
dpone.readiness-report-transaction.v1 journal has subject, intended JSON and
Markdown paths/digests, staged paths/digests, and for each existing member a
confined durable prior-byte backup path plus its digest (or explicit null for
an absent prior member). It has state PREPARED, JSON_REPLACED,
PAIR_REPLACED, or COMMITTED. Digests are verification data and are never
treated as reconstructable prior content. The implementation reuses
dpone.manifest.confined_mutations and its compare-and-exchange/recovery
primitives rather than creating a second pathname mutation boundary. Before
replacement it fsyncs both intended files, the retained prior-byte locations,
the journal, and their parent. Replacing an existing member uses confined
atomic exchange so the displaced prior bytes remain at the journal-bound backup
path; replacing an absent member uses a confined create/rename recorded as such.
The commit point is the parent-directory fsync after both replacements and
before prior backups and the journal are removed last. While a journal exists,
consumers treat the pair as UNVERIFIED, even if both public files look valid.
Retry first authenticates the journal, confined paths, and current files.
PREPARED, JSON_REPLACED, and PAIR_REPLACED also require every prior-byte
backup needed for rollback to be present and digest-valid. PREPARED
restores/cleans only from those exact bytes; JSON_REPLACED restores the prior
JSON bytes or rolls forward only when the staged Markdown and both intended
digests are exact; PAIR_REPLACED fsyncs and commits when both current digests
match, otherwise restores both members from the journal-bound prior bytes (or
removes a member whose prior state was absent).
COMMITTED revalidates the exact intended public pair first; that pair is now
the authority and no rollback is allowed. Each valid present prior backup is
deleted idempotently, while an absent backup means that exact cleanup step
already completed. A present backup with the wrong digest is UNVERIFIED and
is preserved. After each deletion the parent is fsynced; after all backups are
absent, the journal is removed and the parent fsynced last. Thus a crash between
either backup deletion and journal removal resumes cleanup without inventing a
missing-evidence failure. Public-pair digest mismatch remains UNVERIFIED and
preserves all remaining pair/journal/backup bytes for investigation.
Focused recovery tests inject a crash after each COMMITTED backup deletion and
prove that replay only completes idempotent cleanup.
Cleanup failure leaves the valid pair but returns non-PASS until an exact retry
completes backup and journal removal.
Missing/torn/foreign/symlinked state is UNVERIFIED and preserves all bytes for
recovery. Failures at create, write, file fsync, replace, directory fsync,
journal update, or cleanup return non-PASS, never expose a mixed pair as
committed, and make an exact retry idempotent. PR 6 freezes CLI streams/exits,
24-hour evidence freshness, five-minute future-clock skew, and these schemas in
focused PR 6B public-contract tests before implementation handoff.
Read-only CI shadow observation overlay v2¶
PR 7 introduces a non-authoritative dormant observation/target overlay, not a
second production policy, not the release-centric
dpone.github-governance-policy.v2 governed by ADR 0028, and not the active
branch-protection v1 file. Its closed fixture/input schema is
dpone.ci-shadow-governance-policy.v2, but no .agents/policy production file
or active consumer is created.
The contract supports only load, resolve, live GET, local snapshot, dry-run
diff, and receipt. No --apply, restore, write credential,
Administration: write, other admin-write credential, or mutation transport
exists. A least-privilege Administration: read credential may be injected
only for the classic-protection GET; if it is absent or denied, that live field
and the observation are UNVERIFIED, never empty or passing. This goal does not
provision that optional credential, and PR 4C never consumes it. Its planning
projections are not active protection:
legacy_sourcecontains only the canonical active v1 path, schema version, and SHA-256;governance_policy_access.pyresolves the required context names from those exact bytes and the overlay never copies that list. Active v1 does not encode provider/App bindings and must never be described as if it did;legacy_expected_provideris a separate versioned, non-authoritative observation expectation in the dormant overlay:app_id=15368, observed 2026-08-08 from ruleset18806829, with source snapshot digest and timestamp. Live GET independently reads every ruleset/classic binding and compares it with that expectation. Missing/mixed provider IDs or inability to read them isUNVERIFIED; the expected value neither mutates nor authorizes protection;unionis dormant and contains legacy plusPR Gate shadow;final.enabled=false,context=PR Gate,app_id=null,receipt=null.
Even dry-run final resolution fails for missing/stale receipt, App ID 15368,
non-positive App ID, or context/App/subject identity mismatch. A disabled final
record is negative design evidence, not an activation placeholder. Existing
legacy workflows stay unconditional. Moving any projection into the active
branch/release governance policy is a different approved ADR 0028 atomic
objective; the overlay must be absorbed or deleted rather than become a second
authority.
The planned read-only entry point is
uv run python tools/agent_policy/ci_shadow_observer.py. It requires
--legacy-policy, --overlay, and exactly one of --live-read or
--snapshot-in; accepts --snapshot-out, --receipt-out, and
--format text|json; and has no apply/restore option or write client. JSON goes
to stdout with empty stderr; text is bounded stdout. Exit 0 means a complete
valid no-drift observation, exit 1 means complete drift/blockers, and exit 2
means configuration/API/permission/schema uncertainty. Local snapshots and
receipts use closed dpone.ci-shadow-governance-snapshot.v1 and
dpone.ci-shadow-governance-receipt.v1, bind source digests and observation
time, and are staged/fsynced/atomically replaced. Missing classic-protection
permission is UNVERIFIED/exit 2, never an empty or passing projection.
Compatibility and migration¶
- The frozen PR #511 implementation, checks, and artifacts are historical and non-authoritative.
- The unmerged prototype name
PR Gatehas no compatibility entitlement and is replaced byPR Gate shadowbefore child implementation. - Existing maturity payloads remain readable only for diagnosis after PR 6;
they cannot authorize
PASS. - Existing canonical active context names, App bindings, required status, strict protection, and unconditional availability remain unchanged by this goal. Enumerated non-release workflow hygiene may change workflow bytes without changing that authority projection.
- PR 2 changes design documentation only. Each later public artifact/CLI/schema change carries its own migration tests, documentation, and task contract.
Detailed algorithm¶
Shadow producer and evaluator¶
- Treat event/REST
run.head_shaasH, never asM, and treat current PR association fields as diagnostic only. A checkout-free static collector in the approved workflow copies only direct GitHub event contexts into claims: repository IDs, PR number, base/head refs,B,H, andGITHUB_SHA=M. The trusted auditor later requires the still-current GitHub-ownedrefs/pull/<pr_number>/mergeto equal claimedM, derives exact ordered parentsB/H, and rejects an ambiguous twin PR. Prove the producer workflow path/mode/blob and collector are identical/approved inB/H/M, then acquire a rename-disabledB..Hdiff. - In every product/control job, checkout the exact event head repository at
immutable
H, disable credential persistence and submodules, and prove the cleanH/subject-tree identity before executing repository bytes. - Parse root configuration semantically where path alone is insufficient.
- Classify every path; fail closed on invalid input and select full on unknown.
- Emit canonical plan bytes and digest before scheduling product jobs.
- Run selected jobs with the documented parallelism limits and
strategy.fail-fast: falsefor every evidence matrix. - Let every required product command fail its job natively; step-level and
job-level
continue-on-errorare forbidden for product work, and no later same-runner step is trusted to enforce status. - A separate fresh always-existing claims aggregator compares every
needsresult with the plan, pages its own exact-attempt Jobs API for matrix cases, directly uploads claims without overwriting history, and is the solePR Gate shadowcontext. No post-product same-runner enforcement exists.
Trusted audit¶
Every require_*/observe_* operation below returns the closed decision/blocker
algebra rather than raising past receipt construction. On any terminal blocker,
the algorithm skips unavailable later phases, records their nullable outputs,
persists UNVERIFIED, and exits nonzero. Only receipt persistence failure may
leave the attempt without a receipt.
event = require_workflow_run_completed()
trusted_revision = require_default_branch_revision(GITHUB_SHA)
run = api.get_run(event.workflow_run.id)
require_exact_run_identity(run, event)
artifact = api.select_one_exact_artifact(run, expected_attempt_name)
require_transport_metadata(artifact)
bytes = download_by_id_to_runner_temp(artifact)
claims = parse_closed_json(bytes, max_bytes=1_MiB, reject_duplicates=true)
require_run_and_head_claims(claims, run)
initial_snapshot = observe_stable_current_pr_snapshot(
api,
git_refs,
claims,
run,
max_observations=5,
timeout_seconds=30,
)
# Each internal observation freshly and completely pages the open-PR set,
# requires one unique eligible tuple, polls GET /pulls/N, resolves the ref, and
# returns the tuple, ordered parents, and eligible-set digest.
require_equal(initial_snapshot.merge_ref_oid, claims.merge_sha)
merge_commit = api.get_commit(initial_snapshot.merge_ref_oid)
base_sha, head_sha = require_exact_two_parents(merge_commit, run.head_sha)
require_equal(base_sha, claims.base_sha)
base_tree = api.walk_complete_git_tree(base_sha)
head_tree = api.walk_complete_git_tree(head_sha)
merge_tree = api.walk_complete_git_tree(initial_snapshot.merge_ref_oid)
workflow_blob = require_same_workflow_blob(base_tree, head_tree, merge_tree, run.path)
diff = trusted_rename_disabled_tree_diff(base_tree, head_tree)
config_blobs = api.read_bounded_git_blobs(diff.required_config_oids)
trusted_plan = recompute_plan_with_default_branch_code(diff, config_blobs)
require_every_job_checks_out_exact_subject_head(run, head_sha, head_tree.oid)
producer_bundle = recompute_subject_closure(workflow_blob, head_tree)
require_bundle_equal_trusted_manifest(producer_bundle, trusted_revision)
attempt_jobs = api.page_attempt_jobs(run.id, run.run_attempt)
require_exact_job_and_matrix_set(trusted_plan, attempt_jobs)
provider_decision = evaluate_provider_conclusions(trusted_plan, attempt_jobs)
if provider_decision == FAIL:
decision = FAIL # provider failure is authoritative; embedded status is ignored
elif provider_decision == UNVERIFIED:
decision = UNVERIFIED # cancellation/timeout/API uncertainty
else:
require_claims_equal_api_identity(claims, run, initial_snapshot, base_sha, head_sha)
decision = evaluate_success_claims(trusted_plan, attempt_jobs, claims)
final_snapshot = observe_stable_current_pr_snapshot(
api,
git_refs,
claims,
run,
max_observations=5,
timeout_seconds=30,
)
if not same_eligible_set_pr_ref_and_parents(initial_snapshot, final_snapshot, base_sha, head_sha):
decision = UNVERIFIED
blockers.add(AUDITOR_MERGE_REF_UNVERIFIED)
persist_attempt_bound_audit_receipt(decision)
exit_nonzero_unless_verified_pass(decision)
PR 4C calibration-probe command¶
The separately approved calibration child freezes this source-free, default-branch-only invocation:
uv run python tools/ci/calibrate_pr_gate_shadow_capacity.py \
--event <trusted-event.json> \
--output <capacity.json>
The composition root injects the same canonical observation policy and bounded
read-only GitHub ports planned for reconciliation. It enforces the parent hard
maxima before every dispatch/read, performs no mutation, and renders only the
closed capacity schema. Exit 1 is the expected successful creation of a
schema-valid UNVERIFIED capacity observation (qualifying or blocked);
exit 2 is invalid input on stderr with no file, and exit 70 is a bounded
redacted operational failure with no qualifying artifact. The probe has no exit
0, no daily-root PASS, and no authority to approve limits by itself.
PR 4C reconciler composition command¶
The later thin default-branch adapter has one public invocation and no external policy file argument:
uv run python tools/ci/reconcile_pr_gate_shadow.py \
--event <trusted-event.json> \
--output <reconciliation.json>
The composition root constructs the trusted clock, canonical
ReconciliationPolicyV1, and read-only GitHub ports; calls one stateless
service; and writes one observer-attempt-bound report. Exit 0 means a
schema-valid report with root decision PASS; exit 1 means a schema-valid
FAIL|UNVERIFIED report; exit 2 is parser/event-contract usage on stderr
with empty stdout; exit 70 is a bounded redacted unexpected operational
error with no certified output.
The child matrix freezes stdout/stderr and file existence for invalid event, existing identical/different target, symlink target, provider-shape error, resource stop, stage/write/fsync/rename/parent-fsync failure, and unexpected exception. Identical committed bytes are idempotent success; every other identity or write ambiguity is fail-closed. Identical bytes become success only after current descriptor-pinned identity checks and current file/parent fsync.
Reconciliation¶
- Instantiate the exact child-owned
ReconciliationPolicyV1; the design fixture is test input only and cannot be loaded by the CLI. - Floor trusted observation time to a UTC second and compute the closed exact 14-day interval ending at the 30-minute grace bound.
- Perform two consecutive complete whole-window observations. Each builds the
full deterministic partition tree, uses singleton children for adjacent
seconds and overlapping-midpoint children for wider slices, and pages every
leaf under every global request/record/byte/time budget. Each pass also
acquires every producer/auditor attempt record, exact-attempt Jobs page,
artifact inventory, bounded selected artifact bytes, and exact producer-run
lookup for every auditor record. Require identical
topology, canonical records, downloaded bytes, and all response/content
digests; retain pass-two bytes and record its completion as
evidence_observed_through. - Without further provider reads, classify every captured auditor record by the
exact producer run's authenticated
created_at: old producer reruns are diagnostic, in-window producers evaluate normally, grace-window producers defer, future producers block, and missing/foreign/query-omitted identity isUNVERIFIED. Then evaluate every in-window producer attempt and exact-attempt Jobs result. For each captured auditor run, require its highest observed attempt to be terminal; any higher nonterminal attempt isUNVERIFIED. - From the captured snapshot, authenticate artifact transport identity and
source-specific upload authority: producer workflow bytes in
B/H/M, and allowlisted default-branch workflow identity/revision for auditor and reconciler. Require explicitretention-days: 90; treatexpires_atonly as an availability deadline and require bytes to remain present and unexpired. - Produce coverage status, per-attempt product status, and root
UNVERIFIED > FAIL > PASSfor only this interval. A canary visible inside the interval folds as an ordinary attempt; its separate rollout oracle never excludes or repaints it. - Render bounded direct JSON without a self-digest, commit through the exact
DescriptorPinnedCreateOnlyEvidenceWriter, and require a current file plus parent fsync even for an identical retry target. Then upload withretention-days: 90,archive: false, andoverwrite: false.
A transient retry repeats the complete interval in a new observer attempt. No cursor, prior report, predecessor, checkpoint, or partial-progress state is read or promoted.
Compatibility and readiness¶
- Producer A builds one candidate inventory and uploads immutable bytes.
- Producer A terminates; no same-run verifier can create verified evidence.
- Trusted Workflow B authenticates producer/run/artifact metadata.
- The ephemeral cacheless executor verifies embedded SHA and executes the full compatibility profile.
- Trusted evaluator classifies provenance, compatibility, and evidence using the closed taxonomy.
- Readiness consumes the exact verifier receipt for the requested subject.
- Default-deny provenance and crash-safe report publication precede exit.
State machines¶
stateDiagram-v2
[*] --> Planned
Planned --> Running
Running --> ProducerCompleted: success/failure/cancel/timeout
ProducerCompleted --> AuditTriggered
AuditTriggered --> SourceAuthenticated
SourceAuthenticated --> PlanRecomputed
PlanRecomputed --> AttemptJobsBound
AttemptJobsBound --> ArtifactBound: eligible artifact exists
AttemptJobsBound --> Unverified: identity/API/cancel/timeout uncertainty
ArtifactBound --> Verified
ArtifactBound --> VerifiedFailure: authenticated product/contract failure
ArtifactBound --> Unverified
Verified --> Reconciled
VerifiedFailure --> Reconciled
Unverified --> Reconciled: later exact-input attempt
Reconciled --> Terminal
Terminal --> [*]
flowchart LR
P["Untrusted PR-head product jobs"] --> J["Direct closed JSON claims"]
J --> S["Diagnostic PR Gate shadow"]
J --> A["Trusted default-branch data-only auditor"]
G["GitHub REST transport identity"] --> A
F["GitHub-owned pull-request merge ref"] --> A
B["Approved transitive bundle manifest"] --> A
A --> R["Immutable audit receipt"]
R --> O["Acceptance telemetry and reconciliation"]
L["Legacy 19 checks"] --> M["Only merge authority"]
Failure codes and recovery¶
Stable families distinguish IDENTITY_*, ARTIFACT_*, JSON_*,
PLAN_*, JOB_*, AUDITOR_*, RECONCILIATION_*, API_*, and
PROVENANCE_*. At minimum, missing/expired/duplicate/ambiguous artifacts,
foreign repository/head/workflow/run/attempt, size/digest mismatch, malformed
or extra JSON, missing/nonterminal/cancelled/timed-out/action-required/stale/
unexpectedly-skipped jobs,
superseded head, API/permission unavailable, and contradictory receipts have
distinct codes.
The v1 stable code set is:
| Family | Codes | Decision/recovery |
|---|---|---|
| identity | IDENTITY_REPOSITORY_MISMATCH, IDENTITY_HEAD_REPOSITORY_MISMATCH, IDENTITY_PR_MISMATCH, IDENTITY_BASE_SHA_MISMATCH, IDENTITY_HEAD_SHA_MISMATCH, IDENTITY_MERGE_SHA_MISMATCH, IDENTITY_MERGE_PARENT_MISMATCH, IDENTITY_WORKFLOW_MISMATCH, IDENTITY_RUN_ATTEMPT_MISMATCH, IDENTITY_SUPERSEDED_HEAD |
UNVERIFIED; retry only an unchanged complete tuple, otherwise create a fresh eligible event/PR, never relabel |
| artifact | ARTIFACT_MISSING, ARTIFACT_DUPLICATE, ARTIFACT_AMBIGUOUS, ARTIFACT_EXPIRED, ARTIFACT_DIGEST_MISMATCH, ARTIFACT_SIZE_MISMATCH |
UNVERIFIED; create a complete new producer attempt |
| JSON | JSON_INVALID_UTF8, JSON_DUPLICATE_KEY, JSON_SCHEMA_INVALID, JSON_LIMIT_EXCEEDED |
UNVERIFIED; fix producer/schema and create a new attempt |
| plan | PLAN_DIFF_UNAVAILABLE, PLAN_POLICY_INVALID, PLAN_DIGEST_MISMATCH, PLAN_JOB_SET_MISMATCH |
uncertainty is UNVERIFIED; authenticated contract mismatch is FAIL |
| job | JOB_MISSING, JOB_NONTERMINAL, JOB_SKIPPED, JOB_CANCELLED, JOB_TIMED_OUT, JOB_ACTION_REQUIRED, JOB_STALE, JOB_UNEXPECTED, JOB_FAILED |
missing/nonterminal/skipped/cancelled/timed-out/action-required/stale required evidence is UNVERIFIED; with a complete uncertainty-free case set, unexpected or failed work is FAIL |
| auditor | AUDITOR_SOURCE_UNTRUSTED, AUDITOR_MERGE_REF_UNVERIFIED, AUDITOR_WORKFLOW_SOURCE_UNVERIFIED, AUDITOR_BUNDLE_UNAPPROVED, AUDITOR_RECEIPT_PERSIST_FAILED |
UNVERIFIED; source/storage may rerun the auditor, but moved merge identity needs a fresh PR event/run and closed/merged identity is terminal |
| reconciliation | RECONCILIATION_MISSING_RECEIPT, RECONCILIATION_DUPLICATE_TRIGGER, RECONCILIATION_CONTRADICTION, RECONCILIATION_RETENTION_HISTORY_LOST, RECONCILIATION_RESOURCE_LIMIT, RECONCILIATION_PRODUCER_QUERY_OMISSION, RECONCILIATION_PRODUCER_IDENTITY_UNRESOLVABLE, RECONCILIATION_FUTURE_PRODUCER, RECONCILIATION_CAPACITY_CALIBRATION_ONLY, RECONCILIATION_PR4C_IMPLEMENTATION_BLOCKED |
UNVERIFIED; a query omission, unresolved identity, or future producer blocks the current report; calibration observations never certify a root and incomplete calibration blocks the PR 4C reconciler child; transient API/wall-time failure may retry, but history loss blocks every overlapping interval and deterministic caps wait for a clean window or approved parent amendment; a fresh attempt can establish new exact-head evidence but cannot repair an overlapping daily root; no history is relabelled and no state/cursor/prior report is repaired |
| API | API_UNAVAILABLE, API_PERMISSION_DENIED, API_PAGINATION_INCOMPLETE |
UNVERIFIED; restore read access/service, then rerun auditor/reconciler |
| provenance | PROVENANCE_NON_ALLOWLISTED, PROVENANCE_SAME_RUN, PROVENANCE_SUBJECT_MISMATCH, PROVENANCE_APPROVAL_PENDING, PROVENANCE_PR_CLOSED |
UNVERIFIED; pending approval is telemetry only; audit only independently authenticated post-start completion; ambiguous lifecycle requires a fresh eligible event/head |
blockers is sorted by (code, subject); each item has only code, bounded
repo-relative/identity subject, and recovery_command_id. Free-form exception
text, absolute runner paths, tokens, URLs with credentials, and arbitrary shell
reproduction strings are forbidden from machine receipts. Trusted documentation
maps each command ID to a copyable safe recovery.
RECONCILIATION_RETRY means restore ordinary read availability and rerun the
complete stateless interval. RECONCILIATION_WAIT_FOR_CLEAN_WINDOW means keep
the immutable non-PASS report and wait until a later exact interval excludes
the lost attempt or deterministic over-limit density. A fresh auditable attempt
may establish new exact-head evidence but does not repair that daily interval.
A later PASS has different bounds and never repairs or relabels the older
report. A limit change requires an approved child-contract amendment; neither
recovery authorizes settings mutation, state repair, re-genesis, or receipt
rewriting.
CREATE_NEW_PRODUCER_RUN is the only recovery for
OUT_OF_SCOPE_OLD_PRODUCER_RERUN; rerunning only its auditor cannot move the
producer into the current interval. VERIFY_PRODUCER_IDENTITY_OR_CREATE_NEW_RUN
checks the exact repository/workflow/event/run identity and creates a new
eligible producer only when the old identity cannot be resolved.
PR4C_CALIBRATE_CAPACITY invokes the approved read-only probe and emits the
capacity evidence required to clear
RECONCILIATION_PR4C_IMPLEMENTATION_BLOCKED before reconciler-child approval.
The decision fold applies the matrix uncertainty precedence before evaluating
JOB_UNEXPECTED or JOB_FAILED; code ordering cannot downgrade UNVERIFIED
to FAIL or promote either state to PASS.
- Source failure: push a fix and use the new head.
- Transient failure while the complete PR/base/head/merge tuple is unchanged: rerun the whole producer, creating a new attempt.
- Missing/expired/ambiguous evidence: create a complete new attempt for new exact-head evidence; do not rename or copy evidence, and do not treat it as repair of an overlapping reconciliation interval.
- Auditor source/API failure: reconcile or rerun the auditor for the exact immutable producer coordinates.
- Moved merge ref on an open PR: create a fresh PR event/run; do not rerun the
stale producer event. Closed/merged PR identity is terminal
UNVERIFIEDand can be replaced only by evidence from a new corrective PR. - Superseded head: stop; prior-head evidence cannot be promoted.
- Shadow/legacy disagreement: retain both, investigate, and do not bypass or mutate protection.
Edge cases¶
- Empty PR diff is invalid; an explicitly defined initial push may select full.
- Duplicate paths normalize before digesting; duplicate JSON keys reject.
- A planned non-applicable job is
N/A; an expected job that is skipped yieldsUNVERIFIEDand blocksPASS. - Cancellation, timeout, provider/API uncertainty, missing audit, or ambiguous
identity never becomes
PASS. - Auditor rerun does not overwrite the prior attempt.
- Artifact URL expiry does not affect identity; URL is never authorization.
- Queue and concurrency state are telemetry, not durable state.
- A provider-owned merge ref authenticates only the current merge snapshot; independent plan, workflow, job, and artifact validation still decides the audit.
Architecture¶
Components and responsibilities¶
| Component | Existing/new | Responsibility | Dependencies |
|---|---|---|---|
| Route classifier | new | Exact diff and semantic config to canonical plan | standard library plus injected Git adapter |
| Gate evaluator | new | Closed result algebra and producer claims | CI contracts only |
| Shadow workflow | new | Untrusted product/claims and one final diagnostic context | classifier/evaluator, pinned read-only actions |
| Bundle closure validator | new | Transitive local/external trust manifest | AST and closed policy |
| Shadow auditor | new | API/Git-ref preflight, data-only parse, audit receipt | GitHub REST/Git-object adapters, trusted default branch |
| Stateless reconciler | new | Complete bounded interval scan and one report | read-only GitHub ports, trusted clock, ReconciliationPolicyV1, create-only evidence-writer port |
| Candidate producer | new | Immutable build/inventory | packaging tooling |
| Compatibility verifier | new | Completed-producer provenance and case evidence | read-only API plus ephemeral executor |
| Readiness contract/service | new | Exact-subject default-deny decision | injected provenance verifier and clock |
| CI shadow policy observer | new | Optional dormant GET-only drift telemetry | read-only GitHub API |
Domain and contract code must not depend on GitHub clients. Closed identities,
schemas, result algebra, and state transitions live in cohesive
dpone.contracts.ci_shadow_* modules; orchestration lives under
dpone.services.ci.shadow_*; provider protocols live under
dpone.ports.github_ci_shadow; and REST/Git-object implementations live under
dpone.adapters.github_ci_shadow_*. Exact filenames are frozen by each child
specification before implementation and must respect module-size budgets.
.github/workflows/** and tools/ci/** are thin declarative/CLI composition
roots only: they parse arguments/events, construct injected ports/adapters, call
one service, render the closed result, and contain no routing, evidence,
reconciliation, or status-fold policy. Dependencies are injected; no import-time
I/O, service locator, or vendor SDK exists on base import/help paths.
New components reuse or narrowly adapt existing bounded primitives instead of
creating parallel authorities: artifact limits from
tools/agent_policy/artifact_resource_limits.py; provenance/API codecs from
pr_receipt_source.py and pr_receipt_github_metadata.py; canonical active policy access from
governance_policy_access.py; and GET-only ruleset/classic readback from
github_settings_drift.py. Pure CI contracts do not import those
GitHub adapters; thin composition roots inject them through canonical ports.
PR 7 adds projections and codecs, not a second GitHub client, policy store, or
live-state authority.
Trust boundaries¶
Product, control, and claims jobs are untrusted and receive no secrets, write
scopes, OIDC, environment authority, or trusted cache. No PR-head shadow job
creates an attestation. The native
PR Gate shadow result is diagnostic producer output, not verified evidence.
The auditor is trusted because its code revision comes from the default branch,
but it is still configured with only contents: read, actions: read, and
pull-requests: read, no
secrets, cache, PR checkout, content execution, or
check publication. Its
default-branch workflow context is not a PR-head result and is never used as
one. The candidate executor is untrusted, ephemeral, cacheless, and
network-isolated from private resources. Policy observation uses read-only
credentials and exposes no mutation method.
The reconciler is a separate source-free trusted default-branch job. It never
checks out a PR, executes subject content, or receives repository-admin
authority. Its exact allowlist is contents: read, actions: read, and
pull-requests: read. Artifact payloads are bounded data. Repository retention
settings, if later observed by PR 7, are optional telemetry and cannot change a
PR 4C result.
Alternatives and tradeoffs¶
| Alternative | Advantages | Disadvantages | Decision |
|---|---|---|---|
| Reuse frozen #511 workflow | Existing prototype | Wrong authority name, same-run verification, mutation/release scope | rejected |
| Workflow-level path filters | Fewer runs | Required/diagnostic context may be absent or pending | rejected |
| Cross-workflow polling gate | Flexible graph | Race-prone list-order selection and missing-run ambiguity | rejected |
| Trusted auditor executes producer code | Easy reuse | workflow_run privilege escalation and cache poisoning risk |
rejected |
| ZIP machine evidence | Existing action default | Adds trusted archive-parser surface | rejected for shadow JSON |
| One unified governance v2 | One policy | Conflicts with ADR 0028 release boundary and this no-release goal | rejected in this goal |
| Dormant observation overlay | No active migration; derives legacy from canonical v1 | Future cutover must absorb/delete it through ADR 0028 | accepted |
ADR requirement¶
- ADR 0046 owns the diagnostic shadow context and future App authority separation.
- ADR 0048 owns exact-SHA, data-only, attempt-bound evidence and default-deny verification.
- ADR 0028 remains the untouched release/publication authority.
- ADR 0037 supplies exact-head repository approval evidence.
- ADR 0047 is the completed baseline prerequisite.
Quality-budget impact¶
New production Python modules target at most 350 SLOC and never exceed the
400-SLOC hard limit from docs/benchmarks/quality_budgets.yml. Parsing,
policy, adapters, rendering, and workflow composition remain separate cohesive
responsibilities. Every new dependency edge is validated against import, layer,
and clustering budgets; no copied budget constants become authority.
Market and platform comparison¶
Checked 2026-08-09 against official primary sources.
| System/version | Capability | Observed design | Strength | Limitation | Adopt | Reject | Source/date |
|---|---|---|---|---|---|---|---|
| GitHub Actions, hosted service as documented 2026-08-09 | completed-workflow trust boundary | workflow_run executes from default branch, can receive secrets/write token, triggers regardless of producer conclusion, and warns against untrusted content |
trusted downstream source can verify an untrusted producer | downstream privilege and chain depth expand blast radius | data-only trusted preflight/evaluation, one-hop cap, and a separate zero-secret/read-only hosted executor where compatibility requires candidate execution | content execution in trusted/privileged jobs and downstream chaining | GitHub events documentation, 2026-08-09 |
| GitHub rulesets, hosted service as documented 2026-08-09 | required-check producer authority | a required check can select one expected GitHub App source | binds a familiar context to a specific producer identity | needs separately administered App lifecycle and live mutation | reserve context + app_id for the future authority |
same-name Actions context as authority | GitHub ruleset rules, 2026-08-09 |
| GitHub Actions concurrency, hosted service as documented 2026-08-09 | pending-run behavior | queue: max preserves up to 100 pending runs and cannot combine with cancel-in-progress: true |
explicit cancellation versus retention semantics | concurrency is not a durable lock or evidence ledger | cancel stale PR heads; queue non-release nightly work | using concurrency identity as evidence identity | GitHub concurrency documentation, 2026-08-09 |
| GitHub pull-request merge refs, hosted service as documented 2026-08-09 | synthetic merge identity | pull-request workflows use refs/pull/<N>/merge; GITHUB_SHA is the last merge commit, GitHub creates a temporary simulated-merge ref, and the refs/pull/ namespace is read-only |
a read-only auditor can bind current provider merge bytes without granting PR-head write/OIDC authority | the temporary ref can be stale, move, or disappear after update/merge, so an old event cannot be recovered | bounded-poll exact PR mergeability, require two stable PR/ref observations, claimed M, ordered parents (B,H), identical workflow blob in B/H/M, and final revalidation |
mutable current PR association SHA fields, local recomputation of historical M, or retroactive relabelling |
GitHub pull-request event documentation, GitHub pull-request refs, GitHub pull-request REST contract, read-only refs/pull/ namespace, 2026-08-09 |
actions/upload-artifact@v7 |
direct machine evidence | archive: false uploads one file, uses its filename as artifact name, and exposes artifact ID/digest after upload |
removes trusted ZIP parsing and exposes provider identity | ID/digest cannot self-appear in the uploaded bytes | direct JSON and no self-reference | overwrite and self-reported transport identity | upload-artifact v7 source, 2026-08-09 |
| GitHub Actions artifact REST API | exact artifact retrieval | exposes artifact ID/run/repository/digest/size and redirect download | exact provider-ID selection with a project-owned bounded streaming reader | downloaded content remains untrusted; redirects and bytes require metering | ID/API preflight and create-new unprivileged download | artifact-name/list-order authority or unbounded action download | official artifact REST documentation, 2026-08-30 |
| GitHub Dependabot, hosted service as documented 2026-08-09 | uv dependency updates |
the uv package ecosystem is supported in dependabot.yml |
native lock-aware update PRs and schedule/label controls | update PR success does not certify project compatibility | separate bounded uv and Actions schedules |
unlocked CI resolution as evidence | Dependabot options reference, 2026-08-09 |
| Astronomer Cosmos, current hosted documentation 2026-08-09 | Airflow/dbt version compatibility | documents runtime compatibility rather than repository check authority | relevant compatibility-matrix input | no authority over this repository's checks | version-policy evidence only | use as check producer authority | Cosmos documentation, 2026-08-09 |
dlt, Informatica, Airbyte, Fivetran, Pentaho, Microsoft SSIS, Apache Beam,
and gusty are N/A: they provide data execution, orchestration, or DAG-authoring
capabilities, not GitHub check-producer identity or this repository's branch
authority layer.
Measurable differentiation¶
axis: pull-request runner cost without loss of selected compatibility evidence
scenario: docs-only and uv.lock-only pull requests under legacy coexistence
baseline: same-SHA canonical-required-check job timestamps captured beside each canary; PR 496 is context telemetry only
metric: runner_minutes and execution_wall_seconds
target: docs <=5 runner-minutes and <=6 minutes; uv.lock <=38 runner-minutes and <=18 minutes
procedure: evaluate each completed non-cancelled producer attempt after audit/reconciliation and record the exact same-SHA legacy run/job IDs separately
artifact: test_artifacts/ci/metrics/<window>/summary.json
limitations: does not prove production authority, vendor-live correctness, publication safety, or all-repository performance
Metrics are:
queue = run_started - run_created
execution_wall = run_completed - run_started
end_to_end = run_completed - run_created
runner_minutes = sum((job_completed_utc - job_started_utc).total_seconds()) / 60, including time before cancellation
flake = unchanged-SHA FAIL -> PASS, excluding deliberate canary, cancellation, and documented infrastructure outage
escaped_regression = shadow PASS with deterministic legacy FAIL on the same SHA, or a post-merge defect the selected route was required to detect
Queue, end-to-end, first signal, and repository-global fanout are coexistence telemetry rather than closure SLOs.
The SLO sampling unit is one exact completed attempt of
.github/workflows/pr-gate-shadow.yml. runner_minutes sums only started jobs
in that producer attempt, and execution_wall uses that producer run's own
timestamps. Legacy workflows, the later workflow_run auditor, daily
reconciliation, retries, and other PRs are not charged to that per-producer SLO;
they are reported separately as contour/coexistence telemetry. Every eligible
docs-only and uv.lock-only canary sample must meet the threshold—there is no
averaging or percentile that can hide a slow run. Cancelled attempts still
contribute their consumed runner minutes to cancellation/cost telemetry and the
burst acceptance, but are not relabelled as completed SLO samples. The metrics
artifact lists every included and excluded run/attempt with its reason and never
combines producer, legacy, auditor, or repository-global timestamps.
The thresholds are absolute. A differentiation report may compare the producer sample with the exact same-SHA legacy check runs by applying the same started/completed job formula to a separately listed legacy run/job set. It may not use PR #496 or frozen #511 as a numeric baseline, mix their historical runner fleet/timing with the canary, or subtract legacy work from shadow cost. The metrics artifact records all provider run/job IDs, attempts, timestamps, formula version, and raw/derived values needed to reproduce either series.
Security, privacy, and operations¶
Semantic privilege boundary¶
A PR-reachable job is privileged when any effective top-level or job-level
permission is write, permissions: write-all is used, id-token: write is
used, or an unknown permission is set to write. Inheritance is resolved before
classification. read-all is forbidden in security-sensitive jobs as a
least-privilege violation.
The PR-reachability graph starts at pull_request and pull_request_target,
follows local workflow_call edges and workflow_run edges from reachable
producers, and resolves reusable workflows transitively. pull_request_target
is forbidden for this goal. Unknown/dynamic edges are privileged fail-closed.
The only general PR-head privileged profiles are closed CodeQL and the exact
source-free ADR0037_GOVERNANCE_SOURCE_ATTESTOR finalizer. PR 3B retires the
legacy .github/workflows/ci.yml job-wide exception: quality and the single
governance artifact producer are read-only, while the action-only finalizer
downloads the exact provider artifact as data and attests its bytes without
checkout, run, installation, cache, local action, secret, or environment.
Current legacy bytes are a deterministic RED fixture until that atomic split is
implemented. Every mandatory profile closes its complete normalized trigger,
single subject occurrence, and exact route-coordinate multiset; missing,
duplicate, copied, or trigger-drifted profiles fail.
As before, any change/retirement requires a prior approved ADR 0037 amendment;
the paired PR3B amendment supplies that authority before implementation.
No PR-head shadow job may receive write, OIDC, attestation, secret, or
environment authority. The CodeQL profile closes workflow path, job ID, trigger set, exact
permissions, pinned action SHA, and allowed inputs. Arbitrary run,
github-script, local/Docker actions, unpinned actions,
command/script/entrypoint/args inputs, package installation, and execution of
downloaded artifacts are forbidden.
ADR 0037's existing merged-closure-check-publisher remains a separate exact
capability: only .github/workflows/agent-pr-receipt.yml, only its closed job,
only after pull_request event closed with merged == true, only integrated
default-branch commit code, and only its exact checks: write publication. It
is not a PR-head execution profile and PR 3B must preserve its action/input and
permission allowlist unchanged. No generic or new check-publisher profile is
introduced by this goal.
The CodeQL profile permits checkout with persist-credentials: false, pinned
init and analyze actions, and no autobuild, repository build scripts, custom
config/query packs, dependency installation, or local queries/actions. The
finalizer remains outside the shadow trust graph: its result cannot
authenticate N/B/H/M or satisfy an audit phase, and attestation provenance is
not semantic PASS. ADR 0037's merged-closure publisher is retained only as an
existing post-merge drift-detection capability; it is not permission to add a
new publisher. No other attestation or check-publisher profile is introduced.
Concurrency and resource limits¶
- PR group: one pull request, stale head cancellation enabled.
- Nightly non-release group: stable group,
queue: max,cancel-in-progress: false. - Release concurrency is unchanged.
- Airflow matrix
max-parallel: 2; shadow wheel smoke1; nightly full compatibility4. - Producer/auditor JSON remains closed and bounded as specified above.
- Stateless reconciliation uses a 14-day window, 30-minute grace, 1,000 results per provider slice, partition depth 32, 4,096 producer attempts, 4,096 audit receipts, and a 1-MiB report.
- Any incomplete pagination, irreducible same-second overflow, attempt/receipt
cap, JSON limit, or report-size overflow is
UNVERIFIED; no partial scan may returnPASS.
Operational decisions¶
For audit receipts, PASS means authenticated, complete evidence. FAIL means
verified product or contract failure. UNVERIFIED means absent, ambiguous,
unauthenticated, stale, cancelled, timed-out, or unavailable proof. N/A is
only a planned non-applicable child job. Auditor FAIL or UNVERIFIED blocks
acceptance of that attempt and invalidates the evidence window, but cannot
change the already-published producer context or merge protection. A producer
PR Gate shadow PASS without a matching verified audit receipt is a critical
false-green condition, not verified evidence.
Test and certification plan¶
| Layer | Scenarios | Expected evidence |
|---|---|---|
| Unit | routing, semantic TOML diff, outcomes, taxonomy, limits, identity | focused JUnit |
| Contract | unique contexts, permissions inheritance/write-all/unknown scopes, CodeQL profile, cacheless executor, closed JSON, bundle closure | policy test report |
| Integration | direct upload/download identity, API preflight, auditor conclusion matrix, complete stateless interval scan, report recovery | attempt-bound audit and reconciliation JSON |
| Compatibility | completed allowlisted producers on two distinct SHAs, Python 3.11/3.12, eight Airflow cells, two wheel smokes | verifier receipts |
| Live shadow | eight route canaries plus burst/cancellation | producer/audit/reconciliation triplets |
| Performance | docs and lock SLOs, concurrency ceiling, telemetry | metrics JSON/Markdown |
| Security | no secret/write/cache/PR execution path; adversarial claims and artifacts | governance report |
| Documentation | contributor, auditor, policy observer, failure/recovery journeys | strict docs/MkDocs report |
Executable child-contract gate¶
This parent specification approves architecture, not underspecified public bytes. Before any child production code is written, its exact merged child specification must freeze the paths/schemas/commands below, examples, streams/exits, overwrite and retry semantics, and the listed positive/negative/boundary matrix. The child task contract cites that approved head. Missing child detail is a stop condition, not implementer discretion.
| Child | Planned executable contract | Required positive / negative / boundary / retry evidence |
|---|---|---|
| PR 3A | .github/dependabot.yml, non-release workflow diffs; actionlint plus workflow-governance tests |
exact schedules/labels/limits and locked sync / release diff detected / queue 100 and PR cancellation / rerun preserves nightly work |
| PR 3B | tools/agent_policy/workflow_security_privileged.py --root . --format json; closed profile fixture/schema; atomic ci.yml producer/finalizer migration |
current legacy quality envelope FAIL, then exact read-only governance producer plus source-free finalizer, CodeQL, and existing ADR0037 merged closure PASS / inherited unknown write, write-all, producer write/OIDC, missing/duplicate/trigger-drifted profile, any extra finalizer step/input, PR-head publisher rejected / reusable-workflow reachability and closed inputs / deterministic rescan; the approved ADR0037 retirement/replacement amendment must merge before implementation |
| PR 4A | thin tools/ci/change_plan.py and gate_evaluator.py adapters over canonical services; change-plan, producer-evidence, route-policy schemas |
each exact route map, exact-H checkout, closed product+infrastructure job sets, claims upload, final context, same-repository/fork/Dependabot read-only runs / unknown, inaccessible foreign head, changed B/H/M workflow blob, any product continue-on-error, false status / nine-selection, fail-fast-false matrix, and mixed failure/cancellation boundaries / whole producer rerun creates a new immutable attempt |
| PR 4B | thin tools/ci/audit_pr_gate_shadow.py --event <fixture> --output <receipt> adapter; audit and bundle schemas |
current merge-ref (B,H,M) PASS for same-repository/fork/Dependabot post-start completion / mutable association, moved/deleted/ambiguous ref, forged claims, foreign artifact, changed workflow source, false-green / action-required incoming event is receipt-free telemetry only and never assumes later identity continuity / all JSON, Git-ref/object, final-revalidation, and pagination limits; same auditor-run rerun supersedes earlier attempt without overwriting |
| PR 4C calibration probe | calibrate_pr_gate_shadow_capacity.py --event ... --output ...; source-free read-only workflow; closed capacity schema; canonical observation service/ports |
exact default-branch source/run/attempt plus ZIP-archive digest/size-bound external artifact transport and separate hardened single-JSON payload digest/size, policy/interval/current-observation/strict-pre-expiry freshness, matching controllable runner configuration, two equal complete observations, exact request-class/retry-subset conservation, API/artifact/total response bytes, and shared acquisition manifest/digest with separate wrapper roots, output always UNVERIFIED / missing, stale, partial, mismatched, untrusted, duplicate/extra/symlink/encrypted/traversal archive member, unsorted/duplicate/symlink/dynamic/unresolved manifest evidence; 401 requests, 64 MiB+1, 450s+epsilon / hard-stop/canonical saturated lower bounds at parent 800/128 MiB/900s, remaining+1 bytes, remaining-budget timeouts, redirects/retries counted before dispatch, create-only artifact / rerun probe under unchanged policy/shared bundle/configuration; exceeding half-max threshold or changing acquisition closure requires parent amendment/new calibration |
| PR 4C reconciler | reconcile_pr_gate_shadow.py --event ... --output ...; child-owned ReconciliationPolicyV1; dpone.pr-gate-shadow-reconciliation.v1 |
two equal whole-window observations including run/attempt, Jobs, inventory, exact producer lookup per auditor, and downloaded receipt bytes; stable empty/nonempty closed-second 14-day interval / created_at at scan_from-1s, scan_from, safe_scan_through, safe_scan_through+1s, old-producer rerun, grace producer, future/query-omitted/foreign producer with exact code/recovery, adjacent-second split, overlapping seam, changed late rerun/Jobs/artifact/page, same-second overflow, higher nonterminal auditor rerun, missing/duplicate/conflicting receipt, early deletion, source-authority/API/identity uncertainty / 999/1,000/1,001 slice results, 31/32/33 depth, 799/800/801 requests, raw record/byte/time caps, attempts and receipts 4,095/4,096/4,097, report 1 MiB, descriptor-pinned identical-target file+parent fsync, legacy route-attestation compatibility, write/no-replace and JSON limits / full stateless rerun creates a new immutable observer report; approved fresh within-threshold calibration and PR4B-safe-fallback public-fork canary precede this child, other canaries follow implementation |
| PR 5A | tools/ci/build_candidate_manifest.py --dist <path> --output <path>; candidate manifest schema |
exact three-wheel inventory / extra, duplicate, symlink, digest mismatch / size/name/cardinality limits / producer rerun creates new artifact identity |
| PR 5B | completed-producer workflow plus closed verifier receipt | allowlisted master PASS/FAIL and exact eight cells/two smokes / manual, same-run, cancelled, malformed / 3.11/3.12 and cacheless profile / full rerun, no prior-cell inheritance |
| PR 6A | dormant internal default-deny core and pair-journal transaction; no public CLI or PASS authority | exact-subject pure decisions and retained-prior-byte recovery / fixture, stale, future, forged, concurrent subject / per-stage I/O and COMMITTED-cleanup boundaries / idempotent rollback/roll-forward |
| PR 6B | public readiness CLI/schema/adapter and migration | authenticated PR 5B verifier PASS/FAIL / missing or stale producer, streams/exits, 24-hour age, five-minute skew / activation only after PR 5B live evidence / no planned UNVERIFIED public window |
| PR 7 | ci_shadow_observer.py command and overlay/snapshot/receipt schemas |
canonical-v1-derived no-drift / missing permission, stale final receipt, App 15368 / streams/exits and live/snapshot exclusivity / dry-run repeat is byte-stable |
After PR 4B's safe fallback is on the default branch, PR 4C's prerequisite live matrix captures the event journal and fully paginated REST/Jobs observations before approval, immediately after approval, while running, after completion, after rerun, after close/reopen, and after deletion. Until that authorized canary exists, fork approval remains empirical UNVERIFIED telemetry and cannot authorize audit identity.
Each matrix includes valid, invalid, boundary, failure, recovery, idempotency,
and compatibility cases. Mocked provider tests are not live evidence; live rows
remain UNVERIFIED until the exact child PR produces their authorized artifacts.
Eight route canaries¶
Each route canary changes a stable real path already governed by the production
classifier; no test-only policy mapping may certify a product route. The draft
PR change is a documented comment/description-only probe unless the row says
otherwise, is never merged, and is inspected to prove it does not change runtime
semantics. The unknown sentinel is deliberately outside every mapping.
RUN/N/A order is the closed job vocabulary: static, contracts, docs,
python-3.11, python-3.12, packaging, postgresql, airflow,
runtime-wheel-smoke.
| Canary | Exact draft-PR change | Expected map | Producer/audit oracle |
|---|---|---|---|
| docs only | edit only docs/cicd/pr-gate-shadow.md |
RUN,RUN,RUN,N/A,N/A,N/A,N/A,N/A,N/A |
producer PASS, audit PASS, zero Python/Airflow jobs |
uv.lock only |
regenerate and change only uv.lock from a valid locked graph |
RUN,RUN,N/A,RUN,RUN,RUN,N/A,N/A,RUN |
producer/audit PASS; zero Airflow cells |
| core | comment-only edit to src/dpone/runtime/__init__.py |
RUN,RUN,N/A,RUN,RUN,RUN,N/A,N/A,N/A |
producer/audit PASS |
| PostgreSQL | comment-only edit to src/dpone/xmin/__init__.py |
RUN,RUN,N/A,RUN,RUN,RUN,RUN,N/A,N/A |
producer/audit PASS; XMin case present |
| Airflow | comment-only edit to packages/dpone-airflow-pack/src/dpone_airflow_pack/__init__.py |
RUN,RUN,N/A,RUN,RUN,RUN,N/A,RUN,RUN |
producer/audit PASS; exactly eight cells and two smokes |
| control surface | comment-only edit to .github/dependabot.yml after PR 3A |
all nine RUN |
producer/audit PASS; full fallback reason; trust-core bundle unchanged |
| unknown path | add only test_artifacts/ci-shadow-unknown/unknown.txt |
all nine RUN |
producer/audit PASS; unknown_paths contains exact path |
| deliberate failure/recovery | commit 1 adds tests/test_ci_shadow_deliberate_failure_canary.py with the approved failing marker; commit 2 changes only that marker to the approved passing value |
core map on both heads | first producer FAIL/audit FAIL; second distinct head producer PASS/audit PASS; no prior receipt relabelled |
Each is a distinct exact-head draft PR. Evidence binds base/head, producer
run/attempt, plan digest, selected/N/A map, artifact ID/digest, auditor
run/attempt/receipt digest, and reconciliation receipt. Until real runs exist,
all canaries are UNVERIFIED.
Burst and cancellation¶
- Three rapid commits in one PR; superseded runs become terminal cancelled
within 90 seconds and never
PASS; the newest head completes and audits. - Three parallel
uv.lockPRs; no cross-PR cancellation and zero shadow Airflow jobs. - Rerun creates attempt 2 and never overwrites attempt 1.
- Reconciliation observes every provider-visible producer in the exact 14-day interval after the 30-minute grace and never carries unresolved state between runs.
Broad gates¶
Every implementation PR runs its selected focused tests and, as applicable, Ruff, format, mypy, import/layer/module gates, all non-live pytest, actionlint, workflow governance, strict docs/MkDocs, four builds, and Twine. Live checks are never reported as PASS when skipped or unauthorized.
Exit criteria¶
- all child PRs merge without bypass and with the canonical active required contexts green;
- exactly one Actions
PR Gate shadowand zero ActionsPR Gatecontexts; - eight route canaries plus burst/cancellation pass;
- at least two verified distinct-SHA full runs use completed producers;
- no ruleset, classic protection, repository variable, release workflow, tag, or publication change;
- frozen PR #511 closes as superseded with its audit ledger preserved.
- fresh architecture, certification, CLI/UX, and docs/CJM reviews approve the exact acceptance head.
Representative experimentation requires a separate decision after at least 14 days, 20 verified distinct-SHA full runs, zero observed flakes, and zero escaped regressions. Replacing the full PR profile is another objective requiring at least 100 comparative distinct-SHA runs, observed flake rate below one percent, zero escaped regressions, and identical representative/full decisions.
Documentation plan¶
PR 2 lands this overview, ADR 0046/0048, ADR index entries, one validated PR 2 task contract, a control-surface link, and navigation. It does not import frozen operational pages or claim implementations exist.
Later PRs add focused pages:
- PR 4A:
docs/cicd/pr-gate-shadow.md; - PR 4B:
docs/cicd/shadow-auditor.md; - PR 4C:
docs/cicd/shadow-reconciliation.md; - PR 5A/5B:
docs/cicd/exact-sha-compatibility.md; - PR 6: readiness evidence reference and CLI/Python migration;
- PR 7:
docs/cicd/governance-read-only.md.
Every page labels current, planned, historical, and future authority; live facts
include timestamp/source; commands link to exact recovery. docs/ci-cd.md
remains the stable overview.
Related guidance¶
- PR 3A executable child specification
— the approved
dd45dd85CI-hygiene baseline remains historical authority. Any separately reviewed provider-bound security amendment must beAPPROVED, merged without bypass, and named by the rebased task contract before implementation; the linked child's status and checklist are the authoritative current lifecycle state. - CI/CD overview — current implemented workflow behavior.
- GitHub branch protection — current live authority and the pending design boundary.
- ADR 0046 — diagnostic shadow versus future App authority.
- ADR 0048 — attempt-bound evidence and default-deny readiness.
- Agent task contracts — next step before each child writer handoff.
Rollout and rollback¶
- Freeze #511 and preserve its historical ledger.
- PR 1 baseline and module ratchet merged as
0fa1b35bfd20c35fa0cb2a8c3dfa966d7afc11d4. - Merge this specification after exact-head repository approval.
- Land CI hygiene and privilege boundary before the evidence window.
- Land shadow producer and auditor, then the separately approved read-only capacity probe, then the calibrated stateless reconciler independently.
- Land immutable compatibility. PR 6A may develop dormant internal readiness and pair-transaction groundwork after PR 2. PR 6B public adapters, migration, merge, and activation require PR 5B merged with authenticated verifier evidence. Then land optional read-only policy observation.
- Execute canaries and SLO acceptance under unchanged legacy protection.
- Close #511 as superseded.
Before any authority cutover, rollback is a reviewed revert or disable of the
non-required shadow workflow. Legacy checks continue unchanged. Any false green,
duplicate context, untrusted execution, identity mismatch, unreconciled current
head, or live governance drift is immediate NO-GO; no mutation or bypass is a
permitted recovery.
Agent execution plan¶
The integrator exclusively owns workflows, policies, schemas, lockfiles, navigation, changelog, and other shared semantic files. Parallel writers use separate worktrees and validated, disjoint contracts created only after their real base commit exists.
| Child | Owned responsibility | Dependency |
|---|---|---|
| PR 3A | CI hygiene; no release workflow | PR 2 merged |
| PR 3B | semantic PR privilege boundary | PR 2 and PR 3A merged; linked child status and paired ADR 0037 amendment are the lifecycle authority; implementation requires their merged APPROVED/ACCEPTED state; before evidence window |
| PR 4A | shadow classifier/evaluator and workflow | PR 2 merged |
| PR 4B | trusted data-only auditor and bundle closure | PR 4A |
| PR 4C calibration probe | source-free read-only capacity evidence under parent hard maxima | PR 4B plus approved calibration-probe child contract |
| PR 4C reconciler | stateless bounded attempt reconciliation | PR 4B, live public-fork lifecycle canary, and fresh within-threshold calibration evidence |
| PR 5A | immutable candidate identity | PR 4A |
| PR 5B | completed-producer verifier and cacheless executor | PR 5A |
| PR 6A | dormant readiness core and pair transaction; no public CLI/PASS | PR 2 merged |
| PR 6B | public readiness adapters, schema, migration, and activation | PR 6A plus PR 5B merged with authenticated verifier evidence |
| PR 7 | separate dormant CI shadow policy and GET-only observer | PR 2 merged |
Concrete PR 3A–PR 7 task contracts are created and validated immediately before
writer handoff, after the preceding merge supplies the true base_commit.
Fabricated future base SHAs are prohibited.
PR 6A may prepare isolated dormant internals while PR 5 is in flight, but PR 6B public default-deny behavior, CLI, and migration do not land ahead of their only authenticated producer; no planned outage or permanently
UNVERIFIED production window is accepted.
PR4C capacity V2 amendment¶
The historical V1 calibration contract remains accepted for immutable receipt
retention and validation at 800 hard / 400 approval requests. The capacity
producer now constructs ReconciliationPolicyV2, emits
dpone.ci-shadow-reconciliation-capacity.v2, and applies 3,000 hard / 1,500
approval request limits. An amended-prerequisite consumer must require both the
V2 schema and V2 policy digest; V1 receipts cannot qualify it. This narrow
successor changes no interval, independent-observation, source binding,
byte/wall, decision, or merge-authority rule. It supersedes only the V1
calibration-capacity values wherever this parent design describes that
prerequisite; the future reconciler's public contract remains separately
approved work.
Approval checklist¶
- User problem and CJM are clear.
- Parent algorithms and stop conditions are explicit; child public bytes remain non-implementable until their separately approved executable contract.
- Public contracts and compatibility are explicit.
- Architecture and alternatives are justified.
- Relevant platform research uses current official sources.
- Claimed differentiation is measurable.
- Tests, evidence, docs, rollout, and rollback are complete.
- Path ownership and integration plan are conflict-safe.
- PR 2 exact head
15f6b11e97e0217b4cbbca35b3e5660ccbf3e963received owner attestation and a successful receipt, then merged without bypass asec0323feba829d547e5f9c2d23acaac5b686f1a4; issue #512 records the closure.