Uh oh!
There was an error while loading. Please reload this page.
feat(lint): the five gating object rules cross the runtime publish gate (#4716) - #9825
Conversation
…te (#4716) The 2026-08-18 adjudication's narrowed scope: validateFunctionalCompleteness, validateManagedApiMethods, lintAutonumberFormats, validateRuleCompilability and validateRuleSchemaFormats gain runtimeTypes: ['object']; the six advisory-tier object rules stay behind (advisory-volume decision, its own card). The false-positive budget was exempted on 0 refusals / 75 real object declarations (authored config-file metadata — a lower bound), with a post-launch overlay-row replay as the standing audit. runtime-lazy-deps.test.ts re-pins the boot-path contract as three tiers after its object leg (PR #9295) fired red on this widening as designed: parsers never load; ajv never loads at import or on schema-free writes; ajv is REQUIRED to load, lazily, when an object write carries a json_schema validation — and the gate must refuse the uncompilable one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017qYPmkKEsfbWY1yVg83p8F
…Zod-required message on the json_schema fixture (#4716) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017qYPmkKEsfbWY1yVg83p8F
… the 403 stays the sentence under test (#4716) The five gating object rules now answer at the 422 lint door, which speaks before the overlay lock (the established #8310 ordering, recorded in this fixture's own comment). The control body carried an optionless select — incidental, and now refused by validateFunctionalCompleteness as field/choice-without-options — so the NOT_OVERRIDABLE control was refused for the wrong reason. Authoring the options restores the fixture to what it pins. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019yDEhPBC3tcGkW9bkce1HM
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c0bdc3285343dcaf97f352722b32fc07990febef && git checkout c0bdc3285343dcaf97f352722b32fc07990febef
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9ff11921a26f44a62ca834651adaa54105468536 3b7f59907f108df395de8ad064dcbd3f53fc9bc3 && git checkout -B drift-repro 9ff11921a26f44a62ca834651adaa54105468536 && git merge --no-ff 3b7f59907f108df395de8ad064dcbd3f53fc9bc3
node scripts/docs-audit/affected-docs.mjs --json 9ff11921a26f44a62ca834651adaa54105468536
|
os-elon
commented
Aug 19, 2026
PM review — ACCEPT, ready flipped, enqueuedReviewed against GitHub, not against the report. Squash-merge auto-merge enabled. Per-file divergence — clean, with a control that firesThe check that matters for this branch is per file, never per package: Merge base
The zero is a measurement, not a silence: the identical command returns 1 for a file main touched, so a collision on CIAll 25 check runs completed on head What I checked beyond green
One finding, filed rather than blocked: this PR falsifies three rows of a shipped docThe drift check flagged
Control on that reading: the page already uses
Why this is not a hold on this PR. The repo's own The crossing-history clause ( Generated by Claude Code |
os-elon
commented
Aug 19, 2026
The docs finding above is filed as #9836, carrying the three rows, the One correction to what I wrote: #9836 is Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
…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
Fixes#4716
What this is
The 2026-08-18 maintainer adjudication's narrowed scope (issue comment
5328603673, converted to an executable scope in5328674256): the fivegatingrules of the formerRUNTIME_OBJECT_WRITES_P2group cross onto theobjectwrite door —validateFunctionalCompleteness,validateManagedApiMethods,lintAutonumberFormats,validateRuleCompilability,validateRuleSchemaFormatsgainsurfaces: CLI_AND_RUNTIME+runtimeTypes: ['object'].An
active-stateobjectsave throughsaveMetaItem(Studio's field editor, REST/metaitem CRUD, an MCP/AI author) is now refused with the existing 422invalid_metadataenvelope when it carries a defect these five rules judge. All five already gatedos validate/os build/os lint; the runtime door — the only door a tenant overlay row has — ran none of them.Deliberately NOT in this PR (each fenced by the adjudication): the six advisory-tier object rules (their
surfaceReasonis rewritten toRUNTIME_OBJECT_ADVISORY_VOLUME, and both the lint-layer and protocol-layer fence tests pin them out by name);RUNTIME_NEEDS_FULL_SNAPSHOT(split to #9612); any advisory→gating severity upgrade;validateSecurityPostureconsolidation;TYPE_TO_STACK_KEYwidening; #9600.The false-positive budget — exempted, with its caveat in as many words
The maintainer exempted the false-positive budget on the 2026-08-18 measurement (0 refusals / 75 real object declarations across two authoring lineages, 0 phantoms after the instrument fix, non-vacuity proven by 6 synthetic violations refusing correctly). Route A — replaying real tenant
sys_metadataoverlay rows — was ruled structurally unavailable, not declined: objectstack cloud has not launched, so no tenant exists whose stored overlay rows could be exported. Maintainer, verbatim: 「objectstack cloud 还没有正式上线,简化开发。」The lower-bound caveat, which is part of the exemption: every measured population is authored config-file metadata — the cleanest possible input — so 0/75 is a lower bound on the refusal rate, not an estimate of it. Post-launch follow-up expectation (an expectation, not a gate): when the first real tenants exist, a replay of stored overlay rows is run as an after-the-fact audit of this exemption.
The six refusal controls from that measurement are pinned permanently in
runtime-gate.object-writes.test.ts, so the evidence the exemption rests on cannot rot silently.The lazy-deps tripwire fired — and was re-pinned deliberately
runtime-lazy-deps.test.ts's object leg (PR #9295) had never been exercised, because firing it requires exactly this widening. Fired on this branch, mechanically verified:scripts/ablation-dist-preflight.mjs @objectstack/lint 'Advisory-tier object rule');git statusclean).The re-pin is the "stop and think" the red demanded: source parsers (
typescript/sucrase) still load never; ajv still loads never at import and never on a schema-free object write (the hot path — an ordinary Studio field edit pays for no compiler); ajv is required to load, lazily, when an object write carries ajson_schemavalidation — and the gate must refuse the uncompilable one. Tier 3 is what keeps tier 2's clean reads falsifiable.Cost delta — measured, not worsened silently
Measured on the built gate (
dist/runtime.cjs, medians of 30 iterations after warmup, same machine and process shape for both legs; BEFORE = origin/main registry rebuilt and preflighted absent, AFTER = this branch, preflighted present):json_schemavalidationThe pre-existing linear cost of the shipped gate (33.4 ms @ 21 → 506 ms @ 420 per flow publish, measured at
5309093362) is its own finding, pre-existing and untouched here — the flow rows above show this PR adds nothing to it. The ajv cold load is once per process; warm is per schema-carrying publish only.The one consumer red, and its repair
The widening turned exactly one downstream test red:
packages/runtime/src/meta-field-overlay-lock.test.ts's control "objectis still refused, exactly as before" — its packaged-object fixture carried an optionlessselect(incidental), sovalidateFunctionalCompleteness's 422 now answered before the overlay lock's 403. The fixture's own #8310 comment records the established resolution for precisely this interaction (the 422 lint door speaks first; keep control bodies clean so the lock's sentence stays under test): the select now declaresoptions, and the comment carries the #4716 note. No production ordering was changed.Evidence
3b7f599(post-merge of today'sorigin/main, full packages closure rebuilt):@objectstack/lint75 files / 2099 tests green andtsc --noEmitclean;@objectstack/metadata-protocol123 files / 1702 tests green (no typecheck script — its type gate is the tsup DTS build, green in the closure);@objectstack/runtime174 files / 2598 tests green.7218142(same tree minus a merge that did not touch them beyond what CI re-verifies):@objectstack/rest129 files,@objectstack/cli134 files,@objectstack/objectql219 files / 3878 tests,@objectstack/platform-objects25 files,@objectstack/plugin-approvals24 files — all green after the one fixture repair above.runtime-gate.object-writes.test.ts10/11 red (only the direction-neutral D4 differential case stays green); the protocol block's two refusal legs red; every other protocol case is refusal-free in both states by design. Restored, rebuilt, preflighted present, all green, tree byte-identical.object); object door = exactly 7 rules in registry order; flow door untouched;TYPE_TO_STACK_KEY11 mappings, noreport/dataset/app.3b7f599withnode scripts/pm/dispatch-gates.mjs(no hand-fed paths — the script derives the 6-file change set from the merge base), all green: check:changeset-gate-self-tests, check:cross-package-test-inputs, check:durability-log-level, check:objectui-changeset, check:query-options-erasure, check:type-check-coverage, check:engine-double-contract, check:where-matcher, check:nul-bytes, check-adr-0087-registration, check-changeset-no-major, check-empty-changeset, check-affected-docs, andcheck:type-check-debt --re-measureon the BUILT closure ("33 ledger entries re-measured, 1926 raw tsc errors total, none above its recorded number; surplus: none").Generated by Claude Code