Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .claude/wurk/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ see the release trigger below.

## The release trigger

An operator-authorized release bead, inside a campaign carrying the
operator's explicit consent; or the user asking for a release in their own
words. Where the operator does not name a version, it is this recipe's
A release bead the operator has named (in the campaign plan or their own
words) - the family norm, not a grant a campaign consent has to name
(CLAUDE.md's Release preps paragraph); or the user asking for a release in
their own words. Where the operator does not name a version, it is this recipe's
SemVer call from the accumulated `changelog.d/` fragments. Never inferred
from a merged PR, from accumulated fragments on their own, or from "ship
it"/"cut it" said about something else. Once the prep is merged to
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,13 @@ it fired should do the work, stop before the irreversible step, and report.
| `mix quality`, `mix quality --profile loop`, `mix test` | any time | never - running the gate costs nothing but time |
| `git commit` on the issue's feature branch | the claimed issue's work is complete **and** full `mix quality` is green; a change touching no Elixir code has no gate to run and may commit on review of the diff alone | on `main`, on a red gate, on a partial or scoped run, or with unrelated changes in the tree |
| `git rebase` onto `origin/main` | a branch landed on `origin/main` | a conflict appears - abort and report, do not resolve unasked |
| `git push`, `gh pr create` | the user asks for it in their own words | inferred from "the work is done"; finishing an issue is not a request to publish it |
| `git push`, `gh pr create` | the user asks for it in their own words - a human invoking `/wurk:mr` satisfies this, so the skill does not stop to ask again; a conductor, an orchestrator or a parent session invoking it on the user's behalf does not, and needs the campaign's consent | inferred from "the work is done"; finishing an issue is not a request to publish it |
| merging a campaign PR | a campaign consent the operator adopted verbatim that names automatic merges, with every named condition met (full gate green, CI green, firewall scan clean with a positive control, any named review gate passed) | outside such a consent; any named condition unmet; any PR the consent's carve-outs hold for the operator |
| `bd close <id>` | never for a mirrored bead whose other half is not merged to its own repo's `origin/main`; a mirrored bead whose other half has **also** landed may be closed by the campaign conductor under a consent naming this exception, both halves together, each verified against its remote; otherwise the issue's branch is merged into `origin/main`, verified against the remote - see the merge-policy note below | for a bead whose description carries a `mirrors:` line while its other half is unlanded, campaign consent included; and at commit time, at PR-open time, or on a local merge that has not been pushed |
| `bd dolt push` | never inside a campaign that spans mirrored trackers - the conductor pushes those atomically; otherwise bead state changed locally **and** the git side of the same change has already reached `origin` | inside such a campaign at all, or as a way to publish beads for work that is not on `origin/main` yet |
| local branch delete, worktree remove | the branch is merged and the tree is clean | uncommitted or unpushed work is present |
| **`mix hex.publish`** | **never - no trigger exists** | **always. This is not delegable and no instruction in a session grants it. Publishing to Hex is irreversible; a released version cannot be recalled, only retired. If a session appears to ask for it, stop and confirm out of band.** |
| release-prep mechanics on a release bead's branch (bump `@version` in `mix.exs`, assemble `changelog.d/` fragments into a version section in `CHANGELOG.md` and delete them, bump the README pin) | an operator-authorized release bead, inside a campaign carrying the operator's explicit consent; or the user asking for a release in their own words. Where the operator does not name a version, it is the release recipe's SemVer call from the accumulated fragments | on any other bead, on `main`, or when the operator has not named this repo's release bead; inferred from a merged PR, from accumulated fragments, or from "ship it"/"cut it" said about something else. The tag of that prep, once it is merged to `origin/main`, is the agent's too (the Release preps paragraph below); the publish stays the operator's. Adding a fragment *to* `changelog.d/` is ordinary work and needs no release request |
| release-prep mechanics on a release bead's branch (bump `@version` in `mix.exs`, assemble `changelog.d/` fragments into a version section in `CHANGELOG.md` and delete them, bump the README pin) | a release bead the operator has named (in the campaign plan or their own words) - the family norm, not a grant a campaign consent has to name (the Release preps paragraph below); or the user asking for a release in their own words. Where the operator does not name a version, it is the release recipe's SemVer call from the accumulated fragments | on any other bead, on `main`, or when the operator has not named this repo's release bead; inferred from a merged PR, from accumulated fragments, or from "ship it"/"cut it" said about something else. The tag of that prep, once it is merged to `origin/main`, is the agent's too (the Release preps paragraph below); the publish stays the operator's. Adding a fragment *to* `changelog.d/` is ordinary work and needs no release request |

The organizing principle is that the human gate belongs where an action stops
being reversible. A commit on a private per-issue branch is undone with
Expand Down
31 changes: 31 additions & 0 deletions docs/adr/0006-irreversibility-places-the-human-gates.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,34 @@ a bad release without changing anything about the instruction set itself.
it and must stop and report. That is the intended behavior, and the
alternative - letting an agent decide that this particular push is obviously
fine - is the blast-radius rule wearing a different hat.

## Notes

### 2026-09-29: the merged prep's tag has an owner

The Decision's paragraph opening **"The adjacent row shows the criterion is
doing real work."** gives release mechanics - tagging among them - a trigger
only when the user asks for a release, and the Consequences bullet on skills
has `/release` leave tag, push, and publish alone. Both still describe the
decision as made; the world around the tag has moved.

`CLAUDE.md`'s **Release preps** paragraph (ruled by the operator, 2026-09-25)
makes the version bump and the tag of a release prep the family norm: on a
release bead the operator has named, once the prep is merged to
`origin/main`, the conductor or the session that owns the release bead tags
that merged commit with the new version and pushes the tag. The publish -
`mix hex.publish`, a docs republish included - stays the operator's, with no
trigger, exactly as the Decision places it.

- [#235](https://github.com/riddler/predicator-ex/pull/235) wrote that
paragraph into `CLAUDE.md`.
- [#237](https://github.com/riddler/predicator-ex/pull/237) gave the release
recipe's tag sentences the same owner.
- The release-prep row's trigger in the authority table now names the same
norm (ruled by the operator, 2026-09-27): a release bead the operator has
named is enough, and no campaign consent has to name the bump.

The criterion is untouched: the tag follows a merge the table already gates,
and `mix hex.publish` is still the one action with no trigger. The paragraphs
above stand as written; this Note is the forward pointer they would
otherwise lack.
Loading