@sema-agent/server 7.112.0 → 7.114.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 (88) hide show
  1. package/MIGRATION.md +95 -0
  2. package/USAGE.md +4 -4
  3. package/dist/approval-ask-audit-store.d.ts +3 -4
  4. package/dist/approval-ask-audit-store.js +3 -3
  5. package/dist/approval-card.d.ts +4 -4
  6. package/dist/boot/coordinators.js +1 -1
  7. package/dist/boot/memory-boundary.d.ts +2 -1
  8. package/dist/boot/memory-consolidation.d.ts +1 -1
  9. package/dist/boot/resolve-spec.js +2 -2
  10. package/dist/boot/stores.js +4 -6
  11. package/dist/boot/workflow-orchestration.js +4 -4
  12. package/dist/config-catalog.js +6 -5
  13. package/dist/config-invariants.d.ts +27 -0
  14. package/dist/config-invariants.js +54 -39
  15. package/dist/config-types.d.ts +5 -11
  16. package/dist/config-types.js +0 -1
  17. package/dist/config.d.ts +0 -16
  18. package/dist/config.js +44 -69
  19. package/dist/env-facts.d.ts +3 -1
  20. package/dist/env-facts.js +5 -1
  21. package/dist/execution-lane-caps.d.ts +54 -30
  22. package/dist/execution-lane-caps.js +48 -20
  23. package/dist/hooks/hook-runner.d.ts +87 -7
  24. package/dist/hooks/hook-runner.js +113 -20
  25. package/dist/host-decision.d.ts +5 -3
  26. package/dist/hosted-posture.d.ts +2 -2
  27. package/dist/http/admission.js +8 -4
  28. package/dist/http/request-guards.js +2 -2
  29. package/dist/http/resume-legs.d.ts +9 -10
  30. package/dist/http/resume-legs.js +64 -18
  31. package/dist/http/route-ctx.d.ts +38 -8
  32. package/dist/http/route-ctx.js +4 -0
  33. package/dist/http/routes/background.js +2 -2
  34. package/dist/http/routes/capabilities.js +2 -2
  35. package/dist/http/routes/mcp-reconnect.js +2 -2
  36. package/dist/http/routes/notify-wake.js +3 -3
  37. package/dist/http/routes/runs.js +19 -13
  38. package/dist/http/routes/session-sync.js +6 -6
  39. package/dist/http/routes/sessions.js +20 -20
  40. package/dist/http/routes/tasks.js +1 -0
  41. package/dist/http/send.d.ts +1 -1
  42. package/dist/http/send.js +1 -1
  43. package/dist/http/wire-types.d.ts +1 -1
  44. package/dist/local-data-root.d.ts +14 -0
  45. package/dist/local-data-root.js +5 -0
  46. package/dist/observability/corrupt-read-seat.d.ts +46 -66
  47. package/dist/observability/corrupt-read-seat.js +79 -63
  48. package/dist/observability/fail-open.d.ts +28 -0
  49. package/dist/observability/fail-open.js +28 -0
  50. package/dist/observability/run-terminal.js +39 -0
  51. package/dist/orchestration/workflow-agent-session-index.d.ts +1 -5
  52. package/dist/orchestration/workflow-agent-session-index.js +2 -2
  53. package/dist/orchestration/workflow-notify-journal.d.ts +1 -3
  54. package/dist/orchestration/workflow-notify-journal.js +2 -2
  55. package/dist/plugins/approval-ask-store-file.d.ts +1 -4
  56. package/dist/plugins/approval-ask-store-file.js +2 -2
  57. package/dist/plugins/file-resume-anchor-store.d.ts +1 -6
  58. package/dist/plugins/file-resume-anchor-store.js +2 -4
  59. package/dist/plugins/file-run-store.d.ts +0 -4
  60. package/dist/plugins/file-run-store.js +1 -3
  61. package/dist/plugins/remote-env-host.d.ts +0 -35
  62. package/dist/plugins/remote-env-host.js +0 -8
  63. package/dist/plugins/remote-env-local-docker.d.ts +5 -13
  64. package/dist/plugins/remote-env-local-docker.js +2 -4
  65. package/dist/plugins/send-file-ledger.d.ts +1 -5
  66. package/dist/plugins/send-file-ledger.js +2 -2
  67. package/dist/plugins/store-backend.js +8 -9
  68. package/dist/plugins/user-input-ledger-file.d.ts +1 -5
  69. package/dist/plugins/user-input-ledger-file.js +2 -2
  70. package/dist/run-local.d.ts +1 -1
  71. package/dist/run-local.js +2 -2
  72. package/dist/runs.js +1 -0
  73. package/dist/spec-fields.js +0 -1
  74. package/dist/store-posture.d.ts +7 -7
  75. package/dist/store-posture.js +0 -1
  76. package/dist/task-settings.d.ts +13 -0
  77. package/dist/task-settings.js +3 -0
  78. package/dist/tool-approval.d.ts +1 -1
  79. package/dist/trace/core-keyset-guard.d.ts +1 -1
  80. package/dist/trace/project.js +2 -3
  81. package/dist/trace/redact.d.ts +18 -0
  82. package/dist/trace/redact.js +4 -0
  83. package/dist/unattended-permission-mode.d.ts +9 -2
  84. package/dist/unattended-permission-mode.js +5 -1
  85. package/dist/user-input-ledger.d.ts +24 -5
  86. package/dist/user-input-ledger.js +46 -11
  87. package/package.json +4 -3
  88. package/scripts/retired-mode-seed-drain-local.mjs +317 -0
package/MIGRATION.md CHANGED
@@ -7,6 +7,101 @@
7
7
  > ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
8
8
  > 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。
9
9
 
