Uh oh!
There was an error while loading. Please reload this page.
docs(validating-metadata): the four-door table records the object runtime door after #4716 - #9847
Conversation
…time door after #4716 (#9836) Three rows of the "one gate, four doors" table read `—` in the `runtime publish` column for rules that now run there. #4716 (PR #9825, squash 1408ae3) crossed five gating object rules onto that door, taking it from 2 rules to 7. Corrected against the built registry, not the source text: :392 functional completeness + managed API methods — -> ✓ᵒ :413 autonumber `{field}` interpolation — -> ✓ᵒ The "declared enforcement that cannot run" row is SPLIT rather than edited. It bundled four rule ids that now sit on opposite sides of the wall: `validateRuleCompilability` (#4762) and `validateRuleSchemaFormats` (#5178) carry `runtimeTypes: ['object']`, while `validateSharingRuleEnforceability` (#4698) and `validateRlsPredicateEnforceability` (#4983) are `CLI_ONLY`. `✓ᵒ` on the merged row would claim the door judges an RLS predicate on an object write; `—` keeps the current falsehood. The split is drawn by which collection carries the declaration each rule reads, and a new paragraph states that rule so the next crossing does not re-derive it — following the precedent the page already sets for the security vocabulary freeze. Also: #4716 appended to the crossing-history clause, and the `—`-reasons paragraph gains the reason class #4716 created. All four existing reasons still hold, but the advisory-tier object rules moved: their old `RUNTIME_OBJECT_WRITES_P2` reason is retired (0 occurrences), replaced by `RUNTIME_OBJECT_ADVISORY_VOLUME` on six rules whose registry text says crossing them is "explicitly NOT a bare `runtimeTypes` edit" — the opposite of what the paragraph's third reason would have told a reader. No change to `AUTHORING_RULES`, no rule's `runtimeTypes`, nothing under `content/docs/releases/`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019yDEhPBC3tcGkW9bkce1HM
os-elon
commented
Aug 19, 2026
PM review — ACCEPT, ready flipped, enqueued (squash auto-merge)Reviewed against GitHub, not against the report. Every load-bearing claim in the body was re-derived here independently rather than taken on trust. Independent verification, on |
| claim in the body | my measurement | ✓ |
|---|---|---|
| object door = 7 rules (anchored probe) | ^\s+runtimeTypes: \[[^]]*'object' → 7 | ✓ |
| naive probe returns 8 (the trap) | runtimeTypes.*'object' → 8 | ✓ reproduces |
RUNTIME_OBJECT_WRITES_P2 retired | 0 occurrences | ✓ |
RUNTIME_OBJECT_ADVISORY_VOLUME is its replacement | 8 occurrences | ✓ |
validateSharingRuleEnforceability absent from the door | surfaces: CLI_ONLY, no runtimeTypes | ✓ |
validateRlsPredicateEnforceability absent from the door | surfaces: CLI_ONLY, no runtimeTypes | ✓ |
The two absence rows are what make the :411 split truthful rather than merely plausible — they are the reason the — half of the split row is a correct statement about the door and not a leftover.
Divergence — and the check that actually mattered here
Merge base 1408ae337 (correctly the #4716 squash — the card required deriving on the merged ref, and the branch is based on it). origin/main has since advanced 17 commits:
| probe | result |
|---|---|
commits touching content/docs/deployment/validating-metadata.mdx | 0 |
⭐ commits touching packages/lint/src/authoring-rules.ts | 0 |
git merge-tree conflicts against current main | 0 |
| files this branch changes | 1 |
| positive control — same command on a file main did move | 1 |
That second row is the one I ran deliberately. This PR's entire content is a derivation from the registry, so a doc that is correct against authoring-rules.ts at the merge base would be silently wrong if that file had moved underneath it. It has not moved in 17 commits, so the derivation is still live. A per-file check on the changed file alone would not have caught that class of staleness — the input file matters as much as the output file when the change is a derivation.
What raises this above the card as written
The card asked the dev to "re-read the —-reasons paragraph … check the others rather than assuming." They did, bucketed all 25 no-runtimeTypes rules from the built registry, accounted for 18 across the four existing reasons — and found the remaining 6 sit in a class the paragraph did not have, created by #4716 itself.
The consequence is the part worth keeping: those six now carry RUNTIME_OBJECT_ADVISORY_VOLUME, whose own text says crossing one is a UX decision with its own card and "never a bare runtimeTypes edit." Left uncorrected, the paragraph's third reason would have pointed the next reader at a runtimeTypes flip for exactly the population where the registry says that is the wrong move. That is a doc actively teaching the wrong repair, caught by doing the check rather than asserting it. Both readings agree and the trap was reproduced rather than merely avoided.
The :411 split is drawn on a principled basis — which collection carries the declaration each rule reads — and the doc quotes each rule's own surfaceReason rather than re-modelling the reasoning, so the two — rules stay correct for the right reason (the door does not accept sharing_rule as a type at all; permissions has been in the snapshot since #8309 and only the declaration is missing). The prose paragraph stating the rule follows the page's own vocabulary-freeze precedent, so the next crossing inherits it.
The U+200B that slipped into validateRuleCompilability mid-prose and was caught by a scan before commit is recorded in the body rather than quietly fixed. That is the right instinct — a zero-width character inside an identifier is exactly the defect that survives review by being invisible.
Note on sequencing
Enqueued without waiting for the structured report. The PR body is a complete deliverable that declares 3c693d15c final and carries 12/12 named gates green; the artifact is on GitHub and I have verified it directly. Auto-merge re-validates through the queue regardless, so a late push cannot land unchecked.
Tracking to MERGED and verifying on the artifact.
Generated by Claude Code
Fixes#9836
Docs-only.
content/docs/deployment/validating-metadata.mdxdescribes "the one gate, four doors"; #4716 (PR #9825, squash1408ae337) crossed five gating object rules onto the runtime publish door, and three rows of the table still read—for rules that now run there.The column was re-derived from the registry, not from the issue's table
Two independent readings, both on this branch's base
1408ae337, because the naive probe is known to be wrong here.Reading 1 — the built registry.
pnpm --filter '@objectstack/lint^...' buildthenpnpm --filter @objectstack/lint build, thenAUTHORING_RULESimported frompackages/lint/dist/index.jsand filtered onruntimeTypes.includes('object'). 7 rules, allsurfaces: cli,runtime-publish, alltier: gating:runtimeTypesvalidateFunctionalCompleteness["object"]validateManagedApiMethods["object"]validatePresetComparands["dashboard","view","object","page","flow"]lintAutonumberFormats["object"]validateSecurityPosture["seed","permission","book","object"]validateRuleCompilability["object"]validateRuleSchemaFormats["object"]Reading 2 — anchored source grep,
^\s+runtimeTypes: \[overpackages/lint/src/authoring-rules.ts: 16 declarations, 7 containingobject(:472 :500 :597 :1072 :1260 :1382 :1408). Agrees with reading 1.The trap, reproduced.
grep -c "runtimeTypes.*'object'"returns 8. The extra is:1276, a comment inside thevalidateSecurityRoleWordblock that quotes a declaration it does not make. The anchored form excludes it because a comment line begins with//, not whitespace-then-runtimeTypes. A grep cannot tell a claim from a description of a claim; both readings above avoid the form that can't.Non-vacuity control. The two rules the issue predicts are ABSENT from the object door read absent in the same query that finds the other seven:
validateSharingRuleEnforceability→surfaces: clionly, noruntimeTypes;validateRlsPredicateEnforceability→ same. So the—cells are a positive claim about the door, not an artifact of a query that finds nothing. Cross-check on the other side: pre-merge (1408ae337^) the anchored grep finds only:567and:1226carryingobject— the door was 2 rules and is now 7, which is exactly #4716's five.The three rows
:392Zod-valid but functionally inert declarations →✓ᵒ(validateFunctionalCompleteness,validateManagedApiMethods):413Autonumber{field}interpolation →✓ᵒ(lintAutonumberFormats):411— split, not a cell edit.How the
:411split is drawnThe row bundled four rule ids now on opposite sides of the wall.
✓ᵒon the merged row would claim the door judges an RLS predicate on an object write;—keeps the current falsehood. Split by which collection carries the declaration each rule reads:formatnames (#5178)validateRuleCompilability,validateRuleSchemaFormats✓ᵒvalidateSharingRuleEnforceability,validateRlsPredicateEnforceability—The two that crossed read
validationRules[]on the object being written, so anobjectwrite already carries everything they judge. The two that stayed readsharingRules[]andpermissions[], and are held by different things — each stated in the registry's ownsurfaceReason, so the doc quotes the registry rather than modelling it: the door does not acceptsharing_ruleas a type at all, whereaspermissionshas been in the snapshot since #8309 and only the declaration is missing.A new paragraph states the split rule in prose, following the precedent the page already sets for the security vocabulary freeze ("one rule id has to sit on ONE side of the wall"), so the next crossing does not have to re-derive it.
The
—-reasons paragraph — checked, and one reason class was missingAll four enumerated reasons are still true of at least one remaining
—cell, bucketed from the built registry over the 25 rules with noruntimeTypes: stack-wide collection 13 rules;typescript/sucrasesource parse 2; snapshot-safe-not-rolled-out 2 (exactly the sharing-rule / RLS pair the paragraph names, which survives untouched); capability-reference graduation 1.That accounts for 18 of 25. The remaining 6 sit in a class the paragraph did not have, and #4716 is what created it. Their old reason constant
RUNTIME_OBJECT_WRITES_P2is retired — 0 occurrences post-merge; they now carryRUNTIME_OBJECT_ADVISORY_VOLUME, whose text says crossing one is a UX decision with its own card and "never a bareruntimeTypesedit". Left uncorrected, the paragraph's third reason would have pointed a reader at aruntimeTypesflip for precisely the population where the registry says that is the wrong move. Added as a fifth reason, phrased from the registry constant.Also
#4716appended to the crossing-history clause in its siblings' shape (what crossed, onto which type).Out of scope, untouched
content/docs/releases/(release-owned);AUTHORING_RULESand every rule'sruntimeTypes/surfaces/surfaceReason; theRUNTIME_NEEDS_FULL_SNAPSHOTpopulation (17 occurrences, unchanged — #9612's territory).Verification — 12/12 gates green at
3c693d15c, the final commitUnion re-derived at the final HEAD with
node scripts/pm/dispatch-gates.mjs(no paths passed; it takes its own change set from the merge base), then run:check:nul-bytes·check:doc-anchors·check:doc-authoring·check:docs-audit-scope·check:docs-redirects·check:role-word·check:published-readme-links·check:cross-package-test-inputs·@objectstack/speccheck:empty-state·check:liveness·check:strictness-ledger·check:variant-docsNo package tests are affected and none should be: the diff is one file under
content/docs/, in no workspace package. The only code references to this page are two route entries inapps/docs/redirects.mjs, a comment incheck-doc-anchors.mjs, and the generatedapps/docs/.source/*— no test pins the table's content.docs-drift-check.ymldoes not trigger here; itspaths:arepackages/**,scripts/docs-audit/**and its own file, so acontent/**-only diff is correctly outside it. The affected-docs mapper itself is exercised bycheck:docs-audit-scope, which is green.One byte-discipline note against the record: a U+200B slipped into
validateRuleCompilabilitywhile this prose was being written, and was caught by a scan before commit. It is gone;check:nul-bytesis green and the identifier greps as one token.No changeset —
skip-changesetAGENTS.md §"Add a changeset for feature work" scopes changesets to features and functional improvements. This ships no package:
content/is in no workspace entry, andapps/docsis private. The ten most recent docs-only commits onmaincarry zero changesets. Theskip-changesetlabel is applied instead.Generated by Claude Code
Generated by Claude Code