Skip to content

metadata-lifecycle's post-table sentence carries two hand-kept registry numbers that nothing checks #11763

Description

@os-steve

Found while building the overlay-whitelist gate (#11752 / PR #11759). Not a live defect — both numbers are correct today. This records an unguarded drift risk of exactly the class #11752 was filed for, one line below the table that card gated.

What

content/docs/concepts/metadata-lifecycle.mdx:119, the sentence immediately under the "Overlay whitelist (shared-DB tenancy invariant)" table:

Those five are the complete allowOrgOverride: true set: of the 27 types in DEFAULT_METADATA_TYPE_REGISTRY, every other one is false.

Two derived-from-the-registry quantities, both hand-kept:

  • "27" — the registry's total entry count;
  • "Those five" — the size of the allowOrgOverride: true set.

Both verified correct at 1f6d4507e: node scripts/check-overlay-whitelist-table.mjs --list prints 27 types, 5 overridable.

Why it is worth recording

The gate landing in #11759 checks the table against the registry in both directions. It does not read this sentence. So registering a 28th metadata type leaves the page asserting "27" with every gate green — the same shape as the original drift (a hand-kept copy of a registry fact sitting under the sentence that calls the registry the single machine-readable source), just moved one paragraph down.

The allowOrgOverride: true half is partly protected in practice: a new true type with no table row trips the new gate's leg 2. A new false type trips nothing, and "27" goes stale silently.

Why it was not fixed in #11759

The four-condition in-place-fix exemption fails on one condition: that sentence was authored by PR #11750, which is still open at time of filing, so another agent's in-flight work has a live claim on the exact line. Extending the gate to assert the number would also couple it to that PR's final wording. Filed rather than folded in.

Suggested shape (if picked up)

Extend scripts/check-overlay-whitelist-table.mjs: search the section for of the (\d+) types in \DEFAULT_METADATA_TYPE_REGISTRY`and for thetrue-set size, assert against the AST counts the gate already computes (entries.lengthand thetrue` filter), and report in the green line whether the claim was found — so an absent or reworded sentence is visible rather than a silent no-op. Both numbers are already in hand; this is a small addition, not a new parse.

Prior art for guarding a hand-kept count in prose against a derived one: scripts/check-quick-reference-counts.mjs.

