Uh oh!
There was an error while loading. Please reload this page.
fix(lint): judge a schema-bound metadata form at its own binding layer (#7815) - #8041
Conversation
#7815) The runtime publish gate calls `validateVisibilityPredicates(stack)` with no options, so every view was judged at the `'runtime'` layer default — including schema-bound metadata forms, which bind the row under edit as `data`. Correct metadata therefore drew a `visibility-root-mislayered` advisory telling its author to write `record.`, and `visibility-bare-identifier`'s hint prescribed `record.<word>`, on a surface that binds no `record` at all. The layer is now read off the metadata where the metadata states it: a form view declaring `data: { provider: 'schema', schemaId }` is judged at `metadata`, and every other site — a plain runtime view, every page component — still takes `opts.layer`. Derived from the same `schemaIdOf` call that decides the #7696 right-hand-literal-slot stand-down, so the two cannot disagree about which surface they are on. The rule itself is unchanged; this is layer plumbing at the invocation side. `visibility-root-mislayered` is `warning` in both directions, so acceptance is untouched — measured as identical error sets across the 19-row controls corpus, and pinned as a property in `runtime-gate.test.ts`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WocN37om5bw81JDoEEMA2e
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also reference the affected code. These are read-only:
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#7815
What was wrong, and where
authoring-rules.ts:859callsvalidateVisibilityPredicates(stack)with no options, andvalidate-visibility-predicates.ts:849defaultsopts.layer ?? 'runtime'. So at the runtime publish gate every view was judged at the runtime layer — including schema-bound metadata forms, which bind the row under edit asdata. No production caller ever passed the real layer (layer: 'metadata'hit only test files). All three line numbers re-verified onorigin/main@e3c8ed0before editing.Two consequences, both measured below:
data.-rooted form drewwarning visibility-root-mislayeredtelling its author to writerecord.— an advisory that is wrong on correct metadata, at the publish door, for a whole surface class;visibility-bare-identifier's hint — the finding that blocks — prescribedrecord.< word >on a surface that binds norecordat all. This is the issue's point 2, and it was held in place by a test assertion (see "The one pin that had to change").Nothing went red, which is what let it survive.
The fix
opts.layeris now the layer for sites whose binding environment the metadata does not state. A form view declaringdata: { provider: 'schema', schemaId }states it — that data source is what makes the console's metadata-admin evaluator the thing that renders the surface, and that evaluator bindsdata. Such a site is judged atmetadata; every other site (a plain runtime view, every page component) still takesopts.layer, default'runtime'.Derived per site, from the same
schemaIdOfcall that already decidesliteralRhs(#7696's right-hand-literal-slot stand-down). One call, two consumers: the layer verdict and the stand-down can never disagree about which surface they are on, which is the reasonschemaIdOfwas shared in the first place. Per-site rather than per-stack because oneviews[]entry can carry both kinds —formViews.< key >sub-containers each declare their owndata, and there is a test for exactly that.⛔ The rule is untouched.
visibility-root-mislayeredwas always correct for the layer it was told; this is plumbing at the invocation side, as the card scopes it.Controls table — reproduced, not trusted
PR #7810's 14-row table re-measured at the runtime publish gate (
runRuntimeAuthoringRules({ type: 'view', … })) onorigin/main@e3c8ed0vs this branch, plus 5 rows this card needs.FORM= schema-bound metadata form,RTV= runtime view. Rows 1-14 reproduce #7810's "after" column exactly, including every¹mis-layer advisory.FORM data.type == activewarningmislayered +warningrhs-path-shapedFORM data.type == labelwarningmislayered +warningrhs-path-shapedFORM status == activeerrorbare-identifier → "writerecord.status" +warningrhs-path-shapeddata.statusFORM data.type == data.labelerrorrhs-path-shaped +warningmislayeredFORM data.type == 'active'warningmislayeredFORM data.type == data.activeerrorpath-unresolved +errorrhs-path-shaped +warningmislayeredFORM data.tpye == activeerrorpath-unresolved +warningmislayered +warningrhs-path-shapedRTV record.status == activeerrorbare-identifier →record.activeRTV record.status == 'active'FORM active == data.typeerrorbare-identifier +errorrhs-path-shaped +warningmislayereddata.activeFORM data.name == active && activeerrorbare-identifier +warningmislayered +warningrhs-path-shapeddata.activeFORM data.tags.all(t, t == active)errorbare-identifier +errorpath-unresolved +warningmislayereddata.activeFORMunresolvableschemaId,data.x == activewarningmislayered +warningrhs-path-shapedFORMunresolvableschemaId,data.x == data.yerrorrhs-path-shaped +warningmislayeredFORM data.type == 'grid'— correct metadatawarningmislayered → "writerecord."FORM record.type == 'grid'— never matcheswarningmislayered → "writedata." ← D3's other directionRTV data.status == 'open'warningmislayeredRTV record.status == 'open'FORM status == 'x'errorbare-identifier →record.statusdata.statusEvery
FORMrow loses the wrong advisory; every non-form row (8, 9, 17, 18) is byte-identical; row 16 is the direction this door was blind to.Acceptance is untouched, measured rather than asserted
The card's bound is that nothing accepted becomes rejected or vice versa.
visibility-root-mislayerediswarningin both directions, and no other rule's severity or firing condition depends on the layer — only the root quoted inside two hints does.Mechanically compared, error-only, across all 19 rows above:
Pinned permanently as a property in two places (
runtime-gate.test.tsand the rule's own suite) with exact expected error sets, so a later edit to the layer plumbing that promoted or demoted anything goes red in CI rather than at a tenant's publish door.Reverse verification — and its honest direction
The fix was taken out with
git checkout origin/main -- < the one source file >(nevergit stash), tests kept. 8 red, and 4 deliberately green:a correctly data.-rooted form is SILENTa data.-rooted predicate on a schema-bound form is CORRECTa record.-rooted form now draws the advisory the OTHER way(×2)prescribes the root the surface actually binds when it REFUSESderives per SITE, not per stackformViewsan unresolvable schemaId is still a schema-bound SURFACEproves the scanner still sees(the pre-existing pin)the runtime view one fixture-line away still draws itmoves NO finding across the error/advisory boundary(×2)opts.layer still governs every site that declares no data sourcea non-schema provider is NOT a metadata formprovider: 'object'keeps the runtime directionThe four green-on-revert cases are not weak tests — a test whose job is to pin an invariant or a preserved contract is supposed to survive the revert, and reporting them as reds would have meant writing them wrong.
The one pin that had to change
validate-visibility-predicates.test.tsasserted, on a schema-bound fixture:That is the wrong-layer prescription the issue's point 2 names, held in place by a green assertion. It now reads
`data.status`; the refusal is unchanged — same id, sameerror, same single finding — and only the root moved. It was the only red in the package's 1924-test suite when the fix landed, which is itself the measurement: nothing else in the repo depended on the runtime layer being assumed for these forms.Gates
pnpm --filter '@objectstack/lint^...' builddisttrap)pnpm --filter @objectstack/lint run testpnpm --filter @objectstack/lint run typechecknpx eslint < 3 changed files > --no-inline-configcheck:nul-bytesnode scripts/check-nul-bytes.mjscheck-empty-changeset,check-changeset-no-major,check-adr-0087-registration@objectstack/metadata-protocol(runtime-authoring-gate),@objectstack/objectqlos build/validate/compilepath)pnpm --filter @objectstack/cli run testprovider: 'schema'occurrences inexamples/os build)Note on the CLI suite: a first pass showed 2 failures in
src/utils/schema-migrate.pending-render.test.ts. Both are 5 s test timeouts under this shared container's load, and both reproduce on cleanorigin/mainwith this change reverted; they pass with--testTimeout=60000, and the full suite is green at--testTimeout=30000. Unrelated to this change and not a defect — recorded so the next reader does not re-chase it.CI has not been waited on — that is the PM's, per the dispatch contract.
Generated by Claude Code