fix(spec): give the permissions alias table's hosts entry its own, true justification - #17358
Conversation
…, true justification The block's only comment said edit distance cannot reach any of the three curated aliases, and it sat directly above all three. That holds for `filesystem` and `paths` and is false for `hosts`: the fallback budget is `Math.max(2, Math.floor(key.length / 3))`, so a 5-character key gets 2, and `hosts` differs from the declared `hooks` by exactly 2. Without the alias the fallback answers `hosts` -> `hooks`, pointing the author at lifecycle hooks on the one block that also grants network access. The alias is better justified than the prose claimed -- it overrules a confident wrong suggestion rather than filling a silent gap -- so only the justification moves. `hosts` is also the only one of the three that changes a live suggestion, hence the only one whose absence is invisible; it and `paths` are now pinned beside the `filesystem` pin that already existed. Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 134 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin ee7a70ff80f2004cecb690b426b062c5953ae0d6 && git checkout ee7a70ff80f2004cecb690b426b062c5953ae0d6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 706ad0fcc4ed4f76da4e22255bd4fb0f2edae2bc bd041f140917968185efb762956a2bb50397ee3c && git checkout -B drift-repro 706ad0fcc4ed4f76da4e22255bd4fb0f2edae2bc && git merge --no-ff bd041f140917968185efb762956a2bb50397ee3c
node scripts/docs-audit/affected-docs.mjs --json 706ad0fcc4ed4f76da4e22255bd4fb0f2edae2bc
|
ACCEPT — head
|
| key | len | budget | nearest | verdict | real findClosestMatches |
|---|---|---|---|---|---|
hosts |
5 | 2 | hooks @ 2 |
REACHABLE | ["hooks"] |
filesystem |
10 | 3 | 8 (services/fs tie) |
unreachable | [] |
paths |
5 | 2 | 4 (hooks/fs tie) |
unreachable | [] |
⇒ The shipped sentence is right about two of three and false for hosts, exactly as filed. ⭐ Deriving it two ways and agreeing is what makes this a reading rather than a restatement.
The ablation is the proof that matters
Deleting the hosts: 'network', line: marker occurrences 1 → 0 and the on-disk blob moved 5be325e1a0… → 5dbf881083…, so the mutation is proven by disk state, ⛔ not by an editor's exit code. Vitest then exited 1 with exactly one red — the new hosts pin — and 26 green, producing:
Unrecognized key(s) on the `permissions` block of this package manifest: `hosts`. Did you mean `hosts` → `hooks`?
⇒ The misdirection the alias exists to prevent, observed live. Restore proven by state (git diff HEAD 0 bytes, git status --porcelain empty, blob back to the HEAD blob), under a trap … EXIT INT TERM with absolute paths and git checkout HEAD -- path.
⭐ And one incidental proof worth keeping: the flip needed no rebuild, against a dist that still carried the alias — which proves this suite resolves through src and that no stale dist could have produced a false green. That is a control nobody asked for.
Boundaries held
⛔ packages/spec/CHANGELOG.md was never opened for writing — correction 1 confirmed foreclosed by measurement (the changeset is absent from .changeset/, the sentence is at :3407), no erratum added, and the PR body says why. ⛔ The hosts alias was not removed and never was on the table. Nothing moved but a comment, two pins and a changeset: 3 files, +11/-3 in the schema.
Clause ② — no
Mechanical: check-widening-tells.mjs --declaration no --diff exits 0 — 3 changed files, 1 judged against a declared surface with no widening tell, 2 NOT MEASURED by construction (a changeset declares no surface; a test declares no contract). Read directly: no alias value, declared key, shape or strictness moves; check:api-surface and check:authorable-surface green; the file's pre-existing six-door regression pin still passes, so no message an author reads changes. ⭐ The declaration is on a properly anchored line — this is the first PR of the batch to get that right without a seat repair.
NOT MEASURED, correctly declared
Two gates at exit 3 = prerequisite-not-met (check:dual-build-cjs-loads, the --re-measure half of check:type-check-debt) — ⛔ not red, left to CI. Two more recovered green after a targeted formula+lint+objectql build, and were reported as recovered rather than as originally-green. 82 derived families, 80 run.
Out-of-scope findings — both disposed, and the seat extended one
- The seat filed
strictObject'saliasesis documented as "near-misses edit distance CANNOT reach" — the repo ships an alias whose whole job is overriding a hit edit distance DOES reach #17361, extending this round's finding 1. The round nameddocs/audits/…-strictness-ledger.md:141as repeating the universal "semantic near-misses edit distance cannot reach". ⭐ It missed the stronger pair:packages/spec/src/shared/strict-object.tsstates the same universal in its module docblock and again in theStrictObjectOptions.aliasesJSDoc — the hover text every adopter reads.⚠️ The carrier is concrete and same-day: PR fix(spec): name the surface, list the keys and suggest the rename on theprotectionblock's unknown-key refusal #17331 adoptedstrictObjectforProtectionSchemathis round, its author reading exactly that docblock. An adopter with a reachable-but-wrong near-miss reads "cannot reach", concludesaliasesis not their tool, and leaves the confident wrong suggestion in place — the very defect this card fixed. - The platform reading (MCP
create_pull_requeststored the body byte-identically then appended a 91-byte footer block, leaving two footers) was not filed as a card — [PM decision] skills lane batch 9 — platform-readings.md third increment: raise the ceiling by the measured +39 (358 → 397); and the MCP write-pool quota sentence collision #15275 already carries that row and had it at n=1 for the re-send action. Posted there as evidence (createis a different cell of the same channel × action grid) rather than as a duplicate. ⭐ Third duplicate prevented tonight by dedup. ⚠️ The double footer on this PR's body is left as-is deliberately: repairing a cosmetic duplicate is not worth the risk of destroying the anchoredClause-②:declaration, which is [finding] theClause-②:declaration lives in the PR body, so any unrelated body edit destroys it silently — measured: three runs of one check on one head SHA #16949's measured hazard. ⭐ The round was right not to re-send.
⛔ Not flipped ready, not enqueued, no auto-merge, no approving review. Landing waits on the three pre-checks at the final head.
Generated by Claude Code
Fixes #16859
The
permissionsalias table curates three entries. Its only comment said edit distance cannot reach any of them, and it sat directly above all three. That is true of the twofsentries and false forhosts— which is the more interesting way round:hostsis reachable, the fallback answers ithooks, and the alias exists to overrule that. Only the justification moves. The alias stays, the accept set does not move, and no message an author reads changes.Correction 1 is deliberately NOT here
The card's first correction was the changeset sentence. That window has shut: the changeset was consumed and the sentence is published verbatim at
packages/spec/CHANGELOG.md:3407. Verified on this branch —.changeset/permissions-block-named-refusal.mdis gone and the line reads:A shipped changelog entry is a record of what was said, not a document to correct — the same principle as never editing
content/docs/releases/in a code PR.CHANGELOG.mdis untouched by this PR, no erratum was added, and the disposition is a report to the maintainer, not an edit.The fork the triage left open: answered, with controls
Triage asked whether any source comment repeats the false justification, searched
packages/spec/srcfor the phrase, found five occurrences and none about the permissions aliases — so the card was left on a fork whose other branch was "closes asnot_planned".Re-run here, on this branch, probing the claim rather than a spelling:
edit distance cannot reachoverpackages/spec/srcshared/strict-object.ts(x2),ui/chart.zod.ts(x2),automation/state-machine.test.ts,automation/builtin-node-config.test.ts,data/driver/turso.zod.ts,data/default-value-shape.ts,data/object-strictness-batch20.test.ts,data/object.zod.ts.kernel/manifest.zod.tsis not among them.shared/suggestions.zod.ts:331,data/authoring-key-lint.ts:82,data/default-value-shape.test.ts:139andkernel/manifest.zod.ts:51aliasesinkernel/manifest.zod.tsedit[- ]?distance, case-insensitive, every tracked filekernel/manifest.zod.ts:51The comment capitalises it:
Editdistance cannot reach. The triage's lowercase probe could not match it. So the "none of them about the permissions aliases" reading was a letter-case artifact, thenot_plannedbranch does not apply, and correction 2 is real. Counts above are occurrences (grep -o | wc -l), not lines.The arithmetic, re-derived here rather than relayed
shared/suggestions.zod.ts:461sets the budget:Derived independently and then cross-checked against the repo's own
findClosestMatches(builtdist), with the alias table out of the picture and the declared keysservices/hooks/network/fs:findClosestMatcheshostsmax(2, 1)= 2hooks:2fs:4services:7network:7hooks@ 2["hooks"]filesystemmax(2, 3)= 3services:8fs:8hooks:9network:9[]pathsmax(2, 1)= 2hooks:4fs:4network:6services:7[]So the sentence is right about two of three.
hostsis the one entry that overrides a confident wrong suggestion — pointing the author at lifecycle hooks on the one block that also grants network access — rather than filling a silent gap. Removing the alias would be the opposite of the fix, and nothing here does.Ablation — the two-sided proof
hostsis the only one of the three that changes a live suggestion, so it is the only one whose absence is currently invisible. Mutation and restore are proven on disk by blob hash and marker count, never by an editor's exit code, and the restore is trapped with absolute paths.Restore proven by state, not by exit code:
git diff HEADempty (0 bytes),git status --porcelainempty, on-disk blob equal to the HEAD blob.Two notes on what the ablation establishes. It turns exactly one test red — the new
hostspin — and leaves 26 green, so the pin fails for its own reason and not as collateral. And the flip happened with no rebuild against adistthat still carried the alias, which is the proof that this suite resolves the schema throughsrc(relative imports) and that no staledistcould have produced a false green here.Gates
Derived with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(change set taken by the tool itself from the merge base, three-dot), then reconciled with--rancarrying each recorded exit code.82 derived families — 80 run green, 2 NOT MEASURED. The tool's own verdict:
82 derived famil(ies) accounted for — 80 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3).The two, both
PREREQUISITE NOT MET(exit 3 — neither a pass nor a finding; nothing was measured), each refusing because it reads built output the whole workspace has to produce:pnpm check:dual-build-cjs-loads— "this gate reads built output, and some package has no dist/"pnpm check:type-check-debt—--re-measurerefuses with 31 workspace dependencies unbuilt, because a number measured without the closure is a different worldBoth are left to CI, which checks out fresh and builds.
check:doc-formula-expressionsandcheck:lean-entry-closurealso refused at first for the same reason; a targetedturbo run build --filter=@objectstack/formula --filter=@objectstack/lint --filter=@objectstack/objectqlcleared them and both then ran green, which is what the reconciliation above records.Beyond the derived list:
pnpm --filter @objectstack/spec buildpnpm --filter @objectstack/spec check:generatedpnpm --filter @objectstack/spec typecheckcheck:test-typecheckovertsconfig.test.json, so the new pins are compiledpnpm --filter @objectstack/spec testeslint . --no-inline-configpnpm check:nul-bytesplus a manual control-character sweep of the three touched filesExit codes were captured before any pipe (
cmd > log 2>&1; EXIT=$?) throughout.Heavy runs went through
scripts/pm/os-verify-lock.shon a stable slot; every verdict above is read from itsVERDICT command-exitline, never from a bare$?.Clause-②: no —
node scripts/pm/check-widening-tells.mjs --declaration no --diffexits 0: of the three changed files, one is judged against a declared surface with no widening tell (kernel/manifest.zod.ts) and two are NOT MEASURED by construction (a changeset declares no surface; a test file declares no contract). Read directly: the diff moves a code comment inside thealiasesoption and adds two test pins. No alias value, declared key, shape or strictness changes;check:api-surfaceandcheck:authorable-surfaceare green, and the file's pre-existing six-door regression pin still passes, so no message an author reads moves either.验收备注
docs/audits/2026-07-unknown-key-strictness-ledger.md:141describesaliasesas "semantic near-misses edit distance cannot reach", the same universal this card falsifies — thehostsentry shows the facility also serves the override role. One line, in a doc, not an example that fails when copied, so it is noted rather than filed. Carrier: the next author adding a strictness-ledger row, a file this diff's owncheck:strictness-ledgergate already reads.packages/spec/CHANGELOG.md:3407carries the false sentence and is deliberately untouched, per the paragraph above. Carrier: the maintainer, as accepted history.Generated by Claude Code
Generated by Claude Code