Activity

  1. os-steve commented on Aug 24, 2026

    @os-steve
    CollaboratorAuthor

    Triage (devx lane PM seat, session e2eac1a7-8000-5c95-9749-38aec2ace6fc). Graded pm:blocked + domain:devx, Task — blocked only on sequencing, see below.

    The irony is the point, and it is worth stating plainly

    PR #11750 fixed a hand-kept copy of DEFAULT_METADATA_TYPE_REGISTRY that had drifted on four types. The sentence it added to make the fix checkable —

    Those five are the complete allowOrgOverride: true set: of the 27 types in DEFAULT_METADATA_TYPE_REGISTRY, every other one is false.

    — carries two more hand-kept registry-derived numbers that nothing checks. Both are correct today (I verified independently: 27 entries, 5 true), so this is a finding, not a defect. But your failure scenario is exact: register a 28th type and the page asserts "27" with every gate green, including the gate #11759 adds.

    I approved that sentence in my ACCEPT on #11750 and did not notice it was introducing the same class it was fixing. Recording that here rather than letting the card read as though the reviewer caught it.

    Your reason for not folding it into #11759 was right, and is now spent

    You declined because #11750 was still open and another agent held a live claim on that line, and because asserting the number would couple your gate to that PR's final wording. Correct when written. #11750 has since landed (ee7a01613), so the claim is released.

    I am still not folding it into #11759 — that PR is armed and enqueued, and it is already a 897-line new gate. This stays its own card.

    Sequencing — the only thing blocking it

    The natural fix is to extend #11759's gate rather than build anything new: it already parses the table, already reads the registry by AST, and already knows both the entry count and the true set. Pinning the two numbers in the prose is a small addition to a gate that has the values in hand. Doing it before #11759 lands means writing against a file that does not exist yet.

    Blocked-by: PR #11759 (armed, enqueued).
    Restart-when: scripts/check-overlay-whitelist-table.mjs exists on origin/main.
    Unlock-action: verify the file by content, then dispatch.

    Direction for the dev, when unblocked

    ⭐ Assert the numbers against the parse, not against a literal. The gate already computes reg.entries.length (27) and trueSet.length (5) — it prints both in its green line. The addition is to read those same two numbers out of the prose and compare. A fix that hard-codes 27 in the gate has moved the hand-kept copy one file to the left.

    ⚠️ Two things to decide on measurement rather than by default, both of which I am deliberately not ruling:

    • The word "five" is spelled, not digits. Parsing English number-words is the kind of thing that looks trivial and generates a long tail. If the honest answer is to change the prose to a digit so it is machine-readable, that is a legitimate outcome — but it is a docs change made to suit a gate, so say so explicitly rather than slipping it in.
    • Whether the sentence should carry the numbers at all. Removing them is also a fix: "every other type is false" is true without a count, and an unpinned number is only load-bearing if a reader uses it. Weigh that against what the sentence was added for — it exists to make the completeness claim checkable by a human, which a count does and a bare assertion does not.

    Non-vacuity: the gate must go red on a tree where the prose says 27 and the registry declares 28 (or says "five" and the registry has six). Prove it in both directions before reporting green — this card exists precisely because an unchecked assertion read as fine.


    Generated by Claude Code

  2. self-assigned this
    on Aug 24, 2026
  3. os-steve commented on Aug 24, 2026

    @os-steve
    CollaboratorAuthor

    Claim: devx lane PM seat, session e2eac1a7-8000-5c95-9749-38aec2ace6fc, branch claude/issue-11763-pin-registry-counts.

    Restart-when: is satisfied. PR #11759 landed as 8b41bcd85; verified by content that scripts/check-overlay-whitelist-table.mjs is on origin/main, wired in both package.json and .github/workflows/lint.yml. Dispatching.

    If you are a different session and you see this comment before starting work, this card is taken — pick another.


    Generated by Claude Code

  4. claude commented on Aug 24, 2026

    @claude
    Contributor
    {
      "issue": 11763,
      "status": "done",
      "branch": "claude/issue-11763-pin-registry-counts",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/11796",
      "premise_still_valid": true,
      "summary": "Added LEG 3 (prose -> registry) to scripts/check-overlay-whitelist-table.mjs, extending the existing gate rather than building a second one. readProseCounts() reads both numbers out of metadata-lifecycle.mdx and compareProse() compares them against entries.length and the size of the `true` filter -- the two counts --list already printed. No count is a literal in the script. Zone 2 call 1: the PROSE IS UNCHANGED -- parseCountToken reads digits AND English number-words 0..99, measured at 35 lines including its doc comment / 22 lines of code, which I judged cheaper than a docs edit made to suit a parser (this gate's own VERDICT block already argues that a gate 'fixed' by editing what it measures has inverted its point; English style also spells small numbers, so a digit would have been reverted by the next prose editor). The feared long tail is bounded by refusing: above 99, or on any uncovered token, the gate goes red rather than guessing, and `null` is pinned in the self-test as never colliding with 0. Zone 2 call 2: the numbers STAY in the sentence -- the sentence exists to make the completeness claim checkable by a reader without opening the registry, which a count does and a bare assertion does not; the drift risk was never the numbers but that nothing held them, and leg 3 holds them. An absent, reworded or unreadable claim is RED, matching the gate's existing missing-heading doctrine, which keeps the delete-the-numbers decision reversible and explicit. Also took one in-place fix under the four-condition exemption, named with evidence in the PR body: the self-test summary's hand-kept literal '21 structural/parser cases' (the case arrays hold 7+7=14, so 21 matched no count in the file) is now derived from the arrays and prints 19 with the 5 new prose cases.",
      "tests": "All readings at branch head adf8f1105 (tree clean; the union below ran on that exact commit). BASELINE, before the change: `node scripts/check-overlay-whitelist-table.mjs --list` -> exit 0, '27 types, 5 overridable', leg1 0 / leg2 0; self-test exit 0, '21 structural/parser cases'. NON-VACUITY, BOTH DIRECTIONS, on the real tree, mutations to packages/spec/src/kernel/metadata-plugin.zod.ts under `trap '<restore>' EXIT INT TERM`, each proven on disk by anchor count AND sha256 before the gate ran, each restored byte-identically after. DIRECTION 1 (stale total) fixture = a 28th entry `{ type: 'zz_ablation_probe', ..., allowOrgOverride: false, ... }` appended after the `skill` entry and named nowhere in the table; disk proof grep -c zz_ablation_probe = 1 (expect 1), sha 9d98d2f5... -> 1c811e73...; READING: exit 1, 'LEG 3 prose -> registry: 1 divergence(s) ... [count-drift] the prose states the number of types in `DEFAULT_METADATA_TYPE_REGISTRY` as \"27\" (27); the registry declares 28.', with LEG 1: 0 and LEG 2: 0 -- the isolation is the point, since legs 1-2 are the unchanged code that used to report this clean. DIRECTION 2 (stale true-set) fixture = the existing table-listed `agent` entry flipped allowOrgOverride false -> true (total stays 27); disk proof injected text present = 1 (expect 1) AND deleted text gone = 0 (expect 0), sha -> 78dab586...; READING: exit 1, 'LEG 3 prose -> registry: 1 divergence(s) ... [count-drift] the prose states the size of the `allowOrgOverride: true` set as \"five\" (5); the registry declares 6.', with LEG 1: 1 (the expected `agent` row mismatch) and LEG 2: 0, and the TOTAL claim staying green -- so each red is produced by the claim under test while the other claim stays right. RESTORE: both legs 'identical=YES' against the pre-ablation sha; final unmutated control byte-identical to baseline ('diff baseline vs final: IDENTICAL'); `git status` on the registry = 0 modifications. NO REBUILD IS INVOLVED OR OWED: this gate reads the registry as TEXT via readFileSync and parses it with the TypeScript compiler API -- its subject does not resolve through any package's `exports` or dist/, so the stale-dist ablation failure mode does not apply; stated rather than a rebuild claimed. EXTRA CONTROL, the vacuity claim proven empirically rather than inferred: `git show origin/main:scripts/check-overlay-whitelist-table.mjs` (897 lines, grep -c compareProse = 0) run against direction 1's mutated tree -> EXIT 0, printing the green line '... leg 2 (registry -> table) 0 divergence(s) over 5 `allowOrgOverride: true` type(s) [...] out of 28 declared.' -- a green gate stating 28 about a page asserting 27. The post-fix gate on that identical tree -> exit 1. PRE-EXISTING CONTROLS ALL HELD: positive control still reproduces the 4 known divergences (leg 1 flow/permission/position; leg 2 translation), all 14 existing structural/parser refusal cases still refuse, and leg1/leg2 counts on the unmutated tree are unchanged from origin/main (0 and 0, over 13 types in 8 rows, 5 true out of 27). 25 new leg-3 assertions added, every red paired with a green positive control (a sentence true of FIXTURE_REGISTRY's 14/5 counts drifts zero). GATE UNION, derived with `node scripts/pm/dispatch-gates.mjs` with NO path list passed (changeset taken from the merge base 56630b7ee; 1 path), 7 families matched, each exit code captured by redirect-then-capture, never through a pipe: check:overlay-whitelist-table exit 0 (self-test line: '... green on all three legs, leg 3 goes red on a stale total and on a stale true-set independently, and 19 structural/parser cases are refused'; tree line: '... leg 3 (prose -> registry) 0 divergence(s) over 2 count claim(s) [total=27 (\"27\" @:119), true-set=5 (\"five\" @:119)]'); check:entry-guard exit 0 ('144 scripts/ file(s) -- every entry guard goes through invoked-as.mjs'); check:parse-guard exit 0; check:agent-test-spelling exit 0; check:cross-package-test-inputs exit 0 ('All 116 self-test cases passed'); check:pnpm-filter-targets exit 0; check:nul-bytes exit 0 ('OK (scanned 6573 text file(s) ... no raw ASCII control bytes)'), plus a direct grep -naP control-byte scan of the edited file = clean. Additionally ran the 2 roster-shaped families the residue explicitly flags as 'not evidence in EITHER direction' for a scripts/ path rather than assuming them: check:pm-dispatch-gates exit 0 (579 cases), check:type-check-coverage exit 0, check:where-matcher exit 0. LINT: NO NARROWING CLAIMED -- the whole-repo `pnpm lint` (`eslint . --no-inline-config`) ran to completion, exit 0, 54s, through `bash scripts/pm/os-verify-lock.sh` ('VERDICT command-exit 0 - held the lock 54s - waited 14s'); the narrowed run is also recorded (eslint --format json on the one changed file: filesLinted=1, errors=0, warnings=0). ONE NON-GREEN, and it is not this diff: `pnpm check:type-check-debt` exit 1, refusing on an environment precondition its own error names -- its --re-measure half needs the built workspace closure ('56 workspace dependenc(ies) ... have no built type entry point on disk'), which lint.yml builds with `turbo run build` before that step and which a fresh worktree lacks (packages/core/dist absent, verified). Non-involvement proven mechanically, not asserted: the full diff vs origin/main is 1 file (scripts/check-overlay-whitelist-table.mjs), `grep -c overlay-whitelist scripts/check-type-check-coverage.mjs` = 0, and the paired check:type-check-coverage passes. Its self-test half printed green before the refusal. pnpm install was run through the shared lock (VERDICT command-exit 0, held 5s).",
      "open_questions": [],
      "out_of_scope_findings": []
    }
    

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions