Repository navigation
finding(plugin-chatbot): per-message avatar / avatarFallback work end to end but no authoring-facing type declares them — #4424's RuntimeOnlyMessageKeys picked only three keys #7295
Description
Activity
- addeddomain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec laneobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lane
on Sep 5, 2026 分诊 —
domain:spec/priority:p3/pm:queue锚定 (anchoring):卡片的读法 1(把两个键加进
@object-ui/types的ChatMessage)落在packages/types⇒domain:spec;读法 2(放宽RuntimeOnlyMessageKeys)落在packages/plugin-chatbot⇒domain:ui。按"谁拥有 authoring contract"锚定domain:spec—— 卡片自己就是这么问的("Someone who owns the authoring contract should pick"),而且ChatbotSchema.messages的类型来自 types,裁决权在那一侧。⚠️ 若裁决走读法 2,这张卡要交接给domain:ui,见下文。在
origin/maina472b07上复核的锚点 —— 全部成立,行号未漂packages/plugin-chatbot/src/index.tsx:173 ? (message.avatar || userAvatarUrl) packages/plugin-chatbot/src/index.tsx:177 ? (message.avatarFallback || userAvatarFallback) packages/plugin-chatbot/src/index.tsx:178 : (message.avatarFallback || assistantAvatarFallback) packages/plugin-chatbot/src/chatMessageAdapter.ts:91 type RuntimeOnlyMessageKeys = Pick< packages/plugin-chatbot/src/chatMessageAdapter.ts:92 RuntimeChatMessage, packages/plugin-chatbot/src/chatMessageAdapter.ts:93 'buildProgress' | 'blueprintProgress' | 'charts' packages/plugin-chatbot/src/chatMessageAdapter.ts:94 >; packages/plugin-chatbot/src/chatMessageAdapter.ts:256 const { role, timestamp, toolInvocations, ...passthrough } = message;三个键、
...passthrough、message.avatar*的读取,全对上。@object-ui/types侧的负结果带正控制:packages/types/src/complex.ts:594 export interface ChatMessage,声明体到:642共 49 行(这是分母),在其中 grepavatar与[key: string]—— 0 命中。所以"没有 avatar 键、也没有 index signature,注解后必然TS2353"是实测的缺席,不是读错文件。定级理由
priority:p3:运行期无损,卡片自己写清楚了("the keys work today")。代价是类型面 + 一个门禁覆盖洞(PR #7294 在plugin-chatbot.mdx上故意留了三块示例不加注解,5 处TS2353都是同一对键)。没有静默错答,没有安全面,有明确绕法(不加注解 /as any)。定型:两条读法都不是 Feature,不踩 manual floor
用机械边界测试逐条过:
- 读法 1 = 把一对已经被消费、已经被文档教授的键补进声明 ⇒ 恢复 declared = enforced ⇒ Bug/tidy。它不加宽可授权面:作者今天就能写、就能渲染出来,只是类型骗他说不行。
- 读法 2 = 把同两个键补进 seam 词表 ⇒ 同理,也是恢复。
所以不需要人工底线,
domain:spec席可以直接裁并落地。⚠️ 但两者都动已发布的 TS 类型,必须带 changeset。⛔ 给执行席的边界
- 卡片对读法 2 的自我批评要当真、不要绕过:
RuntimeOnlyMessageKeys放宽了,并不能帮到写ChatbotSchema.messages的作者 —— 他从头到尾不会写出SeamChatMessage这个名字。单做读法 2 不关闭上面那个门禁洞。若裁决选它,必须同时说明门禁洞怎么办(否则这张卡不算做完)。 - ⛔ 不要用给
ChatMessage加 index signature 的办法收工 —— 那正是 finding(types): BaseSchema's[key: string]: anyleaves every component schema open, so a "declare the surface" fix can never reject a misspelled TOP-LEVEL key #5155 记录的、让"声明表面"永远无法拒绝拼错键的那个形状,是在这张卡上还债的同时新欠一笔。 - 落地后回
content/docs/plugins/plugin-chatbot.mdx把 PR docs(guides,plugins,tooling): clear three .mdx pages off the doc-snippet ledger (#5174 batch 5) #7294 留下的三块示例补上注解,并从UNGATED_DOCS里销账 —— 这是本卡的完成判据之一,不是可选尾巴。 - 若裁决是读法 2,请把
domain:spec改成domain:ui并说明理由,别让 lane 与落点脱节。
⛔ 分诊席不认领、不派单、不写码、不裁决读法。
Generated by Claude Code
Claim: PM loop round R1 (third wave)
Session:session_01BAZFhALsQsGqxui8sNqM8s
Branch:claude/issue-7295-chat-message-avatar-keys
Worktree:objectui-issue-7295
Domain:domain:spec
File surface (region level):packages/types/src/complex.ts— theChatMessageinterface only (:594–642on446d93d) ·packages/types/src/zod/complex.zod.ts— theChatMessageSchemaobject only (:360…) · one pin underpackages/types/src/__tests__/·content/docs/plugins/plugin-chatbot.mdx— annotate the three example blocks PR #7294 left unannotated (no prose rewrite) ·scripts/check-doc-snippet-types.mjs— only if anUNGATED_DOCSrow for that page still exists (one row removal; a triage-declared completion criterion, comment 5548376176, so the cross-domain touch is declared here) ·.changeset/*(@object-ui/types) · ⛔packages/plugin-chatbot/**read-only (RuntimeOnlyMessageKeysuntouched; reading 2 rejected as incomplete by triage's own boundary 1) · ⛔ no index signature onChatMessage(triage boundary 2, #5155)
Container & model:S,mode:subagent,model: fable=CONTRACT_REVIEW_TIER(two new declared keys on a published payload type)
Clause-②: yes
Serial constraints cleared (file / region granularity per #5734 comment 5551274423):complex.ts/complex.zod.tsare held by no in-flight branch (#5741:packages/core/src/evaluator/**; #6751: one fixture; PR #7726 queued for landing:base.ts/base.zod.ts/form.zod.tstop /expression.*; PR #7723 queued:form.ts/form.zod.tsCheckboxSchema) ·plugin-chatbot.mdxuntouched by every open branch · the dev mergesorigin/mainbefore opening the PR and again if a sibling lands; the queue arbitrates.Premise re-verified by this seat on
origin/main=446d93d(12:22Z):packages/types/src/complex.ts:594 export interface ChatMessagewith zeroavatar/ index-signature hits inside it;zod/complex.zod.ts:360 export const ChatMessageSchema = z.object({with noavatarmember;packages/plugin-chatbot/src/index.tsx:173–178readsmessage.avatar/message.avatarFallback;chatMessageAdapter.ts:91–94RuntimeOnlyMessageKeys='buildProgress' | 'blueprintProgress' | 'charts',:256spreads...passthrough. Last commits on the two type files:4ce14f1(#7655), neither touched theChatMessageblock.Disposition — reading 1, under a declared veto window (⛔ not a maintainer ruling). Triage (5548376176) graded both readings as Bug-side restoration of
declared = enforced, outside the manual floor, and delegated the choice to this seat ("domain:spec席可以直接裁并落地"); its boundary 1 records that reading 2 alone does not close the doc-gate hole, so reading 1 is the only complete fix: declareavatar?: stringandavatarFallback?: stringonChatMessageon both faces, with docblocks citing the read sites. Reversible by a two-line re-declaration if vetoed. Completion criteria carried from triage: the threeplugin-chatbot.mdxexample blocks get theirChatbotSchemaannotations back and the doc-snippet gate stays green on them; no index signature; changeset present.
Generated by Claude Code
Claim (dev seat): implementing under the
domain:specseat's dispatch (PM claim comment 5551795779, sessionsession_01BAZFhALsQsGqxui8sNqM8s, PM loop round R1 third wave).Branch:
claude/issue-7295-chat-message-avatar-keys(pushed empty first as the write-route probe; base446d93d)
Worktree:objectui-issue-7295Disposition executed as dispatched — reading 1:
avatar?: stringandavatarFallback?: stringdeclared onChatMessageinpackages/types/src/complex.tsand onChatMessageSchemainpackages/types/src/zod/complex.zod.ts, each docblock citing the read site (packages/plugin-chatbot/src/index.tsx:173–178). No index signature.packages/plugin-chatbot/**stays read-only (RuntimeOnlyMessageKeysuntouched;SeamChatMessageinheritance verified by that package's type-check against the rebuilt types dist). The threecontent/docs/plugins/plugin-chatbot.mdxexample blocks PR #7294 left unannotated (supportChat,salesBot,multiAgentChat) get theirChatbotSchemaannotation back. NoUNGATED_DOCSrow for that page exists onmain— PR #7294 already removed it — so nothing underscripts/is touched.Zone-2 "before" measured on
446d93d(builtpackages/types/dist):ChatMessageSchemais a plain strip-modez.objectwith no catchall. An authored per-messageavatar/avatarFallbackparses green throughChatMessageSchema,ChatbotSchemaandsafeValidateSchemaand is DROPPED from the parsed output on all three paths;avatar: 42is admitted-and-stripped, never refused. The runtime honours the key only because the renderer receives the un-parsed document. Declaring the keys makes the parse keep them and refuse a wrong-typed value at the key — recorded in the PR body as a measured behaviour change (a restoration: the renderer already honours the key). The TS-face "before" reproduces the card's count exactly: the three blocks annotatedChatbotSchemaagainst the base dist give 5 x TS2353 onavatarFallbackand nothing else. Both faces ofChatbotSchema's own avatar config keys arestring, and the runtimeChatMessageinChatbotEnhanced.tsxdeclares both per-message keys asstring, so the declared type is not narrower than what the runtime accepts.Assignee untouched.
Generated by Claude Code
os-dev-report
{ "issue": 7295, "status": "done", "branch": "claude/issue-7295-chat-message-avatar-keys", "pr": "https://github.com/objectstack-ai/objectui/pull/7732", "premise_still_valid": true, "clause_2": "yes — two new declared keys on a published payload type (ChatMessage / ChatMessageSchema); needs:contract-review attached, PR stays draft for the in-seat review", "line_budget": "n/a", "summary": "Reading 1 executed as dispatched: avatar?: string and avatarFallback?: string declared on ChatMessage (packages/types/src/complex.ts) and on ChatMessageSchema (packages/types/src/zod/complex.zod.ts), each docblock citing packages/plugin-chatbot/src/index.tsx:173-178; no index signature; packages/plugin-chatbot/** untouched, SeamChatMessage inheritance proven by type-check plus a positive scratch probe against the rebuilt dist (red against the ablated dist). Premise re-verified on 446d93d: zero avatar / index-signature hits inside ChatMessage on either face, read sites and the seam spread at :256 present. Zone-2 measured before: ChatMessageSchema is a plain strip-mode z.object (no catchall) — an authored per-message avatar parses green through ChatMessageSchema, ChatbotSchema and safeValidateSchema and is DROPPED from the parsed output on all three paths; avatar: 42 was admitted-and-stripped. After: the parse keeps the value and refuses a non-string at the key (messages.0.avatar through the entry point) — recorded in the PR body and the changeset as a measured behaviour change, still a restoration; patch level with the argument stated (objectui#6938 / #6150 precedent). Value type string matches ChatbotSchema's own userAvatarUrl / userAvatarFallback (pinned by invariant equality) and the runtime ChatbotEnhanced.tsx:92-93 declaration. Docs: the three plugin-chatbot.mdx example blocks PR #7294 left unannotated (supportChat, salesBot, multiAgentChat; measured from that PR's landed diff 9bf0abfe) are annotated ChatbotSchema again, no prose rewrite; no UNGATED_DOCS row for that page exists on main (PR #7294 removed it), so scripts/ untouched. origin/main merged before the PR (b74a859, PR #7723 landed after dispatch; clean merge, diff vs main is exactly the five files). Assignee untouched; claim comment 5551863889.", "files_changed": [ "packages/types/src/complex.ts", "packages/types/src/zod/complex.zod.ts", "packages/types/src/__tests__/chat-message-avatar-keys-7295.test.ts", "content/docs/plugins/plugin-chatbot.mdx", ".changeset/7295-chat-message-avatar-keys.md" ], "gates": [ { "command": "pnpm --filter @object-ui/types build", "exit": 0, "head": "4b0f366c", "verdict": "dist completeness: 1 package(s) complete (120 emitted files verified); dist/complex.d.ts carries avatar?: string (count 1)" }, { "command": "pnpm exec vitest run --maxWorkers=2 packages/types/", "exit": 0, "head": "4b0f366c", "verdict": "Test Files 115 passed (115) · Tests 1985 passed (1985); pin alone 25/25" }, { "command": "pnpm --filter @object-ui/types type-check", "exit": 0, "head": "4b0f366c", "verdict": "tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json — 0 diagnostics; --listFiles on tsconfig.test.json lists the pin (count 1)" }, { "command": "pnpm --filter @object-ui/plugin-chatbot type-check (against rebuilt types dist)", "exit": 0, "head": "4b0f366c", "verdict": "tsc --noEmit && tsc -p tsconfig.test.json — 0 diagnostics" }, { "command": "scratch tsc probe: Equal of NonNullable SeamChatMessage avatar / avatarFallback with string + literal carrying both", "exit": 0, "head": "4b0f366c", "verdict": "0 diagnostics; same probe against the ABLATED dist: exit 2, TS2339 on SeamChatMessage avatar and avatarFallback, TS2353 on the literal" }, { "command": "pnpm exec vitest run --maxWorkers=2 packages/plugin-chatbot/", "exit": 0, "head": "63ece2c8", "verdict": "Test Files 37 passed (37) · Tests 456 passed (456) — run before the main merge; the merge (PR #7723: form.ts, form.zod.ts, one test, one changeset) touches no plugin-chatbot input" }, { "command": "node scripts/check-changeset-presence.mjs", "exit": 0, "head": "4b0f366c", "verdict": "3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/7295-chat-message-avatar-keys.md" }, { "command": "node scripts/check-changeset-fixed.mjs", "exit": 0, "head": "4b0f366c", "verdict": "All workspace packages are in the changeset fixed group" }, { "command": "node scripts/check-changeset-no-major.mjs", "exit": 0, "head": "4b0f366c", "verdict": "No changeset declares a major bump" }, { "command": "node scripts/check-control-bytes.mjs (re-run after the merge commit)", "exit": 0, "head": "4b0f366c", "verdict": "OK (scanned 6300 tracked text file(s); skipped 85 binary)" }, { "command": "node scripts/check-spec-symbol-derivation.mjs (pnpm check:spec-symbols)", "exit": 0, "head": "63ece2c8 content", "verdict": "spec member citations: no comment cites a key its spec symbol does not declare" }, { "command": "node scripts/check-doc-component-types.mjs (pnpm check:doc-types)", "exit": 0, "head": "4b0f366c", "verdict": "Every documented component type is registered" }, { "command": "node scripts/check-doc-fence-languages.mjs (pnpm check:doc-fences)", "exit": 0, "head": "4b0f366c", "verdict": "every TypeScript block in 227 document(s) is fenced ts/tsx/typescript (declared exceptions unchanged)" }, { "command": "node scripts/check-doc-links.mjs", "exit": 0, "head": "63ece2c8 content", "verdict": "Links are valid across 17 scan roots" }, { "command": "node /home/user/objectstack/scripts/pm/check-governed-merges.mjs --test (five paths)", "exit": 0, "head": "file list unchanged by the merge", "verdict": "0 of 5 path(s) hit the register — NOT governed" }, { "command": "pnpm check:doc-snippets", "exit": "NOT MEASURED", "head": "4b0f366c", "verdict": "--build-filter names the whole plugin + app-shell closure (26 packages), beyond the foreground cap on a shared box. Narrowed measurement declared: the three annotated blocks extracted from the mdx as on disk, tsc --strict against the rebuilt packages/types/dist → exit 0, 0 diagnostics (before, base dist: exit 2, exactly 5 x TS2353 on avatarFallback). CI Doc Snippet Type Check is the measurement." }, { "command": "CI on PR #7732", "exit": "in_progress", "head": "4b0f366c", "verdict": "report delivered at draft-PR time per contract; not waited on" } ], "tests": "Pin packages/types/src/__tests__/chat-message-avatar-keys-7295.test.ts (form of checkbox-wrapper-class-6938 / undeclared-but-consumed-keys-6150, adapted for a STRIP-mode mirror and a TS face with NO index signature): 25 tests — read-site text and message.KEY read set derived off index.tsx (both keys present, control avatarUrl absent), seam spread text, shape membership, value survives through ChatMessageSchema / ChatbotSchema / safeValidateSchema, refusal at the key and at messages.0.KEY through the entry point, invariant type-level pins (string, optional, not any, equal to ChatbotSchema userAvatarUrl / userAvatarFallback, string extends keyof ChatMessage is false, control key not in keyof, @ts-expect-error control literal), control key absent from shape and still admitted-and-stripped, and the three doc blocks' annotations with their five per-message avatarFallback overrides. Ablation (fix committed first at 63ece2c8; trap-guarded, absolute paths, under the verify lock): both faces checked out from 446d93d — mutation proven by blob hash (fd59df10 / b1cd5ac0 = BASE blobs) and marker counts 1/1 → 0/0; pin 12 failed | 13 passed (25): red = membership x2, survive x6, refusal x4; green = read-site, read-set, seam, control-key, doc legs; tsc -p tsconfig.test.json exit 2 with 13 diagnostics all inside the pin file (TS2339 / TS2344 / TS2353), 0 elsewhere; types dist rebuilt from the mutated source (marker 0) turned the seam probe red (exit 2). Restore git checkout HEAD on both paths: blobs e3bb6a4e / 08ab25c4 = HEAD blobs, markers 1/1, git diff HEAD empty, git status empty; dist rebuilt from the restored source (marker 1). Before-state measurements through the base dist: ChatMessageSchema / ChatbotSchema / safeValidateSchema all success with messages[0] keys id, role, content only (avatar dropped); avatar: 42 success; control avatarUrl: 42 success and stripped.", "mcp_calls": "7 — add_issue_comment (claim), create_pull_request, pull_request_read get (labels + body read-back), issue_write update (label union), pull_request_read get (label read-back), add_issue_comment (this report), issue_read get_comments (this report's read-back)", "deviations": [ "REST reads are 403 from this seat too (measured on /repos/objectstack-ai/objectui/issues/7295: GitHub access is not enabled for this session); issue #7295 body and both comments, #4424 and #5155 were read through the public issue page's embedded JSON payload (front timeline hasNextPage false for #7295); PR #7294's diff read from git after deepening the shallow clone by 400 (commit 9bf0abfe); PR #7723's changeset read from a git scratch ref (refs/scratch/7295/6938, dropped afterwards) — it had already landed on main as b74a859.", "needs:contract-review attached via MCP issue_write update with the union of the auto-labeler's set (package: types, tests) — read back equal to the union, nothing stripped.", "pnpm check:doc-snippets NOT MEASURED locally (26-package build closure); narrowed tsc measurement of the three blocks declared above. CI Doc Snippet Type Check is the measurement.", "vitest packages/plugin-chatbot/ was run on 63ece2c8 (pre-merge); not re-run on 4b0f366c because the merge touches no plugin-chatbot input (form.ts / form.zod.ts / a types test / a changeset). types build, types suite and both type-checks WERE re-run on 4b0f366c under the lock (VERDICT command-exit 0).", "PR body footer: written in the harness-instructed form (Generated with Claude Code line + session URL); create_pull_request appended its own session-URL _Generated by_ block after it (read back verbatim, nothing eaten). No PATCH edit was made to the body.", "The pin reads content/docs/plugins/plugin-chatbot.mdx off disk from the types package (annotation + five-override count) — precedent in that directory reads docs the same way; it keeps triage's completion criterion pinned.", "The mdx Per-message Avatars block still imports ChatMessage from @object-ui/plugin-chatbot (PR #7294's choice when the types face lacked the keys); it is not one of the three blocks and was left as-is — handed back below rather than filed." ], "open_questions": [ { "question": "Should the Per-message Avatars block on plugin-chatbot.mdx now import ChatMessage from @object-ui/types (the authoring face, which declares the keys after this PR) instead of @object-ui/plugin-chatbot (the runtime shape)?", "options": ["A — leave it (runtime import is correct too; not one of the three blocks the card names)", "B — one-line import swap as a rider on PR #7732 during contract review"], "recommendation": "A for this PR; B only if the reviewer wants the page to teach the authoring face uniformly — a one-line doc change, no contract movement." } ], "out_of_scope_findings": [] }Worktree removed (node_modules deleted, git worktree remove exit 0); shared checkout untouched. Placeholders spelled as uppercase words because the body sanitizer eats angle-bracket shapes.
Generated by Claude Code
Landing paused — PR #7732 red on head
4b0f366c(reading 2026-09-05T12:58Z, sessionsession_01BAZFhALsQsGqxui8sNqM8s):Lintcompleted: failure(job 101309392962) —@object-ui/types#lintexits 1 on exactly one error, in the PR's own pin:packages/types/src/__tests__/chat-message-avatar-keys-7295.test.ts:118:41 @typescript-eslint/no-empty-object-type(the{}type); the 268 warnings are pre-existingno-explicit-any.Doc Snippet Type Checkis green on this head (the three re-annotatedplugin-chatbot.mdxblocks compile — triage's completion criterion). A real failure in code the PR adds, not a flake; no re-run.The contract increment was read by ref against
origin/main(complex.tsChatMessageblock +2 optional string members with read-site docblocks;complex.zod.tsChatMessageSchema+2z.string().optional(); changeset@object-ui/types: patchwith the measured strip-mode behaviour change stated; docs = threeChatbotSchemaannotations only; not governed on all 5 paths) — the in-seat verdict at tier is deferred to the patched head so the contract increment and the delta are judged once.needs:contract-reviewstays on the PR. Patch round 1 routed to the dev (SendMessage, 12:59Z): fix the type at:118, run the PACKAGE lint (0 errors), types suite + type-check, mergeorigin/main(a00db9ef), push, report. Review re-check armed.
Generated by Claude Code
os-dev-report
{ "issue": 7295, "patch_round": 1, "status": "done", "branch": "claude/issue-7295-chat-message-avatar-keys", "pr": "https://github.com/objectstack-ai/objectui/pull/7732", "head": "fdd09f70", "premise_still_valid": true, "clause_2": "yes — unchanged; needs:contract-review left on the PR, the seat reviews the contract increment and this delta together on fdd09f70", "line_budget": "n/a", "summary": "Patch round 1 from the PM seat (2026-09-05T12:59Z): CI Lint was red on 4b0f366c with exactly one error — @typescript-eslint/no-empty-object-type at chat-message-avatar-keys-7295.test.ts:118. Fixed in one commit (fdd09f70): the IsOptional helper now spells the empty object as Record of string to never (an object with no keys is assignable to the pick only when the key is optional), with the docblock line above it (117) re-worded to match; the rule is not disabled and no other line moved. origin/main merged first (a00db9ef, PR #7729: one fixture, one test, one changeset — no overlap; clean merge 58f5f85b). Package lint back to 0 errors (268 pre-existing warnings untouched); the pin's type-level legs still compile; a scratch tsc probe confirms the helper still discriminates (true for avatar / avatarFallback, false for the required id / content). Pushed; gate table for the new head posted as PR comment 5551996152 (body not edited). Diff against origin/main is still exactly the five files.", "files_changed": [ "packages/types/src/complex.ts", "packages/types/src/zod/complex.zod.ts", "packages/types/src/__tests__/chat-message-avatar-keys-7295.test.ts", "content/docs/plugins/plugin-chatbot.mdx", ".changeset/7295-chat-message-avatar-keys.md" ], "files_changed_this_round": [ "packages/types/src/__tests__/chat-message-avatar-keys-7295.test.ts" ], "gates": [ { "command": "pnpm --filter @object-ui/types lint", "exit": 0, "head": "fdd09f70 (content, run pre-commit)", "verdict": "268 problems (0 errors, 268 warnings) — the one error gone, warnings pre-existing and untouched" }, { "command": "pnpm --filter @object-ui/types type-check", "exit": 0, "head": "fdd09f70 (content, run pre-commit)", "verdict": "tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json — 0 diagnostics" }, { "command": "pnpm exec vitest run --maxWorkers=2 packages/types/ (verify lock, VERDICT command-exit 0)", "exit": 0, "head": "fdd09f70", "verdict": "Test Files 116 passed (116) · Tests 1991 passed (1991)" }, { "command": "node scripts/check-control-bytes.mjs (after staging)", "exit": 0, "head": "fdd09f70 (content)", "verdict": "OK (scanned 6302 tracked text file(s); skipped 85 binary)" }, { "command": "scratch tsc --strict probe of IsOptional over ChatMessage", "exit": 0, "head": "fdd09f70 (content)", "verdict": "true for avatar / avatarFallback, false for id / content — 0 diagnostics" }, { "command": "CI on PR #7732", "exit": "in_progress", "head": "fdd09f70", "verdict": "not waited on, per contract" } ], "not_rerun_this_round": "types build, plugin-chatbot type-check and suite, changeset gates, doc gates, governed-surface test — inputs unchanged since the 4b0f366c table in the PR body (file list unchanged: 0 of 5 paths governed)", "tests": "Same pin, same 25 tests, same ablation evidence as the round-0 report (comment 5551953202); this round changed only the spelling of one type-level helper, verified by the package type-check plus the probe above.", "mcp_calls": "11 — 7 in round 0 (see comment 5551953202) + this round: add_issue_comment (PR gate table), add_issue_comment (this report), issue_read get_comments on the PR (gate-table read-back), issue_read get_comments on #7295 (this report's read-back)", "deviations": [ "The fix touched lines 117-118 (the helper and the docblock sentence directly above it), not line 118 alone — the comment named the empty-object idiom and would otherwise have described the old spelling.", "Lint, type-check, control-bytes and the probe ran on the working tree immediately before the commit; the committed content is byte-identical (git status clean after commit). The suite ran on the committed head under the lock.", "No lint entry was in the round-0 gate table: the dispatch's gate list did not name the package lint and I did not derive it from the repo's Lint workflow — recorded here so the omission is visible; the round-0 table's other entries stand." ], "open_questions": [], "out_of_scope_findings": [] }Worktree removed again after this round (node_modules deleted, git worktree remove); shared checkout untouched. Placeholders spelled as words because the body sanitizer eats angle-bracket shapes.
Generated by Claude Code
Contract review at tier — PASS on PR #7732 head
fdd09f70(reading 2026-09-05T13:05Z)Implemented-by: dev subagent of
session_01BAZFhALsQsGqxui8sNqM8s,mode:subagent, branchclaude/issue-7295-chat-message-avatar-keys(claim 5551863889; reports 5551953202 and 5551998296)
Reviewed-by:session_01BAZFhALsQsGqxui8sNqM8s(domain:specexecution seat); served tier read at 13:03Z viaget_session:session_context.model=last_served_model=claude-fable-5-1=CONTRACT_REVIEW_TIER; no fallback evidence in this round.Judged on the PR diff against
main=a00db9ef(five files, 410/6) — the contract increment and the round-1 delta together, as the paused note (5551976750) said they would be.① Derived judgments, per item
- TS face,
packages/types/src/complex.tsChatMessage:avatar?: string,avatarFallback?: string— CORRECT. Both are read atpackages/plugin-chatbot/src/index.tsx:173–178in a||fold with the chatbot-leveluserAvatarUrl/userAvatarFallback(bothstring), and the runtime shape atChatbotEnhanced.tsx:92–93declares themstring; optional is right (the fold falls back). Public surface: two additive optional members on an exported interface; no index signature added (triage boundary 2, objectui#5155) and pinned type-level (string extends keyof ChatMessageis false). - zod face,
packages/types/src/zod/complex.zod.tsChatMessageSchema: twoz.string().optional()— CORRECT. Strip mode is preserved (control keyavatarUrlstill admitted-and-stripped, pinned). Accept-set movement as measured on446d93dand re-derived from the diff: (a) an authored string at either key now SURVIVESChatMessageSchema/ChatbotSchema/safeValidateSchemawhere it was dropped before — widening toward what the renderer already does; (b) a non-string at either key is now REFUSED (messages.0.avatarthrough the entry point) where it was admitted-and-stripped — enforcement of the declared type. Same verdict shape as objectui#6938 (wrapperClass) and the objectui#6150 batch. packages/plugin-chatbot/**untouched;SeamChatMessageinherits through itsChatMessagehalf, shown by that package's type-check against the rebuilt dist and by the positive/ablated scratch probe (round-0 report). CORRECT — reading 2 was rejected by triage's own boundary 1, and nothing here re-opens it.- Docs: the three
plugin-chatbot.mdxblocks PR docs(guides,plugins,tooling): clear three .mdx pages off the doc-snippet ledger (#5174 batch 5) #7294 left unannotated are annotatedChatbotSchema(import line, annotation, semicolon; no prose change).Doc Snippet Type Checkiscompleted: successonfdd09f70(job 101310586371, 13:04Z), so triage's completion criterion is measured, not asserted. NoUNGATED_DOCSrow existed onmainfor the page;scripts/untouched. CORRECT. - Pin
chat-message-avatar-keys-7295.test.ts: 25 tests; ablation of both faces reds membership x2, survive x6, refusal x4 and leaves the control legs green. Round-1 delta (IsOptionalhelper spelledRecord<string, never>, docblock line above re-worded; nothing else moved) keeps the discriminator (probe: true for the two keys, false forid/content) and clears the oneno-empty-object-typeerror; package lint 0 errors. - Describe strings citing the read-site line and the issue number: house style on
main(26 such describes underpackages/types/src/zod,form.zod.ts:264forwrapperClassamong them). Not a finding.
② Semver
@object-ui/types: patch— consistent with the precedent already onmain(.changeset/6938-checkbox-wrapper-class.md,.changeset/6150-undeclared-but-consumed-keys.md) for exactly this verdict shape. Wording note, non-blocking: the bolded sentence "no document that validated before stops validating" over-claims — a document withavatar: 42validated before (admitted-and-stripped) and now refuses; the same paragraph discloses that refusal two bullets later, and the identical wording sits in the #6938 changeset onmain. Left as is for consistency; recorded here for the central release-note writer, who reads the whole changeset.③ Flags and open questions
- Open question (Per-message Avatars block importing
ChatMessagefrom@object-ui/plugin-chatbot): A — leave it. The runtime import is correct for that block, it is not one of the three blocks the card names, and a rider would be doc churn with no contract movement. - Round-0 deviation "no lint entry in the gate table": acknowledged; the Lint gate is in the round-1 table and CI
Lintis the measurement on this head. pnpm check:doc-snippetsNOT MEASURED locally: CIDoc Snippet Type Checkgreen on this head is the measurement.
Verdict: PASS. Clearing
needs:contract-reviewon PR #7732 in the same round with a provenance comment citing this PASS; this card never carried the label. Landing then follows the pre-landing three when every check run onfdd09f70iscompleted: success(skipped allowed).
Generated by Claude Code
- TS face,
Landing note — PR #7732 enqueued (2026-09-05T13:20Z)
Pre-landing three:
- In-seat contract-tier PASS on record: comment 5552020607 on this card, head
fdd09f70, carrying theImplemented-by:/Reviewed-by:pair and the three-item judgment (served tier read at 13:03Z). - Both carriers cleared: this card never carried
needs:contract-review; the PR-side label was cleared with provenance comment 5552022295 and read back at 13:06Z (package: types,tests).node scripts/pm/check-clause2-carriers.mjs --pair 7732exits 3 from this seat (REST session-gated, seat post §4) — the three limbs were verified by hand instead. - Check runs on
fdd09f70: 29completed: success+ 3skipped, none failed, none pending (full farm read by name at 13:19Z;Lintgreen after patch round 1,Doc Snippet Type Checkgreen — triage's completion criterion on the threeplugin-chatbot.mdxblocks).
Landing actions:
draft:falseat 13:19Z.- Auto-merge enabled with
mergeMethod: SQUASHexplicitly at 13:20Z (tool echoes empty method/time — known reading, seat post §4). - Positive probe at 13:20Z:
git ls-remote origin 'refs/heads/gh-readonly-queue/main/*'showsrefs/heads/gh-readonly-queue/main/pr-7732-3bde19ba07276e5eb963a27bb13dbd05a1990c9dat858cd72e, queued behind PR test(types): derive the zod-mirror-parity population instead of writing it down #7731 (objectui#7433);main=9587fc95.
Veto window: this card's reading 1 was a seat disposition under a declared veto window (claim 5551795779), not a maintainer ruling; the landing keeps that window open — a two-line re-declaration reverses it.
Next reading: MERGED by two readings (PR
merged: true/ this card'sclosed_by_pull_requestsstate MERGED, andorigin/maincontaining the squash), then strippm:dispatchedhere. The stalefindinglabel is triage's to drop.
Generated by Claude Code
- In-seat contract-tier PASS on record: comment 5552020607 on this card, head
MERGED — two readings (2026-09-05T15:25Z, session
session_01BAZFhALsQsGqxui8sNqM8s): (1) PR #7732 readsmerged: true,merged_at13:35:51Z, and this card'sclosed_by_pull_requestsnames PR #7732 in state MERGED (card closed 13:35:52Z,state_reason: completed); (2)origin/maincontains the squash858cd72e("fix(types): declareavatarandavatarFallbackonChatMessage, on both faces (objectui#7295) (#7732)"),maintip83c77dc3at 15:23Z. Landed headfdd09f70; reading 1 executed under the declared veto window (claim 5551795779) and PASSED at tier (5552020607) — the window stays open, a two-line re-declaration reverses it.pm:dispatchedstripped in this round;finding,priority:p3,domain:specstay (findingis triage's to drop). The reading was delayed from its 13:39Z timer by the account usage-window reset (15:20Z); no state moved in between.
Generated by Claude Code
Found while covering
content/docs/plugins/plugin-chatbot.mdxfor #5174 batch 5 (PR #7294). Filed unassigned and not fixed there — that PR's surface is theUNGATED_DOCSledger plus three doc pages, and this is a package type.What
Authoring a
chatbotnode with a per-message avatar override works, all the way through:Chatbotresolves it:message.avatarFallback || userAvatarFallbackandmessage.avatar || userAvatarUrl—packages/plugin-chatbot/src/index.tsx:177-178.authoredToRuntimeMessagedestructures onlyrole/timestamp/toolInvocationsand spreads the rest (...passthrough) —packages/plugin-chatbot/src/chatMessageAdapter.ts:256-262.toRuntimeMessages(messages)intoChatbot—packages/plugin-chatbot/src/renderer.tsx.But no type an author can name declares those two keys.
avatar/avatarFallback?ChatMessagefrom@object-ui/types(whatChatbotSchema.messagesis typed as)SeamChatMessagefrom@object-ui/plugin-chatbotRuntimeOnlyMessageKeysisPick(RuntimeChatMessage, 'buildProgress' or 'blueprintProgress' or 'charts')ChatMessagefrom@object-ui/plugin-chatbot(the runtime shape)So
ChatbotSchema.messagesisChatMessage[]from@object-ui/types, and that interface has no index signature — an author who annotates their schema getsTS2353: 'avatarFallback' does not exist in type 'ChatMessage'on a value that renders correctly.Measured
With the four example schemas on
content/docs/plugins/plugin-chatbot.mdxannotatedChatbotSchema, the doc-snippet gate reports exactly five of these, one per documented per-message avatar:PR #7294 therefore leaves those three example blocks unannotated rather than deleting working, documented behaviour to satisfy a checker. That is a deliberate hole in that page's gate coverage, and it closes when this is decided.
Why it is worth a card rather than a shrug
This is the residue of #4424, which is the card that introduced
SeamChatMessageand named the render-only keys. It pickedbuildProgress,blueprintProgressandcharts— the keys API mode lifts out of the stream — and did not consider the two keys a human author writes by hand. The seam is otherwise exactly the mechanism that is supposed to make this expressible.Related: #4383 (the barrel used to export two different
ChatMessagetypes), #4399 (theas anythis seam replaced), #6169 (thechatbotauthoring surface becoming one named type).Two readings, not resolved here
avatar?: stringandavatarFallback?: stringto@object-ui/types'ChatMessage, since a metadata author demonstrably writes them and the runtime honours them. Cheapest, and makesdeclared = enforcedtrue again for this key pair.RuntimeOnlyMessageKeysto include them, leaving the pure authoring contract narrow. This keeps@object-ui/typesminimal but does not help theChatbotSchema.messagesauthor, who never namesSeamChatMessage— so on its own it does not close the hole above.Reading 1 is what the measured usage argues for; reading 2 is what the existing seam vocabulary argues for. Someone who owns the authoring contract should pick.
Reachability
Nothing breaks at runtime — the keys work today. The cost is type-level: a TypeScript author annotating their chatbot metadata is told a correct value is wrong, and a documentation page cannot be fully gate-covered.