Repository navigation
sweep(spec): 2-item "validation diagnostics don't reach the real defect" sweep — one claim, one PR, per-item checklist (#6391 #5389) #7025
Description
Activity
Claim: PM loop round 2 (maintainer-approved cloud bundling, 2026-08-09 「同意」)
Session:session_01PiRUoQkTSBBmpyXBY3cVn2(dispatching); implementation runs as an independent cloud session (own container)
Branch:claude/issue-7025-diagnostics-descent-sweep
Worktree: cloud session workspace (solo container)
Domain:domain:spec
File surface:packages/spec/src/ui/view*.zod.ts(union discriminant + member exports, #6391) and theinvalid_key/invalid_elementissue-descent consumers (#5389 names three). ⛔ Acceptance face frozen — membership pins in both directions are mandatory. (Stop on breach; explain in the report.)
Serial constraints cleared: members #6391/#5389 leave the dispatch pool while this sweep is open. In-flight siblings source-disjoint; generated-tree overlap possible ⇒ rides the landing relay.
Container assessment: M bundle,mode:cloudsingle container (maintainer-approved).
Generated by Claude Code
OS-DEV-REPORT
{ "card": 7025, "status": "complete", "pr": 7042, "pr_url": "https://github.com/objectstack-ai/objectstack/pull/7042", "pr_state": "draft", "branch": "claude/issue-7025-diagnostics-descent-sweep", "head_sha": "471a7c5", "base": "main", "merged_main_at": "fd74233", "session": "cloud container, solo; dedicated worktree per Prime Directive #11", "members": [ { "issue": 6391, "verdict": "completed", "closes": "Fixes #6391", "premise_recheck": "HOLDS against origin/main@0f539bd. Union members 2/3/4 were inline expressions (ViewSchema.refine(...), ListViewSchema.extend(...).strip(), FormViewSchema.extend(...).strip()) with no exported name; member 1 was already ViewItemWireSchema. The only structural route to a branch was error.issues[0].errors[<position>].", "route_taken": "Both of the filer's options, in the form that does not move membership: the members are NAMED and published (VIEW_METADATA_MEMBERS, keyed by branch) AND a by-body diagnostic dispatch is exported (selectViewMetadataBranch / diagnoseViewMetadata). z.discriminatedUnion was DECLINED - it would refuse an unknown discriminant outright where this union falls through all four members, i.e. a membership change, which the card forbids.", "landing_sites": ["packages/spec/src/ui/view.zod.ts", "packages/spec/src/ui/view-union-diagnostics.test.ts", "packages/spec/api-surface/ui.json"], "before": "ViewMetadataSchema.safeParse({type:'grid',columns:'not-an-array'}) -> one root invalid_union; formatZodError renders the CONTAINER branch's prescription ('wrap it in defineView'), which is not the defect. The real leaf lives at errors[2] - member POSITION 2.", "after": "diagnoseViewMetadata(same body) -> { success:false, branch:'listOverlay', issues:[{ path:['columns'], ... }] }. No position anywhere. ViewMetadataSchema.safeParse output is byte-identical to before." }, { "issue": 5389, "verdict": "completed", "closes": "Fixes #5389", "premise_recheck": "HOLDS. All three consumers gated on code === 'invalid_union' / issue.errors and none read issue.issues. Dormancy also re-measured on zod 4.4.3 (v4/core/schemas.js): z.record raises invalid_key when the KEY schema rejects; z.map raises invalid_key/invalid_element only for non-PropertyKey keys; z.set never raises invalid_element; an enum-keyed record raises top-level unrecognized_keys - matching the triage table on #5389 exactly. Still unproducible from packages/spec's own authoring surface.", "route_taken": "Route (1) from the card - complete the family in the consumers. Route (2) (a lint gate on record key schemas) was not taken: it belongs to a different lane per the card's own triage, and it constrains rather than fixes.", "landing_sites": ["packages/spec/src/shared/error-map.zod.ts", "packages/rest/src/rest-server.ts", "packages/cli/src/utils/format.ts", "+ one pin block in each of the three consumers' existing test files"], "before": "formatZodError -> ' ✗ fields.First Name: Invalid key in record' and nothing else; zodIssuesToFields -> exactly one entry {field:'fields.First Name', code:'invalid_shape', message:'Invalid key in record'}; the CLI prints 'invalid_key: Invalid key in record' with the prescription stranded in the payload.", "after": "All three descend issue.issues additively: the container's own line/entry is unchanged and the leaf follows it - '✗ fields.First Name: Invalid identifier. Must be lowercase snake_case ...' / a second fields[] entry {code:'invalid_format', message:'Invalid identifier. ...'}." } ], "dropped_members": [], "red_line": { "requirement": "acceptance face must not move; pinned in both directions", "method": "42-body corpus through ViewMetadataSchema on origin/main@0f539bd and on this branch, recording verdict + full parse OUTPUT on accept + issue-code set on reject", "result": "byte-identical reports, sha256=fca9df8937bbb9f736f11895a6e1ddf23b7fb25d9b13cfa7e67c71c8dfaaf2b2 on both revisions", "committed_pin": "packages/spec/src/ui/view-union-diagnostics.test.ts -> '[#7025] the acceptance face of ViewMetadataSchema did not move': 19 still-ACCEPTS + 23 still-REFUSES cases", "rejection_envelope": "every REFUSED case asserts its issue `code` set (ADR-0112 / #6142). #5389 touches no schema at all, so its acceptance face is unmoved by construction.", "discriminated_union_assertion": "asserted directly - a discriminated union changes error SHAPE, not membership, and this PR adds no discriminant; diagnoseViewMetadata is pinned to 'never disagree with ViewMetadataSchema about acceptance'." }, "sweep_rules": { "one_pr": true, "per_item_checklist_in_body": true, "before_after_samples_in_body": "yes, real captured output for both members and all three #5389 surfaces", "fixes_only_for_completed": "both members completed; both carry Fixes", "diff_maps_1_to_1": true, "files_changed": 10, "out_of_scope_findings_filed": [] }, "verification": { "build_first": "pnpm build green on origin/main before any edit (#6371), and again after", "reverse_verification": { "spec/src/ui/view-union-diagnostics.test.ts": "66 passed with the fix / 25 failed when reverted (all in the #6391 blocks; the [#7025] acceptance-face block stayed green on BOTH revisions)", "spec/src/shared/error-map.test.ts": "35 passed / 3 failed reverted", "rest/src/zod-union-fields.test.ts": "18 passed / 2 failed reverted", "cli/test/format-zod-union.test.ts": "13 passed / 2 failed reverted" }, "consumer_sweep": "direction '...@objectstack/spec' (downstream), against the rebuilt d.ts (#6218): turbo build 66/66, turbo typecheck 121/121, examples typecheck green, @objectstack/downstream-contract typecheck green", "gates_enumerated_from_lint_yml": "all 59 check:* steps run one by one - all green", "adr_0087": "node scripts/check-adr-0087-registration.mjs --base origin/main -> no declared-breaking changeset", "generated_trees": "generators only (gen:api-surface). check:generated -> all 10 artifacts up to date. No content/docs/references change needed; nothing hand-edited.", "api_surface_delta": "0 breaking (removed/narrowed), 6 added", "local_full_suite": "84/94 turbo tasks green per run, one different unrelated 5000ms vitest timeout each run (@objectstack/types node.test.ts; @objectstack/plugin-email queue-delivery) on a saturated container - each re-run alone on the same tree and PASSED (230/230, 302/302). Every suite this diff touches green in both runs. CI's Test Core (1..3/3) is green on the pushed head.", "main_moved": "origin/main advanced 0f539bd -> fd74233 mid-verification; MERGED (never rebased), no merge=os-regen conflicts, four-step re-run after (install --frozen-lockfile, full build, rm -rf packages/runtime/.objectstack, check:generated). Branch delta vs origin/main unchanged at 10 files." }, "ci": { "head_sha": "471a7c5", "converged": true, "ESLint": "success", "TypeScript Type Check": "success", "all_checks": "26 checks: 24 success, 2 skipped (Build Docs, Console Pin Gate), 0 failure" }, "open_questions": [ { "question": "Export the three container/overlay member schemas as individual `...Schema` consts, or publish them only through VIEW_METADATA_MEMBERS?", "decided": "through the record only", "why": "Measured: exporting ViewContainerWireSchema directly mints ui/ViewContainerWire in json-schema.manifest/ AND 15 keys in the ratcheted authorable-surface/ that are byte-duplicates of ui/View's - 15 phantom entries on the ADR-0049 liveness worklist for a wire door nobody authors against. VIEW_METADATA_MEMBERS.container is the same contractual handle with none of that, and it reduces the PR's generated-tree footprint to the 6 api-surface lines alone." }, { "question": "#5389 names three consumers but its triage says only spec + rest need editing, since the CLI imports spec's formatZodIssue.", "decided": "all three edited", "why": "The CLI does reuse spec's renderer, but its own gate read `code !== 'invalid_union'` and returned [] before ever calling it. Widening that gate is the CLI's whole share of the fix - without it the terminal stays exactly as broken. Verified by measurement, not by reading." }, { "question": "Should diagnoseViewMetadata name a branch for a body the #5599 identity precondition rejects?", "decided": "no - branch is null", "why": "The precondition short-circuits the pipe with z.NEVER, so the union never ran. Naming 'formOverlay' for `{nope:1}` would send an author to fix a shape they were never writing. Detected by re-running the precondition's own predicate (extracted for this), not by pattern-matching its message text." } ], "landing": "NOT merged, NOT queued, NOT auto-merge-armed by this seat. Draft, green, awaiting the PM's serialized relay." }
Generated by Claude Code
ACCEPT → PR #7042 (both members: Fixes #6391, #5389). Verified against GitHub: 10-file diffstat maps 1:1 onto the checklist; CI 26 checks — 24 success + 2 skips, ESLint and TypeScript Type Check both
success.Review highlights: the acceptance-face freeze is proven the strong way — a 42-body corpus with sha256-identical accept/reject reports on both revisions, committed as a 19-accept + 23-refuse pin; declining
z.discriminatedUnionfor exactly the membership reason the sweep's red line names, and declining individual member exports on a MEASURED cost (15 phantom authorable-surface entries) — both are the discipline working, not caution theater. The #5389 half edited all three consumers because the CLI's owninvalid_uniongate returned[]before ever reaching spec's renderer — verified by measurement against the card's triage note, and the measurement wins. Every refusal pin keepscode+status(ADR-0112).Landing: queue, third of the three cloud PRs (pairwise disjoint). Note for the backfill order: with this landed, #6227 (ViewFilterRuleSchema.value shapes — held back for the view-zod file collision) becomes dispatchable.
Generated by Claude Code
Sweep complete — closing the claim card. PR #7042 merged to
mainat3fc2e4861(2026-08-09T12:04Z). Both members delivered and verified closed by direct reading: #6391 ✓, #5389 ✓ (eachstate_reason: completed, closed 12:04Z). No members dropped. Acceptance-face pin held in both directions per the sweep's red line — error SHAPE changed (named view-union branches,invalid_key/invalid_elementdescent), membership did not.
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
Sweep card per the findings-round bundling discipline (#6243 / PR #6288 precedent), packaged by the
domain:specseat with the maintainer's approval (2026-08-09 「同意」, chat). This card is the claim object; the member cards keeppm:queuebut are NOT dispatch candidates while this sweep is open.Shared criterion (what makes these one sweep)
Both members are cases where a validation failure's diagnostic cannot reach the real defect: the refusal fires, but the structure of the error hides which element/key actually failed. Both fixes improve the DIAGNOSTIC face only. The acceptance face must not move: every input that parsed before parses after, every input refused before is refused after — pinned in both directions in the PR. (A discriminator added to a union changes error SHAPE, not membership — assert exactly that; if a member turns out to require a membership change, DROP it from the sweep and report.)
Members (read each card in full before touching it; premise-first per member)
ViewMetadataSchema's union has no discriminant and its container members are unexported, so a consumer diagnosing a failure can only index nestederrorsby member position. Fix: discriminated dispatch where the schema family supports it + export the member schemas, so diagnostics name the branch.invalid_key/invalid_element把真实 issue 挂在issue.issues上,union 家族的三个消费者一个都不下降 #5389 —invalid_key/invalid_elementhang the real issues onissue.issues, and the union family's three consumers do not descend into them — the surfaced message is the generic wrapper. Fix: the consumers descend (or the issue is lifted), so the author sees the leaf diagnosis.Sweep rules (binding on the PR)
Fixes #6391/Fixes #5389only for members actually completed; dropped members getPart of+ reason.git diff --statmaps 1:1 onto the checklist.code+status(ADR-0112) — improving a diagnostic never weakens the envelope (fix(drivers):undefined比较数一律拒收 —— 闸落在任何发射器/守卫之前,两个毛病同闸消灭 (#6050) #6142).Refs: #6243 (sweep pilot), ADR-0112 (error envelope).