Skip to content

sweep(spec): 2-item "validation diagnostics don't reach the real defect" sweep — one claim, one PR, per-item checklist (#6391 #5389) #7025

Description

@os-zhuang

Sweep card per the findings-round bundling discipline (#6243 / PR #6288 precedent), packaged by the domain:spec seat with the maintainer's approval (2026-08-09 「同意」, chat). This card is the claim object; the member cards keep pm:queue but 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)

  1. spec/ui: ViewMetadataSchema 的 union 无判别式且容器成员未导出——消费方做失败诊断只能按成员序索引嵌套 errors #6391 — ViewMetadataSchema's union has no discriminant and its container members are unexported, so a consumer diagnosing a failure can only index nested errors by member position. Fix: discriminated dispatch where the schema family supports it + export the member schemas, so diagnostics name the branch.
  2. 休眠:invalid_key / invalid_element 把真实 issue 挂在 issue.issues 上,union 家族的三个消费者一个都不下降 #5389 — invalid_key / invalid_element hang the real issues on issue.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)

Refs: #6243 (sweep pilot), ADR-0112 (error envelope).

Activity

  1. self-assigned this
    on Aug 9, 2026
  2. os-zhuang commented on Aug 9, 2026

    @os-zhuang
    ContributorAuthor

    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 the invalid_key/invalid_element issue-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:cloud single container (maintainer-approved).


    Generated by Claude Code

  3. os-zhuang commented on Aug 9, 2026

    @os-zhuang
    ContributorAuthor

    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

  4. os-zhuang commented on Aug 9, 2026

    @os-zhuang
    ContributorAuthor

    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.discriminatedUnion for 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 own invalid_union gate returned [] before ever reaching spec's renderer — verified by measurement against the card's triage note, and the measurement wins. Every refusal pin keeps code + 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

  5. os-zhuang commented on Aug 9, 2026

    @os-zhuang
    ContributorAuthor

    Sweep complete — closing the claim card. PR #7042 merged to main at 3fc2e4861 (2026-08-09T12:04Z). Both members delivered and verified closed by direct reading: #6391 ✓, #5389 ✓ (each state_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_element descent), membership did not.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions