Skip to content

ci(release): decide changeset coverage from turbo's build hashes - #6

Open
systemfsoftware-maker wants to merge 5 commits into
mainfrom
ci/release-turbo-gate
Open

systemfsoftware-maker wants to merge 5 commits into
mainfrom
ci/release-turbo-gate

Conversation

@systemfsoftware-maker

@systemfsoftware-maker systemfsoftware-maker commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The Changeset Check now decides which packages need an intent by turbo build hash, not by path. release.jsonc changes from { "strategy": "paths" } to { "strategy": "turbo", "task": "build" }. pnpm-release-management is pinned at b9b4a1d. At that pin, a publishable package needs an intent when its build task hash differs between the base and the head (process-adapter/src/change-verdict.ts verdictTouched, evidence from turbo-evidence.ts). systemfsoftware and pnpm-release-management already use the same gate.

The old comment said the turbo gate dry-runs turbo at the base and refuses a base without turbo.json. That is out of date: main has carried turbo.json since b7e907a (#2). .changeset/README.md now states the turbo rule. Its 0.0.0 caveat now keys on a change to the tarball rather than to the packages/api-extractor path.

.changeset/README.md:12-15 also says that files outside the package can move the hash, with measured examples (below), and that such a pull request names the package, with none when its tarball does not change.

No package, app or intent changes, and the shipped tarball is unchanged.

Turbo evidence (turbo 2.10.12, the catalog pin)

Gate proof

Each run is changeset-management check origin/main, using the release tools from this repo's dev shell (the b9b4a1d pin), against base 9bd7396, which has turbo.json. Each control was a throwaway local commit on top of this branch. The controls are deleted, and no base worktree was left behind.

Change on top of this head turbo gate paths gate
this PR alone exit 0, "no publishable package changed" (same, on main)
A: comment in packages/api-extractor/src/mod.ts exit 1, names @systemfsoftware/api-extractor —
B: turbo.json build.env gains a variable exit 1, names the package exit 0 (misses it)
C: comment in apps/api-extractor-e2e/outputs.ts (private) exit 0 —
D: PR #5 merged in exit 0 —
E: packages/api-extractor/stryker.config.ts only (not in files) exit 1 exit 1 (same path rule)

Under turbo build, any file in the package's default inputs counts as a change. E shows the cost: a change outside the tarball still needs an intent, and its intent is none. The paths gate behaves the same here, so this is not a regression.

CI on this head is the proof that the gate runs inside the devshell: true sandbox. No sibling caller runs turbo with devshell today: stryker-js-effect uses devshell with paths.

Credentials

The gate runs turbo run build --dry=json with no TURBO_TOKEN/TURBO_TEAM. ci.yml and changeset-check.yml pass neither, so turbo uses only the local cache and a public-repo run gets no credentials.

Code review (full path, report-only, final head 0a72fe4)

ce-code-review mode:agent base:origin/main depth:full ran against head 0a72fe466b7851d3392b5bc1232eeec4e55a90d4, base cd9f133d595faf51e319b4fe9990d3b1d8eede1c (main after #8). Run 20261013-093026-84707681. CE verdict: Ready to merge. The review has no actionable finding. The one pack-rule breach is a departure the root accepted (below), and the PR waits for the root's verdict; it is not merged.

Rounds on this PR:

  • da6d7f2: full path, run …856eb364.
  • a2aa2cd: plain merge of origin/main (cd9f133, chore(packs): enroll ci-wall-clock and repin compound packs to 8d07118079 #8). The merge changes only .compound-engineering/config.yaml, which pins 8d071180798f9ea82ef5a4fd877152dacc02f6b6 and enrolls ci-wall-clock; nothing was rebased or force-pushed. Full-path delta round, run …d26930a4.
  • 12e09fe: README fix. Round …3e69dc8e is superseded, because its two findings were fixed in the next commit.
  • 0a72fe4: this receipt.

Packs: resolved from this head's own .compound-engineering/config.yaml. --declared-only exit 0 with no errors; full resolve exit 0 with no errors or warnings, five roots at 8d071180798f9ea82ef5a4fd877152dacc02f6b6. The packs hold 50 rule files: cell-architecture 15, boundary-testing 6, schema-laws 10, package-topology 7, ci-wall-clock 12. Applicability was decided by meaning (CONST-T12), and systemfsoftware CONSTITUTION.md outranks pack rules (CONST-S2).

Reviewers: correctness, project standards (AGENTS.md plus the constitution), learnings with the per-rule receipt, reliability, security, and adversarial. All six ran as omp agents, and their session logs show only deepseek/deepseek-v4.1-flash turns through OpenRouter, with no Claude, Grok or xAI turn. The non-Claude peer is omp's adversarial-reviewer on deepseek-v4.1-flash (20 turns); no external peer CLI is installed. No reviewer timed out: the longest slice took 7 m 24 s, under the 15-minute bound. In the a2aa2cd round the adversarial slice took 15 m 03 s but returned a complete, schema-valid artifact.

Declared breach: ci-wall-clock/light-checks-run-the-pinned-tool (accepted by root ruling)

  • Rule: a changeset check runs its pinned tool directly, with no workspace install, dev shell or producer job before it, and the tool comes from one pinned fetch.
  • Breach: release.jsonc:13 ("gate": { "strategy": "turbo", "task": "build" }) makes the check take turbo from the workspace install. pnpm-release-management b9b4a1d packages/process-adapter/src/turbo-evidence.ts:27 reads TURBO_BIN = 'node_modules/.bin/turbo', and :106-116 refuses with TurboPinUnusable when it is absent. So this check can never run install-free; the paths strategy needed only git.
  • Root ruling, 2026-10-13, D1 = (a): the departure is accepted for this PR, and the cure is commissioned upstream.
  • Cure and owner: unit R, in systemfsoftware/pnpm-release-management: the turbo evidence takes turbo from one pinned fetch instead of the workspace install. After that ships, a later follow-up removes devshell: true at .github/workflows/changeset-check.yml:17 and repins pnpm-release-management, so the check runs install-free. Neither change belongs in this PR.
  • The job already bootstraps: changeset-check.yml:17 passes devshell: true, so the job already runs the dev shell and this repository's bootstrap before the verdict, on main as well. Measured: the check job took 7 m 44 s on da6d7f2 (run 38298009147) and 8 m 28 s on ci(mutation): run mutation testing on main through systemfsoftware's reusable workflow #5's head with the paths gate (run 38291945550); it took 6 m 39 s on this head (run 38376783369). The bootstrap dominates every one of these runs, so the switch adds no measured wall time today. The breach is structural.
  • Going back to paths is closed: upstream #56 (merged as 2b89555) refuses the paths gate where turbo drives the comparison.

Findings and dispositions

Round # Sev File:line Finding Disposition
0a72fe4 1 P2 (conf 50, advisory) .changeset/README.md:15 The check cannot tell whether a hash-moving change also changed the tarball, so a none intent is the author's judgement. Accepted as residual; no change in this PR. While the version is 0.0.0, Release refuses a misjudged none with tag-integrity-mismatch, and the README's 0.0.0 bullet says so. Guidance in the refusal text belongs to pnpm-release-management (gate-changes.ts:104-118).
a2aa2cd — P2 .changeset/README.md:10 A change to a global input moves every package's hash, and the README did not say so. Two validators confirmed it. Fixed in 12e09fe and 0a72fe4: README.md:12-15 names measured examples.
a2aa2cd — P2 release.jsonc:13 The check takes turbo from the workspace install. Validator confirmed. Accepted departure (D1 = a, above).
a2aa2cd — P2 (conf 50, suppressed) release.jsonc:13 A build task that vanishes reads as unchanged (change-verdict.ts:35,54). Refuted at turbo 2.10.12: the probe kept the task with command <NONEXISTENT> and its hash moved.
12e09fe — P3 .changeset/README.md:13 The new list of inputs read as complete but omitted lockfile resolutions and other root dependency fields. Fixed in 0a72fe4: the inputs are now given as examples.
12e09fe — P2 (conf 50) release.jsonc:13 The turbo dry run has no timeout. Upstream. This repository's job only calls the reusable workflow, and GitHub does not allow timeout-minutes on such a job. The bound belongs in pnpm-release-management's changeset-check.yml and ProcessRun.ts.
da6d7f2 2 P2 release.jsonc:13 The gate depends on the workspace install. Rejected by that round's validator: the pinned check installs the workspace first.
da6d7f2 3 P3 1146 plan :244 KTD19 still says paths. D2 (does not block; the plan is frozen).

Pack receipt (0a72fe4)

Pack / rule Ruling At
ci-wall-clock / light-checks-run-the-pinned-tool violate, accepted departure (D1 = a) release.jsonc:13
ci-wall-clock / pr-gate-runs-only-what-the-diff-affects comply: the affected set comes from turbo's build graph instead of a hand-kept path list, and this receipt satisfies the rule's full-path review clause release.jsonc:13; .changeset/README.md:10-15

Not matched (48):

  • ci-wall-clock (10): flake-outputs-build-once-on-main, gates-share-one-job, heavy-lanes-state-their-daily-minutes, heavy-suites-judge-every-main-commit, no-dev-shell-on-verdict-lanes, no-lane-grades-the-repos-own-docs, one-stuck-job-backstop, red-main-reverts-its-culprit-same-day, release-promotes-the-staging-proven-build, setup-under-a-minute.
  • cell-architecture (15), boundary-testing (6), schema-laws (10) and package-topology (7): every rule. The diff changes no code, schema, entry point, port, adapter or published surface.

The devshell: true at changeset-check.yml:17 predates this diff. light-checks-run-the-pinned-tool and setup-under-a-minute both refuse that shape on their own, and the follow-up above removes it.

Still open, outside this PR

  • D2 (does not block): the frozen 1146 plan has stale rows at :244 (this PR's da6d7f2 finding 3) and :446 (chore(packs): enroll ci-wall-clock and repin compound packs to 8d07118079 #8's finding 5). The fix is a new dated plan declaring supersedes:, or the PR bodies serve as the record.
  • Residual risks reviewers recorded:
    • Nothing committed exercises the gate's refusal; controls A-E and the probes were throwaway.
    • The gate compares the base ref's tip, not the merge base.
    • A later turbo.json change to build.inputs could narrow what the gate sees.
    • Branches cut before this merge keep running the paths gate until they merge main.

CI at 0a72fe4

CI run 38376783225: all 11 jobs succeeded. Changeset Check run 38376783369: success. 12 of 12 jobs are green. The PR has no review threads and no comments.

ce-compound

ce-compound was evaluated for this PR (mode non-interactive) and ended Documentation skipped. The skip stands: the lesson worth keeping, install-free light checks versus turbo-native change evidence, waits on unit R's upstream fix.

The release gate moves from `paths` to `{ strategy: turbo, task: build }`.
`main` has carried turbo.json since b7e907a, so the comment saying the turbo
gate refuses this repository's base is stale. A publishable package now needs
an intent when its turbo `build` hash moves between base and head
(pnpm-release-management b9b4a1d change-verdict.ts verdictTouched), the gate
systemfsoftware and pnpm-release-management use. .changeset/README.md states
the same rule and keys the 0.0.0 `none` caveat on the tarball.
The turbo gate asks for an intent when a package's `build` hash moves,
and files outside the package can move it. turbo 2.10.12's dry run moved
@systemfsoftware/api-extractor#build on a change to turbo.json's `build`
task, to `globalEnv`, and to the root package.json's dependencies, and
not on root metadata, a lockfile comment or another task's settings.
The README now names those inputs, so a contributor can see why such a
pull request owes an intent (`none` when the tarball is unchanged).
The list of outside-package inputs that move a package's `build` hash
named only the three that turbo 2.10.12's dry run was measured on, but
read as complete. Review named other inputs turbo also folds in (lockfile
resolutions, other root dependency fields). The sentence now gives the
measured inputs as examples; the gate itself compares turbo's hashes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant