| name | stable-release |
| description | Promote an already-shipped rolling release (vX.Y.Z) of the DOS kernel to a named stable channel — gated on a green kernel suite + a green third-party CI run on the candidate + a clean truth syscall + a soak window. Writes an evidence file and adds a stable/<codename> git tag on the same commit. Does NOT bump versions or build new artifacts. |
| disable-model-invocation | false |
| user-invocable | true |
| allowed-tools | Read, Edit, Grep, Glob, Bash, Write |
| argument-hint | <codename> [--from vX.Y.Z] [--window-days N] [--force-promote] |
| output_root | none |
Stable Release — a named channel on top of rolling /release (DOS)
/release ships a rolling tag (vX.Y.Z) whenever DOS lands user-visible change.
That tag promises "this commit merged" — it does not promise "the substrate
was provably trustworthy here." This skill adds a second, less-frequent channel
that does make that promise.
A stable release is a promotion of an already-shipped commit, not a new
build. No new version is minted. No new wheel or zip is produced. The gate is
read at promote time, frozen into an evidence file, and a second annotated tag
(stable/<codename>) is pinned on the same commit.
This is the DOS adaptation of job's stable-release skill. job gates on
apply-loop hero metrics (silent-failure share, PSV verified-success rate,
funnel-stage regressions) and KEEP-slot baselines from execution-state.yaml +
baselines.yaml. None of that exists in DOS — DOS is a domain-free trust
substrate, not an apply pipeline; it has no runtime funnel, no hero metric, no
baseline ledger. So the gate is re-grounded on the only "known-good" signals
DOS actually has: a green kernel suite, a clean truth syscall, and a soak
window. See "The gate" below.
Trunk is master. The stable tag is pinned on a commit reachable from
master; the push targets master's remote.
The kernel-vs-tooling boundary. The gate context script
(scripts/stable_release_context.py) and this skill are dev/release tooling
that operates ON the package — never imported BY dos.*. The
"kernel imports no host, no scripts/" litmus in CLAUDE.md stays intact.
Relationship to other systems
| System | Owns | Stable-release reads | Stable-release writes |
|---|
/release (scripts/release_bump.py) | rolling vX.Y.Z tag, version markers | the latest vX.Y.Z as promotion candidate | nothing — never re-bumps |
the kernel suite (pytest) | substrate correctness | exit code (green == eligible) | nothing |
the truth syscall (dos verify) | ground-truth adjudication | exit + verdict (well-formed == eligible) | nothing |
| git tags | the channel itself | stable/* collision check + previous-stable | one new stable/<codename> tag |
The spec deliberately introduces no new state machine, database table, or
pipeline event. It reuses git tags + the suite + the syscall.
When to invoke
The user (or a scheduled cron) invokes this skill. Typical triggers:
- After a
/release that landed a meaningful kernel change and you want a
rollback anchor that says "the substrate was provably good here."
- Quarterly hygiene — pick the highest
vX.Y.Z that satisfies the gate.
- After a regression rollback, to mark the recovered state.
Cadence — the two-channel contract. Rolling vX.Y.Z tags ship continuously:
/release is deliberately cheap, and a coherent unit that clears the mechanical
gates (suite, leak scan, CI, the publish ci-green witness) just ships — several
per week is healthy. The stable channel is the deliberate counterweight: an
infrequent, gate-driven promotion that answers "which version do I pin in
production." There is still no fixed calendar — the gate either passes or it
doesn't — but the working rhythm is: promote the highest soaked-green tag
(a) after a milestone (a new syscall family, a major/ABI bump, the first
PyPI-published era), or (b) when a meaningful run of rolling tags has accumulated
since the last stable — every few weeks, not every few days. Rolling moves fast
because stable exists to absorb the trust question; stable can afford its soak
window because rolling carries the urgency.
Codename convention
Operator picks one short word at promote time, prefixed by year-month:
stable/2026-06-aardvark. Memorable, sortable, and rollback becomes meaningful
(git checkout stable/2026-06-aardvark says "the substrate was good here" in a
way git checkout v0.3.0 never claimed).
Validate: ^\d{4}-\d{2}-[a-z][a-z0-9-]{2,20}$ (the context script enforces this).
Reject ambiguous reuses by checking git tag -l 'stable/*-<codename>'.
Step 1 — Pre-digest the gate context
One helper collapses every gate read into one JSON payload:
python scripts/stable_release_context.py --codename <codename>
The script returns:
{
"candidate_tag": "v0.3.0",
"candidate_sha": "abc123…",
"codename": "2026-06-aardvark",
"window_days": 3,
"previous_stable": {"tag": "stable/2026-05-zephyr", "sha": "…"},
"tag_collision": false,
"idempotency": {
"tag_exists": false, "tag_sha": null, "tag_matches_candidate": null,
"evidence_file_exists": false, "evidence_path": "docs/stable-releases/2026-06-aardvark.md"
},
"gate": {
"pytest_suite_green": {"name": "...", "pass": true, "exit_code": 0, "summary_tail": "..."},
"ci_green": {"name": "...", "pass": true, "verdict": "GREEN", "advisory": false, "reason": "..."},
"dos_verify_clean": {"name": "...", "pass": true, "exit_code": 1, "verdict": {...}},
"tag_age": {"name": "...", "pass": true, "age_days": 5.2, "window_days": 3}
},
"summary": {"all_green": true, "blockers": [], "forced": false}
}
If summary.all_green is false, the skill stops and reports the failing
rows — exactly which gate row blocked promotion and its reading. This is the
entire point of the skill; never paper over a red gate.
dos_verify_clean exits 1 on a healthy no-plan repo — that is a PASS. The
truth syscall's exit code carries the ship verdict (0 shipped / 1 not), not
execution health. The gate probes with a sentinel (plan, phase) that never
shipped, so a healthy syscall returns shipped=false, source="none" and exit
1; the script treats a well-formed verdict dict + exit ∈ {0,1} as the pass. A
crash (non-{0,1} exit, traceback, no JSON) is the only failure. Don't "fix"
this by demanding exit 0.
Step 2 — Decide the promotion candidate
By default, promote the most recent vX.Y.Z tag that has soaked ≥ window_days
(so the gate had time to observe it). If --from vX.Y.Z is passed, use that.
Refuse to promote (the context script flags these as blockers):
- A tag already promoted (a
stable/* tag points at the same SHA → tag_collision).
- A tag younger than
window_days unless --force-promote is set with a written
rationale in the evidence file.
If candidate_tag is null, there is no vX.Y.Z tag yet — DOS has not cut
a rolling release. Stop and tell the operator to run /release first; a stable
promotion has nothing to promote.
Advisory check (PyPI live since 2026-06-10): did the candidate actually
publish? The stable promise includes "pin this in production," and the pin is
pip install dos-kernel==<underlying> — which only resolves if the candidate
tag's publish.yml run was approved and uploaded (the tag push triggers the
pipeline, but the upload waits on the ci-green witness + the pypi-environment
approval, so a tag can exist unpublished). Check pip index versions dos-kernel
(or the publish run on the tag). If unpublished: prefer approving the pending
publish first; promoting anyway is allowed (the git tag still anchors the
rollback) but the evidence file must note the pin does not resolve. Hand-checked
for now — not a script gate row yet.
Step 2.5 — Idempotent re-run (a partial-failure replay is SAFE)
A promotion writes two durable side-effects in sequence — the stable/<codename>
tag (Step 5) and the docs/stable-releases/<codename>.md evidence file (Step 4 +
its commit in Step 6). If a prior run was interrupted between them (operator
Ctrl-C, a git push that failed), re-running with the same codename must be
safe — detect what already exists and skip it, never error or write a duplicate.
This is the idempotency property the userland app bakes into its promote
orchestrator; DOS reads it from the context script's idempotency block and the
skill body acts on it (DOS keeps the promotion script-light, so this is data the
skill obeys, not a wrapper).
Read idempotency from Step 1 and branch:
tag_matches_candidate: true (the stable/<codename> tag already exists and
points at the candidate SHA) → the tag step already succeeded. Skip Step 5
(do not re-tag — a stable tag is never re-pointed); record it on an
idempotent_skips list for the final summary. This is the clean replay case, NOT
a tag_collision blocker (the context script already excludes a same-codename tag
from the collision check).
evidence_file_exists: true → the evidence file is already written. Re-read
it; if its gate snapshot matches today's, skip re-writing Step 4 and its Step 6
commit (note the skip). If it's stale/partial, finish writing it and commit — the
file is the artifact, completing it is the point.
tag_exists: true AND tag_matches_candidate: false → the codename already
tags a different commit (the word was reused). This is a hard blocker the
context script already raised — stop; a stable tag is immutable, so pick a
fresh codename and add a ## Supersedes note in the new evidence file.
- All false → a normal first run; proceed through Steps 4–6 as written.
The load-bearing property: every side-effect detects its own already-done state, so
the skill can be re-run after any partial failure and converges to one tag + one
evidence file. Report every skip under idempotent_skips in the final summary so
the operator sees what the replay reused versus wrote.
Step 3 — The gate (the load-bearing step)
The gate is four rows, all from the context script. Each is a real check, not a
remembered number:
| Row | Source | Pass condition |
|---|
pytest_suite_green | python -m pytest -q | exit 0 — the kernel suite is green on the promoting machine. The substrate's entire value is deterministic, correct verdicts; a red suite means it can't anchor a stable channel. |
ci_green | the CI/Checks oracle (dos.drivers.ci_status) on the candidate commit | not RED — the third-party CI record, not a local self-report. GREEN passes; RED blocks (a real, accountable failure, e.g. a Windows/py3.11 matrix leg the promoter's box never ran); PENDING/NO_SIGNAL are advisory passes (the local suite row stays load-bearing). See the asymmetry note below. |
dos_verify_clean | dos verify (sentinel probe) | well-formed verdict dict + exit ∈ {0,1} — the truth syscall executes cleanly (see the exit-code note above). |
tag_age | committer date of the candidate tag | age ≥ window_days — the candidate soaked, not a just-cut tag promoted in the same breath. |
ci_green is asymmetric — it can only ADD a blocker, never remove one. It is
the substrate eating its own distrust (docs/93): pytest_suite_green is a
self-report from the very machine cutting the promotion, so ci_green reads the
verdict the CI system recorded on infrastructure the promoter doesn't control.
Only RED blocks (CI proves the build is broken — the row's whole reason to
exist, catching matrix-leg failures invisible locally). PENDING (CI not
finished) and NO_SIGNAL (gh unwired/unauthed, or the candidate was never
pushed) are advisory passes — hard-blocking on them would make the gate
unrunnable offline, contradicting the oracle's fail-safe "never fabricate a
verdict" discipline. The local pytest row remains the load-bearing suite gate; CI
is purely additive signal. Use --skip-ci for an offline smoke. If ci_green is
PENDING and you want CI-verified promotion, wait for the run to finish and re-run
the gate rather than --force-promote.
This row is itself the "richer known-good history across consumers" the gate was
always designed to grow into: as more signals appear (an N-day clean-dos verify
soak, a consumer-side loop reporting back), additional rows thread into
stable_release_context.py the same way — without touching the kernel. That is
the package's own modularity rule: policy (the gate) is data + a driver-shaped
script; mechanism (the kernel) stays untouched.
Step 4 — Write the evidence file
Single markdown file at docs/stable-releases/<codename>.md (create the dir on
the first promotion). Frontmatter is the gate JSON snapshot; body is
human-readable.
---
codename: 2026-06-aardvark
underlying_version: v0.3.0
candidate_sha: abc123…
promoted_at: 2026-06-15
window_days: 3
previous_stable: stable/2026-05-zephyr
gate:
pytest_suite_green: {pass: true, exit_code: 0}
ci_green: {pass: true, verdict: GREEN, advisory: false, repo: anthony-chaudhary/dos-kernel}
dos_verify_clean: {pass: true, exit_code: 1, source: none}
tag_age: {pass: true, age_days: 5.2, window_days: 3}
---
# Stable promotion — 2026-06-aardvark
Promotes `v0.3.0` (commit `abc123…`).
## What's known-good here
- Kernel suite green (`pytest -q`, exit 0) at this commit.
- Truth syscall executes clean (`dos verify` → well-formed verdict).
- Candidate soaked 5.2 days (≥ 3-day window).
- Pinned install: `pip install dos-kernel==0.3.0` (published on PyPI — or note
here if the candidate's publish run was never approved).
## Rollback target
`git checkout stable/2026-06-aardvark` returns to this exact commit + a
provably-green substrate.
## Gate evidence (mechanically generated; do not edit)
[full gate table from the context JSON]
This file is the artifact /stable-release exists to produce. Everything else is
bookkeeping. Use the date from the operator / the system clock provided in
context — do not invent a timestamp.
Step 5 — Tag
git tag -a stable/<codename> <candidate_sha> -m "stable/<codename> — promoted from v0.3.0"
git push origin stable/<codename>
Idempotent skip (Step 2.5): if idempotency.tag_matches_candidate was true,
the tag already exists on the right commit — do not run these commands; record
idempotent_skips: stable/<codename> tag (already present) and move on. If only the
local tag exists but the push didn't land (a rare interrupt), re-running
git push origin stable/<codename> is safe and idempotent.
Never delete or re-tag a stable tag. Mistakes get a new codename + a
## Supersedes section in the next evidence file.
Step 6 — Commit the evidence file
The evidence file is a real doc; commit it (it is not the promoted commit — the
tag points at the candidate, the evidence lands as a fresh commit on master):
Stage and commit by explicit pathspec (-- <path>) — master here is a
concurrently-written tree, and a bare git commit would sweep another session's
staged content under this message (the same hot-tree discipline /release Step 5
follows):
git add docs/stable-releases/<codename>.md
git commit -m "docs/stable: promote v0.3.0 to stable/<codename>" -- docs/stable-releases/<codename>.md
git push origin master
Idempotent skip (Step 2.5): if idempotency.evidence_file_exists was true
and the file's gate snapshot already matches today's, the evidence commit likely
already landed — check git log for the docs/stable: promote … <codename> commit;
if present, skip this step and record idempotent_skips: evidence file + commit (already present). Only write + commit if the file is missing or stale.
Step 7 — Final summary
Print:
- Codename + underlying tag + commit SHA (short)
- Gate result table (one row per check, reading vs pass/fail)
- Evidence file path
- Stable tag + push status
idempotent_skips (if any) — each side-effect a re-run reused rather than
re-created (the tag, the evidence file, the evidence commit), so the operator
sees this was a replay, not a fresh promotion.
- Previous stable, for the rollback hint
Supporting script
scripts/stable_release_context.py produces the Step 1 JSON. It runs the
suite, probes the truth syscall, computes tag age, checks for a stable/*
collision, and reports the idempotency block (Step 2.5 — whether this codename's
tag + evidence file already exist) so a partial-failure re-run is safe. It is
dev/release tooling — never imported by dos.*. There is no heavier orchestrator
(job's stable_release_promote.py / agents.sr.*): DOS's gate is small enough
that the skill body drives the tag + evidence-file steps directly, and keeping it
script-light avoids adding surface the kernel ethos discourages. The idempotency is
therefore data the skill obeys, not a wrapper that enforces it — the same
mechanism/policy split the kernel keeps everywhere.
Anti-patterns (don't do these)
- Don't bump versions. This promotes an existing tag. If you're editing
pyproject.toml or src/dos/__init__.py, you're in the wrong skill (use
/release).
- Don't build new artifacts. No new wheel, no new zip. The git tag IS the
artifact in DOS.
- Don't
--skip-pytest for a real promotion. That flag is for dry inspection
only; the green suite is the load-bearing gate row.
- Don't
--skip-ci to dodge a RED CI run. The flag is for offline smokes. If
ci_green is RED, the build is broken on a leg your machine didn't run — fix it,
don't skip it. (PENDING/NO_SIGNAL already don't block; you only reach for
--skip-ci to silence them, which is fine for a dry run and wrong for a real one.)
- Don't override a red gate without writing why.
--force-promote is allowed,
but the evidence file must contain a ## Force-promote rationale section
explaining which row failed, why it was overridden, and who approved it.
- Don't promote two stable tags in one window without a
--force-promote
rationale.
- Don't fold the rolling channel into the stable channel. Two tags, two
audiences: rolling = "latest code," stable = "last provably-good substrate."
What this skill consciously punts
- A stable install pointer. job flips
stable_current.txt via
install_versioned.py; DOS has no versioned-install snapshot at all (it's a
pip package), so there is no pointer to flip. pip install dos-kernel==<underlying>
pins the promoted version directly (live on PyPI since 2026-06-10) — note the
dist name is dos-kernel, not the bare dos (an unrelated PyPI package).
- GitHub Releases entry for stable tags. The evidence file + annotated tag are
the canonical artifacts. Revisit if operators ask.
- Auto-promotion via cron. Easy to add later (
/loop + this skill), but the
first few promotions should be operator-driven so the gate's calibration gets
validated by hand.
- Richer gate rows (multi-day soak, consumer feedback). Threaded into the
context script when DOS has the history to support them — not before.