@sema-agent/server 7.143.0 → 7.144.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/MIGRATION.md +166 -0
  2. package/USAGE.md +24 -14
  3. package/dist/approval-card.d.ts +31 -14
  4. package/dist/approval-card.js +8 -8
  5. package/dist/boot/execution-env.js +1 -1
  6. package/dist/boot/runner-deps.d.ts +10 -5
  7. package/dist/boot/runner-deps.js +5 -4
  8. package/dist/boot/shutdown.d.ts +1 -1
  9. package/dist/boot/shutdown.js +4 -2
  10. package/dist/boot/stage-01-config.js +2 -0
  11. package/dist/boot/stage-07-capability-layer.js +1 -1
  12. package/dist/boot/stage-10-http-server.js +2 -7
  13. package/dist/capabilities/scenarios.d.ts +14 -18
  14. package/dist/capabilities/scenarios.js +1 -2
  15. package/dist/config-d-migration.d.ts +2 -0
  16. package/dist/delegated-session-owner.d.ts +33 -151
  17. package/dist/delegated-session-owner.js +1 -83
  18. package/dist/entry-mode-clamp-notice.d.ts +1 -1
  19. package/dist/execution-placement.d.ts +7 -4
  20. package/dist/hooks/classic-stations.d.ts +3 -1
  21. package/dist/hooks/classic-stations.js +2 -0
  22. package/dist/http/principal-gate.d.ts +1 -1
  23. package/dist/http/refusal-body.d.ts +7 -3
  24. package/dist/http/refusal-body.js +1 -0
  25. package/dist/http/resume-legs.js +11 -4
  26. package/dist/http/routes/approvals-assistant.js +1 -0
  27. package/dist/index.d.ts +1 -0
  28. package/dist/index.js +1 -0
  29. package/dist/lane-home-probe.d.ts +15 -0
  30. package/dist/lane-home-probe.js +4 -0
  31. package/dist/observability/corrupt-read-seat.d.ts +5 -9
  32. package/dist/observability/corrupt-read-seat.js +1 -2
  33. package/dist/observability/fail-open.d.ts +12 -8
  34. package/dist/observability/fail-open.js +12 -8
  35. package/dist/observability/run-terminal.d.ts +1 -1
  36. package/dist/observability/run-terminal.js +2 -2
  37. package/dist/parked-revive-surface.d.ts +6 -7
  38. package/dist/peer-inbound-face.d.ts +29 -34
  39. package/dist/peer-inbound-face.js +28 -63
  40. package/dist/plugins/caching-session-store.d.ts +0 -3
  41. package/dist/plugins/caching-session-store.js +0 -3
  42. package/dist/plugins/checkpoint-store-sql.d.ts +41 -34
  43. package/dist/plugins/checkpoint-store-sql.js +99 -38
  44. package/dist/plugins/fork-routing-session-store.d.ts +8 -1
  45. package/dist/plugins/fork-routing-session-store.js +6 -0
  46. package/dist/plugins/local-checkpoint-store.d.ts +6 -8
  47. package/dist/plugins/local-checkpoint-store.js +3 -2
  48. package/dist/plugins/local-session-store.d.ts +64 -29
  49. package/dist/plugins/local-session-store.js +126 -56
  50. package/dist/plugins/local-session-window.d.ts +4 -2
  51. package/dist/plugins/one-time-migrations.d.ts +12 -12
  52. package/dist/plugins/one-time-migrations.js +12 -12
  53. package/dist/plugins/pg-session-storage.d.ts +1 -9
  54. package/dist/plugins/pg-session-storage.js +2 -19
  55. package/dist/plugins/remote-env-host.d.ts +8 -4
  56. package/dist/plugins/remote-env-host.js +7 -9
  57. package/dist/plugins/remote-shell.js +12 -2
  58. package/dist/plugins/schema-consistency.d.ts +5 -1
  59. package/dist/plugins/session-fork.d.ts +1 -1
  60. package/dist/plugins/session-ownership.d.ts +9 -0
  61. package/dist/plugins/session-ownership.js +6 -0
  62. package/dist/plugins/session-store.d.ts +13 -16
  63. package/dist/plugins/session-store.js +9 -10
  64. package/dist/plugins/sql-json-column.d.ts +24 -2
  65. package/dist/plugins/sql-json-column.js +38 -3
  66. package/dist/plugins/store-backend.js +3 -2
  67. package/dist/plugins/tidb-session-store.d.ts +2 -14
  68. package/dist/plugins/tidb-session-store.js +2 -20
  69. package/dist/run-local.js +2 -1
  70. package/dist/runs.js +4 -0
  71. package/dist/security.d.ts +0 -4
  72. package/dist/session-error-wire.d.ts +24 -25
  73. package/dist/session-error-wire.js +4 -5
  74. package/dist/session-incarnation.d.ts +2 -2
  75. package/dist/session-notify-door.d.ts +2 -2
  76. package/dist/session-notify-door.js +5 -1
  77. package/dist/tool-approval.d.ts +3 -1
  78. package/dist/tool-approval.js +47 -18
  79. package/dist/trace/core-keyset-guard.d.ts +1 -1
  80. package/dist/trace/engine-notice-wire.d.ts +1 -1
  81. package/dist/trace/engine-notice-wire.js +1 -1
  82. package/dist/trace/ledger-sink.js +12 -4
  83. package/dist/trace/model-segment.d.ts +25 -6
  84. package/dist/trace/model-segment.js +47 -6
  85. package/dist/trace/project.d.ts +8 -2
  86. package/dist/trace/project.js +6 -4
  87. package/dist/trace/redact.d.ts +11 -0
  88. package/dist/trace/redact.js +19 -3
  89. package/dist/trace/self-minted-wire.d.ts +88 -9
  90. package/dist/trace/self-minted-wire.js +25 -3
  91. package/package.json +2 -2
  92. package/wire-changes/server@7.144.0.json +291 -0
  93. package/dist/session-records-skipped-notice.d.ts +0 -21
  94. package/dist/session-records-skipped-notice.js +0 -27
package/MIGRATION.md CHANGED
@@ -7,6 +7,172 @@
7
7
  > ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
8
8
  > 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。
9
9
 
