Five backlog cards, and a published package that was shipping four high advisories - #30
Merged
Merged
Conversation
T-0213 asked whether `doctor` should call a `healthCheck` declared by
`project.config.mjs` at all. It should: `loadWorkspace` already `import()`s
that module in every command, so the module body runs earlier and more
quietly than any hook, and removing the call closes nothing while costing
the feature. LRN-0029 lists all three channels that execute
repository-declared code, with every caller and where it runs.
What was actually broken is that `doctor` trusted the hook's answer:
- a throw propagated out of `runDoctor` and took every caller down with it,
so the report that would have named the broken integration never printed;
- a hook that never settled had no bound at all;
- `{ severity: "catastrophe" }` wrote NaN into the counts, landed in no
bucket, left `ok` true and made the comparator sort on NaN — an
integration could decide whether the repository passed, by typo.
Each call is now isolated, bounded at 10s and validated, attributed to its
integration by id, and all three failures are errors because `doctor` is a
gate. Well-formed entries in a rejected batch still land. A project that
declares no integrations sees none of it, proven by a test.
The bound is partial on purpose and the code says so: a hook runs on
`doctor`'s own event loop, so it catches an awaited hang and not a
synchronous spin. The timer is deliberately not `unref`ed — that was the
first draft, and it let Node exit before the bound fired, printing no
report at all.
The three generated CI templates now state both hops rather than the first
one on GitHub only. "It imports a config file" reads as loading settings,
and somebody pricing this from that sentence prices it wrongly.
Cards: T-0213
Filed: T-0218
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3LTdq3mzMAQ98rwBegGjU
The skip that decided a conflict compared `claimed_by`, which reads as a session check only because `resolveActor` writes a session discriminator into the actor's tail. So it was right often enough to look correct and wrong exactly where it matters: two plain terminals both resolve to `user@host`, and two agents handed the same `--actor` both resolve to that. Each is two processes about to overwrite each other, and each was dropped as one person. `claimSeparation` now answers "provably the same process" and names its evidence — `sessions-differ`, `actors-differ`, or `unproven`. A session is recovered from either place that carries it, the session file or the actor's tail, both normalised through `sessionDiscriminator`, now exported so there is one definition instead of two that can drift. A second defect fed the same rule: `claimState` resolved a claim's session with one `find` over `cardId === card.id || actor === claimed_by`, so two cards held by one actor string could both be attributed to whichever session came first — erasing the exact evidence the comparison needs. Attribution now prefers the session that names the card. ADR-0020 records the sessionless case: `unproven` is reported, not dropped and not prompted on. Dropping it is the bug that let two terminals collide with no trace; prompting on it interrupts somebody about a card they claimed themselves, which is the guard people switch off. The scope guard needed no behaviour change, and that is the finding rather than a shortcut — because the session lives in the actor's tail, its string comparison *is* the session comparison for every pairing it can see. Rather than argue that, `separatesFromMe` names it and a new pin drives the real hook over six pairings against `claimSeparation`. One asymmetry stays, recorded rather than half-fixed: the snapshot can read a session from a session file and the guard cannot, because `board.json` carries none and the hook imports nothing from the package on a p95-under- 30ms budget. LRN-0030 and T-0219. Cards: T-0206 Filed: T-0219 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D3LTdq3mzMAQ98rwBegGjU
`COMMAND_FLAGS` is a contract with two sides and only one was checked. `cli.test.ts` pins the table against the flags each subcommand reads, in both directions, and it caught a stale `card archive --actor` in the branch that removed `init --language`. That branch still failed in CI, because nothing checked the flags a *caller* sends — and the callers were the generated agent instructions, the docs and the package smoke test. The new check covers exactly the sources `pnpm run test` does not execute: text under `.project/agents`, `.claude`, `plugins/workfile/commands`, the docs and the README, plus `scripts/` and `test/package-smoke.ts`. The unit tests are deliberately not scanned. They are full of flags that are meant not to exist — `--bogus`, `--nonsense`, `--statuss` — because asserting the refusal path is their job, and scanning them would mean an allowlist of intentional nonsense with a real stale flag able to hide in it. They need no scanning: a test that sends a removed flag fails when the suite runs. Command-word resolution mirrors `commandKey` and reads `USAGE_ALIASES` and `DEFAULT_SUBCOMMAND` out of the source, which the survey forced — without aliases, `workfile docs create --kind` in SPEC.md looked stale and it is a valid alias for `doc`. A word that resolves to no row is reported rather than dropped. It runs in 52ms, finds 116 invocations, and asserts a floor of 90 so a regex that stops matching fails loudly instead of passing forever. Proven by mutation on both halves: dropping `--scope` from `card claim` names 10 caller sites, dropping `--yes` from `init` names 3 including `package-smoke.ts:215` — the exact command that failed in CI. What it does not cover is named in the test itself, including the route not taken: this reads text, so `check` still proves nothing about the packaged artifact. Cards: T-0182 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D3LTdq3mzMAQ98rwBegGjU
…r resolves T-0148 and T-0220 asked the same question about two gates living in `check:release`, which runs on a tag push — so a failure landed on a release rather than on a pull request. v0.5.4 is what that cost: a published tag whose release did nothing, `Publish to npm` never reached, and the tag moved onto the fix. Two things turned up while answering it. T-0220 was filed on a false premise, mine. `ci.yml` has had a `smoke` job on `pull_request` since before T-0182, so a change that breaks the packaged bin already fails on a pull request; the T-0158 failure that prompted this was that job working as designed. Its body is corrected rather than left standing. And the audit gate was measuring the wrong tree. `pnpm audit` audits this workspace, where four `pnpm.overrides` had already rewritten the graph. Overrides are a workspace-install mechanism and do not travel inside a published package. Two of the four — `sharp` and `adm-zip` — sit under `@huggingface/transformers`, a `dependencies` entry of the published `@illodev/workfile-search-local`, so they made this gate green and fixed those advisories for nobody who installed it. T-0148's own body reads that distinction as reassuring; for two of the four entries it is the opposite. So `pnpm audit --audit-level=high` is now its own blocking job on pull requests, and `scripts/audit-consumer.ts` resolves what the publishable manifests declare, in a scratch root with no overrides, and fails on anything at high or above. Blocking with no allowlist is a deliberate posture, not an omission: a baseline had precedent in `doctor --accept-baseline` and was refused, because the alternative is a list that grows and a package that ships with the list as its answer. Local `pnpm run check` gains neither gate. It runs constantly, both need the network, and posture is a property of what gets published rather than of an edit. That asymmetry is stated in ADR-0021 as a trade rather than hidden. T-0148 stays in `review`: its remaining criterion asks a pull request to prove the job fails in CI, and a local run is not that. Cards: T-0220 Decisions: ADR-0021 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D3LTdq3mzMAQ98rwBegGjU
…hipped `@illodev/workfile-search-local` declared `@huggingface/transformers`, which hard-depends on `sharp` and `onnxruntime-node` — image processing and an archive extractor this package never touched. Both carry high-severity advisories with no upstream fix: the libvips CVEs in `sharp <0.35.0` (GHSA-f88m-g3jw-g9cj) and GHSA-xcpc-8h2w-3j85 in `adm-zip <0.6.0`. Anybody installing 0.8.1 or earlier got all of them, while this repository's audit read clean because overrides do not travel. The pipeline was doing four things: fetch `tokenizer.json` and the ONNX weights, tokenize, run the session, mean-pool over the attention mask and L2-normalize. Done directly over `onnxruntime-web` and `@huggingface/tokenizers` that is about 130 lines, and it audits clean with no override and no allowlist. Measured against the implementation it replaces before committing to the route, on the same q8 weights and the same texts: per-vector cosine 0.9978, unit norms, identical ranking order. The residual is the WASM and native kernels disagreeing at quantized precision, not a difference in method. The `sharp` and `adm-zip` overrides go with it — `pnpm why` finds neither package in the graph now, so those entries had become pins on nothing. `fast-uri` and `js-yaml` stay, and they are the honest kind: nothing ships. One cost, in CHG-0149: the model cache moved to `~/.cache/workfile/models`, so the first search after upgrading re-downloads about 135 MB once. Existing embedding caches stay valid, being keyed by model and content. In exchange, pointing `model` at a directory now skips the download entirely, so this runs with no network — which it could not before. Two corrections to what the card was filed with. `optionalDependencies` would not have worked: npm installs them by default. The mechanism that removes a dependency from a consumer's tree is `peerDependenciesMeta` `.optional`, verified empirically both ways. And transformers 4.2.0 declares all three heavy dependencies with no flag that omits them, so no configuration could have avoided this. All seven pre-existing tests inject `embedder` and touch none of the new code; three were added for the parts that need no model. The pooling one was mutation-checked, and it taught something worth keeping: after L2 normalization the divisor cannot matter, because dividing by the padded width is a positive scalar that normalization cancels. The first version of that test claimed otherwise in a comment, the mutation passed, and the comment was what was wrong. It pins the mask now. LRN-0032. Cards: T-0221 Changelog: CHG-0149 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D3LTdq3mzMAQ98rwBegGjU
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This was referenced Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Five backlog cards. They are independent, one commit each, and the order is the order they were worked — the last two exist because the fourth found something.
Doctor stops trusting what a repository's healthCheck hands itTwo claims are one process only when one session, never when one actorCheck what callers send to the CLI, not only what each subcommand readsSupply-chain gates run on pull requests, and audit the tree a consumer resolvesreviewDrop transformers.js from search-local, clearing four advisories it shippedThe one that matters most
Working T-0148 turned up a gate measuring the wrong tree.
pnpm auditaudits this workspace, and this workspace had fourpnpm.overrides. Overrides are a workspace-install mechanism and do not travel inside a published package. Two of the four —sharpandadm-zip— sat under@huggingface/transformers, adependenciesentry of the published@illodev/workfile-search-local.So the audit had read clean since T-0023, and anybody who ran
npm i @illodev/workfile-search-local@0.8.1resolved four high-severity advisories with no upstream fix: the libvips CVEs insharp <0.35.0(GHSA-f88m-g3jw-g9cj) and GHSA-xcpc-8h2w-3j85 inadm-zip <0.6.0.T-0148's own body reads the workspace/tarball distinction as reassuring — "nothing vulnerable was ever going to reach a user" — and for two of the four entries it was the opposite.
Fixed by changing what is shipped rather than by annotating it:
search-localnow reaches the same ONNX weights throughonnxruntime-weband@huggingface/tokenizers. Measured against the implementation it replaces before committing to the route — per-vector cosine 0.9978 on the same q8 weights, unit norms, identical ranking order.Gate policy, one decision for both cards
ADR-0021.
pnpm audit --audit-level=highbecomes its own blocking job on pull requests, andscripts/audit-consumer.tsresolves what the publishable manifests declare, with no overrides, failing at high or above. Blocking with no allowlist — adoctor --accept-baseline-style list was refused deliberately, and the refusal is what forced the real fix.Local
pnpm run checkgains neither gate: it runs constantly, both need the network, and posture is a property of what gets published. That asymmetry is stated in the ADR as a trade rather than hidden.Two premises I filed wrong and corrected on the record
smoke:packageonly runs on a tag. It does not —ci.ymlhas had asmokejob onpull_requestsince before T-0182, so criterion 1 was already satisfied. Body rewritten.optionalDependenciesas a route. It would not have worked: npm installs them by default. The mechanism that removes a dependency from a consumer's tree ispeerDependenciesMeta.optional, verified empirically both ways.Reviewing
pnpm run audit:consumer— clean at high and above, and below it too.pnpm why sharp/pnpm why adm-zip— neither is in the graph, which is why those two overrides are gone.separatesFromMetoreturn truefails 4 of 6 pinned pairings; dropping--scopefrom thecard claimrow fails the caller check naming 10 sites; removing the attention-mask guard fails both new pooling tests.Note the intermediate commits: at
cc8d738the consumer audit is red by design, because the exposure it names is fixed by10c1e38. The branch tip is green.What is left for you
T-0148 stays in
review, notdone. Its remaining criterion asks that a pull request prove the audit job fails in CI, and a local run is not that. This PR is the run — check theauditjob, then it can close.One user-visible cost, in CHG-0149: the model cache moved to
~/.cache/workfile/models, so the first search after upgrading re-downloads about 135 MB once. Embedding caches stay valid. In exchange, pointingmodelat a directory now skips the download entirely, so this runs with no network — which it could not before.🤖 Generated with Claude Code
https://claude.ai/code/session_01D3LTdq3mzMAQ98rwBegGjU