agentleFS
ShehabovShehabov/ai-dev-crew/.claude/skills/team-release/SKILL.md

team-release

Skill4 starsChanged 46 days agoMITView on GitHub

Release a change so it can be proved and reversed. Use when release-engineer commits a run's work, runs pre-flight, applies the data layer to the target at release, deploys server functions, tags and pushes, writes the release note, verifies after release or rolls back, and when anyone needs to know whether a change is releasable. Covers who may push, tag or change an environment, the attribution check on every commit, tag and note, branch and commit conventions where PROJECT.md is silent, pre-flight and its record, the release sequence per PROJECT.md § Release and the stack pack, migration ordering, hosting recorded as deferred: no target chosen while no target exists, post-release verification, the release log, the rollback plan written before the first change, the rule to roll back first and report second, and the evidence under evidence/release/.

  • Reads credentials
  • Commits and pushes

18 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use create-stories, story-done and 10 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • lilangMaxSkill

    create-stories

    Break a single epic into implementable story files. Reads the epic, its GDD, governing ADRs, and control manifest. Each story embeds its GDD requirement TR-ID, ADR guidance, acceptance criteria, story type, and test evidence path. Run after /create-epics for each epic.

    Also in 11 projects that use this

  • lilangMaxSkill

    story-done

    End-of-story completion review. Reads the story file, verifies each acceptance criterion against the implementation, checks for GDD/ADR deviations, prompts code review, updates story status to Complete, and surfaces the next ready story from the sprint.

    Also in 11 projects that use this

  • +10 more from lilangMax

The file

Show its rules and text

We split this skill into the rules it gives an agent

14 rules. 0 are common, 7 are distinctive, and 7 are very specific to this project.

  • 0

    Common

    Most of the 16 edited versions we read keep these.

  • 7

    Distinctive

    This author's own choices. Worth a look.

  • 7

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  • could be automatic: a setting could enforce it
  1. Very specific:

    Check every commit on the work branch before anything is pushed, the tag annotation before it is pushed, and the release note before it is …

    line 71
  2. Distinctive:

    Run it over the commit messages, then over the tag annotation and the note when they exist:

    line 102
  3. Distinctive:

    Never git add -A, git add .

    line 140
  4. Distinctive:

    Never commit on the release branch.

    line 174
  5. Distinctive:

    Never force-push a branch anyone else has read, and never rewrite a pushed commit.

    line 175
  6. Distinctive:

    Read every hit. One that holds a real value is a stop: it goes to security-analyst through the orchestrator, and to the Product Lead as a …

    line 292
  7. Distinctive:

    Every row appears, in the agent file's order.

    line 346
  8. Distinctive:

    Commit, then pre-flight, then release.

    line 353
  9. Very specific:

    No exclamation marks, no emoji, no word the brand spec's voice rules forbid.

    line 477
  10. Very specific:

    When the change is user-facing, the note holds one section per locale in § Locales, the first-authored locale first, built word for word …

    line 481
  11. Very specific:

    Leave backward-compatible migrations in place.

    line 631
  12. Very specific:

    Verify the previous version: the history listing, the on-target tests, the platform checks and the smoke paths, with evidence saved as …

    line 637
  13. Very specific:

    Write the rollback record in release-log.md: what happened, when, what was reverted, what data was affected, and what the fix will be.

    line 639
  14. Very specific:

    Every file the release produces sits under .devteam/runs/<run-id>/evidence/release/, with its shell time and its command at the top.

    line 670

“Common” is measured: kept by at least 6 in 10 of the 16 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Release

release-engineer is the only role that pushes to a remote, cuts a tag, releases to an environment or rolls one back, and the only role that certifies the target's state at release. Everything in this skill is a check with an output. A command that exited zero proves the command ran, and nothing about whether a user can finish the flow.


Where the facts come from

FactSource
The release target, the release branch, the tag formatPROJECT.md § Release
The attribution policy, the commit style, any other house rulePROJECT.md § House rules
The commands for install, build, lint, typecheck, test, e2e and db testPROJECT.md § Commands
The tools that existPROJECT.md § Toolchain
How the data layer is applied and proved on the target, how server functions deploy, where logs are read, the platform checks, the clean install, the local production buildThe stack pack named in PROJECT.md § Stack pack, read by path. For stack-nextjs-supabase: its sections Release on this stack, Toolchain, Migrations, Edge Functions and The app in web/
The claim and the invariants the smoke checks probePROJECT.md § Product, PROJECT.md § Product invariants
The locales a user-facing note ships inPROJECT.md § Locales
The voice, date format and numeric treatment of the noteThe brand spec at the path in PROJECT.md § Brand
Every migration, its written reverse, the lock it takesbackend-engineer/files.md, backend-engineer/rollback-notes.md
The rollout strategy, and every secret the code readsThe ADR
Permission to releaseqc-lead/handoff.json, qc-lead/readiness.md, run.json, and the Product Lead's decision lines in ledger.md