10
+ ## 7.144.0 —— 🔴 会话日志回放丢了记录:用户面通告只剩引擎的 `session.lineage_rerooted`(`session.records_skipped` 退役)、丢了分支上祖先的会话从断点续上、走不成一棵树的已存日志答 `corrupt_session`(打开面 `invalid_session` 退役)(wire BREAKING:一个通告码与一个错误码退出;行为修复;部署零动作) · 跨会话来信有序关停:info 行字段改为 `{ queues, cards }`、回执没落进发信方的箱每条一行 warn(运维面;日志形变更) · 引擎在没有自己席的地方说的话落结构化 `engine_notice` 日志行,不再是裸 `console.warn` 句(运维面;日志形变更) · 🔴 `DB_BACKEND=local`:数据根里的会话元数据边档本构建读不出(更新的引擎写下 / 损坏 / 读不了)⇒ `ensureSchema()` 拒启 `config.schema_mismatch`,开始服务之前判(行为修复;fail-closed;持久面;零 DDL) · SQL 后端:更新引擎停驻的行续跑 / 决断答 409 `checkpoint.unsupported_version`(不再答 `conflict.checkpoint_unreadable`)、运维队列行与助手收件箱行带 `rowVerdict`(wire 拒码改答 + 加性;部署零动作) · 持久问店车道上同一只审批问被另一腿再问时,「这一问要人」取行上那张卡与这一腿请求的并集(安全;行为收紧;wire 零形变;部署零动作) · 🔴 引擎 7.62.0:部署的 mod 包按内核契约 8 重建(部署面;BREAKING) · 远端五车道的私有暂存目录多一道 ACL 核:继承来的 ACL 条目去不掉 / 读不出 / 目标机系统不在闭集里 ⇒ 不暂存,退回就地写目标(行为收紧,fail-closed;安全;部署零动作) · 🔴 活流文字段的脱敏单位收成段(与账本 `text` 行同一个):引擎的 `text_end` 是文字块的交付点、不收段,锚与值分在同一段两块时 `text_delta` / `text_end` 照样遮;更正 7.143.0 节第 11 条「文字段的单位是引擎的文字块」一句(wire 值语义;安全;部署零动作) · 🔴 `DB_BACKEND=local`:仍带属主戳的存档的拒有了自己的码 `conflict.session_archive_stamped`,fork 与改日志的各形推送(含载荷分叉的 fast_forward)同码同句(wire BREAKING:这两扇写口上带戳存档的拒由 `conflict` 改新码;行为修复;部署零动作) · 更正 7.143.0 节五处说法:退役工具名写进 deny / ask 规则时按名单的来源各答各码(请求体 422 `request.unprocessable` · 部署名单拒启 · `config.legacy_tool_name` 只来自存量会话规则)、`decidedVia` 在直连门部署上哪几口不收、续跑门 `/resume` 的真路由、user 层要根的 allow 免不了配置根下写保护表上的写、被拒的个人层抹除与点名个人层的只读也会在盘上留下一只从没写过的个人店(文书;零行为变化) · host 车道:执行环境的家目录主张证不了 ⇒ 环境在开跑前就不铸,run 失败终局码 `env.home_unconfirmed`(同步提交腿 409),命令带不带路径同一首拒;worktree 的 git 基环境不再带家目录主张;更正 7.140.0 节第 12 条 ③(wire 新码;行为修复;部署零动作)
11
+
12
+ 1. **🔴 `DB_BACKEND=local`:会话日志回放丢了读不出的记录 —— 用户面只剩一只帧 `session.lineage_rerooted`(引擎铸),本服务自铸的 `session.records_skipped` 退役;丢了分支上祖先的会话从断点续上;走不成一棵树的已存日志答 `corrupt_session`,打开面不再出现 `invalid_session`(wire BREAKING;行为修复;部署零动作)**:
13
+ 旧形(7.143.0,core 7.61.0):打开一只回放跳过了记录的会话时,只有本服务自铸的一帧 `session.records_skipped`(`{ dropped, entriesKept, sessionId }`,每次打开一帧、会话缓存命中的轮次不说);引擎不发 `session.lineage_rerooted`(那一帧从 core 7.62.0 才有)。
14
+ 被跳过的记录若是分支上的祖先,这一轮失败(`invalid_session`:同步口 500、流式终帧 failed、后台 run 行 failed),会话再也跑不动;读口与提交口对走不成一棵树的已存日志答 500 `invalid_session`。
15
+ 新形:只剩 `session.lineage_rerooted`(`detail: { break, recordsDropped, recordsTotal, leaf, entriesOnPath, sessionId, runId }`,`break: true` 另带 `{ missingParentId, rootedAt, entriesCut }`),每条腿一帧(会话缓存命中的轮次照样说)。
16
+ 丢了祖先的会话从断点续上(`break: true`),这一轮跑完(同步口 200)。回放没丢记录的断开(悬空父 / 自指 / 成环 / 叶子缺席)答 `corrupt_session`(与存档读不出同码、同一个客户端动作);
17
+ `invalid_session` 只剩会话同步导入面(答 `422 invalid_entries`,不变)。
18
+ 已知差异:进程已持有这只会话的活存储时盘上才坏的行,这一轮读的是内存里那份完整历史 ⇒ 不发帧(此前每次打开一帧),下一次起服后的第一轮才发;
19
+ 丢了祖先的会话 run 续上了,而会话视图(`GET /v1/sessions/:id` / `…/memory-status`)对同一只断开照答 500 `corrupt_session`。
20
+ 运维面不变:回放跳过的每条记录照样计 `fail_open_total{tag="server.session-repo.corrupt-record-skipped"}`、逐条进探针文件(`SEMA_FAILOPEN_PROBE`)。执行放置回填(一次性迁移)点名读不出存档的 `fault` 闭集收成 `corrupt_session` / `storage`。
21
+ **谁需要**:文件后端上存档坏了的会话的用户(会话还能跑;一件事一只帧)。**谁受伤**:按 `session.records_skipped` 渲染、或按打开面 `invalid_session` 分支的消费方。
22
+ **补偿**:同一事实改读 `session.lineage_rerooted`(`recordsDropped` = 旧 `dropped`;`entriesOnPath` 是 run 上下文里保留的条目数,不等于旧 `entriesKept` 的全部幸存条目);结构坏改读 `corrupt_session`
23
+ (别重试;换一个新会话、从断点之上的条目分叉,或请运维修好 / 移走那份日志)。
24
+ **升级前做**:无。**回滚**:回到上一版 ⇒ 回到只有 `session.records_skipped` 那一帧(上一版的引擎不发 `session.lineage_rerooted`)、丢了祖先的会话重新失败(`invalid_session`);落盘数据不受影响(本版不写任何新形)。
25
+
26
+ 2. **跨会话来信的有序关停改为结清进程里的每只 held 队列,并等结清回执落进发信方的箱(运维面;日志形变更;部署零动作)**:
27
+ 旧形:info `peer_inbound_settled_at_shutdown` 的字段是 `{ queues, scopeUnresolved }`(`queues` = 结清了的、scope 求得出的队列数;`queues > 0` 或 `scopeUnresolved > 0` 才发);关停按会话行的属主求每只队列的 scope,求不出的计进 `scopeUnresolved`、那只队列不结清;结清回执投出即返。
28
+ 新形:字段改为 `{ queues, cards }`(`queues` = 注册表里的每只队列、含空的;`cards` = 关停那一刻快照里的卡数;只在 `cards > 0` 时发;`scopeUnresolved` 退役:关停读引擎的队列枚举,不再求 scope);回执没落进发信方的箱 ⇒ 每只有回执没落箱的队列一行 warn `peer_inbound_shutdown_receipt_unlanded`(带引擎那句话,点名该队列所有没落箱的 seq,同一只队列两条都没落是一行里列两个 seq;卡照样已结清)。
29
+ 兜底计数 `server.peer-inbound.notice-coordinates-unreadable` 退役,新计数 `server.peer-inbound.queue-box-unknown`(按构造不可达的一形:队列的箱既不是会话自己的箱、也不是收件箱)。
30
+ **谁需要**:滚动发布与重启时关停的运维 —— 关停真的等结清回执落进发信方的箱,落不进的按队列一行 warn 点名 seq。
31
+ **谁受伤**:按 `scopeUnresolved` 或旧计数告警的运维。**补偿**:回执没落箱改读 `peer_inbound_shutdown_receipt_unlanded`。**升级前做**:无。**回滚**:回到上一版即恢复旧字段。
32
+
33
+ 3. **引擎在没有自己通告席的地方说的话(品牌模型构造 / 一次请求的路由读 / 对端拨号 / 直建蒸馏座)改落结构化 `engine_notice` 日志行,不再是裸 `console.warn` 句(运维面;日志形变更;部署零动作)**:
34
+ 典型一形:`MODEL_API_KEY` 不足 8 字符时,起服日志里只剩本服务那一行结构化 `own_credential_value_not_withheld`,引擎那一行裸句消失。HTTP 服务与本机单次运行两个入口在日志接上之后装这只进程缺省席;装席之前的几步(读配置)引擎若说话仍是裸句。
35
+ **谁需要**:把 stderr 当 JSON 行流收的日志管道(引擎裸句不再混进来)。
36
+ **谁受伤**:按引擎裸句子串告警的运维。**补偿**:同一事实改读 `engine_notice` 行的 `code`。**升级前做**:无。**回滚**:回到上一版即恢复裸句。
37
+
38
+ 4. **🔴 `DB_BACKEND=local`:数据根里的严格边档(今天一只:`session-incarnations.json`)本构建读不出 ⇒ 起服拒 `config.schema_mismatch`,在 `ensureSchema()` 里、开始服务之前判(行为修复;fail-closed;零 DDL;部署零动作)**:
39
+ — **此前**:没有这只码。严格读在第一次用到那只边档时抛一句无码的拒因(`… is unreadable (malformed) — refusing to read or write session owners and incarnations …`),落点随路径漂:
40
+ 起服一次性迁移读到这只边档的(7.141.0 起迁移窗内每次起服都读;7.139.0 / 7.140.0 在数据根里有 run 的会话时读)以它拒启;起服路径没读到它的(例:只调 `ensureSchema()` 的嵌入方)起得来,每个碰到会话元数据的请求答 500 `internal.error`。
41
+ 起服之后另一只写者写进来的、本构建读不出的字节,会被本进程下一次写静默盖掉。
42
+ — **本版**:`ensureSchema()`(先于 M33 快照消化与任何请求)把这只边档从盘上读一遍(同一进程里先前已载入的也重读),读不出 ⇒ 拒启,码与 SQL 后端列册对账同一个 `config.schema_mismatch`,拒句同形:
43
+ 点名文件、种别与不认得的键名,方向句「the data root is NEWER than this build」,出路三条 —— 部署写下它的那一版;回滚 = 恢复升级前的数据根备份(字节损坏同此出路);读不出字节(例:`EACCES`)⇒ 修好读权限 / 盘 / 挂载再起,文件不用动。
44
+ 检查只读,文件原地不动。没设 `DB_BACKEND`(缺省推导的 local)同样拒启,不退回内存。
45
+ 起服之后这只边档第一次被碰时才读不出(嵌入方跳过了 `ensureSchema()`;或起服后另一只写者把它写成本构建读不出的形 —— 每次写之前先读盘上现有字节)⇒ 同一只拒(同码);
46
+ 读不出的是形 ⇒ 这只边档在本进程里此后每一读每一写都拒(重启才重判),碰它的请求失败(经全局兜底的路由答 500 `internal.error`、运维日志一行 error 级 `request_error` 带本码);盘上字节一个不盖。
47
+ 读不出字节(I/O)只让那一次读写失败,不粘。
48
+ 嵌入方:包根多导出一只类 `LocalDataRootSchemaMismatchError`(`code` = `config.schema_mismatch`;`findings` 逐文件 `{ path, label, problem, unknownFields }`)与它的类型 `LocalSidecarMismatch`,与 SQL 后端的 `RetiredSchemaPresentError` 同码。
49
+ **谁需要**:`DB_BACKEND=local` 的部署与壳起的本机引擎 —— 降级附着(新引擎写过的根交给旧一些的引擎)拒在开始服务之前、带机读码,拒句即出路。
50
+ **谁受伤**:只调 `ensureSchema()` 的嵌入方遇到读不出的会话元数据边档,此前起得来(只有碰它的请求失败),现在拒启;按旧拒因句子写的告警失配。
51
+ **补偿**:拒句点名文件、种别与出路;判只读,文件原地不动;告警改按码 `config.schema_mismatch`。≤ 7.143.0 的引擎附着更新引擎写过的根时照旧按各自的旧路径失败(已发版本改不了),规则不变:**不许降级附着;回滚 = 恢复升级前的数据根备份**。
52
+ 订正 7.143.0 节第 43 条:「服务进程本身起得来」只对起服时不读这只边档的引擎与路径成立(7.141.0 起起服一次性迁移在窗内每次起服都读它;7.139.0 / 7.140.0 在数据根里有 run 的会话时起服回填读它;读不出即以无码的拒因拒启);规则与出路不变。
53
+ **升级前做**:无。**回滚**:回到上一版即回到上一版的落点;本条不改任何持久数据。
54
+
55
+ 5. **`DB_BACKEND=mysql|pg`:更新引擎停驻的审批行,续跑 / 决断答 409 `checkpoint.unsupported_version`,不再答 409 `conflict.checkpoint_unreadable`;运维队列行带 `rowVerdict`(wire:一处拒码改答 + 加性键;行为修复;部署零动作)**:
56
+ 旧形:滚动升级 / 回滚中,一行由更新的引擎停驻(格式版本超出本副本的上限),本副本 SQL 店的 `get()` 在解码之前按格式版本列先行拒:续跑 / 决断答 409 `conflict.checkpoint_unreadable`,体里 `error` 是店内句子
57
+ 「checkpoint format version N is newer than this build supports (max M) — upgrade the service to resume it」;运维队列(`GET /v1/approvals` / `/stream`)上这张卡与可批的卡同形。
58
+ 新形:行照常读出,续跑第一级由引擎的判官拒 —— 409 `checkpoint.unsupported_version`、体顶层 `reason: "version_newer"`(引擎 `detail.reason` 原值过境;行留 pending、字节不动;`retriable` 与两版本键照该码的既有读法,与 LOCAL 后端同一答)。
59
+ 运维队列行带 `rowVerdict`(引擎判官对这一行的判决;`{ "kind": "lacking", "code": "checkpoint.unsupported_version", "reason": "version_newer", "sentence": … }` 这一形就是上面那一行),与助手收件箱行同名位同一答(收件箱行本版起也带这一位,两只后端同读);
60
+ 缺席(存储体解不出 / 两次读之间行离开待决或字节变了)不是 `ok`。`conflict.checkpoint_unreadable` 不再由格式版本产生,剩下的是读这一行时抛出的故障(存储体解不出、库读故障等)。流内审批找停驻门时,判官答 `lacking` 的行本副本不绑(留给能续它的副本)。
61
+ 运维面:SQL 后端起服后第一次列出审批队列时把待决行的存储体各读一次(之后按存储体摘要记忆,字节不变不再读);存储体解不出的行照列、不带 `rowVerdict`,计 `fail_open_total{tag="server.approvals.park-row-blob-unreadable"}`。
62
+ **谁需要**:运维(队列上看得出哪张卡本引擎续不了);混版车队(那张卡留给能续它的副本)。**谁受伤**:SQL 后端上按 `conflict.checkpoint_unreadable` 的句子判「版本太新」的消费方。
63
+ **补偿**:改读 `checkpoint.unsupported_version` 体顶层的 `reason`,或在列表上先读 `rowVerdict`。**升级前做**:无。**回滚**:回到上一版即恢复旧拒形;落盘数据不受影响(本版不写任何新形)。
64
+
65
+ 6. **持久问店车道:同一只审批问被另一腿再问(换副本重入 / 混版窗)时,「这一问要人」取行上那张卡与这一腿请求的并集(安全;行为收紧;wire 零形变;部署零动作)**:
66
+ 旧形:重入腿的呈卡帧采信行上那张卡,判据只读这一腿的请求 —— 行上要人而这一腿请求没带时,`allow_session` 记下会话放行、`decidedBy:"policy"` 的放行被收下、旧族 `tool_approval` 帧与
67
+ `livePending[]` 行不带 `requiresRealApproval` / `rememberRefused`;行上不要人而这一腿请求带时,呈卡帧的 `card.risk.requiresRealApproval` 说 `false`。
68
+ 新形:两者任一说要人即按要人 —— `allow_session` 只放行这一次(`rememberApplied:false`)、策略的放行答 400(卡还在)、两族帧与列表行同说要人、提示位亮;
69
+ 同一副本上同一只问的重复登记同值(应答口按最早那条答);行不回写(重放帧与耐久问回决口照读行)。
70
+ **谁需要**:多副本 / 滚动升级部署上答审批卡的人与按卡作答的策略客户端。**谁受伤**:依赖重入腿上要人卡被「本会话放行」记住的调用方。**补偿**:不需要(人照常逐次放行这一次)。
71
+ **升级前做**:无。**回滚**:回到上一版即恢复旧读法(本版不写任何新形,落盘数据不受影响)。
72
+
73
+ 7. **🔴 引擎 7.62.0:部署的 mod 包按内核契约 8 重建(部署面;BREAKING)**:
74
+ — **此前**:mod 内核契约号是 7。
75
+ — **本版**:引擎只加载声明 `"contract": 8` 的 mod 包(读包者按相等判):按契约 7 写的包在起服被拒 `config.mods_invalid`(`MODS_PACKAGES` 的那一席,拒句点名两个契约号)。
76
+ 8 = 扩展内核闭集收成参照实现的 145 名:`session.start` / `turn.start` / `session.measure` / `prompt.submit` 等事件开站(`session.start` / `session.end` 的底升 `classic.SessionStart` / `classic.SessionEnd`)、
77
+ `$.session.turns` / `usage` / `version` 有底、`session.messages` 收 `{ agentId }`。扩展在 `prompt.submit` 上改写过的输入,模型看到改写后的文本,它的 `human_input` 帧带 `rewrittenBy`(改写它的扩展注册名,按改写序;缺席 = 原样送达)。
78
+ 请求 `settings.hooks` 里的 `SessionStart` / `SessionEnd` 照收、不在服务端挂(壳属主事件,与 `PermissionDenied` / `SubagentStart` / `SubagentStop` 同一读法)。
79
+ **谁需要**:写扩展改写 / 审阅输入、观察腿生命周期的部署。**谁受伤**:装了按契约 7 写的 mod 包的部署(起服拒);没声明 `MODS_PACKAGES` 的部署零动作。
80
+ **补偿**:mod 包把 `hooks.json` 的 `"contract"` 改成 8 并按新契约重建(新站点与 `$` 方法见引擎文档)。**升级前做**:装了 mod 包的部署先备好契约 8 的包。**回滚**:回到上一版须同时换回契约 7 的包。
81
+
82
+ 8. **远端五车道的私有暂存目录多一道 ACL 核:读到继承来的 ACL 条目就去掉,去不掉 / 读不出 / 目标机系统不在闭集里 ⇒ 不暂存,退回就地写目标(行为收紧,fail-closed;安全;部署零动作)**:
83
+ 旧形:私有暂存目录收紧后只读回模式位与属主。目标机是 macOS、且工作区目录带可继承的原生 ACL 允许条目(`directory_inherit`)时,新目录一建出来就带着条目,读回 `700` 也挡不住 ——
84
+ 暂存件(替换 0600 的目标时,新内容)对条目所点的身份可见;FreeBSD / illumos ZFS 的 NFSv4 ACL(`aclmode=passthrough`)同类。7.139.0 节第 23 条 ⑧ 末尾把它写成已知缺口,就私有目录自身而言自本版起不再成立(已发节原文不改)。
85
+ 新形:读回模式与属主之后,再在**目标机**上按 `uname -s` 读目录的 ACL 条目,闭集四臂 —— Linux(POSIX ACL 由模式位读回的掩码覆盖,不另读)· Darwin(`ls -led` 读、`chmod -N` 去)· FreeBSD(`ls -ld` 的 `+` 读、`setfacl -b` 去)·
86
+ SunOS / illumos(同读、`chmod A-` 去)。读到条目就去、去完再读,读不到才算私有;读不出、去不掉、去完仍读到、`uname` 读不出或名字在闭集之外 ⇒ 这一次写不暂存,退回就地写目标
87
+ (与第 23 条 ⑧ 同一条退路:非原子;ssh / local-docker / k8s 写进原 inode、权限位不变,e2b 的 `files.write` 与 adb 的 `adb push` 整份写目标、inode / 权限位由对端定),遥测计 `server.remote-write.stage-seal-fallback`。
88
+ 整文件替换提交时跟一跳符号链接到别的目录重新暂存的那只私有目录走同一只判。wire 零变化。
89
+ 覆盖面:只管五条远端车道的私有暂存目录;host 车道的暂存是引擎的、device 车道的是设备侧执行器的,本版不动;Linux 上经 NFS 挂载、由服务端自己执行的 NFSv4 ACL 在客户端读不出条目,Linux 臂按掩码读法算过;
90
+ FreeBSD / illumos 两臂按系统文档写、未上真机,工具输出与预期不符(退出状态不成、往 stderr 报了一句、头一行不是权限串、多出一行)时同样退回就地写;读 ACL 的工具若自己静默吞掉读取错误(macOS 的 `ls -le` 没有第二条读路),读到的是「没有条目」,本版看不出;
91
+ 替换之后目标不带原件自己的 ACL 条目(整文件替换换 inode)本版不改。
92
+ **谁需要**:ssh 车道上目标机是 macOS / FreeBSD / illumos、且工作区目录带可继承 ACL 条目的部署(其余四条车道的目标是 Linux 容器或 Android 设备,走 Linux 臂)。
93
+ **谁受伤**:ssh 目标机系统不在 Linux / macOS / FreeBSD / illumos 之内的部署(OpenBSD / NetBSD / AIX / Cygwin 一类):这类目标上每一次写都改走就地写(与 7.138.x 同形,非原子),`stage-seal-fallback` 计数非零。
94
+ **补偿**:就地写与 7.138.x 逐字同形、写照常成功;计数点名目录;要原子替换就把工作区放到被支持的系统上。macOS / FreeBSD / illumos 目标上继承来的条目去得掉的照常暂存,无需动作。
95
+ **升级前做**:无。**回滚**:回到上一版即恢复旧形(私有目录上继承来的条目不再被去);暂存名是瞬时的,零数据面动作。
96
+
97
+ 9. **🔴 活流文字段的脱敏单位收成段(与账本 `text` 行同一个):引擎的 `text_end` 是文字块的交付点、不收段 —— 锚与值分在同一段的两个文字块里时,`text_delta` 与 `text_end` 照样遮(wire 值语义;安全;帧数 / 帧序 / 身份零变化;部署零动作)**:
98
+ 旧形(7.143.0):`POST /v1/tasks/stream`(顶层与转发子代腿)与子代 tail 面上,文字段的流式口在引擎每个 `text_end` 上收掉重开,`text_end.content` 是引擎那一块孤立地过脱敏器;
99
+ 账本 `text` 行(`GET /v1/runs/:id/events` / turns)按段把同一段的几块拼起来判 —— 锚在块 1(`password=`)、值在块 2 时,活流与 `text_end` 带值明文、账本遮;
100
+ 账本行只拼逐块帧字节,只有 `text_end`(没有逐块帧字节)的块不在行里。
101
+ 7.143.0 节第 11 条「文字段的单位是引擎的文字块 … 此前 `text_end` 上就是这样」一句自本版起不再成立(已发节原文不改)。
102
+ 新形:文字段与推理段同一条状态边界 —— 段(段界谓词 + 腿终局 / 子代终态),= 账本 `text` 行的单位。引擎的 `text_end` 是这一块的交付点:块里还在窗里的字节按整段读法、文本到此为止交出
103
+ (紧贴 `text_end` 之前那一帧不带 `eventId` 的 `text_delta`),`text_end.content` = 引擎这一块的正文接在本段前文之后判、只交这一块那一截。一块的原文 = 它的逐块帧字节,
104
+ 只有 `text_end`(没有逐块帧字节)的块 = 它的正文;段文本 = 各块原文依次拼接,活流的段与账本 `text` 行同读(只有 `text_end` 的块活流上由它的 `text_end` 交付、不另出逐块帧,账本行里照样有它)。
105
+ 有逐块帧的块:逐块帧拼接 = 该块 `text_end.content`(只有 `text_end` 的块没有逐块帧,它的正文只在自己的 `text_end.content` 里;引擎修剪过退化循环尾的块照旧以修剪后的为准);各块依次拼接 = 账本 `text` 行(下面两个方向除外)。交付时按到此为止的判定与整段判定不同的两个方向一个处置:凭据形恰横跨块界(读得出的半形出 `«redacted…»` 记号;读不出的半截 —— 如 `sk-ant-api0` 这样的短前缀 —— 原样出门,
106
+ 至多是块界之前的字节;后一块上横跨的那一条整枚换记号),或交付时认作凭据、后文让它不再是(如 `password=config.` | `password2;`:活流那一截是记号、账本原样)——
107
+ 活流按到此为止,账本整段判,该段收段时计一次 `server.live-text.hold-diverged`。帧数、帧序、`eventId`、`text_end` 在场性与 7.143.0 相同。
108
+ **谁需要**:读活流的 API 消费方与壳(多文字块的段:引用切块、穿插思考的回答 —— 活流与 `GET /v1/runs/:id/events` 对同一段文字同一个答案)。
109
+ **谁受伤**:按 `text_end` / `text_delta` 字节做逐字 diff 的消费方 —— 锚值分块的那一形从明文变记号;凭据形恰横跨块界时,后一块的 `text_end` 与逐块帧以一枚整的记号起头(前一块已带一枚);块界上认作凭据、后文让它不再是的那一截活流是记号、账本原样;按账本 `text` 行逐字 diff 的消费方 —— 只有 `text_end` 的块的正文从本版起在行里;
110
+ 多文字块长段的 CPU:每个 `text_end` 整段扫两次(交付余下字节一次、`text_end.content` 接本段前文判一次),上界 O(块数 × 段长)(7.143.0 每块只扫这一块)。
111
+ **补偿**:按段读的同一份文本在 `GET /v1/runs/:id/events` 的 `text` 行;按块整段替换的读法照旧幂等(有逐块帧的块:逐块帧拼接 = 该块 `text_end.content`)。
112
+ **升级前做**:无。**回滚**:回到上一版即恢复旧形(文字段按块判、账本行只拼逐块帧字节);本版写下的行原样留着;零数据面动作。
113
+
114
+ 10. **🔴 `DB_BACKEND=local`:仍带属主戳的存档的拒有了自己的码 `conflict.session_archive_stamped`,fork 与改日志的各形推送同码同句;载荷分叉的 `fast_forward` 与提交时才翻成 fork / stale 的无同意推送也答它(wire BREAKING:这两扇写口上带戳存档的拒由 `conflict` 改新码,按 `errorCode == "conflict"` 分支的消费方先改读;行为修复;部署零动作)**:
115
+ 旧形(7.141.0 节第 10 条):仍带戳的存档(一次性迁移留着的,或跳过带迁移的版本升上来的数据根)上,`POST /v1/sessions/:id/fork` 与改日志的推送(会话同步导入的提交)答 `409 conflict` + 带出路句的固定句
116
+ (「… delete the session and push it again」)。两处不齐:
117
+ ① **码**:`conflict` 同时是暂存提交时属主格已变那一拒的码(固定句「… start the sync again」,出路相反:从第一阶段重来)—— 同状态、同码、两条相反的出路,按码分支的机读方分不开,只能读句子;
118
+ ② **句**:改日志的推送在提交时先过「谁赢」的判,带戳判排在它们后面 ⇒ 三形被截住,答通用的 `409 conflict`、句子 `sync conflict`、体带 `relation`:`fast_forward` 而共享条目的载荷与目的端的不同
119
+ (目的端把某条补写过、id 不变,推送拿着旧版本再追加);第一阶段到提交之间分类翻成 fork、翻成 stale,又没带同意(`resolution: "overwrite-dst"`)。这三形的通用句指的出路是「带同意重来」,而带同意重来在带戳存档上照样被拒 —— 死路;
120
+ 干净追加的 `fast_forward`、同 id 异载荷、带同意的 fork 答的是带出路句的那一条,这三形不带。
121
+ 新形:① 带戳存档的拒有自己的 wire 码 `conflict.session_archive_stamped`(状态 409,固定句的字节不变,附录 A.1 新行);fork 路由与会话同步导入的提交两扇写口读同一张映射,同码同句;
122
+ 属主变更那一拒仍是 `conflict`。② 带戳判是提交里第一道存档判 —— 只要这一提交要换日志(「id 与内容都相同」那一支之外的一切),先问这份存档的日志换不换得了;
123
+ 带戳存档上各形(干净追加 / 载荷分叉的 `fast_forward` / 同 id 异载荷 / fork / stale,带同意与否)一律答同一码同一句,体只有 `error` / `errorCode` 两键(不带 `relation`),什么都没写(行不动、存档原样带戳)。
124
+ 「id 与内容都相同」的重推照旧不碰存档、照常落定;匿名存档上的应答不变(提交现在先读一次存档头:头读不出的存储故障先于同步冲突上抛,不再答 409)。
125
+ 提交里的次序也随之定下:带戳判排在本处写者拒(`conflict.session_writer`)与引擎的日志校验之前 —— 前者在 HTTP 上只有第一阶段之后别处认领了本处写者的竞态窗里才到得了(第一阶段已拒写者),
126
+ 后者流式上传时已判过(只有直接调店方法的嵌入方碰得到);带戳存档与这两样同时成立时,先读到新码。
127
+ 范围(如实):第一阶段(`POST /v1/sessions/:id/sync/import`)按 id 分类出 fork / stale 且没带同意时,仍先答通用的 `409 conflict`(`sync conflict` + `relation`)—— 那一口只按 id 分类、不经提交里的带戳判,答的是同步协议的冲突答复;
128
+ 带同意重来之后,第二阶段答新码。所以带戳存档上 fork / stale 形的推送,要到带同意的第二轮才读到出路句。
129
+ **谁需要**:壳 / SDK 等按机读码分支的消费方(带戳存档:删掉会话再推;属主变更:从第一阶段重来)—— 两件事现在是两个码;local 部署上对迁移留着的带戳存档(起服 warn `local_archive_kept_stamped` 点名)推送的客户端,载荷分叉的推送一次就读到「删掉会话后重推」,不会先去试带同意重来。
130
+ **谁受伤**:① 按 `errorCode == "conflict"` 处置带戳存档的 fork / 改日志推送拒的消费方(7.141.0–7.143.0 的读法)—— 这两扇写口上它们不再读到 `conflict`,按状态 `409` 或按 `conflict.session_archive_stamped` 分支;
131
+ ② 按 `sync conflict` 句子或体里 `relation` 键判「要解决冲突」的客户端:载荷分叉的 `fast_forward` 与无同意的 fork / stale 在带戳存档上不再读到它们(这三形在带戳存档上本来就解决不了)。
132
+ **补偿**:改读新码 `conflict.session_archive_stamped`(读体的 `error` 即出路:删掉会话 `DELETE /v1/sessions/:id` 后重推);按状态 `409` 分支的不受影响。**升级前做**:无。**回滚**:回到上一版即恢复旧码与旧判序(本版不写任何新形,落盘数据不受影响)。
133
+
134
+ 11. **更正(7.143.0 节第 19 条、第 26 条、第 10 条、第 31 条与第 36 条五处说法,文书;零行为变化)**:
135
+ ① **第 19 条**写「策略的 deny / ask 名单点名 `TaskOutput`(或 `TeamCreate` / `TeamDelete`)⇒ 跑前拒 `config.legacy_tool_name`」,这只对一种来源成立。退役工具名写进 deny / ask 规则时,在哪里拒、答什么,按名单的来源分:
136
+ — 请求体 `settings.permissions.deny` / `ask`:三条提交口(`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs`)受理时拒,零模型调用;`422`、码 `request.unprocessable`、句首 `request settings cannot be applied as written:`,体带 `field`(`settings.permissions.deny` / `settings.permissions.ask`)与 `detail.problems[]`,退役名那一条 `reason: "retired"`。
137
+ — 部署名单 `APPROVAL_REQUIRE` / `APPROVAL_DENY` / `APPROVAL_NEVER_AUTO` / `APPROVAL_GRANT`:起服拒启,拒句点名条目(没有 wire 码);中心下发的 `governance.approvalRequire` 整组被拒、保前值。
138
+ — `config.legacy_tool_name`(同步腿 `400`;异步与流式腿落在终态 run 行的码上)只来自存量:升级前的写口收下、盘上留着的会话规则 `toolDeny`(例:7.142.x 上经 `PUT /v1/sessions/:id/policy` 写进去的 `TaskOutput`),升级后在那只会话上提交即得这一码,拒句是引擎的;出路 = operator 用 `PUT /v1/sessions/:id/policy` 改写那条会话的规则。
139
+ 请求体 `settings.permissions.allow` 点名这三个名字照旧只得一行提示。
140
+ ② **第 26 条**没有标门。**直连门部署**(`DIRECT_APPROVAL_DOOR` 连同它要求的 HMAC 钥与签名身份锚都在)上,`POST /v1/approvals/:sessionId/decide`、`POST /v1/assistant/tasks/:id/plan_review` 与 `POST /v1/tasks/:taskId/asks/:askId/decision` 三口不收 `decidedBy`(在场即 `400 decided_by_not_in_proof`,不论值),
141
+ 所以 `decidedBy: "policy"` + `decidedVia` 在这三口上是 400;只带 `decidedVia`(旁边是缺席的人)在任何部署的任何一口上都是 400(live `/respond` 码 `request.body_shape`,其余三口 `request.field_invalid`)。
142
+ live `POST /v1/tool-approvals/:id/respond` 不带决策证明,直连门部署上照样按第 26 条受理。不是直连门的部署,四口按第 26 条原句受理。
143
+ ③ **第 10 条**的 `/resume` 是 `POST /v1/assistant/tasks/:id/resume`(没有 `POST /v1/runs/:id/resume` 这条路由)。四道续跑门各续各的门:`POST /v1/approvals/:sessionId/decide` 续工具审批卡;`POST /v1/assistant/tasks/:id/plan_review` 续 `plan_review` 门;
144
+ `/resume` 续资源限停驻(门 `resource_limit`:调度抢占,或预算 / 轮数 / 墙钟分片),停在审批卡上的任务打它得 `409 gate_not_resumable`;`POST /v1/sessions/:id/wake` 续 `task_done` 停驻,有未决门时得 `409 wake.gate_pending`。
145
+ ④ **第 31 条**写「`userSettings` 里要根的 allow 规则免问的是配置根下的路径」(CHANGELOG 同节「现在」句:「它免问配置根下 `secret` 的写」),对配置根下的**写**不成立:
146
+ — 写:目标在引擎缺省写保护表上的写(配置根就在表的 `.sema` 行上,见下)照问,任何 allow 规则 —— 带层条目、纯串、`//` 绝对形 —— 都清不掉;卡上 `mandate: "write_protection"`,问句说它由确认这一次清掉、不由常驻 allow 规则。
147
+ 规则答不了这一类问;模式级放行与 auto 模式分类器对它照引擎自己的读法,不在本条。
148
+ — 配置根在每条车道上都在 `.sema` 行上:声明了 `SEMA_CONFIG_DIR` 时它就是交给引擎的配置目录,`.sema` 行在它及其以下全命中;缺省 `~/.sema` 与路径里带 `.sema` 段的任何目录(含沙箱车道缺省的 `<home>/.sema`)在任何车道上都命中;
149
+ 转进沙箱的 `SEMA_CONFIG_DIR` 是本服务同名 env 的原值(名单只收名),与交给引擎的配置目录是同一只。
150
+ — 两处不归这一行:引擎数据根(记忆引擎的根;壳布局 `~/.sema/engine-data`)坐在配置根下时,根里的写按根内的相对路径判、豁免于 `.sema` 行(表上别的行照判);
151
+ 部署整表替换写保护表(`WRITE_PROTECTED_TABLE_REPLACE`)去掉 `.sema` 行时,这一行随之不在。
152
+ — 读不在写保护表上(这张表只管写),第 31 条对读的说法不受本条影响。
153
+ 构造:本机车道,请求体 `settings.permissions.allow` 带 `{ "rule": "Edit(/secret/**)", "layer": "userSettings" }`,写配置根下 `secret/n.txt` ⇒ 停在一张审批卡上(`mandate: "write_protection"`),不是免问落盘。
154
+ ⑤ **第 36 条 ③**写「本部署的店集 = 盘上的(个人挂载上有店就算)」,少说了「盘上的店」是怎么来的:记忆引擎的 file 店**一打开就建目录、写根锚**,部署对象又把「目录在 / 本对象开过」算进店集 —— 所以一次**被拒**的个人层抹除(`400 config.memory_erasure_scope`,什么都没擦)、或任何一次点名个人层的只读(`GET …/memory-status?scope=user:<x>`、出处 / 清标账带层座)都会留下一只从没写过的个人店,
155
+ 此后不点名层的出处 / 清标账 / 清标请求永久答 `400 request.memory_layer`(重启照旧);7.142.0 同序列不翻(那时店集由配置点名的层推出,不读盘)。这是缺陷(被拒请求不该改变部署之后的应答),根在引擎的店集读法,本服务只能消症状、不修根,本版**不改**:
156
+ **补偿**:在请求里点名层(GET 口 `?scope=<层键>`、POST 口体 `"scope"`)照常可用;要退回「不点名层」的读法时,确认个人店目录 `<记忆根>/personal/.engine` 下**只有** `root-anchor.json` 一个文件(= 从没写过)再删掉整只 `personal/` 目录 —— 有别的文件就不是空店,别删。构造与现状读数见本版发车帖。
157
+ 7.143.0 节原文不改(已发节字节不动)。无三问(更正条)。
158
+
159
+ 12. **host 车道:执行环境的家目录主张证不了 ⇒ 环境在开跑前就不铸,run `failed` 终局带码 `env.home_unconfirmed`(同步提交腿 409),命令带不带路径都是同一首拒;worktree 的 git 基环境不再带家目录主张;更正 7.140.0 节第 12 条 ③(wire 新码;行为修复;部署零动作)**:
160
+ 旧形(7.140.0–7.143.0):host 车道(含 `run-local`)上家目录主张(`REMOTE_EXEC_HOME_DIR`,缺席时服务进程的家目录)与子进程拿到的 `HOME`(继承面 + 会话 `settings.env`)对不上 / 读不出 / 不是绝对路径时,环境照铸、门不开:
161
+ run 照常开跑、模型照常被调;模型发出的命令不带路径(`echo hi` / `ls`)⇒ 工具结果是环境的拒句(`shell_unavailable`,点名声明值与量到的 `HOME`);带路径(`cat notes.txt` / `cat ~/notes.txt`)⇒ 先撞引擎的读边界
162
+ (`read_unestablished`,「could not establish what is read」),句子不点名家目录 —— 同一根因两种首拒,后一种把人引去读边界。run 多以 `completed` 收场(模型读到拒句后自己作答)。
163
+ 新形:host 环境在构造时就判(读数是本服务组合给子进程的那份 env,铸环境时就到手),证不了 ⇒ 环境不铸 —— 工厂抛带码的拒,引擎在准备期原样承运:run `failed`,
164
+ `terminal.code` = `env.home_unconfirmed`,`terminal.message` = 判官的拒句(点名主张、量到的 `HOME` 与改法;与旧形无路径命令那一句同文)。一条命令没跑、模型一次没调、一个 token 没烧,命令带不带路径无从分别。
165
+ 同步提交腿 `POST /v1/tasks` 按零工作拒答 **409** `errorCode: "env.home_unconfirmed"`(不计费、不占提交幂等键);流式腿终帧 `done.result.terminal`、后台 run 行同码。码是本服务自铸的执行环境拒码(`env.*` 族)今天唯一的一员,契约附录 A 有行。
166
+ worktree 隔离的子代:引擎先铸树,子环境构造即拒,树随即被引擎收掉,子代以同码失败。worktree 的 git 基环境(起服铸、只跑 `git worktree add/remove/prune`、从不交给引擎)不再带家目录主张:不对证、不拒,孤儿 worktree 清扫照跑。
167
+ 远端五车道(k8s / local-docker / ssh / adb / e2b)不变:家目录读数要环境开起来才有,照旧在开环境那一拍拒、拒句落在工具结果里;引擎的读边界要先读环境才判得了的命令(递归读形 `grep -r … ~/…` 一类;adb 上连带路径的普通读)在那里先被读边界拦下(要人批 / 只读面拒),句子不点名家目录 —— 这些车道上同一根因两种首拒仍在。
168
+ **订正 7.140.0 节第 12 条 ③**(已发节原文不改):「那只环境拒起(命令面 `shell_unavailable`、文件面 `target_unavailable`、连接 `connect_failed`)」本版起读作「环境不铸,run 失败终局 `env.home_unconfirmed`(同步腿 409)」;
169
+ 「起服时铸的 worktree 基环境同样被证,对不上时孤儿 worktree 清扫不跑」本版起不成立(基环境不带主张,清扫照跑)。三条出路不变:删掉声明(或设成 `HOME` 的值),或去掉会话里的 `HOME`。
170
+ 7.141.0 节第 32 条 ③ 所述的两种首拒(`shell_unavailable` / `read_unestablished`),在 host 车道上本版起收成上面那一只;那一条说到的 adb 与其余远端车道仍是那一形(上段)。
171
+ **谁需要**:host 车道的部署与 `run-local` 用户 —— 家目录配错时第一句就点名根因(声明值 vs 量到的 `HOME`),不再被带路径命令的读边界拒引去别处;零 token 早失败。
172
+ **谁受伤**:按 run `completed` + 工具结果里的拒句(或 `shell_unavailable` 字样)认这一形的脚本与告警;同步腿上按 HTTP 200 读这一形的调用方(现为 409)。
173
+ **补偿**:按码 `env.home_unconfirmed` 分支(同步腿 `errorCode` / 终局 `terminal.code`);句子与旧形无路径命令那一句同文。
174
+ **升级前做**:无(主张与 `HOME` 一致的部署零差别)。**回滚**:回到上一版即恢复旧形;本条不写任何持久数据。
175
+
10
176
  ## 7.143.0 —— `wiring_manifest` 帧的容器段在 run 店 File 孪生按会话块账本去重(持久面;体量;回滚地板 = 本版) · env 两槽 × 目录:本地根首份管模型面目录的首次写入决定文档(K0)、`planeRefusalCode` 只答最新候选、leader 路由模型经服务表解、env cheap 槽悬空一次性 warn(配置面;行为收紧) · 🔴 部署治理的装配相容性门改在部署 MCP 服务器 / A2A 对端物化之后判:`mcp` / `a2a` 锁、合规否决 `mcp_servers` / `a2a_peers` 撞上部署自带的服务器 / 对端 ⇒ 起服拒(行为收紧,fail-closed;部署面) · 运维审批队列行带族名 `kind`、被扣的跨会话来信作 `peer_inbound` 行进审批席(wire 加性;🔴 消费方先按 `kind` 分支) · k8s 车道:停驻时沙箱从未起过的停驻,续跑改作一次新连接(空间开 ⇒ 恢复 `latest`);打包过的停驻快照对象不在 ⇒ 一行 `workspace_latest_dangling`(`lane: "park"`)+ 空目录;更正 7.142.0 节第 9 条尾的已知限制(行为修复;运维面日志加性) · 订正 7.141.0 节第 1 条的「谁受伤」:结构化 warn 只覆盖本服务写的那一行,网关密钥被拒收时引擎另说一行裸 `console.warn`(引擎已知限制),logger 接上后的那一行受 `LOG_LEVEL` 约束(文字订正;零行为变更) · 🔴 SQL 后端 `task_attachment` 加一列生成列 `seed_session_id` + 唯一索引 `uq_att_seed`:一只会话一只种子改由唯一键判,已有种子会话上的并发提交恒答 409(行为修复;DDL 升级前先加列;wire 零变化) · 续跑请求可重述对端表:四道续跑门收提交体同键的 `mcpServers` / `a2aPeers`,缺席 = 冻结读法(wire 加性;行为面;壳宜每次续跑送当前表) · 🔴 活流模型文本过同一只脱敏器:`text_delta` / `reasoning_delta` 逐块帧只放已判定的字节,拼接 = 段末帧正文 = `done.result`,段末余下的字节一帧补齐(wire 值语义;安全) · 会话读面(视图 · 记忆状态 · 记忆抹除 · 头探针 · 事件流 · 执行放置)对「文件后端上行事实没有落下、存档又读不出」的会话,从 500 `corrupt_session` 改答 404 `not_found.session`(与对非属主、对不存在的会话同一只;原故障进运维日志 `session_owner_unreadable`;行为面;应答格状态变化) · 中心 MCP 声明撞同一工具命名空间前缀:同步提交腿从 `200` + `failed` 体改 `409 config.mcp_server_name_collision`,`GET /v1/capabilities/mcp` 从无码 `500` 改同码同状态同句(wire 码与状态变;按 HTTP 状态分支的提交方看一眼) · 🔴 持久 ask 店车道上 askId 的身份含这一问展示给人的动作(工具名 + 入参摘要):同一 run 同一腿里复用调用 id、换了入参或换了工具的那一问出自己的卡(安全轴;行为收紧;不透明 `askId` 值变;零 DDL) · 🔴 `POST /v1/approvals/:sessionId/decide` 的 `boundCallId` 与 `boundInputHash` 两位必带,缺哪位 400 `decide_requires_binding` 点名哪位;不再拿当前停驻行补;`remember_requires_binding` 退役(BREAKING 行为面;安全轴;决断方回显队列行上的那一对) · 订正 7.142.0 节第 18 / 19 条的两处读数说法:坏块 / 撤块时能力位读 `no_caller_credential`(`no_origins` 是平面的第九条前提);强锚名紧贴前一枚凭据尾巴那一形在 7.142.0 上于 `GET /v1/runs/:id/events` 回放的 `tool_end` 行与活流逐块帧明文(文字订正;零行为变更;这一形本版起已修,见第 41 条) · 会话记忆同步触发口的 409 `conflict.session_memory_unobserved` 体恒带 `reason`:最近一条已结 run 的记录里没有记忆观测 ⇒ 点名它的记录里有什么、句子给出路;🔴 它配了记忆而没挂上(`memoryless`)⇒ 由 200 空报告改 409 `memory_not_mounted`;读集仍只读最近一条已结 run(wire 加性;行为面;三句拒句换形 / 新句;零 DDL;部署零动作) · 🔴 引擎 7.61.0:升级前结清在途审批、排空旧 worker,mod 包按契约 7 重建(部署面;BREAKING) · 🔴 审批卡的委派子代出处键 `sourceTaskId` 改名 `sourceSessionId`(wire BREAKING;零别名) · 通告码 `context.image_removed` 改名 `context.media_removed`、TaskOutput 退役(wire 码改名;策略名单) · 设备车道放置围栏 + 协议修订 4 `symlink_loop`(行为面;wire 加性) · SQL 后端新表 `mailbox_consumed`(起服自建) · `DB_BACKEND=local` 工具结果按 TTL 回收(行为变化) · blob 上传哈希不符一律 400、嵌入方两只店换新组形(wire 值变化;类型面 BREAKING) · 记忆同步:别的 scheme 里的 URL userinfo 也判凭据(行为收紧) · 设置导入:用户层 `/` 形路径规则留在文件里(行为收窄)、导入预览的用户层不收 `root`(wire BREAKING) · 🔴 SQL 后端 `approval_ask` 加一列 `decided_via`、决定体加性键 `decidedVia`、能力位 `approvalDecidedVia`(wire 加性;DDL 升级前先加列) · `verify:true` 任务的上限封住实现腿与各轮修复的合计,花光 ⇒ 不再修复(行为收窄;wire 加性) · 订正 7.139.0 节第 24 条 ③:没配包装的 worktree 子代仍由本服务拒(文字订正;零行为变更) · 嵌入方包 `resolveSpec` 的包装须返回同一只对象(嵌入方行为面 BREAKING) · 验证器自己停驻的那张卡改由引擎的作废动词结算、会话钉随之放掉,编排(leader)路径同样作废(行为面;修复;部署零动作;订正 7.141.0 节第 31 条的两条已知限制) · `settings.permissions.allow` 的条目也可带出处层,user 层的 `/` 形 allow 读在执行环境的配置根(wire 条目型放宽 + 能力位;端见位才发;收掉 7.141.0 节第 15 条「已知限制」的 allow 那一半) · 后台子代一个 id:面板 / 完成通知 / 孙代嵌套 / 审批归属都读注册表行 id;升级与回滚前各让停驻的后台子代结清(行为面;部署面) · Runner 之外复活的后台子代(停驻赎回 / 操作员续跑)的完成走委派树根会话的会话通知门(行为修复;wire 零新键) · 🔴 记忆同步:中心每个层分区一只化身、同步口改到 `/v2/memory/sync/:scope`、本机游标原样携带化身(引擎三处硬破坏;中心库新表起服自建;部署:两端同版 + 停驻层删游标) · 🔴 抹除与会话记忆状态答整个部署(`409 memory.erasure_store_unjudged` 与会话自助抹除体的 `scope` 退役;嵌入方类型面 BREAKING) · 🔴 规则店:引擎 7.61.0 之前从用户层设置导入的、从 `/` 写起的全局放行起服即退役(按 add;deny / ask 不动;一次性迁移 M36,每次起服幂等;持久数据面;行为收紧;部署零动作) · 本地导入载体的 deny / ask 在 prepare 当场落店、预览成员带 `outcome`,放行仍在 redeem 落;收紧写不进 ⇒ 整批 503、放行扣下(行为面;wire 加性) · 自由文本脱敏的取证读法按出处读:一枚凭据不拿自己上一轮的读数当证据、私钥收尾栅栏落在不完整读数里两读取并、SSH2 块之后紧跟的孤立收尾之前读作无头体、取证级数界 32 到顶整段遮(行为面;安全;脱敏产物可能变;运维面多一个 fail-open 标签) · 🔴 审批卡的断连重放帧与活铸帧同事实:问行卡信封加一座 `liveFacts`,断连重放的 `approval_request` 带活铸帧顶层的同一组卡外事实;本版之前铸的行重放帧带 `liveFactsUnknown: true`(安全轴;wire 加性;存形加性,零 DDL;🔴 多副本先停光旧副本再让本版接流量) · 🔴 自由文本脱敏的名字面:复合凭据名(`api_key=` 一族)在自由文本的声明形里是名字锚、名字的左界止于前一枚已认出凭据的终点(紧贴形首遍即遮、各出口同读)、不带引号的值里面的赋值照问(行为面;安全;脱敏产物变;带这些赋值的命令不再给常驻放行;部署零动作) · 运行时依赖 `@modelcontextprotocol/server` 2.4.0(npm 安装多一只包;部署零动作)
