Skip to content

Commit b74ace5

Browse files
committed
docs(release): say why the rebuild subshell's eval is not the banned one (#563)
The previous commit told operators to run `( eval "$REBUILD" )` in a file that carries a MUST NOT read the row with `eval` rule, added because blind exercise run 15 found `eval` executing declared row values before the gate that authorizes them. Left as-is, the two read as a contradiction, and the next reader has to guess which one governs. They are different acts. The ban is on evaluating a row's values while merely READING the row. This is executing a value as the command it was declared to be, at the step whose job is to execute it -- the same thing `run-pre-tag` already does for `pre-tag` commands. Reading is not execution; the rule is against confusing the two, not against ever running a declared command. Last skill edit before the proof exercise is staged. Claude-Session: https://claude.ai/code/session_01C8oamhuiaBjZnauWRGjb86
1 parent b725158 commit b74ace5

4 files changed

Lines changed: 4 additions & 4 deletions

File tree

‎core/surface/skills/release/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ Read these, or STOP and surface the gap — never guess:
144144
`0.0.0` is a placeholder that contradicts data already on disk, and the tag alone is only half the data. Deriving from the maximum honestly skips any versions the project already claimed but never tagged. **`<none>` is a sentinel, not a revision — derive `$WINDOW` from it before using it anywhere** (HIGH, adversarial review 2026-07-31, run 5): every command below spells the window `$WINDOW`, and `$WINDOW` is `${LAST_TAG}..HEAD` when a tag was found and bare `HEAD` when `LAST_TAG` is `<none>`. Substituting the sentinel into a range is a hard failure, not a soft one — `git log <none>..HEAD` exits 128 with `fatal: bad revision`. This is not an edge case: a consumer that has just declared its first target through the Back-fill lane has, by construction, no tag in that series, so the very first release of every back-filled project lands here. In shell: `if [ "$LAST_TAG" = "<none>" ]; then WINDOW=HEAD; else WINDOW="${LAST_TAG}..HEAD"; fi`. **Residual (MEDIUM, adversarial review 2026-07-31), documented rather than silently accepted:** this replacement fixes ancestry-based `git describe`'s failure mode in one direction (a sibling series' tag can no longer leak in) but has no ancestry awareness of its own in the OTHER direction — it resolves by highest SEMVER across every tag in the series, commit-graph reachability from HEAD notwithstanding. A tag pushed once from a branch of this series that was later abandoned permanently still counts as "highest tag in the series" forever after, raising the baseline for every subsequent release even though no released history actually contains it. `last_tag_select` has no way to detect that case; a project that hits it must remove the stray tag by hand (never simply retarget or delete a PUBLISHED one — see "Recovering from a bad release" below) rather than expect this helper to route around it.
145145
- **Scope the release window to `$PAYLOAD`:** the commit set is `git log $WINDOW -- $PAYLOAD`, NOT the whole repo — a `feat(some-other-target)` commit must not bump `$TARGET` or land in its changelog, and vice versa. This payload-scoped set must be non-empty; if empty, STOP — nothing to release for `$TARGET`.
146146
- **Manifest read:** read the `version` field of every path in `$MANIFEST` — a row may declare more than one. Phase 1 asserts the derived bump equals each of them and updates them — a tag whose version runs ahead of a manifest ships nothing, since a plugin/package installer typically no-ops on an unchanged version string. A path also listed in `$GENERATED_MANIFEST` is not "updated" directly — it is regenerated by the row's declared `generate` command, and the same equality assertion is what confirms the regeneration landed on the derived version.
147-
- **`$ARTIFACTS` freshness — rebuild unconditionally:** every release, regardless of whether the sources changed in the window, run the row's declared `rebuild` command (when one is declared) **in a subshell, so it cannot move this lane's working directory** — `( eval "$REBUILD" )` — and assert every path in `$ARTIFACTS` is in sync afterward (`git diff --quiet -- <each artifact>`). The subshell is the fix for a measured HIGH (blind exercise run 17), not a style preference: a declared `rebuild` commonly BEGINS with `cd` (this repository's own row is `cd <subdir> && npm run build`), the shell an operator runs this lane in persists between steps, and nothing here previously said to come back. From the subdirectory that leaves you in, three later gates fail silently rather than loudly — `git log $WINDOW -- $PAYLOAD` returns zero commits and fires the false "nothing to release" STOP on a full window; `git diff --quiet -- <artifact>` exits 0 without ever resolving the artifact, so the freshness gate passes while blind; and the clean-tree check reads a dirty tree as clean. Two of those block a release that should have succeeded and the third is a safety gate that stops looking at the thing it guards. A non-empty diff means a shipped bundle is stale — a release blocker, because a target ships the built file, not its source; commit the rebuild through `commit-gate` before tagging. Scope is `$TARGET` only: another target's stale bundle is that target's release problem, not this one's. A row declaring neither `rebuild` nor `artifacts` has nothing to assert here. (The old form gated the rebuild on an in-window source change and so missed a bundle that went stale *before* the window.)
147+
- **`$ARTIFACTS` freshness — rebuild unconditionally:** every release, regardless of whether the sources changed in the window, run the row's declared `rebuild` command (when one is declared) **in a subshell, so it cannot move this lane's working directory** — `( eval "$REBUILD" )` — and assert every path in `$ARTIFACTS` is in sync afterward (`git diff --quiet -- <each artifact>`). The subshell is the fix for a measured HIGH (blind exercise run 17), not a style preference: a declared `rebuild` commonly BEGINS with `cd` (this repository's own row is `cd <subdir> && npm run build`), the shell an operator runs this lane in persists between steps, and nothing here previously said to come back. From the subdirectory that leaves you in, three later gates fail silently rather than loudly — `git log $WINDOW -- $PAYLOAD` returns zero commits and fires the false "nothing to release" STOP on a full window; `git diff --quiet -- <artifact>` exits 0 without ever resolving the artifact, so the freshness gate passes while blind; and the clean-tree check reads a dirty tree as clean. Two of those block a release that should have succeeded and the third is a safety gate that stops looking at the thing it guards. **This `eval` is not the one the Targets section bans.** That rule forbids evaluating a row's values while merely READING the row, which runs operator shell before the gate that exists for it. Here the value is being deliberately EXECUTED as the command it was declared to be, at the step that executes it — the same thing `run-pre-tag` does for `pre-tag` commands. Reading is not execution; the ban is on confusing the two, not on ever running a declared command. A non-empty diff means a shipped bundle is stale — a release blocker, because a target ships the built file, not its source; commit the rebuild through `commit-gate` before tagging. Scope is `$TARGET` only: another target's stale bundle is that target's release problem, not this one's. A row declaring neither `rebuild` nor `artifacts` has nothing to assert here. (The old form gated the rebuild on an in-window source change and so missed a bundle that went stale *before* the window.)
148148

149149
## Phase 1 — Version & changelog · gate: BLOCK
150150

‎plugins/ca-codex/routines/release/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ Read these, or STOP and surface the gap — never guess:
144144
`0.0.0` is a placeholder that contradicts data already on disk, and the tag alone is only half the data. Deriving from the maximum honestly skips any versions the project already claimed but never tagged. **`<none>` is a sentinel, not a revision — derive `$WINDOW` from it before using it anywhere** (HIGH, adversarial review 2026-07-31, run 5): every command below spells the window `$WINDOW`, and `$WINDOW` is `${LAST_TAG}..HEAD` when a tag was found and bare `HEAD` when `LAST_TAG` is `<none>`. Substituting the sentinel into a range is a hard failure, not a soft one — `git log <none>..HEAD` exits 128 with `fatal: bad revision`. This is not an edge case: a consumer that has just declared its first target through the Back-fill lane has, by construction, no tag in that series, so the very first release of every back-filled project lands here. In shell: `if [ "$LAST_TAG" = "<none>" ]; then WINDOW=HEAD; else WINDOW="${LAST_TAG}..HEAD"; fi`. **Residual (MEDIUM, adversarial review 2026-07-31), documented rather than silently accepted:** this replacement fixes ancestry-based `git describe`'s failure mode in one direction (a sibling series' tag can no longer leak in) but has no ancestry awareness of its own in the OTHER direction — it resolves by highest SEMVER across every tag in the series, commit-graph reachability from HEAD notwithstanding. A tag pushed once from a branch of this series that was later abandoned permanently still counts as "highest tag in the series" forever after, raising the baseline for every subsequent release even though no released history actually contains it. `last_tag_select` has no way to detect that case; a project that hits it must remove the stray tag by hand (never simply retarget or delete a PUBLISHED one — see "Recovering from a bad release" below) rather than expect this helper to route around it.
145145
- **Scope the release window to `$PAYLOAD`:** the commit set is `git log $WINDOW -- $PAYLOAD`, NOT the whole repo — a `feat(some-other-target)` commit must not bump `$TARGET` or land in its changelog, and vice versa. This payload-scoped set must be non-empty; if empty, STOP — nothing to release for `$TARGET`.
146146
- **Manifest read:** read the `version` field of every path in `$MANIFEST` — a row may declare more than one. Phase 1 asserts the derived bump equals each of them and updates them — a tag whose version runs ahead of a manifest ships nothing, since a plugin/package installer typically no-ops on an unchanged version string. A path also listed in `$GENERATED_MANIFEST` is not "updated" directly — it is regenerated by the row's declared `generate` command, and the same equality assertion is what confirms the regeneration landed on the derived version.
147-
- **`$ARTIFACTS` freshness — rebuild unconditionally:** every release, regardless of whether the sources changed in the window, run the row's declared `rebuild` command (when one is declared) **in a subshell, so it cannot move this lane's working directory** — `( eval "$REBUILD" )` — and assert every path in `$ARTIFACTS` is in sync afterward (`git diff --quiet -- <each artifact>`). The subshell is the fix for a measured HIGH (blind exercise run 17), not a style preference: a declared `rebuild` commonly BEGINS with `cd` (this repository's own row is `cd <subdir> && npm run build`), the shell an operator runs this lane in persists between steps, and nothing here previously said to come back. From the subdirectory that leaves you in, three later gates fail silently rather than loudly — `git log $WINDOW -- $PAYLOAD` returns zero commits and fires the false "nothing to release" STOP on a full window; `git diff --quiet -- <artifact>` exits 0 without ever resolving the artifact, so the freshness gate passes while blind; and the clean-tree check reads a dirty tree as clean. Two of those block a release that should have succeeded and the third is a safety gate that stops looking at the thing it guards. A non-empty diff means a shipped bundle is stale — a release blocker, because a target ships the built file, not its source; commit the rebuild through `commit-gate` before tagging. Scope is `$TARGET` only: another target's stale bundle is that target's release problem, not this one's. A row declaring neither `rebuild` nor `artifacts` has nothing to assert here. (The old form gated the rebuild on an in-window source change and so missed a bundle that went stale *before* the window.)
147+
- **`$ARTIFACTS` freshness — rebuild unconditionally:** every release, regardless of whether the sources changed in the window, run the row's declared `rebuild` command (when one is declared) **in a subshell, so it cannot move this lane's working directory** — `( eval "$REBUILD" )` — and assert every path in `$ARTIFACTS` is in sync afterward (`git diff --quiet -- <each artifact>`). The subshell is the fix for a measured HIGH (blind exercise run 17), not a style preference: a declared `rebuild` commonly BEGINS with `cd` (this repository's own row is `cd <subdir> && npm run build`), the shell an operator runs this lane in persists between steps, and nothing here previously said to come back. From the subdirectory that leaves you in, three later gates fail silently rather than loudly — `git log $WINDOW -- $PAYLOAD` returns zero commits and fires the false "nothing to release" STOP on a full window; `git diff --quiet -- <artifact>` exits 0 without ever resolving the artifact, so the freshness gate passes while blind; and the clean-tree check reads a dirty tree as clean. Two of those block a release that should have succeeded and the third is a safety gate that stops looking at the thing it guards. **This `eval` is not the one the Targets section bans.** That rule forbids evaluating a row's values while merely READING the row, which runs operator shell before the gate that exists for it. Here the value is being deliberately EXECUTED as the command it was declared to be, at the step that executes it — the same thing `run-pre-tag` does for `pre-tag` commands. Reading is not execution; the ban is on confusing the two, not on ever running a declared command. A non-empty diff means a shipped bundle is stale — a release blocker, because a target ships the built file, not its source; commit the rebuild through `commit-gate` before tagging. Scope is `$TARGET` only: another target's stale bundle is that target's release problem, not this one's. A row declaring neither `rebuild` nor `artifacts` has nothing to assert here. (The old form gated the rebuild on an in-window source change and so missed a bundle that went stale *before* the window.)
148148

149149
## Phase 1 — Version & changelog · gate: BLOCK
150150

‎plugins/ca-pi/routines/release/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ Read these, or STOP and surface the gap — never guess:
144144
`0.0.0` is a placeholder that contradicts data already on disk, and the tag alone is only half the data. Deriving from the maximum honestly skips any versions the project already claimed but never tagged. **`<none>` is a sentinel, not a revision — derive `$WINDOW` from it before using it anywhere** (HIGH, adversarial review 2026-07-31, run 5): every command below spells the window `$WINDOW`, and `$WINDOW` is `${LAST_TAG}..HEAD` when a tag was found and bare `HEAD` when `LAST_TAG` is `<none>`. Substituting the sentinel into a range is a hard failure, not a soft one — `git log <none>..HEAD` exits 128 with `fatal: bad revision`. This is not an edge case: a consumer that has just declared its first target through the Back-fill lane has, by construction, no tag in that series, so the very first release of every back-filled project lands here. In shell: `if [ "$LAST_TAG" = "<none>" ]; then WINDOW=HEAD; else WINDOW="${LAST_TAG}..HEAD"; fi`. **Residual (MEDIUM, adversarial review 2026-07-31), documented rather than silently accepted:** this replacement fixes ancestry-based `git describe`'s failure mode in one direction (a sibling series' tag can no longer leak in) but has no ancestry awareness of its own in the OTHER direction — it resolves by highest SEMVER across every tag in the series, commit-graph reachability from HEAD notwithstanding. A tag pushed once from a branch of this series that was later abandoned permanently still counts as "highest tag in the series" forever after, raising the baseline for every subsequent release even though no released history actually contains it. `last_tag_select` has no way to detect that case; a project that hits it must remove the stray tag by hand (never simply retarget or delete a PUBLISHED one — see "Recovering from a bad release" below) rather than expect this helper to route around it.
145145
- **Scope the release window to `$PAYLOAD`:** the commit set is `git log $WINDOW -- $PAYLOAD`, NOT the whole repo — a `feat(some-other-target)` commit must not bump `$TARGET` or land in its changelog, and vice versa. This payload-scoped set must be non-empty; if empty, STOP — nothing to release for `$TARGET`.
146146
- **Manifest read:** read the `version` field of every path in `$MANIFEST` — a row may declare more than one. Phase 1 asserts the derived bump equals each of them and updates them — a tag whose version runs ahead of a manifest ships nothing, since a plugin/package installer typically no-ops on an unchanged version string. A path also listed in `$GENERATED_MANIFEST` is not "updated" directly — it is regenerated by the row's declared `generate` command, and the same equality assertion is what confirms the regeneration landed on the derived version.
147-
- **`$ARTIFACTS` freshness — rebuild unconditionally:** every release, regardless of whether the sources changed in the window, run the row's declared `rebuild` command (when one is declared) **in a subshell, so it cannot move this lane's working directory** — `( eval "$REBUILD" )` — and assert every path in `$ARTIFACTS` is in sync afterward (`git diff --quiet -- <each artifact>`). The subshell is the fix for a measured HIGH (blind exercise run 17), not a style preference: a declared `rebuild` commonly BEGINS with `cd` (this repository's own row is `cd <subdir> && npm run build`), the shell an operator runs this lane in persists between steps, and nothing here previously said to come back. From the subdirectory that leaves you in, three later gates fail silently rather than loudly — `git log $WINDOW -- $PAYLOAD` returns zero commits and fires the false "nothing to release" STOP on a full window; `git diff --quiet -- <artifact>` exits 0 without ever resolving the artifact, so the freshness gate passes while blind; and the clean-tree check reads a dirty tree as clean. Two of those block a release that should have succeeded and the third is a safety gate that stops looking at the thing it guards. A non-empty diff means a shipped bundle is stale — a release blocker, because a target ships the built file, not its source; commit the rebuild through `commit-gate` before tagging. Scope is `$TARGET` only: another target's stale bundle is that target's release problem, not this one's. A row declaring neither `rebuild` nor `artifacts` has nothing to assert here. (The old form gated the rebuild on an in-window source change and so missed a bundle that went stale *before* the window.)
147+
- **`$ARTIFACTS` freshness — rebuild unconditionally:** every release, regardless of whether the sources changed in the window, run the row's declared `rebuild` command (when one is declared) **in a subshell, so it cannot move this lane's working directory** — `( eval "$REBUILD" )` — and assert every path in `$ARTIFACTS` is in sync afterward (`git diff --quiet -- <each artifact>`). The subshell is the fix for a measured HIGH (blind exercise run 17), not a style preference: a declared `rebuild` commonly BEGINS with `cd` (this repository's own row is `cd <subdir> && npm run build`), the shell an operator runs this lane in persists between steps, and nothing here previously said to come back. From the subdirectory that leaves you in, three later gates fail silently rather than loudly — `git log $WINDOW -- $PAYLOAD` returns zero commits and fires the false "nothing to release" STOP on a full window; `git diff --quiet -- <artifact>` exits 0 without ever resolving the artifact, so the freshness gate passes while blind; and the clean-tree check reads a dirty tree as clean. Two of those block a release that should have succeeded and the third is a safety gate that stops looking at the thing it guards. **This `eval` is not the one the Targets section bans.** That rule forbids evaluating a row's values while merely READING the row, which runs operator shell before the gate that exists for it. Here the value is being deliberately EXECUTED as the command it was declared to be, at the step that executes it — the same thing `run-pre-tag` does for `pre-tag` commands. Reading is not execution; the ban is on confusing the two, not on ever running a declared command. A non-empty diff means a shipped bundle is stale — a release blocker, because a target ships the built file, not its source; commit the rebuild through `commit-gate` before tagging. Scope is `$TARGET` only: another target's stale bundle is that target's release problem, not this one's. A row declaring neither `rebuild` nor `artifacts` has nothing to assert here. (The old form gated the rebuild on an in-window source change and so missed a bundle that went stale *before* the window.)
148148

149149
## Phase 1 — Version & changelog · gate: BLOCK
150150

0 commit comments

Comments
 (0)