Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
777 changes: 777 additions & 0 deletions docs/design/web-shell/chat-transcript-contract-prevalidation.md

Large diffs are not rendered by default.

451 changes: 451 additions & 0 deletions integration-tests/chat-transcript-contract.test.ts

Large diffs are not rendered by default.

865 changes: 865 additions & 0 deletions integration-tests/chat-transcript-document.test.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Chat transcript contract capability matrix

| Capability | Native source | Contract mapping | Render/action mapping | Consumers | Fixture/evidence | Owner | Gate |
| ---------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------- | ----------------------------------------------------- | ------------------ | -------------------------------------------- |
| user/assistant/thought | prompt-scoped live or persisted segment ID | runtime ordinal ID unchanged; candidate probe projects source-keyed IDs | stable source IDs and semantic-copy hash | Web, Tauri, VS Code, HTML | `representative`, SDK append/prepend matrix | CLI + SDK UI | pass; stable under append/prepend/replay |
| tools and grouping | tool call ID | runtime keeps raw fields; document/export uses typed input and result preview only | group keeps every block/tool call ID; file target is semantic | Web runtime + document | raw compatibility and raw-free document tests | SDK UI + Web Shell | pass |
| plan/todo | plan tool call ID and plan ID | runtime keeps raw fields; document/export uses typed `todo_list` preview/result | standalone deterministic tool item | Web runtime + document | raw compatibility and raw-free todo document tests | SDK UI + Web Shell | pass |
| permission history | request ID and safe tool identity | runtime keeps raw tool call; document/export uses safe identity only | resolved history maps to stable tool target | Web runtime + document | raw compatibility and raw-free permission tests | SDK UI + Web Shell | pass |
| replay/prepend | prompt-scoped live or persisted segment ID | source metadata and persisted record boundaries are preserved | probe compares stable block/item IDs and semantic hash | all | SDK, direct-daemon and ACP append/prepend tests | CLI + SDK UI | pass |
| scope isolation | host scope key + generation | stale input is rejected before reduction | stale action evidence is absent | VS Code | VS Code contract probe | VS Code | pass |
| VS Code direct daemon | daemon event plus source segment ID | unchanged SDK reducer plus read-only stable-ID projection | shared Web Shell render/action probe | VS Code | direct-daemon SDK contract probe | VS Code | pass; retained as a validated alternative |
| VS Code ACP | Qwen ACP live/history segment metadata | thin SDK normalizer/reducer plus read-only stable-ID projection | shared Web Shell render/action probe | VS Code | ACP source + contract probes | VS Code | pass; selected for the later migration phase |
| Tauri distribution | packaged qwen runtime | same daemon blocks and Web Shell build | same renderer artifact | Tauri | desktop packaging-wiring source assertion | Desktop | pass |
| export record policy | ChatRecord type/subtype and parent chain | known visible records only | canonical projector output | HTML | export policy unit test | CLI | pass |
| export block safety | projected block | per-kind allowlist, opaque IDs, zeroed timestamps | direct raw-free renderer input | HTML | export schema/canary tests | CLI | pass |
| Markdown/resources | Markdown image URL and structured raster | approved data raster only in document mode | no automatic remote image source | HTML | Markdown document test + browser request interception | Web Shell | pass |
| document budgets | shared V1 constant | block/text/image/envelope/depth/array/object-property/rich-task caps | non-virtualized document probe | HTML | maximum document browser probe | CLI + Web Shell | pass |