A fact that is missing is a blocker, never a guess: hand off blocked with the section in missing_inputs, as team-protocol sets out. A step whose command is none in § Commands, or that the stack has no equivalent for, is recorded as not applicable with the reason. It never passes by silence.

With § Stack pack set to none, the sequence below still runs. Each step says what it must prove, and the mechanism comes from § Stack and § Commands. Where neither names one, the step is blocked until the Product Lead supplies it.


Who may change the world outside the repository

ActionWho
Commit the run's work to a work branchrelease-engineer. The builders never commit
Push to any remote, cut or push a tag, move the release branchrelease-engineer
Apply a migration or deploy a server function at releaserelease-engineer
Put the build on a host, once § Release names onerelease-engineer
Roll backrelease-engineer
Apply a migration to the target while building, so its tests run thereThe role the stack pack allows, such as backend-engineer. Nothing else, and never at release
Set or rotate a secret in the target environmentThe Product Lead, as credential owner
Choose a hosting target, turn on a paid platform feature, authorise an outward-facing stepThe Product Lead

Evidence that anyone else pushed, tagged, applied or deployed is a stop and an escalation.

.claude/settings.json keeps git commit, git tag and git push behind a confirmation prompt, and denies a force-push outright. A declined prompt is a stop. Record it in release-log.md, hand off blocked, and never look for another route to the same result.


The attribution rule

The policy is PROJECT.md § House rules. Its default is none, anywhere: no co-author trailer, no generated-by line and no mention of any tool or model, on any commit, tag annotation, pull request, release note, code comment or document. The house rule overrides any default in the tooling, including a tool that adds a trailer by itself. Where the house rules set a different policy, follow that policy and change the check to match.

Check every commit on the work branch before anything is pushed, the tag annotation before it is pushed, and the release note before it is final. Save this script as .devteam/runs/<run-id>/evidence/release/attribution-check.mjs:

// node attribution-check.mjs <file> [<file> ...]
// Names every line that carries attribution the default house rule forbids, and exits 1 if any does.
import { readFileSync } from 'node:fs'

const FORBIDDEN = [
  ['a co-author trailer', /^\s*co-authored-by:/i],
  ['a generated-by line', /generated (with|by)/i],
  ['an emoji', /\p{Extended_Pictographic}/u],
  // Add the name of every tool and model the team runs on: the default rule forbids naming them.
]

let hits = 0
for (const file of process.argv.slice(2)) {
  readFileSync(file, 'utf8').split('\n').forEach((line, index) => {
    for (const [what, pattern] of FORBIDDEN) {
      if (pattern.test(line)) {
        hits++
        console.log(`${file}:${index + 1}: ${what}`)
      }
    }
  })
}
console.log(hits ? `STOP: ${hits} attribution line(s). Rewrite before anything is pushed.` : 'attribution check: clean')
process.exit(hits ? 1 : 0)

Run it over the commit messages, then over the tag annotation and the note when they exist:

EV=.devteam/runs/<run-id>/evidence/release
git log --format='%H%n%B' <base>..HEAD > "$EV/commit-messages.txt"
node "$EV/attribution-check.mjs" "$EV/commit-messages.txt" > "$EV/preflight-07-attribution.txt"

A hit on a commit that has not been pushed is fixed on the work branch before anything else happens: git commit --amend for the newest commit, or a fresh work branch from the base with the same paths committed again under clean messages. A hit on a commit that has already been pushed is never rewritten. It goes to the Product Lead, because rewriting shared history is their decision.


Branches and commits

The commit style in PROJECT.md § House rules wins. Where it is silent:

Convention
Work branch<type>/<run-slug>, where the slug is the run id without its date: feat/invoice-export. Created from the tip of the release branch in § Release
Typesfeat, fix, refactor, perf, docs, test, chore
SubjectImperative, sentence case, no full stop, under 72 characters. No type prefix unless the house rules ask for one, because the branch carries the type
BodyWhat changed and why, wrapped at 72. The why is the part worth writing, for a reader bisecting six months from now
ScopeOne logical change per commit. Two unrelated changes are two commits
TrailerRefs: <run-id>, which names the run. Under the default policy, nothing else

Stage by path, and only the paths the builders' files.md lists, plus files the run itself generated that belong in the repository, such as a lockfile the install rewrote or types the stack pack says to commit:

