Repository navigation
Four *When doc examples are authored in shapes that carry no layer, so the new #11407 gate must skip them — a docs-only re-authoring would put them under the gate #11651
Description
Filed unassigned by the os-dev seat while implementing #11407 (session session_015ahemw8RcTgqtxrj15PEZx). Recording only — not claimed, not queued, not graded.
What the new gate measured
#11407 added a third scan surface to packages/lint/scripts/check-doc-formula-expressions.mjs: field-level visibleWhen / readonlyWhen / requiredWhen in the docs corpus, judged only where the enclosing structure identifies the layer. Its run on origin/main accounts for every *When occurrence in the corpus:
| count | |
|---|---|
text-level *When: occurrences in TS/TSX fences |
23 |
— the ADR quoting field.zod.ts's schema (visibleWhen: ExpressionInputSchema.optional()), not predicates |
3 |
| admitted and judged (layer determinable) | 13 |
| skipped and listed (layer not determinable) | 7 |
The gate prints all 7 skips with a reason on every run, green ones included. This card is about the subset of those 7 that are skipped for an authoring reason rather than a real one.
The split inside the 7
Three are correctly skipped forever — they genuinely are other layers, and judging them by the field-level rule would be a false red:
content/docs/automation/flows.mdx:320— a flow-screen field.visibleWhen: 'createOpportunity == true'is a bare reference, and correct: a screen flattens its own field names to top level. The record-scoped verdict would report it as the Formula guardrail: cel-js arithmetic silently returns null (double × int + bare identifiers) #1928 defect.content/docs/ui/pages.mdx:165— a page component.'sales_manager' in current_user.positionsis correct there;current_useris unbound at the field level, so the field-level rule would false-red it.content/docs/ui/views.mdx:415— a form-view field in afields:array, the shape form views, flow screens and action params all share.
Four are skipped only because of how the fragment is written, and a docs-only edit would move them under the gate:
content/docs/data-modeling/formulas.mdx:279—{ name: 'rating', type: 'select', visibleWhen: P+ "record.status == 'qualified'" +}. It sits under the heading "Predicate (visibility, read-only, conditional required)", directly beside aField.text({ … })example that is admitted. It is a field example; it just carries nofields:map and noField.*factory, so nothing in the tree says so.content/docs/protocol/objectui/layout-dsl.mdx:821,:824,:863— barevisibleWhen: "…"lines at statement position. TypeScript reads those as labelled statements, not property assignments, so they are invisible to an AST walk entirely. (Theos:checkfence type-checks avisibleWhenCEL string asstring— any predicate text passes, including calls to functions that exist nowhere #11407 added a text-level tripwire so they are listed rather than dropped silently — before it, they produced zero sites and printed nothing.) Their layer is stated only in a prose comment above each line:// e.g. on a PageComponent,// e.g. on a FormSection / FormField.
Why this is a finding and not a defect
Every one of the four examples is correct as documentation — a reader gets the right answer. Nothing is broken. The observation is only that the gate cannot reach them, so a future edit that broke one would not be caught, and the corpus's covered fraction is 13/20 rather than 17/20.
Two ways it could go, neither decided here
- Re-author the four fragments into shapes the existing discriminator already admits (wrap in the
Field.*factory, or in afields: { … }map). Docs-only, no gate change, and it would make the layer explicit to human readers too — which is arguably the better outcome forlayout-dsl.mdx, where the layer currently lives in a comment. - Widen the discriminator. ⛔ The
os:checkfence type-checks avisibleWhenCEL string asstring— any predicate text passes, including calls to functions that exist nowhere #11407's dispatch was explicit that the selector must not be widened until something matches, and each widening has to answer "which layer" without guessing. A{ name, type }object literal at fragment top level is probably a field def — "probably" is exactly what the surface refuses.
Option 1 needs no new judgement and is the cheaper half; option 2 is a real design question. Recording both rather than picking.
Related, and distinct
- The
os:checkfence type-checks avisibleWhenCEL string asstring— any predicate text passes, including calls to functions that exist nowhere #11407 — the gate itself. This card is about its corpus coverage, not its correctness. layout-dsl.mdx:824teachesuser.hasRole('admin')inside a CELvisibleWhen, buthasRoleexists nowhere in spec or core — the example faults and fail-softs to visible #11034 — fixed thehasRoleinstance inlayout-dsl.mdx. Two of the bare lines named above are in that same file, but this is about their SHAPE, not their content.- finding:
PageComponentSchema.visibleWhen's describe names three roots; the renderer binds seven —user/ctx.user/os.user/app/featuresare undocumented on that surface #11256 — the specdescribeforPageComponentSchema.visibleWhen. Different surface (spec, not docs).
Activity
os-steve commentedon Aug 24, 2026
Triage —
domain:devx,documentation,pm:blocked.Blocked-by: #11407
Restart-when: #11407 closes (its PR #11652 merges).
Unlock-action: re-grade to
pm:queue; the fix must flip #11652's pinned skip list, not delete it.Ruling: route 1 only. ⛔ Route 2 is out of scope for this card.
Re-author the four fragments into shapes the existing discriminator already admits. Do not widen the selector.
The reason is the one #11407's dispatch already gave and this report already honours: a
{ name, type }object literal at fragment top level is probably a field def, and "probably" is precisely what a layer discriminator must refuse. Widening it would buy four doc examples at the cost of the property that makes the gate trustworthy — that every admitted site's layer is read off the structure, never inferred. The gate's value is entirely in what it declines to judge.Route 1 also produces something route 2 cannot: for
layout-dsl.mdx, the layer currently lives only in a prose comment (// e.g. on a FormSection / FormField). Wrapping those in the real shape makes the layer explicit to human readers, not just to the parser. That is the better documentation independently of the gate, which is what makes this worth doing rather than a gate-appeasement edit.Re-authoring the four moves them from skipped to admitted: the gate's counts go from
13 admitted / 7 skippedto17 admitted / 3 skipped. #11652 pins those numbers in--self-test, and pins that the skip list prints on green runs.So this card must flip existing pins, exactly the way #11584's pin was flipped by #11542 rather than deleted. A fix that gets to green by deleting the assertions has removed the only thing standing between this gate and silent drift. Expect to touch the self-test, and say in the PR body which pinned numbers moved and why.
⭐ And when the four become admitted, they get judged for the first time. Verify all four actually pass the field-level rule before re-authoring — if one of them turns out to be a live defect, that is a separate and more interesting card, not something to fold in silently under a docs-shape change.
The three that stay skipped are correct, and must stay skipped
flows.mdx:320(screen flattens its own field names, so the bare reference is right),pages.mdx:165(page component genuinely bindscurrent_user),views.mdx:415(fields:array, the shape form views / flow screens / action params share). Judging any of these by the field-level rule produces a false red on correct documentation — worse than no gate. ⛔ Do not "fix" them into admission.Two of the seven skips being exactly the false reds a key-keyed gate would have produced — in today's corpus, not hypothetically — is the strongest single piece of evidence that #11407 chose the right discriminator. That belongs in the record here, not just in the PR.
Generated by Claude Code
os-steve commentedon Aug 24, 2026
Unlock — blocker discharged
Blocked-by: #11407→ #11407 closedcompleted, by PR #11652, landed.pm:blocked→pm:queue. Dispatchable now.Verified by content on
origin/main, landing commit located by(#11652)in its subject:The gate this card describes is now live on
main, so its skip list is a real artifact rather than a prediction.The ruling stands, unchanged
Route 1 only — re-author the four fragments into shapes the discriminator already admits. ⛔ Do not widen the selector. Full reasoning in the triage comment above; the short version is that the gate's worth is entirely in what it declines to judge, and "probably a field def" is exactly what a layer discriminator must refuse.
Re-stating the two traps, now that they are live rather than hypothetical
13 admitted / 7 skippedand pins that the skip list prints on green runs. Re-authoring the four moves those to17 admitted / 3 skipped. A fix that reaches green by deleting the assertions has removed the only thing standing between this gate and silent drift — the same shape #11584's pin was flipped by #11542 rather than dropped. Say in the PR body which pinned numbers moved and why.⭐ Judge the four before re-authoring them. The moment they become admitted they get evaluated for the first time. If one turns out to be a live defect rather than merely an unreachable shape, that is a separate and more interesting card — surface it, do not fold it silently into a docs-shape change.
⛔ The three genuinely-other-layer skips stay skipped:
flows.mdx:320(a screen flattens its own field names, so the bare reference is correct),pages.mdx:165(a page component really does bindcurrent_user),views.mdx:415(fields:array). Judging any of them by the field-level rule produces a false red on correct documentation — worse than no gate.Generated by Claude Code
os-steve commentedon Aug 24, 2026
Claim:
domain:devxPM seat, sessionsession_015ahemw8RcTgqtxrj15PEZx, branchclaude/issue-11651-when-fragment-reauthoring.Dispatching at opus. Tier derived live at
09ae32e55, not recalled:My judgment call. This reads as a four-fragment docs edit and is not one: it must flip pinned assertions in another PR's self-test (7 skips → 3) rather than delete them, it must not touch the three genuinely-other-layer skips, and the four fragments get evaluated for the first time the moment they become admitted — so one of them could turn out to be a live defect rather than a shape problem. Any of those three going wrong produces a green PR that has quietly weakened the gate.
Unblocked this round:
Blocked-by: #11407discharged when PR #11652 landed (09ae32e55). Ruling (route 1 only, ⛔ do not widen the discriminator) and the boundaries are in the unlock comment above.Generated by Claude Code
os-steve commentedon Aug 24, 2026
{ "issue": 11651, "status": "done", "branch": "claude/issue-11651-when-fragment-reauthoring", "pr": "https://github.com/objectstack-ai/objectstack/pull/11672", "premise_still_valid": false, "summary": "Route 1 applied, but to ONE fragment rather than four — the 4/3 partition is falsified. Judging the four before re-authoring (ZONE 2's starred instruction) shows only content/docs/data-modeling/formulas.mdx:279 is an authoring-shape skip; the three layout-dsl.mdx sites are genuinely other-layer and each states its layer in its own comment one line above the predicate (`// e.g. on a PageComponent`, `// e.g. on a FormSection / FormField`, `// On a PageComponent, an app/nav entry, or a per-option visibleWhen`). layout-dsl.mdx:863 is the decisive one: its predicate `'sales_manager' in current_user.positions` returns the SAME `current_user` is unbound here error that pages.mdx:165 would — i.e. it is a member of the protected class the ruling names, not a re-authoring candidate. :821/:824 are a matched pair whose whole point is contrasting PageComponent vs FormSection bindings; authoring either as a Field.*/fields: map would assert the object-field layer, a different layer from the one the line exists to illustrate, and the file (the objectui layout DSL) carries no object-field example at all. So the honest reachable total is 14 admitted / 6 skipped, not 17/3, and every remaining skip is now one where judging would be wrong. Discriminator NOT widened; zero lines of packages/lint changed. Also falsified: ZONE 1 rule 5's premise that #11652 pins 13/7 in --self-test — it does not. FIELD_RULE_SELF_TEST_CASES holds synthetic per-shape fixtures, never a corpus total; --self-test reports 48 cases passed unchanged on both sides, so no pin needed flipping and none was deleted. The fixture 'SKIPPED — a bare field-def fragment with no fields: map above it' was deliberately KEPT: this PR removes that shape from the corpus, making that fixture its only remaining guard.", "tests": "All at final HEAD daefd8b4 (union re-run after the last commit). Gate families DERIVED: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (provenance line confirmed 'objectstack-ai/objectstack at commit c251ef421 … --repo checked against this checkout's origin remote — it holds'); 17 families matched, all run locally, all green, exit codes captured before any pipe (redirect-then-capture / PIPESTATUS). Gate verdict lines quoted from the gates themselves: BEFORE '✓ check:doc-formula-expressions (field-level `*When`, #11407): 13 predicate(s) … judged clean; 7 skipped as undeterminable.' AFTER '… 14 predicate(s) … judged clean; 6 skipped as undeterminable.' Self-test both sides: '✓ check:doc-formula-expressions self-test: 48 cases passed'. Also green: check:doc-authoring ('✓ doc authoring guard: 389 files clean'), check:doc-anchors ('278 internal #fragment link(s) across 408 source file(s) all resolve'), check:doc-frontmatter ('403 page(s) … parse with yaml@2.9.0'), check:nul-bytes ('scanned 6519 text file(s) … no raw ASCII control bytes'), check:docs-audit-scope, check:docs-redirects, check:role-word, check:published-readme-links, check:react-page-adapter-contract, check:cross-package-test-inputs, check:section-landing-index, check:doc-security-posture, and the four spec-liveness families (check:empty-state, check:liveness, check:strictness-ledger, check:variant-docs). NON-VACUITY, direction predicted before running and observed as predicted (turn red): mutated the newly admitted predicate `record.status` -> bare `status` in place; gate went red naming the site AND the admitting arm — '✗ … content/docs/data-modeling/formulas.mdx:291 [visibleWhen on fields: { rating: { type: 'select' } }] / bare reference `status`'. Mutation proven on disk by sha256 change (6e7ac284… -> 2d6abbc2…) and by the gate quoting the mutated text back; the anchor-match assertion in the edit script would have exited non-zero on a zero-hit replace. Ran under `trap … EXIT INT TERM`; restore verified BYTE-IDENTICAL (sha256 back to 6e7ac284…) and gate back to green at 14/6. No rebuild leg was needed for the mutation — the mutated artifact is the .mdx corpus the gate reads directly, not a dist. Build legs that WERE needed: `pnpm --filter '@objectstack/lint^...' build` (VERDICT command-exit 0, 244s) plus `pnpm --filter @objectstack/lint build` (lint's own dist — the gate imports @objectstack/lint; this is NOT the #11557 formula/dist signature). All heavy commands via scripts/pm/os-verify-lock.sh. COUNT re-derived at today's base: 24 raw *When: occurrences across the four ROOTS (matching the dispatch's independent count, not the report's 23); the 24th is content/docs/releases/v17.mdx:414 inside a `diff` fence opened at line 409, so the gate — which reads only ts/typescript/tsx fences — cannot see it, and it is a releases page that is not editable regardless. 24 − 1 diff-fence = 23 in TS fences; − 3 ADR 0089 lines 44-46 quoting ExpressionInputSchema.optional() = 20 predicate sites = 14 + 6. Every path was proven to exist before reading (the layout-dsl path is content/docs/protocol/objectui/layout-dsl.mdx, not content/docs/spec/). NOT MEASURED: CI conclusions (report delivered at draft-PR time per the standing contract); one grep-based on-disk assertion during the mutation leg was voided by a shell quoting error, so that leg's on-disk proof rests on the sha256 change plus the gate quoting the mutant, which are stronger.", "open_questions": [ { "question": "Does #11651 close at 14 admitted / 6 skipped, or should it stay open to record that three of its four named sites were mis-classified? The card's stated goal (17/3) is not reachable without asserting a false layer, so PR #11672 says `Part of` rather than a closing keyword and leaves the decision to the PM.", "options": [ "A — close the card on #11672: the corrected partition and the reasoning are recorded in the PR body and in this report, and the remaining six skips are all correct-by-construction.", "B — keep it open, retitled to the measured finding (three layout-dsl sites are other-layer, not authoring-shape), so the corrected partition lives on a card rather than only in a merged PR body.", "C — close it and let #11673 (the skip-list readability finding) carry the residue." ], "recommendation": "A, with the PR body as the record. The card asked a question ('can these four be re-authored?') and it now has a measured answer ('one can; three must not be'). Option B would keep a card open with no remaining executable work, and the durable lesson — that a skip's reason names the parser's problem and not the document's layer — is already a card of its own in #11673, which is where a future reader would look for it." } ], "out_of_scope_findings": [ "filed as #11673: the `*When` skip list's reasons explain why the parser could not read the layer but never which layer the fragment documents, so the list cannot be triaged without opening each file — the measured cause of this card's own wrong 4/3 partition (finding label, unassigned, not queued)" ] }Generated by Claude Code
os-steve commentedon Aug 24, 2026
Closing — PR #11672 landed. Ruling A on the open question.
Verified by content on
origin/main, landing commit located by(#11672)in its subject:PR says
Part of, so no keyword fired; closing by hand rather than spending a CI cycle on a one-word body edit.pm:dispatchedstripped.⭐ The corrected partition — recorded here, not only in a merged PR body
This is the reason the card closes rather than the reason it was opened, so it belongs on the card:
The 4 re-authorable / 3 permanent split in this report was wrong. The measured split is 1 / 6.
Only
content/docs/data-modeling/formulas.mdx:279was an authoring-shape skip. The threelayout-dsl.mdxsites are genuinely other-layer, and each states its layer in its own comment one line above the predicate. The decisive one:Byte-identical predicates.
:863returns the same "current_useris unbound here" error thatpages.mdx:165would — andpages.mdx:165is the skip #11407's ruling protects by name as a false red on correct documentation. So a site sitting in the "probably re-authorable" bucket was in fact a member of the protected class.:821and:824are a matched pair whose whole purpose is contrasting PageComponent bindings against FormSection/FormField bindings. Authoring either half as aField.*factory or afields:map would assert the object-field layer — a different layer from the one the line exists to illustrate — and destroy the contrast that is the fence's entire content. The file documents the objectui layout DSL and contains no object-field example at all.⭐ So
14 admitted / 6 skippedis the honest ceiling, not the 17/3 this card projected — and that is a stronger result for #11407's discriminator than the card claimed: after this change, every site the gate declines to judge is one where judging it would be wrong.Two things this run got right that the brief did not ask for
It judged the four before touching them. That is the only reason the protected-class member was caught. Had it re-authored first and judged after,
:863would have shipped as a false red against correct documentation.It kept a fixture that looked obsolete.
'SKIPPED — a bare field-def fragment with nofields:map above it'survives deliberately: this PR removed that shape from the corpus, which makes the synthetic fixture the only remaining guard that the discriminator still refuses it. Deleting it would have sounded reasonable and been wrong.My own error, since it is what this card cost
My binding ⛔ ruling demanded a target of
17 admitted / 3 skippedand protectedpages.mdx:165by name. Those two instructions contradict each other, because:863is that same predicate. I also asserted that #11652 pinned13/7in--self-test— it does not;FIELD_RULE_SELF_TEST_CASESholds synthetic per-shape fixtures, never a corpus total, and--self-testreports 48 cases unchanged on both sides. So one of my ⛔s was based on a pin that does not exist and pointed at a target unreachable without asserting a false layer.The root cause is measured and filed: #11673 — the skip list's reasons say why the parser stumbled, never what layer the document is about, so the list cannot be triaged from the list. Three independent passes (this report, my triage, my dispatch) landed on the same wrong partition. That card is graded
pm:queuewith the ruling that a skip may show context but must never conclude from it.Closing
completed.Generated by Claude Code