11
177
 
12
178
  1. **`DB_BACKEND=local`:run 账本里每条 run 的装配清单(`wiring_manifest` 行)按会话去重存 —— 容器段存进每会话一只块账本 `<数据根>/run-blobs/<会话>.jsonl`,行里只留引用(持久数据面:存形加性,存量不迁移;wire 与读面零变化;回滚地板 = 本版;部署零动作)**:
package/USAGE.md CHANGED
@@ -624,8 +624,7 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
624
624
  下一轮不必从店重新唤醒整只会话。每次命中先读店里这只会话的当前叶(SQL 后端一条主键 SELECT):与暖份的叶相同 ⇒ 照用;
625
625
  不同(这只会话在别的副本上写过)或会话已不在 ⇒ 丢掉暖份、从店重读再用。所以 chart 缺省 Service、轮询负载均衡这类**任意路由**
626
626
  下,同一会话轮流落到不同副本不会撞 `conflict`;会话亲和只提高命中率(少重读),不是正确性前提。代价:每次命中多一次读头
627
- —— SQL 后端一条主键 SELECT;文件后端(`DB_BACKEND=local`)是把这只会话的存档文件再读一遍(只读头,不铸 `session.records_skipped`
628
- 帧)。命中却已陈旧的次数计在 `session_cache_stale_hit_total`(高 = 同一会话在 TTL 内常换副本,加亲和可省掉这些重读)。
627
+ —— SQL 后端一条主键 SELECT;文件后端(`DB_BACKEND=local`)是把这只会话的存档文件再读一遍(只读头,不回放日志)。命中却已陈旧的次数计在 `session_cache_stale_hit_total`(高 = 同一会话在 TTL 内常换副本,加亲和可省掉这些重读)。
629
628
  `0` = 每一轮都从店唤醒,不复用。⚠️ 核的是叶:另一只副本用会话同步**整段替换**(含 `overwrite-dst`)把已有条目的内容改了、
