integration/stacks/personal.json is the only editable personal package list. A promotion turns one exact, already-gated main commit into an attested stack/<sha> release. Flow: verify -> attest -> stack/<sha> release -> dotfiles pins the sha. There is no moving pointer: consumers pin an exact source_sha, never main. This is the trust root for adoption, so every gate fails closed.
Full frozen-candidate verification is mandatory before snapshot construction. It requires actual full matrix/core execution and retained artifacts for the exact combined source SHA, not the selective push/main smoke or old PR checks. Unrelated later main advancement does not invalidate a frozen verified candidate. No operation waives this barrier.
stack-snapshot.json is compact, sorted JSON with this strict schema (integration/harness/promotion.py):
schema_version: currently1.source_sha: the exact 40-charactermaincommit selected.stack: the validatedpersonalstack object, including ordered sources and core release identity.stack_sha256: SHA-256 of compact sorted UTF-8 JSON forstack, not of the surrounding snapshot.gates:required_checks: the configuredmaincheck names, all successful onsource_sha.stack_run_ids: the qualifying full-dispatch CI run for this exact frozen candidate.owned_packages: one{package, integrity}pernpm:@nredd/source, with the registry's real SHA-512 integrity fromscripts/npm_release.py stack-check.core: the core release tag and asset SHA-256 digests, which must equal the stack'scoreand the release'sSHA256SUMS.
The snapshot's stack JSON obeys the canonical-JSON rules in integration/README.md (no floats, unsafe integers, non-string keys or lone surrogates); the producer guard lives in integration/harness/stack.py.
The module rejects unknown fields, malformed SHAs, non-personal stacks, digest mismatch, duplicate evidence, and owned-package evidence that is not exactly the stack's @nredd/ sources.
The snapshot schema remains strict v1 and retains its historical owned-only integrity boundary. New promotions additionally publish and independently attest strict dependency-graph.json, containing its own schema version, exact source_sha, and source-selected complete native runtime locks bound to the canonical stack digest. The hardened standalone installer requires BOTH assets; historical snapshot-only promotions are unsupported, never silently downgraded. Existing dotfiles snapshot-v1 consumers are unchanged and do not gain installer runtime-byte guarantees. Source selection, threat model and bootstrap acceptance limits are documented here.
Workflow: .github/workflows/promote-stack.yml (workflow_dispatch, main only). The workflow concurrency group is operation plus the selected SHA, so a verify-only dispatch never replaces a pending promote of the same commit and two promotions of one SHA run one at a time. With 3+ duplicate same-SHA, same-operation dispatches GitHub keeps one pending run and cancels older pending ones, so duplicate pending dispatches coalesce. That is harmless: stack/<sha> creation is idempotent and fails closed on mismatch. Different SHAs are independent: each writes only its own stack/<sha> tag and release. Every job has if: github.ref == 'refs/heads/main'. All gate logic lives in Python with fixture tests, and every run: is one Python (or one gh/action) call with inputs passed through env: only. The read-only verify job runs harness.release_gate followed by python -m harness.promotion_verify, which repeats the full barrier before candidate construction and fetches the remaining evidence itself (gh/git through promotion.run_command) and calls select_source_sha, descends_from, verify_required_checks, verify_core, collect_owned_evidence (worktree per owned tag plus stack-check), build_gates and build_snapshot. The verifier also reads integration/stacks/runtime-locks.json from that exact source commit, binds its roots/digest to the selected stack, cross-checks owned SRI, and verifies every selected tarball against standard npm SRI (plus authenticated core SHA-256). It never re-resolves the graph during promotion or resume. The write-privileged attest-and-publish job runs stdlib-only python3 -m harness.publish_gate subcommands prepare-release and tag-target (the complete set), plus gh attestation verify, actions/attest-build-provenance and gh release create. harness.promotion is a library with no CLI. Evidence failures and Git failures print FAILED: ..., never a traceback.
- Inputs:
select_source_sha(called bypromotion_verify) requiresoperationto beverify-onlyorpromoteandsource_shato match^[0-9a-f]{40}$. The commit must be an ancestor oforigin/main. Thefetch origin mainthat getsorigin/maincarries no credentials (persist-credentials: false), which is fine because the repository is public. - Required checks: every context from
GET /repos/nredd/pi-harness/rules/branches/main(rulesetmain required checks, readable byGITHUB_TOKEN) must becompleted/successon that exact SHA, reported by the rule'sintegration_idapp (app.id, 15368 for every configured context). No checks from the rules API fails closed. Review statuses are not required checks. - Deferred, review statuses (
migration-review/standards,migration-review/spec): they are no longer required checks and the merge flow posts no commit statuses (docs/ci.md), so there is nothing to read; reviews stay a process step. - Deferred, tap readiness when core changes: not implemented; the core release's asset digests and attestation are the only core gate, and a tap bump is gated by its own green CI.
- Registry-stack evidence: the exact full
workflow_dispatchrun validated byharness.release_gate. Every owned and contracts cell, each stack cell that runs a stack, and the core gate must actually execute, with retained nonempty per-cell artifacts whoseworkflow_runid andhead_shamatch. Both step names and artifact templates are bounded producer contracts guarded by tests; update the verifier alongside producer renames. The personal stack installs the explicit sources from the registry and the core release into a disposable tree. - Why the artifact: a skipped cell still reports its job as
success(recorded run39545048647showsstack (macos-14, personal)green on a main push though it ran nothing).ci.ymluploadsstack-<os>-<stack>only whensteps.plan.outputs.run == 'true', so docs-only and main-push smoke cells never produce it. The verifier matches only the exact artifact name, and a test guards that template inci.yml. - Push, PR and docs-only runs never qualify: only a full
ci.ymldispatch of the exact SHA does, and it skips no docs, WSL-input or stack-input shortcut, so a docs-only candidate is promotable once dispatched; see release verification. WSL cells do not run a stack, and differently named artifacts are not promotion substitutes. Artifacts expire with CI retention, so an old SHA must be dispatched again before it can be promoted. - Owned packages:
scripts/npm_release.py stack-checkruns per@nredd/source against a worktree of its exact tag: registry presence, npmaudit signatures(signature and attestation), and the SLSA provenance binding topublish.yml, the tag and the commit. The registry lookup (npm view) runs in a fresh empty directory withNPM_CONFIG_USERCONFIG=/dev/nulland--registry https://registry.npmjs.org, so a.npmrcin the selected tag's tree cannot steer it. - Core: asset digests are compared with the release's
SHA256SUMS. For non-legacy tagsSHA256SUMSis a mutable release asset, so each recorded asset is also downloaded, hashed against the recorded digest, and verified read-only withgh attestation verify --repo nredd/pi --signer-workflow nredd/pi/.github/workflows/fork-release.yml --source-ref refs/tags/<tag>. Legacy tagsv1.3.0-nredd.3through.7predate that workflow and rely on the frozenLEGACY_CHECKSUMShashes.
Core SSH signing keys are checked by the core release workflow, not by the promotion
consumer's attestation verifier. The core allowlist uses principal nredd and namespace git:
MacBook SHA256:YHzjQXY3SPl20zOMMOoNz8o4dk+eOZEi8FA+vS6nwFY (retained for historical tags)
and Mac mini (nick-mac) SHA256:+16+g7/7VKDJ2svgiuGIbfPnPF9pKKSU/h51+K9iHdk.
The latter is owner-approved for .8 and future releases. No wildcard principal, skipped
verification, new legacy exception, Gitlink move, or stack/pin change is implied.
The trust-location audit records the separate
core, harness, dotfiles and tap boundaries.
The CI evidence is the only coupling to CI: run IDs are read with gh run list/view, the actions: read and checks: read permissions are granted, and an unreadable response fails with an explicit error.
The twelve required checks live in ruleset main required checks (id 25524267, non-strict). See CI for which of them are bypass cells: a green bypass cell is a reported name, not evidence, which is why the registry-stack artifact below is the evidence. The verifier discovers the live contexts rather than hard-coding eight, nine or twelve, so GET /repos/nredd/pi-harness/rules/branches/main returns them to GITHUB_TOKEN with no extra token. Classic branch protection is not read: if the rules API yields no check, verify_required_checks fails closed.
Dispatch Promote stack with operation:
verify-only(the default): runs every check above and builds the candidate snapshot. Writes nothing: no artifact, release, attestation or tag, and theattest-and-publishjob (the only job in thestack-promotionenvironment, with write credentials) is skipped. Run it first to see exactly what a promotion would do; it needs no approval.promote:verify-onlyplus attestation and thestack/<sha>release.
Rollback is re-pinning: point dotfiles at an older retained stack/<sha>. Nothing in this repository moves, so there is no rollback operation. Tags are protected from update and deletion (ruleset 25524298); release assets are not, but a replaced asset fails gh attestation verify, so every previously promoted snapshot stays usable only as attested.
Environment: environment: stack-promotion gates the whole attest-and-publish job, so a required reviewer (the owner) must approve before the job gets contents/attestations/id-token write. It is kept deliberately: it is the only human gate between a green verify and a permanent attested release, and a release cannot be retracted. It also restricts the job to main. No secret is stored in it any more.
Privilege split: the verify job runs only main code plus data from the selected commit, holds contents/actions/checks read only, and runs no npm ci or uv sync from the selected commit. The attest-and-publish job holds id-token/attestations/contents write, installs nothing, and only downloads the verified artifact, runs harness.publish_gate (standard library only, from main, asserted by a test), attests, and publishes.
promote attests both stack-snapshot.json and dependency-graph.json (actions/attest-build-provenance), creates release/tag stack/<source_sha> containing both, then confirms the tag points at source_sha. The tag namespace deliberately does not overlap package tags (@nredd/<package>@<version>).
Resume: prepare-release runs before any attestation. It asks git/matching-refs/tags/stack/<sha> for the exact ref; only an empty answer means create. Any gh failure (403, 5xx, rate limit) raises instead of being read as "absent". An existing tag must be a lightweight tag pointing at source_sha (annotated tags are rejected: gh release create --target only makes lightweight tags, so one is hand-made), its release must carry BOTH stack-snapshot.json and dependency-graph.json (otherwise refuse), and both assets must be byte-equal to the rebuilt source-selected evidence (resume). Both existing attestations are then verified and nothing new is created. A retained legacy release without a graph cannot be upgraded/backfilled. tag-target re-reads the ref after publication and requires the same. Evidence is deterministic (sorted checks, earliest qualifying run); if the required-check set changed in between, equality fails closed, so investigate.
Created 2026-10-20 via gh api; the workflow does not create or change any of them:
- Ruleset
25524267main required checks: twelve contexts (integration_id15368, non-strict). - Environment
stack-promotion: required reviewer the owner, deployment branchesmainonly. The only job in it isattest-and-publish. - Ruleset
25524298stack snapshot tagsonrefs/tags/stack/**: blocks tag update and deletion; bypass is admin. It does not protect release assets, but consumers verify the attestation, so a tampered asset is rejected. Creation stays open: an unattested tag is unconsumable because consumers verify attestation, andGITHUB_TOKENcreates the tag through the release. - One-time cleanup after this PR merges (the owner/orchestrator does it, the workflow does not): delete ruleset
25524297and deploy key167140535, and remove theSTACK_CHANNEL_DEPLOY_KEYenv secret fromstack-promotion. All three belonged to the retiredstack-channelpointer.
Consumers pin an exact source_sha, download the stack/<source_sha> release asset and MUST verify its attestation before use:
gh release download stack/<source_sha> --repo nredd/pi-harness --pattern stack-snapshot.json
gh attestation verify stack-snapshot.json --repo nredd/pi-harness \
--signer-workflow nredd/pi-harness/.github/workflows/promote-stack.yml \
--source-ref refs/heads/main --deny-self-hosted-runners
Then validate it with StackSnapshot (integration/harness/promotion.py) and require its source_sha to equal the pin. load_promoted_snapshot does both. gh attestation verify needs a GitHub login unless it is given the bundle: the public attestations API serves it anonymously, which is what the installer does (--bundle, see install). An unattested file, or one attested from another workflow or ref, must be rejected, keeping the last good stack. To roll back, pin an older retained stack/<sha>.
After this PR merges, run verify-only on a main SHA whose CI ran the stack job, then promote with the same SHA and approve the stack-promotion environment. This PR dispatches nothing and creates no tag, release, ruleset or environment.
stack/17236539759ccc8858e605ed3f07531cacf2d9e0 is an immutable historical owner-waived record, not green-CI evidence. Do not alter its tag or release, and do not treat retrospective review as satisfying its pre-release gates. The owner-override operation no longer exists; recovery requires an ordinary reviewed, green-CI verify-only then promote flow.