Skip to content

[finding] four more exported spec types resolve to unknown while their TSDoc promises a shape — ViewMetadataParsed, InlineAction, AssembledViewArtifact, JoinedReportBlock (the #19871 class, other sites) #19920

Closed

Description

@os-support-ai

Filing gate: ① defects with named landing sites. Finding class (b): a published type whose TSDoc declares a shape the type does not carry, so any value type-checks. Same class as #19871 (ViewMetadata, PR #19919). The sites below are other symbols, and each needs its own fix shape.

Found by the os-dev round on #19871, from a TypeScript compiler-API census over 2359 exported aliases in packages/spec. It found 11 aliases resolving to unknown before that PR and 10 after. Filed by the domain:spec execution seat 1 (session_013RDBh5DqXd2xnLwvHLgLFr, seat post #6017). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.

The sites

Declarations were re-read by this seat on origin/main beac798026. The unknown readings and the causes are the round's.

exported type declaration cause (the round's reading) TSDoc / docs promise
ViewMetadataParsed ui/view.zod.ts: z.infer<typeof ViewMetadataSchema> z.infer of a union whose members are cast to z.ZodTypeAny 「Post-parse shape of ViewMetadata」
InlineAction ui/action.zod.ts: z.input<typeof InlineActionSchema> the input type of a lazySchema(z.preprocess(…)) is unknown the pre-parse shape; references/ui/action.mdx advertises it
AssembledViewArtifact (and …Parsed) ui/assembled-views.zod.ts: z.input<typeof AssembledViewArtifactSchema> a union of VIEW_METADATA_MEMBERS values cast to z.ZodTypeAny 「One assembled viewItems: entry (input shape)」; exported from the root index
JoinedReportBlock ui/report.zod.ts: z.input<typeof JoinedReportBlockSchema> the schema is annotated z.ZodTypeAny references/ui/report.mdx advertises its shape

⚠️ ViewMetadataParsed is not a one-line fix. diagnoseViewMetadata returns data: parsed.data (unknown), which is not assignable to the members' output union (TS2322, measured by the round). That function needs an edit too.

Why it matters

A TypeScript author, or an AI writing typed code, who annotates with one of these names gets no checking at all, while the name and TSDoc say otherwise. check:exported-any reads ZodType<any> annotations only, so no gate sees an alias that resolves to unknown (the round noted a ~60-line compiler-API census could become one). The runtime parse still refuses bad bodies, so no wrong data reaches a store.

Named producers: TypeScript consumers of @objectstack/spec who annotate with these types. No in-repo consumer outside spec tests was found for any of the four.

Suggested shape (⛔ not a ruling)

Per site, derive the type from the member schemas, as PR #19919 does for ViewMetadata. Where the ZodTypeAny cast exists to dodge TS7056 or declaration size, measure that cost first. #19324 showed that cost can be real.

Dedupe

MCP issue search, this repo, at 2026-09-23T19:26Z: exported type resolves to unknown InlineAction JoinedReportBlock AssembledViewArtifact ZodTypeAny returned 1 hit, #19871 (the ViewMetadata site, a different symbol).

Dedupe words: exported type unknown z.input preprocess · ZodTypeAny cast type unknown · InlineAction unknown · JoinedReportBlock unknown · AssembledViewArtifact unknown · ViewMetadataParsed unknown

Blocked-by: #20230


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    Contributor

    Blocked-by: #19871

    定级 pm:blocked · priority:p3 · bug · domain:spec —— #19871 同一类的另外四处;其中一处与在飞的 PR #19919 同文件、相邻两行

    Path: studio-authoring

    Triage: lands in packages/spec/src/ui/{view,action,assembled-views,report}.zod.ts (ViewMetadataParsed view.zod.ts:6312, InlineAction action.zod.ts:2161, AssembledViewArtifact assembled-views.zod.ts:102, JoinedReportBlock report.zod.ts:407) ⇒ domain:spec; rationale: four published types resolve to unknown while their TSDoc promises a shape, so any value type-checks; the runtime parse still refuses bad bodies and no in-repo consumer annotates with them ⇒ p3 like #19871; blocked because PR #19919 (#19871) edits ViewMetadata at view.zod.ts:6310, two lines above ViewMetadataParsed.

    分诊席(session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T20:19Z。本席读完了卡面(本卡尚无评论)。

    本席的读数(origin/main beac798026)

    四处声明逐一复读,与卡面一致:

    • view.zod.ts:6312 ViewMetadataParsed = z.infer<typeof ViewMetadataSchema>;
    • action.zod.ts:2161 InlineAction = z.input<typeof InlineActionSchema>;
    • assembled-views.zod.ts:102 AssembledViewArtifact = z.input<…>;
    • report.zod.ts:407 JoinedReportBlock = z.input<…>。

    「坍缩成 unknown」这一点是实现轮用编译器 API 普查得到的,本席没有重跑。PR #19919 尚未落地。

    判定


    Generated by Claude Code

  2. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    Unblock re-derivation: pm:blocked → pm:queue · 2026-09-25T01:06Z

    Acting on the maintainer's instruction. Provenance: who — the maintainer; verbatim — 「帮我排查一下 blocked 卡片,哪些需要更新状态帮我更新。」; where — the maintainer's chat with session session_013RWUA7bNq5bRhehLPqXwMg, 2026-09-24. ⛔ Not a claim and not a dispatch: the card returns to its lane's take order. Candidate surfaced by check-half-states.mjs H19 on this sweep.

    State: pm:blocked → pm:queue; domain:spec, priority:p3 unchanged; no assignee.


    Generated by Claude Code

  3. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1 · 2026-09-27T15:16Z
    Session: session_01Rjy9MeetSfq34PKn81CRiN
    Account: os-zhuang
    Branch: claude/issue-19920-exported-types-unknown
    Worktree: objectstack-issue-19920
    Domain: domain:spec
    Seat: domain:spec#1
    File surface: the four declarations the card names and what they force, with type-level tests and .changeset/: packages/spec/src/ui/view.zod.ts (ViewMetadataParsed and diagnoseViewMetadata only), packages/spec/src/ui/action.zod.ts (InlineAction), packages/spec/src/ui/assembled-views.zod.ts (AssembledViewArtifact / …Parsed), packages/spec/src/ui/report.zod.ts (JoinedReportBlock only). ⛔ Not FormViewSchema.layout (seat 2's #20221); ⛔ not the flattened-overlay members (PR #20245). (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: default judgment tier (dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable). The diff hits packages/spec/src/**, so it is reviewed at CONTRACT_REVIEW_TIER before enqueue.
    Clause-②: no
    Thread-read: 5824953950
    Serial constraints cleared: view.zod.ts / assembled-views.zod.ts: this seat's PR #20245 (#20186), which rewrote VIEW_METADATA_MEMBERS and the union these types derive from, landed as a91d12af (15:07Z); the dev works on a main that contains it. report.zod.ts: seat 4's open PR #20238 (#20161) edits the joined-report region; JoinedReportBlock is done only after it merges, else it is the remainder (Part of). view.zod.ts is also held by seat 2's #20221 at FormViewSchema.layout only, which is disjoint. action.zod.ts: no open PR or claim. Open PRs read this act: 13; pm:dispatched claims read: 27. Triage 5802280140 set the direction (derive from the member schemas, as PR #19919 did; measure the TS7056 cost first).

  4. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 19920,
      "status": "done",
      "branch": "claude/issue-19920-exported-types-unknown",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/20260",
      "session": "session_01Rjy9MeetSfq34PKn81CRiN — mode:subagent, the PM's session id; this run's identity is the branch",
      "premise_still_valid": true,
      "summary": "Three of the four sites are fixed: InlineAction, ViewMetadataParsed and AssembledViewArtifact / AssembledViewArtifactParsed now resolve to the shapes their TSDoc promises. Each is derived from the member schema the parse actually runs, as PR #19919 did for ViewMetadata: InlineAction from the z.preprocess pipe's `out` member; ViewMetadataParsed from the OUTPUT union of VIEW_METADATA_MEMBERS; the assembled pair from the members minus `container`. No cast, schema, parse or export moves. The one forced change is diagnoseViewMetadata's `data`, which now carries a type assertion (a measured single TS2322 without it; no value changes, and a new test asserts deep equality with the member's own output). JoinedReportBlock is the remainder: PR #20238 (#20161) is still an open draft (head 4aecf01d, auto-merge unarmed), so the PR body opens `Part of #19920` and names this site. The remainder's fork is measured for the next taker: removing the z.ZodTypeAny annotation emits no TS7056 but grows report.zod.d.ts 18,608 → 34,390 bytes. objectui at pin f8a9d0fb holds an inverted `true satisfies IsUnknown` pin on JoinedReportBlock; it is a designed tripwire in objectui's own type-check, and the Console Pin Gate build does not compile it. One deliberate edit to someone else's pending changeset (see open_questions) keeps the advisory Check Changeset red until confirmed in writing.",
      "tests": "Final head cf123662; code identical to 61b382d9 (cf123662 touches only .changeset/). BUILD / TYPECHECK / TEST at 61b382d9 (lock turn 1, VERDICT command-exit 0): spec build exit 0 with TS7056 x0 (base e0f17a37 also x0); `pnpm --filter @objectstack/spec typecheck` exit 0, reading 'check:test-typecheck: OK — ... 53 file(s) / 255 error(s) / 142 pinned signature(s) held'; `pnpm --filter @objectstack/spec test` 'Test Files 550 passed (550)', 'Tests 16120 passed | 2 todo (16122)'. AT cf123662 (lock turn 2, VERDICT command-exit 0): `check:generated` exit 0, 'All 15 generated artifacts are up to date'; the three pin files by name `vitest run --project local --maxWorkers=2`: 'Test Files 3 passed (3)', 'Tests 17 passed (17)'; `tsc -p tsconfig.test.json --listFilesOnly` lists all three pin files (523 test files). DECLARATION COST (spec build, both trees): total .d.ts 30,830,702 → 30,836,475 bytes (+5,773, +0.019%); chunks view.zod 499,735 → 500,541, action.zod 76,621 → 77,606, page.zod 304,750 → 305,845. Root-fix cost, in-memory declaration emit: typing the view union with the member tuple gives view.zod.d.ts 500,881 → 663,301 (ViewMetadataSchema's declaration 323 → 162,743 bytes); the assembled union gives 8,824 → 64,776; no TS7056 in either. REVERSE VERIFICATION (in-memory compiler-host ablation, anchor matched exactly once, nothing written to disk, git status clean): reverting each alias to its base spelling turns every pin red — InlineAction 4 x TS2578, ViewMetadataParsed 3 x TS2578, AssembledViewArtifact 3 x TS2578, AssembledViewArtifactParsed 1 x TS2578; with the fix, 0 diagnostics over the three pin files. Removing the diagnoseViewMetadata assertion yields exactly 1 diagnostic: view.zod.ts TS2322 'Type unknown is not assignable to type ViewMetadataParsed'. The dist-based ablation rules do not apply: the pins are judged by tsc over src (tsconfig.test.json, relative imports), not through a package's exports. GATES: see `gates`. LINT: eslint --no-inline-config --format json over the 6 changed .ts files: 6 files, 0 errors, 0 warnings. The population is eslint.config.mjs's own TS/JS globs (the .md changesets are outside it). The config never enables type-aware linting (eslint.config.mjs:327), so no untouched file's verdict can move. Repo-wide `pnpm lint` is CI's. CONSUMER SUITES: none owed. The census (git grep -w, whole tree) finds no package outside @objectstack/spec naming the four types or diagnoseViewMetadata; objectui at f8a9d0fb names only JoinedReportBlock (untouched); cloud at 48d7066 names none (control leg defineStack: 18 files).",
      "gates": "dispatch-gates.mjs --commands at cf123662: 83 commands, all run; exit codes written to disk before reading. 80 x exit 0. `node scripts/check-empty-changeset.mjs --base origin/main` exit 1: the DELIBERATE CORRECTION class, red by design, advisory `Check Changeset` job only (lint.yml runs just its self-test half). `pnpm check:dual-build-cjs-loads` exit 3 and `pnpm check:type-check-debt` exit 3: PREREQUISITE NOT MET, i.e. NOT MEASURED (both need the whole ./packages/* build closure). `dispatch-gates.mjs --ran` over the exit-coded record: '83 derived, 81 run, 2 NOT-MEASURED, 0 UNRUN'. check:lean-entry-closure and check:doc-formula-expressions were NOT MEASURED at 61b382d9 and were measured green at cf123662 after building @objectstack/formula, @objectstack/lint and @objectstack/objectql. NOT MEASURED, CI-owned: the Type Check workspace and consumer lanes, Test Core shards, Dogfood, Build Core, the 56 artifact-roster families.",
      "line_budget": "not applicable: no skills/** and no line-ratcheted ledger touched. Diff: 8 files, +261 / -9.",
      "files_changed": [
        "packages/spec/src/ui/action.zod.ts +16 -1 (InlineAction alias + TSDoc)",
        "packages/spec/src/ui/view.zod.ts +24 -3 (ViewMetadataParsed alias + TSDoc; diagnoseViewMetadata data assertion)",
        "packages/spec/src/ui/assembled-views.zod.ts +22 -4 (both aliases + TSDoc; type import of ViewMetadataBranch)",
        "packages/spec/src/ui/inline-action-type.test.ts +73 (new pin file)",
        "packages/spec/src/ui/assembled-view-artifact-type.test.ts +80 (new pin file)",
        "packages/spec/src/ui/view-metadata-type.test.ts +26 (ViewMetadataParsed pins)",
        ".changeset/19920-exported-types-not-unknown.md +19 (patch, @objectstack/spec)",
        ".changeset/view-metadata-type-not-unknown.md +1 -1 (one sentence of the pending #19919 note; see open_questions)"
      ],
      "deviations": [
        "Edited a pending changeset this PR did not add (.changeset/view-metadata-type-not-unknown.md): one sentence that this change makes false. It is within the claim's `.changeset/` surface. check-empty-changeset stays red in the advisory Check Changeset job until confirmed in writing; the PR body carries the gate's prescribed confirmation section.",
        "Reverse verification was done in memory (a compiler-host override), not by mutating the tree and rebuilding. The pins are judged by tsc over src, so no dist is on their resolution path.",
        "Zone 3's 'missing required key' pin is not expressible for InlineAction: every key of its input is optional. Its pins are unknown, the two legacy spellings, and a scalar.",
        "A detached comparison worktree, objectstack-issue-19920-base at e0f17a37, was used for the before-build. It is removed at cleanup.",
        "git push x3 via write-pace --run: the empty-branch probe, 61b382d9, cf123662."
      ],
      "mcp_calls": "0",
      "api_writes": "3 relay writes, each one repository_dispatch executed as objectstack-fleet[bot]: (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft #20260), relay run 36333543354; (2) label-write --assign os-zhuang → POST /repos/objectstack-ai/objectstack/issues/20260/assignees, relay run 36333595870, read-back matches; (3) this os-dev-report comment → POST /repos/objectstack-ai/objectstack/issues/19920/comments. Plus git push x3, which is not REST. Reads (GET) were made with the ambient token and are not counted.",
      "open_questions": [
        {
          "question": "Clause-② and changeset level for a published TYPE narrowing. The claim says `Clause-②: no` and the dispatch says patch; both are copied verbatim into the PR and changeset. The #19919 precedent (same defect class, same file, same pending release) declared `Clause-②: no (narrowing)`, `minor`, a BREAKING banner and an adr-0087 not-required marker. Should this PR match it?",
          "options": [
            "A: keep `Clause-②: no` + patch, as claimed. Zero further writes; the changeset already carries the 'if your code stops compiling' instruction.",
            "B: align with #19919: the seat edits the PR body's Clause-② line to `no (narrowing)`, and the changeset becomes minor with a BREAKING banner and the not-required (no-migration-prescription) marker. A changeset-only commit plus one body edit by the seat."
          ],
          "recommendation": "B, on axes 2 and 3. Measured need (axis 1): 0 consumers of the three types in objectstack packages, objectui at f8a9d0fb or cloud at 48d7066, so no measured consumer breaks. Long-term (axis 2): one defect class shipping as BREAKING-minor for ViewMetadata and as patch for ViewMetadataParsed, in the same release, is an inconsistent changelog; the arm exists because a narrowing once shipped with every gate green. AI authoring (axis 3): the arm makes check-adr-0087-registration read the change as breaking and demand its disposition, which is the channel an upgrading agent greps. Startup (axis 4): neutral, since lockstep ships breaking as minor in the launch window and B costs one changeset edit. The arm is the claim owner's to write, so this run did not self-create it."
        },
        {
          "question": "Confirm the deliberate correction of the pending #19919 changeset sentence (check-empty-changeset red in the advisory Check Changeset job).",
          "options": [
            "A: confirm in writing on PR #20260. The correction stands; the approver merges over the advisory red.",
            "B: restore the base text. If both entries ship in one release, the CHANGELOG then says ViewMetadataParsed 'is still unknown', which is false; AGENTS.md says a correction elsewhere never reaches the reader who greps the entry."
          ],
          "recommendation": "A. The gate itself prescribes it for this class. The note's owner is this same seat's #19871 round, and the new wording holds whichever release carries either entry."
        }
      ],
      "out_of_scope_findings": [
        "class: b · reach: named producer — TypeScript consumers annotating `ViewItem` / `ViewItemWire` (the producer #19920 names), and through them the viewItem member of ViewMetadata, ViewMetadataParsed and AssembledViewArtifact · evidence: compiler-API read at cf123662, `config: unknown` on both members of ViewItem and ViewItemWire; cause `viewItemArmShape(viewKind, config: z.ZodTypeAny)` at packages/spec/src/ui/view.zod.ts:4887; the declaration-size cost of typing it is NOT MEASURED · SAME FAMILY as #19920: fold into this family's closing card, not a single-point card · dedupe words: `ViewItem config unknown`, `viewItemArmShape ZodTypeAny`, `ViewItemWire config unknown`",
        "carrier: 承接者:无 · noted, not filed — check:spec-parsed-alias recognises a bare alias only in the spelling z.input of typeof the schema, so member-derived aliases leave its population: 1443 → 1441 bare, 657 → 655 paired at cf123662 (ViewMetadata left it with #19919). The …Parsed siblings exist; the gate just no longer sees the pairs. Recorded in the PR's Acceptance notes."
      ]
    }

    Generated by Claude Code

  5. added a commit that references this issue on Sep 27, 2026
  6. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 19920,
      "status": "done",
      "branch": "claude/issue-19920-exported-types-unknown",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/20260",
      "session": "session_01Rjy9MeetSfq34PKn81CRiN — mode:subagent, the PM's session id; this run's identity is the branch",
      "premise_still_valid": true,
      "round": "2 — the changeset-only round the seat's answers owe (Q1 = B, Q2 = A)",
      "summary": "Q1 = B is applied to .changeset/19920-exported-types-not-unknown.md, all four parts: (i) '@objectstack/spec': minor; (ii) `Clause-②: no (narrowing)`; (iii) a BREAKING banner mirroring #19919's: a narrowing of published TYPES landing as minor in the launch window, the runtime accept set not moving; (iv) the ADR-0087 marker `not-required (no-migration-prescription)` with a one-sentence reason. The 'if your code stops compiling' instruction is kept. The banner names all four aliases (InlineAction, ViewMetadataParsed, AssembledViewArtifact, AssembledViewArtifactParsed): the three sites carry four annotatable names. Q2 = A: the correction to .changeset/view-metadata-type-not-unknown.md stands and was not restored. The PR body's `Clause-②: no` line is left verbatim. No merge: `git merge-tree --write-tree --name-only` against origin/main a9fb83ef exits 0 with no conflicted path; main moved 7 commits since e0f17a37, none touching this diff's files, and no path of this diff is routed to the os-regen driver. Pushed f8792c93 on the paced route (remote tip = local). PR body edits owed by the seat (the dev writes the body once): line 21, 'Changeset …: `patch` on `@objectstack/spec`' → 'minor, `Clause-②: no (narrowing)` with the BREAKING banner and the ADR-0087 not-required (no-migration-prescription) disposition'; lines 69 / 75 / 78 name head cf123662 → f8792c93, whose code is still identical to 61b382d9 (cf123662..f8792c93 touches one changeset only). JoinedReportBlock remains the named remainder: PR #20238 is still an open draft at this reading. Round-1 findings and measurements stand unchanged.",
      "tests": "Head f8792c93. The diff cf123662..f8792c93 is exactly one file, .changeset/19920-exported-types-not-unknown.md. dispatch-gates --commands at f8792c93 re-derives the same 83 commands (sorted diff against cf123662's list: identical). DECLARED NARROWING: every one of the 83 resolves to an existing entry script; 17 of those scripts mention `.changeset`, and those 17 plus `check:spec-changes` were re-run at f8792c93 with exit codes written to disk first. The other 66 stand at their cf123662 reading, since no source moved. Blind spot: a changeset read inside an imported module rather than the entry script. RESULTS (18 run, 17 x exit 0, 1 x exit 1): `node scripts/check-adr-0087-registration.mjs --base origin/main` exit 0: '✓ check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.' reading `.changeset/19920-exported-types-not-unknown.md [BREAKING+clause-②-narrowing] not-required (no-migration-prescription)`; its --self-test exit 0. `node scripts/check-changeset-no-major.mjs --base origin/main` exit 0: '✓ This diff introduces no `major` bump.' Its level axis reads NOT APPLICABLE locally (no pull_request), so it was ALSO driven with `--event` wrapping PR #20260's live GET payload: exit 0, '✓ LEVEL AXIS: this PR declares clause-② `no`, so no package here is declared to have grown a published surface.' `pnpm --filter @objectstack/spec run check:spec-changes` exit 0, 'spec-changes.json is up to date.' It reads the migration registry and package versions, not changesets (its `.changeset` mentions are comment-only), and no spec generator reads `.changeset`, so `check:generated` is not triggered. `node scripts/check-empty-changeset.mjs --base origin/main` exit 1: still ONLY the deliberate correction, naming `.changeset/view-metadata-type-not-unknown.md` and not this PR's own changeset (0 mentions). Also exit 0: check-empty-changeset --self-test, check-keyed-text-bounds (+ --self-test), release-rehearsal-clone --self-test, check:objectui-pin-citations, check:changeset-gate-self-tests, check:driver-memory-census, check:issue-citations, check:nul-bytes, check:objectui-changeset, check:pm-changeset-deadline-census, check:published-files. Code-level readings (build, typecheck, 550-file spec suite, pins, ablations) are round 1's at 61b382d9, whose code f8792c93 carries byte-identically.",
      "gates": "18 of the 83 derived families re-run at f8792c93 (the declared changeset-reading narrowing above): 17 x exit 0; check-empty-changeset exit 1, the DELIBERATE CORRECTION class, red by design in the pull_request-only, non-required Check Changeset job. The 66 others stand at their cf123662 reading (round 1: 81 run, 2 NOT-MEASURED for a missing whole-packages build closure).",
      "line_budget": "not applicable: no skills/** and no line-ratcheted ledger touched. Round-2 diff: 1 file.",
      "files_changed": [
        ".changeset/19920-exported-types-not-unknown.md (patch → minor; Clause-② arm `(narrowing)`; BREAKING banner; ADR-0087 not-required marker) — commit f8792c93"
      ],
      "deviations": [
        "The worktree /home/user/objectstack-issue-19920 was removed at round-1 cleanup. It was re-created on the existing branch (local = remote = cf123662 before the edit, with only this run's two commits on it). No new branch was made.",
        "The banner names four aliases where the instruction says 'the three names': three sites, and the assembled site has two annotatable names, so all four are named to leave no annotated name out of the BREAKING notice."
      ],
      "mcp_calls": 0,
      "api_writes": 1,
      "api_writes_endpoints": "(1) this os-dev-report comment → POST /repos/objectstack-ai/objectstack/issues/19920/comments via post-stamped over the fleet-write relay (repository_dispatch, as objectstack-fleet[bot]). Plus 1 git push (not REST). Round-1 total was 3 relay writes; the cumulative total is 4. GETs used the ambient token and are not counted.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  7. 30 remaining items

  8. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1
    Session: session_01B3TqpoQbTAfG7G74GMDWNW
    Account: hotlong (the seat's linked user as GET /user answers it; the card's assignee; seated on the maintainer's ruling 「放行,以 hotlong 坐席」, seat post #6017 marker 5864561291)
    Branch: claude/issue-19920-exported-types-family-close
    Worktree: objectstack-issue-19920
    Domain: domain:spec
    Seat: domain:spec#1 (seat post #6017)
    File surface (the remainder named by seat 4's release 5865019059, items 1 and 3; item 2 is analysed, not changed):

    • Item 1, ApiError.code: packages/spec/src/api/error-code-ledger.zod.ts (the ErrorCode cast) and packages/spec/src/api/contract.zod.ts (makeApiErrorSchema's cast), plus the in-repo consumers the typing moves, if any: packages/metadata-protocol/src/protocol.ts (toRowApiError) and packages/types/src/response-envelope.ts.
    • Item 3, the list overlay's options bag: listViewKindBlocks() in packages/spec/src/ui/view.zod.ts (~:5669), only if the measured declaration cost is acceptable (see the dispatch); otherwise measured and reported, not changed.
    • Item 2, ViewFilterRule.operator (view.zod.ts:923, a z.preprocess): ⛔ no change. The dev reports the input-type choice as a fork with options; the seat takes it to triage.
    • Their tests, the pin test for exported aliases, the generator-regenerated artefacts, and .changeset/.
      (stop on breach; explain in the report)
      Container & model: M, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate; clause-② suspect surface). Built at the default tier; the at-tier contract review is owed before enqueue.
      Clause-②: no
      Thread-read: 5865019059
      Serial constraints cleared: read at 2026-09-28T10:51Z.
    • PR feat(spec)!: retire the list view's own tabs key; named presets are listViews entries #20357 (spec(ui): retire list.tabs and the view container's body name (2 keys); listViews + ViewTabBar and the row name already deliver both #20301, seat 2) edits view.zod.ts at ListViewShapeSchema (:2769) only; disjoint from listViewKindBlocks (:5669) and ViewFilterRule (~:923). The later lander resolves.
    • No open PR touches api/error-code-ledger.zod.ts or api/contract.zod.ts (open-PR file lists read at this claim).
    • PR fix(spec): JoinedReportBlock, a ViewItem config and the overlay viewKind carry the shapes their doors accept (#19920) #20369 (the card's first half) has landed as 681868ca.

    Amended in place by the same seat: Clause-② was yes and is now no. The at-tier record 5871015216 on PR #20448 reads the diff as widening no accept set and adding no export, so the declaration is no (narrowing), the PR #19919 / #20260 shape for this defect class. The claim’s yes was written before the diff existed.

  9. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 19920,
      "status": "done",
      "branch": "claude/issue-19920-exported-types-family-close",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/20448",
      "session": "session_01B3TqpoQbTAfG7G74GMDWNW — mode:subagent, the PM's session id (claim 5868438648); this run's identity is the branch",
      "premise_still_valid": true,
      "summary": "Items 1 and 3 land in draft PR #20448 (head 4358d1a33, first line `Part of #19920`, line 2 `Clause-②: yes` copied from the claim); item 2 is analysed, not changed. Item 1: `ErrorCode` and `makeApiErrorSchema` now name both of `z.ZodType`'s type parameters, so `ApiError.code` and the `error.code` of every response type built on `BaseResponseSchema` (58 input aliases) are the code vocabulary instead of `unknown`. A1 measured: the cast exists because the spread erases the members to `string`, not for size, but naming the input with the inline union grew the built declarations by 3.36 MB (+11%); spelled with the existing `ErrorCode` alias they shrink by 3,315,656 B (-10.82%), because the output half the base already printed inline is named too. Item 3 (A2): `listViewKindBlocks()` returns a mapped type over the kinds that name a block, each entry typed by zod's own `.partial()` answer through a typed helper; the runtime loop is unchanged. Cost +7,493 B per `view.zod` chunk and 0 TS7056, 2.7 times PR #20369's +2,729 B reference, taken as the same order of magnitude (open question 3). The two named consumer files needed no edit. A4: the census at head reads the same 6 non-family aliases as `unknown`; `ApiError.code` and the four `options` index signatures left it; the only family site left is `ViewFilterRule.operator`, whose fork is open question 1. `Part of` because item 2 has no card of its own yet.",
      "tests": "BUILD: `pnpm --filter @objectstack/spec build` exit 0 and TS7056 x0 at base 0283cb924, at 74a132da1 and at 539295c9e (VERDICT command-exit 0 each); check-dts-references 130 files, 394/394 relative references resolved (base 128, 382/382). DECLARATION COST (all .d.ts/.d.mts, base against 539295c9e): 30,647,033 to 27,331,377 B; api/index.d.ts 2,532,113 to 1,274,615; automation-api chunk 535,440 to 193,663; api-assembled/index.d.ts 184,582 to 86,529; contracts/index.d.ts +55; view.zod chunk 498,393 to 505,886 (+7,493, item 3, the same for .d.mts); new error-code-ledger.zod chunk 31,949 B. SPEC: `pnpm --filter @objectstack/spec typecheck` exit 0, 'check:test-typecheck: OK — ... 53 file(s) / 251 error(s) / 138 pinned signature(s) held'; `tsc -p tsconfig.test.json --listFilesOnly` lists both new pin files (540 test files); `vitest run --project local --maxWorkers=2` at 4358d1a33: 'Test Files 565 passed (565)', 'Tests 16604 passed | 1 todo (16605)'; `check:generated` exit 0, 'All 15 generated artifacts are up to date', no tracked file moved by any build. Earlier targeted run: the 2 new pin files plus view-overlay-viewkind-type, contract and error-code-ledger tests, 5 files / 95 tests passed. CONSUMERS (filter direction: packages that import the narrowed names, i.e. downstream of spec; closure of @objectstack/metadata-protocol built first, 12 packages): `@objectstack/metadata-protocol` typecheck exit 0 (192 test files in its program), vitest 'Test Files 189 passed | 3 skipped (192)', 'Tests 2745 passed | 19 skipped (2764)'; `@objectstack/types` typecheck exit 0 (23/23 test files in program), vitest 'Test Files 22 passed (22)', 'Tests 685 passed (685)'; `@objectstack/client` `tsc --noEmit` (src) exit 0. DIST PROBE: a consumer program importing packages/spec/dist/api and dist/ui, 0 diagnostics with every @ts-expect-error consumed (numeric code on ApiError, invented code on BaseResponse, numeric options.kanban on ViewMetadata, unknown kind on AssembledViewArtifact) and a tuple that compiles only if ApiError.code is neither unknown nor any. REVERSE VERIFICATION from committed 539295c9e via `node scripts/ablation-replace.mjs` wrap mode, each leg under the verify lock, pin files compiled under tsconfig.test.json options (subject is src via relative imports, no dist on the path): control 0 diagnostics over the 3 pin files; leg A (ErrorCode cast back to the output-only parameter) api-error-code-type.test.ts 2 x TS2322 + 3 x TS2578; leg B (makeApiErrorSchema cast back) 1 x TS2322 + 2 x TS2578; leg C (listViewKindBlocks back to a record of string to z.ZodTypeAny) view-overlay-options-type.test.ts 2 x TS2322 + 9 x TS2578. Each leg 'ok mutation landed: anchor 1 -> 0' and 'ok restored: blob == HEAD and git diff HEAD is empty'; independent git hash-object of the 3 files equals the HEAD blobs (4b551580a026, 862667a8b0e8, bedcd47ca479), porcelain 0. LINT (narrowed, proven): population = eslint.config.mjs's own globs; `eslint --no-inline-config --format json` over the 5 changed .ts files at 4358d1a33: 0 errors, 0 warnings; the changeset .md is 'File ignored because no matching configuration was supplied'; invariance: eslint.config.mjs:327 enables no type-aware linting for any file, so this diff cannot move an untouched file's verdict. CENSUS (compiler API, packages/spec/src, tsconfig.json options, tests excluded, injected control module read LIT at base and head): 2,337 non-generic exported aliases (11 generic skipped); alias level 6 and 6, the same six (FlowValueSlot, FlowValueSlotParsed, AssignmentValue, AssignmentValueParsed, GetPublishedMetaItemResponse, GetPublishedMetaItemResponseParsed); top-level keys 194 to 193 (left: ApiError.code; entered: none); index signatures one level down 391 to 387 (left: options on ViewMetadata, ViewMetadataParsed, AssembledViewArtifact, AssembledViewArtifactParsed; entered: none). NOT MEASURED: spec `test:repo` (acquired the lock at once and ran the whole ~595 s foreground window without finishing, twice; CI runs it); the @objectstack/client test-layer typecheck (its dev closure is 33 packages); the compile impact of item 2's option A (in-memory instrument written, not run; impact given from reading); cloud (no checkout here).",
      "gates": "`node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at 4358d1a33 (no paths, off the merge base): 86 commands, the dispatch's 75 plus 11 (check-empty-changeset x2, release-rehearsal-clone --self-test, check:engine-double-contract, check:objectql-double-limit, check:objectui-changeset, check:pm-changeset-deadline-census, check:query-options-erasure, check:type-check-coverage, check:type-check-debt, check:where-matcher), none dropped. All 86 run, exit codes written to disk before reading: 83 exit 0; `pnpm check:lean-entry-closure` first exit 3 (objectql dist absent), then exit 0 after building @objectstack/objectql's JS ('Admitted set held exactly (15 packages)'); 2 exit 3 PREREQUISITE NOT MET: `pnpm check:dual-build-cjs-loads` and `pnpm check:type-check-debt` (whole-packages build). `dispatch-gates.mjs --ran`: 'Run reconciliation — 86 derived, 84 run, 2 NOT-MEASURED, 0 UNRUN.' Readings: check-adr-0087-registration '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition' with `[BREAKING+clause-②-narrowing] not-required (no-migration-prescription)`; check-changeset-no-major 'This diff introduces no `major` bump.'; check-empty-changeset exit 0 ('1 declaring changeset(s) added', no base changeset modified); check:api-surface 'public API surface + factory signatures unchanged'; check:exported-any 'no exported type resolves to any: 2384 types + 1446 schemas across 18 entry points'; check:dts-closure '65/65 declared declaration file(s) present across 12 package(s)'. The derivation printed a stale-tree note: origin/main at least 5 commits ahead, none touching what it derives from.",
      "line_budget": "not applicable: no skills/** and no line-ratcheted ledger touched (test-typecheck-debt.json unchanged).",
      "files_changed": [
        "packages/spec/src/api/error-code-ledger.zod.ts +16 -1 (ErrorCode cast names both type parameters, spelled with the ErrorCode alias; TSDoc with the measurement)",
        "packages/spec/src/api/contract.zod.ts +3 -1 (makeApiErrorSchema's code cast names both type parameters)",
        "packages/spec/src/ui/view.zod.ts +43 -2 (listViewKindBlocks return type; typed helper partialListViewKindBlock; local types ListViewShapeFields, ListViewKindBlockName, ListViewKindBlocks; loop unchanged)",
        "packages/spec/src/api/api-error-code-type.test.ts +93 (new pin file, type half and runtime half)",
        "packages/spec/src/ui/view-overlay-options-type.test.ts +106 (new pin file, type half and runtime half)",
        ".changeset/19920-exported-types-family-close.md +20 (@objectstack/spec minor, Clause-②: yes (narrowing), BREAKING banner, ADR-0087 not-required (no-migration-prescription))",
        "whole branch against BASE 0283cb924: 6 files, +281 / -4; commits 74a132da1, 539295c9e, 4358d1a33"
      ],
      "deviations": [
        "Zone 3's 'drop the second cast' is not taken: makeApiErrorSchema's vocabulary is caller-supplied and spread into a string[], so the cast is what carries the caller's codes into the type, and dropping it would change the returned schema class to ZodEnum. The missing input parameter was the defect and is what changed.",
        "Item 1 was first spelled with the inline union (74a132da1), measured at +3.36 MB of declarations, and respelled with the ErrorCode alias (539295c9e), measured at -3.32 MB. Both commits are on the branch.",
        "Item 3 was implemented at +7,493 B per view.zod chunk, 2.7 times A2's +2,729 B reference, read as 'the same order' of magnitude; 0 TS7056 and no any. See open question 3.",
        "The changeset carries `Clause-②: yes (narrowing)` (the claim's value plus the family's #20369 arm), while PR body line 2 is the claim's `Clause-②: yes` verbatim. See open question 2.",
        "The bundler emits one new shared declaration chunk, error-code-ledger.zod (.d.ts/.d.mts), so declaration files go 128 to 130; every closure check resolves.",
        "@objectstack/objectql's build exited 1 at its DTS step (TS2307: @objectstack/metadata-protocol declarations were never built here), after its JS built; only the JS was needed, by check:lean-entry-closure. This diff is not the cause.",
        "The first full-suite lock turn was killed by the run's own 595 s wall cap during test:repo (exit 124), after the local suite had finished green; lock free and no process of this run left afterwards (checked).",
        "Commit trailers are the model-free pair AGENTS.md requires (Claude-Session + Co-authored-by: Claude), not the harness's model-named trailer.",
        "Worktree cleanup (node_modules removal, git worktree remove) runs right after this comment is posted, because post-stamped.mjs runs from the worktree."
      ],
      "pr_body_delta": [
        { "where": "line 1", "action": "replace only if the seat files item 2 as its own card before merge", "text": "the closing keyword for #19920 (the order's A4 condition then holds); otherwise keep `Part of #19920`" },
        { "where": "line 2", "action": "replace only if open question 2 is answered B", "text": "Clause-②: no (narrowing)" }
      ],
      "mcp_calls": "0 — no MCP tool was called",
      "api_writes": "3 relay writes, each one repository_dispatch executed as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls (draft #20448), relay run 36427325475; (2) label-write --assign hotlong, POST /repos/objectstack-ai/objectstack/issues/20448/assignees, relay run 36427445835, read-back matches; (3) this os-dev-report, POST /repos/objectstack-ai/objectstack/issues/19920/comments via post-stamped.mjs. Plus 4 git pushes through write-pace (not REST): the empty-branch probe, 74a132da1, 539295c9e, 4358d1a33. Reads used the ambient token (GET) and are not writes.",
      "open_questions": [
        {
          "question": "Item 2 (for triage): what should the INPUT type of `ViewFilterRule.operator` be? It is `z.preprocess(normalizeFilterOperator, z.enum(VIEW_FILTER_OPERATORS))`; zod 4.6 types a preprocess's input as its function's parameter (`preprocess` returns ZodPreprocess of the schema and the parameter type), and normalizeFilterOperator takes unknown, so `{ field: 'status', operator: 42 }` compiles as a ViewFilterRule and as a rule on every carrier (ListView.filter, tab filters, Page.filterBy) while the door refuses it. Measured authors of the legacy spellings the fold accepts: examples 0 on a view-filter carrier (19 canonical in 8 files; the one legacy-looking hit, `operator: 'ne'` in app-showcase invoice.object.ts, is a lookupFilters entry, a separate closed dialect); in-repo non-test code 0 (every other hit is another dialect: lookup filters, auth where, skill trigger conditions, analytics); objectui at pin f8a9d0fb: the filter builder emits camelCase ids (13 of its 22 option values are alias-table keys), but both producers typed against spec's ViewFilterRule (viewFilterFold.ts:193, ObjectDataPage.tsx) fold through normalizeFilterOperator before typing, so the canonical id reaches the type; objectui at 9f0c84a44 (its current head) emits the 20 canonical ids only; stored sys_metadata rows carry legacy spellings and are read through the runtime parse, which no option below changes.",
          "options": [
            "A — canonical enum only: type the preprocess function's parameter as ViewFilterOperator (one annotation; the runtime fold is untouched, so stored rows and plain-JS producers still parse). Consumer impact: 0 measured typed producers write an alias; objectui's typed producers fold first. Cost: the annotation, a two-half pin file, a BREAKING-narrowing minor changeset. The type is deliberately narrower than the door for alias spellings.",
            "B — enum plus the alias-table spellings: requires VIEW_FILTER_OPERATOR_ALIASES's published type to change from a record of string to ViewFilterOperator to a literal-keyed type (a published const type change; consumers indexing it with a string then need a guard), and still cannot express the case-folded variants the fold also accepts (GT, NotIn), so it remains narrower than the door anyway.",
            "C — leave unknown and declare why in ViewFilterRule's TSDoc: zero code; the family's class-(b) gap stays open on this key and on every carrier."
          ],
          "recommendation": "A. Business need (axis 1): no measured typed author writes an alias spelling, so B serves nobody and A breaks nobody; the only legacy carriers are stored rows, which the runtime parse handles under every option. Long-term (axis 2): the alias table's own TSDoc says new producers MUST emit canonical forms and the aliases may be dropped; A is contract-first, B writes a second dialect into the type, C keeps declared-not-enforced. AI authoring (axis 3): under A a typed author, AI included, is refused at compile time for an alias spelling or a number; B offers the 36 alias-table spellings as valid; C accepts anything. Startup (axis 4): A is the smallest (one annotation, no new export) and retires the aliases from the typed surface at once, with no staged window; B grows the surface; C costs nothing but leaves the defect. No new gate is recommended."
        },
        {
          "question": "Clause-② value. The claim reads `Clause-②: yes`, copied verbatim into PR body line 2 as required. clause2-line.mjs defines `yes` as a widening; this diff adds no export and moves no accept set, only narrowing published types, which is the `no (narrowing)` reading (PR #20260's shape). PR #20369's `yes (narrowing)` had three new exports behind its `yes`. The changeset carries `yes (narrowing)` so check-adr-0087-registration reads the narrowing either way.",
          "options": [
            "A — keep `yes`: PR body line 2 as is; changeset stays `Clause-②: yes (narrowing)`. Zero further writes.",
            "B — amend to `no (narrowing)`: the seat edits PR body line 2 (pr_body_delta), and one changeset-only commit changes its line to `Clause-②: no (narrowing)`; minor, the BREAKING banner and the ADR-0087 marker stay (round 2 of PR #20260 measured check-changeset-no-major green with `no` and minor)."
          ],
          "recommendation": "B, on axes 2 and 3: the declaration then says what the diff does (no surface grew; published types narrowed), which is what the level axis and an upgrading agent reading the changelog rely on. Axis 1: no consumer is affected either way. Axis 4: one changeset line and one body line. The line is the claim owner's, so this run did not change it."
        },
        {
          "question": "A2's size rule: item 3 costs +7,493 B per view.zod chunk (2.7 times the +2,729 B reference; +1.5% of the chunk), 0 TS7056, no any. This run read 'within the same order' as the same order of magnitude and implemented it. Hold a tighter reading?",
          "options": [
            "A — accept as landed (the whole PR shrinks the declarations by 3.32 MB).",
            "B — revert item 3 to measured-only: a one-commit revert of the view.zod.ts hunk and its pin file, and the changeset loses its second bullet."
          ],
          "recommendation": "A: the cost is bounded to one chunk, the type is exact rather than an any escape, and the net declaration change of this PR is -10.82%."
        }
      ],
      "out_of_scope_findings": [
        "carrier: 承接者:无 · noted, not filed — a field's lookupFilters is its own closed operator dialect (eq, ne, gt, lt, gte, lte, contains, in, notIn) whose members are spellings ViewFilterRule treats as deprecated aliases; both are enforced, so no class a/b/c; recorded in PR #20448's Acceptance notes for item 2's triage.",
        "carrier: 承接者:无 · noted, not filed — makeApiErrorSchema's generic return type still prints the standard catalogue inline, 4 prints in its one emitted declaration (a 4,311 B line) against 2 at base; a local generic alias would name it; recorded in PR #20448's Acceptance notes."
      ]
    }

    Generated by Claude Code

  10. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    Contributor

    Review: ACCEPT · PR #20448 at head 4358d1a334 · 2026-09-28T13:24Z

    domain:spec seat 1 (session_01B3TqpoQbTAfG7G74GMDWNW), reviewer of record under claim 5868438648. The dev report is on this card (os-dev-report). Readings were taken on GitHub by this seat.

    check reading
    Shape Draft, base main. Line 1 is now Fixes #19920, the only closing keyword; line 2 is Clause-②: yes. The seat changed line 1 from the partial marker after filing item 2 as #20450: the order's A4 condition.
    Scope 6 files, +281 / −4: error-code-ledger.zod.ts, contract.zod.ts, view.zod.ts (listViewKindBlocks), two new pin files, and the changeset. The two consumer files named in the claim needed no edit.
    Changeset @objectstack/spec minor, Clause-②: yes (narrowing), a BREAKING banner, and ADR-0087 not-required (no-migration-prescription).
    Governed / size Not governed; 285 changed lines.
    Evidence Reverse verification from the committed head, one leg per cast and one for the mapped type. Each leg turns its pin file red (TS2322 plus unused @ts-expect-error), and each is restored to the HEAD blob.

    The dev's open questions, answered by the seat:

    1. Item 2's fork (ViewFilterRule.operator's input type): filed as spec(ui): ViewFilterRule.operator's input type is unknown (a z.preprocess); type it as the canonical ViewFilterOperator, or admit the alias spellings? (the last site of #19920's family) #20450, with the dev's options and measured authors, for triage.
    2. The Clause-②: value: kept at yes, the claim's value. The claim declared yes conservatively; declaring yes on a narrowing is never an error, and the changeset's yes (narrowing) carries the arm.
    3. A2's size rule: accepted. Item 3 costs +7,493 B on one chunk, with 0 TS7056 and no any. The PR as a whole shrinks the built declarations by 3,315,656 B (−10.82%), because item 1 is spelled with the ErrorCode alias.

    Deviation accepted: zone 3's "drop the second cast" is not taken. makeApiErrorSchema's vocabulary is caller-supplied, and the cast is what carries it into the type; the missing input parameter was the defect.

    Out of scope, both Acceptance notes:

    • lookupFilters is its own closed operator dialect;
    • makeApiErrorSchema's return type prints the catalogue inline 4 times.

    needs:contract-review goes on PR #20448 in this act, and the isolated at-tier review starts now.

  11. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    Contributor

    Close-out by the domain:spec seat 1 (session_01B3TqpoQbTAfG7G74GMDWNW) at 2026-09-28T15:11Z.

    Delivered. PR #20448 merged as cf559147b through the merge queue, and this card closed completed on its Fixes line. It covers items 1 and 3 of the remainder seat 4's release 5865019059 named:

    • ApiError.code (and every BaseResponse error.code) narrows from unknown to ErrorCode, and a makeApiErrorSchema schema's input code narrows to its vocabulary;
    • the list overlay's options bag is typed per kind block.

    No runtime accept set moved. At-tier record 5871627093 PASS at b26d6506, after the patch round that corrected the Clause-② value to no (narrowing) (5871015216; this card's claim 5868438648 was amended in place). pm:dispatched removed.

    What remains, and who carries it.


    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