chore(gui): mission work logs — cordis design finalization, tool-card wire archive, incident records chore: missions chore: missions chore(gui): mission ledger — batch-2 answers, parallel dispatch state, jsdom coverage re-scope chore(gui): ledger — night-mode standing orders (self-commit small, no push, 5-min refresh) chore(gui): ledger — jsdom batches 2-4 landed (233 green), coverage probe next chore(gui): ledger — web-ui coverage probe 65%, four-tier fill plan approved chore(gui): ledger — cordis-impl B1 state after third API drop, decisions on file chore(gui): ledger — 01:32 patrol snapshot (jsdom tier-1 landed, coverage-fixer probed) chore(gui): ledger — 01:37 patrol (peer src trio landed, coverage-fixer still silent) chore(gui): ledger — 01:42 patrol (jsdom tier-2 landed, peer committed x2, coverage-fixer 2nd probe) chore(gui): ledger — coverage diagnosis complete (6-file gap list), web-ui at 91.4% chore(gui): ledger — 01:46 patrol (B1 done, jsdom tier-3 landed, coverage fix batch running) chore(gui): ledger — 01:51 patrol (jsdom tails x2 landed, B2 underway) chore(gui): ledger — 01:56 patrol (gateway.ts 337 lines, checkpoint T-7min) chore(gui): ledger — hold/pending split ruling, coverage-fixer externalize-or-restart ultimatum chore(gui): ledger — 02:01 patrol (B2 done, jsdom final arms, coverage ultimatum pending) chore(gui): ledger — 02:03 checkpoint executed (fixer2 respawn, four lanes released, three owners cold-started) chore(gui): ledger — all six lanes acked, type isolation first live proof (client closure clean) chore(gui): ledger — 02:08 patrol (all seven lanes active, wire carrier assembled) docs(gui): respond-design task checkpoint — apiproxy wire-layer recon done chore(gui): ledger — P0-2 contributor AGENTS.md landed (dd28a5019) docs(gui): respond-design checkpoint 2 — host-side recon (stub respond, frame types, approval seam, ACP answerer precedent) docs(gui): OOP debt inventory — seven territories, 2 real debts (createApiProxy, createFixtureApi), rest ruled keep-as-is docs(gui): disambiguation note on the archived i18n design task chore(gui): ledger — 02:12 patrol (exclude removed, mixed-knife incident under reconciliation) chore(gui): ledger — 02:15 wave (jsdom mission closed, OOP audit done, B3 isolation proof, mixed-knife resolved) chore(gui): ledger — 02:17 patrol (attribution reversal filed, arch-session probed) chore(gui): ledger — 02:22 patrol (B4 done, B5+B6 merged batch, arch-session deadline set) docs(gui): respond-design checkpoint 3 — client-side recon (pending map, PendingCard onRespond stub, AbstractApiClient.respond ready, bootHost missing approval mounts) docs(gui): peer carrier territory review — 2 fixes (SSE cancel leak, route-reservation guard), 1 ruling ask (RPC-log visibility), compliance ledger chore(gui): ledger — 02:27 (arch-shell respawn, territory review verdicts routed, ask-deny finding flagged) docs(gui): P1-5 respond design page complete — pending registry, wire answerer, client state machine, first-wins arbitration chore(gui): ledger — 02:31 patrol (respond design complete, B5 wire smoke green, shell knife 1 underway) docs(gui): respond design — add §0 status warning (web host ask defaults to deny), mount-behavior delta, no-timeout ruling with Config discipline chore(gui): ledger — 02:42 patrol (respond line closed pending review, B7 last piece underway) docs(gui): respond design contract review — direction pass, 2 doc fixes (settle-order contradiction, answering-state race), A/B/C compliance ledger docs(gui): respond design — contract review fixes (R1 verify-before-delete arbitration, R2 answering+resolved-frame transition, ask dual-source wording, rejected-is-ok-value note) chore(gui): ledger — 02:46 patrol (respond line final, two user decisions distilled, arch-shell deadline) docs(gui): respond review addendum — contract-gap ruling: approve plan A (ApprovalRequest.id), wire unchanged, drop plan B backscan chore(gui): ledger — cordis B7 summit: real-browser 10/10 green, user acceptance criterion proven docs(gui): respond design — contract gap #4 approved as plan A (ApprovalRequest.id), backscan fallback retired, blade 0 prepended docs(gui): respond design — final polish (owner-approval vs user-go-ahead wording, implementation handoff notes) chore(gui): ledger — 02:51 (shell-exec third respawn with operational script, respond line 4-knife final) chore(gui): ledger — 02:53 wave (respond five-knife true final, B7 closed 12/12, client.ts green) chore(gui): ledger — 02:56 patrol (cordis closeout bounced pending R1/R2/N1, shell-exec first sign of life) chore(gui): ledger — 03:01 patrol (R1/R2/N1 remediation in flight across four files) chore(gui): ledger — cordis line officially closed and archived, verified on disk (24 knives, 12/12, reviews closed) chore(gui): ledger — 03:06 patrol (shell-exec final window, lowered first-knife bar) chore(gui): ledger — 03:11 patrol (shell line iced-broken: three registries on disk) chore(gui): ledger — 03:15 patrol (quiet window, both active lanes within threshold) chore(gui): ledger — 03:20 patrol (shell five files up, api-proxy plan reported) chore(gui): ledger — 03:25 patrol (shell migration in flight with history-preserving moves, webserver green) chore(gui): ledger — 03:30 patrol (shell knife-1 in verification, api-proxy patching) chore(gui): ledger — shell knife 1 accepted (694cecc53), knife 2 released chore(gui): ledger — 03:40 patrol (knife 2 pre-move stage, cold-list spec appears) chore(gui): ledger — 03:45 patrol (rpclog moves staged, api-proxy two specs in flight) chore(gui): ledger — 03:54 patrol (knife-2 code done, coverage full-run final check) docs(gui): coverage-fixer task ledger — fixer2 takeover, per-file fix log, isolated reportsDirectory pitfall chore(gui): ledger — coverage lane closed and accepted (a19f069a5), the PR #443 CI fix knife chore(gui): ledger — 04:04 patrol (knife-2 calibration, sole active lane) chore(gui): ledger — shell knife 2 accepted (f8fb77b95), knife 3 released as final night task chore(gui): ledger — 04:19 patrol (knife-3 past half: callback chain through, ToolCallDetail up) chore(gui): ledger — night closeout summary: seven lanes closed, wake-up decision sheet chore: missions chore: missions chore: missions chore(gui): mission-local browser/probe verify scripts under missions/scripts/ The six acceptance/probe scripts move here as mission-side working material (headers and relative imports adjusted for the new location): carrier-errors, rpclog-panel, session, session-real, webserver-backpressure, webserver-hardening. chore(gui): verify-relocate mission log chore(gui): verify-relocate mission log — R1 guard addendum chore(gui): gates-continue mission log — CI-equivalent sequence all green chore(gui): VS Code 扩展体系双边设计调研报告 chore(gui): 调研追加 4.5 节——git 扩展数据面与 scope 绑定 docs(gui): web plugin system RFC — walkthrough + design notes docs(gui): RFC — restore existing SSE/POST as the v1 transport; envelope rides on it (D16) docs(gui): RFC — envelope demoted to chan-dispatch, scope out of envelope, rpc-log cut, peer deferred, scope tree is native cordis (D17-D21) docs(gui): RFC — hooks re-derived from component needs: useWatch/useAction only, useService removed; sessionHub cut, projections user-space, router rename, loader-only root (D22-D25) docs(gui): RFC — drop stale fork vocabulary (vendored cordis has Fiber only; scope = mintScope pattern), hook idempotence contract (D26-D27) docs(gui): RFC — session precision seam: plugins read scope key (host paradigm), React gets it from tree position via SlotOutlet (D28) docs(gui): RFC — domain hooks owned by plugins over framework primitives; useConversation paradigm carried over (D29) docs(gui): RFC — ctx services are the inter-plugin API (cordis proper); declarations are wire-only; get(id) returns scoped ctx (D30) docs(gui): RFC — full ctx.conversation walkthrough: root-singleton scope-sensitive service, caller-ctx scope key, get(key) as scoped ctx (D31) docs(gui): RFC — no client-side agents collection: session state machine already expresses the duality; agent resolution stays host authority (D32) docs(gui): RFC — v1 stays session-precision, no agent-level isolation; incarnation/agent-axis designs archived in ledger (D33) docs(gui): RFC walkthrough — full rewrite to final state (D16-D33 consolidated), end-to-end chain restored docs(gui): RFC — apiproxy demoted to generic channel routing; domain RPCs dissolve into owner plugins (D34) docs(gui): RFC — TS-interface-first wire contract (zod internal), conversation owns the dialogue frame with pluggable views (D35) docs(gui): RFC — page skeleton (sidebar+conversation), projects as plugin not service, nested slots via owner registries (D36) docs(gui): RFC — SlotMap declaration-merging slot model: single register API, inject-as-ownership, FC-typed registration, typed outlets (D37) docs(gui): RFC — slot props whitelist: identity, display params, materialized snapshot slices, stable UI callbacks (D38) docs(gui): RFC — end-to-end data flow: three transforms, equality protocol table, immer placement; i18n/theme kept standard (D39-D40) docs(gui): RFC — full external-injection model: props carry values + stable injected hooks; shared/client/react example rewritten (D41-D43) docs(gui): RFC final trio — modules.md (agent implementation spec), architecture.md (human walkthrough), plugins.md (business plugin inventory) docs(gui): RFC — props three-source merge (scope-standard useSession auto-injected); keyed key vs list id disambiguated (D44) docs(gui): RFC — inject comment says what it is (the React-facing props bundle); SessionHandle rename; snapshot-production story unified on buildSnapshot docs(gui): RFC architecture — full React component tree walkthrough: props three sources, slot vs plain children, hook taxonomy per node docs(gui): RFC — module map finalized (ui-slots/web-react/connection/runtime/ui-*/web); slots onChange replaced by cordis events; toolcall dimension; detail sidebar default-collapsed with toolName-keyed routing docs(gui): RFC plugins — openDetail relay chain: toolcard calls chat-view injected action, chat-view relays to conversation sidebar chore(gui): progress ledger — full archive rewrite: RFC outcome digest, open gaps, dispatch plan, cold-start entry docs(gui): RFC grill pass 1 — SlotScope axis (root/session) on declares, Gate dependency inversion, inject handle by scope, W5 acceptance list, gantt relay chain fixed docs(gui): figma analysis — sidebar/projects/sessions 区域交互视觉理解报告 docs(gui): figma 解析报告 — details 面板/多视图 tabs/未来功能区盘点 + slot 需求清单 docs(gui): figma 对话主区解析报告 — 消息流/tool calls 变体/审批接管输入框/Header tabs/视觉 token docs(gui): plugins.md rewritten from figma analysis — three-column layout, full slot reservation table, selection channel, composer-takeover approvals, phased scope docs(gui): layout dynamics ruled (drag+collapse both rails, details yields first, composer swap-panel, same-component transition); toolviews promoted to named scope-aware registry docs(gui): P-I scope locked (details minimal, dual theme, chat-view, custom toolview sample); teammate dispatch plan — 6 owners by package, dependency-driven waves, contract arbitration docs(gui): P-I api-contracts (full inter-package API spec) + dispatch plan (T0 skeleton knife, 7-dev roster, task briefs, milestones) docs(gui): api-contracts v2 — scope tree in P-I, bundle loader + per-plugin CSS isolation in P-I, agent-scoped toolviews live, zustand engine, renames (SessionProvider/ObservableSnapshot/SessionBinding), router owns all shell view-state docs(gui): services roster + progressive loading (no blocking loadAll), SlotsService as real cordis Service, renderSlot/renderSuspenseSlot duo, ui-traj teammate docs(gui): loading-chain gaps ruled — dev=rebundle no HMR, ui-primitives package, externals on globals (no import map), host injects __DSH_BOOT__ into HTML (zero round-trip) docs(gui): api-contracts v3 + dispatch v2 final — 12 packages, services merged in, progressive loader, global externals, __DSH_BOOT__ injection, 8-dev roster with convo split and ui-traj docs(gui): v3 amendments — router renamed ctx.layout, ui-trajectory has no service (pure consumer sample), wait-for-settled loading (no Suspense in P-I, ledger 6b) chore(gui): progress — pre-compact final state: v3 revision chain, 8-dev roster, T0 procedure, doc authority order docs(gui): authority banners — modules/architecture get v3 term-mapping headers, walkthrough marked as archived process doc docs(gui): cssdesign token set is THE theme source (--dsw-* variables, data-ds-dark-theme switch); recorded in contracts + progress docs(gui): architecture.md full v3 rewrite — loading chain, 12-package map, service roster, slot/inject/toolviews, data flow, component tree, perf model, all current docs(gui): contracts — UI plugins are dual-entry host plugins (node half serves client asset via ctx.webPlugins; __DSH_BOOT__ derives from it; client-closure gate back in scope) docs(gui): contracts — closure-factory bundles with DI require (no globals), package.json dshWeb declarative discovery (no serve ritual), create-then-send empty state with project picker, ancestry() for breadcrumb, unload stubbed until HMR, props.renderSlot confirmed docs(gui): dshClient declaration (inject/platform/immediately, exports./client), closure-DI require loading — synced across contracts/dispatch/modules/architecture/walkthrough chore(gui): progress — record final loading-chain rulings (dshClient declaration, closure-DI require, startSession) before compact docs(gui): architecture.md — developer-facing whole-web architecture on master baseline 6b16a67cb: what exists, what is new, no process narrative docs(gui): architecture.md — self-contained whole-web architecture: absorbs still-valid substance from the branch RFCs (host layering, four-quadrant RPC, object layer, testing tiers) under the new plugin system as the override chore(gui): progress — final pre-compact snapshot: contracts digest, apiproxy purity ruling, T0 procedure with first-action list docs(gui): api-contracts v3 §3.1 — apiproxy purity principle with three-way existing-code verdicts docs(gui): api-contracts v3 — immediately reinterpreted as static-infra group (8-package dshClient scope, boot manifest reconciliation) docs(gui): api-contracts v3 — immediately corrected to early-load dynamic group (prod shell must not rebundle); loader shell-held; bundles register their export surface into module table docs(gui): architecture — align with immediately=early-load dynamic group ruling; loader shell-held; module-table registration of loaded bundles docs(gui): T0 checklist — 12-package skeleton table, 4-cut sequence, mv/attic/rewire rules (pre-drafted, awaiting go) docs(gui): t0-checklist — pin figma-flows findings (missing font-family base vars, three alias vars behind upstream) docs(gui): dispatch v2.1 — drop cordis-web salvage wording, two-wave staffing, loader/immediately boundary updates docs(gui): progress + t0-checklist ledger — T0 landed, staffing status, execution accounting docs(gui): api-contracts v3 — arbitration round 1: renderBody deps, RootBindingProvider, flush default sync, prune current, loader subpath, config-source P-I bar docs(gui): v3 §3.2 connection 导出清单附录(rt-core 对账)+ rt-core 实现计划档案 docs(gui): progress — T1 milestone, arbitration round 1 ledger, fw-react timeout escalation docs(gui): progress rolling update — per-line battlefield state at 00:2x, mailbox-vs-contract lesson, small-batch discipline reinforced docs(gui): fw-react notes — v3 §2 complete, seven knives, T1/T2 follow-ups docs(fw-slots): archive — four packages landed, open tails logged docs(gui): progress — framework layer complete (web-react five, fw-slots four packages), T2 gated on rt-core runtime knife only docs: api-contracts docs: style-spec docs docs(gui): tsconfig convergence ruling — no host.json, root resumes host-aggregate duty, typecheck = root + client aggregates docs(gui): missions 根三份 07-18 世代档案加「已被取代」头注——指向 web-plugin-rfc 现行权威并注明新旧对应 missions docs(gui): progress rewritten for post-closeout state — wave ledger, architecture finale, teammate roster with handover notes, pending-user-command queue
24 KiB
20260720-1620 cordis-spike:类型强隔离 spike + 双端包形态 PoC
任务理解
验证 blueprint-v2 第 2 点(cordis 类型体系在 browser 半边的强隔离)的工程方案。产出是可行性结论 + 最小 PoC,不是产品代码。零 commit。
核心命题(用户已定理论框架,本 spike 实证):
declare module 'cordis'的 merge 污染是 program 级:增补文件一旦进入 tsconfig program 的传递闭包,整个 program 所有文件都看得见(如 node-only 的ctx.sessions在浏览器代码里"合法")。- 拦法 = browser 半边用独立 tsconfig program(file-set 从 browser 入口出发)+ 保持传递闭包干净 + gate 脚本机械验证闭包不含 node 半边文件。
- 已知风险:
import type的目标文件仍进 program(类型也要解析),所以 shared 层能引用的包必须本身零 node 增补——import type纪律挡不住增补泄漏。
PoC 方案
放 missions/tasks/20260720-1620-cordis-spike/poc/(不进 packages/,不碰在途文件)。
结构:
poc/
echo-a/ 双端包:src/node.ts、src/browser.ts、src/shared.ts 三入口
echo-b/ 仅 node 半边的包(有自己的 declare module 'cordis' 增补)
tsconfig.browser.json browser program:files/include 从 browser 入口出发
tsconfig.node.json node program:从 node 入口出发
gate.mjs gate 脚本原型:解析 browser program 文件集,断言与 node 半边清单交集为空
实证四件事:
- 正例:browser program 里
ctx.timer类型可见(vendor/timer 增补,浏览器安全);同 program 里写ctx.sessions(dsh-session 的 node 增补)应报 TS2339——负例文件证明它真报错。 - 负负例:故意让 browser 半边经级联依赖 import 一个 node 半边文件(echo-a/browser → 某中间文件 → echo-b/node),证明污染发生(
ctx.sessions的负例不再报错)——说明光靠纪律不够,gate 必要。 - shared import type:shared 层
import type一个干净类型包(如 apiproxy 的 /api 子路径)不引入污染。 - gate 原型:
tsc --listFilesOnly(或 ts API)取 browser program 文件集 ∩ node 半边清单 = ∅;对场景 2 的偷渡要能抓出来。
顺手回答:vendor/cordis 本体(零 node import,design.md §A 已核)在 browser program 是否需要 shim/别名,还是原样 import 即可。
产出(本 README 末尾补结论)
- 方案可行性:program 隔离 + gate 是否"理论正确"。
- 坑清单。
- gate 脚本转正建议(scripts/ 位置、挂哪个门禁)。
- 三入口 exports 形态建议(PoC 实际用的 package.json exports 布局)。
状态
- 第 0 步:本 README 外化方案
- 读 missions/conventions.md、blueprint-v2.md、design.md §A
- PoC 搭建
- 四件实证
- 结论回写(见下)
【已作废——旧标准产物】第一轮结论(gate 主角方案,被「tsconfig 原生自动生效」验收翻转推翻;实证事实仍有效,方案节以文末 v2 为准)
总判定:方案可行,理论正确
program 隔离 + gate 的组合成立:browser 半边独立 tsconfig program(file-set 从 browser 入口传递闭包出发)真实挡住了 node 侧 declare module 'cordis' merge——ctx.timer 可见、ctx.sessions/ctx.echoB 真报 TS2339;污染确实是 program 级、闭包级的(不是 import 语句级),所以 gate 对闭包做机械断言正好补上纪律挡不住的级联偷渡。
四件实证结果
| # | 场景 | tsconfig | 结果 |
|---|---|---|---|
| ① | 干净 browser program:ctx.timer 可见 + ctx.sessions/ctx.echoB 负例真报错 |
tsconfig.browser.json |
GREEN(24 repo 文件,清单在 poc/clean-program-files.txt)。负例用 @ts-expect-error 表达:干净时指令被消费→绿;泄漏时变 TS2578→红,双向 tripwire |
| ② | 负负例:经"无辜中间文件"级联偷渡 echo-b node 半边 | tsconfig.browser-smuggle.json |
RED 如预期:ctx.echoB 的 @ts-expect-error 变 TS2578(增补真进来了)+ node:path TS2591。import 现场毫无异样——证明 gate 必要 |
| ③a | shared 层 import type 干净类型包(dsh-brand) |
并入 ① | 不引入污染,GREEN |
| ③b | shared 层 import type apiproxy /api 子路径 |
tsconfig.browser-apiproxy.json |
RED——重要发现:/api 自称 browser-importable,但其闭包经 dsh-session/dsh-llm/dsh-user-approval/dsh-user-interaction 的 barrel 拖进 7 个非白名单增补 + 5 处 node: import(program 从 24 文件涨到 207)。import type 不能防污染的风险点坐实 |
| ④ | gate 原型 poc/gate.mjs |
三个 program 各跑一遍 | 干净 program PASS;②③b 均被抓(闭包交集 + 增补白名单双检查都命中) |
对照实验(控制变量):把 types 从 [] 恢复成默认(含 node)再跑 ②——node:path 报错消失,但 TS2578 tripwire 仍红。说明 types: [] 是纵深防御的一层(平台 API 泄漏在案发文件立刻报错),不是唯一防线。
vendor/cordis 在 browser program 的答案
原样 import 即可,cordis 本体零 shim 零别名(paths 映射到 vendor/cordis/src 直接编译过)。需要的 shim 全在外围、且都是纯类型(运行时零代码,合计约 15 行):
types/buffer-shim.d.ts:cosmokit 的typeof Buffer !== 'undefined'运行时守卫在types: []下需要一个 ambientBuffer声明(实测去掉即 8 个 TS2591)。types/nodejs-timeout-shim.d.ts:vendor/timer 把句柄写成number | NodeJS.Timeout,browser 下declare namespace NodeJS { type Timeout = number }即消。
坑清单
import type防不住增补(核心风险坐实,③b):类型解析把目标文件整个拉进 program,barrel 里的declare module 'cordis'一并生效。shared 层能引用的包必须本身零 node 增补,这只能靠闭包 gate 保证,纪律与 lint import 语句都不够。- 现状 apiproxy
/api不是干净入口:它import type的是 dsh-session/dsh-llm 的 barrel。但底细是好的——session/types.ts、session/json.ts、llm/types.ts、llm/brand.ts本身零增补零 node:,修法=给这些包开纯类型子路径 exports(/types),apiproxy/api 改从子路径 import。例外:dsh-user-approval / dsh-user-interaction 是单文件包(增补和类型同文件),要先拆文件才有纯子路径可言。 - vendor src 的编译 flag 冲突:单 program 直编 vendor src 时,vendor 各自 tsconfig 的放宽(noImplicitAny:false 等)不生效,得在 browser program 里放宽全 program(PoC 做法,见
tsconfig.shared.json注释)。转正时三选一:project references(各 vendor 保持自己 flag,推荐)/ 消费 vendorlib/types产物 / 接受全 program 放宽。 paths是整体覆盖不可增量合并:browser tsconfig 必须重抄全量 paths 映射(或脚本生成)。会漂移——正好由 gate 兜底。- tripwire 是按 key 的:②里只有
ctx.echoB的指令变 TS2578,ctx.sessions那条照常(session 没被偷渡)。负例文件当编辑器内的快速信号,真正的强制是 gate 的白名单检查(对任意未知增补普适)。 types: []值得保留:没有它,偷渡文件里的node:value import 静默编译过(见对照实验),泄漏要到 tripwire/gate 才发现;有它则案发文件当场红。
gate 脚本转正建议
- 位置:
scripts/verify-browser-closure.mjs(或 .ts 并入现有 verify-* 家族);输入=browser tsconfig 路径。 - 机制照抄 PoC:
tsc -p <cfg> --listFilesOnly拿 program 精确文件集(必须用 tsc 而非 bundler 依赖图——只有 tsc 看得见 type-only 边,而 type-only 边正是污染通道);双检查:A) 闭包 ∩ node 半边清单 = ∅;B) 含declare module 'cordis'的文件必须逐个在 browser-safe 白名单里。B 是 A 的兜底,抓 A 的 pattern 没预料到的文件。 - node 半边清单来源(转正时替换 PoC 的硬编码 pattern):机械推导自 package.json exports——无
./browser/./shared出口的包整包算 node-only;三入口包取./node入口。不再手维护清单。 - 挂哪个门禁:跟
pnpm run typecheck同级(CI 序列里紧随其后);等 web 构建管线成型后并进该管线的 verify 步。成本≈一次 tsc no-emit(PoC 实测秒级)。 - 保留 tripwire 负例文件进 web-runtime 源码(
ctx.sessions等已知 node key 各一条@ts-expect-error),当编辑器里的即时信号;gate 管强制。
三入口 exports 形态建议(PoC 实际所用 + 转正形态)
PoC 用源码直连形态(tsconfig paths + "./node"|"./browser"|"./shared": "./src/*.ts",见 poc/echo-a/package.json)。转正建议:
"exports": {
"./node": { "types": "./lib/types/node.d.ts", "default": "./lib/node.js" },
"./browser": { "types": "./lib/types/browser.d.ts", "default": "./dist/browser.js" }, // bundle 产物,蓝图 §5
"./shared": { "types": "./lib/types/shared.d.ts", "default": "./lib/shared.js" },
"./src/*": "./src/*", // 保留仓库源通道惯例
"./package.json": "./package.json"
}
- 无根入口(不设
"."):强迫每个 import 表态要哪半边——根入口是污染的头号通道(③b 的 barrel 教训)。 - shared 的
default条目保留:creator/泛型方案(蓝图 §6)意味着 shared 可能有少量运行时(creator 函数),"对 shared 的 import 必须 import type" 的四问裁决①约束适用于消费方(node/browser 半边 import shared 用 type),creator 本身由框架在装配点消费。若最终 shared 纯类型,default改types-only 亦可。 - browser 条目指向 tsdown bundle 产物(external cordis),与蓝图 §5 一致;类型仍从 d.ts 走,gate 检查的是 types 侧闭包,与产物 bundle 与否无关。
PoC 文件索引(全部保留在 poc/ 供参考)
echo-a/(三入口双端包)、echo-b/(node-only 带增补)app/:browser-main(①③a)、negative(tripwire)、smuggle-chain + browser-smuggle-main(②)、browser-apiproxy-probe(③b)、node-main(node 侧镜像正例)tsconfig.shared/browser/browser-smuggle/browser-apiproxy/node.json:五份 program 定义types/:两个纯类型 shim;gate.mjs:gate 原型;clean-program-files.txt:干净 program 的 24 文件证据- 复跑:
cd poc && ../../../../node_modules/.bin/tsc -p tsconfig.browser.json && node gate.mjs tsconfig.browser.json && node gate.mjs tsconfig.browser-smuggle.json --expect-fail && node gate.mjs tsconfig.browser-apiproxy.json --expect-fail
升级批次(2026-07-20 傍晚,team-lead 五单合并;以下为任务理解外化,防断线)
上面第一轮结论是「gate 主角」旧标准产物,方案节已作废待改写;实证数据仍有效(program 级污染、import type 传染、apiproxy 污染、vendor 零 shim 等事实不变)。
新验收标准(用户硬约束,翻转)【本节为当时任务单快照;第 2 条 condition 主机制随后被用户终裁推翻(「export 可以改,condition 就不用了」),终态见文末结论 v3】
- 不要重门禁,要 tsconfig 原生自动生效:违规 import(client 半边碰 node 半边)必须在 tsc/IDE 当场编译报错(cannot find module),不是靠 CI gate 事后抓。gate 降级为薄兜底(甚至可无)。
- 主机制 = exports 条件 + customConditions,且经纠偏(55e99fb3):
./node子路径不做了——node 半边维持各包现有"."主入口(存量不干涉);双端包只新增./client./shared。自定义条件(如dsh-node)挂在"."主入口的 types/default 上:node tsconfigcustomConditions: ["dsh-node"]→ 解析到 node 半边;client tsconfig 不带 → import 主入口直接 TS2307。- additive 硬约束(连带实证):不带条件的普通 node 消费者(现存全仓 import)解析主入口必须完全不受影响——带条件多一层解析,不带走 default 原路径。
- 注意:无根入口的「强迫表态」防线随 ./node 取消而不存在了;client program 隔离全靠 customConditions/独立 tsconfig + types:[] + gate 符号溯源(README 取舍注记如实写)。
- 命名裁决:一律 client 不用 browser:tsconfig.client*.json、
./client入口、client-safe 白名单、gate 措辞全改。 - gate 重做为 TS compiler API 进程内(gate-api.mjs,旧 gate.mjs 留档对照):
- 基础档:
ts.getParsedCommandLineOfConfigFile→ts.createProgram→program.getSourceFiles()出闭包(等价 listFilesOnly 但进程内,可给 import 解析链排障)。 - 符号溯源档(主检查):
program.getTypeChecker()→ 拿 cordisContext/Events接口 Symbol →getDeclarations()枚举全部 merge 成员 → 每成员getSourceFile().fileName归属包 → 输出「key → 来源文件/包」清单,与 client-safe 白名单比对。检查本质(挂了什么 key、从哪来)而非代理指标(文件在不在)。 - 双端 diff 模式:同脚本参数化吃 tsconfig,client/node 各出一张 key→来源表,diff 出对等性报告(对等 Loader 验收预埋)。
- 三场景复验:偷渡场景必须精确报出 ctx.echoB 来源文件;apiproxy 场景 7 个增补各自来源。
- 基础档:
- apiproxy 双条件情报(pr-gates 另一树 6dfe2a66b 已把 /api /client 改 types→lib/.d.ts、default→src):「.d.ts 增补传染」从理论题变现实路径题,优先实证(echo-c 模拟 types→d.ts 布局);session 纯类型子路径修法建议要写成对双条件布局的增量。
执行计划(小批落盘,每批 ≤5 分钟)
- A1(最小正例先行):echo-a exports
"."挂 dsh-node 条件 + poc/node_modules symlink 真解析 + tsconfig.client.json / tsconfig.node.json 对照,跑通「client import 主入口 TS2307、node 侧正常」 - A2:additive 验证(不带任何 customConditions 的普通 tsconfig 解析
"."走 default 原路径不受影响) - B1:逃逸变体——src/* 通道、paths 覆盖 exports(各一个负例)
- B2:echo-c 模拟双条件 types→lib/.d.ts 布局,实证 .d.ts 增补传染
- C:gate-api.mjs 符号溯源 + 双端 diff,三场景复验
- D:全量 client 命名改造 + README 结论节按新标准重写(含 IDE 故事)
结论 v3(2026-07-20 深夜定稿;用户终裁「export 可以改,condition 就不用了」——condition 整体降备选)
总判定(v1 主线)
**类型隔离 = 独立 client tsconfig(file-set 从 client 入口出发 + types: [])+ ./client、./shared 纯 exports 子路径(零条件解析)+ gate-api 符号溯源。**condition 方案不在主线(降级理由见下节)。职责划分:
- 独立 program + 纯子路径承担日常隔离:client 代码只 import
./client/./shared子路径,闭包天然干净;types: []让偷渡进来的node:import 在案发文件当场红。 - gate-api 白名单检查是「client 误 import 主入口」的唯一防线(责任加重)。新增实证(echo-d):普通主入口(无条件、无 node: import 的最坏情形)被 client import 时 tsc 全静默零报错(exit=0 实测)——没有任何配置层能拦,gate-api 精确抓出
Context.echoD <- echo-d/src/node.ts。 - 双端 diff 模式照做(对等 Loader 验收预埋)。
【备选记档,v1 不采】condition 方案完整可行性证据(A 批实证,原样保留)
降级理由(用户裁决):
./client、./shared本来就是纯子路径解析,condition 只防「client 误 import 主入口」一个场景,gate 符号溯源本就抓得住——不值得付「每个双端包主入口改两层形态 + 全仓 tsconfig.base 加 customConditions」的配置税。触发条件=主入口误用成为高频痛点时启用;届时以下证据直接可用。
布局(echo-a/echo-b 实测,./node 子路径已按裁决取消):
// 双端包:node 半边住现有 "." 主入口,只新增 ./client ./shared
"exports": {
".": {
"dsh-node": "./src/node.ts", // 转正时: { types: lib/types/node.d.ts, default: lib/node.js }
"default": "./lib/node.js" // 纯运行时产物,无邻接 .d.ts —— additive 关键(见下)
},
"./client": "./src/client.ts", // 转正时: types→d.ts + default→tsdown bundle(蓝图 §5)
"./shared": "./src/shared.ts"
}
- 拦截:client program(tsconfig 无 customConditions)import
@dsh-spike/echo-b→ TS2307 cannot find module(纯 gate 形态)或 TS7016 找不到声明文件(两层形态,strict 仓库必红)。错误就在违规 import 那一行,IDE 同一份 tsconfig 同一个错。 - node 侧:tsconfig
customConditions: ["dsh-node"]→ 同一 specifier 解析到 node 半边源/类型,全绿。 - additive 硬约束(重要发现):
"."只挂dsh-node条目会破坏存量运行时——plain node(无--conditions)解析主入口直接ERR_PACKAGE_PATH_NOT_EXPORTED。必须两层:default指向纯运行时 lib 产物(不带邻接 .d.ts),实测 plain node 解析结果与今天完全一致;node --conditions=dsh-node才切换。推论:全仓 tsconfig.base 要加一行customConditions: ["dsh-node"](node 侧类型挂条件下之后,现有 typecheck 才能继续看到类型;解析目标不变,纯 additive),node 运行时启动脚本加--conditions=dsh-node(或运行时继续走 default 的 lib 产物,两可,转正时定)。
逃逸/误用通道实证(B 批 + echo-d 补充)——独立 program 拦不住的面,gate 的存在理由
| 通道 | 结果 | 处置(已拍部分标注) |
|---|---|---|
主入口误用(echo-d:普通 "."、无 node: import 的最坏情形) |
client program 里解析成功、tsc 全静默零报错,增补直接进 program | gate-api 唯一防线(实测精确归源);condition 备选留作痛点触发 |
./src/* 出口(仓库惯例) |
直达 node 半边源码,污染发生;types: [] 让 node: import 在案发文件当场红(半 loud) |
已拍:双端包不给 ./src/* 出口(都是新包,零成本;存量包通配不动) |
tsconfig paths 映射包名 |
完全绕过 exports;目标无 node: import 时全静默 |
client tsconfig 禁止 paths 指到包内部(vendor 四包除外,纪律)+ gate 兜 |
| .d.ts 增补传染(echo-c 模拟 pr-gates 双条件布局 types→lib/*.d.ts) | import type 一个 types 解析到含 declare module 'cordis' 的 .d.ts 的包,增补照样进 program(tripwire TS2578 坐实),全静默无平台报警 |
纯类型子路径的 .d.ts 必须从零增补源独立发射(硬约束,已拍);gate-api 能溯源 .d.ts 来源兜底 |
gate-api.mjs(v1 主线三大件之一,用户点名的 TS API 符号溯源机制)
进程内 ts.getParsedCommandLineOfConfigFile → ts.createProgram,零子进程。主检查=溯源本质:枚举 program 里所有归并进 cordis 作用域的 Context/Events interface 声明(源文件 interface、declare module 'cordis' 增补、.d.ts 增补一视同仁),输出「key → 来源文件」表,与 client-safe 白名单比对。实测输出样例:
- 干净 client program:34 key 全归源 vendor/cordis + vendor/timer → PASS。
- 偷渡场景:精确报
Context.echoB <- poc/echo-b/src/node.ts;.d.ts 场景报Context.echoC <- poc/echo-c/lib/types/node.d.ts。 - apiproxy 场景:31 条违规逐 key 归源(
Context.llm/sessions/agents/...+Events.agent/*全表)。 - diff 模式(
--diff client.json node.json):双端 key→来源表 + 对等报告(shared 32 / client-only timer / node-only llm、sessions、echoB…)——对等 Loader(蓝图 §4)的验收可直接吃这张表。
转正建议:scripts/verify-client-closure.mjs(JS import typescript,无子进程),CI 挂 typecheck 之后。v1 主线下它的职责清单(比 v2 设想重):主入口误用的唯一防线 + src/*(存量包)、paths、.d.ts 三条逃逸 + 对等性报告。IDE 内无它也有基本体验(独立 program 挡住增补可见性、types:[] 挡平台 API),但「误 import 主入口」要等 gate 跑才红——这是 v1 主线接受的取舍,痛点化则启用备选 condition 方案。
IDE 故事(poc/ide/ 实测;v1 主线下依然成立的部分 + 备选增量)
v1 主线部分:ide/client/ 与 ide/node/ 各带目录级 tsconfig.json,tsserver 按 nearest-config 规则给同窗口两个文件绑不同 program——红线「同窗口不串 program」由 tsconfig 目录边界天然保证(web app 源码目录自带 client tsconfig,与 vite 项目常态一致)。client 文件里 ctx.sessions 现场红(增补不可见)、偷渡文件的 node: import 现场红(types:[]),两个目录 tsc 双绿复验过。v1 编辑器体验的缺口:「误 import 主入口」不红(echo-d 实证),等 gate 跑才报——已记入取舍注记。备选 condition 方案的增量正是把这个缺口也变成现场红(TS2307/TS7016,ide/ 实测过,证据留档);@ts-expect-error 负例形态注记见 ide/client/main.ts 头注。
三入口 exports 转正形态(v1 主线定稿)
"."主入口 = node 半边,完全维持现状(普通 types/default 布局,无条件、无两层形态、存量零改动)。- 新增
./client:{ types: ./lib/types/client.d.ts, default: ./dist/client.js }(tsdown bundle、external cordis,蓝图 §5);该 d.ts 及其引用链必须零 node 增补(echo-c 教训,已拍为硬约束)。 - 新增
./shared:类型+creator;消费方 import 必须import type(四问裁决①),creator 由框架装配点消费。 - 双端包不设
./src/*出口(已拍);存量包./src/*通配不动(老规矩延续)。 - 取舍注记(如实记录):v1 主线下「client 误 import 主入口」在编辑器里不报错(echo-d 实证全静默),gate-api 是唯一防线;备选 condition 方案(含两层 additive 形态、tsconfig.base 加 customConditions 的完整证据)留档于上节,触发条件=该误用成为高频痛点。
复跑索引(poc/ 下)
tsc=../../../../node_modules/.bin/tsc
# —— v1 主线 ——
$tsc -p tsconfig.client.json # ① 干净 client:GREEN
$tsc -p tsconfig.v1-misuse.json # ② 主入口误用(echo-d):GREEN=全静默污染实证
node gate-api.mjs tsconfig.v1-misuse.json --expect-fail # ③ gate 抓出 Context.echoD(唯一防线)
$tsc -p tsconfig.escape-src.json # ④ src/* 逃逸:RED(污染+node:报警)→ 已拍双端包不开此出口
$tsc -p tsconfig.escape-paths.json # ⑤ paths 逃逸:RED(tripwire TS2578)
$tsc -p tsconfig.escape-dts.json # ⑥ .d.ts 传染:RED(tripwire TS2578)
node gate-api.mjs tsconfig.client.json # PASS,34 key 溯源表
node gate-api.mjs tsconfig.escape-dts.json --expect-fail # 抓 .d.ts 增补
node gate-api.mjs tsconfig.client-apiproxy.json --expect-fail # apiproxy 31 违规逐 key 归源
node gate-api.mjs --diff tsconfig.client.json tsconfig.node.json # 对等报告
$tsc -p ide/client/tsconfig.json && $tsc -p ide/node/tsconfig.json # IDE 故事双绿
# —— 备选 condition 方案证据(v1 不采,留档)——
$tsc -p tsconfig.client-violation.json # 拦截正例:GREEN(echo-b 两层形态 TS7016、echo-a 纯 gate 形态 TS2307,均被 @ts-expect-error 消费;该 program 不含 vendor src 故 noImplicitAny 全开=生产条件)
$tsc -p tsconfig.node.json # node 侧(dsh-node 条件):GREEN
node --input-type=module -e "console.log(import.meta.resolve('@dsh-spike/echo-b'))" # → lib/node.js(default,additive 证明)
node --conditions=dsh-node --input-type=module -e "console.log(import.meta.resolve('@dsh-spike/echo-b'))" # → src/node.ts
假包一览:echo-a(双端三入口,. 挂 dsh-node=备选形态样本)、echo-b(node-only+条件+两层 additive=备选形态样本)、echo-c(.d.ts 增补传染)、echo-d(普通主入口=v1 主线误用样本)。旧产物留档对照:gate.mjs(listFilesOnly 文件集方案)、clean-program-files.txt。