10
+ ## 7.114.0(车UW,S-995;车UY)—— `settings.hooks` 工具事件 matcher:别名写成正则形的同一个错误,提交当场 400(7.112.0 只拒字面形) · `@sema-agent/settings-schema` 7.0.1 + `@sema-agent/core` 7.39.2:车道 / 存储词表改读正本 · 写了 `remote-docker` 的 `config.d/remote-exec.json` 拒启 · local 车道坏会话规则文件改拒 · 被拒的 `resumeAt` 不再挪会话叶
11
+
12
+ - **🔴 settings 面硬破坏(响亮拒,无迁移代码、无兼容形)**。① *谁受影响*:提交体 `settings.hooks` 里 PreToolUse / PostToolUse / PostToolUseFailure 的 `matcher` **选中引擎工具别名、却选不中它的正名**的 —— 7.112.0 只拒字面的 `ListPeers` / `Read|ListPeers`,
13
+ `^ListPeers$` / `ListPeers.*` / `(ListPeers)` 这类正则写法当时 200、钩子永不命中(core 按 wire 名匹配,别名不是 wire 名)。判据现在是对 core 工具目录、按 hook-runner 运行期**同一只**匹配函数求值(字面 / 名字表读法:`|` 与 `,` 分隔、逐段 trim,与 CC 2.1.280 同形;区分大小写)。
14
+ ② *怎么改*:把 matcher 改成**正名** `ListAgents`(错误句直接给出);`ListAgents|ListPeers` / `List.*` 这类**也选中正名**的写法不受影响。③ *不改会怎样*:该**新提交** `400 request.field_invalid`(错误句点名别名与正名)。
15
+ 耐久 resume 重放**不判**(会砖 parked 任务;与 7.112.0 同):存量 body 里的别名 matcher 在续跑腿照旧永不命中、同一份 hooks 里其余钩子照常生效。
16
+ **不在本规则内(仍受理)**:大小写错的别名(`"listpeers"` —— CC 同形:区分大小写、静默不跑)与「选不到目录里任何名」另一类(含目录之外的 MCP 工具名,受理面无从区分),后者候设计(见 CHANGELOG `## Unreleased` 7.114.0 线 · 车 UW)。
17
+ **正则只是顺带选中别名也会被拒**:规则问的是「这只 matcher 选不选别名 / 选不选正名」,不问写的人本意 —— 一只写来匹配目录外 MCP 工具、只是顺带也选中 `ListPeers` 的正则(如 `.*Peers`、`mcp__.*|.*Peers`)同样 400,
18
+ 而错误句说的是「改成 `ListAgents`」。出路:在正则里把别名排除掉(如 `(?!ListPeers$).*Peers`),或改成不会选中它的写法。
19
+ - **🔴 运行期 matcher 求值与 CC 2.1.280 同形(行为面;不是配置面硬破坏 —— 是钩子开始触发)**。① *谁受影响*:`settings.hooks` 里 matcher 带**两端空白、分隔符两侧空白或用逗号分隔**的(`" Bash"` / `"Bash "` / `"Read | Bash"` / `"Read,Bash"`)—— CC 容忍,sema 此前把它们当「选不中」(钩子永不触发、无任何声音)。② *怎么改*:无需改 —— 它们现在按作者本意命中(PreToolUse 的 deny 钩子也真拦);**区分大小写、不剥词内空白**(`bash` / `"Ba sh"` 照旧不命中,CC 同形)。含 `.` / `*` / 括号等正则字符的 matcher **一字不动**(整体当锚定正则)。③ *不改会怎样*:升级后,**以前因空白永不触发的钩子会触发** —— 若你依赖「带空白的钩子是死的」(不是合法用法),删掉那条。耐久 resume 的存量 hooks 里带空白的 matcher 在续跑腿同样开始命中。**读法**:只含 `[A-Za-z0-9_-]`、`|`、`,`、空格的 matcher 按 `[|,]` 拆、逐段 trim、大小写敏感、词内空白不剥,对所有带 matcher 的事件(含 StopFailure / PreCompact / PostCompact 与 `if` 条件名部)同一只读法。**已知与 CC 的差异(未做)**:CC 的空白 / 逗号放行按事件分档(sema 不分);CC 正则不锚定。依据:对 CC 2.1.280 的黑盒实测(空白 / 大小写);逗号分隔按参照实现的读法对齐,没有黑盒实测。
20
+ - **多租户车道(`requirePrincipal: true`):字面判保留,另有一处放宽见下条**:该车道 `settings.hooks` 整只被忽略(`task_hooks_ignored`),受理面走**字面档 = 7.112.0 的字面判保留**(裸 `ListPeers` / `Read|ListPeers` 照旧 400;正则写法照旧 200),且**零调用方正则执行**;字面档与运行期共用同一只字面 / 名字表读法,所以**带空白或逗号分隔的别名**(`" ListPeers"` / `"Read | ListPeers"` / `"Read,ListPeers"`)在该车道也同拒 400(只多拒、不放宽)。上面的正则求值只在单用户车道。
21
+ - **受理面判序与资源闸(行为面,零新码;两条车道同一条顺序)**:matcher 超 8 192 字符 ⇒ 两条车道都不读、「未判」、照旧受理;字面 / 名字表读法(只含名字字符、`|`、`,`、空格)两条车道都先走、不占求值预算;
22
+ 只剩正则形时,单用户车道在一次受理共享的 100 ms 墙钟预算里求值(灾难回溯的正则用尽预算 ⇒ 那一只与其后各只正则形「未判」、照旧受理),多租户车道对正则形不判。
23
+ 「未判」过 `recordFailOpen`(F 类 tag `server.hooks.matcher-unjudged`,`reason` ∈ oversize / over_budget / invalid_regex;只含事件 / 原因 / 字符数)。正常 matcher 不会碰到。
24
+ **含空段的别名同拒(两条车道)**:前导 / 尾随 / 连续分隔符的写法(如 `ListPeers|`)7.113.0 受理(且永不命中),自 7.114.0 拒。
25
+ **拒放集合相对 7.113.0 的一处放宽**:**超过 8 192 字符的字面别名 matcher**(如一长串名字交替里夹着 `ListPeers`)7.113.0 拒,自 7.114.0 两条车道都判「未判」、受理。
26
+ - **🔴 `config.d/remote-exec.json` 写了 `provider: "remote-docker"` ⇒ 拒启**(只影响 `CONFIG_PROVIDER=local` 的单机形)。① *谁受影响*:本机配置目录里那份文件点名 `remote-docker` 的部署
27
+ (7.113.x 及以前:启动只告警「未接线」、照用 env 车道跑)。② *怎么改*:远端 docker 守护进程的真形是 `local-docker` 车道 + `DOCKER_HOST`(写进文件的 `local-docker` 臂,或设
28
+ `REMOTE_EXEC=local-docker` + `DOCKER_IMAGE` + `DOCKER_HOST`);不想用文件就删掉它。③ *不改会怎样*:启动报 `config.d/remote-exec.json under <root> is invalid: …` 并退出(settings-schema 7.0.0 起
29
+ 该词不在车道表里,文件读取直接拒,不再静默落到别的车道)。
30
+ - **🔴 local 车道(`DB_BACKEND=local` / run-local)会话规则文件「在但坏」⇒ 拒**(core 7.39.2)。① *谁受影响*:数据根 `session-policy/` 下某个会话的规则文件读不出 / 解析不出 / 形不对的部署
31
+ (位腐、半截写、手改)。7.113.x:该会话按「没有会话规则」照跑(调用方加的收紧被静默丢掉)。② *怎么改*:按运维日志 `file_store_corrupt_read` 一行的 `path` 找到那份文件 —— 修好它,或删掉它
32
+ (删 = 显式「该会话无规则」,原规则不再生效);拒绝句本身**不带**盘上路径(它会经错误体到终端用户)。③ *不改会怎样*:该会话的任务以码 `corrupt` 失败(零轮),`GET` / `PUT /v1/sessions/:id/policy`
33
+ 答 `500 corrupt`;`PUT` 不会覆盖坏文件(覆盖 = 丢证据)。文件**不存在**照旧 = 无规则,不受影响。SQL 车道本来就是拒,不变。
34
+ - **被拒的 `resumeAt` 请求不再动会话**(core 7.39.2):`resumeAt` + `restoreFiles:true` 在没有文件历史边界处被拒(同步口 `422 rewind_snapshot.unresolvable` / 后台口 202 后失败)⇒ 会话叶留在请求前的位置。
35
+ 7.113.x 及以前叶会被挪到目标处,下一次普通请求续在回退点上(之前的回合成了旁枝)。零动作;按日志字节 / 条目数对账的工具会多看到两条 `leaf` 条目(分支 + 写回),请比叶不比条数。
36
+ - **运维面**:`server.session-policy.corrupt-read-fail-open`(7.113.0 起的 P-DEBT 计数)**留着,告警 / 看板不要删**:上一条只覆盖**按键读**(任务装规则、`GET` / `PUT …/policy`);
37
+ 另三条路仍把坏规则行当作「无规则」—— 会话同步导出(导出里没有那位 principal 的规则)、删除**别的**会话时顺带扫掉撕尾的规则行(之后该会话按无规则跑,这一形不出披露行)、数据根重绑;
38
+ 修好之前计数照记。计数里也含已被拒的按键读(座上分不出是哪条路读的),非零就按探针 / `file_store_corrupt_read` 行的 `path` 去修或删那份文件。
39
+ `file_store_corrupt_read` 照旧逐命中一行,`note` 的后果句改写(按键读 = 拒;枚举 / 抹除 / 重绑 = 仍按无规则)。
40
+ - **两句拒启文案改形(只影响按原文 grep 的脚本)**:`REMOTE_EXEC=<未知词>` 的 `valid:` 列表次序改为 `host | e2b | k8s | ssh | adb | local-docker | device`;`MEMORY_ENGINE_BACKEND` 越集值改为
41
+ `env MEMORY_ENGINE_BACKEND="<值>" must be one of: file, pg, tidb`(与 `DB_BACKEND` / `SESSION_BACKEND` 同一句形)。拒 / 放的集合不变。
42
+ - **部署面**:`deploy/sema-up/lib/lanes.json` 自此由 settings-schema 车道表**生成**(字节与 7.113.0 相同,零动作);改车道的必需键 / 租户档去 settings-schema 改正本,本仓重生成。
43
+
44
+ ## 7.113.0(车UP)—— `@sema-agent/core` 7.38.3 → 7.39.1(core 7.39.0 硬破坏六条 + 7.39.1 清单披露补全):`attachments.planModeReminder` 退役 · 停放行排空前提 · 策略结算记在策略名下 · 受理账本收消费证据
45
+
46
+ - **🔴 请求面硬破坏:`attachments.planModeReminder` 退役(响亮拒,无兼容形)**。① *谁受影响*:提交体 `attachments` 里带 `planModeReminder`(任何值,含 `false`)的调用方
47
+ (SDK 的 `TaskRequest.attachments.planModeReminder`、自建客户端)。② *怎么改*:删掉这个键 —— 引擎(core 7.39.0)在 `permissionMode:"plan"` 的腿上自己陈述
48
+ plan 姿态(首请求 / 周期提醒 / 退出帧);要 plan 姿态就发 `permissionMode:"plan"`。③ *不改会怎样*:该提交 `400 request.field_invalid`(错误句点名退役与出路)——
49
+ 不再是「受理、引擎在 prepare 拒」。升级前就停放、存量体里带这个键的任务续跑时**丢键照跑**(它要的提醒今天由引擎在 plan 模式下自己铸,不丢意图)。
50
+ - **🔴 升级前排空 ≤ core 7.33.x 时期停放的 checkpoint 行**(core 7.39.0 删掉一次性迁移):`inheritedGate` 仍带退役模式种子(`autoModeRequested` / `approverPosture` /
51
+ 非 `"always"` 的 `shellGate`)的 pending 行 —— 只可能是 server ≤7.105.0 时期停放、至今仍 pending 的行 —— 升级后续跑被拒 `409 checkpoint.unsupported_version`
52
+ (行字节不动、仍 pending)。**在还跑 ≤7.112.0 的部署上**用 `docs/DEPLOY-PREREQS.md` 同名节的只读 SQL 数行(期望 0),非 0 就答完或取消那些 run 再换钉;
53
+ 漏排的行升级后可 `POST /v1/runs/:id/cancel` 放弃,或一次性回滚到 7.112.x 续跑完再升回来。
54
+ - **`DB_BACKEND=local`(File 店)车道的排空**:没有 SQL 可跑 —— 用随包的只读脚本 `scripts/retired-mode-seed-drain-local.mjs`(7.113.0 起在 npm 包里)。
55
+ **升级前你装的还是 ≤7.112.0,脚本不在已装的包里,但不必先升级服务**:`d=$(mktemp -d) && (cd "$d" && npm pack @sema-agent/server@7.113.0 && tar xzf sema-agent-server-7.113.0.tgz) && node "$d/package/scripts/retired-mode-seed-drain-local.mjs"`
56
+ (已升级的,直接 `node <安装目录>/node_modules/@sema-agent/server/scripts/retired-mode-seed-drain-local.mjs`)。
57
+ **读哪个数据根**:不带参时 = 服务自己的那条解析 —— `LOCAL_DATA_ROOT` → `CONFIG_LOCAL_DIR` → `AGENT_DATA_DIR` → `~/.ai-agent`(脚本取的是服务 `loadConfig` 调的同一只函数,不是它自抄的一份),
58
+ 所以请在**与服务同一份 env** 下跑;也可以把数据根当唯一参数递进去。末行 `RETIRED-MODE-SEED rows=… root=<实际读的数据根>` 回显读了哪个目录。
59
+ 只读、不取锁;它把 `checkpoints/checkpoints.snapshot.jsonl`(压实后的行)与 `checkpoints/checkpoints.jsonl`(之后的事件)折成当前状态再判 pending 行(不要用 grep 代替:压实后老行只在快照里)。
60
+ 行形与事件词的判据与 core 的 File 店逐条同(判据坐标写在脚本头注里)。**服务运行中也能跑**:脚本自己判读到的是不是一致的快照 + 账本 ——
61
+ 读前读后各取两只文件的身份(压实会原子换新快照再清空账本,追加会改大小),变了就重读(至多 3 次);仍读不稳就报 rc 2「服务在写,停写或重跑」,不会在半态下报一个假的「干净」。
62
+ **rc 0** = 零命中,可换钉;**rc 1** = 有命中(逐行 `RETIRED-ROW token=… sessionId=… member=…`,按 sessionId 答完或取消再跑);
63
+ **rc 2** = 不给结论 —— 账本里有它不认的事件词、不合形的记录(含坏行、`put` 的行体不合形、缺 `scope` 的快照行;命中的旧卡照列),
64
+ 或**数据根目录不存在、根下没有 `checkpoints/`**(路径打错、env 没对上服务,读到的是一个什么都没有的目录:缺席不等于干净),或读期间文件一直在变、读文件出错。
65
+ - **新镜像码 `checkpoint.config_refused`(409,`retriable:true`)**:resume 的配置门在 claim 前拒,卡仍 pending、run 行停回 `suspended`;体带 `field` ——
66
+ `taskConfig` = 改配置后同一张卡再决,`inheritedGate` = 行记下的模式种子坏了、只能取消这条 run。本服务自己的配置面产不出它(来路只剩嵌入方自写 `resolveSpec`)。
67
+ - **两个 409 码的体多一个 `retriable:true`(additive;落点改判)**:`checkpoint.walltime_axis_retired` 与 `/wake` 口上的 `steering.blocked_by_hook`。键此前缺席;码、状态、`error` 报文不变。
68
+ 读法与本节 `checkpoint.config_refused` / `seat_retired` 同:这一族里 `retriable:true` = 「卡仍 pending(core 在 claim 之前拒)」,**不是**「原样重试会成功」。
69
+ - **`checkpoint.walltime_axis_retired`(`POST /v1/approvals/:session/decide`;账本带已退役的跨片限时配额 `resourceLedger.totalWalltimeSec` 的存量 pending 行)**。
70
+ **Before(7.112.0)**:这个码落终局 —— run 行被驱成 `failed`、会话锁释放,卡仍 pending 却再也没有可续跑的行(孤儿)。
71
+ **After(7.113.0)**:run 行**留 `suspended`、会话锁保持**,卡仍 pending。这条行在 ≥7.39 的引擎上**永远续不了**(同一份字节每次都被同码拒)。
72
+ core 报文建议的「另开新任务」在**同一会话**上会 `409 conflict.session_active_run`(会话锁还在这条行手里),所以出路是:**先 `POST /v1/runs/:id/cancel` 放弃这条 run**(卡 expire、会话释放),再在该会话上另开新任务;
73
+ 或在 core 报文所说的「previous release」的 worker 上续完它 —— 注意 7.112.x 带的 core 7.38.3 已经同样拒这个码,**回滚到 7.112.x 救不了**,要续只能用限时轴退役之前的 core 版本。
74
+ - **`steering.blocked_by_hook` 的 `/wake` 口(带 `message` 的 wake 撞上部署的 `userPromptSubmit` hook 崩 / 超时 / 拒;core 在 claim 之前拒,卡仍 pending)**。
75
+ **Before(7.112.0)**:409 体不带 `retriable`,`/wake` 为这次尝试铸的合成 run 行以无码的终局(`result:null`)落终局 —— 一次零执行的拒绝被记成普通失败。
76
+ **After(7.113.0)**:409 体带 `retriable:true`;合成行以带 `steering.blocked_by_hook` 码的结构化终局落终局,**不占会话**;hook 恢复后**同一张卡**可再 wake。live `steer` / `interrupt` 口上的同名码(422)不变。
77
+ - **审计面(门记录 `tool_end.gate.settlement`,值变更)**:① 无人值守腿(leader worker / 修复腿 / `sema` CLI / 它们的续跑)上机器按类放行的那一问,结算从
78
+ `human_allowed` / `who.party:"person"` 改为 **`policy_allowed` / `who.party:"policy"` / `who.approver:"unattended_class_answer"`**(7.109.0 起登记的「读作机器放行」
79
+ 这条读法退役);② 一切**策略拒**(`UNATTENDED_APPROVAL_POLICY=deny` 的无人可答、run-local 无 TTY 的拒、`settledBy:"policy"` 的决裁)的 `policy_refused`
80
+ `who.party` 从 `"none"` 改为 **`"policy"`**。按 party 词分桶的审计读者改读新词。**混版 / 回滚**:7.112.x 的读面不认 party `policy`,读到 7.113.0 写下的这两种记录会把整条 `gate` 扣下(读面没有 `gate`、计一次投影缺陷)。
81
+ - **结果面(additive)**:`blocked` 终局多一个可选 `terminal.detail { kind:"prompt_blocked_by_hook", cause, hookReason? }`(主提交被部署 `userPromptSubmit` hook 拦下时;
82
+ 同步 200 体 / 流 `done.result` / `GET /v1/runs/:id` / 账本 `done` 行四面同形),`hookReason` 与 `reason` 同一只脱敏。按封闭 schema 校验结果的消费方加这一可选键。
83
+ - **hook 作者 / 嵌入方**:本服务的 hooks 复合体对每一槽的 PreToolUse 结果先过引擎自己的 hook 席收据 —— 契约成员放在原型上 / 不可枚举 / 访问器上的结果、Proxy 载体,
84
+ 经复合体与直接交给引擎**同答**:引擎拒(工具不跑、`permissionDenied` 响)。此前复合体会把原型上的 `deny` 读丢、把 Proxy 的 `allow` 折成普通 allow。本服务自己的
85
+ `createTaskHooks` 只铸普通对象,不受影响;自写裸 `Hooks` 的嵌入方按引擎契约自查。
86
+ - **用户输入受理账本(零 DDL、零动作)**:引擎确认消费一条 live steer(core 7.39.0 起的 `human_input{delivery:"applied"}`)⇒ 受理结清成新词 `consumed`
87
+ (local:`ledger.jsonl` 的 `settled` 行 `how:"consumed"`;SQL:`user_input_acceptance.settle_how = 'consumed'`,列宽够、无 ALTER);确认消费过的输入崩溃时**不再**告知。
88
+ `task.user_steer_undrained{cause:"crashed"}` 的 `message` 事实句改为「…ended before the engine confirmed consuming them」(`detail` 不变;按文本匹配的脚本改读 `detail.cause`)。
89
+ **wire 值变更**:不带 `Idempotency-Key` 的 live steer,其 `human_input.inputId` 自本版等于 steer 回执上的 `messageId`(此前引擎自铸)。
90
+ **回滚到 ≤7.112.x**:local 车道 —— 旧二进制不认 `consumed` 行,启动重放计一条坏行并跳过 ⇒ 那几条受理在它眼里回到「未结清」,若其写者已不在会被按崩溃告知一次
91
+ (宁多说一遍);要干净回滚:停服后删 `<数据根>/user-input-ledger/ledger.jsonl`(代价 = 丢掉尚未告知的崩溃丢失提醒)。SQL 车道 —— 结清行的 `state` 列仍是 `settled`,
92
+ 旧二进制的收敛只扫 `state = 'open'`,不受影响。
93
+ - **嵌入方(类型面)**:包根导出的 `SettleHow` 多一个词 `consumed`(对它穷举的 switch tsc 红即通知);`TaskSpec.outputStyle`(core 7.39.0 新席)本服务不接、请求面不收。
94
+
95
+ ### 运维日志事件名退役一族(车 UQ S-990 / 车 UR S-1000 / S-569)—— 按事件名配告警 / 数行的运维要改配
96
+ - **退役(不再逐记录发 warn)**:`session_repo_corrupt_read` · `workflow_journal_corrupt_read` · `workflow_run_corrupt_read` · `roster_corrupt_read` · `background_agent_corrupt_read`(该座已删,披露归 backend 的 `file_store_corrupt_read`)· `mailbox_corrupt_read`(S-990 六座);`approval_ask_journal_corrupt_read` · `run_store_corrupt_read` · `resume_anchor_corrupt_read` · `send_file_ledger_corrupt_read` · `approval_ask_audit_corrupt_read` · `workflow_notify_journal_corrupt_read` · `workflow_agent_session_ledger_line_rejected` · `user_input_ledger_corrupt_read`(S-1000 八座)。
97
+ - **替代读法(F 类座纪律,同一只记录器)**:每座一个 tag —— 进程内**一次** `fail_open` warn(首条明细 + 指路)、`fail_open_total{tag}` 计数每命中 +1、`SEMA_FAILOPEN_PROBE=1` 时每命中一行探针(`kind=record path= line= form= reason=`);tag 名见 `docs/FAIL-OPEN-CENSUS.md`(第 110–113 / 115 / 117 行与 S-582 / S-928 / S-967 / S-977 既有行)。
98
+ - **保持逐命中响亮(安全轴混座,不退役)**:`file_store_corrupt_read`;其 session-policy 脸的坏读今天仍 fail-open(core #1299 修后改拒),server 7.113.0 起同时计 `server.session-policy.corrupt-read-fail-open`(P-DEBT)。
99
+ - **run-local 车道**(前台 CLI 进程)的会话 repo 仍走混座逐命中 warn,不降。
100
+
101
+ ### 会话读三口的非属主 404(车 UQ S-989;**安全修复:存在性探测口闭合**;wire 值变更)
102
+ - **修前(7.112.0 及更早;7.103.x 的 detail 口读码已是此形,head / events 两口与更早版本未逐版核)**:`GET /v1/sessions/:id`(detail / head / events)对**非属主**调用方答 `not_found.run` + run 长文,对**不存在**的会话答 `not_found.session` + `session not found` —— 同一端点两臂的码与句子都不同,多租户部署里任何持有效凭据的调用方可以据此判断「别人的某个会话 id 存不存在」(读不到内容,只能探存在性;7.112.0 发布字节黑盒实测可区分)。**7.113.0 起两臂逐字节相同**(`not_found.session` + `session not found`)。多租户部署(`REQUIRE_PRINCIPAL=true`)建议升到 7.113.0 及以上;单用户部署没有「非属主」这一臂,不受影响。
103
+ - **兼容面**:读 `not_found.run` 判「会话读被拒」的旧端要改读 `not_found.session`;属主 200 不变;run 面(`/v1/runs/:id`)不变。
104
+
10
105
  ## 7.112.0(车UG,S-924)—— `settings.hooks` 里按工具别名写的 matcher 提交当场 400(此前静默不命中)
11
106
 
12
107
  - **🔴 settings 面硬破坏(响亮拒,无迁移代码、无兼容形)**。① *谁受影响*:提交体 `settings.hooks` 里 PreToolUse / PostToolUse / PostToolUseFailure 的 `matcher`
package/USAGE.md CHANGED
@@ -66,7 +66,7 @@ docker compose up --build --scale service=4 # 4 个无状态 worker,nginx
66
66
  > | `adb`(一台设备) | ✅ | ❌ 拒启(7.104.0 起) |
67
67
  > | `e2b` / `k8s` / `local-docker`(每任务一只沙箱) | ✅ | ✅ |
68
68
  > | `device`(每人自己的设备) | ✅ | ✅ |
69
- > | 未点名(云形进程内 stub,无手) | ✅(`REQUIRE_EXECUTION_LANE=true` 可改成拒启) | ✅(同左) |
69
+ > | 未点名(云形进程内 stub,零手带工具、非手带工具照常执行) | ✅(`REQUIRE_EXECUTION_LANE=true` 可改成拒启) | ✅(同左) |
70
70
 
71
71
  > 🔴 **7.104.0 起 `CONFIG_PROVIDER` 是闭集**:只认 `local` / `remote` / 不设(空串 = 不设),**逐字**比较 —— `Local`、` local`、`remote ` 这类写法**拒启**,拒因句点名旋钮并列出合法词。此前认不出的词照起,却被配置源当成 `local`、被隐式 host 车道当成「不是 local」,local 配置源的根还落回进程 cwd(与车道文件覆写读的本地根分家)。本地配置根只有一条链:`CONFIG_LOCAL_DIR` → `AGENT_DATA_DIR` → `~/.ai-agent`,配置源与 `config.d/remote-exec.json` 读同一个。
72
72
 
@@ -690,7 +690,7 @@ FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启公
690
690
  | 记忆整理阀门判决 + 驱动席(`MEMORY_CONSOLIDATION_DRIVER=on`,或未设而出厂缺省按目录派生 —— 7.104.0 起) | 重启生效 | 阀门开关与整理跑用的模型都在启动期定一次;中心改了 roles / 档位 / 目录条目后,**判决会翻**(派生开 ↔ 关,含只把主模型挪到整理座那一条上)或开着而座漂了 ⇒ `/health` 报 `restartRequired`,`restart.reasons` 含 `memory-consolidation`(7.100.0 新理由;7.104.0 起派生关的那台同样报) |
691
691
 
692
692
  生效与否的观测口=`/health` 世代账键(`configTargetVersion`/`configAppliedVersion`+两 ordinal+`configApplyStaleMs`,见 API 表 `/health` 行)——target≠applied 持续=「改了没生效」的机读信号。
693
- - **`run-local`(一次性本机 CLI)上三张审批名单同样执法**(7.101.0 起,TTY 问 / 无 TTY 拒):`APPROVAL_DENY` 当场拒;`APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` 命中 ⇒ stdin 是 TTY 就 `y/N` 问人,否则当场拒并在 stderr 点名三张名单键;`APPROVAL_AUTO_BUDGET` 照常生效(never-auto 不吃预算);TTY 问句带上这次调用的参数,人答 N 而停下的 run 退 1(停因 `haltedOnUserRejection`)。**无 TTY 的拒记在部署政策名下**(7.110.0 起,S-956;此前记成人拒):该次调用 `tool_end.gate.settlement` = `{kind:"policy_refused", who:{party:"none"}}`,模型读到的是引擎的策略句(「部署政策拒了、此处没有交互审批,别重试,先做不需要审批的部分」)加一句拒因,而不是「用户不想继续,停下来等他」—— 不停批,模型接着做别的部分;那只要人批的动作没执行 ⇒ run 照样退 1,非 `--json` 形 stderr 停因行是 `task incomplete (approval unanswered): …`。参数展示不出而不问就拒的那一形(TTY 在场也一样)同此。这条腿没有 checkpoint 店也**不**拒启 —— 它有一只当场作答的席,名单的 ask 不需要 park(上表「无 checkpoint 店 ⇒ 整组拒」只说服务腿)。此前三张名单在这条腿上无声不生效。
693
+ - **`run-local`(一次性本机 CLI)上三张审批名单同样执法**(7.101.0 起,TTY 问 / 无 TTY 拒):`APPROVAL_DENY` 当场拒;`APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` 命中 ⇒ stdin 是 TTY 就 `y/N` 问人,否则当场拒并在 stderr 点名三张名单键;`APPROVAL_AUTO_BUDGET` 照常生效(never-auto 不吃预算);TTY 问句带上这次调用的参数,人答 N 而停下的 run 退 1(停因 `haltedOnUserRejection`)。**无 TTY 的拒记在部署政策名下**(7.110.0 起,S-956;此前记成人拒):该次调用 `tool_end.gate.settlement` = `{kind:"policy_refused", who:{party:"policy"}}`(7.113.0 起;7.110.x–7.112.x 为 `party:"none"`),模型读到的是引擎的策略句(「部署政策拒了、此处没有交互审批,别重试,先做不需要审批的部分」)加一句拒因,而不是「用户不想继续,停下来等他」—— 不停批,模型接着做别的部分;那只要人批的动作没执行 ⇒ run 照样退 1,非 `--json` 形 stderr 停因行是 `task incomplete (approval unanswered): …`。参数展示不出而不问就拒的那一形(TTY 在场也一样)同此。这条腿没有 checkpoint 店也**不**拒启 —— 它有一只当场作答的席,名单的 ask 不需要 park(上表「无 checkpoint 店 ⇒ 整组拒」只说服务腿)。此前三张名单在这条腿上无声不生效。
694
694
  - **仅 `/v1/tasks`(同步)+ `/v1/runs`(异步)**——`/v1/tasks/stream` 不支持(多次尝试非单流,400)。与 `verify` **互斥**(同时给 → 400)。
695
695
  - 成本上界 = 任务的 `maxCostUsd`(防冷重跑税)。**三条 core 警示**:① 每档**冷重跑**重付输入成本(便宜档常过才划算);② 门收到**未脱敏**输出(自定义门转发外部 verifier 要脱敏);③ **写工具会跑 N 次**——**只用于只读/幂等任务**(每次升级整任务重跑)。开放式任务(找全 bug/文笔)没有可判定 oracle、级联会空转,那种用 `discuss`(广度交叉)而非级联(深度阶梯)。
696
696
 
@@ -836,7 +836,7 @@ UNATTENDED_APPROVAL_POLICY=park
836
836
  - `deny` ⇒ **真无人值守/headless 部署的显式声明**:这类 ask 当场拒绝,让模型自己改道,不积压一堆
837
837
  等不到人的挂起 run。它是**收紧**方向(不放行任何东西)。
838
838
  这一拒记在**部署政策**名下,不记在人名下(7.100.0 起):那次调用的 `tool_end.gate.settlement` 是
839
- `{kind:"policy_refused", who:{party:"none"}}`;模型读到的是「部署政策拒了、此处没有交互审批,别重试,
839
+ `{kind:"policy_refused", who:{party:"policy"}}`(7.113.0 起;此前 `party:"none"`);模型读到的是「部署政策拒了、此处没有交互审批,别重试,
840
840
  先做不需要审批的部分」,run 接着跑(auto 模式下分类器连拒后回落的那一问被拒时,引擎按「无人可判」停下
841
841
  run:终局 `failed` / `classifier.denial_limit`)。此前记成 `human_refused` / `who.party:"person"`,模型被告知
842
842
  「用户不想继续,停下来等他」,run 以 `haltedOnUserRejection` 收束 —— 而这台部署自己声明了没人会来。
@@ -873,7 +873,7 @@ UNATTENDED_APPROVAL_POLICY=park
873
873
  `ordinary` 的问 —— 模式写门对普通写的问、形状层对非只读 shell 的问(写与命令照常落地,与 7.105.0 同)。其余每一类都不答:
874
874
  写保护表目标(`write_protection`)、shell 读会话目录外(`probe`)、MCP / A2A 工具标记、transcript 写、规则店读不出、共享制品读、
875
875
  plan 的写问(`mode`),以及运维 / hook / 规则 / 组织为人提的问(审批名单、治理层、`APPROVAL_NEVER_AUTO` …)。不答的问:
876
- - **有席的腿交给席**:run-local 在终端里跑 ⇒ 问你 y/N;没有 TTY ⇒ 当场拒(stderr 一句;门记录记在部署政策名下 `policy_refused` / `who.party:"none"`,7.110.0 起,S-956)。
876
+ - **有席的腿交给席**:run-local 在终端里跑 ⇒ 问你 y/N;没有 TTY ⇒ 当场拒(stderr 一句;门记录记在部署政策名下 `policy_refused` / `who.party:"policy"`(7.113.0 起;7.110.x–7.112.x 为 `"none"`),7.110.0 起,S-956)。
877
877
  - **无席的腿**(leader 各腿):配了 durable 审批块的部署 ⇒ park,等人经 `/decide` 答(与 7.105.0 同形);没配 ⇒ 当场拒(fail-closed)。
878
878
  - 答集是常量(只 `ordinary`,core 推荐形),**不是旋钮**。7.106.0–7.108.0 的 `UNATTENDED_PERMISSION_MODE` 已退役:**设了即拒启**
879
879
  (`config.env_key_retired`,任何值都拒);从 `.env` 里删掉它。要让机器多答某一类问,先立案,不开旋钮。
@@ -165,7 +165,6 @@
165
165
  */
166
166
  import type { HostDecision } from "./host-decision.js";
167
167
  import { AppendLog } from "@sema-agent/core";
168
- import type { Logger } from "./observability/logger.js";
169
168
  /** 数据子目录名(挂在 local 数据根下,与 `workflow-notify/` 同层同形)。 */
170
169
  export declare const APPROVAL_ASK_AUDIT_DIR = "approval-ask-audit";
171
170
  /** 崩溃收敛行的读面上限(新前旧后截断;有界 wire —— 读面不许随月份线性长)。 */
@@ -358,15 +357,15 @@ export declare class FileApprovalAskAuditStore implements ApprovalAskAuditSink,
358
357
  * (撕尾不进重放、完整末行进重放,修前修后读数同);
359
358
  * 目录已在而盘后来满 / 变只读 ⇒ boot 照常,append 走 `recordFailOpen`。
360
359
  * @param internals.appendLog 测试注入席(见 {@link AuditAppendPort})。
361
- * @param internals.warn 腐读披露座(缺席 ⇒ 重放属主退回 `console.warn`,不退回沉默)。
360
+ *
361
+ * 坏行披露 = F 类座纪律(S-1000:无 warn 出口)—— 每条跳过的行 `recordFailOpen` 计数 + 探针行,warn 每进程每 tag 一次。
362
362
  */
363
363
  constructor(root: string, internals?: {
364
364
  appendLog?: (path: string) => AuditAppendPort;
365
- warn?: Logger["warn"];
366
365
  });
367
366
  /** 重放的坏值计数(`convergeCrashedAsks().corruptLines`):重放属主跳过的坏行(撕尾不计)+ 成因词表外的行(行在、结清照认,坏值照样计数)。 */
368
367
  private corruptLines;
369
- /** 重放前世账本(共用口 `replayJsonlSkipping`):撕尾 = 未落完的写,静默跳过;坏行跳过 + 逐条披露;坏行数另暴露给收敛日志。 */
368
+ /** 重放前世账本(共用口 `replayJsonlSkipping`):撕尾 = 未落完的写,静默跳过;坏行跳过 + 逐条计数 / 探针;坏行数另暴露给收敛日志。 */
370
369
  private replay;
371
370
  private group;
372
371
  /** append + fsync(逐行落盘:进程猝死不丢已记的行 —— 本店的全部价值就在崩溃时刻;写口 = 共用的 `AppendLog`:单次 `writeSync` 一行、末段在打开与每次追加前修(撕尾截、完整末行补换行)、失败的写截回开写处)。
@@ -191,11 +191,11 @@ export class FileApprovalAskAuditStore {
191
191
  const dir = join(root, APPROVAL_ASK_AUDIT_DIR);
192
192
  ensureDir(dir);
193
193
  this.ledgerPath = join(dir, "ledger.jsonl");
194
- this.replay(internals?.warn);
194
+ this.replay();
195
195
  }
196
196
  corruptLines = 0;
197
- replay(warn) {
198
- const { records, skipped } = replayJsonlSkipping(this.ledgerPath, "approval-ask-audit", LEDGER_SHAPE, warn);
197
+ replay() {
198
+ const { records, skipped } = replayJsonlSkipping(this.ledgerPath, "approval-ask-audit", LEDGER_SHAPE);
199
199
  this.corruptLines = skipped;
200
200
  let line = 0;
201
201
  for (const { rec, outOfSetCause } of records) {
@@ -651,8 +651,8 @@ export declare const ApprovalCardSchema: z.ZodObject<{
651
651
  }, z.core.$strict>>>;
652
652
  personalRuleDotsAbsent: z.ZodOptional<z.ZodCustom<AskEvidenceAbsence, AskEvidenceAbsence>>;
653
653
  }, z.core.$strict>>;
654
- askClass: z.ZodOptional<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary">>;
655
- classes: z.ZodOptional<z.ZodArray<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary">>>;
654
+ askClass: z.ZodOptional<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary">>;
655
+ classes: z.ZodOptional<z.ZodArray<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary">>>;
656
656
  fromSubagent: z.ZodOptional<z.ZodLiteral<true>>;
657
657
  sourceTaskId: z.ZodOptional<z.ZodString>;
658
658
  sourceAgentName: z.ZodOptional<z.ZodString>;
@@ -766,8 +766,8 @@ export declare const ApprovalCardEnvelopeSchema: z.ZodObject<{
766
766
  }, z.core.$strict>>>;
767
767
  personalRuleDotsAbsent: z.ZodOptional<z.ZodCustom<AskEvidenceAbsence, AskEvidenceAbsence>>;
768
768
  }, z.core.$strict>>;
769
- askClass: z.ZodOptional<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary">>;
770
- classes: z.ZodOptional<z.ZodArray<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary">>>;
769
+ askClass: z.ZodOptional<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary">>;
770
+ classes: z.ZodOptional<z.ZodArray<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary">>>;
771
771
  fromSubagent: z.ZodOptional<z.ZodLiteral<true>>;
772
772
  sourceTaskId: z.ZodOptional<z.ZodString>;
773
773
  sourceAgentName: z.ZodOptional<z.ZodString>;
@@ -41,7 +41,7 @@ export function createLiveCoordinators(ctx) {
41
41
  });
42
42
  }
43
43
  const approvalAskAudit = config.toolApprovalEnabled && backend?.kind === "local" && config.localDataRoot !== undefined
44
- ? new FileApprovalAskAuditStore(config.localDataRoot, { warn: (event, fields) => logger.warn(event, fields) })
44
+ ? new FileApprovalAskAuditStore(config.localDataRoot)
45
45
  : undefined;
46
46
  if (approvalAskAudit) {
47
47
  const converged = approvalAskAudit.convergeCrashedAsks();
@@ -55,6 +55,7 @@
55
55
  * 也**不投**这个读面(硬造一个消费点 = 造给自己看的 wire 键)。哪天真要在诊断面露它,先有消费方需求。
56
56
  */
57
57
  import { type ExecutionLane, type RemoteExecProvider } from "../execution-lane-caps.js";
58
+ import type { MemoryEngineBackend } from "@sema-agent/settings-schema/execution-lanes";
58
59
  /**
59
60
  * [ref] A3 源头修(黑板 [ref]/[ref]/[ref],[ref])—— `memoryPersistenceCapable` 的**装配层有效值**:
60
61
  * operator 显式 env 恒赢;env 缺席时,`REMOTE_EXEC=host` 腿自动补 `true`;其余一律缺席(现行为)。
@@ -113,7 +114,7 @@ export interface MemoryWriteBoundaryFacts {
113
114
  * file 腿读(memory-scope 的 `resolveMemoryEngineRoot`),pg/tidb 腿的根是
114
115
  * `<LOCAL_DATA_ROOT|数据根>/memory-work`(boot/stores.ts 两支),那条腿上指 MEMORY_ENGINE_DIR 是让
115
116
  * operator 去拧一个不接线的旋钮 —— 修不好还以为自己修了。 */
116
- engineBackend: "file" | "pg" | "tidb";
117
+ engineBackend: MemoryEngineBackend;
117
118
  /** boot 期算得出的文件工具围栏根。空数组 = 算不出(每任务临时目录 / 未配 workspace)⇒ 不判。 */
118
119
  containmentRoots: readonly string[];
119
120
  }