630
629
  末叶却没变的那一形,别的副本上的暖份在 TTL 内仍是旧内容(与改前同;写不会因此失败)。依赖这一形即时可见的部署设 `0`。
631
630
  - 数据驻留提示:`DB_BACKEND=local` 下显式 `SESSION_BACKEND=memory` 会被收编为 **durable(local)**
@@ -653,7 +652,10 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
653
652
  行与编号高水位一并抹掉(同 anchors/附件/快照的级联)——会话 id 可被重新认领,残留就等于把上一位
654
653
  主人的任务标题与编号进度交给下一个化身。
655
654
  - **会话的行事实只住在数据根的 `session-incarnations.json`**(每只会话一格:属主 / 化身 / 执行放置 / 建档时刻 / fork 谱系);`sessions/*.jsonl` 是匿名的对话内容。
656
- 备份 / 迁数据根时两样一起搬;更早版本写下的带属主戳的存档由起服一次性迁移改写(改不了的点名,fork 与改日志推送答 409),快照目录 `session-archive-m33/` 非空且消化不了 ⇒ 拒启(MIGRATION 写了出路)。
655
+ 备份 / 迁数据根时两样一起搬;更早版本写下的带属主戳的存档由起服一次性迁移改写(改不了的点名,fork 与改日志推送答 409 `conflict.session_archive_stamped`),快照目录 `session-archive-m33/` 非空且消化不了 ⇒ 拒启(MIGRATION 写了出路)。
656
+ `session-incarnations.json` 本构建读不出(更新的引擎写过、格里多出不认得的键;或文件坏了 / 读不了)⇒ 起服拒 `config.schema_mismatch`(与 SQL 后端列册对账同一个码,拒句点名文件与键;
657
+ 出路 = 部署写下它的那一版,或恢复升级前的数据根备份;读不了 ⇒ 修好权限 / 盘再起);起服之后才读不出(跳过了 `ensureSchema()` 的嵌入方;或违规共享的数据根上另一只引擎写了它)⇒ 同码拒,
658
+ 形读不出则那只边档的每一读每一写拒到重启,盘上字节不被盖掉。
657
659
  - 多实例边界:同一数据根同时只允许一个实例(BootLock 独占,第二个进程拒启;死主自愈含 SIGKILL)。
