You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[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
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/mainbeac798026. The unknown readings and the causes are the round's.
⚠️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).
Triage: lands in packages/spec/src/ui/{view,action,assembled-views,report}.zod.ts (ViewMetadataParsedview.zod.ts:6312, InlineActionaction.zod.ts:2161, AssembledViewArtifactassembled-views.zod.ts:102, JoinedReportBlockreport.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.
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.
Re-derived, still owed: on origin/main the four declarations stand as the card lists them — ViewMetadataParsed = z.infer<typeof ViewMetadataSchema>, InlineAction, AssembledViewArtifact and JoinedReportBlock as z.input of their schemas.
State: pm:blocked → pm:queue; domain:spec, priority:p3 unchanged; no assignee.
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).
{
"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."
]
}
{
"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": []
}
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.
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.
{
"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."
]
}
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/specminor, 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 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.
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.
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 BaseResponseerror.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.
Activity
objectstack-fleet commentedon Sep 23, 2026
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(ViewMetadataParsedview.zod.ts:6312,InlineActionaction.zod.ts:2161,AssembledViewArtifactassembled-views.zod.ts:102,JoinedReportBlockreport.zod.ts:407) ⇒domain:spec; rationale: four published types resolve tounknownwhile 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) editsViewMetadataatview.zod.ts:6310, two lines aboveViewMetadataParsed.分诊席(
session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T20:19Z。本席读完了卡面(本卡尚无评论)。本席的读数(
origin/mainbeac798026)四处声明逐一复读,与卡面一致:
view.zod.ts:6312ViewMetadataParsed = z.infer<typeof ViewMetadataSchema>;action.zod.ts:2161InlineAction = z.input<typeof InlineActionSchema>;assembled-views.zod.ts:102AssembledViewArtifact = z.input<…>;report.zod.ts:407JoinedReportBlock = z.input<…>。「坍缩成
unknown」这一点是实现轮用编译器 API 普查得到的,本席没有重跑。PR #19919 尚未落地。判定
p3,与 spec: exported type ViewMetadata is unknown (the z.input of a z.preprocess schema), so it type-checks any body while its TSDoc promises a persisted view body #19871 同级。ZodTypeAny强转是为了躲 TS7056 或声明体积的地方,先量代价([finding] AssembledInstalledPackage.manifest erodes to an index-signature type in the published .d.ts, so the assembled arm absorbs the authoring arm #19324 证明这个代价可能是真的)。ViewMetadataParsed还要连带改diagnoseViewMetadata。Blocked-by: #19871(本条第一行,供解锁扫描):PR fix(spec): ViewMetadata names a view body (the union of its members’ input types), not unknown #19919 正在改相邻两行,⛔ 不并入在飞的 PR。report.zod.ts上还排着 [finding] atype: 'joined'report accepts top-leveldataset/rows/columns/values, and the renderer never reads them; the refinement refuses onlyorder#19856(排在 ReportSchema's alias table answersmeasures:/dimensions:/sort:with no suggestion, while the block table in the same file corrects all three — the file says the two are kept parallel #19822 / PR fix(spec): the top-level report alias table routes the ten block spellings it claimed to mirror #19855 之后)。接手时按在飞 PR 的文件清单再排一次。Generated by Claude Code
objectstack-fleet commentedon Sep 25, 2026
Unblock re-derivation:
pm:blocked→pm:queue· 2026-09-25T01:06ZActing 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 bycheck-half-states.mjsH19 on this sweep.completedon 2026-09-24; its PR fix(spec): ViewMetadata names a view body (the union of its members’ input types), not unknown #19919 is merged.5802280140names exactly that condition (one of the four sites shares the file, two lines away).ViewMetadata(spec: exported type ViewMetadata is unknown (the z.input of a z.preprocess schema), so it type-checks any body while its TSDoc promises a persisted view body #19871's type), not the four types here, and it does not name [finding] four more exported spec types resolve tounknownwhile their TSDoc promises a shape —ViewMetadataParsed,InlineAction,AssembledViewArtifact,JoinedReportBlock(the #19871 class, other sites) #19920. No commit onmaindoes either.origin/mainthe four declarations stand as the card lists them —ViewMetadataParsed = z.infer<typeof ViewMetadataSchema>,InlineAction,AssembledViewArtifactandJoinedReportBlockasz.inputof their schemas.State:
pm:blocked→pm:queue;domain:spec,priority:p3unchanged; no assignee.Generated by Claude Code
objectstack-fleet commentedon Sep 27, 2026
Claim: PM loop round 1 · 2026-09-27T15:16Z
Session:
session_01Rjy9MeetSfq34PKn81CRiNAccount:
os-zhuangBranch:
claude/issue-19920-exported-types-unknownWorktree:
objectstack-issue-19920Domain:
domain:specSeat:
domain:spec#1File surface: the four declarations the card names and what they force, with type-level tests and
.changeset/:packages/spec/src/ui/view.zod.ts(ViewMetadataParsedanddiagnoseViewMetadataonly),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(JoinedReportBlockonly). ⛔ NotFormViewSchema.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 hitspackages/spec/src/**, so it is reviewed atCONTRACT_REVIEW_TIERbefore 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).objectstack-fleet commentedon Sep 27, 2026
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
docs(changeset): declare the #19920 type narrowing as BREAKING minor,…
objectstack-fleet commentedon Sep 27, 2026
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
30 remaining items
objectstack-fleet commentedon Sep 28, 2026
Claim: PM loop round 1
Session:
session_01B3TqpoQbTAfG7G74GMDWNWAccount:
hotlong(the seat's linked user asGET /useranswers it; the card's assignee; seated on the maintainer's ruling 「放行,以 hotlong 坐席」, seat post #6017 marker5864561291)Branch:
claude/issue-19920-exported-types-family-closeWorktree:
objectstack-issue-19920Domain:
domain:specSeat:
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):ApiError.code:packages/spec/src/api/error-code-ledger.zod.ts(theErrorCodecast) andpackages/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) andpackages/types/src/response-envelope.ts.optionsbag:listViewKindBlocks()inpackages/spec/src/ui/view.zod.ts(~:5669), only if the measured declaration cost is acceptable (see the dispatch); otherwise measured and reported, not changed.ViewFilterRule.operator(view.zod.ts:923, az.preprocess): ⛔ no change. The dev reports the input-type choice as a fork with options; the seat takes it to triage..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.
tabskey; named presets arelistViewsentries #20357 (spec(ui): retirelist.tabsand the view container's bodyname(2 keys);listViews+ ViewTabBar and the row name already deliver both #20301, seat 2) editsview.zod.tsatListViewShapeSchema(:2769) only; disjoint from:5669) andlistViewKindBlocks(ViewFilterRule(~:923). The later lander resolves.api/error-code-ledger.zod.tsorapi/contract.zod.ts(open-PR file lists read at this claim).681868ca.Amended in place by the same seat:
Clause-②wasyesand is nowno. The at-tier record 5871015216 on PR #20448 reads the diff as widening no accept set and adding no export, so the declaration isno (narrowing), the PR #19919 / #20260 shape for this defect class. The claim’syeswas written before the diff existed.objectstack-fleet commentedon Sep 28, 2026
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
objectstack-fleet commentedon Sep 28, 2026
Review: ACCEPT · PR #20448 at head
4358d1a334· 2026-09-28T13:24Zdomain:specseat 1 (session_01B3TqpoQbTAfG7G74GMDWNW), reviewer of record under claim5868438648. The dev report is on this card (os-dev-report). Readings were taken on GitHub by this seat.main. Line 1 is nowFixes #19920, the only closing keyword; line 2 isClause-②: yes. The seat changed line 1 from the partial marker after filing item 2 as #20450: the order's A4 condition.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.@objectstack/specminor,Clause-②: yes (narrowing), a BREAKING banner, and ADR-0087not-required (no-migration-prescription).@ts-expect-error), and each is restored to the HEAD blob.The dev's open questions, answered by the seat:
ViewFilterRule.operator's input type): filed as spec(ui):ViewFilterRule.operator's input type isunknown(az.preprocess); type it as the canonicalViewFilterOperator, or admit the alias spellings? (the last site of #19920's family) #20450, with the dev's options and measured authors, for triage.Clause-②:value: kept atyes, the claim's value. The claim declaredyesconservatively; declaringyeson a narrowing is never an error, and the changeset'syes (narrowing)carries the arm.any. The PR as a whole shrinks the built declarations by 3,315,656 B (−10.82%), because item 1 is spelled with theErrorCodealias.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:
lookupFiltersis its own closed operator dialect;makeApiErrorSchema's return type prints the catalogue inline 4 times.needs:contract-reviewgoes on PR #20448 in this act, and the isolated at-tier review starts now.docs(changeset): Clause-② is no (narrowing) — the diff widens nothing (…
objectstack-fleet commentedon Sep 28, 2026
Close-out by the
domain:specseat 1 (session_01B3TqpoQbTAfG7G74GMDWNW) at 2026-09-28T15:11Z.Delivered. PR #20448 merged as
cf559147bthrough the merge queue, and this card closedcompletedon itsFixesline. It covers items 1 and 3 of the remainder seat 4's release5865019059named:ApiError.code(and everyBaseResponseerror.code) narrows fromunknowntoErrorCode, and amakeApiErrorSchemaschema's inputcodenarrows to its vocabulary;optionsbag is typed per kind block.No runtime accept set moved. At-tier record
5871627093PASS atb26d6506, after the patch round that corrected theClause-②value tono (narrowing)(5871015216; this card's claim5868438648was amended in place).pm:dispatchedremoved.What remains, and who carries it.
ViewFilterRule.operator's input type, is spec(ui):ViewFilterRule.operator's input type isunknown(az.preprocess); type it as the canonicalViewFilterOperator, or admit the alias spellings? (the last site of #19920's family) #20450 (pm:queue, for triage's pick among the dev's three options).Blocked-by: #20230line is moot: spec(ui): a flattened view overlay acceptsowner/hiddenat the save door and nothing reads either, sohidden: truesaves clean and the view stays listed #20230 is closed and this card is closed.Generated by Claude Code
feat(spec)!: retire the connector resilience family — health (probe +…
fix(spec): type ApiError.code and the list overlay options bag (objec…
feat(spec): ComponentPropsMap rows for action:button/group/menu/icon …
test(spec): the first ui/ file group's test titles state each cited d…
test(spec): the third ui/ file group's test titles state each cited d…
test(spec): the last ui/ file group's test titles state each cited de…
test(spec): the first api/ file group's test titles state each cited …