@@ -160,7 +160,7 @@ export interface ConsolidationWiringFacts {
160
160
  /** 记忆后端自带控制面归属吗(`typeof backend.controlPlaneRoot === "string"` 且非空)。 */
161
161
  readonly controlPlaneOwned: boolean;
162
162
  /** 只进文案 —— 让「该去拧哪根旋钮」这句话指向该腿上**真的**有效的那一根。 */
163
- readonly memoryEngineBackend: "file" | "pg" | "tidb";
163
+ readonly memoryEngineBackend: ServiceConfig["memoryEngineBackend"];
164
164
  /** 部署声明的 provenance 姿态(`config.memoryProvenance`;缺席 ≡ core 缺省 `carry`)。 */
165
165
  readonly provenance?: "off" | "carry";
166
166
  }
@@ -34,7 +34,7 @@ import { normalizeAttachments, normalizeResilience, normalizeResumeAtMode, norma
34
34
  import { cwdHonored, deviceCwdHonored, effectiveHostWorkspace, inProcessSingleUserLane, isValidCwd, parseAdditionalDirectories, satisfiedByProcessCwd, shellEnvMismatchCount } from "../task-cwd.js";
35
35
  import { assertRequestA2aUnlocked, resolveRequestA2a } from "../task-a2a.js";
36
36
  import { assertRequestMcpContentOrigin, assertRequestMcpUnlocked, resolveRequestMcp } from "../task-mcp.js";
37
- import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, acceptAppendSystemPrompt, applyTaskSettings, effectivePermissionMode, effectiveThinking, hasConstitutionAnchors, parseTaskSettings, providerDropsAppend, settingsPermissionBucketIssue, withPermissionMode } from "../task-settings.js";
37
+ import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, acceptAppendSystemPrompt, applyTaskSettings, effectivePermissionMode, effectiveThinking, hasConstitutionAnchors, parseTaskSettings, taskHooksHonored, providerDropsAppend, settingsPermissionBucketIssue, withPermissionMode } from "../task-settings.js";
38
38
  import { enableForkFromBody, normalizeRetainSubagentSessions, selfOrchestrationFromBody } from "../task-workflow.js";
