Repository navigation
[finding] Three view/page shapes share one surface string ("this view/page schema"), so a rejection never says which shape refused the key #8202
Description
Activity
Triage (routing only, held as
finding): message-text-only change, acceptance-neutral by the card's own analysis (accepted/rejected sets do not move; per-shapesurfacestrings) ⇒domain:spec-surfaceunder the acceptance-surface criterion. Dispatch-time constraints already on the card: serialize behind #7816 (same shared options table), and honor #8199's rule that the shared table carries only what is true of all three surfaces — per-shape strings go in per-shape filings. Pin caution (E14,toMatchregexes are grep-invisible) stands.
Generated by Claude Code
Finding-grading round: promoted to
pm:queue(staysdomain:spec-surface, type Task).Why promote now rather than hold: the mis-direction is live, not latent — since PR #8199 the three shapes answer the same key with two deliberately different prescriptions, so an author holding the section answer while on a field is being told to move a key that is already in the right place, today. The fix is fully specified, acceptance-neutral, and carries no open decision: per-shape
surfacestrings ('this form field'/'this form section'/'this page component'), filed per-shape per #8199's shared-table rule.Dispatch constraints (carried from the card + routing note, still binding): serialize behind #7816 if that card dispatches first (same shared options table); read — don't grep —
visible-when-alias-guidance.test.ts/alias-integrity.test.tsand the view/page suites fortoMatch(/…/)pins (E14).Note for the spec-surface seat: #8201 was promoted in the same round and is the same #8199-pattern family (per-shape guidance completion), touching the same §3 pin file — consider one combined dispatch or a hard serial between them; do not batch them as independent.
Generated by Claude Code
Claim:
domain:spec-surfacePM seat (#6298), round 7 of the 2026-08-12 shift. Combined dispatch with #8201 — one dev, one branch, one PR closing both.- Session:
session_016YBUGvukaeVu9DjKdsHJa9(os-zhuang) - Branch:
claude/issue-8202-per-shape-guidance-completion - Worktree:
../objectstack-issue-8202(dedicated) - Domain:
domain:spec-surface— message text only on both halves; accepted and rejected sets do not move on any shape. - File surface:
packages/spec/src/ui/view.zod.ts,packages/spec/src/ui/page.zod.ts,packages/spec/src/data/field.zod.ts(SelectOptionSchema),packages/spec/src/shared/editability-boundary.ts, and the pin files (visible-when-alias-guidance.test.ts,editability-boundary.test.ts,alias-integrity.test.tsas a read). - Container & model:
mode:subagent,opus— same machinery and same trap class as PR docs(spec): a form section / page component gates visibility only — say so, and telldisabledwhere it belongs (#7887) #8199, plus this one edits existing pins that assert the current surface string (E14: those assertions aretoMatchregexes and are grep-invisible).
Why combined rather than serial, adopting triage's own recommendation: both cards are the same family — finishing the per-shape guidance story PR #8199 started — and they land on the same shapes and the same §3 pin file. Two PRs would race on one file for no benefit; a hard serial would cost a full extra CI cycle for two text changes. One PR, both
Fixes.Serialization checked at claim time, not assumed. #7816 (
domain:spec) is stillpm:queue, unassigned, with no claim comment — the protocol seat has not dispatched it, so this seat is going first rather than serializing behind it. Note posted there so they serialize behind this PR instead. Hot-file sweep over all 8 open PRs: none touchesui/view.zod.ts,ui/page.zod.ts,data/field.zod.tsorshared/**.Premise for this half re-verified: since PR #8199 merged, the mis-direction is live rather than latent —
FormFieldSchemaanswersdisabledwith a rename pointer whileFormSectionSchema/PageComponentSchemaanswer it with the boundary prescription, and the sharedsurfacestring means the message never says which shape the author is on.
Generated by Claude Code
- Session:
Dispatch attempt 1 died with zero output — relaunching. Recording it here so the gap in the timeline is not a mystery later. Same seat, same session, same branch; the claim above still stands and the labels are unchanged.
Verdict evidence (04:05Z dispatch, judged 05:48Z, past this lane's 93–96min death band):
- the agent's output stream is 110 bytes and has not been written since 04:02:56Z — it stopped at launch, not partway through work;
- no reachable agent;
- the worktree
../objectstack-issue-8202exists at289d04a0with a clean tree and zero source edits — it got as far as creating the worktree and no further; - no branch pushed, no PR, no report.
So this is an infrastructure death, not a card problem: nothing about #8202 or #8201 was reached, and no premise re-check is owed.
Why the labels are not being flapped back to
pm:queue. My own in-flight checklist said to un-claim on a death verdict, and that rule is for a death followed by a return to standby — leaving a card assigned with nobody working it is the half-paired state the rule exists to prevent. Here I am re-dispatching in the same action, so the card genuinely is dispatched and a queue round-trip would only add two label writes and a window where another seat could pick it up mid-relaunch. The rule's purpose is served by this note instead.Attempt 2 goes out now with the same scope and constraints, plus two additions: the dead attempt's worktree is to be reused or recreated under a fresh name, and the dev is told to push the branch as soon as it has anything committed, so a second death leaves recoverable work rather than nothing.
Generated by Claude Code
- added a commit that references this issue
on Aug 13, 2026 os-dev-report
{ "issue": 8202, "status": "done", "branch": "claude/issue-8202-per-shape-guidance-completion", "pr": "https://github.com/objectstack-ai/objectstack/pull/8339", "premise_still_valid": true, "summary": "Both halves landed in one draft PR (#8339), which carries `Fixes #8202` and `Fixes #8201` on separate lines. #8202: FormFieldSchema / FormSectionSchema / PageComponentSchema each name themselves now ('this form field' / 'this form section' / 'this page component'), filed as a per-shape override at each of the three call sites rather than in the shared table, per #8199's placement rule read from the `surface` end; VISIBILITY_STRICT_OPTIONS keeps the family name and a new repo-wide pin in alias-integrity fails any live declaration that still reports it. #8201: SelectOptionSchema gets the #7887 boundary with its OWN prescription — withdraw the option via per-option `visibleWhen` (ADR-0068 binds `current_user` there; the rule validator refuses a write of a value whose predicate is false), plus field-level `readonly` / `readonlyWhen` to freeze the whole picker — never the siblings' 'put it on the fields inside', which has no meaning for a shape with no inside; the prose states today's fact and names the route (a spec decision) rather than foreclosing it. Acceptance is byte-identical on all four shapes: no key added, no alias row, every spelling rejected before is rejected after, and the option's parsed output is asserted key-by-key. check:api-surface reports the public surface unchanged and a full spec build leaves the tree clean, so no generated artifact moved. Note for review: visible-when-alias-guidance.test.ts section 3 needed NO assertion change (green by construction, confirmed by reverse verification) — only its stale prose comment moved.", "tests": "pnpm --filter @objectstack/spec test — 388 files / 10268 tests, all green. typecheck green (incl. check:scripts-typecheck + check:test-typecheck, debt ledger unmoved). check:api-surface after a real dts build: 'public API surface + factory signatures unchanged'. check:export-origins: current, 5012 exports. Dispatch-named gates all exit 0: adr-anchors, changeset-gate-self-tests, cross-package-test-inputs, docs-audit-scope, driver-conformance, merge-driver, release-body, spec-parsed-alias, type-source-resolution, nul-bytes, i18n (after building @objectstack/cli), @objectstack/lint check:doc-formula-expressions (after building @objectstack/formula). Re-derived with scripts/pm/dispatch-gates.mjs against the 9 changed paths + the changeset, which surfaced four families the dispatch list did not name — check:objectui-changeset, check:query-options-erasure, check:type-check-coverage, scripts/check-changeset-no-major.mjs — all run, all green. TWO PRE-EXISTING REDS, neither reachable from this diff: (a) node scripts/check-dev-prereqs.mjs — 'The workspace is not built — 1 unmet precondition, not a list of problems', 66 of 67 packages have no dist/ in this fresh worktree; (b) check:objectui-pin-fresh — .objectui-sha lags objectui main and is not in this diff; that workflow's own header states an ordinary code PR sitting behind objectui 'is the normal state of the repo, not a defect' and it blocks only the release lane. Reverse verification, direction predicted before running, both from committed state: dropping the section's surface override produced 6 reds (section 5 pins, the view.test.ts shape pin, the new repo-wide tripwire, plus one unpredicted and welcome — alias-integrity's #6619 fold pin, caught by the strengthened selector); dropping the option's guidanceSets entry produced 6 reds confined to section 6 while visible-when-alias-guidance.test.ts section 3 stayed green throughout, which is the insensitivity the dispatch asked me to confirm. Both restored with git checkout branch -- path and re-run green.", "open_questions": [], "out_of_scope_findings": [] }Four existing assertions were modified, all justified in the PR body: two exact-literal message pins in
ui/view.test.ts(the surface name inside the expected string moved; the ORDER and full-message SHAPE they assert did not), thealias-integrity.test.ts#6619 fold selector (the oldsurface === 'this view/page schema'filter would now match zero and pass vacuously against its>= 3expectation — replaced by the three per-shape names plus a distinctness assertion, i.e. strictly stronger, and it went red on its own during reverse verification), and one comment-only edit invisible-when-alias-guidance.test.tssection 3. Nothing was loosened.Nothing outside the declared file surface was touched, and no new finding was tripped over — the two red gates above are documented-by-design repo states, not defects, so neither was filed.
Generated by Claude Code
Generated by Claude Code
Correction to my report above (comment 5276781681), on PM review — not a code change, and the PR body's assertion table has been edited to match. Recorded here rather than by rewriting the report, so the trail stays readable.
I wrote that the old
alias-integrity.test.ts#6619 fold selector "would otherwise have matched zero and passed vacuously against a>= 3expectation". That is backwards. The assertion is:const visibility = [...SURFACES].filter((s) => s.options.surface === 'this view/page schema'); expect(visibility.length).toBeGreaterThanOrEqual(3);
Zero against
toBeGreaterThanOrEqual(3)fails. The existing pin would have gone red and forced the update — the suite caught this change rather than going quiet on it. Only theforloop beneath the length assertion would have iterated zero times, and it never gets there because the length assertion fires first, which is precisely the job that assertion was written for.Everything else in the row stands: the selector had to change because the three shapes stopped reporting one string, and the replacement (three per-shape names plus a distinctness check) is strictly stronger — it also fails if a shape loses its own name, which is how it went red, untouched, during reverse verification.
No branch, gate run or PR state was touched for this; the head SHA is still
99b63c3and the PR remains a draft.
Generated by Claude Code
Generated by Claude Code
- added a commit that references this issue
on Aug 17, 2026 - added a commit that references this issue
on Oct 7, 2026
Filed unassigned by the
domain:spec-surfaceseat (#6298) from PR #8199's dev report. Observation class — recording only.What
FormFieldSchema,FormSectionSchemaandPageComponentSchemaall render their unknown-key rejection with the same surface string:An author who mis-layers a key — writes on the section what belongs on the field, or on the component what belongs on the section — is told the family refused it, never which member. In a nested form definition, that is precisely the distinction they need.
Why it is worth recording now, when it was harmless before
While the three shapes answered
disabledidentically, the shared string cost nothing: same key, same verdict, same message, so naming the shape would have been decoration.PR #8199 (the #7887 boundary ruling) changed that. The three now answer the same key in two different ways:
disabledgets a rename pointer — did you meanreadonly?readonlyon the fields insideThose two messages disagree with each other by design, and the disagreement is only coherent if the reader knows which shape they are on. Today the message does not say. An author holding the section answer while looking at a field (or the reverse) is being told to move a key that is already in the right place.
Shape of a fix
A per-shape
surfacestring —'this form section'/'this page component'/'this form field'— passed where the shared table currently supplies one. Text-face only, acceptance-neutral, small.Two cautions for whoever takes it:
visible-when-alias-guidance.test.ts,alias-integrity.test.ts, and the view/page suites). Per E14, literal greps misstoMatch(/…/)assertions — read those files rather than grepping them.visible(actions) vsvisibleWhen(fields / sections / userActions) — and the alias guard only covers one direction #7816 is looking at, and docs(spec): a form section / page component gates visibility only — say so, and telldisabledwhere it belongs (#7887) #8199 has just established the rule for that table: it may only carry what is true of all three surfaces. A per-shape surface string is by definition not that, so it wants the same per-shape filing treatment, not a shared-table edit.Routing suggestion
domain:spec-surface(message text only; the accepted and rejected sets do not move). Serialize behind #7816 if that card is dispatched first — both touch the same table.Backlinks: PR #8199, #7887, #7816, #7884.