Skip to content

Commit 5100c42

Browse files
os-billclaude
andauthored
docs(spec): AnchorBindingContext's boot half names the stack's capability DECLARATIONS, not the not-yet-seeded sys_capability rows (#18767)
Clause-②: no Fixes #18603 Comment text only, in one file: the `AnchorBindingContext` docblock in `packages/spec/src/security/high-privilege.ts`. No predicate, type, export or accept set moves. ## What the sentence said, and why a literal follower is refused The docblock named two sources for `declaredCapabilities`: at boot 「the `sys_capability` rows carrying `managed_by: 'package'` provenance」, at authoring time the stack's own `capabilities` array. The boot half carried an ordering precondition the sentence never stated. The ADR-0090 D5 anchor binding runs BEFORE the seeder that writes those rows, so on a first boot the table is empty at exactly the moment the docblock said to read it — and this docblock's own 「omission refuses」 property then turns that emptiness into a silent refusal of every declared token: the app's own `isDefault` set unbindable at the `everyone` anchor, which is the defect #17811 introduced the input to remove, reproduced one layer in. The boot half now names the DECLARATIONS, read through the seeder's own two-step — the ObjectQL registry first, the metadata service as the fallback — which is exactly what `readDeclaredCapabilityContext` (`@objectstack/plugin-security`, PR #18602) already implements, so the contract text and its one runtime consumer corroborate each other instead of contradicting. The `sys_capability` rows stay a valid source, qualified: only once the seeder has written them. ## LIT — the ordering was READ, by symbol, on this branch's base The card's line numbers were taken on PR #18602's head and were carried forward unverified. They were re-derived here by SYMBOL on `origin/main` `95b21b33be` (this branch's merge base), `packages/plugins/plugin-security/src/security-plugin.ts`: | symbol | line | inside | | :-- | :-- | :-- | | `const runBootstrap` | `:3655` | the boot sweep itself | | `await seedCatalogBuiltins(...)` | `:3866` | `runBootstrap` — reaches `bootstrapBuiltinRoles` at `:3572` (defined in `seedCatalogBuiltins`, `:3570`), which seeds the `everyone` anchor | | `await bindBaselineToEveryone(...)` | `:3888` | `runBootstrap` — the ADR-0090 D5 bind; defined at `:3583`, consults `describeHighPrivilegeBits` at `:3595` | | `await reconcileAudienceBindingSuggestions(...)` | `:3905` | `runBootstrap` | | `await bootstrapDeclaredCapabilities(...)` | `:3927` | `runBootstrap` — the seeder that WRITES the `managed_by: 'package'` rows | `:3888` and `:3927` sit in one straight-line `try` body of one function with no branch between them, so the bind precedes the seeder. **The card's conclusion holds.** Three line attributions in the card's table are worth correcting for the next reader, and none of them moves the conclusion: - `:3572` is `bootstrapBuiltinRoles`'s call site inside the helper `seedCatalogBuiltins` (`:3570`), not a line of `runBootstrap`; `runBootstrap` reaches it at `:3866`. - `:3639` is a SECOND `bindBaselineToEveryone` call, inside `seedCatalogForOrganization` (`:3635`) — the organization-creation hook, not the boot sweep. Only `:3888` is `runBootstrap`'s. - `:3742` is `reconcileAudienceBindingSuggestions` inside the publish-materializer callback `runBootstrap` registers — a runtime publish path, not a boot step. The boot step is `:3905`. ## DARK — a reading that must be ZERO, with a control proving it fires Predicate: take `git diff -U0` over `packages/spec/src/security/high-privilege.ts`, keep the `+`/`-` lines that are not the `+++`/`---` headers, and drop every one that is blank or begins with `*`, `//` or `/*`. What remains is CODE. | leg | input | reading | | :-- | :-- | :-- | | this change | `git diff -U0 95b21b3 HEAD -- packages/spec/src/security/high-privilege.ts` | `NON_COMMENT_CHANGED_LINES=0` | | control | the same file's own `d5c91dd681` (#17811), same predicate, same input shape | `NON_COMMENT_CHANGED_LINES=33` — it names the added `import`, the `export interface AnchorBindingContext`, its member and the whole of `appDeclaredCapabilityNames` | The zero is a measurement, not an absence of input: the same instrument reads 33 on a real code change to the same file. `git diff --stat` for this change is 17 insertions / 2 deletions, all of them comment. ## Changeset — measured, not assumed `skip-changeset` would be wrong: published content moves. - `packages/spec/src/security/high-privilege.ts` is NOT shipped as source. `@objectstack/spec`'s `files[]` takes `src/**/*.zod.ts` and this file is not one — `npm pack --dry-run --json` lists 2021 shipped paths and does not include it, with the sibling `src/security/permission.zod.ts` present in the same listing as the lit control. - Its published reach is the EMITTED declarations, and they move. After `pnpm --filter @objectstack/spec build`, the new clause is present in `dist/security/index.d.ts` and `dist/security/index.d.mts` — both in that same shipped listing — the superseded spelling is absent from every built declaration file (0 files), and the docblock's unchanged neighbouring sentence (「Never synthesize this from the set under test」) is present in the same two files as the lit control. Hence `.changeset/18603-anchor-binding-declared-capabilities.md`, `@objectstack/spec: patch`. ## Verification, at `2387ad9a5c` - `pnpm --filter @objectstack/spec build` — green. - `pnpm --filter @objectstack/spec test` — 486 test files, 14017 tests, all passed. - `pnpm --filter @objectstack/spec typecheck` — green. - `pnpm --filter @objectstack/spec check:generated` — all 15 generated artifacts up to date; nothing needed regenerating. - `pnpm build` — 73/73 tasks successful. - `pnpm lint` (`eslint . --no-inline-config`, the repo-wide population) — green, exit 0. - The gate families derived by `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`: all 75 run, all exit 0, reconciled with `--ran` (75 derived / 75 run / 0 NOT-MEASURED, derived from recorded exit codes). Three of them (`check:doc-formula-expressions`, `check:dual-build-cjs-loads`, `check:lean-entry-closure`) first answered `exit 3` PREREQUISITE NOT MET on an unbuilt tree, which is not a finding; they were re-run green after `pnpm build`. ## Acceptance notes Nothing filable was found alongside this change. The three line-attribution corrections above are reported here rather than filed: they are a nuance in a card's evidence table, not a defect in the code, and the ordering they describe is correct. Landing is the owning seat's — left as a draft, auto-merge not armed. --- _Generated by [Claude Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2085be2 commit 5100c42

2 files changed

Lines changed: 64 additions & 2 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
`AnchorBindingContext`'s boot half names the stack's capability DECLARATIONS, not the `sys_capability` rows the seeder has not written yet
6+
7+
The docblock named two sources for `declaredCapabilities`: at boot 「the
8+
`sys_capability` rows carrying `managed_by: 'package'` provenance」, at authoring
9+
time the stack's own `capabilities` array. The boot half carried an ordering
10+
precondition the sentence never stated, and a caller following it literally
11+
lands on the defect the input exists to remove.
12+
13+
`runBootstrap` (`@objectstack/plugin-security`) awaits `bindBaselineToEveryone`
14+
— the ADR-0090 D5 anchor binding, the boot call site that consults
15+
`describeHighPrivilegeBits` — BEFORE it calls `bootstrapDeclaredCapabilities`,
16+
the seeder that WRITES those `managed_by: 'package'` rows. The order is fixed by
17+
two other constraints stated at that call site: the binding must follow the
18+
seeding of the `everyone` anchor it binds to, and precede the audience-binding
19+
suggestion reconciliation. So on a first boot the table is EMPTY at exactly the
20+
moment the docblock said to read it, and this docblock's own 「omission refuses」
21+
property turns that emptiness into a silent refusal of every declared token —
22+
the app's own `isDefault` set unbindable at the `everyone` anchor, which is the
23+
defect #17811 introduced the input to remove.
24+
25+
The boot half now names the DECLARATIONS, read through the seeder's own two-step
26+
— the ObjectQL registry first, the metadata service as the fallback — which is
27+
what `readDeclaredCapabilityContext` (`@objectstack/plugin-security`, #18535)
28+
already implements, so the contract text and its one runtime consumer now
29+
corroborate each other instead of contradicting. The `sys_capability` rows stay
30+
a valid source, qualified: only once the seeder has written them, which is where
31+
an admin-surface or post-boot caller reads them.
32+
33+
⛔ No behaviour changes. The diff is comment text: `git diff` against the branch
34+
point over `src/security/high-privilege.ts` changes **0** non-comment lines (the
35+
same predicate reads 33 on that file's own #17811 commit, which is the control
36+
proving it fires). No predicate, no type, no export, no accept set moves.
37+
38+
**This is shipped, which is why it carries a changeset rather than
39+
`skip-changeset`.** `src/security/high-privilege.ts` is NOT shipped as source —
40+
`@objectstack/spec`'s published `files[]` takes `src/**/*.zod.ts`, and this file
41+
is not one (`npm pack --dry-run` lists 2021 files and excludes it, with the
42+
sibling `src/security/permission.zod.ts` present as the lit control). Its
43+
published reach is the emitted declarations, and they move: the new clause is
44+
present in `dist/security/index.d.ts` and `dist/security/index.d.mts`, both in
45+
that same shipped list, with the superseded spelling absent from every built
46+
declaration file and the docblock's unchanged neighbouring sentence present in
47+
the same two as the lit control.

‎packages/spec/src/security/high-privilege.ts‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,23 @@ function coerceRecord(v: unknown): Record<string, unknown> | undefined {
3131
* The predicates are pure and synchronous — they read one permission-set
3232
* definition and nothing else — so the one fact they cannot discover for
3333
* themselves is which capability names this stack DECLARED. The caller holds
34-
* it: at boot from the `sys_capability` rows carrying `managed_by: 'package'`
35-
* provenance, at authoring time from the stack's own `capabilities` array.
34+
* it: at boot from the stack's own `capabilities:` DECLARATIONS — and from
35+
* the `sys_capability` rows carrying `managed_by: 'package'` provenance only
36+
* ONCE THE SEEDER HAS WRITTEN THEM — at authoring time from the stack's own
37+
* `capabilities` array.
38+
*
39+
* ⚠️ [#18603] Why the boot half names the DECLARATIONS and not the rows: the
40+
* ADR-0090 D5 anchor binding (`bindBaselineToEveryone` in
41+
* `@objectstack/plugin-security`) runs BEFORE `bootstrapDeclaredCapabilities`,
42+
* the seeder that WRITES those `managed_by: 'package'` rows, and that order is
43+
* fixed by two other constraints — the binding must follow the seeding of the
44+
* `everyone` anchor it binds to, and precede the audience-binding suggestion
45+
* reconciliation. On a first boot the table is therefore EMPTY at the bind
46+
* moment, and "omission refuses" below turns that emptiness into a silent
47+
* refusal of every declared token. A boot caller reads the declarations
48+
* through the seeder's own two-step — the ObjectQL registry first, the
49+
* metadata service as the fallback — as `readDeclaredCapabilityContext`
50+
* (`@objectstack/plugin-security`) does.
3651
*
3752
* ⛔ Never synthesize this from the set under test. The point of the input is
3853
* that a set cannot vouch for its own tokens; a "declared" list derived from

0 commit comments

Comments
 (0)