Repository navigation
Commit 9b00f9f
Clause-②: no
Closes #16898
`scripts/symbol-anchors.mjs` states the #13556 ruling it implements in
its own header, verbatim: **"One resolver."** The platform-checklist
corpus resolved symbol anchors with a rule of its own — mask comments,
then ask whether the bare token appears anywhere in the file — in
exactly the place that ruling says there is to be one implementation. It
was the **looser** of the two, which is the dangerous direction: a
second resolver that is greener than the shared one is never the
resolver anybody points at, so the drift is only ever found by census.
## What changed
`check-platform-checklist.mjs#absentAnchorSegments` now hands every
segment to `scripts/symbol-anchors.mjs#symbolResolutionClass` and holds
**no matcher at all** — no regex, no `maskComments`, nothing. The
per-segment split of a dotted anchor stays, because that is this
corpus's anchor *grammar* (the shared `#symbol` carries no dot), not a
rule about what "present" means. A self-test assertion (N5) reads this
file's own source to pin the body against re-forking.
⛔ `scripts/symbol-anchors.mjs` is **untouched**. Widening the shared
core to keep this ledger green would export the looser reading to the
ADR, `scripts/**`, spec-docblock and system-context corpora — the exact
failure the ruling exists to prevent.
⛔ No floor is lowered, and the maintainer-only
`scripts/checklist-symbol-anchor-baseline.json` is **not edited at
all**.
## The measurement — anchors ceasing to resolve IS the deliverable
| | anchor occurrences |
| --- | --- |
| scanned | 633 |
| resolved under the **withdrawn permissive rule** | 633 |
| resolved under the **shared resolver** | **577** — 516 `declaration`,
61 `literal` |
| **stopped resolving** | **56** (55 distinct `(family file, anchor)`
pairs, 54 distinct anchors) |
That 56 is coverage this checklist was reporting and did not have. It is
carried in `SHARED_RESOLVER_RESIDUAL`, one row per `(family file,
anchor)`, each with the shape that used to resolve it and a verdict —
**not** absorbed by a rule and **not** absorbed by a lowered floor.
The residual is a **closed ledger, not an allow-list with a shape**:
- every row must **fire**; a row whose anchor resolves again or has gone
is a `STALE RESIDUAL ROW` red that names it and asks for its deletion,
so the ledger cannot outlive the defect it records;
- an anchor that fails to resolve and is **not** on the list is an
ordinary `ABSENT SYMBOL` red — new bad citations cannot join silently;
- a grow-never ceiling sits beside it, so rows leave by **repair**,
never by raising a number.
The floor population is now `resolved + residual` and the gate's own
output prints both halves, so the coverage claim is legible rather than
folded:
```text
symbol anchors: 578/634 resolved by `symbol-anchors.mjs` (the ONE resolver) against 309 cited sources, 56 on the named #16898 residual, 17 file floors held
```
(578/634 rather than 577/633 because the README rewrite below adds one
anchor, which resolves.)
## Every broken anchor, classified
Acceptance 2. `bad-citation` = the cited file does not declare the
symbol; the repair is in the ledger (re-point the anchor, or drop to a
bare citation). `accept-set` = the anchor names something real the
**shared** accept set does not reach ⇒ **a different card against
`scripts/symbol-anchors.mjs`**, and ⛔ nothing here widens it.
### verdict: `bad-citation` — 47 rows
**`string-substring`** (29) — symbol survives only INSIDE a longer
string token
| anchor | family file |
| --- | --- |
| `examples/app-showcase/src/data/seed/index.ts#Specimen` |
`areas/records-forms.json` |
| `packages/mcp/src/plugin.ts#OS_MCP_SERVER_ENABLED` | `areas/ai.json` |
| `packages/objectql/src/overlay-precedence.test.ts#not_overridable` |
`areas/studio-authoring.json` |
|
`packages/platform-objects/src/identity/sys-oauth-application.object.ts#OAuth`
| `areas/identity-auth.json` |
| `packages/plugins/plugin-auth/src/auth-plugin.ts#Providers` |
`areas/platform-core.json` |
|
`packages/plugins/plugin-auth/src/auth-route-ledger.ts#bootstrapStatus`
| `areas/identity-auth.json` |
| `packages/plugins/plugin-auth/src/auth-route-ledger.ts#linkSocial` |
`areas/identity-auth.json` |
| `packages/plugins/plugin-auth/src/auth-route-ledger.ts#revokeOthers` |
`areas/identity-auth.json` |
|
`packages/plugins/plugin-auth/src/auth-route-ledger.ts#sendVerificationEmail`
| `areas/identity-auth.json` |
| `packages/plugins/plugin-auth/src/auth-route-ledger.ts#setActive` |
`areas/identity-auth.json` |
| `packages/plugins/plugin-auth/src/auth-route-ledger.ts#updateUser` |
`areas/identity-auth.json` |
|
`packages/qa/dogfood/test/membership-role-vocabulary.dogfood.test.ts#PermissionSet`
| `areas/identity-auth.json` |
|
`packages/qa/dogfood/test/package-first-authoring.dogfood.test.ts#writable_package_required`
| `areas/platform-core.json` |
| `packages/rest/src/meta-write-actor-identity.test.ts#Actor` |
`areas/studio-authoring.json` |
| `packages/rest/src/rest-route-ledger.ts#describeDelegableScope` |
`areas/identity-auth.json` |
| `packages/rest/src/rest-route-ledger.ts#getHistory` |
`areas/studio-authoring.json` |
| `packages/rest/src/rest-route-ledger.ts#jobId` |
`areas/records-forms.json` |
| `packages/rest/src/rest-route-ledger.ts#REST` |
`areas/api-backend.json` |
| `packages/rest/src/rest-route-ledger.ts#REST` |
`areas/studio-authoring.json` |
| `packages/rest/src/rest-route-ledger.ts#saveItem` |
`areas/access-security.json` |
| `packages/rest/src/rest-route-ledger.ts#shareId` |
`areas/access-security.json` |
| `packages/runtime/src/route-ledger.ts#commitId` |
`areas/platform-core.json` |
| `packages/runtime/src/route-ledger.ts#getLegalNextStates` |
`areas/api-backend.json` |
| `packages/runtime/src/route-ledger.ts#getRuntimeStatus` |
`areas/automation.json` |
| `packages/runtime/src/route-ledger.ts#getScreen` |
`areas/automation.json` |
| `packages/runtime/src/route-ledger.ts#runId` | `areas/automation.json`
|
| `packages/spec/src/data/object.zod.ts#FEEDS_DISABLED` |
`areas/records-forms.json` |
| `packages/spec/src/data/object.zod.ts#query` |
`areas/records-forms.json` |
|
`packages/triggers/trigger-api/src/trigger-api-route-ledger.ts#flowName`
| `areas/api-backend.json` |
**`import-only`** (9) — cited file IMPORTS the symbol; the declaration
is elsewhere
| anchor | family file |
| --- | --- |
| `examples/app-showcase/objectstack.config.ts#ConnectorRestPlugin` |
`areas/automation.json` |
|
`examples/app-showcase/src/security/seed-approval-demo.ts#AUDITOR_DEMO_USER`
| `areas/approvals.json` |
|
`examples/app-showcase/src/security/seed-approval-demo.ts#PHONE_DEMO_USER`
| `areas/identity-auth.json` |
| `packages/cli/src/commands/compile.ts#emitJson` | `areas/cli.json` |
|
`packages/cli/src/commands/doctor-deprecation-hint-commands.test.ts#Doctor`
| `areas/cli.json` |
| `packages/cli/src/utils/format.exit-code.test.ts#emitJson` |
`areas/cli.json` |
|
`packages/platform-objects/src/identity/sys-member.object.ts#BUILTIN_MEMBERSHIP_ROLE_OPTIONS`
| `areas/identity-auth.json` |
| `packages/runtime/src/domains/ai.ts#capabilityUnavailable` |
`areas/ai.json` |
|
`packages/services/service-messaging/src/messaging-service.ts#PreferenceResolver`
| `areas/integration-system.json` |
**`json-value-not-key`** (3) — JSON target carries it as a VALUE; the
shared rule reads KEYS
| anchor | family file |
| --- | --- |
|
`packages/create-objectstack/src/templates/blank/package.json#objectstack`
| `areas/cli.json` |
| `packages/spec/liveness/email_template.json#requireVars` |
`areas/integration-system.json` |
| `packages/spec/liveness/field.json#live` |
`areas/attachments-storage.json` |
**`regex-literal`** (1) — symbol survives only inside a regex literal
| anchor | family file |
| --- | --- |
| `packages/verify/src/verify.ts#VALIDATION_FAILED` | `areas/cli.json` |
**`detector-artifact`** (1) — not a citation at all: THIS gate's
detector truncated an item-id at its first hyphen
| anchor | family file |
| --- | --- |
| `docs/qa/platform-checklist/areas/access-security.json#access` |
`areas/identity-auth.json` |
**`member-access`** (3) — symbol survives only as `x.symbol` on another
object
| anchor | family file |
| --- | --- |
| `packages/objectql/src/engine.ts#objectExtensions` |
`areas/platform-core.json` |
|
`packages/plugins/plugin-security/src/security-plugin.ts#__referentialFieldClear`
| `areas/identity-auth.json` |
| `packages/runtime/src/domains/notifications.ts#markRead` |
`areas/platform-core.json` |
**`local-binding`** (1) — a parameter name (plus a hyphenated string
sharing the token)
| anchor | family file |
| --- | --- |
| `packages/lint/src/validate-action-locations.ts#action` |
`areas/records-forms.json` |
### verdict: `accept-set` — 8 rows
**`inline-key`** (6) — a real object-literal key, written INLINE rather
than at the start of a line
| anchor | family file |
| --- | --- |
| `examples/app-showcase/objectstack.config.ts#declarativeStdio` |
`areas/integration-system.json` |
|
`examples/app-showcase/src/data/objects/business-unit.object.ts#allowCreate`
| `areas/records-forms.json` |
| `examples/app-showcase/src/data/seed/index.ts#sales_region` |
`areas/dashboards.json` |
| `examples/app-showcase/src/data/seed/index.ts#signed_on` |
`areas/dashboards.json` |
|
`examples/app-showcase/src/system/datasources/showcase-external.datasource.ts#onMismatch`
| `areas/integration-system.json` |
| `examples/app-showcase/src/ui/actions/index.ts#maxSize` |
`areas/records-forms.json` |
**`dotted-string-head`** (2) — a DATA identifier that is the HEAD
SEGMENT of a dotted string token
| anchor | family file |
| --- | --- |
| `packages/spec/src/kernel/public-auth-features.ts#sys_invitation` |
`areas/identity-auth.json` |
| `packages/spec/src/kernel/public-auth-features.ts#sys_user` |
`areas/identity-auth.json` |
⭐ The single sharpest row is the `detector-artifact`:
`docs/qa/platform-checklist/areas/access-security.json#access`. There is
no such anchor — **this gate's own detector** truncated an item-id
reference (`…json#access-security.scope-depth-asymmetry`) at its first
hyphen, and the permissive rule then "resolved" the phantom `#access`
against the spelling `access-security` sitting in the file. A looser
second resolver does not merely miss rot; it manufactures green for
citations that were never written.
## Does the third corpus diverge? — No. (Acceptance 3, with the reading)
The card counted **three** registered corpora when it was filed. On
`main` today there are **four**, and the fourth arrived after the card
(#17241, `6aa1d09043`). All four were read:
| gate | registration | its own resolution rule? |
| --- | --- | --- |
| `scripts/check-adr-symbol-anchors.mjs` | `defineCorpus` +
`sweepCorpus` | none |
| `scripts/check-scripts-symbol-anchors.mjs` | `defineCorpus` +
`sweepCorpus` | none |
| `scripts/check-spec-docblock-symbol-anchors.mjs` | `defineCorpus` +
`sweepCorpus` | none |
| `scripts/check-system-context-census.mjs` | `defineCorpus` +
`sweepCorpus` | none — and it additionally calls `symbolResolutionClass`
**directly** for its own `NON_READ_ANCHORS` ledger rows, i.e.
delegation, not a fork |
Mechanical readings behind that table:
- `grep -ln "defineCorpus({" scripts/*.mjs` ⇒ the four gates above plus
`scripts/symbol-anchors.mjs` (the definition).
- `grep -ln "symbolResolutionClass" scripts/*.mjs` ⇒
`check-system-context-census.mjs`, `isystem-census.mjs`,
`symbol-anchors.mjs` — all consumers, no re-implementation.
- No local symbol-presence predicate exists in any of the four (no
`function …Symbol…` / `…Resolve…` / `absent…` / `declares…` definitions
beyond their `assert` helpers).
⇒ **The shape of the fix is unchanged.** This is "one corpus went its
own way", not "the shared resolver was never really adopted" — the only
divergence in the tree was `check-platform-checklist.mjs`, and this PR
closes it.
⚠️ One honest residual on that question: this corpus is now bound to the
shared **resolution rule**, but it is still **not** a `defineCorpus`
registration — its anchors live in JSON string values rather than
markdown code spans, and its `#symbol` may be dotted where the shared
grammar's may not. Full registration would need the shared core widened
on both counts, which is precisely what this card forbids doing here.
Recorded below as an unsettled question for the PM.
## Positive control (Acceptance 4)
⛔ Without it, "bound correctly" and "everything broke" are
indistinguishable. Three independent readings, all on this branch:
1. **At scale, live.** 577 of 633 anchors still resolve — 516 as
`declaration`, 61 as `literal`. A binding that simply refused everything
would have reported 0.
2. **In the self-test, on one fixture.** `N4` asserts a declaration
(`export function declaredHere`) **and** a complete quoted token
(`'whole_token'`) both still resolve, in the same fixture where
`N1`/`N2`/`N3` assert that a call site, a local parameter and a
substring of a longer string token do not.
3. **The control is load-bearing, proved by ablation.** Re-forking the
resolver (below) fires `P3`, `P5`, `N1`, `N2`, `N3`, `N5` — and leaves
`N4` **silent**, because the permissive rule also accepts a declaration
and a whole token. That is exactly why `N4` alone cannot detect a fork
and why it has to sit beside `N1`–`N3` rather than instead of them.
## Ablation — four legs, each with an on-disk mutation proof and a
hash-settled restore
⭐ ⛔ No restore relies on `trap` (measured to silently not fire in this
container, #17875). Every restore is `git checkout HEAD -- PATH` — never
bare — settled by `git hash-object` equality against the `HEAD` blob
**and** an empty `git diff HEAD`, never by an exit code. Every mutation
is proved to have reached disk by a before/after `grep -c` on the exact
anchored text **and** a blob-hash change, before any verdict is read.
`HEAD` blob for `scripts/check-platform-checklist.mjs`:
`7be4b5ddef7f08f3fc02cc1ffe86626f80ace9c4`.
| leg | mutation | on-disk proof | result |
| --- | --- | --- | --- |
| A | put the permissive token match back into the resolver body |
anchor `1 → 0`, injected text `×1`, blob `7be4b5d… → b1a9d29…` | gate
**exit 1**, resolver positive control FAILED: `P3 P5 N1 N2 N3 N5` — `N4`
silent |
| B | delete one residual row (`…/plugin.ts#OS_MCP_SERVER_ENABLED`) |
row `1 → 0`, blob changed | gate **exit 1**, 1 × `ABSENT SYMBOL` naming
that anchor — an unlisted failure still reds |
| C | append a 56th residual row | bogus row `×1`, blob changed | gate
**exit 1**, grow-never ceiling refuses (`D3`) — the ledger cannot grow |
| D | re-point one residual row at an anchor that resolves | old `1 →
0`, new `×1`, blob changed | gate **exit 1**, 1 × `STALE RESIDUAL ROW` +
1 × `ABSENT SYMBOL` — a row that stops firing is a red |
All four restores printed `RESTORE: SETTLED — blob ==
7be4b5d AND git diff HEAD empty`. Tree
verified clean afterwards; ⛔ no ablation artefact is committed.
## Tests pinning the permissive behaviour — re-judged in place, ⛔ never
deleted
Two self-test assertions asserted a resolution only the permissive rule
could produce. Both are kept, with the verdict inverted and the reason
recorded in a comment block above them, because deleting them would
leave the tree unable to say the narrowing ever happened. Each keeps the
direction it really covered, on a shape the shared rule does accept:
| assertion | was | is now | direction preserved as |
| --- | --- | --- | --- |
| `P3` | `RESOLVED.mode` resolves — `{ mode: 'strict' }` written INLINE
| that member is **ABSENT**; the shared rule takes a member key at the
**start of a line** | `P3b`, on a member declared at the start of a line
|
| `P5` | a `.json` **value** resolves, because JSON was scanned unmasked
| a `.json` value is **ABSENT**; the shared rule reads JSON **keys** |
`P5b`, which resolves a key while a `//`-carrying string value sits
beside it |
⚠️ `P3`'s shape is not incidental — six of the eight `accept-set`
residual rows are that same inline-object-literal-key shape, so `P3` now
pins the very narrowness the follow-up card is about.
New assertions: `P3b`, `P5b`, `N1`–`N5` (the binding and its positive
control), `D1`–`D4` (the residual ledger, both directions). The
`BATTERY_SYMBOL_ANCHORS` floor is ratcheted `29 → 40`; the file's
self-test total is `176 → 187`.
## Reverse-read: which existing sentence does this make false?
Sentences carrying a rev or a date are history and were left alone; what
rots is a bare present-tense claim. Both directions were checked.
**Made false — fixed in this PR (1):**
1. `docs/qa/platform-checklist/README.md`, the author-facing statement
of the rule. Three claims went false at once: *"A symbol inside a string
literal counts as present"* (now: a **complete quoted token** only),
*"This is presence-in-code, not a declaration analysis"* (it now **is**
one, plus the literal class), and the framing that
`check:platform-checklist` decides the question at all. Rewritten to
state that the rule belongs to the shared module, to name what an author
actually gets (comments stripped, declaration, complete quoted token —
and **nothing else**), and to say that an anchor the core refuses is
either a bad citation or a card against that module.
**Made false — reported, ⛔ NOT edited here (1):**
2. `scripts/checklist-symbol-anchor-baseline.json`, its `$comment`:
*"Each entry is the count of anchors that RESOLVED in that family
file."* An entry is now `resolved + residual`. ⛔ Not edited: #16898
states that file is maintainer-only by its own `$authority` line, and
the safe reading of "maintainer-only" is the whole file, not just the
numbers. **No floor is changed either way.** The gate's own
`--anchor-census` output now says the per-file counts are the FLOOR
population and not a coverage figure, so the honest reading is available
from the gate even while the stale sentence stands. ⇒ **a one-line
maintainer edit, not this PR's to make.**
**Reverse direction — a sentence that was false and is now closer to
true, ⛔ NOT edited (1):**
3. `scripts/symbol-anchors.mjs`, its ruling block: *"#13788 … reuses
THIS module by registering a corpus."* That was simply **false** before
this PR — the checklist reused nothing. It is now **half true**: the
module's resolution rule is reused; a `defineCorpus` registration still
does not exist, for the grammar reasons above. Left for the maintainer
along with the follow-up card, since qualifying a quoted ruling is their
call and the remaining gap is exactly that card's subject.
**Zeros, reported as zeros:** no ADR sentence, no `content/docs/**` page
and no other gate header states the checklist's resolution rule — `grep`
over `docs/qa/platform-checklist`,
`scripts/checklist-symbol-anchor-baseline.json` and `.github/workflows`
returned the three items above and nothing else. No test outside
`check-platform-checklist.mjs` asserts on this rule.
## Verification
- `node scripts/check-platform-checklist.mjs` ⇒ **exit 0**, line quoted
above.
- `node scripts/check-platform-checklist.mjs --self-test` ⇒ **exit 0**,
187 assertions.
- Derived gate family (`node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack`, change set taken from git, not
hand-listed): **34 families derived, 34 run, 0 NOT-MEASURED, 0 UNRUN**,
reconciled with recorded exit codes via `--ran`. All 34 exit 0 —
including `check:scripts-symbol-anchors` (this file's own comment prose
is in that corpus), `check:comment-mask-corpus`, `check:nul-bytes`,
`check:self-test-wired` and `check:declaration-mirrors`. One family
needed `turbo run build --filter=@objectstack/formula
--filter=@objectstack/lint` first and reported exit 3 (PREREQUISITE NOT
MET, nothing measured) until it had it; it is green on the built tree.
- Control-character sweep beyond the gate: `grep -naP
'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'` over both changed files ⇒ no hits.
## Changeset — measured, then judged: `skip-changeset`
Nothing published moves. Measured rather than asserted: 82
`package.json` files enumerated, 70 publishable (non-private), and
**no** publishable package's `files[]` reaches `scripts/` or `docs/qa/`.
Positive control on the same enumeration: 70 of those 70 `files[]`
blocks do mention `dist`, so the scan was reading real arrays and not an
empty set. ⇒ the `skip-changeset` **label** is applied to this PR (a
label, not a body sentence).
## Follow-ups this PR deliberately does not do
1. **Repair the 47 `bad-citation` rows.** #16898 says in as many words
that how the findings land "is a sizing question this card does not
answer", and floors have almost no headroom, so dropping a `#symbol` is
not available and each row needs a re-point with judgement. Rows leave
the ledger one repair at a time.
2. **Widen the shared accept set for the 8 `accept-set` rows** — an
inline object-literal key (6) and the head segment of a dotted string
token such as `sys_user` in `'sys_user.actions.invite_user'` (2). That
is a card against `scripts/symbol-anchors.mjs`, weighed against all five
corpora, ⛔ never a local widening.
3. **Register this corpus through `defineCorpus`**, which the ruling
actually asks for and which needs the shared grammar to reach dotted
symbols and non-code-span anchors.
4. ⛔ **#16821's accept set** is out of scope per triage ("correctly
fenced") and was not touched.
---
_Generated by [Claude
Code](https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU)_
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 82d8942 commit 9b00f9f
2 files changed
Lines changed: 344 additions & 51 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
173 | 173 | | |
174 | 174 | | |
175 | 175 | | |
176 | | - | |
177 | | - | |
178 | | - | |
179 | | - | |
180 | | - | |
181 | | - | |
182 | | - | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
183 | 201 | | |
184 | 202 | | |
185 | 203 | | |
| |||
0 commit comments