658
660
  core 5.54.0 起拒启错误带**可分派 code**(`FileStoreLockError`,message 点名占用方 pid),运维按码
659
661
  三动作:`store.dir_in_use`=另一个活进程持有该目录 → 停掉它(或换数据根/迁 SQL 后端);
@@ -769,9 +771,12 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
769
771
  - **服务进程被 `SIGKILL` 时,暂存目录会留在目标旁(可直接删)**:上一条的清理是服务进程自己做的 —— `kill -9`、内存杀手、容器被强停这类**没有机会跑清理**的退出,正在传输的那一次写留下 `.<名>.stage-<12 位十六进制>/`,里面是在传的新内容(上传途中被杀只有前半截,传完才被杀则是整份);**目标逐字是旧内容**,整文件替换的保证不受影响,多出来的只是这只目录。ssh 车道实测(被强杀的写都留下了);其余四条远端车道走同一段暂存脚本,同形。取消(Ctrl-C 中断那一次写)不留 —— 取消路径上清理那一往返照跑。**本版不自动清**:下一次对同一目标的写不会去扫陈旧的暂存目录,它会一直留着,确认没有另一个进程正在写同一个目标之后 `rm -rf` 即可(名字里的十六进制每次写都不同,在写的那一只和陈旧的那一只光看名字分不出)。host 车道的暂存是引擎的,同形的残留名是 `<目标>.tmp.<进程号>.<十六进制>`,同样可以删。
770
772
  - 暂存自创建起不比目标更可读:远端五车道的暂存只在私有目录里(别的用户进不去;替换 0600 的文件时新内容在传输期间只有写入方与 root 读得到),device 的暂存件以 0600 打开;
771
773
  host 车道的这一保证在引擎原语里:引擎 7.60.0 起目标存在时暂存件以 0600 打开。新建的文件是写入方身份的 umask 形(local-docker = 容器内镜像用户的)。
772
- 私有目录建出后要读回核实是 0700 且属写入方;**核不过(`chmod` 被拒、文件系统不认权限位、缺省 ACL 的掩码收不下来、目标机没有 `stat`)⇒ 不暂存,退回就地写目标**
774
+ 私有目录建出后要读回核实是 0700 且属写入方、且目录上没有 ACL 条目:macOS 的原生 ACL 与 FreeBSD / illumos ZFS 的 NFSv4 ACL 不经模式位 —— 工作区父目录带可继承的允许条目时,新建的私有目录一建出来就带着它,读回 `700` 也挡不住,
775
+ 所以读到条目就去掉(macOS `chmod -N`、FreeBSD `setfacl -b`、illumos `chmod A-`)、去完再读;**核不过(`chmod` 被拒、文件系统不认权限位、缺省 ACL 的掩码收不下来、继承来的 ACL 条目去不掉或读不出、目标机的系统不在 Linux / macOS / FreeBSD / illumos 之内、目标机没有 `stat`)⇒ 不暂存,退回就地写目标**
773
776
  (两只文件面、五条腿都有;非原子;ssh / local-docker / k8s 写进原 inode、权限位不变,e2b 的 `files.write` 与 adb 的 `adb push` 整份写目标、inode / 权限位由对端定;
774
- 遥测计 `server.remote-write.stage-seal-fallback`);adb 的追加 = 7.138.x 的形:读旧 + 拼 + 整份 push(push 没有追加写)。Android 共享存储(`/sdcard` 一类)上 adb 的写按 sdcardfs / MediaProvider FUSE 的权限语义**推断**每一次都走这一形(未上真机,真机读数候 test)—— adb 的工作区放在 `/data/local/tmp` 一类认权限位的目录即照常暂存。
777
+ 遥测计 `server.remote-write.stage-seal-fallback`);读 ACL 的臂按**目标机**的 `uname -s` 选(Linux:POSIX ACL 由模式位读回的掩码覆盖,不另读 · Darwin · FreeBSD · SunOS);FreeBSD / illumos 两臂按系统文档写、未上真机,读 ACL 的工具报错 / 输出形不对 / `uname` 退出状态不成都按读不出处理(读 ACL 的工具自己静默吞掉的读取错误看不出);
778
+ Linux 上经 NFS 挂载、由服务端自己执行的 NFSv4 ACL 在客户端没有可读的条目,不在覆盖内;其它系统(OpenBSD / NetBSD / AIX / Cygwin 一类)的目标机每一次写都走就地写并计数。
779
+ host 车道的暂存是引擎的,device 车道的暂存是设备侧执行器的 —— 这一道不管它们;adb 的追加 = 7.138.x 的形:读旧 + 拼 + 整份 push(push 没有追加写)。Android 共享存储(`/sdcard` 一类)上 adb 的写按 sdcardfs / MediaProvider FUSE 的权限语义**推断**每一次都走这一形(未上真机,真机读数候 test)—— adb 的工作区放在 `/data/local/tmp` 一类认权限位的目录即照常暂存。
775
780
  - 一个身份:准备目录、上传、提交、清理都以同一个身份做。local-docker 的上传是容器内以镜像的用户经 stdin 收字节(不是守护进程以 root 的 `docker cp`)——
776
781
  写文件与代理的 shell 同一套权限,镜像用户写不了的路径答 `permission_denied` —— 镜像里没预建的工作区目录由容器启动以 root 建出,镜像用户 ≠ root 时要在镜像里预建并归镜像用户。
777
782
  - 回落(host 与五条远端车道,与引擎同一套;device 由设备侧执行器自定,非常规文件拒写):单跳符号链接 ⇒ 替换它指的那只常规文件(链接仍是链接;远端车道先在那只文件的目录里重新暂存,rename 只在一个目录之内);链接链 / FIFO / 设备文件 / rename 被拒(如单文件绑定挂载)⇒ 由已落定的暂存件就地拷入(非原子);
@@ -1457,7 +1462,7 @@ TASK_WRITE_FACE_ESCALATION=allow # 词表 allow|refuse;坏词启动期响亮
1457
1462
  只有 `__proto__` 名的成员坏 ⇒ 剔掉它、其余照交。server 照挂,运维 warn `mcp_transport_members_dropped`(`server` / `lost` / `lostTotal?` / `note`)点名没交出去的键 / 成员与整键没交的
1458
1463
  `transport.args` / `transport.principalHeader`。A2A 对端 `headers` 同读:值不是串 ⇒ 对端照旧不挂;只有 `__proto__` 成员坏 ⇒ 续跑照挂、剔掉它,一行 `a2a_peer_headers_members_dropped`
1459
1464
  (`peer` / `lost` / `note`);新提交上两形都整条不挂。
1460
- - **续跑时换表(7.143.0 起)**:停着的任务续跑(`/decide` · `plan_review` · `/resume` · `/wake`)默认重放提交时存下的 `mcpServers` / `a2aPeers`;续跑请求体带上同名键(与提交体同形、同一只判官)就以它为准,
1465
+ - **续跑时换表(7.143.0 起)**:停着的任务续跑(`POST /v1/approvals/:sessionId/decide` · `POST /v1/assistant/tasks/:id/plan_review` · `POST /v1/assistant/tasks/:id/resume` · `POST /v1/sessions/:id/wake`,各续各的门:工具审批卡 / `plan_review` 门 / 资源限停驻 / `task_done` 停驻)默认重放提交时存下的 `mcpServers` / `a2aPeers`;续跑请求体带上同名键(与提交体同形、同一只判官)就以它为准,
1461
1466
  并写回成之后续跑的表 —— 用户移除过一台 MCP 服务器之后,壳送此刻的整张表,续跑就不再重连那一台(停在那一台调用上的任务由引擎判:引擎 7.61.0 起三码各答各形,以 core 为准:那只工具不在了 ⇒ `resume.tool_unavailable`(引擎重开检查点:卡仍 pending、可再决);同名而已不是批准时那一只 ⇒ `resume.tool_contract_mismatch`(同样重开);还是那一只而它的面或声明变了 ⇒ `resume.target_moved`(这次调用不执行、批准用掉,须重新发起),本服务逐字过境);直连门部署的两道回决门拒收这两键(契约 §4e-bis)。
1462
1467
 
1463
1468
  **可选 — MCP 入站表单(elicitation,E23):默认关**
@@ -1987,7 +1992,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
1987
1992
  ## 7. 其它接口(按需)
1988
1993
  | 端点 | 用途 |
1989
1994
  |---|---|
