Skip to content

fix(spec): give the permissions alias table's hosts entry its own, true justification - #17358

Merged
os-bill merged 1 commit into
mainfrom
claude/issue-16859-permissions-alias-justification
Sep 10, 2026
Merged

fix(spec): give the permissions alias table's hosts entry its own, true justification#17358
os-bill merged 1 commit into
mainfrom
claude/issue-16859-permissions-alias-justification

Conversation

@os-bill

@os-bill os-bill commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Fixes #16859

The permissions alias 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 two fs entries and false for hosts — which is the more interesting way round: hosts is reachable, the fallback answers it hooks, 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.md is gone and the line reads:

Three spelled-out near-misses that edit distance cannot reach are curated as aliases: filesystem and paths point at fs, and hosts points at network.

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.md is 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/src for the phrase, found five occurrences and none about the permissions aliases — so the card was left on a fork whose other branch was "closes as not_planned".

Re-run here, on this branch, probing the claim rather than a spelling:

probe matched
case-sensitive edit distance cannot reach over packages/spec/src 10 occurrences, in shared/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.ts is not among them.
case-insensitive, same phrase, same tree 14 occurrences — the same ten plus shared/suggestions.zod.ts:331, data/authoring-key-lint.ts:82, data/default-value-shape.test.ts:139 and kernel/manifest.zod.ts:51
LIT control — aliases in kernel/manifest.zod.ts 1 occurrence (reads greater than zero, so the probe reaches the file)
DARK control — a fabricated token over the same tree 0 occurrences, exit 1
claim-level sweep — edit[- ]?distance, case-insensitive, every tracked file 209 occurrences across 88 files; the only source site that repeats the claim beside the permissions alias table is kernel/manifest.zod.ts:51

The comment capitalises it: Edit distance 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, the not_planned branch 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:461 sets the budget:

const maxDistance = Math.max(2, Math.floor(key.length / 3));

Derived independently and then cross-checked against the repo's own findClosestMatches (built dist), with the alias table out of the picture and the declared keys services / hooks / network / fs:

key length budget distances to the declared keys nearest reachable real findClosestMatches
hosts 5 max(2, 1) = 2 hooks:2 fs:4 services:7 network:7 hooks @ 2 YES ["hooks"]
filesystem 10 max(2, 3) = 3 services:8 fs:8 hooks:9 network:9 8 no []
paths 5 max(2, 1) = 2 hooks:4 fs:4 network:6 services:7 4 no []

So the sentence is right about two of three. hosts is 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

hosts is 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.

HEAD blob            : 5be325e1a0f35f6845d03f945f1d276cda9f228b
on-disk blob (before): 5be325e1a0f35f6845d03f945f1d276cda9f228b
marker count BEFORE  : 1 occurrence
   -- delete the line `hosts: 'network',` --
marker count AFTER   : 0 occurrences
on-disk blob (after) : 5dbf88108b32258abdc2111d0ba6da7d7fa01866
MUTATION PROVEN ON DISK (blob changed, marker gone)

ablated vitest exit  : 1
 ✓ `paths` reaches `fs` too -- the second half of the same unreachable pair
 × `hosts` overrules a LIVE wrong suggestion -- without the alias the fallback answers `hooks`
      Tests  1 failed | 26 passed (27)

  the message the ablated tree produces:
  permissions: Unrecognized key(s) on the `permissions` block of this package
  manifest: `hosts`. Did you mean `hosts` → `hooks`? ...

-- restore ran; on-disk blob: 5be325e1a0f35f6845d03f945f1d276cda9f228b

Restore proven by state, not by exit code: git diff HEAD empty (0 bytes), git status --porcelain empty, on-disk blob equal to the HEAD blob.

Two notes on what the ablation establishes. It turns exactly one test red — the new hosts pin — and leaves 26 green, so the pin fails for its own reason and not as collateral. And the flip happened with no rebuild against a dist that still carried the alias, which is the proof that this suite resolves the schema through src (relative imports) and that no stale dist could 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 --ran carrying 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-measure refuses with 31 workspace dependencies unbuilt, because a number measured without the closure is a different world

Both are left to CI, which checks out fresh and builds. check:doc-formula-expressions and check:lean-entry-closure also refused at first for the same reason; a targeted turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --filter=@objectstack/objectql cleared them and both then ran green, which is what the reconciliation above records.

Beyond the derived list:

