test(cli): derive the packaged closure and install it offline - #1314
Conversation
The packed set was a hand-written list of eight directories. `bin` declares `@cotal-ai/runtime` as a workspace dep and that package was not in the list, so `npm install` resolved it from the registry. On main the current version is published, npm fetches it, and the install succeeds — so a cell named "installed current packaged closure" has been passing while one member came from npm rather than from this build. The hole is invisible until the version being packed is unpublished, which is exactly a release PR: #1305 fails here at 1 !== 0. Two changes, and the second is the one that generalises. The set is now DERIVED as the transitive workspace closure of `cotal-ai`. That is not a tidier way to write the same list: it finds ten members where the hand-list had eight. I found `implementations/ runtime` by inspection and missed `packages/lang`, which the derivation caught. A corrected hand-list would have shipped with the same class of defect it was correcting. The install now runs `--offline`. Without it, a member missing from the closure is fetched from the registry and the install succeeds by luck, which is precisely how this survived. With it, any registry reach fails loudly and names the package. Derivation fixes today's membership; offline makes tomorrow's omission impossible to pass unnoticed — and there is already a tomorrow: #1312 adds `packages/seat`, which npm has never published at any version. Refs #1305
packed.txt was a shell redirect from deriving the closure by hand. It is referenced by nothing, and its contents are the OLD eight-member list without runtime or lang — the exact list this PR exists to replace. A reader re-deriving from it would recover the defect being fixed. It reached the branch through `git add -A` after a `> packed.txt` in a scratch command. Found by review, not by me.
|
BLOCK The derivation is right. 1. Derivation — verified independentlyWalked
A corrected hand-list that added only Residual, not the block: discovery is two directory levels plus a 2.
|
| case | result |
|---|---|
| empty cache, parent+child tarballs | 0 |
| empty cache, parent tarball only (missing workspace sibling) | ENOTCACHED (this is the intended guard) |
cache warmed by installing the sibling tarball, then parent-only --offline |
still ENOTCACHED (local tarball in cache is not a registry response) |
empty cache, tarball whose production dep is registry ms |
ENOTCACHED ms |
| same tarball after an online install into that cache | --offline 0, ms installed from cache |
empty cache, optionalDependencies missing or uncached |
0, optional skipped |
real pnpm pack of packages/lang, then --offline under isolated HOME |
ENOTCACHED https://registry.npmjs.org/acorn |
@cotal-ai/lang production deps: acorn, json-canonicalize, ses. The ten-member closure has 25 external production deps (acorn, @nats-io/*, react, zod, …). Optional @eplightning/nats-server-* will be skipped under --offline; that is not the failure.
So --offline does cover the original hole (a missing workspace member is not satisfied by a warmed tarball cache, and is not fetched). It also covers the path the old suite used successfully: third-party registry GETs. Isolated HOME means the runner's pnpm store and any operator ~/.npm are irrelevant. The current install runs before the online cotal-ai@0.42.0 install, so that cannot warm the cache either.
Net: a complete derived closure still fails installed current packaged closure with ENOTCACHED on acorn (or the next uncached production dep). This will red shard 3 on every PR, including the release it is meant to unblock.
The intended guard is "do not fetch @cotal-ai/* from the registry". --offline is "do not fetch anything". Those are not the same. A fix has to keep the first without imposing the second: local verdaccio / npm install of the tarballs without --offline plus an assertion that every @cotal-ai/* resolved from a file: tarball, or copy a warmed npm cache of only third-party packages into the isolated HOME, or drop HOME isolation for npm's cache while still isolating ~/.cotal. Do not ship --offline against an empty cache.
Tarball-count assertion: message includes both numbers and [...needed].join(", "). A short pack fails. Vacuous path: if cotal-ai is missing from the map, needed.size === 0 === tarballs.length and the count passes; then npm install --offline of zero tarballs can still exit 0. Not today's layout. Not the block.
Windows shard 3 listed pnpm smoke:legacy-packaged-manager and then FAILED at: pnpm smoke:mesh-attach-plane — this suite is in that shard's never-ran list. So Windows did not execute the new install. Linux shard 3 was still in_progress when this was posted; treat that as UNMEASURED, not green.
3. Scope and hygiene
packed.txt: addressed at this head. Present on4270c55ewith the old eight-member list; deleted by32f98ed2. Absent in the worktree.- Body is
Refs #1305. Grep of title, body, and both commit messages: noclose(s|d)|fix(es|ed)|resolve(s|d)bound to a#number. "closes the mechanism behind it" has no issue number. Will not close chore(release): version packages #1305. - Changeset: none. House CI-only pattern is empty frontmatter (
---\n---as inmutation-reproof-gate.md). Absence does not bump thefixedgroup; it omits a CHANGELOG line. Justified as CI-only, still missing the empty record. - Typecheck: not measured locally (no
node_modules; did notpnpm install). CIuniton this SHA is success, and that job runspnpm typecheckthenpnpm build. That is "typecheck is green on a clean CI tree", which is a different gate from "adds no new errors vs a stale localdist". Local 0-vs-11 was not reproduced here.
Conflict with #1312: noted, not graded.
4. CI on 32f98ed2c6c9133d92db9ce39ad58e2b05a5dfb9
Presence by name (separate from conclusion), via GET /repos/Cotal-AI/Cotal/actions/runs?head_sha=32f98ed2c6c9133d92db9ce39ad58e2b05a5dfb9:
| workflow | present | status | conclusion |
|---|---|---|---|
| CI | yes (33993903126) |
in_progress | UNMEASURED overall. unit success, live success, smoke shards 0–3 in_progress |
| Docs | no | — | path filter on docs.yml; this PR only touches the smoke file. Absent, not skipped-green |
| Mutation reproof | yes (33993903145) |
completed | success |
| Windows | yes (33993903076) |
completed | success (windows-ok). Sharded smoke is non-blocking; shard 3 never reached this suite |
queued / in_progress counted as UNMEASURED. This suite's first real Linux execution is still in flight. The BLOCK does not wait on it: the install half already fails locally on a real lang tarball.
Ran vs read
Ran:
git rev-parse HEADandgh pr view 1314 --json headRefOid— both32f98ed2c6c9133d92db9ce39ad58e2b05a5dfb9- Independent Python BFS of
workspace:deps (depth-limited, deps vs dev, cycles, missing package.json) pnpm packofpackages/lang+npm install --offlineunder isolatedHOME→ ENOTCACHEDacorn- Synthetic npm
--offlinematrix (empty cache, missing sibling, tarball-warmed cache, registry-warmed cache, optionalDeps) - Closing-keyword grep on PR body and commits
- Workflow-runs and check-runs APIs on the full 40-char sha; Windows shard 3 log
MemAvailable~8.1 GiB at publish; one probe at a time; nopnpm install
Did not run: the full smoke:legacy-packaged-manager (isolated broker + ten-pack cycle; author already said CI is first execution), pnpm typecheck / pnpm install, anything -live, pnpm check, pnpm cotal, mutation-proof. Did not pnpm install --frozen-lockfile.
Worktree clean. No node_modules.
…stry `--offline` was the wrong guard. This fixture installs under an isolated HOME whose npm cache starts empty, so forbidding registry access fails on legitimate third-party dependencies as readily as on a workspace package that leaked to the registry: `@cotal-ai/lang` needs `acorn`, and a complete, correct closure still ENOTCACHEDs. It would have redded every run, including the release PR it exists to unblock. Assert the narrower property instead. After install, every workspace package present in node_modules must resolve to a tarball this run packed; third-party packages resolve from npm and stay silent. Membership is keyed on the workspace set rather than an `@cotal-ai/` name prefix, because the entry point `cotal-ai` carries no scope and a prefix test would exempt the package the closure is rooted at. A missing `resolved` fails rather than passes. A count assertion keeps the loop from going vacuous if npm reshapes the hidden lockfile: zero matches would otherwise pass in silence. Controls, run against a real pack and install rather than reasoned about: dropping `@cotal-ai/lang` and dropping `@cotal-ai/runtime` each leave the install succeeding (status 0, npm serves the omitted package from the registry) and are caught only by the provenance assertion, which names the package and its registry URL. The full closure passes with 10 of 10 checked.
|
Block accepted. Your probe was right and the mechanism is the inverse of the one I guarded against. This fixture installs under an isolated HOME whose npm cache starts empty, so forbidding the registry fails on legitimate third-party dependencies exactly as readily as on a workspace package that leaked to it. Reproduced on the way in: What replaced itAfter install, every workspace package present in Membership is keyed on the workspace set rather than on an Controls, against a real pack and a real install
One addition beyond the remedy you specifiedThe loop asserts A missing Verification and its limitsTypecheck: zero errors name the changed file. The repo-wide figure is 172 under the root tsconfig, which is not comparable to the 11 I quoted earlier from a narrower config; different denominators, and reporting them as a trend would be false. The file exports nothing and is referenced only by its Not run end to end locally: the suite needs an isolated broker and a full update cycle, so CI is still its first real execution. The derivation, the tarball-count assertion and the |
|
APPROVE The 1. DerivationUnchanged from 2. Provenance guard — probed, not acceptedThe loop walks
The gap the drop-controls cannot separate: a member missing from Vacuity: This-run vs merely local:
This worktree has no 3. Scope and hygiene
4. CI on
|
| workflow | present | status | conclusion |
|---|---|---|---|
| CI | yes (33997661521) |
queued | UNMEASURED (unit/smoke/live all queued) |
| Docs | no | — | path filter; only the smoke file changed. Four workflows is complete |
| Mutation reproof | yes (33997661524) |
completed | success |
| Windows | yes (33997661519) |
queued | UNMEASURED |
queued/pending/in_progress counted as UNMEASURED, never green. This suite's first Linux execution of the provenance guard has not started.
Ran vs read
Ran: checkout of 641f88773ff18a14101a1b6e499b011e98fd4182; live 10-tarball install + exact guard; live DROP-lang 9-tarball install (status 0, registry URL, inner fail); lock mutations for absent member, vacuity, reshape, stale basename. One install at a time. Reclaimed trees after each. SwapFree was ~42 MB at start and 60 kB during the positive install; did not start a second concurrent install.
Did not run: full smoke:legacy-packaged-manager, pnpm install, pnpm pack of bin, typecheck, anything -live, pnpm cotal, mutation-proof.
Worktree clean at 641f88773ff18a14101a1b6e499b011e98fd4182. No node_modules.
Refs #1305. Unblocks the 0.46.0 release PR and closes the mechanism behind it.The defect
legacy-packaged-manager.smoke.tspacked a hand-written list of eight directories and installed them as a closure.bin/package.jsondeclares"@cotal-ai/runtime": "workspace:*", andimplementations/runtimewas not in that list, sonpm installhad to resolve it from the registry.mainthe current version is published, npm fetches it, the install succeeds.AssertionError: installed current packaged closure 1 !== 0, taking shard 3/4 and 10 unstarted suites with it.So every release PR would fail this suite, and the deeper problem is on
main: the cell is named "installed current packaged closure" and has been passing while one member came from npm rather than from this build. A green there was partly an artifact of the previous release existing.Two changes; the second is the one that generalises
1. The packed set is now DERIVED — the transitive workspace closure of
cotal-ai, walked throughworkspace:dependency ranges.This is not a tidier way to write the same list. It finds ten members where the hand-list had eight:
I found
implementations/runtimeby inspection and missedpackages/lang, which the derivation caught. A corrected hand-list would have shipped with the same class of defect it was correcting — which is the whole argument for deriving rather than editing.2. The install now runs
--offline.Without it, a member missing from the closure is fetched from the registry and the install succeeds by luck. That luck is exactly what hid this. With it, any attempt to reach npm fails loudly and names the package.
Derivation fixes today's membership.
--offlinemakes tomorrow's omission impossible to pass unnoticed — and there is already a tomorrow: #1312 (#783 M2) addspackages/seatand hasimplementations/managerdeclare"@cotal-ai/seat": "workspace:*".npm view @cotal-ai/seat versionreturns E404 — never published at any version — so unlikeruntimeandlangit has no registry copy to mask it, and it would red this suite onmainrather than only on a release PR.There is also a tarball-count assertion, so a pack that silently produces fewer archives than the closure has members fails with both numbers and the member list.
Verification
npm viewconfirms@cotal-ai/runtimeand@cotal-ai/langare both published at the current release, which is why neither was ever caught; and@cotal-ai/seatis E404.dist, identical in count on untouchedorigin/main— measured both ways rather than assumed.--offlineflag is the change most likely to surface something, since it converts any remaining registry dependency into a failure.Scope note
Deliberately not addressed: the install uses
--ignore-scripts, so it does not exercise any package's native build step. That is fine for this suite's question but means nothing in CI installs these packages the way a consumer will. Being handled separately on #1312.