git add -- supabase/migrations/20261001120501_invoice_export_function.sql
git add -- web/src/app/billing/export-button.tsx web/src/app/billing/page.tsx

Never git add -A, git add . or git commit -a, which sweep in whatever else is in the tree. The run folder is gitignored, and it is never committed or force-added. A path in the working tree that no files.md names is not committed: it is a pre-flight finding for the orchestrator to route.

Order the commits so each one builds and the history bisects: the data layer first, then server code, then the interface and its strings.

Export one month of invoices as CSV from the billing page

Account owners need their invoices in a file their accountant can open.
The export runs in the database behind the same access rule as the
invoice list, so it can only ever read the selected account's rows, and
a member who cannot see billing meets the restricted state rather than
an error.

Cells that begin with =, +, - or @ are prefixed with a quote, so a
project name cannot run as a formula when the file is opened.

Refs: 2026-10-01-invoice-export
Scope the invoice export to the selected account

The export read every account the caller belongs to, so an owner who was
also a member of another account received that account's invoices and
totals. It now reads only the account selected in the session, and the
invariant suite covers a caller who belongs to two accounts.

Refs: 2026-10-01-invoice-export
  • Never commit on the release branch. It moves only by fast-forward.
  • Never force-push a branch anyone else has read, and never rewrite a pushed commit.
  • A pull request description, where the house rules use pull requests, carries the same attribution rule as a commit.

Pre-flight

Pre-flight runs after the run's commits are on the work branch and before anything changes in any environment, so it reads commits rather than a working tree that can still move. Every row of the pre-flight table in release-engineer's agent file runs and records its real output in release-engineer/preflight.md, with the full output saved under evidence/release/. A single failure stops the release, and the refusal goes to the named owner of the fix.

Authorisation, read first

  • qc-lead/handoff.json reads "status": "passed" with quality at pass, and section 1 of qc-lead/readiness.md states a go. A go by override cites the Product Lead's decision line in ledger.md. You read both files; being told is not evidence.
  • run.json shows every gate upstream of release at pass. None is fail, blocked or pending, and none is absent unless its owner is in omitted with a written reason. A missing gate is a fail.
  • ledger.md shows the orchestrator's utilisation check after the last completed stage, with no finding open.
  • Every outward-facing or irreversible step in this release is authorised for this run in writing: a push or tag to a public remote, a user-visible note, a destructive migration, a hosting target, a paid platform feature. Each authorisation is a decision line in ledger.md or is recorded in run.json.

The checks, and how each is run

#CheckHowFails if
1Working tree cleangit status --porcelainAny output
2On a work branchgit rev-parse --abbrev-ref HEAD, then git fetch origin and git merge-base --is-ancestor origin/<release branch> HEADIt returns HEAD or the release branch, or the release branch tip is not an ancestor, which would make the fast-forward at tag and push fail
3Diff is what the run claimsgit diff --stat <base>..HEAD and git diff --name-only <base>..HEAD, compared with every path in the builders' files.md and the run's generated filesA path no files.md names
4No conflict markersgit grep -nE '^(<<<<<<<|>>>>>>>)'Any hit
5No secrets in the diffThe secret scan belowA hit that holds a real value
6No committed env filegit ls-files -- ':(glob)**/.env' ':(glob)**/.env.*' ':(exclude,glob)**/.env.example'Any output
7AttributionThe attribution check above, over every commit message since the baseAny hit
8Tests greenThe test command in § Commands, full run, no filterNon-zero exit, or a test skipped that was not skipped in qc-engineer's run
9Types and lint cleanThe typecheck and lint commandsNon-zero exit
10Data-layer tests, offlineThe db test command, output saved in fullAny failing case, or non-zero exit
11Data-layer tests, on the targetThe stack pack's on-target proof, every test file, the invariant suite includedAny failing case, or a file that did not run
12Build reproducibleThe stack pack's clean install from the committed lockfiles, then git status --porcelain, then the build commandNon-zero exit, or the install rewrote a lockfile
13Migration history matches the repositoryThe stack pack's history listing, compared with the migration files by name and order in a tableAn applied migration with no file, a name that is not its file's, or a different order. An unapplied file fails too, with one exception: where builders do not apply migrations during the build, the unapplied files may be exactly this run's new migrations, sorting after every applied one, for step 2 to apply. Where builders do apply them during the build, as on stack-nextjs-supabase, every file must already be applied.
14Platform checks and key exposureThe advisors or scanners the stack pack names, and its key scan for privileged keysAny finding not accepted in writing, or a privileged key anywhere the browser downloads
15Migrations reversibleEach migration's written reverse in backend-engineer/files.md, with its proof in backend-engineer/rollback-notes.mdA migration with no reverse and no written Product Lead decision, or a reverse that was never proved
16Environment variables presentThe variable table belowA variable the code reads with no source
17Accessibility and brand carried forwardqc-engineer's and qc-lead's evidence, measured against the standard in PROJECT.md § Quality barA pair recorded as estimated, or a failing pair with no written waiver