1990
- | `GET /v1/sessions/<id>` | 审计回溯:当前上下文 + 摘要(owner 校验);消息里模型 / 工具产出的文本与 live 帧、账本帧同一只脱敏(密钥形换占位,其余字节不变);Read 图像 / PDF 的媒体卡(工具结果消息的 `details`)的 `file.base64` 是字节编码,逐字。**委派子代的转录**也走这一口:`<id>` = 子代帧 / fleet 子代行上的 `transcriptId`(未压缩过的子代首条用户消息是委派 prompt;子代跑过的工具是 assistant 消息里的 `toolCall` 段 + 同 `toolCallId` 的 `toolResult` 行;整段逐字条目走 `…/sync/entries`)。体顶层加性键 `agent: { id?, parentSessionId?, rootSessionId? }`(**7.133.0+**)说这是哪只子代:只投影会话行上存下来的委派元组,`id` 与 fleet 行的 `id`、`GET /v1/agents/roster` 的 `handle` 同一个串;键缺席 = 普通会话 / 老 server / 会话店不存元组(local 后端),读作「没存」、回落 fleet 行 / 名册上的 id。子代会话的属主是铸行那一刻它所在委派树的根会话的属主(**7.133.0+**,写下那一行时按当时读到的写入;此前 SQL 后端上子代会话无主、谁都读得到)⇒ 用发起子代的那位主体读;别的主体与不存在的 id 同一个 `404 not_found.session`。属主为空的委派子代行(根会话本就无主,或升级前写下、还没回填)只有 fleet 调用方读得到(出示 service 凭据、不带主体头;dev-open 部署不钉人),任何具名主体、没有 service 凭据的无身份调用方同样 404(存量无主会话互认臂 —— 单用户形下 `default` 主对无主的普通会话放行,fork / 删除 / `/sync/*`(同步族自 7.134.0 起)同走这一只 —— 对这些 placed 行同样不放行,审批豁免、notify 同样不放行;fork / 删除 / `/sync/*` 的 fleet 调用方与此处同一个判据:出示 service 凭据、不带主体头,带主体头的 service 凭据请求是那位主体,7.134.0 起);SQL 后端每次起服回填这些行(起服读数行 `placedSessionOwnersRemaining` 非零 = 还有没定下的,再起直到为 0,或等它们下一次停驻)。推不出属主的子代写成属主 `_orphan:<id>`,只有 fleet-wide / operator 读得到:SQL 后端的子代行根会话已删时按它记录的父会话,父也删了才是 `_orphan:`;停驻时拷进宿主店的那一份(local 后端)只按根判,根会话已删 ⇒ `_orphan:`;子代被派出 / 批准之后、再次停驻读根之前根会话被删并由别人以同一 id 重新登记 ⇒ 其下的子代再次停驻被否决(读过根之后才换的,写进去的仍是读根那一刻的属主) |
1995
+ | `GET /v1/sessions/<id>` | 审计回溯:当前上下文 + 摘要(owner 校验);消息里模型 / 工具产出的文本与 live 帧、账本帧同一只脱敏(密钥形换占位,其余字节不变);Read 图像 / PDF 的媒体卡(工具结果消息的 `details`)的 `file.base64` 是字节编码,逐字。**委派子代的转录**也走这一口:`<id>` = 子代帧 / fleet 子代行上的 `transcriptId`(未压缩过的子代首条用户消息是委派 prompt;子代跑过的工具是 assistant 消息里的 `toolCall` 段 + 同 `toolCallId` 的 `toolResult` 行;整段逐字条目走 `…/sync/entries`)。体顶层加性键 `agent: { id?, parentSessionId?, rootSessionId? }`(**7.133.0+**)说这是哪只子代:只投影会话行上存下来的委派元组,`id` 与 fleet 行的 `id`、`GET /v1/agents/roster` 的 `handle` 同一个串;键缺席 = 普通会话 / 老 server(`DB_BACKEND=local` 到 7.140.x 也是)/ 会话店不存元组(`DB_BACKEND=memory`;嵌入方自建、不声明委派分区的店),读作「没存」、回落 fleet 行 / 名册上的 id。子代会话的属主是引擎判的委派树属主(**7.144.0+**:没人派生的 run 读自己那一行的属主、按化身绑一次,原样带给每一层子代;三只持久会话店〔MySQL 协议 / PostgreSQL / local〕建行那一笔原样记引擎交来的属主,`null` = 根匿名;停驻挂点只比对宿主行属主与引擎交来的属主,不认领、不推断。**7.133.0–7.143.x** 是本服务在建行那一笔按根 / 父推断:根会话的行在 ⇒ 它的属主,根行已不在 ⇒ 它记录的父会话的,都不在 ⇒ 哨兵 `_orphan:<id>`;7.132.x 及更早的 SQL 后端上子代会话无主、谁都读得到)⇒ 用发起子代的那位主体读;别的主体与不存在的 id 同一个 `404 not_found.session`。属主为空的委派子代行(根会话本就无主,或升级前写下、还没回填)只有 fleet 调用方读得到(出示 service 凭据、不带主体头;dev-open 部署不钉人),任何具名主体、没有 service 凭据的无身份调用方同样 404(存量无主会话互认臂 —— 单用户形下 `default` 主对无主的普通会话放行,fork / 删除 / `/sync/*`(同步族自 7.134.0 起)同走这一只 —— 对这些 placed 行同样不放行,审批豁免、notify 同样不放行;fork / 删除 / `/sync/*` 的 fleet 调用方与此处同一个判据:出示 service 凭据、不带主体头,带主体头的 service 凭据请求是那位主体,7.134.0 起);SQL 后端两店每次起服由一次性迁移 M23 在它的窗内回填升级前写下的无主 placed 行(按 7.133.0 起的读法:根的属主,根行不在取记录的父的,都不在写 `_orphan:<id>`;起服读数行 `placedSessionOwnersRemaining` 非零 = 还有没定下的,再起直到为 0),local 后端没有回填臂。属主 `_orphan:<id>` 由 7.133.0–7.143.x 的建行推断与 M23 回填写下(7.144.0 起本服务不再推哨兵,建行原样记引擎交来的属主 —— 已有的哨兵属主随引擎的树属主原样带到它下面新建的子代行上):任何主体都不匹配,只有 fleet-wide / operator 读得到,前缀永久保留。停驻时宿主行属主与引擎交来的不等 ⇒ 停驻否决(那张待批卡作废、那一只子代以失败收尾) |
1991
1996
  | `GET /v1/approvals?owner=user:42` | 高危写审批(durable,需 `DURABLE_APPROVAL=true`):**operator** 看待办队列(可按 owner 过滤);非 operator 只看自己的。队列里某一行的存储体读不出来、重试不会好时,答 `500` + `errorCode:"approvals.read_corrupt"`(**7.134.0+**;与订阅流同码、同一只判官;运维处置那一行后再读,别原样重试);此刻读不出队列(连接 / 超时 / 店抖动)⇒ `503` + `errorCode:"approvals.read_unavailable"`(**7.135.0+**,此前 500 `internal.error`;稍后重发,这一答不表示队列为空)。`GET /v1/assistant/inbox` / `GET /v1/assistant/tasks` 读同一个队列,两种读失败同码同状态(**7.135.0+**)。停驻的**技能卡**(对引擎 `Skill` 工具的调用)的行带 `boundGrantDigest` / `boundGrants`(批准它还授予的东西:技能名 + 两只工具清单;清单只在哈希等于摘要时才有,是部署文本,渲染要按上下文转义),分类器没给判词的卡带 `classifierNoRuling`(`error` / `timeout` / `parse_error`)—— **7.136.0+**,与 `GET /v1/assistant/inbox` 行逐字同值(此前只有收件箱行与活卡帧带);普通卡三位都缺席。`GET /v1/assistant/inbox` 行的 `objective` 是门所属 run 的**目标预览**(写入时已脱敏、截到约 120 字符,与 `GET /v1/assistant/tasks` 读同一列;委派子代的门读派它的那条 run;没有 run 账本 ⇒ `null`;**7.136.0+**,此前原样回显存着的任务目标,运维 `?owner=` 跨看也是原文) |
1992
1997
  | `GET /v1/approvals/stream` | 同一队列的 SSE 推送(范围同上):首拍快照 → `pending` / `resolved` 增量(开了工具审批时另有 `live_pending` / `live_resolved`),空闲时心跳,15 分钟上限(`STREAM_MAX_DURATION` 帧后重连)。**心跳只表示流活着,不表示这一拍读过队列**:队列读不出时每次读失败发一帧 `error`(`approvals.read_unavailable`,流留着、下一拍再读,恢复后同一条流继续);某一行的存储体读不出来、重试不会好时发 `approvals.read_corrupt` 并收流(运维处置那一行后再重连)。15 分钟上限对读失败的拍同样算数。`pending` 帧带的行与 `GET /v1/approvals` 同一只投影(含上面的技能卡三位,**7.136.0+**) |
1993
1998
  | `POST /v1/approvals/<sessionId>/decide` `{"decision":"approve"|"deny","reason":"…"}` | 批/否并恢复挂起任务(CAS,重复决议 409);**仅 operator**(非 operator → 403)。5.0.0 起旧轮询腿 `POST /v1/approvals/<id>` 已退役。停驻的后台子代**复活不了**(行上没有根会话锚;未起名的后台子代不在此列 —— 引擎 7.57.0 起每只子代都有名字,升级前的无名行读作派生名,照常复活)⇒ `deny` 作废这张卡:`200 {taskId, status:"failed", decision:"deny"}`,卡离开待决队列、子代终局 `failed`;`approve` 答 `409 decide.row_unrevivable`、拒句指路 deny(**7.136.0+**;此前两个决定都 409,卡处理不掉)。有根会话锚的停驻子代卡上,`decide` 与删根会话 / 卡作废并发:写口放行之后卡已被作废 ⇒ `422 parked_resume.startup_failed`(卡已作废、什么都没复活;与 `409 decide.gate_moved` 并列为并发终局码,调用方须同时处理 409 / 422) |
@@ -2259,9 +2264,10 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2259
2264
  > 没有家目录读数,座缺席,起服响一行同一只 warn。`~/` 形规则的下单门只认声明(提交那一刻执行环境还没开,读不到实测值):要写 `~/` 形规则就声明 `REMOTE_EXEC_HOME_DIR`。
2260
2265
  > **声明与出厂答案都会被对证**(本版起六条车道同一只判官):k8s、local-docker、ssh、adb、e2b 在每只执行环境开起来那一拍问一次 `$HOME`(与命令同一条 shell、同一份 env;
2261
2266
  > adb 经设备 shell、e2b 是建 / 恢复沙箱时的一次 `commands.run`),host 读它自己组合给子进程的那份 env 里的 `HOME`(会话 `settings.env` 改了 `HOME` 也算);
2262
- > 与主张(`REMOTE_EXEC_HOME_DIR`,或 host 的本进程家目录 / e2b 的 `/home/user`)不一致(去掉尾斜杠后逐字比)或问不出 ⇒ 那只执行环境不开,工具结果里的拒句点名两个值与改法
2263
- > (把 `REMOTE_EXEC_HOME_DIR` 设成实测值、删掉错的声明,或让环境的 `HOME` 读主张那个值);环境答了的不一致记在那只环境上(此后每次回同一句,不再起容器 / pod / 沙箱、不再问),
2264
- > 问不出的那一次不记、下一次重问。e2b 恢复出来的沙箱对不上时重新挂起(快照留着),不杀。
2267
+ > 与主张(`REMOTE_EXEC_HOME_DIR`,或 host 的本进程家目录 / e2b 的 `/home/user`)不一致(去掉尾斜杠后逐字比)或问不出 ⇒ 那只执行环境不开,拒句点名两个值与改法
2268
+ > (把 `REMOTE_EXEC_HOME_DIR` 设成实测值、删掉错的声明,或让环境的 `HOME` 读主张那个值)。host 车道的读数在铸环境时就到手 ⇒ 环境不铸,run 在开跑前失败
2269
+ > (码 `env.home_unconfirmed`,同步腿 409;一条命令没跑、模型一次没调,命令带不带路径都是这一句;7.144.0 起,此前拒句落在工具结果里);远端五车道的读数要环境开起来才有 ⇒
2270
+ > 拒句落在工具结果里,环境答了的不一致记在那只环境上(此后每次回同一句,不再起容器 / pod / 沙箱、不再问),问不出的那一次不记、下一次重问。e2b 恢复出来的沙箱对不上时重新挂起(快照留着),不杀。
2265
2271
  > e2b 的出厂答案 `/home/user` 已于 2026-10-29 在真沙箱上实测一致(缺省模板:用户 `user`、`$HOME` = `/home/user`);自定义模板换了用户的,`$HOME` 若不是它,没声明 `REMOTE_EXEC_HOME_DIR` 的 e2b 部署的每只沙箱建起来就拒,把声明设成沙箱里 `echo $HOME` 的结果(声明同样被对证)。
2266
2272
  > device 车道读不了家目录:声明即座,起服响一行 `execution_lane_home_unmeasured`(点名声明值与「未实测」)。
2267
2273
  >
@@ -2381,6 +2387,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2381
2387
  | HTTP `409`(`/v1/runs`) | 同 session 已有活跃 run | 等它完成 / 用返回的 `activeTaskId` |
2382
2388
  | HTTP `409` + `errorCode:"conflict.idempotency_payload_mismatch"`(7.126.0 起;`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs`) | `Idempotency-Key` 已绑定在一条你读得到的 run 上,而绑定它的那次请求的体与这次不同(规范化:按键排序、去掉 `taskId`;契约 §0.7「同一请求 = 同一份体」)—— 不回放、不新建、那一条不动,体不回显键与那一行;流式腿在开流之后才撞上时,是终帧 `done.result` 带同一 `errorCode` 的 `failed`。A2A 不会遇到 | 重试就重发同一份体;不同的请求换一把新键。与 `conflict.run_exists` 不同:后者是那一行你读不到、或带的 `taskId` 不是那一行 |
2383
2389
  | HTTP `429` | 限流 | 看 `Retry-After` 退避 |
2390
+ | HTTP `409` + `errorCode:"conflict.session_archive_stamped"`(7.144.0 起;`DB_BACKEND=local`;`POST /v1/sessions/:id/fork` 与会话同步导入的提交 `POST …/sync/import/:stagingId/entries`;7.141.0–7.143.0 答的码是 `conflict`) | 这只会话的存档仍带更早版本写下的属主戳(一次性迁移改写不了的,或跳过带迁移的版本升上来的数据根;起服 warn `local_archive_kept_stamped` 点名):不在它上面造新存档、不原地换它的日志(干净追加 / 载荷分叉 / fork / stale 各形的推送同答),什么都没写;读 / 续跑 / 拉取照常,「id 与内容都相同」的重推照常落定 | 按体里的固定句办:删掉这只会话(`DELETE /v1/sessions/:id`)再推。与暂存提交时属主变了的 `errorCode:"conflict"`(出路相反:从第一阶段重来)按码分开 |
2384
2391
  | HTTP `429` + `errorCode:"limit.cost_quota_exceeded"` | per-principal 累计成本配额越顶(`MAX_PRINCIPAL_COST_USD`;**进场门**,不打断在跑的 run) | 看 `Retry-After` / 体 `retryAfterSec` 退避(体 `windowResetsAt` = 当代窗滚过的时刻);窗滚过或调高上限后放行 |
2385
2392
  | HTTP `400` + `errorCode:"request.id_invalid"` | 调用方给的 id 形不对。**会话 id 的形门只有一只** = 任意版本 canonical uuid(壳预设的 v4 也过门):会话口 `:id`(fork / 抹除 / 会话策略 / 2c 同步)与提交口(`POST /v1/tasks` / `/v1/tasks/stream` / `/v1/runs` 体里的 `sessionId`,A2A 的 `contextId` 同)同判,提交口的句子在句首之后点名「改用 uuid、自己记映射」;`GET /v1/sendfile-links?before=` 游标与 `body.taskId` 幂等键要求 uuidv7(时间序 / 幂等键契约) | 按 `errorCode` 分支;句子各自点名要求的形(`must be a canonical uuid` / `must be a uuidv7 …`),只作展示、别锚文案 |
2386
2393
  | HTTP `400` + `errorCode:"request.invalid_json"` + `reason:"not_json"` \| `"not_object"`(`reason` 7.133.0 起) | 请求体不是合法 JSON(`not_json`;所有读体的口同一个体 —— 含 `notify` / `wake` / 权限规则这几口,它们 7.132.x 及以前答 `request.body_shape`,其余多数口答 `request.rejected`),或合法 JSON 却不是这一口要的对象(`not_object`,`POST /v1/runs/:id/compact`)。读体超 8 MiB 是 `413 request.payload_too_large`(`field:"body"` / `reason:"too_large"` / `cap`,没有 `count`),不是本码 | 修请求体;按 `errorCode` + `reason` 分支,句子只作展示 |
