feat(objectql): the silent skips stop being silent — registration-time completeness diagnostics (ADR-0078 Phase 4) - #4599
Merged
Conversation
…e completeness diagnostics (ADR-0078 Phase 4)
Phase 4 had two halves; they got opposite verdicts, decided on evidence already
in hand rather than deferred for evidence that will never arrive.
## Rejected — the generative rule sweep (not deferred: rejected)
A generator can enumerate candidates ("which optional keys might be
load-bearing?") but cannot verify runtime skip sites, and a rule without its
skip-site citation is a false prescription. This campaign shipped four of those;
every one was caught by the verification pass a generator would structurally
skip. No amount of waiting fixes that — the route is wrong, not early.
## Built — registration-time diagnostics at the choke point every door shares
The author-time gate (validate-functional-completeness) only protects metadata
that passes through `os build` / `validate` / `lint`. Two shipped instances
prove the other doors are real: #3896 (Setup authoring inserted
`sys_sharing_rule` rows directly, bypassing the schema that "required"
`criteria`) and cloud's `rowColor.mapping` (an `as never` cast bypassed tsc,
then the strip-era parse dropped the key).
`SchemaRegistry.registerObject` is where EVERY door converges — declared
stacks, plugin objects, `extend` contributions, `saveMetaItem`, raw
registerObject calls. It now runs the SAME shared predicate
(`checkFieldCompleteness` from `@objectstack/spec/kernel`) and emits one
aggregated warning per incomplete object, carrying the SAME rule ids the lint
reports — a boot log greps straight into the same docs and suppression story.
WARN, never throw — deliberately: ADR-0078 §1 maps error severity to "the
INSTANCE is dead", not "the system is dead". An inert field must not kill a
boot that thousands of healthy objects share. Errors block at author time; the
registry's job is that the silence never survives to runtime unobserved.
Shape follows `warnStrippedLegacyApiMethods` (#3543) exactly: module-level
once-per-object dedup, injectable `warn`, pure observation, hot path untouched.
## The webhook skip now names itself
`auto-enqueuer.ts`'s `if (triggers.size === 0) return null` sat under a comment
blessing the empty case as "a manual-only webhook" — a mode #3196 removed (no
manual fire path exists). The skip now warns with the author-time rule id
(`webhook/without-triggers`) and the comment tells the truth. Verified, not
assumed: only ACTIVE rows reach parseRow (`where: { active: true }`), so a
deliberately disabled webhook stays warning-free — zero false positives on the
repo's one real webhook (shipped inactive, full trigger list).
## Scope honesty
Field rules + the webhook rule get the runtime twin. `view/layout-without-
binding` stays author-time-only: views do not register through this choke point
and the renderer half of the evidence lives in objectui.
## Verification
- registry 85/85 (6 new, incl. an integration test through a raw
registerObject call — the #3896 class of door)
- auto-enqueuer 15/15 (1 new)
- Full suite 132/132. The dogfood boots double as the authoritative sweep:
every shipped stack (platform objects, showcase, CRM, Todo) registers with
ZERO functionally-incomplete warnings — no false positives, and no inert
fields hiding behind doors the author-time gate could not see.
Closes the ADR-0078 loop end to end: author-time error, runtime warning, one
shared predicate deciding both. Tracked in #4544.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
os-zhuang
marked this pull request as ready for review
August 2, 2026 09:38
os-zhuang
enabled auto-merge
August 2, 2026 09:38
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 14 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 4 of #4544 — decided rather than deferred. The phase had two halves; they got opposite verdicts, both decided on evidence already in hand rather than parked for evidence that will never arrive.
Rejected — the generative rule sweep (not deferred: rejected)
A generator can enumerate candidates ("which optional keys might be load-bearing?") but cannot verify runtime skip sites, and a rule without its skip-site citation is a false prescription. This campaign shipped four of those; every one was caught by the verification pass a generator would structurally skip. No amount of waiting fixes that — the route is wrong, not early. Recorded in the changeset so nobody re-proposes it as "finally have enough data".
Built — registration-time diagnostics at the choke point every door shares
The author-time gate (
validate-functional-completeness, Phase 1) only protects metadata that passes throughos build/validate/lint. Two shipped instances prove the other doors are real, not hypothetical:POST /data/sharing/rules绕过SharingRuleSchema:criteria缺失或拼错静默变成"共享该对象全部记录",与 ADR-0049 "never seeded as a permissive match-all" 直接冲突 #3896 — Setup authoring insertedsys_sharing_rulerows directly, bypassing the schema that "required"criteriarowColor.mapping— anas nevercast bypassed tsc, then the strip-era parse dropped the keySchemaRegistry.registerObjectis where every door converges — declared stacks, plugin objects,extendcontributions,saveMetaItem, rawregisterObjectcalls. It now runs the same shared predicate (checkFieldCompletenessfrom@objectstack/spec/kernel) and emits one aggregated warning per incomplete object, carrying the same rule ids the lint reports — a boot log greps straight into the same docs and suppression story.WARN, never throw — deliberately. ADR-0078 §1 maps error severity to "the instance is dead", not "the system is dead". An inert field must not kill a boot that thousands of healthy objects share. Errors block at author time; the registry's job is that the silence never survives to runtime unobserved.
Shape follows
warnStrippedLegacyApiMethods(#3543) exactly: module-level once-per-object dedup, injectablewarn, pure observation, hot path untouched (one line per object, all findings aggregated).The webhook skip now names itself
auto-enqueuer.ts'sif (triggers.size === 0) return nullsat under a comment blessing the empty case as "a manual-only webhook" — a mode #3196 removed (no manual fire path exists; the Phase 3 finding). The skip now warns with the author-time rule id (webhook/without-triggers) and the comment tells the truth.Verified, not assumed: only active rows reach
parseRow(where: { active: true }), so a deliberately disabled webhook stays warning-free — zero false positives on the repo's one real webhook (shipped inactive, full trigger list).Scope honesty
Field rules + the webhook rule get the runtime twin.
view/layout-without-bindingstays author-time-only: views don't register through this choke point, and the renderer half of the evidence lives in objectui.Verification
registerObjectcall — thePOST /data/sharing/rules绕过SharingRuleSchema:criteria缺失或拼错静默变成"共享该对象全部记录",与 ADR-0049 "never seeded as a permissive match-all" 直接冲突 #3896 class of door)This closes the ADR-0078 loop end to end: author-time error, runtime warning, one shared predicate deciding both.
Generated by Claude Code