In row 4 the bar is escaped only because it sits in a table; the command is git grep -nE '^(<<<<<<<|>>>>>>>)'.

Row 13 depends on where migrations first reach the target. Where the stack pack has a builder apply them while building, so the tests run on the target (as stack-nextjs-supabase does), every file must already show as applied, and one that does not is a fail: the on-target tests ran without it. Where migrations reach the target only at release, the files not yet applied must be exactly the ones this run adds, in filename order after every applied one, and everything else must match.

A privileged key in anything the browser downloads, found at row 14, stops the release and is a rotation event: the fix is a new key, not a deleted line. A secret found anywhere in history, rather than only in this diff, goes to the Product Lead, because rotation and disclosure are the credential owner's call.

The secret scan

Save as .devteam/runs/<run-id>/evidence/release/scan-secrets.mjs and run node <that file> <base> > <evidence>/preflight-05-secrets.txt. It reads only the lines the work branch adds, and it prints the file, the line and the kind of secret, never the value, so its output is safe to keep.

// node scan-secrets.mjs <base>
// Scans the lines added since <base> for secrets. Prints where, never what.
import { execFileSync } from 'node:child_process'

const RULES = [
  ['a private key header', /-----BEGIN [A-Z ]*PRIVATE KEY-----/],
  ['an sk- key', /\bsk-[A-Za-z0-9_-]{16,}/],
  ['an AWS access key id', /\bAKIA[0-9A-Z]{16}\b/],
  ['a GitHub token', /\bgh[pousr]_[A-Za-z0-9]{30,}/],
  ['a secret assigned a value', /[A-Za-z0-9_]*(SECRET|TOKEN|PASSWORD|API_KEY)[A-Za-z0-9_]*\s*[:=]\s*['"]?[^\s'"]{8,}/i],
]

const diff = execFileSync('git', ['diff', '--unified=0', `${process.argv[2]}..HEAD`], {
  encoding: 'utf8',
  maxBuffer: 1 << 28,
})

let file = ''
let line = 0
let hits = 0
for (const text of diff.split('\n')) {
  if (text.startsWith('+++ ')) {
    file = text.replace(/^\+\+\+ (b\/)?/, '')
    continue
  }
  const hunk = text.match(/^@@ -\d+(?:,\d+)? \+(\d+)/)
  if (hunk) {
    line = Number(hunk[1])
    continue
  }
  if (!text.startsWith('+')) continue
  for (const [kind, pattern] of RULES) {
    if (pattern.test(text)) {
      hits++
      console.log(`${file}:${line}: ${kind}`)
    }
  }
  line++
}
console.log(hits ? `${hits} hit(s) to read` : 'no secrets found in the added lines')
process.exit(hits ? 1 : 0)

Read every hit. One that holds a real value is a stop: it goes to security-analyst through the orchestrator, and to the Product Lead as a rotation. One that holds no value, such as a variable read or an empty placeholder in the example env file, is recorded in preflight.md as read, with the reason.

The variable table

List every environment variable the code reads, then give each one a source:

git grep -hoE "process\.env\.[A-Z][A-Z0-9_]*|import\.meta\.env\.[A-Z][A-Z0-9_]*|Deno\.env\.get\(['\"][A-Z][A-Z0-9_]*" | sort -u
VariableRead atSource
NEXT_PUBLIC_SUPABASE_URLweb/src/lib/supabase/client.tsweb/.env.example, filled in the environment
INVOICE_EXPORT_SIGNING_SECRETsupabase/functions/export-link/index.tsSet by the Product Lead, confirmed in writing: ledger.md, decision line 38

The example env file is the one the stack pack or § Stack names. You never read, list or set a secret's value. A secret the new code reads is named in the ADR; the Product Lead sets it in the target and confirms it in writing; you verify it at release by calling the code path and reading the logs for a missing-variable error. A variable with no source, or a secret with no written confirmation, is a pre-flight fail that goes to the Product Lead.

The record

An excerpt: the authorisation table is whole, and the checks table shows four of its seventeen rows.

# Pre-flight · release-engineer · 2026-10-01-invoice-export

Work branch feat/invoice-export. Base 9c1e0b7, the tip of main. Head 4f2a9c1, 3 commits.
Started 2026-10-02T09:10:04Z.

## Authorisation

| Check | Read from | Result |
|---|---|---|
| qc-lead go | qc-lead/handoff.json (passed, quality pass), qc-lead/readiness.md section 1 | go, written 2026-10-01T16:40:12Z |
| Gate list | run.json gates[] | every gate upstream of release at pass, none omitted |
| Utilisation check | ledger.md, after stage 10 | no finding |
| Outward-facing steps | ledger.md, decision line 41 | push and tag to origin authorised for this run |

## Checks

| # | Check | Command or call | Exit | Output | Result |
|---|---|---|---|---|---|
| 1 | Working tree clean | git status --porcelain | 0 | evidence/release/preflight-01-status.txt, empty | pass |
| 6 | No committed env file | git ls-files (env pathspecs) | 0 | evidence/release/preflight-06-env-files.txt, empty | pass |
| 13 | Migration history matches | list_migrations, compared with supabase/migrations/ | n/a | evidence/release/preflight-13-history.md | pass: 14 files, 14 applied, names and order match |
| 17 | Accessibility carried forward | evidence/qc/accessibility.md | n/a | contrast measured on 4 new pairs, both hex values | pass |

Every row appears, in the agent file's order. The output column points at the saved output, never at a summary of it. A row that is not applicable says why in its result.


The release sequence

Commit, then pre-flight, then release. The data layer goes first and stays backward compatible for one release, so the running client never talks to a schema it does not know. Server functions follow the schema they rely on, and the front end follows both.

#StepWhat it must proveOn stack-nextjs-supabase
1Rollback plan and log baselinerollback.md exists and its first-line timestamp predates this step. The platform's logs for the hour before the first change are savedget_logs for api, postgres and edge-function
2Data layerEach migration the target does not show, applied once, in the order the stack pack sets (filename order for timestamped files), exactly as the file reads. Never a change that is not in a migration file in the repository. The history and table listings savedapply_migration with the slug as name and the file's contents as query, then list_migrations and list_tables
3Prove on the targetEvery on-target test file, under the roles and claims it needs, saved beside the offline run, each labelled with where it ranEach supabase/tests/*.test.sql through execute_sql
4Platform checksClean, or every finding accepted in writingget_advisors for security and performance, and the key scan in Security probes
5Server functionsEach one in the diff, deployed after the schema it relies on, called at its real URL, with its logs read. The API serves the new shape and still serves the old one to a client that has not updateddeploy_edge_function, a call to the function's URL, get_logs for edge-function
6Front endA clean install and the build command, green, with the log saved. The build released to the target in § Release, or the deferred line recordednpm ci at the root and in web/, then the build command
7Hold destructive migrationsNothing destructive ships in the release that makes it possibleNo pack-specific tool
8VerifyEvery critical path smoked against the target, with evidenceA local production build of web/ (npm run build, then npm run start) pointed at the project, driven by Playwright through npx playwright
9Tag and pushAn annotated tag, the release branch fast-forwarded to the work branch, both pushedNo pack-specific tool
10Release noteWritten in the product's voice, checked for attribution, in every locale when user-facingNo pack-specific tool

A failure at any step stops the sequence there. From step 2 onward, the triggers under Rollback decide whether it rolls back, and a failed check at step 8 always does.

Migration ordering

SituationSequence
Additive and backward compatibleApply the migration, then release the code that uses it
A new required columnThree releases: add it nullable, backfill in batches, then make it required. Never one
Removing a column or a fieldTwo releases: stop reading it and release that, then drop it once the code that stopped reading it has been live and verified
RenamingNever a rename. Add the new one, write to both, backfill, stop reading the old one, drop it
An index on a table that already holds rowsThe stack pack's rule. For stack-nextjs-supabase, a concurrent build in a migration of its own, with the Product Lead decision its Migrations section describes
A new tableIts access rules ship in the same migration. A table whose rules come later ships open
A correction to an applied migrationA new migration. An applied file is never edited

A migration that is not backward compatible with the client that is currently live is not applied. It is split, or it goes back to backend-engineer. A destructive migration arrives only with the expand-and-contract sequence written in the ADR, and it runs in a later release than the code change that makes it possible, after the Product Lead has authorised it in writing.

Hosting

While § Release reads deferred: no target chosen, the front-end release is the clean install and the build, green, with the build log saved, and this line in release-log.md and in review.md:

Front-end hosting: deferred: no target chosen

That line is not a release-gate failure. Nothing goes to any host, and no hosting tool is installed or called to prove the point.

Choosing a target is the Product Lead's decision, and it is written into § Release. Once a target is named, the build is released there by the route § Release and the stack pack give, and verified at its URL. A companion skill for that host may be loaded for the host's mechanics only, if installed (docs/SKILLS.md lists the known ones). It never replaces pre-flight, the rollback plan or verification. A paid platform feature, such as a preview environment, is optional and asked for first, and no gate requires one.


Tag and push

The tag follows the format in § Release. Where that format is semantic versioning, a fix is a patch, an additive change is a minor version, and a breaking change to a public contract is a major version. git describe --tags --abbrev=0 gives the previous tag.

EV=.devteam/runs/<run-id>/evidence/release
git fetch origin
git tag -a <tag> -m "<the change, in one line>"
git tag -l --format='%(contents)' <tag> > "$EV/tag-annotation.txt"
node "$EV/attribution-check.mjs" "$EV/tag-annotation.txt"
git switch <release branch>
git merge --ff-only origin/<release branch>
git merge --ff-only <work branch>
git push origin <release branch>
git push origin <tag>

The first merge proves the local release branch equals the remote. The second moves it by fast-forward only, to a work branch that passed pre-flight. Save the output of every push to push.txt.

A rejected push means the remote moved, and the release stops there. Never force a push, and never rebase a branch someone else may have read. Record it in release-log.md and hand off rejected with next set to orchestrator: the work branch is brought up to date and goes through pre-flight again.


Post-release verification

Smoke every critical path in release-engineer's agent file against the target, and capture each one. Until a hosting target exists, front-end paths run against a local production build pointed at the target, as the stack pack names it. Once a target exists, its URL replaces the local build.

Critical pathHowEvidence
The changed surface completes its primary actionAt the narrowest width in § Quality bar, on a mobile device profile and a throttled connection, with the console capturedsmoke-<surface>-<locale>-<width>.png, smoke-<surface>-console.txt
The product's claim holdsThe probe qc-lead used for the claim in § Product, repeated on the targetsmoke-claim-<promise>.txt
Touched invariants holdEach touched invariant, through the API as a real role and at the data layer under that rolesmoke-inv-<n>-api.txt, smoke-inv-<n>-target.log
Access control holdsAn unauthenticated request to a protected resource, then an authenticated one with no grantsmoke-access-anonymous.txt, smoke-access-no-grant.txt
Every locale rendersThe changed surface in every locale in § Locales, each right-to-left locale mirroredsmoke-<surface>-<locale>-<width>.png
Numbers and statuses readEvery figure in the brand spec's numeric treatment with its context, every status with a written label, read from the capturesNoted against each capture in review.md
Errors are in the product's voiceA forced failure, such as an invalid input or a refused requestsmoke-error-<case>.png
Logs are quietThe logs after the smoke, against the baseline from step 1: no new error class, no rise in errorslogs-after.txt beside logs-baseline.txt

A path you could not verify is written in review.md as unverified, with the reason. It is never rounded up to a pass.


The release note

release-engineer/release-note.md, in the product's voice as the brand spec sets it. Read it aloud before it is final; if it sounds like marketing, rewrite it.

PartSays
HeadingThe tag and the date, in the brand spec's date format
What changedThe change, in the reader's terms, one short paragraph per change
What it means for the readerWhat they can now do, or what now behaves differently
What it does not do yetThe limits a reader will meet, stated plainly
FixedEach fix to behaviour that had shipped, one line each

No exclamation marks, no emoji, no word the brand spec's voice rules forbid. Every number carries its context, and every status its written label. Attribution follows § House rules: run the attribution check over the note.

When the change is user-facing, the note holds one section per locale in § Locales, the first-authored locale first, built word for word from the release-note strings ux-writer supplied. A locale missing from those strings is a rejection back to ux-writer, never a translation you write. The note stays in the repository and the run folder: sending or publishing it anywhere else is an outward-facing step for the Product Lead.

# v1.4.0 · 2 October 2026

## en

### Invoice export

Account owners can export a month of invoices as a CSV file from the billing page. Each file
holds one account and one calendar month, in the account's time zone, and its name says which.

The file your accountant asks for now comes straight from the product, with the same totals
you see on screen.

What it does not do yet: exports cover one month at a time, and they are not sent by email.

The example shows the first-authored section. In a product that also ships French and Arabic, ## fr and ## ar sections follow in the same shape, each built from ux-writer's strings for that locale.


The release log

release-engineer/release-log.md is the timed record of the release, and the evidence for the release gate. It opens with who authorised the release, the work branch with its commit count and the attribution result, and the hosting line. Then every step, with its shell time, the command or call, the result and the evidence path. Then the versions the target returned. Every attempt stays in the file, newest last, and a declined prompt, a refusal or a rollback is recorded where it happened.

# Release log · 2026-10-01-invoice-export

## Attempt 1

Authorised by: the qc-lead go written 2026-10-01T16:40:12Z (qc-lead/readiness.md); the
run.json gate list clean at 2026-10-02T09:05:51Z; push and tag to origin authorised in
ledger.md, decision line 41.
Work branch: feat/invoice-export, 3 commits, attribution check clean
(evidence/release/preflight-07-attribution.txt).
Front-end hosting: deferred: no target chosen

| Time (UTC) | Step | Command or call | Result | Evidence |
|---|---|---|---|---|
| 09:31:40 | 1 Rollback plan | rollback.md written | predates every change | release-engineer/rollback.md |
| 09:33:02 | 1 Log baseline | get_logs: api, postgres, edge-function | no errors in the hour | evidence/release/logs-baseline.txt |
| 09:35:18 | 2 Data layer | list_migrations | every file applied at build, none to apply | evidence/release/list-migrations.json |
| 09:36:40 | 3 Prove on the target | execute_sql, 3 test files | 41 of 41 ok | evidence/release/pgtap-project-invariants.tap |
| 09:39:12 | 4 Platform checks | get_advisors: security, performance | no findings | evidence/release/advisors-security.json |
| 09:41:05 | 5 Server functions | deploy_edge_function export-link, then a call | 200, logs clean | evidence/release/edge-export-link-200.txt |
| 09:47:30 | 6 Front end | npm ci at the root and in web/, then the build | green | evidence/release/build.txt |
| 09:55:48 | 8 Verify | Playwright on the local production build | 8 of 8 paths pass | evidence/release/smoke-billing-export-ar-320.png |
| 10:01:02 | 8 Logs | get_logs against the baseline | no new error class | evidence/release/logs-after.txt |
| 10:02:44 | 9 Tag and push | git tag -a v1.4.0, git merge --ff-only, git push | main at 4f2a9c1, v1.4.0 pushed | evidence/release/push.txt |
| 10:05:10 | 10 Release note | written, attribution check clean | en, fr, ar | release-engineer/release-note.md |

## Recorded versions

| | |
|---|---|
| Commit | 4f2a9c1 |
| Tag | v1.4.0 |
| Migrations on the target | 14 files, the newest 20261001120501_invoice_export_function |
| Server functions | export-link, version 1 |
| Front-end hosting | deferred: no target chosen |

The versions in this table, the tag, the commit and the target's own listings must agree with each other. A disagreement is a finding before the handoff, never after it.


Rollback

The plan, written first

release-engineer/rollback.md is written before the first change to any environment, with the shell timestamp on its first line to prove it. It states the previous release tag and commit, the server function versions before this release, the written reverse for every migration in the release ready to ship as a new migration file, the data restore point and its age, the time budget for the rollback, and who is told when it fires.

# Rollback plan · 2026-10-01-invoice-export

Written 2026-10-02T09:31:40Z, before the first change to any environment.

| | |
|---|---|
| Previous release | v1.3.2, commit 9c1e0b7 |
| Server functions before this release | invoice-webhook version 12. export-link is new in this release |
| Data restore point | The platform's daily backup from 2026-10-02T03:00:00Z, 6 hours old when this was written |
| Time budget | 20 minutes from the trigger to the previous version verified |
| Who is told | The Product Lead, through the orchestrator, at once |

## Migrations in this release

| Order | Migration | Kind | Compatible with the previous client | Reverse, and the file it ships as | Loses data |
|---|---|---|---|---|---|
| 1 | 20261001120501_invoice_export_function | additive | yes | Stays in place. If it must go: reverse-invoice_export_function.sql, shipped as <shell timestamp>_revert_invoice_export_function.sql | no |

## Order of reversal

1. Revert the three release commits and push the revert.
2. export-link has no previous version. The reverted client stops calling it, and it stays
   deployed and unused until the next release removes it.
3. The migration is backward compatible and stays.
4. Verify v1.3.2: list_migrations, the pgTAP suite through execute_sql, get_advisors and the
   smoke paths.

When to roll back

Roll back without waiting to be asked when any of these is true:

  • An invariant fails on the target. That is immediate and needs no discussion.
  • The error rate on a changed path rises above the pre-release baseline.
  • A flow the change touched is broken.
  • A migration did not complete cleanly.
  • Data is being written that cannot be corrected later.

The procedure

  1. Revert the release commits on the release branch and push the revert, through a rollback branch so the release branch still moves only by fast-forward. Never force-push.

    EV=.devteam/runs/<run-id>/evidence/release
    git fetch origin
    git switch -c rollback/<run-slug> origin/<release branch>
    git revert --no-edit <base>..<head>
    git log --format='%H%n%B' origin/<release branch>..HEAD > "$EV/rollback-commit-messages.txt"
    node "$EV/attribution-check.mjs" "$EV/rollback-commit-messages.txt"
    git switch <release branch>
    git merge --ff-only rollback/<run-slug>
    git push origin <release branch>

    The pushed release tag stays, because a tag others may have fetched is never moved or deleted. Tag the revert with the next version in the § Release format, so the tag list says what is live.

  2. Redeploy each server function from its source at the previous release tag (git show <previous tag>:<path>), exactly as it was, through the stack pack's path.
  3. Leave backward-compatible migrations in place. Where a migration was not backward compatible, ship its written reverse from rollback.md as a new migration file and apply it, in the order rollback.md states. A reverse that would discard rows written since the release is the one step that waits for the Product Lead's written decision. Until it comes, the previous code runs against the newer schema, which the backward-compatibility rule makes safe.
  4. Verify the previous version: the history listing, the on-target tests, the platform checks and the smoke paths, with evidence saved as rollback-<step>.txt.
  5. Write the rollback record in release-log.md: what happened, when, what was reverted, what data was affected, and what the fix will be.
  6. Tell the Product Lead at once, with facts. The release gate is fail. A rollback that restored service hands off rejected, with a blockers entry naming the failing check and the agent who owns the fix. One that did not fully restore service hands off escalated, with the decision in decisions_for_product_lead.

Roll back first and report second. The cause is worked out afterwards.


What goes to the Product Lead first

Confirm before, never after, unless it is already authorised for this run in writing:

  • A push, tag or release that run.json or ledger.md does not record as authorised.
  • Any destructive or irreversible migration, or a reverse that would lose data.
  • Choosing a hosting target, and any paid platform feature.
  • Anything that sends a message to a real user or customer.
  • Anything public beyond the repository: a published release note, a repository going public, a domain change.
  • A secret to set or rotate, and any secret found in history.
  • Releasing with a known blocker, which is the Product Lead's override to make and the orchestrator's to record in the ledger.
  • A pre-flight check that can pass only by breaking a brand or accessibility rule.
  • A rollback that did not fully restore service.

Evidence under evidence/release/

Every file the release produces sits under .devteam/runs/<run-id>/evidence/release/, with its shell time and its command at the top. Where the stack pack names its own file for one of these (for stack-nextjs-supabase, see the Evidence table in its Toolchain section), use its name.

FileHolds
preflight-<NN>-<check>.txtThe full output of pre-flight row NN
attribution-check.mjs, commit-messages.txtThe attribution check and the messages it read
scan-secrets.mjsThe secret scan whose output is preflight-05-secrets.txt
logs-baseline.txt, logs-after.txtThe platform's logs for the hour before the first change, and after the smoke
history-before.<ext>, history-after.<ext>The migration history before and after step 2
tables-after.<ext>The table listing after step 2
tests-offline.<ext>, tests-target-<test file>.<ext>The data-layer runs, each labelled with where it ran
platform-<check>.<ext>Each advisor or scanner's output, with any written acceptance beside it
functions-<name>-<case>.txt, functions-<name>-logs.txtA deployed function's call and its log lines
build.txtThe clean install and the build
smoke-<path>[-<locale>][-<width>].<ext>Each critical path
tag-annotation.txt, push.txtThe tag's annotation, and the output of every push
rollback-<step>.txtEach rollback step, when one fires

A key, a token or a password never appears in evidence. Redact the value and keep the name.


Before handing off

In addition to the four sub-gates in release-engineer's agent file:

  • rollback.md carries a shell timestamp earlier than the first change to any environment.
  • Every pre-flight row is in preflight.md, in order, with its saved output or a reason it does not apply.
  • The attribution check is clean on every commit, on the tag annotation and on the note.
  • release-log.md opens with the authorisation, the work branch and its commit count, the attribution result and the hosting line, and its versions agree with the target's listings.
  • Every critical path has its evidence, and the logs were compared with the baseline.
  • The release note covers every locale in § Locales when the change is user-facing.
  • release is the only entry in gates[], with release-engineer/release-log.md as its evidence, and the sub-gates are in review.md.
  • The PROJECT.md sections you relied on are cited by name in plan.md, and consumed lists the files you read as paths.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.