@@ -2397,10 +2404,10 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2397
2404
  | HTTP `422`(`request settings cannot be applied as written: …`)+ `detail.problems[].reason:"home_unresolvable"`,句子说 `user-settings path rule rooted at the configuration directory`(7.141.0 起) | 请求 `settings.permissions.deny` / `ask` 里一条 `layer:"userSettings"` 的 `/` 形路径规则,而这条沙箱车道既没把 `SEMA_CONFIG_DIR` 转进沙箱、也没有可知的 home(k8s / local-docker / ssh / adb / device 且未声明 `REMOTE_EXEC_HOME_DIR`)—— 配置根读不出,不猜目录(续跑同体 409 同句) | 往沙箱 env 名单里加 `SEMA_CONFIG_DIR`,或声明 `REMOTE_EXEC_HOME_DIR`,或把规则写成 `//` 绝对形 / 放进项目层(读在任务根);见 9.4d |
2398
2405
  | HTTP `429` + `errorCode:"usage.window_exhausted"` | 部署级治理窗耗尽(`USAGE_WINDOWS`,token 或 $ 天花板先满者) | 看响应体 `retryAfterSec` 退避;窗滑动/桶到期后放行 |
2399
2406
  | `terminal:{kind:"failed", code:"usage.window_exhausted"}` | 已受理的 run 在 **turn 边界**撞上治理窗且无法 durable 挂起 | 同上退避后重投;配好 checkpoint 基建则改为 `suspended` 等窗自动续跑 |
2407
+ | HTTP `409` + `errorCode:"env.home_unconfirmed"`(同步 `POST /v1/tasks`;流式 / 后台腿是 `terminal:{kind:"failed", code:"env.home_unconfirmed"}`;7.144.0 起) | 执行环境的家目录主张证不了:部署给环境的家目录(`REMOTE_EXEC_HOME_DIR`,没设时 host 车道 = 服务进程的家目录)与环境里命令看到的 `HOME`(含会话 `settings.env` 里写的 `HOME`)对不上 / 读不出 / 不是绝对路径 ⇒ 环境在开跑前就不铸:一条命令没跑、模型一次没调、一个 token 没烧,不管模型要跑的命令带不带路径都是这一句(句子点名两个家目录与改法)。今天只有 host 车道(含 `run-local`)这样答;远端五车道照旧在开环境那一拍拒、同一句落在工具结果里(读边界要先读环境才判得了的命令 —— 递归读,adb 上连带路径的普通读 —— 在那里先被读边界拦下,句子不点名家目录) | 把 `REMOTE_EXEC_HOME_DIR` 设成环境里 `echo $HOME` 的结果(或删掉它),或让环境的 `HOME` 等于声明,或去掉会话 `settings.env` 里的 `HOME`;原样重试不改判 |
2400
2408
  | HTTP `501`(`/v1/runs`) | 内存模式不支持异步 | 配持久存储(`DB_BACKEND=mysql` / `pg` + 坐标;7.106.0 起给了坐标必须点名 `DB_BACKEND`) |
2401
- | HTTP `500` + `errorCode:"corrupt_session"`(7.104.0 起;流式口在应答头之后则是 `done` 帧的 `result.terminal.code`,后台 run 是 run 行的 `errorCode`) | 这只会话的**存档读不出**、唤不醒(文件后端:会话日志没有会话头;SQL 后端:一行事件读不出)。这一轮没跑,一个 token 没烧。直接读这只会话的口(头探针 / 事件订阅 / 会话视图 / 改名 / 导出 / 分叉 / 2c 同步)同答(契约 §17;文件后端上事件订阅与改名 7.136.0 起同答,此前一个 200 起流却零帧、一个 404)。会话列表 `GET /v1/sessions` 里它仍是一行,行带 `unreadable: { errorCode }`,码就是这些口对它答的那一个(文件后端,7.142.0 起;此前它从列表里消失;存档文件本身读不出 —— 权限 / I/O —— 时码是 `internal.error`) | **别重试**(确定性失败)。**文件后端**:运维看 `fail_open_total{tag="server.session-repo.corrupt-record-skipped"}` 的计数、stderr 契约行 `[sema] fail-open: server.session-repo.corrupt-record-skipped` 与随带的一条 `fail_open` warn(后两者每进程每 tag 只一次,warn 带首条明细);逐条明细(文件路径、会话 id、行号、成因)每命中一行进探针文件 —— 起服前设 `SEMA_FAILOPEN_PROBE=<文件路径>`(会话头那一行本身读不出时,它就是其中一条);修或移开那份存档。**SQL 后端**不经这只座:坏的那一行由错误句子点名(`session_event.payload` + 会话 id + 序号 `seq`)。要继续工作就换一个新会话 |
2402
- | HTTP `500` + `errorCode:"invalid_session"`(7.137.0 起;流式口在应答头之后则是 `done` 帧的 `result.terminal.code`,后台 run 是 run 行的 `errorCode`) | 这只会话**存着的日志**读得出、结构走不通:某条目的父不在日志里(悬空)、条目是自己的祖先(自指 / 成环)、叶子不在日志里。这一轮没跑、一个 token 没烧(同步提交答 500、不计费)。读整条分支的口(会话视图 `GET /v1/sessions/:id` / `…/memory-status`)同答;事件订阅与头探针只读会话头的叶子,对这一形照旧 200 / 起流(契约 §17.7)。体的 `error` 是引擎原句(点名条目 id,不含盘上路径与会话 id),没有 `reason` 机读位。会话同步**导入**时调用方交来的日志没过校验是另一件事:`422 invalid_entries` | **别重试**(确定性失败)。换一个新会话;或从断点之上的条目分叉;或由运维修好 / 移走那份日志。同一 `Idempotency-Key` 重发会重新受理(这类零工作的拒不占键) |
2403
- | 流里一帧 `engine_notice` `code:"session.records_skipped"`,`detail:{ dropped, entriesKept, sessionId }`(7.104.0 起,只在文件后端) | 这只会话的存档打开时有 `dropped` 条记录读不出、被跳过了;这一轮**照跑**,但模型读到的历史只剩 `entriesKept` 条。存档没修好之前,每次真正打开这只会话都会再说一次(会话缓存命中的轮次不打开、不说;`SESSION_CACHE_TTL_SEC=0` 时每一轮都说) | 告诉用户「这段对话的一部分历史丢了」;运维按 `fail_open_total{tag="server.session-repo.corrupt-record-skipped"}` 的计数与探针文件(`SEMA_FAILOPEN_PROBE=<文件路径>`,每命中一行)里的逐条明细(文件路径、会话 id、行号、成因)查那份文件。非流式 `POST /v1/tasks` 收不到通告帧(只有运维面的计数与探针) |
2409
+ | HTTP `500` + `errorCode:"corrupt_session"`(7.104.0 起;流式口在应答头之后则是 `done` 帧的 `result.terminal.code`,后台 run 是 run 行的 `errorCode`) | 这只会话的**存档读不出**、唤不醒(文件后端:会话日志没有会话头;SQL 后端:一行事件读不出);或(7.144.0 起)存着的日志读得出、**走不成一棵树**:某条目的父不在日志里而回放没丢记录(悬空)、条目是自己的祖先(自指 / 成环)、叶子不在日志里(7.137.0–7.143.0 这一形答 `invalid_session`;句子点名条目 id,读整条分支的口 —— 会话视图 `GET /v1/sessions/:id` / `…/memory-status` —— 同答,事件订阅与头探针只读会话头的叶子,照旧 200 / 起流)。这一轮没跑,一个 token 没烧。直接读这只会话的口(头探针 / 事件订阅 / 会话视图 / 改名 / 导出 / 分叉 / 2c 同步)同答(契约 §17;文件后端上事件订阅与改名 7.136.0 起同答,此前一个 200 起流却零帧、一个 404)。会话列表 `GET /v1/sessions` 里它仍是一行,行带 `unreadable: { errorCode }`,码就是这些口对它答的那一个(文件后端,7.142.0 起;此前它从列表里消失;存档文件本身读不出 —— 权限 / I/O —— 时码是 `internal.error`) | **别重试**(确定性失败)。**文件后端**:运维看 `fail_open_total{tag="server.session-repo.corrupt-record-skipped"}` 的计数、stderr 契约行 `[sema] fail-open: server.session-repo.corrupt-record-skipped` 与随带的一条 `fail_open` warn(后两者每进程每 tag 只一次,warn 带首条明细);逐条明细(文件路径、会话 id、行号、成因)每命中一行进探针文件 —— 起服前设 `SEMA_FAILOPEN_PROBE=<文件路径>`(会话头那一行本身读不出时,它就是其中一条);修或移开那份存档。**SQL 后端**不经这只座:坏的那一行由错误句子点名(`session_event.payload` + 会话 id + 序号 `seq`)。要继续工作就换一个新会话 |
2410
+ | 流里一帧 `engine_notice` `code:"session.lineage_rerooted"`,`detail:{ break, recordsDropped, recordsTotal, leaf, entriesOnPath, sessionId, runId }`(7.144.0 起,只在文件后端;7.104.0–7.143.0 这件事由 `session.records_skipped` 说,已退役) | 这只会话的日志回放时有 `recordsDropped` 条记录读不出、被跳过了;这一轮**照跑**,模型读到的历史是 `entriesOnPath` 条。`break: true` = 被跳过的是分支上的祖先(`missingParentId`),这一轮从点名它作父的那一条(`rootedAt`)续上、`entriesCut` 条更早的条目不在上下文里(7.143.0 及以前这一形整轮失败);`break: false` = 分支完整,从 `leaf` 续上(`leaf: null` = 一条都没剩,从空对话起跑)。日志没修好之前每一轮都会再说一次(会话缓存命中的轮次照样说);进程已持有这只会话时盘上才坏的行,这一轮读的是内存里那份完整历史、不说,重启后的第一轮才说 | 告诉用户「这段对话的一部分历史丢了」;运维按 `fail_open_total{tag="server.session-repo.corrupt-record-skipped"}` 的计数与探针文件(`SEMA_FAILOPEN_PROBE=<文件路径>`,每命中一行)里的逐条明细(文件路径、会话 id、行号、成因)查那份文件。非流式 `POST /v1/tasks` 收不到通告帧(只有运维面的计数与探针) |
2404
2411
  | 流里一帧 `engine_notice` `code:"route.finish_reason_unknown"`,`detail:{ finishReason, sessionId, runId }`(7.126.0 起) | 提供方用一个引擎读不懂的结束原因结束了这一轮:回答按模型留下的样子保留,但可能被提供方截短了 | 告诉用户「这一段回答可能不完整」;同一个词同一 run 只说一次。运维按 `finishReason` 查是哪家网关 / 模型发的 |
2405
2412
  | 流里一帧 `engine_notice` `code:"route.stream_end_ignored"`,`detail:{ statement, word, frames, sessionId, runId }`(7.126.0 起) | 提供方已经结束了这一轮,它的流却又发来了东西(又一个结束 / 一个错误 / 内容);第一个结束算数,之后的没进这一轮 | 一般无需处置;频繁出现 ⇒ 运维查那条网关的流式实现(`statement` / `word` 说的是迟到的第一帧) |
2406
2413
 
@@ -2891,7 +2898,10 @@ plan 起步的任务把计划交给人之后停在 `plan_review` 门上。`POST
2891
2898
  出路:转 `SEMA_CONFIG_DIR`、声明 `REMOTE_EXEC_HOME_DIR`,或把规则写成 `//` 绝对形。转进去的值不是绝对路径 ⇒ deny / ask 422 `unsupported.path_base`(allow 桶见下面「allow 桶同读」)。
2892
2899
  - **层不改准入**:多租部署上的准入档只按这次请求验过的凭据推(9 节速查 `below_trust_floor` 一行);条目上的层只决定规则读在哪。
2893
2900
  - **回滚 / 混版副本**:本版之前的服务对带层条目是桶门 `bad_entry`(新提交 400,续跑 409),不会静默读丢;端见位才发,所以没升级的服务上它照旧只收到纯串。
2894
- - **allow 桶同读**:`"allow": [{ "rule": "Edit(/secret/**)", "layer": "userSettings" }]` 免问的是配置根下 `secret` 的写,不是任务根下的;别的层与纯串照旧读在任务根。
2901
+ - **allow 桶同读**:`"allow": [{ "rule": "Read(/secret/**)", "layer": "userSettings" }]` 放的是配置根下 `secret` 的读,不是任务根下的;别的层与纯串照旧读在任务根。
2902
+ **配置根下的写不归 allow 放**:配置根在每条车道上都在引擎缺省写保护表的 `.sema` 行上(声明的 `SEMA_CONFIG_DIR` 及其以下全命中,转进沙箱的是同名 env 的原值、同一只目录;缺省 `~/.sema` 与沙箱车道缺省的 `<home>/.sema` 带 `.sema` 段),
2903
+ 那里的写照问(卡上 `mandate: "write_protection"`;规则答不了这一类问),带层条目、纯串、`//` 绝对形的 allow 都清不掉 —— user 层 `Edit(/secret/**)` 放不掉配置根下 `secret` 的写。
2904
+ 两处不归这一行:引擎数据根(壳布局 `~/.sema/engine-data`)坐在配置根下时,根里的写按根内的相对路径判;整表替换写保护表(`WRITE_PROTECTED_TABLE_REPLACE`)去掉这一行时随之不在。
2895
2905
  一条一员按位置交给引擎(命中的 `positions` = settings 表的下标);配置根读不出时与 deny / ask 同一个 422;转进沙箱的值不是绝对路径时受理口不拒、引擎开跑时拒这条 run
2896
2906
  (`config.invalid_permission_rules`,issue `unsupported.path_base`)。任何一层的 deny / ask 照旧压过它。
2897
2907
 
@@ -517,24 +517,18 @@ export declare function parkRowFacts(stored: unknown): ParkRowFacts;
517
517
  * (「never a list beside a digest that binds another」—— 一份与摘要对不上的清单会让批准人看着一份清单、批的却是另一份绑定);成因词只在闭集成员时回显。
518
518
  * 本仓**不重算哈希**(core 的 `boundInputHashOf` 不在公开导出面上,清单校验读口 `readBoundBinding` 同样不导出),所以**不许**拿寄存行 `pendingAction` 上的原清单直接投 ——
519
519
  * 原值上的清单是未经哈希校验的(这正是它此前不上队列行的原因,见 `trace/core-keyset-guard.ts` ⑧b 记账的旧注)。两只店各自拿到「这一行的摘要」的办法不同
520
- * (LOCAL 孪生本来就握着 core 对同一行投的摘要;SQL 孪生对可能带两位之一的行读一次该行再调 core 的 `summarizeCheckpoint`,见 `plugins/checkpoint-store-sql.ts`),
520
+ * (LOCAL 孪生本来就握着 core 对同一行投的摘要;SQL 孪生在判官要的那一次整行读里调 core 的 `summarizeCheckpoint` —— 两位与 `rowVerdict` 同一次读、
521
+ * 同一份按字节摘要的记忆,见 `plugins/checkpoint-store-sql.ts` 的 `storedRowProjections`),
521
522
  * 但投影只有这一只:OMIT 契约 —— 缺席即缺席,恒不铸 `undefined` 值的键。