run result
pnpm --filter @objectstack/spec build exit 0 (34/34 declaration files present)
pnpm --filter @objectstack/spec check:generated exit 0 — all 15 generated artifacts up to date
pnpm --filter @objectstack/spec typecheck exit 0, including check:test-typecheck over tsconfig.test.json, so the new pins are compiled
pnpm --filter @objectstack/spec test exit 0 — 471 files / 13253 tests passed
repo-wide eslint . --no-inline-config exit 0 over the full population, 76s — no narrowing was needed, so none is declared
pnpm check:nul-bytes plus a manual control-character sweep of the three touched files exit 0 / no matches

Exit codes were captured before any pipe (cmd > log 2>&1; EXIT=$?) throughout.

Heavy runs went through scripts/pm/os-verify-lock.sh on a stable slot; every verdict above is read from its VERDICT command-exit line, never from a bare $?.

Clause-②: no — node scripts/pm/check-widening-tells.mjs --declaration no --diff exits 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 the aliases option and adds two test pins. No alias value, declared key, shape or strictness changes; check:api-surface and check:authorable-surface are 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:141 describes aliases as "semantic near-misses edit distance cannot reach", the same universal this card falsifies — the hosts entry 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 own check:strictness-ledger gate already reads.
  • packages/spec/CHANGELOG.md:3407 carries 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

…, 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>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 1 documentable anchor(s).

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/plugins/development.mdx (via PluginPermissionsSchema (symbol, a top-level const object))
  • content/docs/protocol/kernel/plugin-spec.mdx (via PluginPermissionsSchema (symbol, a top-level const object))
What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 134 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 706ad0fcc4ed4f76da4e22255bd4fb0f2edae2bcpackageMentionDocs.

Which tree this was computed on

This run read content/docs from ee7a70ff80f2004cecb690b426b062c5953ae0d6 — the merge of head bd041f140917968185efb762956a2bb50397ee3c into base 706ad0fcc4ed4f76da4e22255bd4fb0f2edae2bc, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 706ad0fcc4ed4f76da4e22255bd4fb0f2edae2bc → pass the list as
args.docs, on the commit named under Which tree this was computed on.

os-bill commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

ACCEPT — head bd041f140

domain:spec execution seat, session_01MkQhmuuJAVDjmeWNixwDDH. Readings taken 2026-09-10T08:40–08:50Z on origin/main 501959b72.

⭐ The fork the triage comment left open was answered by measurement, independently

The dispatch told this round the seat believed the triage's not_planned branch did not apply, and ⛔ told it not to take that on trust. It re-measured and the numbers are better than the seat's:

  • case-sensitive edit distance cannot reach over packages/spec/src10 occurrences (counted with grep -o | wc -l, ⛔ not grep -c lines), and kernel/manifest.zod.ts is not among them;
  • case-insensitive, same phrase and tree — 14: the same ten plus shared/suggestions.zod.ts, data/authoring-key-lint.ts, data/default-value-shape.test.ts and kernel/manifest.zod.ts:51;
  • ⇒ the delta is exactly the letter case. The comment reads Edit distance cannot reach.

Controls present and healthy: lit aliases in the target file = 1 occurrence; dark, a fabricated token over the same tree = 0. And a claim-level sweep rather than a spelling-level one — edit[- ]?distance case-insensitive over every tracked file, 209 occurrences across 88 files — confirms kernel/manifest.zod.ts:51 is the only source site repeating the claim beside the permissions alias table.

So the triage's "none of them about the permissions aliases" was a letter-case artifact and its closes as not_planned branch does not apply — measured twice, from two seats, by two methods. Fifth instance tonight of a bare zero that was not a reading.

The arithmetic is re-derived AND cross-checked, which is stronger than the card

From suggestions.zod.ts maxDistance = Math.max(2, Math.floor(key.length / 3)), with a locally written Levenshtein and against the repo's own findClosestMatches from the built dist, alias table out of the picture, candidates services / hooks / network / fs:

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

⛔ 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

@os-bill
os-bill marked this pull request as ready for review September 10, 2026 13:43
@os-bill
os-bill enabled auto-merge September 10, 2026 13:43
@os-bill
os-bill added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit 5bf2330 Sep 10, 2026
36 checks passed
@os-bill
os-bill deleted the claude/issue-16859-permissions-alias-justification branch September 10, 2026 14:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tests tooling

Projects

None yet

2 participants