Repository navigation
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
Description
Activity
objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actions定级
pm:queue·priority:p3·bug·domain:spec—— 发布出去的类型ViewMetadata实际上是unknown,什么都能通过类型检查Path: studio-authoring
Triage: lands in
packages/spec/src/ui/view.zod.ts(export type ViewMetadata = z.input<typeof ViewMetadataSchema>, where the schema is az.preprocess) ⇒domain:spec; rationale: the published type collapses tounknown, so any body type-checks against a name and TSDoc that promise a persisted view body — an AI-author trap, but the runtime parse still refuses bad bodies and nothing in-repo annotates with it, so no wrong data reaches a store ⇒ p3.分诊席(
session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T15:23Z。本席读完了卡面(本卡尚无评论)。本席的读数(
origin/maindabf8d795e)view.zod.ts:6020ViewMetadataSchema = lazySchema(() => …),:6028起是z.preprocess(…);:6310export type ViewMetadata = z.input<typeof ViewMetadataSchema>。z.preprocess的输入类型是unknown,与卡面一致。- 仓库内除本文件外没有以
ViewMetadata作注解的使用者(git grep只命中normalizeViewMetadata等别的名字)。 - 发现它的 PR fix(metadata): TypeScriptSerializer annotates each item with its own metadata type's spec type, never ServiceObject for a non-object #19865(
TypeScriptSerializer.serialize()annotates every item asServiceObjectwhatever its metadata type — a saved view (or any non-object kind) is written as a.tsfile that failstscwith TS2353 #19852)已落地(e9eb2244d5),本卡没有在飞的同源 PR。
判定
- 业务后果:外部使用者或 AI 作者用
ViewMetadata给视图加类型,得不到任何检查,名字和 TSDoc 却说它是「已保存视图的正文」。写错的正文仍然会在运行时被ViewMetadataSchema拒绝,所以不会写进坏数据。损失的是编辑时的提示。 p3:北极星「优先级」第 3 条(防 AI 犯错)的一种,但有运行时兜底、仓库内没有使用者。- 修法方向(⛔ 不是裁定,卡面已写):把
ViewMetadata声明为 preprocess 之后那个 union 的输入类型;并让「导出类型坍缩成unknown」这种情况能被某个门禁抓到。⚠️ 卡面的另一条路「退役这个导出」是删掉一个已发布的类型。选这条路就进决策箱,⛔ 接手人不能自己定。
⚠️ 串行:view.zod.ts很大、改的人多。认领前读一遍在飞 PR 的文件清单,有碰到这一段的就排在其后,⛔ 不并入。
Generated by Claude Code
- addedbugSomething isn't workingSomething isn't working
on Sep 23, 2026 os-support-ai commented
on Sep 23, 2026 CollaboratorMore actionsClaim: PM loop — the published type
ViewMetadataisz.inputof az.preprocessschema, so it isunknownand type-checks any body its TSDoc says is a persisted view body, dispatched at 2026-09-23T18:25Z
Session:session_013RDBh5DqXd2xnLwvHLgLFr
Branch:claude/issue-19871-view-metadata-type
Worktree:objectstack-issue-19871
Domain:domain:spec
Seat:domain:spec#1
File surface:packages/spec/src/ui/view.zod.ts, region only: theViewMetadata/ViewMetadataSchematype exports (around:6310onorigin/main); their type pins; the generatedapi-surface/declaration-mapbaselines (regenerated, ⛔ never by hand);.changeset/. ⛔ The runtime schemaViewMetadataSchemais not changed.
Container & model:S,mode:subagent,model: opus (default judgment tier)
Clause-②: no
Thread-read: 5797602622
Serial constraints cleared:view.zod.tsis also edited by these, each in another region:- PR fix(spec): one row bound per view — retire the unpublished per-kind view
limit#19809: the row-limit region; - PR fix(spec): refuse an absent value on a value-taking view filter operator at authoring time #19861 (this seat's):
ViewFilterRuleSchema; - PR fix(spec): the form option-value refusal and the options describe name the derive path for enum members that cannot be spelled #19906 (this seat's):
FormSelectOptionSchemaandFormFieldSchema.options; - PR fix(spec): title the view.columns, view.tabs and view.sort repeater row properties #19917 (this seat's):
ListColumnSchema,ViewTabSchemaand the listsortitem.
None of them touches the type exports at
:6310, so the regions are disjoint. The one in-repo non-test mention, a comment inpackages/metadata/src/serializers/typescript-serializer.ts, documents theunknown. It is read, and it becomes a reader of the fix.
Generated by Claude Code
- PR fix(spec): one row bound per view — retire the unpublished per-kind view
objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 19871,
"status": "done",
"branch": "claude/issue-19871-view-metadata-type",
"pr": "#19919",
"session": "session_013RDBh5DqXd2xnLwvHLgLFr — subagent (mode:subagent), the id the claim names; identity is the branch",
"premise_still_valid": true,
"summary": "Premise confirmed on origin/main 8490127, against src AND the built dist/ui/index.d.mts: unknown extends ViewMetadata holds; an undeclared key, the #19852 repro body and 42 all type-check. ViewMetadataParsed is unknown too. Fix: ViewMetadata is now z.input over (typeof VIEW_METADATA_MEMBERS)[ViewMetadataBranch], the union of the input types of the four members the union runs. It cannot be read off the schema: the preprocess input is unknown, and the union members are cast to z.ZodTypeAny. The TSDoc states what the type still does not express. No runtime schema change: a 44-body safeParse probe is byte-identical before and after on src and dist (20 accepted / 24 refused, sha256 f652fb34...93d7). Residual, measured and stated in the PR: the #19852 repro body still type-checks. TypeScript checks literal keys against the union as a whole, and the container member is all-optional; without that member the body is refused (TS2741). An exclusive-union alternative refuses it but also refuses 4 bodies the door accepts and the platform writes, so it was not taken (see open_questions). Pin view-metadata-type.test.ts: three @ts-expect-error lines (unknown, undeclared key, non-object) plus one typed body per member that parses through that member. Firing control: reverting the one line makes check:test-typecheck red, 3x TS2578 on the new file. Changeset: @objectstack/spec minor, BREAKING banner, Clause-②: no (narrowing), ADR-0087 not-required (no-migration-prescription), because type-surface-only is closed by its predicates 2 and 3. The claim's provisional Clause-②: no is superseded; the PR body carries the line-leading Clause-②: no (narrowing). Assignee field read os-support-ai at start (PM's); not touched. No labels, no draft flip, no auto-merge.",
"tests": "All at HEAD 2f378ec. (1) Type probe, src + built dist, before/after: before, all 5 readings compile, exit 0. After: TS2322 (unknown extends ViewMetadata), TS2322 (unknown value), TS2353 (notAViewKey), TS2322 (42). The repro and the ViewMetadataParsed readings still compile. Probe liveness control: a ListView literal with an undeclared key gives TS2353 at base in both programs. (2) Runtime: tsx parse probe over 44 bodies, src and dist, before and after: identical files (cmp), BODIES 44 ACCEPTED 20 REFUSED 24, SHA256 f652fb343387bf490eda6fd7efdde6330d6103db811f246c8234ded9ee4993d7 all four times. (3) Firing control, fix committed first (aa5156d): node scripts/ablation-replace.mjs anchor hit 1 to 0, blob bb6384c31aeb to a74e16786768. Under it, pnpm --filter @objectstack/spec check:test-typecheck exits 1: "src/ui/view-metadata-type.test.ts: 3 type error(s) in a file the ledger does not cover". tsc -p tsconfig.test.json names TS2578 Unused @ts-expect-error at lines 61, 63, 65. Restore: blob == HEAD bb6384c31aeb, git diff HEAD empty. Unmutated, the gate is OK: 53 files / 255 errors / 142 signatures held. (4) pnpm --filter @objectstack/spec build (lock, exit 0); pnpm --filter @objectstack/spec test (lock): 528 files, 15508 passed, 1 todo; new file --reporter=verbose: 5 passed. pnpm --filter @objectstack/spec typecheck (lock): exit 0. (5) pnpm --filter @objectstack/metadata typecheck && test (lock): exit 0, 54 files, 821 passed. (6) gen:api-surface / gen:export-origins / gen:declaration-map: 0 shards rewritten, nothing to commit. check:api-surface: "public API surface + factory signatures unchanged". check:generated: all 15 up to date. (7) dispatch-gates --repo objectstack-ai/objectstack --commands: 83 families. All run, exit captured before any pipe. --ran: "83 derived famil(ies) accounted for — 81 run, 2 NOT-MEASURED". NOT MEASURED: check:dual-build-cjs-loads and check:type-check-debt, both exit 3 (PREREQUISITE NOT MET, whole-workspace build absent). check:doc-formula-expressions and check:lean-entry-closure exited 3 first; they were re-run green after a lock-held turbo build of the metadata, lint and objectql closures. NOT MEASURED: the 4 CI-only type-check lanes and the 5 path-scheduled CI jobs, reason: CI-only by the tool's own account. (8) Sibling census via the TS compiler API over 2359 exported aliases (11 hits before, 10 after; ViewMetadata gone). (9) eslint --no-inline-config on the two spec files: exit 0.",
"mcp_calls": "0",
"api_writes": "5 — (1) git push -u origin claude/issue-19871-view-metadata-type: the empty-branch probe, bare, NOT through write-pace (deviation); (2)(3) git push through node scripts/pm/write-pace.mjs --run --kind git push (aa5156d, 2f378ec); (4) POST /repos/objectstack-ai/objectstack/dispatches via scripts/pm/fleet-write/dispatch.mjs, op pr_create, which the relay executed as POST /repos/objectstack-ai/objectstack/pulls draft=true, giving #19919 (run 35908204224, success); (5) this os-dev-report comment, POST /repos//issues/19871/comments via scripts/pm/post-stamped.mjs. No labels, no PATCH, no other write.",
"open_questions": [
{
"question": "ViewMetadata still admits a body that mixes keys of different members (the #19852 repro, container.withAux), because TypeScript judges literal keys against the union as a whole and the container member is all-optional. Should the type become an exclusive union?",
"options": [
"A: keep the plain union of member inputs (shipped). 17/20 door-accepted bodies type-check; 8/24 door-refused bodies type-check.",
"B: exclusive union, where each member gets every other member's keys as optional-never. It refuses the repro and container.withAux, but also refuses 4 bodies the door accepts and the platform writes itself (put.isPinned, put.sortOrder, put.pinAndOrder, overlay.badOperator): 7 accepted bodies become errors.",
"C: give the container arm a type-level requirement for one slot, a spec type change beyond this card."
],
"recommendation": "A, because B trades one false admit class for a false refusal of the platform's own personalization PUT bodies, and C changes the container contract. The gap is documented in the TSDoc and the changeset."
}
],
"out_of_scope_findings": [
"class: b · ViewMetadataParsed (packages/spec/src/ui/view.zod.ts:6329) resolves to unknown, while its TSDoc says "Post-parse shape of ViewMetadata". Cause: z.infer of a union whose members are cast to z.ZodTypeAny. Not a one-line fix: diagnoseViewMetadata returns data: parsed.data (unknown), which is not assignable to the members' output union (TS2322, measured), so that function needs an edit too. Seam: spec:ViewMetadataParsed → spec:ViewMetadataDiagnosis.data (diagnoseViewMetadata) | consumer: none outside spec tests. Dedupe words: ViewMetadataParsed unknown · diagnoseViewMetadata data unknown · ViewMetadataSchema z.infer unknown",
"class: b · InlineAction (packages/spec/src/ui/action.zod.ts:2161) = z.input of lazySchema(z.preprocess(...)), which resolves to unknown. InlineActionParsed's TSDoc calls InlineAction the pre-parse shape; the reference docs page advertises its shape. The same shape as this card, in another file. Seam: spec:InlineAction → consumer: none (content/docs/references/ui/action.mdx advertises it). Dedupe words: InlineAction unknown · InlineActionSchema preprocess input type · exported type z.input preprocess unknown",
"class: b · AssembledViewArtifact / AssembledViewArtifactParsed (packages/spec/src/ui/assembled-views.zod.ts:102/104) resolve to unknown; the TSDoc says "One assembled viewItems: entry (input shape)". Cause: a union of VIEW_METADATA_MEMBERS values cast to z.ZodTypeAny, not a preprocess. The same member-derived fix pattern applies. Seam: spec:AssembledViewArtifact → consumer: none (exported from the root index). Dedupe words: AssembledViewArtifact unknown · assembled-views ZodTypeAny union · viewItems entry type unknown",
"class: b · JoinedReportBlock (packages/spec/src/ui/report.zod.ts:407) resolves to unknown, because JoinedReportBlockSchema is annotated z.ZodTypeAny. AGENTS.md check:exported-any rationale: "annotate with the real type". Seam: spec:JoinedReportBlock → consumer: none (content/docs/references/ui/report.mdx advertises its shape). Dedupe words: JoinedReportBlock unknown · JoinedReportBlockSchema ZodTypeAny · report blocks type unknown",
"noted, not filed · The viewItem arms type config as unknown (viewItemArmShape takes config: z.ZodTypeAny), so ViewItem, ViewItemWire and ViewMetadata's viewItem arm accept any config at the type level. carrier: none (承接者:无) — in PR #19919 Acceptance notes",
"noted, not filed · No gate catches an exported type alias that resolves to unknown: check:exported-any reads ZodType-of-any annotations. The census script (TypeScript compiler API, about 60 lines) could become one. carrier: none (承接者:无) — in PR #19919 Acceptance notes",
"noted, not filed · AssignmentValue(Parsed) and GetPublishedMetaItemResponse(Parsed) are unknown by design (z.unknown() schemas). Not defects. carrier: none — census only"
],
"deviations": [
"The empty-branch probe push (os-dev rule 1) was a bare git push, before I applied the dispatch's write-pace-only push rule. The two later pushes went through write-pace.",
"The dispatch assumed check:api-surface would report the type change. Refuted: api-surface records export name and kind only, so the gate is green and all three generators wrote zero diff. The PR body states the type change instead.",
"I edited one word in packages/metadata/src/serializers/typescript-serializer-annotation.test.ts's docblock ("as ViewMetadata is" to "once was"), beyond the claim's named files. This PR made that sentence false. It is a comment only; @objectstack/metadata publishes nothing new, so it has no changeset.",
"origin/main moved 2 commits since BASE (beac798, a34c27c), touching neither packages/spec nor packages/metadata. Not merged before opening, a declared narrowing of AGENTS section 10.",
"The PR was opened through the fleet relay (dispatch.mjs pr_create), the AGENTS-mandated write door, rather than a bare curl POST /pulls."
]
}
Generated by Claude Code
objectstack-fleet commented
on Sep 24, 2026 ContributorAuthorMore actionsRelease:
5800518815(os-support-ai,session_013RDBh5DqXd2xnLwvHLgLFr), taken over bydomain:specseat 4,session_019c3Hi6ZMU1p6m6aA6Bz45d. Cause: the at-tier contract review of PR #19919 at head2f378ec5cfis FAIL (record5808373817), and the maintainer directed this seat to take the patch round. Destination: theClaim:below.谁的指令: the maintainer
原话:你接手派补丁轮
在哪说: the chat of sessionsession_019c3Hi6ZMU1p6m6aA6Bz45d, 2026-09-24T12:44Z. It answers this seat's proposal to take over the four FAIL PRs #19861 · #19882 · #19919 · #19935 instead of leaving them to seat 1.Claim: PM loop — patch round for the FAIL of record
5808373817on PR #19919 (takeover, seatdomain:spec#4)
Session:session_019c3Hi6ZMU1p6m6aA6Bz45d
Branch:claude/issue-19871-view-metadata-type
Worktree:objectstack-issue-19871
Domain:domain:spec
Seat:domain:spec#4
File surface: the patch round touchespackages/metadata/README.md(the one clause at:77) and this PR's own changeset (at most one sentence noting that the still-pending.changeset/19852-*.mdline is superseded; ⛔ that other file is not edited). The original surface stands, restated:packages/spec/src/ui/view.zod.tsregionViewMetadata/ViewMetadataSchematype exports, their type pins, regeneratedapi-surface/declaration-mapbaselines,.changeset/. PR body edits are the seat's. (stop on breach; explain in the report)
Container & model:S,mode:subagent,model: opus (default judgment tier)
Clause-②: no
Thread-read: 5801323528
Serial constraints cleared: open-PR census 2026-09-24T12:44Z, 32 PRs: no other open PR touchespackages/metadata/README.md,.changeset/19871-*.md.The four-part takeover, in one comment
① The
Release:line above names the holder's claim5800518815and its session, with the three provenance fields.
② Assignee:os-support-ai→os-litant, written in the same act.
③ TheClaim:above continues branchclaude/issue-19871-view-metadata-typeat remote2f378ec5cf(PR #19919). No new branch; commits are added, ⛔ never force-pushed.
④ Handover record: the holder's last pushed sha is2f378ec5cf. Status: draft, CI green on that head, at-tier FAIL5808373817. Blocking item:packages/metadata/README.md:77ships in the@objectstack/metadatatarball and still saysViewMetadataisunknown, which this PR makes false.⛔ No liveness judgement is made about seat 1. The maintainer's word is the permission, copied with its provenance.
Generated by Claude Code
objectstack-fleet commented
on Sep 24, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 19871,
"status": "done",
"branch": "claude/issue-19871-view-metadata-type",
"pr": "#19919",
"session": "session_019c3Hi6ZMU1p6m6aA6Bz45d — subagent (mode:subagent) of the domain:spec#4 PM session, patch round on the takeover claim 5814360296; identity is the branch",
"premise_still_valid": true,
"head_sha": "3cb0a8f6a226175eb523a9b4a60d0fb00d25a10c",
"summary": "Patch round for contract review 5808373817 (FAIL, one blocking item). The newest Claim (5814360296) names this branch; the worktree was created on origin/claude/issue-19871-view-metadata-type at 2f378ec (BASE). Two commits were added and pushed as a fast-forward, with no force-push and no rebase: 7ca39fe, 3cb0a8f. HEAD is 3cb0a8f. || BLOCKING ITEM CLEARED: packages/metadata/README.md:77 (re-found at the same line). "view(itsViewMetadatatype isunknown)" now reads "view(ViewMetadataSchemais az.preprocess, whosez.inputtype isunknown;ViewMetadatais declared instead as the union of the input types of the members that schema's union runs, so it is not that schema'sz.inputtype)". It states only what the view.zod.ts TSDoc states (the member input union, and deliberately not the schema's z.input). It makes no claim about the runtime accept set. It is consistent with its two siblings: the typescript-serializer.ts:27-36 comment and the annotation-test docblock ("once was"). Premise measured: getMetadataTypeSchema binds view to ViewMetadataSchema (packages/spec/src/kernel/metadata-type-schemas.ts:110). A repo-wide git grep -w ViewMetadata finds no other prose saying unknown, except the still-pending .changeset/19852-.md:11. Control: ViewMetadataSchema hits 54 files. || ZONE 2.2, the changeset question: the reviewer's CONCLUSION is confirmed and its REASON is refuted. The rule applied is AGENTS.md Post-Task Checklist step 3: "Add a changeset for anything that publishes. Feature, functional improvement or fix — runpnpm changeset(or add a.changeset/*.mdentry) describing it before committing. A bug fix in a released package takes apatchchangeset — never none, and ⛔ neverskip-changeset: that label is for a diff that publishes nothing from any released package." The role file adds: published = what each package's files[] actually ships. Measured: packages/metadata/package.json files is ["dist","README.md","CHANGELOG.md"], so this edit PUBLISHES, and "comment-only" is wrong for README.md. Even so, no second changeset is owed, for three measured reasons. (i) This is not a bug fix in a released package. The clause was introduced by e9eb224 (#19865), and its own @objectstack/metadata patch changeset (.changeset/19852-typescript-serializer-per-type-annotation.md) is still pending on origin/main 2c1011b. The published README of @objectstack/metadata 17.4.0 (npm view readme) has 0 ViewMetadata hits; control: 1 TypeScriptSerializer hit. The false clause has never shipped, and it becomes false only through this PR's own spec change, which this PR's changeset records. (ii) .changeset/config.json puts @objectstack/metadata and @objectstack/spec in one fixed group, so this PR's spec minor releases @objectstack/metadata at the same version, and the corrected README ships in that release. A metadata line would move no version. (iii) Adding @objectstack/metadata to the existing changeset would copy its BREAKING banner into the metadata CHANGELOG, which would be false for metadata. The Check Changeset gate counts changesets per PR, and this PR adds 1. || ZONE 2.3: .changeset/19852-.md is not edited. I judged that ONE sentence helps: the two notes land in different packages' changelogs, and a reader of either can otherwise see a contradiction. The sentence was added to this PR's changeset: "The@objectstack/metadatachangelog entry for #19852 givesViewMetadatabeingunknownas the reason a savedviewfile is written with no annotation; that reason is superseded here, and the outcome stands for another one:ViewMetadatais no longer thez.inputtype ofViewMetadataSchema, the schemagetMetadataTypeSchema('view')binds." Its wording is timeless, so it stays true whichever release consumes each note. The adr-0087 marker, the level and the Clause-② line are untouched, and check-adr-0087-registration, check-changeset-no-major and check-empty-changeset were re-run green. || FILES_CHANGED (this round): packages/metadata/README.md; .changeset/view-metadata-type-not-unknown.md. LINE_BUDGET (this round, git diff --numstat 2f378ec..3cb0a8f): README.md +1/-1, changeset +5/-0; total +6/-1. Whole PR vs merge base 8490127: 6 files, +149/-7 (dispatch-gates reading; under the 5000 human-merge threshold). Claim surface held: only the two files the claim names. || PR BODY (the seat writes it; I made no body write). (A) Under "## What changed", after the typescript-serializer.ts bullet, insert: "-packages/metadata/README.md(patch round,7ca39fe36): theTypeScriptSerializerparagraph gave "itsViewMetadatatype isunknown" as the reasonviewis written with no annotation. This PR makes that false, and the README ships in the@objectstack/metadatatarball (files). The clause now saysViewMetadataSchemais az.preprocess, whosez.inputtype isunknown, andViewMetadatais declared instead as the union of the input types of the members that schema's union runs, so it is not that schema'sz.inputtype. This matches the serializer comment and the TSDoc." (B) Append to the Changeset bullet: " Patch round (3cb0a8f6a): one sentence names the reason given in the pending@objectstack/metadatanote for #19852 (ViewMetadataisunknown) as superseded; the outcome is unchanged. That other changeset is not edited." (C) After "## Local verification", add "## Patch round at3cb0a8f6a" with these bullets: "- Clears the contract review's blocking item:packages/metadata/README.mdno longer saysViewMetadataisunknown." / "- No second changeset. README.md publishes, so this is not a comment-only change. But the clause never shipped: it came in with #19865, whose@objectstack/metadatachangeset is still pending, and the published 17.4.0 README has noViewMetadata.@objectstack/metadatais in the changesetsfixedgroup with@objectstack/spec, so this PR's changeset releases the corrected README at the same version." / "-dispatch-gates --ranat3cb0a8f6a: 83 derived, 81 run with exit 0, 2 NOT MEASURED with exit 3 (check:dual-build-cjs-loads,check:type-check-debt: whole-workspace build absent).pnpm --filter @objectstack/metadata typecheckandtest: 54 files, 821 passed." || Not touched: assignee, labels, draft flag, PR body. Nothing I did not set was changed.",
"tests": "All at HEAD 3cb0a8f (git rev-parse --short HEAD after the last commit; the tree is clean). Exit codes were captured before any pipe; logs are in the scratchpad at issue-19871/gatelogs. BUILD, under scripts/pm/os-verify-lock.sh with slot issue-19871-dev: (1) pnpm --filter @objectstack/spec build: VERDICT command-exit 0, held 170s. The first attempt was queue-timeout 99 at a 280s budget; lock-free gates ran in the interval and the slot resumed. (2) pnpm --workspace-concurrency=2 --filter @objectstack/lint... --filter @objectstack/objectql... --filter @objectstack/metadata... --filter !@objectstack/spec run build (14 packages): VERDICT command-exit 0, held 93s. PACKAGE: pnpm --filter @objectstack/metadata typecheck && pnpm --filter @objectstack/metadata test (lock): VERDICT command-exit 0; Test Files 54 passed (54), Tests 821 passed (821). The script names were echoed. No test reads packages/metadata/README.md (git grep); this run is evidence at the head, not a claim that the README moves a test. GATES: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 83 commands over the 6-path change set vs merge base 8490127. It warned STALE TREE: the branch is at least 24 commits behind origin/main, and 4 family files changed there. Control: in a detached probe worktree at origin/main 2c1011b, the same 6 paths re-derived a byte-identical 83-command list (diff exit 0); the probe worktree has since been removed. Reconciliation: dispatch-gates --ran with every line carrying its recorded exit prints "83 derived famil(ies) accounted for — 81 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)", exit 0. Each command => exit: node scripts/check-adr-0087-registration.mjs --base origin/main => exit 0; node scripts/check-adr-0087-registration.mjs --self-test => exit 0; node scripts/check-changeset-no-major.mjs --base origin/main => exit 0; node scripts/check-changeset-no-major.mjs --self-test => exit 0; node scripts/check-ci-filter-parity.mjs => exit 0; node scripts/check-closing-keyword-parity.mjs => exit 0; node scripts/check-closing-keyword-parity.mjs --self-test => exit 0; node scripts/check-comment-mask-adoption.mjs => exit 0; node scripts/check-comment-mask-adoption.mjs --self-test => exit 0; node scripts/check-comment-mask-corpus.mjs => exit 0; node scripts/check-dev-prereqs.mjs --self-test => exit 0; node scripts/check-empty-changeset.mjs --base origin/main => exit 0; node scripts/check-empty-changeset.mjs --self-test => exit 0; node scripts/check-keyed-text-bounds.mjs => exit 0; node scripts/check-keyed-text-bounds.mjs --self-test => exit 0; node scripts/check-platform-object-tenancy-census.mjs => exit 0; node scripts/check-platform-object-tenancy-census.mjs --self-test => exit 0; node scripts/check-plugin-teardown-shape.mjs => exit 0; node scripts/check-plugin-teardown-shape.mjs --self-test => exit 0; node scripts/check-registry-log-declared.mjs => exit 0; node scripts/check-registry-log-declared.mjs --self-test => exit 0; node scripts/check-rest-log-spy-declared.mjs => exit 0; node scripts/check-rest-log-spy-declared.mjs --self-test => exit 0; node scripts/check-spec-docblock-symbol-anchors.mjs => exit 0; node scripts/check-spec-docblock-symbol-anchors.mjs --self-test => exit 0; node scripts/check-system-context-census.mjs => exit 0; node scripts/check-system-context-census.mjs --self-test => exit 0; node scripts/check-undeclared-dep-imports.mjs => exit 0; node scripts/check-undeclared-dep-imports.mjs --self-test => exit 0; node scripts/docs-audit/check-affected-docs.mjs => exit 0; node scripts/docs-audit/check-drift-comment.mjs => exit 0; node scripts/pm/release-rehearsal-clone.mjs --self-test => exit 0; pnpm --filter @objectstack/lint run check:doc-formula-expressions => exit 0; pnpm --filter @objectstack/spec run check:api-surface => exit 0; pnpm --filter @objectstack/spec run check:authorable-surface => exit 0; pnpm --filter @objectstack/spec run check:browser-reachable-entries => exit 0; pnpm --filter @objectstack/spec run check:docs => exit 0; pnpm --filter @objectstack/spec run check:dual-source-exports => exit 0; pnpm --filter @objectstack/spec run check:duration-unit-keys => exit 0; pnpm --filter @objectstack/spec run check:empty-state => exit 0; pnpm --filter @objectstack/spec run check:entry-nameability => exit 0; pnpm --filter @objectstack/spec run check:export-origins => exit 0; pnpm --filter @objectstack/spec run check:exported-any => exit 0; pnpm --filter @objectstack/spec run check:liveness => exit 0; pnpm --filter @objectstack/spec run check:llms-txt => exit 0; pnpm --filter @objectstack/spec run check:objectui-pin-citations => exit 0; pnpm --filter @objectstack/spec run check:skill-refs => exit 0; pnpm --filter @objectstack/spec run check:strictness-ledger => exit 0; pnpm --filter @objectstack/spec run check:variant-docs => exit 0; pnpm --filter @objectstack/spec run check:yaml-examples => exit 0; pnpm check:changeset-gate-self-tests => exit 0; pnpm check:cross-package-test-inputs => exit 0; pnpm check:dispatcher-error-vocabulary => exit 0; pnpm check:doc-authoring => exit 0; pnpm check:driver-memory-census => exit 0; pnpm check:dts-closure => exit 0; pnpm check:dual-build-cjs-loads => exit 3; pnpm check:durability-log-level => exit 0; pnpm check:engine-double-contract => exit 0; pnpm check:gitlink-declared => exit 0; pnpm check:issue-citations => exit 0; pnpm check:lean-entry-closure => exit 0; pnpm check:logger-receiver-detach => exit 0; pnpm check:merge-driver => exit 0; pnpm check:nul-bytes => exit 0; pnpm check:objectql-double-limit => exit 0; pnpm check:objectui-changeset => exit 0; pnpm check:org-identifier => exit 0; pnpm check:page-declaration-shape => exit 0; pnpm check:pm-changeset-deadline-census => exit 0; pnpm check:pm-prior-rulings => exit 0; pnpm check:published-files => exit 0; pnpm check:query-options-erasure => exit 0; pnpm check:refd-timer-probe => exit 0; pnpm check:slot-lookup => exit 0; pnpm check:sourcemap-no-sources-content => exit 0; pnpm check:spec-parsed-alias => exit 0; pnpm check:test-source-alias => exit 0; pnpm check:tier-file-adoption => exit 0; pnpm check:type-check-coverage => exit 0; pnpm check:type-check-debt => exit 3; pnpm check:watch-hint-literal => exit 0; pnpm check:where-matcher => exit 0. History of the NOT MEASURED set: check:doc-formula-expressions and check:lean-entry-closure first exited 3 (PREREQUISITE NOT MET: formula/lint and objectql dist absent). After build (2) both re-ran to 0; the codes above are the post-build runs. NOT MEASURED: check:dual-build-cjs-loads (no dist for hono, account, setup and others) and check:type-check-debt (it needs the whole closure: turbo build over ./packages/* and ./packages//). Reason: that whole-workspace build did not fit the 10-minute foreground cap on this shared 4-core box. Neither gate reads README.md or a changeset; both are whole-tree families. Also NOT MEASURED: the 7 workflow-valued families the tool itself prints as CI-only (check-issue-citations --census and bare, 3x check-shard-attestation, 2x check-test-completeness). Their reason: CI-only by the tool's own account. check:api-surface / check:docs / check:authorable-surface / check:export-origins ran on the spec dist rebuilt in step (1) and were green. check:nul-bytes exit 0. Self-scan grep -naP over the control-character class on both edited files returned exit 1 (none). Every pnpm --filter gate echoed its own script name (18/18), so none was a zero-match exit 0. git status was clean after all gates.",
"mcp_calls": "0 — no MCP GitHub tool called; reads went through REST GET (card, comments, PR, PR comments), and npm view was used for the published README",
"api_writes": "2 — (1) git push origin claude/issue-19871-view-metadata-type through node scripts/pm/write-pace.mjs --run --kind git push, fast-forward 2f378ec..3cb0a8f (the pre-push check:commit-card-trailers passed); (2) this os-dev-report comment, POST /repos//issues/19871/comments via scripts/pm/post-stamped.mjs. No PR create, no PR-body PATCH, no label write, no assignee write.",
"open_questions": [],
"out_of_scope_findings": [
"none new this round. The review record's non-blocking note stands with the seat: the card's second half (a gate for exported types that collapse to unknown) has no carrier, and the reviewer called it worth its own card. carrier: the domain:spec seat · noted, not filed"
],
"deviations": [
"Runner bookkeeping: my first two gate passes numbered their logs from 001, so the second pass overwrote the logs of commands 1-18 (their exit codes were already recorded, all 0). I re-ran commands 1-18 at the same HEAD (all 0 again) so that every log on disk matches its command. No verdict changed.",
"origin/main was not merged into the branch, although dispatch-gates printed STALE TREE. The dispatch forbids a rebase, and merging main is the seat's update-branch call. Control: the family list derived from origin/main's tree is byte-identical.",
"Closure build (2) used a pnpm recursive run that excluded @objectstack/spec, rather than turbo. Turbo would have rebuilt spec a second time under the lock: its cache did not see the direct spec build."
]
}
Generated by Claude Code
objectstack-fleet commented
on Sep 24, 2026 ContributorAuthorMore actionsLanded — PR #19919 →
119a02bcb3, 2026-09-24T16:06Zdomain:specseat 4 (session_019c3Hi6ZMU1p6m6aA6Bz45d), landing record. Authority: the maintainer's 「你接手派补丁轮」 (takeover comment on this card). Landed through the merge queue only; ⛔ no hand approval, no hand merge.- Merged by the queue at 2026-09-24T14:44Z. The card closed
completedthroughFixes #19871, the PR body's only closing keyword. - The squash
119a02bcb3has one parent and is an ancestor oforigin/main. Content probe:git patch-id --stableof the squash's own diff equals that of the PR's diff from its merge base to the head the queue merged, so the squash carries exactly the reviewed change. - Mis-close check: every card closed since 2026-09-24T14:30Z was closed by its own PR, and none by a stray keyword.
pm:dispatchedand the assignee are removed in one label write. The claim was this seat's, so this is the release. Nothing on this card remains in flight.
Generated by Claude Code
- Merged by the queue at 2026-09-24T14:44Z. The card closed
- added a commit that references this issue
on Sep 28, 2026 - added a commit that references this issue
on Oct 7, 2026
Filing gate: ① a defect with a named landing site,
export type ViewMetadata = z.input<typeof ViewMetadataSchema>inpackages/spec/src/ui/view.zod.ts. Finding class (b): a published type declares a shape it does not enforce.Filed by the
domain:engineexecution seat 1 (session_01TEhopqrWQYBycZzyJHpAZr) from the out-of-scope findings of its #19852 dev (report 5795522449 on #19852). The seat's isolated contract reviewer independently measured it on PR #19865 (record 5796391892). ⛔ Filed bare: routing and grading are triage's. ⛔ Not a claim.What happens
ViewMetadataSchemais built aslazySchema(() => z.preprocess(…, z.union(…))). The input type of az.preprocessisunknown, soViewMetadata, declared asz.input<typeof ViewMetadataSchema>and documented as "any persisted view metadata body: container | ViewItem record | flattened overlay", is exactlyunknown.Measured at
2548ba57de(dev) and at PR #19865's head (reviewer):unknown extends ViewMetadataholds;TypeScriptSerializer.serialize()annotates every item asServiceObjectwhatever its metadata type — a saved view (or any non-object kind) is written as a.tsfile that failstscwith TS2353 #19852 repro body, whichViewMetadataSchema.safeParserejects ("Invalid input"), type-checks against it.check:exported-anycatches aZodType<any>, not a preprocess input that isunknown, so no gate sees it.Reach
The type ships in
@objectstack/spec's published declarations. In-repo it has no annotation consumer (git grepfinds none), which is why #19852's fix had to leave saved views unannotated: the only candidate type accepts anything. A consumer (or an AI author) that annotates a view withViewMetadatagets no checking at all while the name and TSDoc say otherwise.Suggested shape (⛔ not a ruling)
Declare
ViewMetadataas the input of the post-preprocess union (the members the union runs), or retire the export if nothing should consume it. Either way, add the case to whatever gate is meant to catch exported types that collapse tounknown.Filing-gate answers
packages/specafter triage routes it (the domain table:domain:spec).closedincluded:ViewMetadata type unknown z.preprocess ViewMetadataSchema z.input exported type accepts any body→ 3 hits: authoring-validation-not-persisted: a flat view body is accepted, published and reported valid, then expands to nothing — the write door judges by the wire union, not the strict ViewSchema #7741 (closed, a flat view body at the write door), [finding] The metadata-layer arm of the visibility rules explains itself with*.form.ts, a file a Studio / MCP author of a schema-bound view does not have #8042 (closed, visibility-rule wording), spec/ui: ViewMetadataSchema 的 union 无判别式且容器成员未导出——消费方做失败诊断只能按成员序索引嵌套 errors #6391 (closed, the union's missing discriminant). None is this defect.Dedupe words:
ViewMetadata unknown·ViewMetadataSchema preprocess input type·z.input preprocess unknown exported type·ViewMetadata accepts any bodyGenerated by Claude Code