522
523
  *
523
524
  * `boundGrants` 是**部署文本**(技能名、清单条目):本函数不脱敏、不转义,与收件箱行逐字同口径 —— 渲染按上下文转义是客户端的事(契约同句)。
524
525
  * 本投影的产物不进 `card_json`(它是停驻面的投影);问行卡信封的活面事实座(7.143.0)存的是呈卡帧上同名两位(活铸素材,与停驻孪生同一只工厂铸)。
525
526
  *
526
- * 🚧 清单校验读口 `readBoundBinding` 若成为 core 的公开导出,SQL 孪生那一次整行读与 {@link parkRowCarriesSkillCardFacts} 一并退役:`parkRowFacts` 直接读寄存行原值经 core 的读口投两位
527
- * (`grantBindingOf` 也改成它的薄包装 —— 那时 core 是绑定读法的唯一属主)。
527
+ * 🚧 清单校验读口 `readBoundBinding` 若成为 core 的公开导出:`grantBindingOf` 改成它的薄包装(那时 core 是绑定读法的唯一属主)。SQL 孪生的整行读不随之退役 ——
528
+ * 7.144.0 起它首先是判官 `judgeCheckpointRow` 要的([ref]:判官读要求表要整行),两位随它同一次读、同一份记忆,不另开读法。
528
529
  */
529
530
  export type SkillCardFacts = Pick<CoreCheckpointSummary, "boundGrants" | "classifierNoRuling">;
530
531
  export declare function skillCardFacts(summary: SkillCardFacts): SkillCardFacts;
531
- /**
532
- * 这一行的寄存行(`pendingAction`,读自持久层 ⇒ `unknown`)**有没有可能**让 core 的摘要投出两位里的任何一位 —— 只回答「值不值得为它取一次摘要」,**不投值**:
533
- * `tool_approval` 臂上摘要字段是串(core 只在这时回显清单),或成因词键在场(是不是闭集成员由 core 的摘要判)。判错的方向只有一个 —— 多取一次摘要(摘要读数为空),
534
- * 绝不漏取:摘要字段不是串 ⇒ core 的摘要没有清单;成因词键不在 ⇒ 无成因词 —— 两者都不满足的行,摘要里这两位恒空。普通卡(绝大多数行)因此零额外读。
535
- * 只读键在不在 / 是不是串,不读值的内容,所以不需要 {@link readClosedWordSeat} 那样的计数。
536
- */
537
- export declare function parkRowCarriesSkillCardFacts(stored: unknown): boolean;
538
532
  /** [ref](core 7.4.0 [ref])—— 回落卡的计数与窗。`limit` 是**闭二词**(core d.ts 逐字),两个计数是
539
533
  * 非负整数,`autoDenyAfterMs` 是 core 契约里的 `0..2147483647` 非负整数(`0` = 本卡不武装窗:部署把
540
534
  * 旋钮关了,或 TOTAL 档的卡按 [ref]① 恒 0 等人)。四成员**全必填**——core 的 `DenialLimitFallback` 上
@@ -599,6 +593,8 @@ export declare const ApprovalCardSchema: z.ZodObject<{
599
593
  fetch: "fetch";
600
594
  workflow: "workflow";
601
595
  generic: "generic";
596
+ "computer-access": "computer-access";
597
+ "computer-control": "computer-control";
602
598
  }>>;
603
599
  inputHasBidi: z.ZodOptional<z.ZodLiteral<true>>;
604
600
  allowRuleShadowed: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -775,6 +771,8 @@ export declare const ApprovalCardEnvelopeSchema: z.ZodObject<{
775
771
  fetch: "fetch";
776
772
  workflow: "workflow";
777
773
  generic: "generic";
774
+ "computer-access": "computer-access";
775
+ "computer-control": "computer-control";
778
776
  }>>;
779
777
  inputHasBidi: z.ZodOptional<z.ZodLiteral<true>>;
780
778
  allowRuleShadowed: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -1194,10 +1192,29 @@ export declare function buildRevokeFrame(batchId: string, askIds: readonly strin
1194
1192
  */
1195
1193
  export declare function buildReplayFrame(row: Pick<AskRow, "askId" | "taskId" | "expiresAtMs" | "cardJson">, nowMs: number): ApprovalRequestFrame | undefined;
1196
1194
  /**
1197
- * [ref] —— 持久行上那张卡**要不要人**:卡面 `risk.requiresRealApproval`(铸卡时取自引擎在 `AskRequest` 上盖的同一位,
1198
- * {@link buildApprovalCard} 第三参)。读者 = 耐久 ask 回决口在裁决之前交给引擎判官 `policyCannotAnswer` 的那一问(`host-decision.ts` 的 `heldAskOf`)。
1199
- * 🔴 读不出卡(`card_json` 形不合)⇒ **按要人读**(fail-closed):这一位只用来**拒**策略的放行,读坏的行上拒一次可以改成人答 / 策略拒
1200
- * 再来;反方向(读坏当不要人)会让一次引擎终将拒掉的放行先落成一条宣称 approve 的行。
1195
+ * 「这一问要人」(core `requiresRealApproval`:只有真人逐次答得了 —— 一揽子席、记住的放行、策略的放行都不行)的**唯一读法**:
1196
+ * 引擎在**这一腿**请求上盖的那一位 ∪ 这只问**行上那张卡**记着的那一位(`risk.requiresRealApproval`;新铸的卡上那一位就是本函数对铸卡那一腿的读数,
1197
+ * 见 {@link buildApprovalCard} 第三参)。任一为真即真 —— 并集只升不降:同一只问被另一腿再问(换副本重入、混版窗)读到不同的引擎事实时取严,
1198
+ * 永不因采信行或采信请求而放宽。
1199
+ * 两个输入各自可缺,调用方只交「手里有什么」:手里还没有行(会话授权短路、新铸那张卡、无问店)⇒ `recordCard` 缺席;手里没有这一腿的请求
1200
+ * (起服接管的登记、耐久问回决口 —— 只有行)⇒ `legRequest` 缺席。请求上的位按引擎的契约 `=== true` 严判(真才带;别的真值是照另一份契约写的)。
1201
+ * 读者:`tool-approval.ts` 的活审批腿(读侧会话授权短路的事实、铸卡、条目定稿 —— 条目 / 「本会话放行」判据 / 策略放行门 / 两族帧 / 列表行、
1202
+ * 起服接管登记、策略层自己交出去的问)与本文件的 {@link storedCardDemandsAPerson}。core 停驻行(`PendingAction.tool_approval`)上的孪生座是
1203
+ * 另一只店、另一个写者,读法是 {@link readParkRowTrueSeat}。
1204
+ */
1205
+ export declare function askDemandsAPerson(legRequest: {
1206
+ readonly requiresRealApproval?: unknown;
1207
+ } | undefined, recordCard: Pick<ApprovalCard, "risk"> | undefined): boolean;
1208
+ /**
1209
+ * 问行上存的卡(`card_json`)交给 {@link askDemandsAPerson} 的那份**记录**:读得出 ⇒ 那张卡;读不出 ⇒ 一份「要人」的记录。
1210
+ * 🔴 读不出按要人(fail-closed):这一位只用来**拒**(策略的放行、记住的放行),读坏的行上拒一次可以改成人答 / 策略拒再来;反方向(读坏当不要人)
1211
+ * 会让一次引擎终将拒掉的放行先落成一条宣称 approve 的行、或让一张要人卡被记住。读者:{@link storedCardDemandsAPerson} 与 `tool-approval.ts` 里
1212
+ * 「行在、但这一腿没有采信行上信封」的那一臂(`ensureAsk` 报错后复核读读到行)。
1213
+ */
1214
+ export declare function storedPersonDemandRecord(cardJson: unknown): Pick<ApprovalCard, "risk">;
1215
+ /**
1216
+ * [ref] —— 持久行上那张卡**要不要人**(手里只有行:经 {@link askDemandsAPerson},请求缺席;读不出按要人,见 {@link storedPersonDemandRecord})。
1217
+ * 读者 = 耐久 ask 回决口在裁决之前交给引擎判官 `policyCannotAnswer` 的那一问(`host-decision.ts` 的 `heldAskOf`)。
1201
1218
  */
1202
1219
  export declare function storedCardDemandsAPerson(cardJson: unknown): boolean;
1203
1220
  /** {@link buildApprovalPreamble} 的返回形——帧 + 两个**计数**(调用方按自己的 logger 记 warn;本模块
@@ -399,12 +399,6 @@ export function skillCardFacts(summary) {
399
399
  ...(summary.classifierNoRuling !== undefined ? { classifierNoRuling: summary.classifierNoRuling } : {}),
400
400
  };
401
401
  }
402
- export function parkRowCarriesSkillCardFacts(stored) {
403
- if (typeof stored !== "object" || stored === null || !("kind" in stored) || stored.kind !== "tool_approval")
404
- return false;
405
- const digest = "boundGrantDigest" in stored ? stored.boundGrantDigest : undefined;
406
- return typeof digest === "string" || ("classifierNoRuling" in stored && stored.classifierNoRuling !== undefined);
407
- }
408
402
  export const DenialLimitFallbackSchema = z
409
403
  .object({
410
404
  consecutive: z.number().int().nonnegative(),
@@ -667,9 +661,15 @@ export function buildReplayFrame(row, nowMs) {
667
661
  return undefined;
668
662
  return buildApprovalRequestFrame({ askId: row.askId, taskId: row.taskId, approvalId: row.askId, card: stored.card, expiresAtMs: row.expiresAtMs, nowMs, live: stored.liveFacts });
669
663
  }
664
+ export function askDemandsAPerson(legRequest, recordCard) {
665
+ return legRequest?.requiresRealApproval === true || recordCard?.risk.requiresRealApproval === true;
666
+ }
667
+ const UNREADABLE_CARD_DEMANDS_A_PERSON = { risk: { requiresRealApproval: true } };
668
+ export function storedPersonDemandRecord(cardJson) {
669
+ return readStoredCardEnvelope(cardJson)?.card ?? UNREADABLE_CARD_DEMANDS_A_PERSON;
670
+ }
670
671
  export function storedCardDemandsAPerson(cardJson) {
671
- const parsed = readStoredCardEnvelope(cardJson);
672
- return parsed === undefined || parsed.card.risk.requiresRealApproval;
672
+ return askDemandsAPerson(undefined, storedPersonDemandRecord(cardJson));
673
673
  }
674
674
  export function buildApprovalPreamble(rows, nowMs, replayMax) {
675
675
  const frames = [];
@@ -416,7 +416,7 @@ export function createExecutionEnv(ctx) {
416
416
  });
417
417
  }
418
418
  else if (config.remoteExec?.provider === "host") {
419
- const gitBaseEnv = new RemoteHostExecutionEnv({ ...homeDirSeat, workspaceDir: config.worktreeIsolation.repoRoot, inheritEnv: "all", backgroundShell: config.requirePrincipal !== true });
419
+ const gitBaseEnv = new RemoteHostExecutionEnv({ workspaceDir: config.worktreeIsolation.repoRoot, inheritEnv: "all", backgroundShell: config.requirePrincipal !== true });
420
420
  executionEnvFactory = withWorktreeIsolation(executionEnvFactory, {
421
421
  repoRoot: config.worktreeIsolation.repoRoot,
422
422
  ...(config.worktreeIsolation.allowedRoots ? { allowedRoots: config.worktreeIsolation.allowedRoots } : {}),
@@ -25,7 +25,6 @@ import type { ServiceWorkflowJournalStore, SessionCaptureRecordSource, StoreBack
25
25
  import type { createSessionStore } from "../plugins/session-store.js";
26
26
  import type { QuestionCoordinator } from "../question.js";
27
27
  import type { ToolApprovalCoordinator } from "../tool-approval.js";
28
- import { type PeerInboundFace } from "../peer-inbound-face.js";
29
28
  import type { MemoryBackend } from "@sema-agent/core";
30
29
  import type { MemorySyncRunner } from "../memory-sync-client.js";
31
30
  import type { MailboxRecipientLifecycle } from "../plugins/mailbox-recipient-lifecycle.js";
@@ -281,10 +280,16 @@ export interface EngineNoticeMetricsSeat {
281
280
  }
282
281
  export declare function createEngineNoticeForwarder(logger: {
283
282
  warn: (msg: string, fields?: Record<string, unknown>) => void;
284
- }, router?: EngineNoticeRouter, metrics?: EngineNoticeMetricsSeat,
285
- /** [ref]:收信方决断面的坐标表(进程单例,与 core 的 held 队列注册表同寿命;它只读两条 peer 通告的坐标,见 `peer-inbound-face.ts`)。
286
- * 默认值与 `router` 同一条理由:装配点多,必填会让「新车道忘了传」变成被扣的信静默不列。 */
287
- peerInbound?: Pick<PeerInboundFace, "observe">): NonNullable<RunnerDeps["onNotice"]>;
283
+ }, router?: EngineNoticeRouter, metrics?: EngineNoticeMetricsSeat): NonNullable<RunnerDeps["onNotice"]>;
284
+ /**
285
+ * [ref](core 7.62.0 [ref]):**进程缺省通告席** —— 引擎在没有自己席的地方说的话(四个无席收值口:品牌 brain 构造 / 一次请求的路由读 / 对端拨号 /
286
+ * 直建蒸馏座;core 读法:在场的席恒赢,缺席才落这里)交给与有席通告**同一只**转发体({@link createEngineNoticeForwarder}:先结构化 `engine_notice` 日志、再分流)。
287
+ * 修前这些话落 `console.warn` 裸句 —— 同一种事实两种写法。构造引擎的两个进程入口(`boot/stage-01-config.ts` / `run-local.ts`)在 logger 建起之后各装一次;
288
+ * 装之前(声明自有凭据名、读配置那几步)引擎若无席说话仍落 `console.warn`(那几步不建 brain,四个收值口一个都不经过)。
289
+ */
290
+ export declare function installDefaultEngineNoticeSeat(logger: {
291
+ warn: (msg: string, fields?: Record<string, unknown>) => void;
292
+ }, metrics?: EngineNoticeMetricsSeat): void;
288
293
  /**
289
294
  * `onNotice` **席**的装配判据本身 —— 「`warn` 真在场才铸席」这一条从此是**一个可直接调用的函数**,
290
295
  * 不是散在装配点、只能靠扫源码文本去猜的一行三元(codex 交叉复审 R1-[medium],验真后修)。