Both VS Code candidates pass the stable identity matrix without changing the
default reducer's ordinal runtime IDs. Qwen ACP prompt-bound live text is
stamped at source with a prompt-scoped deterministic segment ID; persisted
replay keeps its record-derived segment ID. The candidate probes project those native identities
to scope-keyed block IDs, while existing persisted record boundaries prevent
unrelated history segments from merging. ACP text that has neither a stable
prompt source nor segment identity still fails closed. ACP remains the selected path for the later migration phase
because it is the current production transport; direct-daemon stays a validated
alternative, not a production migration in Step 0.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{"sessionUpdate":"user_message_chunk","content":{"type":"text","text":"Inspect the contract"},"_meta":{"qwenTranscript":{"segmentId":"user-1:0"}}}
{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":"Checking identity"},"_meta":{"qwenTranscript":{"segmentId":"assistant-1:0"}}}
{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"The contract is stable."},"_meta":{"qwenTranscript":{"segmentId":"assistant-1:1"}}}
{"sessionUpdate":"tool_call","toolCallId":"read-1","title":"Read file","status":"completed","rawInput":{"path":"src/index.ts"},"_meta":{"toolName":"read"}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{"uuid":"user-1","parentUuid":null,"sessionId":"session-test","timestamp":"2026-08-16T00:00:00.000Z","type":"user","cwd":"/workspace/project","version":"test","message":{"role":"user","parts":[{"text":"Inspect the contract"}]}}
{"uuid":"assistant-1","parentUuid":"user-1","sessionId":"session-test","timestamp":"2026-08-16T00:00:01.000Z","type":"assistant","cwd":"/workspace/project","version":"test","message":{"role":"model","parts":[{"text":"Checking identity","thought":true},{"text":"The contract is stable."}]}}
{"uuid":"tool-start","parentUuid":"assistant-1","sessionId":"session-test","timestamp":"2026-08-16T00:00:02.000Z","type":"assistant","cwd":"/workspace/project","version":"test","message":{"role":"model","parts":[{"functionCall":{"id":"read-1","name":"read_file","args":{"path":"/workspace/project/src/index.ts"}}}]}}
{"uuid":"tool-result","parentUuid":"tool-start","sessionId":"session-test","timestamp":"2026-08-16T00:00:03.000Z","type":"tool_result","cwd":"/workspace/project","version":"test","message":{"role":"user","parts":[{"functionResponse":{"id":"read-1","name":"read_file","response":{"output":"hidden raw output"}}}]},"toolCallResult":{"callId":"read-1","status":"success","resultDisplay":{"type":"vision_bridge_notice","summary":"Visible summary","notice":"Visible notice"}}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{"id":10,"v":1,"type":"session_update","data":{"update":{"sessionUpdate":"user_message_chunk","content":{"type":"text","text":"Inspect the contract"},"_meta":{"qwenTranscript":{"segmentId":"prompt-1:user:0"}}}}}
{"id":20,"v":1,"type":"session_update","data":{"update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":"Checking identity"},"_meta":{"qwenTranscript":{"segmentId":"prompt-1:thought:0"}}}}}
{"id":30,"v":1,"type":"session_update","data":{"update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"The contract is stable."},"_meta":{"qwenTranscript":{"segmentId":"prompt-1:assistant:0"}}}}}
{"id":40,"v":1,"type":"session_update","data":{"update":{"sessionUpdate":"tool_call","toolCallId":"read-1","title":"Read file","status":"completed","rawInput":{"path":"src/index.ts"},"_meta":{"toolName":"read"}}}}
{"id":50,"v":1,"type":"permission_request","data":{"requestId":"permission-1","sessionId":"session-test","title":"Allow read?","options":[{"optionId":"allow","name":"Allow","kind":"allow_once"}],"toolCall":{"toolCallId":"read-2","name":"read","kind":"read","rawInput":{"path":"src/other.ts"}}}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"schemaVersion": 1,
"forbiddenFields": [
"rawInput",
"rawOutput",
"toolCall",
"details",
"locations",
"sourceRecordIds",
"sessionId",
"sessionLabel"
],
"expectedToolResult": "Visible summary\nVisible notice",
"timestamps": 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"kinds": ["user", "thought", "assistant", "tool"],
"sourceRecordIds": [
["user-1"],
["assistant-1"],
["assistant-1"],
["tool-start", "tool-result"]
],
"rawFreeToolResult": "Visible summary\nVisible notice"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"unexpectedRequests": 0,
"cspViolations": 0,
"allowedImageSources": [
"data:image/png",
"data:image/jpeg",
"data:image/gif",
"data:image/webp"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"roles": ["user", "thinking", "assistant", "tool_group"],
"requiredCapabilities": ["copy", "edit-user-message", "open-file"],
"identityFields": ["renderedItemId", "sourceBlockIds", "sourceToolCallIds"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"fixtureVersion": 1,
"name": "representative",
"generatorVersion": "chat-transcript-prevalidation-v1",
"sources": ["daemon", "acp", "chat-records"],
"consumers": ["web", "tauri", "vscode", "html"],
"capabilities": [
"text-thinking-usage-images",
"streaming-replay-prepend",
"tools-plan-permission",
"render-action-identity",
"scope-generation",
"export-security-network-budgets"
],
"complete": true,
"expectedDiagnostics": [],
Comment on lines +15 to +16

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Pattern — the contract's machine-readable locks are advisory, not enforced: (1) manifest.json:15 — expectedDiagnostics/normalizedFields/complete/capabilities/sources/name/generatorVersion have zero read sites repo-wide, and the hash check iterates only the entries manifest.hashes lists without asserting coverage of the case directory; (2) export-transcript-document-v1.schema.json — the published cross-host schema is never used to validate any document instance (no Ajv compile references it) and both under- and over-specifies the runtime gate (base64 length%4, home_path_forbidden are unexpressible); (3) manifest.schema.json:45 — never compiled or validated against manifest.json anywhere, though the design doc (line 528) states CI validates schema/hash first; (4) chat-transcript-contract.test.ts:137 — the toolPreview closed-schema assertion filters out $ref variants, so todoPreview escapes the additionalProperties: false lock (the block loop below resolves $refs; the schema files are not hash-locked, so nothing compensates). — Failure scenario: executed: delete one entry from manifest.hashes and mutate that fixture → contract test 4/4 green (the fixture mutates out from under the gate); set $defs/todoPreview.additionalProperties = true → 4/4 green; a schema-conformant document with base64 length %4 ≠ 0 is rejected by the runtime gate. The "machine-generated gates" the doc claims do not exist for these faces.

Witness (executed):

mutation probes: drop-hash + mutated fixture -> Tests 4 passed (4); todoPreview.additionalProperties=true -> Tests 4 passed (4); same mutation on userBlock (resolved by the block loop) -> RED (control); ajv: data:'AAA' (schema-conformant) rejected by the runtime gate with invalid_block

Suggested fix: In the contract test: assert hashes covers every file in the case directory; compile both schemas with Ajv (draft 2020-12, mirroring packages/core/src/utils/schemaValidator.ts) and validate manifest.json plus exported document instances; resolve $refs in the toolPreview closed assertion like the block loop does.

中文说明

契约的机器可读锁是建议性的:manifest 字段零读取点且 hash 检查不断言目录覆盖;v1 文档 schema 与 manifest.schema.json 从未被实例/编译校验;toolPreview 闭合断言过滤 $ref 变体——实测多处变异仍全绿。

失败场景: 实测:删掉 manifest.hashes 中一个条目并篡改对应夹具 -> 契约测试 4/4 全绿(夹具在门禁下静默漂移);把 $defs/todoPreview.additionalProperties 改为 true -> 4/4 全绿;schema 合规但 base64 长度非 4 倍数的文档被运行时门禁拒绝。设计文档宣称的"机器生成门禁"在这些面上并不存在。

建议修复: 在契约测试中:断言 hashes 覆盖目录全部文件;用 Ajv(draft 2020-12,参照 packages/core/src/utils/schemaValidator.ts)实例校验 manifest.json 与导出的文档实例;toolPreview 闭合断言像 block 循环一样解析 $ref。

— qwen3.8-max via Qwen Code /review (v0.21.13)

"normalizedFields": ["clientReceivedAt", "createdAt", "updatedAt"],
"hashes": {
"daemon-events.jsonl": "ea25e535847aea996ee3062b7497272540520a9a213de7223ac705780ccb7ac7",
"acp-session-updates.jsonl": "2f79f50505bd17de22979d47a63e52183cde541d416e4c44eb58cd5351e6a13a",
"chat-records.jsonl": "b66abea928c3c65cdedc4ca1c455d86b1b0a46b90e2a6a186afefaa89e87db0e",
"expected-model.json": "4bbf808871c62219910127b50b3290a5c74b64b4464ec57bf25353faad79a5e5",
"expected-render-items.json": "3ef27f09cc9b8cd2350fff269262dc0a653e0dba8435cce846a07bee8d0fbc73",
"expected-export.json": "160f87a0f2c069a8378b27ba3f51e2209d31359e1f86fa21c0786cf9fb02c32b",
"expected-network.json": "ee14c9469e2f80f1262a23ca8452bc41abb9ce43a625176c1b428608c503ad11"
}
}
Loading
Loading