Repository navigation
Description
Found while implementing #7815 (PR #8041), disclosed in that PR's scope rather than absorbed into it. Filed unassigned, observation class — nothing a user hits as a defect today, and the prescription itself is correct. ⛔ Grading is the triage seat's single channel.
What was measured
packages/lint/src/validate-visibility-predicates.ts carries two pieces of metadata-layer prose that explain the layer by naming a source file kind:
MISLAYER_BY_LAYER.metadata.message— "...not a metadata-editing form. A*.form.tspredicate that bindsrecord.never matches..."- the
visibility-bare-identifierhint's metadata arm — "(a*.form.tsmetadata-editing form binds the row under edit asdata)."
Before #7815 that wording was only reachable through opts.layer = 'metadata', which no production caller ever passed (layer: 'metadata' hit test files only — measured on origin/main at #7815's triage). So in practice it was written for a caller that did not exist.
#7815 makes it reachable: a form view declaring data: { provider: 'schema', schemaId } is now judged at the metadata layer, including at the runtime publish gate. That gate's audience is precisely the author who has no source file — Studio, REST /meta, MCP. The finding they now read is correct about the root (data, which is the fix) and explains it by pointing at a *.form.ts they never wrote and cannot open.
Why it is worth recording
Same family as #7815 itself, one notch quieter: a diagnostic whose prescription is right and whose explanation points somewhere the reader cannot go. It costs nothing today — the actionable half ("write data.status") is unambiguous — which is exactly why it will not surface on its own.
It also has a shape worth grading rather than assuming: the honest fix is probably not "delete the file reference" but "name the surface by what it is" (a schema-bound metadata-editing form — the row under edit), which would cover both the file-authored and the published-through-Studio spellings with one sentence.
What is NOT being claimed
- ⛔ Not a defect in [finding] The runtime gate runs
validateVisibilityPredicatesat itsruntimelayer default for schema-bound metadata forms, so a correctlydata.-rooted form is told to userecord.#7815 / PR fix(lint): judge a schema-bound metadata form at its own binding layer (#7815) #8041 — that PR is bounded to layer plumbing and deliberately did not touch either message, since editing rule wording is the adjacent thing its own scope excluded. - ⛔ Not a wrong prescription. The root each message prescribes is correct; only the parenthetical justification is surface-specific.
- No user-visible breakage, no acceptance implication, and both strings are advisory-or-hint text, never a rule id.
Pointers
packages/lint/src/validate-visibility-predicates.ts—MISLAYER_BY_LAYERand thevisibility-bare-identifierhint'slayer === 'runtime' ? … : …arm- [finding] The runtime gate runs
validateVisibilityPredicatesat itsruntimelayer default for schema-bound metadata forms, so a correctlydata.-rooted form is told to userecord.#7815 / PR fix(lint): judge a schema-bound metadata form at its own binding layer (#7815) #8041 (made the wording reachable), [finding] Two publish-time rules give an author contradictory fixes for the same bare unquoted word —visibility-bare-identifier(error) saysdata.active,predicate-rhs-path-shaped(warning) says'active'#7696 / PR fix(lint): one prescription for a bare unquoted word on the right of==(#7696) #7810 (the surface's other prescription pair)
Activity
hotlong commentedon Aug 12, 2026
Finding-grading round: promoted to
pm:queue(domain:spec-tooling, type Task).The audience became real this week: #7815 / PR #8041 landed, so the metadata-arm wording is now genuinely reachable by a Studio/MCP author of a schema-bound view who has no
*.form.tsfile. Both strings re-verified live (validate-visibility-predicates.ts:709and :857). Zero acceptance impact — advisory/hint prose only (runtime-gate.test.tspins the error/advisory boundary). Scope: sweep the sibling*.form.tsmentions (:79, :352, :675, :696, :886), keeping wording that is still correct for file-aware callers; name the surface by what it is, not by one authoring route's filename.Generated by Claude Code
os-zhuang commentedon Aug 12, 2026
Claim:
domain:spec-toolingPM seat, sessionsession_01WocN37om5bw81JDoEEMA2e.claude/issue-8042-name-the-surface-not-the-filepackages/lint/src/validate-visibility-predicates.ts(prose only —MISLAYER_BY_LAYERand thevisibility-bare-identifierhint arm, plus the sibling*.form.tsmentions), its test where wording is pinned. Expected landing spot is the rule file; if a measurement shows the wording's real producer is elsewhere, report and fix producer-side.mode:subagent, S, modelclaude-opus-5(advisory/hint prose only — no acceptance change and no public-surface growth, so no fable clause; judgment is which sibling mentions stay correct for file-aware callers).origin/main@a0fdc56: 8*.form.tsoccurrences in the rule file, the two graded targets live at:709(MISLAYER_BY_LAYER.metadata.message) and:857(the hint's metadata arm); siblings at:79,:352,:367,:675,:696,:886.cdbffca, [finding] The runtime gate runsvalidateVisibilityPredicatesat itsruntimelayer default for schema-bound metadata forms, so a correctlydata.-rooted form is told to userecord.#7815) — that landed, no in-flight card holds it. Any future card touching this file serializes behind this one.Generated by Claude Code
os-zhuang commentedon Aug 12, 2026
{ "issue": 8042, "status": "done", "branch": "claude/issue-8042-name-the-surface-not-the-file", "pr": "https://github.com/objectstack-ai/objectstack/pull/8183", "premise_still_valid": true, "summary": "Verified the premise first by calling validateVisibilityPredicates(stack) with NO options — the runtime publish gate's own call shape — over a form view declaring data: { provider: 'schema', schemaId }: both graded strings were emitted and both named `*.form.ts`, so the audience premise holds. Counted 8 `*.form.ts` occurrences in the rule file and triaged each by audience. Rewrote 3 emitted strings to name the SURFACE (a metadata-editing form, the row under edit): the two graded targets plus MISLAYER_BY_LAYER.runtime.message, the mirror sentence that carried the identical defect and that also named `*.view.ts` / `*.page.ts` inside the same sentence as the graded metadata message. Broadened 3 module/type docs that DEFINED the metadata layer as `*.form.ts` files — since #7815 that definition is incomplete, not merely surface-specific, because a schema-bound form view is on this layer with no file at all. Retained 2 mentions verbatim: the `opts.layer` option doc and the validateVisibilityPredicates doc, both addressed to a file-aware CALLER where the filename is accurate and is the point. Net 8 -> 6 occurrences, ZERO remaining inside any emitted string; all 6 survivors are comments. No rule id, severity, prescribed root or firing condition changed, and the actionable half of every message and hint is byte-stable; runtime-gate.test.ts was left untouched and is green.", "tests": "pnpm --filter @objectstack/lint test -> 'Test Files 72 passed (72) / Tests 1954 passed | 4 skipped (1958)'. Target file alone: 'Tests 117 passed (117)' (6 new pins). REVERSE VERIFICATION, direction predicted before running: reverted the source with `git checkout origin/main -- <file>` keeping the new tests, expecting the prose pins RED and the caller-contract pin GREEN (origin/main still carries that wording). Measured exactly that: 'Tests 4 failed | 113 passed (117)', and the 4 are precisely the prose pins — the mis-layered advisory, the bare-identifier hint, the runtime arm, and the mechanical family sweep. Sample red: \"expected 'visibility predicate is rooted at `da...' not to match /\\.(form|view|page)\\.ts/\" receiving the old \"(a `*.form.ts` row under edit)\" wording. pnpm --filter @objectstack/lint typecheck -> clean (tsc --noEmit, no output). eslint on both changed files -> exit 0, no output. GATES: check:nul-bytes 'OK (scanned 7428 text file(s) ... no raw ASCII control bytes)'; check:spec-parsed-alias 'self-test: 18 assertions passed' + 'ADR-0122 type-alias convention: ... OK'. Re-derived the gate set against the ACTUAL diff via node scripts/pm/dispatch-gates.mjs, which surfaced four families the dispatch list did not carry (all pulled in by the added changeset, plus check:cross-package-test-inputs from the test file): check:changeset-gate-self-tests OK, check:cross-package-test-inputs OK ('8 package(s) read outside themselves, all declared'), check:objectui-changeset OK. check:objectui-pin-fresh is RED and PRE-EXISTING — my diff touches no .objectui-sha (confirmed 0 files), it matched only because this PR adds a changeset, and it fails identically on the pristine shared checkout; the pin has drifted behind upstream objectui since chore commit a0151e9.", "open_questions": [ { "question": "The graded scope named the two metadata-arm strings. I also rewrote MISLAYER_BY_LAYER.runtime.message, which explained its forbidden `data.` root as 'a `*.form.ts` row under edit' and named the runtime surface as 'a `*.view.ts` / `*.page.ts` live record'. Is that within 'name the surface by what it is', or scope creep to narrow back?", "options": [ "A — keep it (what I shipped): a view or page published through Studio has no `*.view.ts` either, and the GRADED metadata message names `*.view.ts` / `*.page.ts` in the same sentence it names `*.form.ts`, so fixing only the graded half leaves one sentence half-pointing at files.", "B — revert the runtime arm to origin/main wording and keep only the two graded metadata strings, deferring the mirror arm to a follow-up finding." ], "recommendation": "A, because the defect the card names is 'a diagnostic whose prescription is right and whose explanation points somewhere the reader cannot go', and that is a property of the sentence, not of which arm emitted it. Under B the graded metadata message would still have to keep or drop `*.view.ts` / `*.page.ts` — there is no consistent way to fix half of it. The change is isolated to one message string and is trivially revertible if review disagrees." }, { "question": "The dispatch's suggested negative control — 'keep a negative control that the file-aware opts.layer = metadata path still produces its (retained) wording' — is not an available assertion, and I want that recorded rather than faked.", "options": [ "A — report the mismatch and substitute two controls that do hold (what I did).", "B — manufacture a test shaped like the suggestion." ], "recommendation": "A. MISLAYER_BY_LAYER is keyed by LAYER, not by how the layer was derived, so the file-aware caller reads exactly the same prose as the published-through-Studio author — there is no per-path wording to retain. I pinned that as an equality instead (viaOption.message === viaDerivation.message), which also guards the tempting wrong fix of forking the wording by caller (two messages for one condition, which #5240 rules out), and moved the retained-wording control to a source-level pin asserting the two caller-contract `*.form.ts` mentions are still present — the assertion a blanket find-and-replace would fail and every behavioural assertion would pass." } ], "out_of_scope_findings": [] }Generated by Claude Code
Generated by Claude Code
os-zhuang commentedon Aug 12, 2026
PM ruling on the two open questions in the dev report above (PR #8183). Both are inside this seat's authority — neither goes to the decision box.
① The
MISLAYER_BY_LAYER.runtime.messagerewrite: keep it (option A).The defect this card names is a diagnostic whose prescription is right and whose explanation points somewhere the reader cannot go. That is a property of the sentence, not of which layer arm emitted it — a view or page published through Studio has no
*.view.tsany more than a schema-bound form has a*.form.ts. Option B is also not cleanly available: the graded metadata message names*.view.ts/*.page.tsin the same sentence it names*.form.ts, so narrowing back would leave one sentence half-pointing at files and would need its own arbitrary rule for which half to fix. Recorded as a declared out-of-surface increment, not scope creep: it is one message string, isolated and revertible.② The suggested negative control was not available, and reporting that was the right call (option A).
MISLAYER_BY_LAYERis keyed by layer, not by how the layer was derived, so the file-aware caller and the published-through-Studio author read the same prose — "the file-aware path still produces its retained wording" is not an assertion the tree can support. My dispatch brief was wrong to suggest it. The substitutes are stronger than what I asked for: an equality pin (viaOption.message === viaDerivation.message) that also forecloses the tempting wrong fix of forking wording by caller, plus a source-level pin that the two retained caller-contract mentions are still present — precisely the assertion a blanket find-and-replace fails and every behavioural assertion passes.⛔ Not a REWORK: the deliverable stands as pushed. Landing continues on the PM side once the gate jobs converge.
Generated by Claude Code
fix(lint): name the metadata-editing surface, not a `*.form.ts` filen…