CLI Verification Caching Planning Research
CLI Verification Caching Planning Research
Scope and method
Two readonly lanes inspected the current Turbo/CI trust surface and the CLI test/release/build surface. The coordinator checked the resolved Turbo 2.9.14 graph, the accepted spec, prior performance handoffs, and upstream Turborepo and GitHub Actions documentation. This report records current facts and planning constraints; it does not replace the accepted product decisions in the spec.
Facts
Current task and release graph
- Root
release:publishenters the uncached@punks/cli#release:publishTurbo task, butapps/cli/scripts/publish-release.mjsinternally invokes build, release tests, and typecheck withnpm run. Turbo therefore cannot cache those nested verification commands. Sources:package.json,apps/cli/package.json,turbo.json,apps/cli/scripts/publish-release.mjs. - The initial npm version probe already precedes deterministic verification and preserves the existing-version retry path. The new-version path does not probe npm again immediately before
npm publish. Source:apps/cli/scripts/publish-release.mjs. apps/cli/scripts/run-release-tests.mjsruns the four update wrappers together before the ordinary suite. Its public process contract asserts the exact two waves. Sources:apps/cli/scripts/run-release-tests.mjsandapps/cli/src/scripts/run-release-tests.test.ts.- Current release inventory is 84 ordinary test files containing 1,118 tests and four update wrappers containing 96 registered cases. The four wrappers contain 24 cases each and their union is exact. Source:
apps/cli/src/update/run.shards.test.ts; verified with Vitest list output on 2026-08-06.
Missing cache-policy contracts
- Ordinary files currently join release execution through a broad Vitest glob; there is no file-level cache policy. Registered update cases carry timeout selection but no cache-policy metadata. No contract can reject missing or duplicate cache assignments. Sources:
apps/cli/vitest.config.ts,apps/cli/src/update/run.test-cases.test.ts, andapps/cli/src/update/run.shards.test.ts. - The real child-process wall-clock assertion in
apps/cli/src/data/scripts/sync-subagents.test.tsusesDate.now(), a real spawned process, and an OS timeout. It is the accepted uncached exception. - Existing loopback tests own ephemeral local servers. Other timer-based tests still need classification evidence; a loopback address or timer alone does not prove deterministic cache eligibility.
- The implemented CLI runtime-hardening work rejected wider file parallelism after GitHub subprocess starvation. Cache task decomposition must not revive that rejected scheduling change.
Build identity and restoration gaps
apps/cli/scripts/build-dist.mjscopies rootCHANGELOG.mdandBASELINE_CHANGELOG.mdintodist, but the resolved CLI build inputs omit both root files.- The same build rewrites tracked
apps/cli/src/data/bundled-baseline-identity.generated.ts. Its previous bytes are included by$TURBO_DEFAULT$, while the declared outputs restore onlydist/**. The generated identity therefore influences its own next hash and cannot be restored as part of a cache hit. - The repository does not currently have an automated contract for forced execution, first miss, second hit, deleted-output restoration, each declared invalidation input, fresh/cached byte equality, or clean fresh/cached worktree paths.
Runtime and trust boundaries
- CI pins Bun 1.3.5 and Node 24. The repository pins Bun 1.3.5 and Turbo 2.9.14, but does not pin Node or explicitly hash OS, architecture, Bun, and Node identity into CLI verification tasks. The planning host used Node 22.22.0, so the mismatch is observable rather than hypothetical.
- The Behavior Contract workflow has a SHA-keyed local Turbo cache action. The dedicated CLI ordinary and update jobs do not restore it. No repository configuration currently supplies a remote-cache token, signature key, development/protected namespace, or same-repository fork predicate. Source:
.github/workflows/behavior-contract.yml. - GitHub documents that fork pull-request workflows do not receive Actions secrets. It also warns that cache contents are not signed or verified and must be treated as untrusted input. Sources: https://docs.github.com/en/code-security/reference/secret-security/secret-types and https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching.
- Turborepo signs remote artifacts with HMAC-SHA256 when
remoteCache.signatureis enabled and rejects missing or invalid signatures. Its own documentation calls this integrity/authenticity defense-in-depth, not a complete security boundary. Source: https://turborepo.com/docs/core-concepts/remote-caching, cross-checked against the upstreamvercel/turborepodocumentation source retrieved on 2026-08-06. - Turborepo documents that
passThroughEnvvalues are available at runtime but do not contribute to task hashes. Current CLI tests pass through HOME, TMPDIR, Git configuration, and XDG roots. Those values cannot affect cacheable tasks unless controlled or moved into hashed environment identity. Source: https://turborepo.com/docs/reference/configuration andturbo.json.
Inferences for planning
- The public release seam must remain an uncached dispatcher. Static Turbo dependencies cannot preserve the accepted initial npm probe because they run before the package script. The dispatcher can route only a new version through deterministic Turbo verification and then enter an uncached mutation executor.
- Task-level caching requires separate cacheable and uncached processes. A title filter inside one shared Vitest process is not an eligible cache boundary.
- The test-policy module should own discovery and exact assignment. New ordinary files can resolve to uncached by default, while registered update cases must declare metadata explicitly so missing and duplicate declarations fail.
- Development and protected use need separate hashed namespace values and separate signing authority. Forks must receive neither remote-cache nor signing credentials. A generic repository contract can name these inputs without binding delivery to one remote-cache vendor.
- The generated bundled identity should be excluded from build inputs and added to declared outputs. Narrowing build inputs further would be risky because the current broad package input set conservatively covers bundled data, skills, package metadata, and build scripts.
- The shared task topology in
turbo.json, package scripts, release runner, workflow, and root behavior contract should have one integration owner. The policy, build-byte, and release-dispatcher modules can be implemented in parallel before that integration step.
Conflicts and uncertainty
- No remote-cache provider credentials or protected release environment exist in repository state. Repository delivery can define and test the generic credential/namespace contract, but a real local-to-PR remote hit needs external credentials configured by the repository operator.
- Turborepo signatures verify an artifact against a shared secret but do not by themselves prove who ran the producing task. Separate credentials, namespace hashing, protected secret access, declared inputs/outputs, and release eligibility checks must work together.
- Existing runbooks say operators should commit the regenerated bundled identity and rerun release. That workflow contradicts the accepted derived-output and clean-worktree contract and must change during docs ingest.
Planning conclusion
Use three disjoint first-wave tracer bullets: exact test-policy inventory, reproducible build outputs, and pre-probe release dispatch. Follow with one shared Turbo/package graph integration task. After graph integration, wire same-repo PR development-cache credentials and protected release authority separately, then run one acceptance task covering force/miss/hit/restoration/invalidation, uncached execution, byte equality, and clean repository state.