39
39
  import { redactSecrets } from "../trace/redact.js";
40
40
  import { DeferredSandboxPathEnv, isSandboxPathAdjudicationLane, sandboxPathEnvSlots } from "./deferred-sandbox-path-env.js";
@@ -210,7 +210,7 @@ export function createResolveSpec(ctx) {
210
210
  const hc = parsedSettings.settings?.hooks;
211
211
  if (!hc)
212
212
  return undefined;
213
- if (config.requirePrincipal === true) {
213
+ if (!taskHooksHonored(config)) {
214
214
  logger.warn("task_hooks_ignored", { honored: false, sessionId: auth?.sessionId ?? null });
215
215
  return undefined;
216
216
  }
@@ -1,6 +1,6 @@
1
1
  import { join } from "node:path";
2
2
  import { FileBackgroundAgentStore, FileMailboxStore, FileRosterStore, FileUsageWindowStore, InMemoryUsageWindowStore, mailboxCrossProcessMountVerdict } from "@sema-agent/core";
3
- import { createCorruptReadSeat } from "../observability/corrupt-read-seat.js";
3
+ import { createCorruptReadFailOpenSeat } from "../observability/corrupt-read-seat.js";
4
4
  import { createMemorySyncRunner, createMemorySyncTransport } from "../memory-sync-client.js";
5
5
  import { memoryEmbedderFor } from "../plugins/memory-embedder.js";
6
6
  import { ensurePgMemoryEmbedderMetaSchema, reconcileEmbedderFingerprint } from "../plugins/memory-embedder-fingerprint.js";
@@ -232,7 +232,7 @@ export async function openStores(ctx) {
232
232
  }
233
233
  else if (backend?.kind === "local") {
234
234
  rosterStore = new FileRosterStore(join(config.localDataRoot ?? localRoot, "roster.json"), {
235
- onCorruptRead: createCorruptReadSeat(logger.warn.bind(logger), "roster_corrupt_read"),
235
+ onCorruptRead: createCorruptReadFailOpenSeat("server.roster.corrupt-record-skipped"),
236
236
  });
237
237
  }
238
238
  if (rosterStore)
@@ -252,9 +252,7 @@ export async function openStores(ctx) {
252
252
  backgroundAgentStore = new TiDBBackgroundAgentStore(mysqlPool);
253
253
  }
254
254
  else if (backend?.kind === "local") {
255
- backgroundAgentStore = new FileBackgroundAgentStore(config.localDataRoot ?? localRoot, {
256
- onCorruptRead: createCorruptReadSeat(logger.warn.bind(logger), "background_agent_corrupt_read"),
257
- });
255
+ backgroundAgentStore = new FileBackgroundAgentStore(config.localDataRoot ?? localRoot);
258
256
  }
259
257
  if (backgroundAgentStore)
260
258
  logger.info("background_agent_store_enabled", { backend: pgPool ? "pg" : mysqlPool ? "tidb" : "file" });
@@ -308,7 +306,7 @@ export async function openStores(ctx) {
308
306
  }
309
307
  else if (backend?.kind === "local") {
310
308
  mailboxStore = new FileMailboxStore(config.localDataRoot ?? localRoot, {
311
- onCorruptRead: createCorruptReadSeat(logger.warn.bind(logger), "mailbox_corrupt_read"),
309
+ onCorruptRead: createCorruptReadFailOpenSeat("server.mailbox.corrupt-record-skipped"),
312
310
  });
313
311
  mailboxRecipientLifecycle = createDropOnlyRecipientLifecycle(mailboxStore);
314
312
  }
@@ -2,7 +2,7 @@ import { FileWorkflowRunStore, InMemoryWorkflowRunStore } from "@sema-agent/core
2
2
  import { join } from "node:path";
3
3
  import { FleetEventBus } from "../fleet/fleet-bus.js";
4
4
  import { LocalWorkflowAgentSessionIndex } from "../orchestration/workflow-agent-session-index.js";
5
- import { createCorruptReadSeat } from "../observability/corrupt-read-seat.js";
5
+ import { createCorruptReadFailOpenSeat } from "../observability/corrupt-read-seat.js";
6
6
  import { FileWorkflowCompletionInbox, InMemoryWorkflowCompletionInbox, resolveCompletionRoute } from "../orchestration/workflow-completion-inbox.js";
7
7
  import { FileWorkflowNotifyJournalStore, JournalingWorkflowRunStore, WorkflowNotifyGate } from "../orchestration/workflow-notify-journal.js";
8
8
  import { SubagentSteerRegistry } from "../orchestration/subagent-steer.js";
@@ -16,11 +16,11 @@ export function createWorkflowOrchestration(ctx) {
16
16
  ? new InMemoryWorkflowRunStore()
17
17
  : (sqlWorkflowRunStore ??
18
18
  new FileWorkflowRunStore(join(config.localDataRoot ?? localRoot, "workflows"), {
19
- onCorruptRead: createCorruptReadSeat(logger.warn.bind(logger), "workflow_run_corrupt_read"),
19
+ onCorruptRead: createCorruptReadFailOpenSeat("server.workflow-run.corrupt-record-skipped"),
20
20
  }))
21
21
  : undefined;
22
22
  const workflowNotifyJournal = config.selfOrchestrationEnabled && config.workflowRunStoreBackend !== "memory"
23
- ? (sqlWorkflowRunStore ? backend.notifyJournal() : new FileWorkflowNotifyJournalStore(join(config.localDataRoot ?? localRoot, "workflows"), undefined, (msg, meta) => logger.warn(msg, meta)))
23
+ ? (sqlWorkflowRunStore ? backend.notifyJournal() : new FileWorkflowNotifyJournalStore(join(config.localDataRoot ?? localRoot, "workflows"), undefined))
24
24
  : undefined;
25
25
  const workflowCompletionInbox = config.selfOrchestrationEnabled
26
26
  ? config.workflowRunStoreBackend === "memory"
@@ -64,7 +64,7 @@ export function createWorkflowOrchestration(ctx) {
64
64
  ? new LocalWorkflowAgentSessionIndex()
65
65
  : (sqlWorkflowRunStore
66
66
  ? backend.workflowAgentSessionIndex()
67
- : new LocalWorkflowAgentSessionIndex(join(config.localDataRoot ?? localRoot, "workflows"), undefined, (msg, meta) => logger.warn(msg, meta)))
67
+ : new LocalWorkflowAgentSessionIndex(join(config.localDataRoot ?? localRoot, "workflows"), undefined))
68
68
  : undefined;
69
69
  const workflowRunStore = baseWorkflowRunStore
70
70
  ? new JournalingWorkflowRunStore(baseWorkflowRunStore, workflowNotifyGate, fleetBus, workflowAgentSessionIndex)
@@ -2,10 +2,11 @@ import { CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES } fr
2
2
  import { CENTER_CREDENTIAL_ENV, DOMAIN_SCHEMAS } from "@sema-agent/settings-schema";
3
3
  import { CENTER_CREDENTIAL_RENEW_LEAD_DEFAULT_MS, CENTER_CREDENTIAL_RENEW_LEAD_MAX_MS, CENTER_CREDENTIAL_RENEW_LEAD_MIN_MS } from "./center-credential-renewer.js";
4
4
  import { THINKING_FORMATS } from "./model-compat.js";
5
- import { AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS, CONFIG_PROVIDER_WORDS, DB_BACKEND_WORDS, DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS, DRAIN_SECOND_SIGNAL_MIN_GAP_DEFAULT_MS, FIRST_BYTE_TIMEOUT_BOUNDS, RESILIENCE_DEFAULTS, TASK_WRITE_FACE_ESCALATIONS } from "./config-types.js";
5
+ import { AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS, CONFIG_PROVIDER_WORDS, DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS, DRAIN_SECOND_SIGNAL_MIN_GAP_DEFAULT_MS, FIRST_BYTE_TIMEOUT_BOUNDS, RESILIENCE_DEFAULTS, TASK_WRITE_FACE_ESCALATIONS } from "./config-types.js";
6
6
  import { webSearchProviderFromEnv, WEB_SEARCH_PROVIDERS } from "./plugins/web-search.js";
7
7
  import { AUTONOMY_MODES } from "./runtime-governance.js";
8
8
  import { REMOTE_EXEC_PROVIDER_WORDS } from "./execution-lane-caps.js";
9
+ import { MEMORY_ENGINE_BACKENDS, SESSION_BACKENDS, STORE_BACKENDS } from "@sema-agent/settings-schema/execution-lanes";
9
10
  import { MODEL_PROVIDER_WORDS } from "./model-provider.js";
10
11
  import { buildDisplaySafeUrl } from "./trace/redact.js";
11
12
  import { describeAllowlistPostures } from "./env-name-allowlist-knobs.js";
@@ -99,7 +100,7 @@ export const CONFIG_CATALOG = [
99
100
  r("COST_QUOTA_WINDOW_SEC", "limitsHttp", "number", "per-principal 成本配额窗长(秒);SQL 腿窗长进桶主键 ⇒ center 腿显式 deferred(重启生效)", { staticDefault: "86400", danger: BILL, configKey: "costQuotaWindowSec", center: cLimits("costQuotaWindowSec", { timing: "restart", restartReason: "cost-quota-window" }) }),
100
101
  r("CROSS_SESSION_DIALOG_EXPIRY", "approval", "enum", "held 跨会话消息等待人审的期限(CC settings 键名 dialogExpiry;未设=不铸键,引擎自落缺省 5m;词表外拒启)", { danger: SEC, enumValues: [...CROSS_SESSION_DIALOG_EXPIRY_VALUES], configKey: "crossSessionDialogExpiry", derivedDefaultNote: "缺席 ⇒ 不铸键,core 自缺省 5m", resolve: (c) => c.crossSessionDialogExpiry ?? null }),
101
102
  r("CROSS_SESSION_INBOUND", "approval", "enum", "入站跨会话消息的部署/组织层治理(CC settings 键名 crossSessionInbound 的 managed 层;未设=本层不表态≠ next-turn;词表外拒启)", { danger: SEC, enumValues: [...CROSS_SESSION_INBOUND_SETTINGS], configKey: "crossSessionInbound", derivedDefaultNote: "缺席 ⇒ managed 层不设,由其余层与 mode-parity 决定", resolve: (c) => c.crossSessionInbound ?? null }),
102
- r("DB_BACKEND", "store", "enum", "store 后端引擎选择(mysql 兼容/pg/本机文件店/纯内存)", { enumValues: DB_BACKEND_WORDS, danger: DATA, derivedDefaultNote: "裸 boot ⇒ local(多租户裸 boot ⇒ memory + notice);有 SQL 信号 ⇒ mysql", configKey: "dbBackend", resolve: (c) => c.dbBackend }),
103
+ r("DB_BACKEND", "store", "enum", "store 后端引擎选择(mysql 兼容/pg/本机文件店/纯内存)", { enumValues: STORE_BACKENDS, danger: DATA, derivedDefaultNote: "裸 boot ⇒ local(多租户裸 boot ⇒ memory + notice);有 SQL 信号 ⇒ mysql", configKey: "dbBackend", resolve: (c) => c.dbBackend }),
103
104
  r("DB_QUERY_TIMEOUT_MS", "store", "number", "per-query DB 超时(ms;坏值回默认姿态+warn,绝不 NaN 进 driver)", { derivedDefaultNote: "缺席 ⇒ 池级不设/计数族 30s(按引擎)", configKey: "dbQueryTimeoutMs" }),
104
105
  r("DEFAULT_SCENARIO", "integrations", "string", "出厂缺省场景(编码产品出厂态;通用中性 persona 用 default 显式选回)", { staticDefault: "code", configKey: "defaultScenario" }),
105
106
  r("DELEGATION_MAX_CONCURRENT", "orchestration", "number", "委派入口并发帽", { derivedDefaultNote: "缺席 ⇒ 不铸键,core 自缺省(CC parity 20)" }),
@@ -232,7 +233,7 @@ export const CONFIG_CATALOG = [
232
233
  r("MEMORY_EMBEDDER_MODEL", "memory", "string", "embeddings 模型名"),
233
234
  r("MEMORY_EMBEDDER_TIMEOUT_MS", "memory", "number", "单次 embed 活性上限(ms)", { staticDefault: "30000" }),
234
235
  r("MEMORY_ENGINE", "memory", "enum", "记忆引擎总开关(注入式文件记忆面;双族词表,未知词拒启)", { enumValues: ["on", "off"], staticDefault: "on", configKey: "memoryEngineEnabled", booleanEnum: { true: "on", false: "off" } }),
235
- r("MEMORY_ENGINE_BACKEND", "memory", "enum", "记忆持久面方言(file=现状;pg/tidb=DB 真身,多租户点亮的唯一门)", { enumValues: ["file", "pg", "tidb"], staticDefault: "file", danger: DATA, configKey: "memoryEngineBackend" }),
236
+ r("MEMORY_ENGINE_BACKEND", "memory", "enum", "记忆持久面方言(file=现状;pg/tidb=DB 真身,多租户点亮的唯一门)", { enumValues: MEMORY_ENGINE_BACKENDS, staticDefault: "file", danger: DATA, configKey: "memoryEngineBackend" }),
236
237
  r("MEMORY_ENGINE_DIR", "memory", "path", "记忆引擎根覆写", { derivedDefaultNote: "缺席 ⇒ core 默认链(AGENT_DATA_DIR → ~/.ai-agent)", configKey: "memoryEngineDir" }),
237
238
  r("MEMORY_ENGINE_REMOTE_LANE", "memory", "enum", "远端执行车道上放行 file 记忆引擎的 plane-split 覆写", { enumValues: ["allow"] }),
238
239
  r("MEMORY_ORG_ADMISSION_MODE", "memory", "enum", "org 记忆准入模式(audit=判决照算纯观察)", { enumValues: ["audit", "enforce"], staticDefault: "enforce", danger: SEC, configKey: "memoryOrgAdmissionMode" }),
@@ -358,7 +359,7 @@ export const CONFIG_CATALOG = [
358
359
  r("REAP_RUN_STALE_SEC", "limitsHttp", "number", "run 活性判陈旧阈(秒)", { staticDefault: "120", configKey: "runStaleSec", center: cLimits("runStaleSec") }),
359
360
  r("REMOTE_EXEC", "orchestration", "enum", "执行车道选择(点名而配不齐/认不出 ⇒ 拒启,绝不静默降级到 host/stub)", { enumValues: REMOTE_EXEC_PROVIDER_WORDS, danger: SEC, derivedDefaultNote: "缺席:CONFIG_PROVIDER=local ⇒ host;其余 ⇒ 进程内 stub(手带整族不挂;残余名册看活体 wiring_manifest 里 mountedBy=hands 的条目数,不按工具名点。想让这一形拒启见 REQUIRE_EXECUTION_LANE)", configKey: "remoteExec", resolve: (c) => c.remoteExec?.provider ?? null }),
360
361
  r("REMOTE_EXEC_HOME_DIR", "orchestration", "path", "执行环境的 home 目录(`~/` 形权限规则的基;core ExecutionEnv.homeDir 座)", { danger: SEC, derivedDefaultNote: "缺席:host/in-process ⇒ os.homedir();e2b ⇒ /home/user;k8s/local-docker/ssh/adb/device ⇒ 无(`~/` 规则 422 响亮拒)", configKey: "remoteExecHomeDir", resolve: (c) => c.remoteExecHomeDir ?? null }),
361
- r("REQUIRE_EXECUTION_LANE", "orchestration", "boolean", "开了之后:REMOTE_EXEC 未点名 ∧ CONFIG_PROVIDER≠local ⇒ **拒启**并点名该设什么。默认关 = 照旧启动,但执行环境是进程内 stub、手带整族不挂,任务仍回 status=completed(活体读数:mountedBy=hands 条目 10 → 0,名册 38 → 19)—— 与「模型不肯用工具」在用户面同形。残余名册判据 = 活体 wiring_manifest 里 mountedBy=hands 的条目数,不按工具名点。托管/云部署推荐开(docs/DEPLOY-PREREQS.md)", { staticDefault: "false", danger: SEC, configKey: "requireExecutionLane" }),
362
+ r("REQUIRE_EXECUTION_LANE", "orchestration", "boolean", "开了之后:REMOTE_EXEC 未点名 ∧ CONFIG_PROVIDER≠local ⇒ **拒启**并点名该设什么。默认关 = 照旧启动,但执行环境是进程内 stub、**零手带工具**(手带整族不挂;活体读数:mountedBy=hands 条目 10 → 0),名册里其余工具照常执行,任务仍回 status=completed —— 凡需要手的请求,与「模型不肯用工具」在用户面同形。判据 = 活体 wiring_manifest 里 mountedBy=hands 的条目数,不按工具名点;名册总条数随 core 版本走,不写死。托管/云部署推荐开(docs/DEPLOY-PREREQS.md)", { staticDefault: "false", danger: SEC, configKey: "requireExecutionLane" }),
362
363
  r("REQUIRE_PRINCIPAL", "auth", "boolean", "多租户开关(posture 判别位:true ⇒ CC 能力族默认收紧)", { staticDefault: "false", danger: SEC, configKey: "requirePrincipal" }),
363
364
  r("RESOURCE_SUSPEND", "approval", "boolean", "durable 路径 opt-in core resourceSuspend(预算/preempt ⇒ 可恢复挂起)", { staticDefault: "false", configKey: "resourceSuspend" }),
364
365
  r("RESOURCE_SUSPEND_MAX_SLICES", "approval", "number", "resume 链片数上限(0=不设;零进展循环兜底)", { staticDefault: "0", configKey: "resourceSuspendMaxSlices" }),
@@ -408,7 +409,7 @@ export const CONFIG_CATALOG = [
408
409
  r("SERVICE_AUTH_TOKEN", "auth", "secret", "共享 service token(写面门;缺席且未开 ALLOW_UNAUTHED_WRITES ⇒ 拒提交)", { danger: SEC }),
409
410
  r("SERVICE_AUTH_TOKENS", "auth", "secret", "per-system 凭据表(tok=system CSV;source 从凭据派生,绝不自报)", { danger: SEC }),
410
411
  r("SESSION_AUTO_TITLE", "orchestration", "boolean", "首次提交自动铸 session 标题(一次 cheap 调用)", { staticDefault: "true", danger: BILL, configKey: "sessionAutoTitle" }),
411
- r("SESSION_BACKEND", "store", "enum", "session 店引擎(auto=探到 DB 用 DB,探不到回内存;回显=boot 解析后的真引擎)", { enumValues: ["memory", "mysql", "auto"], danger: DATA, derivedDefaultNote: "缺席:显式 DB_BACKEND=mysql|pg ⇒ mysql,否则 memory", configKey: "sessionBackend", resolve: (c) => (c.sessionBackend === "tidb" ? "mysql" : c.sessionBackend) }),
412
+ r("SESSION_BACKEND", "store", "enum", "session 店引擎(auto=探到 DB 用 DB,探不到回内存;回显=boot 解析后的真引擎)", { enumValues: SESSION_BACKENDS, danger: DATA, derivedDefaultNote: "缺席:显式 DB_BACKEND=mysql|pg ⇒ mysql,否则 memory", configKey: "sessionBackend", resolve: (c) => (c.sessionBackend === "tidb" ? "mysql" : c.sessionBackend) }),
412
413
  r("SESSION_CACHE_TTL_SEC", "store", "number", "session 读缓存 TTL(秒;0=关)", { staticDefault: "300", configKey: "sessionCacheTtlSec" }),
413
414
  r("SESSION_EVENTS_HOT_MS", "limitsHttp", "number", "session SSE 订阅热探针间隔(ms)", { derivedDefaultNote: "缺席 ⇒ 200" }),
414
415
  r("SESSION_EVENTS_MAX_CONNS", "limitsHttp", "number", "session SSE 并发连接帽", { derivedDefaultNote: "缺席 ⇒ 256" }),
@@ -57,6 +57,33 @@ export interface CrossInvariant {
57
57
  /** 返回拒因;`undefined` = 通过。 */
58
58
  check(input: CrossInvariantInput): string | undefined;
59
59
  }
60
+ /**
61
+ * ── S-1008 ① 推广(车 UZ 推广轮):**拒因句单铸点**是一类 —— 下面五只是 `config.ts` 区里五条跨域拒启的**判据 + 句子的唯一属主**
62
+ * (`executionLaneRequiredRefusal` / `modelProviderRefusal` 同形):boot 的 throw(`config.ts`,锚在那儿)与注册表条目的 `check` **都调它**,
63
+ * 没有第二份文本可漂。修前这五条逐条两份不同的句子(boot 手写一份、表里另写一份),boot 那份是运维在 stderr 读到的(`config-validation.test.ts`
64
+ * 钉着它的指纹),表那份只在热路径上被调 —— 而热键集带不上这些键 ⇒ 实际无读者。句子**取 boot 那份**(逐字不变);类钉 =
65
+ * `test/config-hot/batch1-cross-invariants.test.ts` 的「类钉」一格(每条带 `bootAnchor` 的条目,boot 点不手写句子、且来自注册表本人或 check 同调的函数)。
66
+ * 参数取 `CrossInvariantView` 的窄 `Pick`:boot 期手里只有装配中的各域值,不是整只 config,按真字段名铸一个小对象传入。
67
+ */
68
+ /** `direct-door-anchors` 的状态臂:半开的直连门(开着却缺锚点)。转换臂(门姿态翻转)只活在热路径,留在条目里。 */
69
+ export declare function halfOpenDirectDoorRefusal(v: Pick<CrossInvariantView, "directApprovalDoor" | "durableApproval" | "approvalHmacKeys" | "principalJwtPubkeys" | "principalJwtIss" | "principalJwtAud" | "requirePrincipal" | "operatorPrincipals">): string | undefined;
70
+ /**
71
+ * `operator-principals-required`([ref]-① 普查候选 1/2):BFF 多租户 durable 形(DURABLE_APPROVAL ∧ REQUIRE_PRINCIPAL,直连门关)的空 operator 名单 ——
72
+ * approvals-assistant 四个读面按裸 `isOperator` 划 scope,空名单 true-for-all ⇒ 任一租户 principal 可读他租户的待批队列。单租户 legacy(REQUIRE_PRINCIPAL 未设)刻意保留。
73
+ */
74
+ export declare function operatorPrincipalsRequiredRefusal(v: Pick<CrossInvariantView, "durableApproval" | "requirePrincipal" | "operatorPrincipals">): string | undefined;
75
+ /** `bake-operator-principals`(IMAGE-API-DESIGN §P2.4a):bake 门是 build-host 无限制执行面,空 operator 名单让 isOperator true-for-all = 无限制写入面。 */
76
+ export declare function bakeOperatorPrincipalsRefusal(v: Pick<CrossInvariantView, "imageBakes" | "operatorPrincipals">): string | undefined;
77
+ /**
78
+ * `bake-runner-credential`(IMAGE-API-DESIGN §P2.12 / H1):bake-runner 的身份由它的 bearer 凭据**派生**(token-derived `source`),不是自称的头 ——
79
+ * 没有 `system == BAKE_RUNNER_PRINCIPAL` 的 SERVICE_AUTH_TOKENS 条目,runner 永远过不了 isRunner,claim/ingest/heartbeat 整条管线静默全 403。
80
+ */
81
+ export declare function bakeRunnerCredentialRefusal(v: Pick<CrossInvariantView, "imageBakes" | "authTokens">): string | undefined;
82
+ /**
83
+ * `leader-durable-store`([ref] 车4 件2,台账 P1-9 / P1-10):leader run(N worker、最长 24h、以真 git push 收尾)的登记表没有外部 SQL 店就退化成进程内 Map。
84
+ * 「多副本」在 config 层判不了(副本数不是本进程的知识),可判的**代理**就是 durable 前提本身。
85
+ */
86
+ export declare function leaderDurableStoreRefusal(v: Pick<CrossInvariantView, "leaderEnabled" | "dbBackend">): string | undefined;
60
87
  export declare const CONFIG_CROSS_INVARIANTS: readonly CrossInvariant[];
61
88
  /**
62
89
  * 按 id 取**一条**(S-157②:boot 面的单源化出口)。