@sema-agent/server 7.117.0 → 7.119.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 (159) hide show
  1. package/MIGRATION.md +182 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +58 -28
  5. package/deploy/sema-up/README.md +2 -0
  6. package/deploy/sema-up/chart/templates/minio.yaml +2 -1
  7. package/deploy/sema-up/chart/templates/server-v4pro.yaml +1 -0
  8. package/deploy/sema-up/chart/templates/server.yaml +1 -0
  9. package/deploy/sema-up/chart/values.yaml +7 -1
  10. package/deploy/sema-up/cluster-up.sh +20 -8
  11. package/deploy/sema-up/kube-up.sh +29 -16
  12. package/deploy/sema-up/lib/buckets.sh +21 -0
  13. package/deploy/sema-up/lib/health.sh +33 -0
  14. package/deploy/sema-up/sema-up.sh +14 -9
  15. package/deploy/sema-up/smoke.sh +16 -6
  16. package/deploy/sema-up/stack/compose.yaml +1 -1
  17. package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -1
  18. package/dist/approval-ask-audit-store.js +2 -7
  19. package/dist/approval-card.d.ts +40 -10
  20. package/dist/approval-card.js +11 -1
  21. package/dist/approval.d.ts +55 -17
  22. package/dist/approval.js +74 -33
  23. package/dist/audit.d.ts +12 -1
  24. package/dist/audit.js +62 -2
  25. package/dist/auto-mode-face.d.ts +2 -3
  26. package/dist/bake-runner/main.js +1 -6
  27. package/dist/bake-runner/protocol.d.ts +18 -0
  28. package/dist/bake-runner/protocol.js +22 -2
  29. package/dist/bake-runner/runner.d.ts +12 -8
  30. package/dist/bake-runner/runner.js +5 -9
  31. package/dist/boot/config-center.js +1 -1
  32. package/dist/boot/coordinators.js +4 -1
  33. package/dist/boot/device-lane.d.ts +3 -3
  34. package/dist/boot/device-lane.js +1 -2
  35. package/dist/boot/leader.d.ts +4 -4
  36. package/dist/boot/leader.js +1 -2
  37. package/dist/boot/parked-revive-gate.d.ts +4 -6
  38. package/dist/boot/parked-revive-gate.js +2 -6
  39. package/dist/boot/resolve-spec.js +9 -11
  40. package/dist/boot/runner-deps.js +2 -2
  41. package/dist/boot/stage-07-capability-layer.js +7 -3
  42. package/dist/boot/stage-10-http-server.js +3 -2
  43. package/dist/boot/stores.js +12 -7
  44. package/dist/capabilities/center-plugins.d.ts +2 -0
  45. package/dist/capabilities/center-plugins.js +24 -6
  46. package/dist/capabilities/code-review-council.js +8 -11
  47. package/dist/capabilities/hands-lane.d.ts +25 -0
  48. package/dist/capabilities/hands-lane.js +1 -1
  49. package/dist/capabilities/mcp-probe.d.ts +2 -2
  50. package/dist/capabilities/scenarios.d.ts +34 -7
  51. package/dist/capabilities/scenarios.js +33 -6
  52. package/dist/config-catalog.js +3 -3
  53. package/dist/config.d.ts +14 -18
  54. package/dist/config.js +15 -26
  55. package/dist/deployment-governance.d.ts +5 -7
  56. package/dist/deployment-governance.js +4 -5
  57. package/dist/host-decision.d.ts +114 -31
  58. package/dist/host-decision.js +39 -2
  59. package/dist/http/admission.js +12 -6
  60. package/dist/http/approval-sweeps.js +1 -1
  61. package/dist/http/mcp-dial-deadline.d.ts +35 -0
  62. package/dist/http/mcp-dial-deadline.js +21 -0
  63. package/dist/http/memory-compliance-failure-reply.d.ts +11 -5
  64. package/dist/http/memory-compliance-failure-reply.js +17 -8
  65. package/dist/http/memory-store-refusal-reply.d.ts +14 -11
  66. package/dist/http/memory-store-refusal-reply.js +16 -5
  67. package/dist/http/resume-legs.js +8 -3
  68. package/dist/http/routes/approvals-assistant.d.ts +25 -3
  69. package/dist/http/routes/approvals-assistant.js +27 -26
  70. package/dist/http/routes/capabilities.js +6 -3
  71. package/dist/http/routes/mcp-reconnect.d.ts +17 -0
  72. package/dist/http/routes/mcp-reconnect.js +6 -1
  73. package/dist/http/routes/memory-bundle.js +1 -1
  74. package/dist/http/routes/memory-compliance.js +1 -1
  75. package/dist/http/routes/memory-origin.d.ts +1 -1
  76. package/dist/http/routes/memory-origin.js +1 -1
  77. package/dist/http/routes/runs.js +47 -33
  78. package/dist/http/routes/sessions.d.ts +1 -1
  79. package/dist/http/routes/sessions.js +23 -22
  80. package/dist/http/routes/workflows.js +2 -0
  81. package/dist/http/wire-types.d.ts +17 -13
  82. package/dist/images/manifest.d.ts +7 -1
  83. package/dist/images/manifest.js +18 -16
  84. package/dist/memory-store-refusal.d.ts +38 -18
  85. package/dist/memory-store-refusal.js +25 -7
  86. package/dist/model-unreachable-notice.d.ts +1 -1
  87. package/dist/namespaced-rule-name.d.ts +46 -41
  88. package/dist/namespaced-rule-name.js +36 -22
  89. package/dist/observability/corrupt-read-seat.d.ts +4 -0
  90. package/dist/observability/corrupt-read-seat.js +1 -0
  91. package/dist/observability/fail-open.d.ts +19 -3
  92. package/dist/observability/fail-open.js +19 -3
  93. package/dist/operator-ask.d.ts +6 -2
  94. package/dist/orchestration/workflow-agent-session-index.d.ts +82 -59
  95. package/dist/orchestration/workflow-agent-session-index.js +80 -14
  96. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -0
  97. package/dist/orchestration/workflow-completion-inbox.js +39 -17
  98. package/dist/orchestration/workflow-notify-journal.d.ts +1 -1
  99. package/dist/orchestration/workflow-notify-journal.js +7 -2
  100. package/dist/outbound-dispatcher.d.ts +29 -20
  101. package/dist/outbound-dispatcher.js +105 -16
  102. package/dist/parked-decide.d.ts +43 -48
  103. package/dist/parked-decide.js +54 -16
  104. package/dist/permission-rule-vocab.d.ts +4 -3
  105. package/dist/permission-rule-vocab.js +1 -1
  106. package/dist/plugins/approval-ask-store-sql.d.ts +17 -20
  107. package/dist/plugins/approval-ask-store-sql.js +0 -3
  108. package/dist/plugins/approval-exemption-store.d.ts +4 -0
  109. package/dist/plugins/approval-exemption-store.js +1 -0
  110. package/dist/plugins/blob-backend.d.ts +13 -0
  111. package/dist/plugins/blob-backend.js +26 -0
  112. package/dist/plugins/local-session-store.d.ts +78 -47
  113. package/dist/plugins/local-session-store.js +152 -122
  114. package/dist/plugins/one-time-migrations.d.ts +11 -0
  115. package/dist/plugins/one-time-migrations.js +128 -4
  116. package/dist/plugins/pg-pool.js +1 -1
  117. package/dist/plugins/snapshot-bucket-presence.d.ts +36 -0
  118. package/dist/plugins/snapshot-bucket-presence.js +40 -0
  119. package/dist/plugins/store-backend.d.ts +17 -6
  120. package/dist/plugins/store-backend.js +46 -16
  121. package/dist/plugins/store-open-wait.d.ts +33 -0
  122. package/dist/plugins/store-open-wait.js +73 -0
  123. package/dist/plugins/tidb-pool.js +1 -1
  124. package/dist/plugins/web-search.d.ts +20 -1
  125. package/dist/plugins/web-search.js +39 -10
  126. package/dist/plugins/workflow-run-store-sql.d.ts +7 -6
  127. package/dist/plugins/workflow-run-store-sql.js +5 -7
  128. package/dist/request-key-closure.js +1 -1
  129. package/dist/request-value-closure.d.ts +1 -1
  130. package/dist/rule-entry-list.d.ts +35 -46
  131. package/dist/rule-entry-list.js +18 -26
  132. package/dist/rules-consent.js +3 -0
  133. package/dist/run-local.js +5 -3
  134. package/dist/runs.d.ts +1 -1
  135. package/dist/security.d.ts +5 -3
  136. package/dist/security.js +4 -0
  137. package/dist/session-policy-wire.d.ts +13 -12
  138. package/dist/session-policy-wire.js +11 -11
  139. package/dist/spec-fields.d.ts +13 -11
  140. package/dist/spec-fields.js +4 -7
  141. package/dist/standing-answer-grant.d.ts +14 -2
  142. package/dist/standing-answer-grant.js +8 -1
  143. package/dist/store-posture.d.ts +14 -6
  144. package/dist/store-posture.js +7 -4
  145. package/dist/task-settings.d.ts +27 -55
  146. package/dist/task-settings.js +13 -21
  147. package/dist/tool-approval.d.ts +133 -138
  148. package/dist/tool-approval.js +175 -66
  149. package/dist/trace/core-keyset-guard.d.ts +5 -5
  150. package/dist/trace/redact.d.ts +6 -0
  151. package/dist/trace/redact.js +9 -9
  152. package/dist/trace/self-minted-wire.d.ts +16 -2
  153. package/dist/trace/self-minted-wire.js +7 -0
  154. package/dist/user-input-ledger.d.ts +30 -14
  155. package/dist/user-input-ledger.js +25 -14
  156. package/package.json +2 -2
  157. package/scripts/drain-check-local.mjs +74 -3
  158. package/scripts/drain-check.mysql.sql +43 -17
  159. package/scripts/drain-check.pg.sql +46 -20
package/MIGRATION.md CHANGED
@@ -7,6 +7,188 @@
7
7
  > ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
8
8
  > 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。
9
9
 
10
+ ## 7.119.0 —— 场景指派悬空不再静默落 `default`:`DEFAULT_SCENARIO` 不在册 ⇒ 拒启 · 中心指派的场景名本部署没有 ⇒ 该主体的 run 503 · `APPROVAL_REQUIRE` 的「本会话放行」恢复生效
11
+
12
+ **升级前核查(一句话)**:把 `DEFAULT_SCENARIO`(缺省 `code`)与**中心里每个主体 / 组 / 全局的场景指派**逐名对本部署的在册场景
13
+ (内建 `default` / `code` / `code-review` / `scan` / `discuss` ∪ 配置中心声明的场景;`GET /v1/capabilities` 的 `scenarios` 就是这张表)。
14
+ 对不上的名字,7.118.x 上是**静默跑在 `default`**(工具、提示词、skills、MCP 整包跟着换,零日志);本版起:
15
+
16
+ 1. **`DEFAULT_SCENARIO` 不在册 ⇒ 起服拒**,拒句点名那个名字与在册词表。改 env(或把那个场景声明回配置中心)再起。
17
+ `DEFAULT_SCENARIO` 点名的是**配置中心声明的**场景时,起服要在启动窗内拿到那份声明(中心不可达 / 超窗、且无 last-known-good 缓存 ⇒ 声明缺席 ⇒ 同样拒启,中心恢复后再起)。
18
+ 2. **中心指派的场景名不在册 ⇒ 该主体不带 `scenario` 的提交答 `503 state.scenario_assignment_unknown`**,体 `detail: { scenario, source: "ruling" }`,
19
+ 句子点名指派名与在册词表;零 run、零计费、不带 `Retry-After`。出路:在中心把指派改到在册的名字(或把那个场景加回本部署),然后原样重试
20
+ (本副本读到新指派要等它自己的缓存窗,缺省一分钟)。续跑腿上同一条拒折成 `409 resume_blocked_by_policy`(`blockedBy: "state.scenario_assignment_unknown"`)。
21
+ **在中心新增场景 X、同一拍把主体指派到 X**:本部署的场景表重启才生效、指派是热读的 ⇒ worker 重启之前那个主体的每一次 run 都是这条 503 —— 先重启 worker 让 X 进表,再指派。
22
+ 3. 不变:请求体显式给的未知名仍是 `400 scenario_unknown`;体里显式给在册名照常服务(不受悬空指派影响);在册的指派 / 缺省照常。
23
+ 4. 嵌入方自己装配 `resolveSpec`:缺省场景悬空的装配在**提交时**拿到同一条 503(`detail.source: "deployment"`),起服那一拍没人替你拒。
24
+ 5. 库面 `selectScenario(scenarios, name)`:`name` 不在册(含 `""`)现在**抛**,不再回落 `default`;不传名(`undefined`)仍取内建 `default`。
25
+ 6. `run-local`(单次本地 CLI):`--scenario <不在册的名>` 与 `DEFAULT_SCENARIO` 悬空都退出码 2、点名在册词表(此前静默落 `default`)。
26
+
27
+ **`SESSION_BACKEND=auto` 收紧(行为破坏性)**:配了坐标(`MYSQL_HOST` / `PG_HOST`)而库起服时连不上,7.118.x 上静默落内存(一行 warn、`/health` 200);本版起在 `STORE_LOCK_WAIT_MS`(缺省 60s)窗内重试、到窗**拒启**,拒句点名后端与 host:port。
28
+ 升级前核查:库晚于 server 就绪的编排(k8s 同时起)把窗调大(`STORE_LOCK_WAIT_MS=300000`);要内存就显式写 `DB_BACKEND=memory`。`/health` 的 `sessionBackend` 读到 `memory` ⇒ 这台没落库。能力位 `store.degradedReason` 的词 `db_unreachable` 随之退役(只剩 `local_unwritable`)。
29
+
30
+ **对象存储桶与 MinIO 镜像(sema-up 部署)**:引擎配了对象存储(`MINIO_*` 三键)而 `SESSION_SNAPSHOT_BUCKET`(缺省 `session-snapshots`)桶不存在,7.118.x 上起服绿、之后每分钟一条 404;本版起起服核一次、缺则在 `STORE_LOCK_WAIT_MS` 窗内重试后**拒启**。
31
+ 升级前:自己建桶的部署确认这只桶在(`mc mb <别名>/session-snapshots`)。sema-up 只有**新装、或重跑本版的 kube-up / cluster-up / sema-up.sh** 才建它;存量 sema-up 部署只做 `helm upgrade`(或只换引擎镜像)的,升级前先重跑本版脚本,或手工 `mc mb <别名>/session-snapshots`。同版 sema-up 的 MinIO 镜像从 `:latest` 钉成 `minio/minio:RELEASE.2025-09-07T16-13-09Z`(`imagePullPolicy: IfNotPresent`):
32
+ 存量部署升级前对一下正在跑的镜像(`kubectl -n sema get pod -l app=sema-minio -o jsonpath='{..imageID}'`),不同就 `--set minio.image=<正在跑的那一版>`。
33
+
34
+ ### 审批名单(`APPROVAL_REQUIRE`)的「本会话放行」恢复生效(订正 7.118.0 节第 13 条)
35
+
36
+ 1. **零操作**。7.118.0 节第 13 条说「升级前已落的『本会话放行』对 `APPROVAL_REQUIRE` 的卡失效」—— 本版起**恢复生效**:名单的问是本服务自己的问,
37
+ 它的「本会话放行」记在会话豁免店里,名单门在出问之前读它;同一会话、同一工具命中 ⇒ 名单门不出卡。豁免店的形没有变:7.117.x 及更早落下、
38
+ 7.118.x 期间留在店里的行原样生效,不迁移。不想让哪一行生效:`DELETE /v1/approvals/:sessionId/exemptions/<工具名>` 撤掉,或删会话。
39
+ 2. 名单卡上点「本会话放行」(活卡的 `allow_session` / `/decide` 的 `remember: "session"`)⇒ 记下:回执 `rememberApplied: true`,卡面不再带 `rememberRefused`。
40
+ 卡上同时还有别的问(强制、治理、读会话目录外 ……)时照旧只放行这一次。
41
+ 3. **不变**:名单的问照样 `permissionMode: "bypassPermissions"` 放不掉、auto 模式的分类器答不了 —— 免问只来自这一会话里人说过的「本会话放行」;
42
+ `APPROVAL_NEVER_AUTO`、耐久预算耗尽、请求的 `settings.permissions.ask` / 存下的 ask 行铸的卡照旧记不住(`rememberApplied: false`)。
43
+ 4. **已知边界**:「本会话放行」只在读得到同一只豁免店的实例上生效(SQL 后端跨副本共享;`DB_BACKEND=local` 只在本机数据根)。
44
+ 续跑落到读不到它的实例上 ⇒ 名单的问再问一次。
45
+ 5. **回滚到 7.118.x**:豁免行留在店里、在 7.118.x 上不生效(那一版的读法),零操作。
46
+
47
+ ### local 后端(`DB_BACKEND=local`):会话属主写进会话边档,跨重启保留
48
+
49
+ 1. **零操作**。`<数据根>/session-incarnations.json` 的每一格从「只有化身(一个串)」扩成「化身 + 属主」(`{ "incarnation": …, "owner": … | null }`);
50
+ 本版只在新建 / 登记 / 认领 / 导入时写新形,7.118.x 留下的旧格原样可读(读作「属主没被记下」)。不要手改这只文件:它是严格读的,形不对整只拒。
51
+ 2. **升级窗内的认领**:7.118.x 及更早建的会话没有记下属主,读作无主(它的会话规则读不了也改不了:`409 conflict.session_ownerless`)。
52
+ 升级之后在窗内**第一次带 principal 提交任务**到这只会话(壳的 `--resume` 就是),它被认领给那个 principal(日志一行 info `session_owner_claimed`),
53
+ 之后它的规则照常读写。窗的日期见 `plugins/one-time-migrations.ts` 登记表 M10(由本版的发布准备提交回填);窗关之后不再认领,没被认领的存量会话一直读作无主 ——
54
+ 要清它的规则就删会话(`DELETE /v1/sessions/:id`,规则随之删)。
55
+ 3. **回滚到 7.118.x**:旧版读不懂新形的格(严格读 ⇒ 会话操作整只拒,拒句点名这只文件)。回滚前先把每一格改回只有化身的串
56
+ (`{ "<id>": { "incarnation": "T", "owner": … } }` → `{ "<id>": "T" }`;属主随之丢失,即 7.118.x 本来的行为)。
57
+
58
+ ### 引擎 7.45.0(随本版提货):审批与删除的五处行为变化
59
+
60
+ 1. **删除受保护位置一律要人答**:`rm` / `rmdir` 的目标是文件系统根、顶层目录、家目录、任务根 / 工作目录 / 附加工作目录或它们的祖先(含 `rm -rf *` 在任务根、`rm -rf ~/*`),
61
+ 或 shell 拿到的是运行时才拼出的脚本(`bash -c "$CMD"`、`sh -c "$(…)"`、`eval "$(…)"`)—— 任何模式(含 `bypassPermissions`)、任何 allow(请求 `settings.permissions.allow` 的
62
+ `Bash` / `Bash(rm:*)`、`APPROVAL_GRANT`、存下的 allow 行、技能清单、PreToolUse hook 的 allow)、`auto` 的分类器都清不掉,出一张要人的卡(`askClass: "dangerous_delete"`);
63
+ 没人答就拒(无人值守腿按部署政策拒,`run-local` 无终端时只指终端)。**升级前核查**:靠 `rm -rf *` / `rm -rf <任务根>` 清场、或在 bypass 下跑运行时拼出的 `bash -c` 的脚本化部署,
64
+ 改成按名删(`rm -rf ./build ./dist`)或安排人答卡。没有旋钮放行这一类。已知边界:喂给 shell 标准输入的脚本(`bash <<'EOF' … EOF`、`echo … | bash`)这一版仍按旧判据
65
+ (`default` 出卡、bypass 放行)。
66
+ 2. **`roots` 读面 × `auto`:根外的 shell 读出卡,不再交分类器**。单用户 turnkey 部署缺省 `READ_FACE=open`,不受影响;多租户部署(设了 `REQUIRE_PRINCIPAL`、没设 `READ_FACE`
67
+ ⇒ 引擎缺省 `roots`)与显式 `READ_FACE=roots` 的部署上,`auto` 档的 `cat <根外文件>`、放不进根的拼法(heredoc 提交说明、变量路径)从分类器自动放行变成一张卡(与 `default`
68
+ 同一张;没人在就拒)。要分类器继续答 ⇒ 显式 `READ_FACE=open`。生效来源见 `GET /v1/diagnostics/wiring` 的 `readFace.source`。
69
+ 3. **子代 `agents[].denyTools` 拼错的名字不再让 run 失败**:没有工具叫这个名字(`Bahs`)⇒ 照跑,运维日志一行 `runner_config_advisory`(计数 `runner_config_advisory_total`)
70
+ 点名它 —— 那一条 deny 没生效。`Tool(content)` 形(`Edit(src/**)`)⇒ 整只工具对该子代藏起并同样告一行。仍开跑前拒(`config.agent.unknown_tool`)的:带空白 / 不可见字符的条目、
71
+ 非字符串条目。**退役拼法(`bash`)与大小写孪生(`BASH`)在本服务的部署形上也只告不拒**(7.118.x 上开跑前拒):本服务的委派工具带逐次注入的 extraTools,引擎开跑前列不全子代工具池,
72
+ 不把像 `Bash` 的名字判成拼错;引擎给出宿主声明名册的座后这两形回到开跑前拒。**升级前核查**:把「拼错就失败」当校验的调用方改看那一行告警;靠子代 `denyTools` 藏 shell / 写工具的请求改用会话策略
73
+ `toolDeny` 或部署 deny 规则 —— `denyTools` / `allowTools` 只约束委派池里的工具,带手的子代自带的 Read / Write / Edit / Bash 不受它约束(写 `"Bash"` 也藏不住;引擎后续版本一张表盖全)。
74
+ 4. **沙箱 / 设备车道上的 `//` 绝对形路径规则对相对路径生效**:这些车道上请求规则编译时没有任务根,此前一条 `Read(//w/proj/secrets/**)` 的 deny / ask 对 `secrets/k`、
75
+ `./secrets/k` 这类相对拼法不生效;现在按引擎在每条调用上带的根 / 工作目录解析(拒就拒、问就问)。调用上也没有根(无文件手的腿)⇒ 一张 `rule_unreadable` 卡
76
+ (`bypassPermissions` 带理由放行)。相对形的规则模式(`Read(src/**)`)在这些车道上仍 422。
77
+ 5. **工具名里带不可见字符(零宽、软连字符、双向控制符 …)的条目读成文法错**:`APPROVAL_GRANT` 里有这种条目 ⇒ 拒启;技能 `disallowedTools` 里有 ⇒ 提交 400、
78
+ 存量停驻体续跑 409(`resume_blocked_by_policy`)—— 升级前照 7.117.0 节跑排空判据 `skill-deny-inert`(它同数这一形);请求 `settings.permissions.allow` 里有 ⇒ 开跑时一帧
79
+ `permission.allowed_tool_not_granted`(`reason: "grammar"`)。拒句里那个字符看不见,指引句点名「invisible or control character」。
80
+
81
+ ### 接口面
82
+
83
+ - **`400 rules.legacy_tool_name` 的体顶层多一键 `guidance`**(`PUT /v1/sessions/:id/policy`、同步导入的 `policy` 面):引擎判官对这一条的改法指引原句(退役名 / 对端段铸不出的
84
+ 协议名带;无协议前缀的 `__` 名不带)。句子字节不变。对拒体做精确键集比对的客户端放宽一键。
85
+ - **运维 warn `memory_store_form_refused` 的 `form`** 只取引擎闭集里的词(`symlink` / `file` / `other`);闭集外的值不再写进这一行。409 码与句子不变。
86
+ - **嵌入方直接调引擎的 `steerPriorityOf` / `injectionLaneOf`**:档位不是 `now` / `next` / `later`(`null`、别的词、数字)现在抛 `steering.invalid_content`(此前原样返回,`null` 读作 `next`);
87
+ 只有缺席读作 `next`。本服务的受理口与停驻 steer 列读回都不会把档外值交给它。
88
+ - **嵌入方自装 `Runner` 只给静态 `executionEnv`(没有 `executionEnvFactory`)**:委派的 `isolation: "worktree"` 现在在建任何东西之前拒(`isolation.unavailable`),
89
+ 不再在调用方自己的根里跑子代。本服务的主车道带工厂,不受影响。
90
+ - **`council_done` 事件与 code-review 工具结果 `details.council`**:`lenses` / `perLens` 只含成功的镜头,失败的镜头移到新键 `failedLenses: [{ lens, reason }]`(键恒在,无失败 ⇒ `[]`);
91
+ 对 `perLens` 键集或 `lenses` 计数做精确比对的消费方更新(此前失败镜头在 `perLens` 里是一条 `tokens: 0` 的行)。
92
+ - **请求面 `permissionMode` 多一词 `dontAsk`**(闭集 = 引擎 `PERMISSION_MODES` 全集,受理后原样投引擎);能力位 `permissionModes` 给全集,壳按位发词;词表外拒句的成员表多 `dontAsk`。
93
+ - **退役键 `settledBy` 的 `400 request.body_shape`**(live `/respond`、耐久 ask 账、`/decide` 三条决定口)体顶层多一键 `field: "settledBy"`,三口同一句;壳按码 + `field` 判,不锚文案。
94
+
95
+ ## 7.118.0 —— 审批决定体的出处位 `decidedBy`(取代 `settledBy`)· 能力位 `approvalDecidedBy` · 有审批名单却无检查点店的装配,任务提交一律 501
96
+
97
+ **审批决定体用一只键说「谁做的这个决定」**:`decidedBy?: "person" | "policy"`(缺席 = `"person"`)。三条工具审批决定口 ——
98
+ `POST /v1/tool-approvals/:approvalId/respond`、`POST /v1/tasks/:taskId/asks/:askId/decision`、`POST /v1/approvals/:sessionId/decide` ——
99
+ 同一只读法收它;`"policy"` 让引擎把这次决定记在策略名下(`tool_end.gate.settlement.kind` = `policy_allowed` / `policy_refused`,
100
+ `who.party:"policy"`),**放行与拒两向都可以**(此前一次规则代答的放行只能记成 `human_allowed`)。
101
+
102
+ **要动的(发过 `settledBy` 的调用方)**
103
+
104
+ 1. **`settledBy` 退役,改发 `decidedBy`**。7.93.3–7.117.x 的决定体上有一格 `settledBy?: "policy"`(只配 deny);它与 `decidedBy` 是同一个
105
+ 事实,本版删掉,零别名。送它:三条决定口都 `400 request.body_shape`,拒在裁决之前、卡不动(live `/respond` 的句子点名 `decidedBy`)。
106
+ 迁移 = 一处装配:`settledBy:"policy"` → `decidedBy:"policy"`;放行一向现在也可以带它。
107
+ 2. **见位才发**:`GET /v1/capabilities` 的 `approvalDecidedBy`(恒 `true` 的版本位)在场才发 `decidedBy`。老 server 上耐久两口与
108
+ `/v1/assistant/tasks/:id/plan_review` 对它 400,live `/respond` 静默丢 + 200 —— 位缺席就照老形发。
109
+ 3. **直连门部署**:耐久两口上带 `decidedBy`(不论值)⇒ `400 decided_by_not_in_proof`(它不在决策证明的签名载荷里);错误码
110
+ `settled_by_not_in_proof` 随退役键一起删。live `/respond` 照收。
111
+ 4. **回滚到 7.117.x**:本版 live `/respond` 与耐久 ask 账口上 `decidedBy:"policy"` 的放行,落成 ask 账里一条「`decision = approve` + 来源位 `settled_by = policy`」的行;
112
+ 7.117.x 的读法对放行行不读来源位,同一只 ask 回滚后被重入(故障切换 / 重启后重放)时把它读成**人**放行 —— 审计归属错,不多放权(放行本来就生效)。
113
+ 要审计准确:回滚前让在途的审批都结掉 —— 流内的卡清零、停驻的审批卡决掉或取消(做法:回滚前停收新任务、等在跑的 run 全部结束;流内卡数 = `SELECT COUNT(*) FROM approval_ask WHERE state = 'STREAM_PENDING'`,停驻的 workflow 子代卡照下面「7.118.0 线」第 10 条数),没有还会被重入的 ask,就没有被读错的行。
114
+
115
+ **新的拒(按需处置)**
116
+
117
+ - 闭集外(含 `"sla_timeout"` —— 「等待到点、没人答」只有服务端自己的截止扫说得出;含旧词 `human` / `timeout` / 结算词 / 空串 / 非串)⇒ 400,
118
+ 句子点名闭集:live `/respond` = `request.body_shape`,耐久两口 = `request.field_invalid`。拒在裁决之前,卡不动。
119
+ - 一张**要人**的卡(帧顶层 `requiresRealApproval` / 卡面 `risk.requiresRealApproval`)上 `decidedBy:"policy"` 的放行:live `/respond` =
120
+ `400 request.body_shape`,ask 决定口 = `400 request.field_conflict`(拒在裁决之前,卡不动);`/decide` = 引擎在 CAS 之前拒,
121
+ `409 resume_outcome_invalid` + `field:"hostDecision"`(卡留 pending)。改成人答,或以策略的名义拒。
122
+ - `plan_review` 不收 `decidedBy`(引擎的计划审阅结局没有决定者位)⇒ `400 request.body_shape`,落 `unsupportedKeys`。
123
+ - **有审批名单(`APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` / `APPROVAL_DENY` 任一非空)或显式 `DURABLE_APPROVAL=true`、而装配里没有检查点店** ⇒ 每个任务提交(含续跑腿)`501 capability.checkpoint_store_required`,
124
+ 拒在一切副作用之前;句子点名缺的是耐久席位、怎么配。**只有自己装配 `resolveSpec` 的嵌入方会撞到**:`sema-server` 起服对同一形早就拒启(`DURABLE_APPROVAL=false` / `DB_BACKEND=memory`),到不了任务提交。
125
+ 7.117.0 上这一形的装配读到的是静默放行(审批基线铸成 `undefined`,请求的 `settings.permissions.allow` 清掉普通问后任务零卡落盘)。出路:给装配一只检查点店(`DURABLE_APPROVAL=true` + 带检查点面的后端),或去掉名单;
126
+ 单用户 turnkey(没有名单、没有显式旗)的 allow-all 基线不变。
127
+
128
+ **已知边界(老版本,升级决策知悉)**:≤7.115.x 的 `DB_BACKEND=local` 后端没有文件历史店 —— 会话同步的文件历史 blob 两口(`PUT …/sync/blobs/:hash` 恒 501、`GET …/sync/history/blobs/:hash` 对清单引用的 blob 恒 404 `not_found.blob`),而那一代的能力位 `sessionSync` 仍为真。7.116.0 起拆成独立位 `sessionSyncFileHistory`(local 为假):消费方以这一位为唯一读法,位缺席 / 为假 ⇒ 按「这台不搬文件历史」处置,不要照旧尝试。SQL 后端(两只文件历史店在场)自 7.52 起 blob 面可用,不受此条影响。
129
+
130
+ **订正 7.117.0 节的一行配方**:排空判据 `skill-deny-inert` 逐行精确核的 `node -e` 一行此前写成裸子路径 `import("@sema-agent/server/dist/rule-entry-list.js")`,而本包不导出 `./dist/*` 子路径(`ERR_PACKAGE_PATH_NOT_EXPORTED`);已改成按文件路径 import(7.117.0 节与仓内部署前提文档同步改)。 正确的一行(7.117.0 节正文保持发布时原样,照这里的跑):
131
+ `node -e 'import(process.argv[1]).then(m => console.log(m.skillDenyInertEntryOf(JSON.parse(require("fs").readFileSync(process.argv[2], "utf8")).body) ?? "ok"))' "$(node -p 'require.resolve("@sema-agent/server/package.json").replace(/package\\.json$/, "dist/rule-entry-list.js")')" ctx.json`(包不导出 `./dist/*` 子路径,所以按文件路径 import,`ctx.json` 作第二个实参),
132
+
133
+ **部署前提(拉配置中心的部署,升级决策知悉)**:7.114.0 起引擎按 `@sema-agent/settings-schema` ≥ 7 的形读中心下发的 `workers` 配置;中心仍发 6.x 形时**整份中心配置被拒**,起服回落 env 并 `ready:false`,之后每次刷新照拒(响亮,不静默套用旧形;本仓不为 6.x 形留第二种读法)。**先把中心升到 settings-schema ≥ 7(web-admin SS8 / 8.0.0 + M-R2),再把拉它配置的引擎升到 ≥ 7.114.0**;顺序反了就是上面那一形。7.114.0 节当时没写这一句,在此补。
134
+
135
+ ### 7.118.0 线 —— `@sema-agent/core` 7.42.1 → 7.44.0:规则名只认引擎判官 · allow 侧名字层交引擎 · 技能规则拼法 `Skill(<名>)` · 请求键 `approvalReach` · 崩溃告知按车道分族 · 重放拒体带 `detail.differs`
136
+
137
+ **升级前必读**
138
+
139
+ 1. **嵌入方 import 了引擎的旧名**:`USER_STEER_UNDRAINED_CAUSES` / `UserSteerUndrainedCause` 已改名 `USER_INPUT_UNDRAINED_CAUSES` / `UserInputUndrainedCause`(引擎 7.44.0,零别名;TS 编译红、JS 读到 `undefined`)。换新名,成员不变。
140
+ 2. **规则里的工具名只认引擎的判官**。协议名的服务器段引擎铸不出 / 为空(`mcp__github.com__*`、`mcp__*`、`mcp__`):
141
+ - 运维名表(`APPROVAL_REQUIRE` / `APPROVAL_DENY` / `APPROVAL_NEVER_AUTO`、中心下发)与请求 `settings.permissions.ask` / `deny` 照旧拒,**句子换成引擎的**(带它铸出的写法);按旧句子匹配的脚本要改。
142
+ - 会话规则写口(`PUT /v1/sessions/:id/policy`、同步导入的 `policy` 面)对这一族的码由 `400 request.field_invalid` 改为 `400 rules.legacy_tool_name`(体顶层 `verdict: "unmintable_segment"`、`currentName` = 该写的名字(带通配的协议名上它是摘要名,见本节末「已知边界」),与 `field` / `name` 同层,不在 `detail` 里;第 6 条 steer 重放拒的 `detail.differs` 才是嵌套的)。库里已存的这种死 deny,属主自己的写现在删得掉。
143
+ - 工具段为空的协议名(`mcp__srv__`)照旧在每张表上拒,会话写口仍是 `request.field_invalid`。
144
+ 3. **allow 侧的名字交引擎**:请求 `settings.permissions.allow` 与请求自带技能的 `allowedTools` 里写退役名 / 无前缀短名 / 铸不出的协议名(`bash`、`figma__x`、`mcp__*`)不再 422 / 400,
145
+ 而是受理、开跑时收通告 `permission.allowed_tool_not_granted`(`reason: "tool_name"`,带 `currentName`)—— 这些条目从来什么都不授予,现在只是不在提交时拦。依赖「写错名字当场被拒」的调用方改读那条通告。
146
+ 技能 `disallowedTools` 里的同一族仍是 400(判据换成引擎的条目读法,原因词 `tool_name`);`APPROVAL_GRANT` 里的同一族仍拒启。
147
+ **存量停驻体**里技能 `disallowedTools` 写了这一族(7.116.x 受理过的)⇒ 续跑认领之前 `409 resume_blocked_by_policy`(`blockedBy: request.field_invalid`)——
148
+ 引擎 7.43 起这一类拒侧条目同样让那只技能不激活;随包排空判据 `skill-deny-inert` 同数它们,升级前照 7.117.0 节的做法排空。
149
+ 4. **技能规则只有一种拼法**:`settings.permissions.ask` / `deny` 里 `Skill(<名>)`(精确)/ `Skill(<前缀>:*)`(名字前缀,无词界)现在受理并生效(此前 422);参数面拼法 `Skill(skill:<名>)` 现在 422。先改写再升。
150
+ 持久规则(`/v1/rules`、导入)的 `match` 多两个词 `name` / `name_prefix`;回滚到 7.117.x 的副本读到这种行会把它当坏行。
151
+ 5. **崩溃丢掉的 `priority:"later"` steer 报在 follow-up 一族**:进程崩溃后下一条 run 首拍的告知,`later` 档那几条现在是 `task.user_followup_undrained{followUp, cause:"crashed"}`(其余仍是 `task.user_steer_undrained`);
152
+ `GET /v1/sessions/:id` 的 `userInputUndrained[].kind` 多一个词 `"follow_up"`。按 `code` 写死只认 steer 一族、或按 `kind === "steer"` 过滤的消费方两码同读。回滚到 7.117.x:那几条 `follow_up` 行旧版本读不懂、不告知。
153
+ 6. **`steering.duplicate_input_id` 的 409 体多一格 `detail.differs`**(`text` / `trusted` / `priority` / `actor` 的非空子集):含 `"text"` ⇒ 换键;不含 ⇒ 别换键重发(会排两次),用原值重放。句子是引擎的,已改。
154
+ 7. **新请求键 `approvalReach`**(唯一一词 `"none"`;能力位 `approvalReach`):声明这条 run 上没有人能答审批 —— 有停驻设施的部署上第一次门控调用就停驻(`paused`),要「拒了接着跑」用 `permissionMode:"dontAsk"`。不发它的调用方零变化。
155
+
156
+ 8. **升级那一刻还停驻着的 workflow 子代审批卡**:决定口答 `409 decide.workflow_parks_unreadable`(句子说成因是更早的引擎版本),卡仍 pending、什么都不投递。
157
+ 出路:续跑体里 `runId` 那条 workflow run,它走到那张卡时写下自己的持有者位,之后再决即可。
158
+ 引擎 7.17 之前写下、记录上根本没有 `parks` 的那一代:没有条目、持有不了任何东西 —— 同一张卡上有新引擎写下的持有者时照常交给它;只有它点名这张卡时答
159
+ `409 conflict.resume_context_unavailable`(体里不带 `runId`;出路同上,续跑发起它的那条 workflow run)。想省掉这一步:**升级前在旧版本的数据上**跑随包排空判据 `workflow-park-holder`
160
+ (SQL:`node_modules/@sema-agent/server/scripts/drain-check.mysql.sql` / `drain-check.pg.sql` 的那一段,期望 `hit_rows = 0`;local:`node node_modules/@sema-agent/server/scripts/drain-check-local.mjs`,
161
+ 期望 `WORKFLOW-PARK-HOLDER rows=0`;命中行点名 workflow run),把这些卡先决掉。
162
+ 9. **新码 `409 decide.workflow_park_forked`**(体带 `runIds`):同一条 workflow run 先后被续跑两次、两支都停在同一张卡上(判得出的前提是 park 指针索引齐;一枚竞争指针没写上时决定投给指针在的那一支;指针**写失败**那一形运维计数 `server.workflow-park-index.sync-failed`,**写记录与写指针之间崩溃**那一形不计数 —— 计数为 0 不等于分叉门完好)。重试不会好:续跑 `runIds` 里的一条再决。把未知 409 一律当「稍后重试」的客户端要加这一支。
163
+ `GET /v1/workflows/:id` 的 `parks[]` 条目多两位 `parkedBy` / `generation`(在场才有);钉过条目精确键集的探针要放宽。
164
+ 10. **SQL 后端**:`workflow_agent_session` 主键改成 `(session_id, run_id)`,启动期自动改形(暂存表复制 + 原子换名,任何一步被打断下一次起服从暂存表续上、旧行不丢;启动日志 `one_time_migrations_applied` 的 `workflowParkIndexKey: "reshaped"` 记一次)。
165
+ 按旧主键写过外部查询的运维:同一个 `session_id` 现在可以有多行。
166
+ **SQL 多副本整队一次切,不混跑**:先停光 7.117.x,再起 7.118.0。改形之后 7.117.x 副本对这张表 ——
167
+ PostgreSQL:**每一次写都失败**(它的 upsert 冲突目标是 `(session_id)`,新主键下没有这只唯一约束;新键本来就允许一个子会话多行,这只约束保不住)⇒ 经它停驻的 workflow 子代卡没有指针,决定口决不掉,要人续跑那条 workflow run;
168
+ TiDB / MySQL:写照常落(`REPLACE INTO` 按新主键落行,不再覆盖同一子会话别的 run 的行),读一个子会话时多行里取哪一行不定 ⇒ 续跑链 / 分叉上的卡经它可能决不掉。
169
+ 两方言都有一瞬:整表复制到换名之间旧副本写进旧表的指针随旧表删掉。
170
+ **回滚到 7.117.x**:先排空 workflow 子代的待批卡 —— 在 7.118.0 的数据上跑随包排空判据 `workflow-park-holder`(SQL 两份文件的那一段,期望 `hit_rows = 0`;此时数出的就是全部待批的 workflow 子代卡,逐张决掉或取消发起它的任务);
171
+ 停掉全部 7.118.0 副本后再跑一次确认仍为 0;然后 `DROP TABLE workflow_agent_session`(派生投影,7.117.x 起服按它自己的建表语句重建旧形);再起 7.117.x。不删这张表,PostgreSQL 上 7.117.x 对它的写全部失败。
172
+ 11. **出网代理拒隧道约 2 s 终局**:代理有时拒隧道、几十秒内又恢复的部署,以前靠长阶梯熬过的 run 现在失败、要重提;没有恢复长阶梯的旋钮,升级前稳住代理。终局句以
173
+ `proxy authentication required (HTTP 407)` / `proxy forbids this destination (HTTP 403)` / `proxy refused the tunnel (HTTP <n>)` 开头(按 `[network] fetch failed (` 开头匹配的脚本改成在句内搜)。
174
+ 12. **记忆作用域家必须是真目录**:软链(哪怕指向真目录)或文件形的家在每条碰到它的记忆路由上答 `409 memory.scope_home_not_directory`(此前平铺布局是 500 `internal.memory_control_plane_corrupt`)。
175
+ 升级前 `find <记忆目录> -maxdepth 1 -mindepth 1 ! -type d` 核一眼;要换盘就挂载整个记忆目录。记忆的 409 句子不再带宿主路径(`memory.target_outside_store` 同):路径在运维 warn `memory_store_form_refused { code, path, form? }`。
176
+
177
+ 13. **`ask` 规则卡上的「本会话放行」不再落记录**:`APPROVAL_REQUIRE`、`APPROVAL_NEVER_AUTO`、请求的 `settings.permissions.ask` 或存下的 ask 行铸的卡带 `rememberRefused: true`;
178
+ `allow_session` / `remember: "session"` 只批这一次(`rememberApplied: false`)。**升级前已落的「本会话放行」对 `APPROVAL_REQUIRE` 的卡失效**:每次照问(旧行还在豁免列表里、按名撤得掉,
179
+ 只是不再压住名单门的问)。工具不该被问就从名单里拿掉;要自动批用 `APPROVAL_AUTO_BUDGET`。
180
+ 14. **没有类集的卡不记**:引擎 7.37 之前写的停驻卡(server ≤ 7.108)没有类集,`remember: "session"` 在它们上面只批这一次。
181
+ 15. **技能卡**带 `boundGrantDigest` 时,live「本会话放行」按同技能同清单记住;耐久 `remember: "session"` 仍不记技能卡 —— 停驻的技能卡在运维队列行 / 推送帧上带 `rememberRefused: true`,`/decide` 回执 `rememberApplied: false`;
182
+ 活卡停驻之后(`tool_approval_complete` 带 `parked: true`)对它的迟到应答同样不记。`rememberRefused` 按应答路读:活的面与停驻的面各说自己那条路(契约「缺席的读法」表)。
183
+
184
+ **已知边界(升级决策知悉)**
185
+
186
+ - workflow 子代的审批卡,只要点名它的记录里有一条的 `parks` **条目坏形**(或这一位在场却不是列表),决定口对这张卡恒答 `409 decide.workflow_parks_unreadable`,
187
+ 直到运维把那条记录(体里的 `runId`)的 JSON 修好;续跑解不开 —— 那条记录的指针不随任何后继记录消失。体里另带 `readableHolderRunId` 时,工作可以从那条记录续上。
188
+ 引擎从不写坏形,这一形只来自存储层改写或手改。
189
+ - 规则里写了带通配的协议名(`mcp__*`、`mcp__git*__x`)时,拒句里引擎的改法指引说「its tools are named mcp__<一串摘要>__<tool>」—— 那串摘要不是任何服务器的名字,照写不生效;
190
+ 会话规则写口拒体顶层的 `currentName` 在这一形上也是那个摘要名(本服务的拒句不再在句尾追加它)。写具体的服务器名即可(`mcp__<server>__*` / `mcp__<server>`)。指引句在后续引擎版本修正。
191
+
10
192
  ## 7.117.0 —— 2026-10-06
11
193
 
12
194
  ### 7.117.0 线 —— `@sema-agent/core` 7.41.2 → 7.42.1:「这个调用不用问」只有一个去处(引擎的 allow 层)· 升级前排空带 `settings.permissions.allow` 的停驻行 · `APPROVAL_GRANT` 语义变宽 · 卡面披露键改名 `allowRuleShadowed` · 部署 hook 回 `allow` 现在清问 · 能力位 `settings.permissions.allowLayer` · 通告码改名 · 新错误码 `config.image_invalid`
package/README.md CHANGED
@@ -189,7 +189,7 @@ The server is configured entirely through environment variables. The most import
189
189
  | `SSH_HOST_FINGERPRINT` / `SSH_KNOWN_HOSTS` | unset | **SSH lane host-key verification** (`REMOTE_EXEC=ssh` only). `SSH_HOST_FINGERPRINT` = the host key's SHA256 base64 fingerprint (`SHA256:` prefix and `=` padding both optional); `SSH_KNOWN_HOSTS` = a path to a `known_hosts` file, matched literally on `host` / `[host]:port`. **Either one present ⇒ strict verification, a mismatch REFUSES the connection** (the error carries both fingerprints plus an `ssh-keyscan` line); both set ⇒ both must pass. **Both unset ⇒ a loud per-connection warning (`ssh_host_key_unverified`, man-in-the-middle risk) and the connection proceeds** — the deliberate posture for the batch-deploy lane, registered as a P-DEBT fail-open (`docs/FAIL-OPEN-CENSUS.md`). A malformed fingerprint or an unreadable `known_hosts` path **refuses to start**. Not supported in `known_hosts`, on purpose: hashed (`\|1\|…`) lines, wildcard patterns, `@cert-authority` — all skipped, so a host whose only entries are those is REFUSED (`@revoked` is honored and wins over any plain line, whatever the order) |
190
190
  | `HOOKS_TIMEOUT_MS` | unset (engine default 600000) | Per-invocation time bound for every hook seat (core `Hooks.timeoutMs`). Unset = the engine's own default (this server does not copy upstream defaults). `0` is honored as written (every seat expires immediately). A non-integer, negative, or above-`setTimeout`-ceiling (2147483647) value **refuses to start** |
191
191
  | `CONFIG_PROVIDER` | unset | Config source: `local` (file-backed `config.d/`, single machine) / `remote` (registry control plane). Closed set, matched exactly (7.104.0+): any other value (wrong case, surrounding spaces) refuses to boot; empty = unset |
192
- | `DEFAULT_SCENARIO` | `code` | Default scenario when the request body names none |
192
+ | `DEFAULT_SCENARIO` | `code` | Default scenario when the request body names none; must be a scenario this deployment has (boot refuses otherwise) |
193
193
  | `SANDBOX_PKG_SOURCE` | `global` | Package sources inside sandboxes: `global` (official upstreams) / `cn` (China mirrors) / `custom` / `none` |
194
194
  | `SENSITIVE_WRITE_PATTERNS` | core's recommended set | Sensitive-path write deny list; comma-separated value replaces the set, `off` **or an empty value** disables. Applied unconditionally at the governance layer (independent of client permission mode, lane or settings presence) — including on the `run-local` leg. A value that cannot compile into a guard set (e.g. `/`, a pattern with no path segment) refuses to start |
195
195
  | `WRITE_PROTECTED_EXTRA` | unset (engine default table) | **Adds** rows to core's write-protection table (the literal-name table whose hit demotes a surviving `allow` to `ask` on Write/Edit/NotebookEdit). Comma-separated bare names (a bare name matches ANY path segment; a name containing `/` matches a consecutive segment run) or a JSON array of `"name"` strings / `{name, kind}` rows (`kind`: `basename` \| `segment` \| `segment-run`). The value is composed as `[...WRITE_PROTECTED_DEFAULT_TABLE, …]`, so **no default row can be lost**. Unset = no seat is wired = the engine's default table is in force (this server never copies that table). Form discrimination is by content, not by first character: a value containing any JSON structural character (`[ ] { } "`) is parsed as JSON and **must** be a top-level array (a missing pair of brackets refuses to start instead of being split into junk bare names). An empty value, a malformed entry (glob metacharacter, unknown kind, kind/name mismatch) or setting this together with `WRITE_PROTECTED_TABLE_REPLACE` **refuses to start**. Read faces: `GET /v1/capabilities` → `writeProtection.{armed,rows,replaced}`; `GET /v1/diagnostics/wiring` → `writeProtection.{rows,source,droppedDefaultRows}` (operator-only) |
package/README.zh-CN.md CHANGED
@@ -162,7 +162,7 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
162
162
  | `SSH_HOST_FINGERPRINT` / `SSH_KNOWN_HOSTS` | 未设 | **SSH 通道主机密钥校验**(仅 `REMOTE_EXEC=ssh`)。前者 = 主机公钥的 SHA256 base64 指纹(`SHA256:` 前缀与 `=` 填充都可选);后者 = `known_hosts` 文件路径,按 `host` / `[host]:port` **逐字**匹配行。**任一在场 ⇒ 严格校验,不符即拒连**(拒因带两半指纹 + `ssh-keyscan` 取指纹指路一行);两只同时在场 ⇒ 合取(都要过)。**两只全缺席 ⇒ 每连接一条响亮 warn(`ssh_host_key_unverified`,中间人风险)后照常连** —— 批量部署通道的既定姿态,已登记为 P-DEBT fail-open(`docs/FAIL-OPEN-CENSUS.md`)。指纹拼错 / `known_hosts` 路径读不到 ⇒ **启动即拒**。`known_hosts` 里**刻意不支持**:hashed(`\|1\|…`)行、通配符 pattern、`@cert-authority` —— 一律跳过,于是只有这些条目的主机会被**拒连**(`@revoked` 照常生效,且无论写在哪一行都优先于普通匹配行) |
163
163
  | `HOOKS_TIMEOUT_MS` | 未设(引擎缺省 600000) | 每一只 hook 席位**单次调用**的时间上限(core `Hooks.timeoutMs`)。未设 = 用引擎自己的缺省(本服务不复制上游默认值)。`0` **按字面生效**(每个席位立即到期)。非整数 / 负数 / 超 `setTimeout` 上限(2147483647)⇒ **启动即拒** |
164
164
  | `CONFIG_PROVIDER` | 未设 | 配置来源:`local`(单机文件 `config.d/`)/ `remote`(registry 控制面)。闭集、逐字比较(7.104.0 起):其余任何值(大小写不同、带首尾空白)拒启;空串 = 未设 |
165
- | `DEFAULT_SCENARIO` | `code` | 请求体未指定场景时的缺省场景 |
165
+ | `DEFAULT_SCENARIO` | `code` | 请求体未指定场景时的缺省场景;必须是本部署在册的场景名,否则拒启 |
166
166
  | `SANDBOX_PKG_SOURCE` | `global` | 沙箱内装包源:`global`(官方源)/ `cn`(国内镜像)/ `custom` / `none` |
167
167
  | `SENSITIVE_WRITE_PATTERNS` | core 推荐集 | 敏感路径写拒集;逗号分隔值为整体替换,`off` **或留空**关闭。在治理层无条件施加(与客户端权限模式/lane/settings 在场性无关),`run-local` 腿同样生效;编译不出守卫集的值(如 `/`)启动即拒 |
168
168
  | `WRITE_PROTECTED_EXTRA` | 未设(引擎缺省表) | 给 core 的**写保护名表**加行(字面名表:命中即把幸存的 `allow` 降级成 `ask`,作用于 Write/Edit/NotebookEdit)。逗号分隔裸名(裸名匹配**任意路径段**,含 `/` 的名匹配连续段)或 JSON 数组(`"name"` 串 / `{name, kind}` 行,`kind`:`basename` \| `segment` \| `segment-run`)。值按 `[...core 缺省表, …]` 组合,**丢不掉任何缺省行**。未设 = 不铸座 = 引擎缺省表在岗(本仓从不复制那张表)。两形按**内容**判而不是猜首字符:值里出现 JSON 结构字符(`[ ] { } "`)即按 JSON 解析,且顶层**必须是数组**(少写一对方括号 ⇒ 拒启,而不是被拆成垃圾裸名静默收下)。空值 / 坏值(通配符、未知 kind、kind 与名字段数矛盾)/ 与 `WRITE_PROTECTED_TABLE_REPLACE` 同时设置 ⇒ **启动即拒**。读面:`GET /v1/capabilities` 的 `writeProtection.{armed,rows,replaced}`;`GET /v1/diagnostics/wiring` 的 `writeProtection.{rows,source,droppedDefaultRows}`(operator-only) |
package/USAGE.md CHANGED
@@ -51,9 +51,10 @@ docker compose up --build --scale service=4 # 4 个无状态 worker,nginx
51
51
  > 而 `DB_BACKEND` 缺省 ⇒ 探的是 mysql)。修前这三形**静默跑内存**(什么都不设是 local 文件持久,加一族坐标反而落到内存)。坐标被用到的存量形
52
52
  > (`SESSION_BACKEND=mysql|auto` + MYSQL 坐标、不写 `DB_BACKEND`)照旧起服;「不用坐标」是显式表态的(`DB_BACKEND=memory|local`、
53
53
  > `SESSION_BACKEND=memory`)也不拒。拒句点名在场的坐标键、为什么没人用、三条出路:要持久 `DB_BACKEND=pg|mysql`(或 `SESSION_BACKEND=mysql`);
54
- > 要内存 去掉坐标或 `DB_BACKEND=memory`;要尽力而为 `SESSION_BACKEND=auto` 并把引擎指对(`DB_BACKEND=pg` 配 `PG_HOST`)。本仓两份 compose
55
- > 显式带 `DB_BACKEND=mysql`。同版:`DB_BACKEND=mysql|pg` 显式而 `SESSION_BACKEND` 不许降级时店打不开,拒句同样点名 `DB_BACKEND=<引擎>` 与
56
- > `SESSION_BACKEND=auto` 出路(修前只有驱动的一句 `connect ECONNREFUSED …`)。持久化姿态读面:caps `store`(附录 B)。
54
+ > 要内存 去掉坐标或 `DB_BACKEND=memory`。本仓两份 compose
55
+ > 显式带 `DB_BACKEND=mysql`。同版:`DB_BACKEND=mysql|pg` 显式而店打不开,拒句点名 `DB_BACKEND=<引擎>`(修前只有驱动的一句 `connect ECONNREFUSED …`)。
56
+ > 🔴 **7.119.0 起 `SESSION_BACKEND=auto` 配了坐标而库连不上同样拒启,不再有「auto 内存起服」这条出路**:`auto` = 「配了该引擎的坐标就是那只库(boot 期窗内重试、到窗拒启),没配就是内存」,
57
+ > 由配置决定、不由起库成败决定;内存部署写 `DB_BACKEND=memory` 或 `SESSION_BACKEND=memory`。`GET /health` 的 `sessionBackend` 如实说装好的后端(`durable(pg)` / `memory`)。持久化姿态读面:caps `store`(附录 B)。
57
58
 
58
59
  > 🔴 7.96.0 起根 `docker-compose.yml` 的 `REMOTE_EXEC`(执行车道)是**必填**:不设 ⇒ `docker compose config` 当场失败(此前静默起成一个没有手的 worker)。单人自用 `REMOTE_EXEC=host`(手落在 worker 容器里);多人 / 生产选隔离车道 `e2b`(配 `E2B_API_KEY`)或 `k8s`(配 `K8S_SANDBOX_IMAGE`)。
59
60
 
@@ -172,7 +173,7 @@ WEB_SEARCH_PROBE_ON_BOOT=true # boot 期一次性真出网探活,
172
173
  | env 键 | 类型 | 默认 | 缺席行为 | 坏值行为 |
173
174
  |---|---|---|---|---|
174
175
  | `WEB_SEARCH_PROVIDER` | 闭集(`brave`\|`tavily`\|`searxng`) | 无 | WebSearch 工具整体不装配(功能缺席,无 warn) | 三词之外的任何值 = 同缺席处理,不装配、不 warn(`webSearchConfigFromEnv` 的词表守卫 `isWebSearchProvider`)。**读面(S-382 起)**:`GET /v1/capabilities` 的 `webSearch.backend` 把这一格的生效值广告成闭集词(缺席/坏值都报 `"none"` —— 两者行为本就相同);端点/密钥/配额**不上 wire**,那些仍只在 operator 面 `GET /v1/config/catalog` |
175
- | `WEB_SEARCH_API_KEY` | secret string | 无 | brave/tavily:后端仍会装配(装配只看 `PROVIDER`),**首次真实工具调用**时抛 `WEB_SEARCH_API_KEY is required for the <provider> provider`(`braveSearch` / `tavilySearch` 的首行守卫);searxng:本键无消费点 | 空串 / 纯空白同缺席;内容不设形(它是凭据) |
176
+ | `WEB_SEARCH_API_KEY` | secret string | 无 | brave/tavily:后端仍会装配(装配只看 `PROVIDER`),**首次真实工具调用**时抛 `WEB_SEARCH_API_KEY is required for the <provider> provider`(`braveSearch` / `tavilySearch` 的首行守卫);searxng:本键无消费点 | 空串 / 纯空白同缺席。内容只有一条形律:**能当 HTTP 请求头值**(brave 放 `X-Subscription-Token`,tavily 放 `Authorization: Bearer <key>`;判的是实际发出去的那一串)。含换行 / NUL / 其它控制字符 / U+007F / U+00FF 以上字符等头不能携带的字符 ⇒ **拒启**(`config.web_search_invalid`,句 `env WEB_SEARCH_API_KEY must be usable as an HTTP header value (it contains a character an HTTP header cannot carry, such as a line break)`,**不回显值** —— 7.118.0 起;改前采纳,首次搜索时运行时抛的错误句带着整串明文)。判据就是运行时构造并发出请求头的那条流水线(Node 自带的两步:`Headers` 构造的归一化 + 发送层的字符表;不是另一张字符表):首尾空白(含换行)归一化后不剩,所以从密钥文件带出的尾随换行照收;searxng 不发鉴权头,但这一键照判(按裸 key)。出路:修好值,或不用这一键时 unset 它 |
176
177
  | `WEB_SEARCH_ENDPOINT` | url string(brave 下 = 完整搜索 URL,可指向 Brave 兼容网关如 Nólë) | brave/tavily → 各自官方 API;searxng → 无默认 | brave/tavily:落官方 endpoint;searxng:**首次真实工具调用**时抛 `WEB_SEARCH_ENDPOINT (the SearXNG instance URL) is required for the searxng provider`(`searxngSearch` 的首行守卫) | 空串同缺席(compose 模板 `${WEB_SEARCH_ENDPOINT:-}` 铸的就是空串)。**不是 `scheme://host[:port][/path]` 形的绝对 URL ⇒ 拒启**(`searx.internal`、`localhost:8888` 这类少了 `scheme://` 的形都算;判据与模型路由 base URL 拒启同一只)。**带 userinfo ⇒ 拒启**(7.101.0 起,S-716:`https://user:pw@host/…`、只带用户名的 `https://user@host/…` 都算;判据 = WHATWG 解析后用户名或口令任一非空,与运行时 fetch 拒收含凭据 URL 的约束同一条,所以修前这一形起得来、每次搜索必抛;brave / tavily 的凭据走 `WEB_SEARCH_API_KEY`,searxng 腿没有凭据通道,带鉴权的实例需在前面放一层注入鉴权的反代)。拒启句带键名与机读码 `config.web_search_invalid`,**不回显值**(这一格可能带凭据) |
177
178
  | `WEB_SEARCH_MAX_RESULTS` | number | `10` | 用默认 10 | 空串同缺席;**非数字或 < 1 ⇒ 拒启**(`config.web_search_invalid`;改前静默落默认 10);是数则向下取整,再夹到 `[1,20]`(设 999 被夹到 20 —— 夹紧不是形错) |
178
179
  | `WEB_SEARCH_TIMEOUT_MS` | number(ms) | `10000` | 用默认 10000 | 空串同缺席;**非数字(`"30s"` 这类带单位的也算)或 < 1 ⇒ 拒启**(`config.web_search_invalid`;改前静默落默认 10000 —— 写了 30s 的运维实际拿到 10s);是数则向下取整,再抬到 ≥ 1000ms |
@@ -537,6 +538,8 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
537
538
  🔴 **两条取数据根的路是同一条腿、同一只旋钮**:HTTP 服务的 local 臂与 `run-local`(一次性 CLI)共用
538
539
  同一只等待腿 —— **不为前台形留第二套语义**。前台形要「立刻告诉我谁占着」就设 `STORE_LOCK_WAIT_MS=0`,
539
540
  那是文档化的通道,而不是两条路各有一套默认。
541
+ 🔴 **同一只窗 7.119.0 起也管「已配置的 SQL 库此刻连不上」**:`DB_BACKEND=mysql|pg`(含 `SESSION_BACKEND=auto`)boot 期开不了店 ⇒ 窗内每 250ms 重试、到窗拒启(点名后端与 host:port;
542
+ 永久错如凭据 / DDL 权限也拖到窗尾才拒,要当场拒设 `0`)。k8s 清单里库与 server 同时起,所以清单把窗给到 `300000`。
540
543
  ⚠️ **裸 boot(没有显式 `DB_BACKEND`)那一形的时序也跟着变**:它的锁冲突走的不是拒启而是既有的
541
544
  「退回内存并 warn」降级臂(`store_db_unreachable_fallback_memory`),所以第二只实例此前**当场**降级、
542
545
  现在要**等满窗**才降级 —— 方向仍是好的(窗内拿到就用上真数据根,而不是静默丢掉持久面),但它是一次
@@ -859,12 +862,18 @@ UNATTENDED_APPROVAL_POLICY=park
859
862
 
860
863
  **「本会话不再询问」对引擎的问也生效(7.106.0 起)**
861
864
 
862
- - 审批面 `POST /v1/approvals/:sessionId/decide {decision:"approve", remember:"session"}` 与活卡的 `allow_session` 记下的放行,两处兑现:
863
- 本服务自己的问(`APPROVAL_REQUIRE` 名单、`run_workflow` 问句、acceptEdits 的域外写)在它们自己的层上不再问;**引擎自己的问**(`default`
864
- 模式下的每一次写、证不了只读的 shell 命令)由服务的**审批席**(工具审批协调器)在下一次到达时代答。后一半要审批席在场:
865
+ - 审批面 `POST /v1/approvals/:sessionId/decide {decision:"approve", remember:"session"}` 与活卡的 `allow_session` 记下的放行,两处兑现 —— **谁问的,谁读它的答案**:
866
+ 本服务自己的问在它们自己的层上、出问之前读,命中就不问:`APPROVAL_REQUIRE` 名单的问(7.119.0 起;卡面带「本会话放行」,回执 `rememberApplied:true`,
867
+ 同一会话下一次调用名单门不出问,引擎看到的只是「这次调用没出问」)、`run_workflow` 问句、acceptEdits 的域外写;**引擎自己的问**(`default`
868
+ 模式下的每一次写、证不了只读的 shell 命令、没人表态的服务器工具)由服务的**审批席**(工具审批协调器)在下一次到达时代答。后一半要审批席在场:
865
869
  `TOOL_APPROVAL_ENABLED` 开(多租户缺省关);不想要活卡(纯 park 部署)再配 `STREAM_ASK_WINDOW_MS=0`。
870
+ 名单的问在引擎眼里仍是 `ask_rule`:`permissionMode: "bypassPermissions"` 放不掉、auto 模式的分类器答不了 —— 免问只来自这一会话里人亲口说过的「本会话放行」。
866
871
  - 代答不到的:`APPROVAL_DENY` / 敏感路径守卫(是拒,不是问);`APPROVAL_NEVER_AUTO` 与耐久预算耗尽的问(「恒问人」,回执 `rememberApplied:false`);
872
+ 人写的 ask 规则(请求的 `settings.permissions.ask`、存下的 ask 行 —— 引擎的问,类表说站着的答案清不掉,回执 `rememberApplied:false`);
867
873
  委派子代经父链继承到祖先席上的引擎问(引擎盖强制位,席依约不代答)。
874
+ - 已知边界:「本会话放行」记在本部署的会话豁免店(SQL 后端跨副本共享;`DB_BACKEND=local` 只在这台机器的数据根里)。续跑落到另一台**读不到这只店**的
875
+ 实例上(local 车道换机 / 换数据根)⇒ 名单的问再问一次 —— 方向是多问一次,不是静默放行。名单靠别名命中的调用(嵌入方给工具挂了 `Bash` 别名)
876
+ 按调用的 wire 名记、按 wire 名读;一张卡上同时有名单的问与别人的问(人写的 ask 规则、强制位、读会话目录外 ……)⇒ 那张卡的「本会话放行」不记。
868
877
 
869
878
  **无人值守腿:`default` 模式 + 按类作答的审批席(7.109.0 起;S-934 / S-906)—— 没有旋钮**
870
879
 
@@ -893,8 +902,9 @@ APPROVAL_GRANT='mcp__figma__*,a2a__planner__*,Bash(git:*)' # 逗号分隔的 a
893
902
  leader 没有请求体 —— 运维为自己接的服务器、自家信得过的命令,在这里写一次。
894
903
  - **值形(与引擎的文法相同)**:工具名、`mcp__<server>__*` 或两段形 `mcp__<server>`、`a2a__<peer>__*`、`Bash(<命令>)` / `Bash(<前缀>:*)` 命令规则、
895
904
  `Read(//<dir>/**)` 读目录。`auto` 模式下交出任意代码的条目(整只 shell、解释器 / 启动器前缀)交分类器判(引擎的剔除表)。
896
- 坏值 ⇒ **启动拒**(句子点名那一条、引擎给的原因词、怎么写):引擎读成空的条目(`*`、`mcp__*`、写路径形 `Edit(src/**)`、域名形、带空白的名字)、
897
- 协议名服务器段铸不出 / 缺段 / 写通配。内容形里带逗号的规则写不进(这张表的分隔符就是逗号)。
905
+ 坏值 ⇒ **启动拒**(句子点名那一条、引擎给的原因词、怎么写):引擎读成空的条目(`*`、`mcp__*`、写路径形 `Edit(src/**)`、域名形、带空白的名字;
906
+ 7.118.0 起条目名也过引擎的判官 —— 退役名 / 无前缀短名 / 协议名服务器段铸不出读成 `tool_name`,句子带现名)、工具段为空的协议名(`mcp__srv__`)。
907
+ 内容形里带逗号的规则写不进(这张表的分隔符就是逗号)。
898
908
  - **永远清不掉的**:任何一层的 deny(`APPROVAL_DENY`、请求 / 会话规则的 deny、命令策略拒档、敏感写路径)、ask 规则(`APPROVAL_REQUIRE`、请求的 ask)、
899
909
  `APPROVAL_NEVER_AUTO`、`MANUAL_MODE_SHELL_GATE=always`、写保护路径、治理、PreToolUse hook 的问、plan 模式。命中而清不掉时卡上带
900
910
  `allowRuleShadowed: [{ member: "deployment", entries: [...] }]`。同一工具既在本键又在 `APPROVAL_REQUIRE` / `APPROVAL_DENY` ⇒ ask / deny 赢。
@@ -1391,7 +1401,7 @@ bake-runner 要用 ⇒ `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW` 点名放回。
1391
1401
  | `sessionId` | — | 续聊:带上次返回的 `sessionId`,服务端自动 wake 历史 |
1392
1402
  | `images` | — | 图文输入 `[{data,mimeType}|{url}]`(模型需支持 vision) |
1393
1403
  | `attachmentIds` | — | D-1 通用文件上传(1.289+):先 `POST /v1/attachments?name=…`(raw body,content-type=mime)拿句柄,提交时引用 ≤16 个;文件物化到执行环境工作目录 `attachments/` 下,objective 尾部自动追加文件清单(内容不进会话流)。单文件缺省 ≤32 MiB(`ATTACHMENT_MAX_BYTES`);可配 mime 白名单(`ATTACHMENT_MIME_ALLOWLIST` CSV,缺省不限);上传后未引用的按 `ATTACHMENT_UNBOUND_TTL_MS`(缺省 24h)回收。**云形态(tidb/pg)字节本体存对象存储——MinIO 必配**(`MINIO_ENDPOINT/MINIO_ACCESS_KEY/MINIO_SECRET_KEY`,与快照 lane 同一组变量),未配则附件面 501;local 形走本地文件店。 |
1394
- | `scenario` | — | 省略时取本部署的 `DEFAULT_SCENARIO`(工厂缺省场景名 = **`code`**——CC 编码 persona 蒸馏版,default 全量工具面,终验旋钮独立;`default` 场景**已非出厂缺省**,要用它须显式传 `scenario:"default"`,或部署方显式设 `DEFAULT_SCENARIO=default`)。可选内建名:`default`(通用/助理场景,域中性 persona,CC agent loop 全量工具面)/ `code-review`(见 §5)/ `scan`(同 §5 的 repo 只读工具但**中性无框架提示词**——objective+中心下发 skill 全权主导输出,OA 扫描类用;与 `code-review`/`discuss` 同属 clone-free 无执行环境场景,见 §5 末)/ **配置控制面可声明任意新场景**(`{name, toolset: none\|repo-readonly, prompt?}`,组合即配置、能力写死在部署;restart-to-apply;center 可覆盖内建名,boot 日志 `config_center_scenarios.shadowsBuiltin` 可审计) |
1404
+ | `scenario` | — | 省略时取本部署的 `DEFAULT_SCENARIO`(工厂缺省场景名 = **`code`**——CC 编码 persona 蒸馏版,default 全量工具面,终验旋钮独立;`default` 场景**已非出厂缺省**,要用它须显式传 `scenario:"default"`,或部署方显式设 `DEFAULT_SCENARIO=default`)。可选内建名:`default`(通用/助理场景,域中性 persona,CC agent loop 全量工具面)/ `code-review`(见 §5)/ `scan`(同 §5 的 repo 只读工具但**中性无框架提示词**——objective+中心下发 skill 全权主导输出,OA 扫描类用;与 `code-review`/`discuss` 同属 clone-free 无执行环境场景,见 §5 末)/ **配置控制面可声明任意新场景**(`{name, toolset: none\|repo-readonly, prompt?}`,组合即配置、能力写死在部署;restart-to-apply;center 可覆盖内建名,boot 日志 `config_center_scenarios.shadowsBuiltin` 可审计)。**缺省与指派都必须是本部署在册的场景名,悬空不回落 `default`**:`DEFAULT_SCENARIO` 不在册 ⇒ **起服拒**(拒句点名那个名字与在册词表);中心按主体指派的场景名本部署没有 ⇒ 该主体的 run 答 **503 `state.scenario_assignment_unknown`**(体 `detail: { scenario, source: "ruling" }`,不带 `Retry-After`;修法 = 中心改指派,或把那个场景加回本部署,然后原样重试);请求体显式给的未知名仍是 400 `scenario_unknown` |
1395
1405
  | `repo` / `council` / `debate` | — | `repo` 为 `code-review`/`scan` 必填;`council`/`debate` 仅 `code-review`,见 §5 |
1396
1406
  | `systemPrompt` / `appendSystemPrompt` | — | 调用方自带的系统提示 / 追加段(各 ≤16384 字符,超限 400 `request.field_invalid`;`appendSystemPrompt` 必须非空串)—— 任务自带时优先于角色 / 全局缺省(见 §模型角色) |
1397
1407
  | `model` | — | 本次用哪个模型:只能点服务端**已配置目录**里的名字 / 档位词 / 模型 id(目录见 `GET /v1/models`;请求体带不进新模型)。未知 ⇒ 400 `request.unknown_reference`(带 `available` 列表);目录里有、但本部署服务不了 ⇒ 400 `request.model_unavailable`;空串 / 非字符串 ⇒ 400 `request.field_invalid` |
@@ -1515,7 +1525,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
1515
1525
  ## 7. 其它接口(按需)
1516
1526
  | 端点 | 用途 |
1517
1527
  |---|---|
1518
- | `GET /v1/sessions/<id>` | 审计回溯:当前上下文 + 摘要(owner 校验) |
1528
+ | `GET /v1/sessions/<id>` | 审计回溯:当前上下文 + 摘要(owner 校验);消息里模型 / 工具产出的文本与 live 帧、账本帧同一只脱敏(密钥形换占位,其余字节不变) |
1519
1529
  | `GET /v1/approvals?owner=user:42` | 高危写审批(durable,需 `DURABLE_APPROVAL=true`):**operator** 看待办队列(可按 owner 过滤);非 operator 只看自己的 |
1520
1530
  | `POST /v1/approvals/<sessionId>/decide` `{"decision":"approve"|"deny","reason":"…"}` | 批/否并恢复挂起任务(CAS,重复决议 409);**仅 operator**(非 operator → 403)。5.0.0 起旧轮询腿 `POST /v1/approvals/<id>` 已退役 |
1521
1531
  | `GET /v1/agents/roster` | 后台 agent **名册**(7.29 起,需 durable background-agent store,否则 501 `capability.background_agent_store_required`):调用方自己 scope 的 `a…` 行,新→旧。行只带 `handle`/`name`/`agentType`/`status`/`spawnedAt`/`updatedAt`/`settledAt`(content-free;子代产物正文走 `GET /v1/runs/<id>/subagents/<handle>/output`)。查询:`?limit=`(1..100,默认 20)、`?before=`(上一页的 `nextBefore` 游标)、`?status=`(running\|parked\|completed\|failed\|killed)、`?session=`(按会话树窄化)、`?scope=`(**仅** service-token/单用户形;带 principal 的调用方锁定自己)。回体 `{agents,total,nextBefore?,truncated?}`;`truncated:true` = 租户行数超过名册窗(500),`total` 此时是窗内数。非属主的行**不出现也不计数**(无存在性 oracle) |
@@ -1865,7 +1875,9 @@ tag 词表与每条的最坏后果见 `docs/FAIL-OPEN-CENSUS.md`。引擎装配
1865
1875
  (core 给宿主定级的判别词;词表外原样透传并标 `classificationKnown:false`),指标 `runner_config_advisory_total`
1866
1876
  按 `code` / `classification` / `level` 打标;其中 `shell-gate-off` 在本 session 显式声明 `permissionMode:
1867
1877
  "bypassPermissions"` 时是调用方要的姿态,降为 **info** 并标 `expected`——只有没表态落到引擎默认 off 的无壳直连
1868
- 形才 warn。
1878
+ 形才 warn。子代 `agents[].denyTools` / `allowTools` 的**名字**也归引擎开跑前一只判、同落这一行:无人挂的名只告不拒;**本服务部署形上退役拼法 / 大小写孪生
1879
+ (`bash` / `BASH`)也只告不拒**(委派工具带逐次注入的 extraTools,引擎列不全子代工具池),名字按工具名原拼写 `Bash`;且这两张表只约束**委派池**里的工具 ——
1880
+ 内建 hands 带的工具(如 `Write`)不受 `denyTools` 约束(引擎后续版本一张表盖全),今天要对子代藏内建工具用会话策略 `toolDeny` / 部署 deny 规则。
1869
1881
 
1870
1882
  🔴 **`shell-gate-off` 这条注的语义自 7.73.0(core 7.15.0)起收窄,文案由引擎改写**:`off` 档**不再**意味着
1871
1883
  「这条会话的 shell 完全没有门」。**读边界**(内建读拒表 + 工作区容纳面)现在**每一档都判**,`shellGate` 只管
@@ -1985,7 +1997,7 @@ GET /v1/capabilities?permissionMode=default
1985
1997
  ```
1986
1998
 
1987
1999
  - `armed`:按上式此刻会不会武装(`?permissionMode=` 折进本次意图;查询串缺席 = 按 auto 意图答「若请求会不会武装」;
1988
- 五词之外 400 `request.field_invalid`)。
2000
+ core 闭集之外 400 `request.field_invalid`)。
1989
2001
  - `reason`(未武装时,闭集**六词**,判序即武装式合取序):`mode_not_auto`(意图不是 auto)/ `deployment_incapable`
1990
2002
  (装配面没挂分类器席位——生产装配恒挂,桩与一次性 CLI 不挂)/ `org_denied`(`caps.autoMode === false`:center 显式 deny、
1991
2003
  center 硬失败 fail-closed 拒项)/ `local_denied`(本地旋钮 `PERMISSIONS_DISABLE_AUTO_MODE=true`,改本机 env)/ `settings_denied`
@@ -2055,7 +2067,7 @@ TOC 本地形)时被**采纳**;`REQUIRE_PRINCIPAL=true`(多租户)上一段**形
2055
2067
  | 键 | 类型 | 必填 | 默认 | 形错(⇒ 400) |
2056
2068
  |---|---|---|---|---|
2057
2069
  | `provider` | `"brave"|"tavily"|"searxng"` | **是** | 无 | 缺席、不是字符串、或不是三词之一(大小写 / 首尾空白照旧归一)——一段 `settings.webSearch` 就是在点名目的地,认不出就 400 点名 `settings.webSearch.provider`(括号里只说哪一形:`it is missing` / `it is not a string` / `it is not one of them`,**不回显**收到的串 —— 7.101.0 起,S-716),**不**静默换成部署 env 的 backend(改前:整段丢弃、回落 env backend、不报错)。词表守卫与 env 腿**同一只** `isWebSearchProvider`;env 腿词表外是「不装配」(部署自己的缺席) |
2058
- | `apiKey` | string(明文) | brave/tavily 建议带(缺了首次调用才报错,见下表);searxng 不读 | 无(不继承 env 的 `WEB_SEARCH_API_KEY`);空串同缺席 | 不是字符串 |
2070
+ | `apiKey` | string(明文) | brave/tavily 建议带(缺了首次调用才报错,见下表);searxng 不读 | 无(不继承 env 的 `WEB_SEARCH_API_KEY`);空串同缺席 | 不是字符串,或不能当 HTTP 请求头值(含换行 / NUL / 其它控制字符 / U+007F / U+00FF 以上字符等;判据 = 运行时构造并发出请求头的那条流水线,与 env `WEB_SEARCH_API_KEY` 同一只;7.118.0 起,句 `settings.webSearch.apiKey must be usable as an HTTP header value (it contains a character an HTTP header cannot carry, such as a line break)`,**不回显值**;改前采纳、首次搜索时运行时抛的错误句带着整串明文)。首尾空白(含换行)运行时会剥掉 ⇒ 照收 |
2059
2071
  | `endpoint` | string | searxng 必需(缺了首次调用才报错);brave/tavily 可选覆盖 | 无(不继承 env 的 `WEB_SEARCH_ENDPOINT`);空串同缺席 | 不是字符串,或不是 `scheme://host[:port][/path]` 形的绝对 URL(`localhost:8888` 这类少了 `scheme://` 的也算),或带 userinfo(`user:pw@` / 只带 `user@`;7.101.0 起,S-716,句 `settings.webSearch.endpoint must not carry userinfo (user or user:password before the host)`,不回显值) |
2060
2072
  | `searxngParams` | `Record<string,string>`(对象)或 `"name=value;name=value"`(字符串) | 否 | 无;空串 / 空对象同缺席 | 与 env `WEB_SEARCH_SEARXNG_PARAMS` 同一套文法:数组、数字、一段不是 `name=value`、名不是参数名(字母 / 数字 / `_` / `-` / `.`)、值为空或不是字符串、同名两次(句里只报第几项 `entry #N`,不回显名或值 —— 7.101.0 起,S-716)。⚠️ **未列入已发布的 `@sema-agent/sdk` 8.8.0 `SettingsWebSearch` 类型**(该接口只有 `provider`/`apiKey`/`endpoint`/`maxResults` 四键、无开放下标)——server 侧代码认这个键,但当前发布的 TS 类型接不到它;手写 JSON 请求体仍可以发,server 会照常解析(源码头注自述:字段和消费点先落地,配置录入面尚未跟上,是一笔尚未还清的既有债务) |
2061
2073
  | `maxResults` | number | 否 | 10(与 env 同一 clamp,`[1,20]`,小数向下取整;999 夹到 20 不是形错) | 不是数,或 < 1(`"10"` 这种字符串也算) |
@@ -2086,8 +2098,8 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
2086
2098
  工具在场 ⇔ 调用方的 per-request 配置被采纳(单用户车道)或调用方根本没带(用部署默认)。
2087
2099
 
2088
2100
  **`apiKey` 明文与 env 槽边界**:server 收到的 `settings.webSearch.apiKey` 是**明文字符串**,没有任何服务端
2089
- 密钥槽位/引用间接——收到什么字符串就直接进 backend 闭包(判官 `judgeRequestWebSearch` 只判「是不是字符串」,与 `WEB_SEARCH_API_KEY` 同一只判官、同一条消费路径,
2090
- 见 `src/plugins/web-search.ts` 的 `WebSearchBackendConfig.apiKey` 字段注)。**server 本批不改受理面**:明文字段的形状维持原样。调用方(壳)如何在
2101
+ 密钥槽位/引用间接——收到什么字符串就直接进 backend 闭包(判官 `judgeRequestWebSearch` 只判「是不是字符串」与「能不能当 HTTP 请求头值」——后者见上表 `apiKey` 行,与 `WEB_SEARCH_API_KEY` 同一只判官、同一条消费路径,
2102
+ 见 `src/plugins/web-search.ts` 的 `WebSearchBackendConfig.apiKey` 字段注)。明文字段的形状维持原样(不是引用、不是槽位名)。调用方(壳)如何在
2091
2103
  自己机器上管理这份明文是调用方的事——例如 cli 壳侧的约定是在**调用方自己的环境**里按
2092
2104
  `SEMA_WEBSEARCH_KEY_<PROVIDER>` 这样的命名空间存放每个 provider 的 key,由壳在本地读出后把明文塞进请求体;
2093
2105
  这纯粹是**客户端约定**,本仓不读取、不校验、也不感知任何这类客户端侧 env 命名(这条命名事实来自任务书,
@@ -2175,8 +2187,8 @@ server 进程(HTTP 服务与 `run-local`)**所有走 undici 的出网跳**都认
2175
2187
  范围按 undici 划:目标 `http:` **且**代理 URL 也是 `http:` 才走绝对 URL 形;`https://` 代理 + http 目标仍是 `CONNECT`。
2176
2188
  🔴 **要知道的三件**:① **明文过代理**:http 目标的请求行与头(含发给网关的凭据头)以明文经过代理,代理看得到完整 URL 与头;要对代理保密,网关用 https(隧道里是密文)。
2177
2189
  ② 代理按 `CONNECT` 记账 / 鉴权、且不转发绝对 URL 形请求的部署(罕见):把该网关改成 https,或把它写进 `NO_PROXY`。
2178
- ③ **代理拒绝的读法**:`CONNECT` 腿上代理回非 200 ⇒ 下面「代理那一跳失败时怎么读」的 `via outbound proxy` 句;绝对 URL 形里代理的回话是**普通 HTTP 响应**,分两种 ——
2179
- **407(要求代理认证)同样读成 `via outbound proxy (from <变量>) ← proxy answered the request with HTTP 407`**(407 按协议只可能来自代理,两条腿同一种终局;一个自己回 407 的源站是违约,也会被读作代理);
2190
+ ③ **代理拒绝的读法**:`CONNECT` 腿上代理回非 200 ⇒ 下面「代理答了拒」那一条(短阶梯 + 按状态选的句子);绝对 URL 形里代理的回话是**普通 HTTP 响应**,分两种 ——
2191
+ **407(要求代理认证)同样读成代理答了拒**,根因 `proxy answered the request with HTTP 407`(407 按协议只可能来自代理,两条腿同一种终局;一个自己回 407 的源站是违约,也会被读作代理);
2180
2192
  **403 等其余状态不是代理拒**:它与网关自己回的 403 在 HTTP 层原理上分不开(没有通用判别式),调用方读到一条普通 403 响应(「网关回了 403」),代理的 ACL 拒绝请对着代理自己的日志查。网关自己回的 401 / 403 照原样交给调用方,不会被读成代理拒。
2181
2193
  - **`NO_PROXY` 语法 = undici 的**:逗号或空白分隔;`host` 精确匹配**且含其子域**;`.host` / `*.host` 后缀;`host:port` 限端口;整串 `*` = 全直连;
2182
2194
  IPv6 写方括号形 `[fd00::1]`。**不支持 CIDR / IP 段**(`10.0.0.0/8`、`10.*` 都不生效):按 IP 访问的目标逐个列(可带 `:port`)。
@@ -2189,22 +2201,38 @@ server 进程(HTTP 服务与 `run-local`)**所有走 undici 的出网跳**都认
2189
2201
  行为一致,只多回环直连。不设它也一样生效。
2190
2202
  - **代理那一跳失败时怎么读**(Node 运行时):连代理的每一次拨号 —— 拒连、解析不了、超时、`https://` 代理自身的 TLS、收下连接就关、CONNECT 回
2191
2203
  非 200 —— 失败时,引擎的终局句(与 `run_failed` 日志行)形如
2192
- `fetch failed (via outbound proxy (from HTTPS_PROXY) ← connect ECONNREFUSED 10.0.0.9:3128)` / `… ← proxy answered CONNECT with HTTP 403)` —— 这句会进
2193
- 租户可见的失败原因:**包装句**(`via outbound proxy (from <变量>)`)点明「经代理 + 哪只变量」、不带代理地址;`←` 之后是根因原文(undici 的底层网络
2194
- 错误原样,拒连 / 解析不了时它本身会写出代理的 host:port —— 本仓不改写上游错误;代理地址对照启动日志 `outbound_proxy` 行)。代理拨号有一只
2204
+ `fetch failed (via outbound proxy (from HTTPS_PROXY) ← ECONNREFUSED)` / `… ← proxy answered CONNECT with HTTP 403)` —— 这句会进
2205
+ 租户可见的失败原因:**包装句**(`via outbound proxy (from <变量>)`)点明「经代理 + 哪只变量」,`←` 之后**只有码**(`ECONNREFUSED` / `ENOTFOUND` /
2206
+ `UND_ERR_SOCKET` / `ERR_TLS_…` 等)或本服务自己写的无地址句子(`proxy answered … with HTTP <状态码>` / `proxy did not answer CONNECT within …`)—— **整句没有代理的
2207
+ host:port、没有 userinfo**:上游错误原文(`connect ECONNREFUSED 10.0.0.9:3128` 这类点名代理地址的句子)在代理那一跳上被收成码,不上 wire、不进 `String(err)`。
2208
+ **要地址去运维面**:运维日志 `outbound_connect_failed` 的 `proxyHop`(原句,含代理 host:port)与启动日志 `outbound_proxy` 行(display-safe 代理值);诊断面
2209
+ `GET /v1/diagnostics/wiring` 与用户通告仍只报变量名。直连失败不在此列(目标的地址本来就是失败原因的一部分)。代理拨号有一只
2195
2210
  **从拨号起计的绝对期限** 10 s(= undici 连接阶段的缺省时限;连代理的 TCP / TLS 与 CONNECT 交换都算在内,代理中途吐字节也不续命)—— 到点
2196
2211
  CONNECT 还没回完 ⇒ 根因 `proxy did not answer CONNECT within 10000 ms`、那条连接随之关掉;调用方先取消时请求当场结束,挂着的那次拨号同样
2197
- 在期限内收口、不留连接。请求当场失败,不重连(代理收下连接就关也一样)。隧道建好之后的失败(目标的 TLS、
2212
+ 在期限内收口、不留连接。**一次拨号**失败当场收口、dispatcher 不在原地重连(代理收下连接就关也一样)—— 这说的是传输层的那一次请求;
2213
+ **run 层**还会按引擎的重试阶梯重发这次模型调用,阶梯长短由失败的形决定(见下一条)。隧道建好之后的失败(目标的 TLS、
2198
2214
  目标的响应)不带这段前缀 —— 那是目标本身的事。**http 目标(绝对 URL 形)那一腿**:拨号失败(拒连 / 解析不了 / 超时 / 代理 TLS)同一个包装句、同一个 `proxyInUse`;
2199
2215
  10 s 拨号期限与「CONNECT 回非 200」只属于 `CONNECT` 腿 —— 绝对 URL 形的请求发出之后就是普通请求,代理收下连接就关、中途断流、回 4xx(407 除外,见上一条③)都与目标自己的失败同形,不冠这段前缀。
2216
+ - **代理答了拒 = 确定的答案,短阶梯(7.118.0 起;Node 运行时)**:代理**回了话**而且是拒绝 —— `CONNECT` 回非 200(403 / 407 / 502 …),或绝对 URL 形那一腿回 407 ——
2217
+ 代理那一跳的错误带引擎的确定性连接码 `ERR_PROXY_TUNNEL_REFUSED` 与代理说的状态码 ⇒ 引擎不再走长阶梯:缺省重试 2 次(≤1 s 退避,约 2 s 内终局),
2218
+ 等待帧写 `proxy refused the tunnel, retrying briefly`,终局句以按状态选的一句开头 —— 407 `proxy authentication required (HTTP 407)`、
2219
+ 403 `proxy forbids this destination (HTTP 403)`、其余 `proxy refused the tunnel (HTTP <n>)` —— 之后 ` — ` 接原来的失败链,形如
2220
+ `[network] proxy forbids this destination (HTTP 403) — fetch failed (ERR_PROXY_TUNNEL_REFUSED: via outbound proxy (from HTTPS_PROXY) ← proxy answered CONNECT with HTTP 403)`
2221
+ (整句仍无代理地址与凭据)。7.117.x 及以前这一形读不出,走满长阶梯(缺省 10 次重试、约 3 分钟)才终局,等待帧一直说「connection lost, reconnecting」。
2222
+ 代理的状态码**不**当作模型服务商的状态发布(`terminal.detail` / `apiFailure.status` 不带它 —— 那些位说的是服务商回了什么)。
2223
+ **拨号失败**(拒连 / 解析不了 / 收下就关 / 静默到 10 s 期限 / 代理 TLS)不带这枚码,阶梯照旧由下面那枚码决定:拒连 / 解析不了确定、收下就关 / 静默 / TLS 不确定(长阶梯)。
2224
+ 🔴 **谁受伤**:代理**短暂**拒隧道、几十秒后恢复(凭据轮换、策略推送中)的部署,以前靠长阶梯熬过去,现在约 2 s 就失败、要重跑。
2225
+ **补偿**:本版没有恢复长阶梯的旋钮 —— 引擎恢复它的座位(`deterministicConnectRetries` ≥ 重试上限)本服务没有接出来,`GATEWAY_MAX_RETRIES` 调小只会更早终局;
2226
+ 出路是修好代理的策略 / 凭据后重跑。按 407 / 403 分支的消费方今天读句子里的 `(HTTP n)`;结构化的状态位引擎本版没有。
2200
2227
  - **启动日志**:恰一行 `outbound_proxy`,`{httpProxy, httpsProxy, noProxy, loopbackDirect, runtime}`(代理值 display-safe;`httpsProxy` 是 https
2201
2228
  目标**实际**走的那只,含回落)。配置目录 `GET /v1/config/catalog` 有 `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` 三行(`limitsHttp` 组,回显同一份
2202
2229
  display-safe 投影)。`/health` 与 fleet 公告**不**带代理信息。
2203
2230
  - **建连失败时怎么读(7.108.0 / S-925;Node 运行时)**:任何一条出网请求在**建连时**就失败(码闭集:`UND_ERR_CONNECT_TIMEOUT` —— TCP + TLS 没在
2204
2231
  传输层的建连时限内完成 / `ECONNREFUSED` / `ENOTFOUND` / `EAI_AGAIN` / `EHOSTUNREACH` / `ENETUNREACH`;可能出现在已建立连接上的
2205
2232
  `ECONNRESET` / `ETIMEDOUT` 刻意不算)⇒ 三件,同一份事实:
2206
- ① **运维日志** 一行 `outbound_connect_failed { targetHost, code, proxyInUse, proxyReadAtMs, sinceBootMs, hint }` —— `proxyInUse` = 这一跳走的代理
2207
- **变量名**(`HTTP_PROXY` / `HTTPS_PROXY`,不打值;直连 ⇒ `null`),`proxyReadAtMs` = 代理变量被读进来的时刻(= 起服那一拍),`sinceBootMs` = 进程已跑多久
2233
+ ① **运维日志** 一行 `outbound_connect_failed { targetHost, code, proxyInUse, proxyHop?, proxyReadAtMs, sinceBootMs, hint }` —— `proxyInUse` = 这一跳走的代理
2234
+ **变量名**(`HTTP_PROXY` / `HTTPS_PROXY`,不打值;直连 ⇒ `null`),`proxyHop` = 只在这一跳走了代理时在场的**原句**(上游错误原文,含代理 host:port,凭据位已脱敏;
2235
+ 这是代理地址在运行时失败里唯一的去处,wire 上没有),`proxyReadAtMs` = 代理变量被读进来的时刻(= 起服那一拍),`sinceBootMs` = 进程已跑多久
2208
2236
  —— 两者一对照就知道「起服之后 VPN / 代理是不是换过」;全进程的出网都算(模型、中心、工具 …),每条失败的请求一行(重试各算一次)。
2209
2237
  ② **诊断面** `GET /v1/diagnostics/wiring` 的 `outbound` 段:`{ proxy: { http, https }, noProxy, loopbackDirect, proxyReadAtMs, lastConnectFailure }` ——
2210
2238
  代理只报**名字**(哪只变量供的;`https` 在 `HTTPS_PROXY` 缺席时回落 `HTTP_PROXY`)、`NO_PROXY` 只报在不在,地址一个字节都不上本段;
@@ -2241,10 +2269,10 @@ server 进程(HTTP 服务与 `run-local`)**所有走 undici 的出网跳**都认
2241
2269
  `tag`(本地 tag 在、tag 树的版本对)→ `precheck`(registry 上本版在不在;读不出 ⇒ 红,不盲发)→ `token` → `clone`(干净 tag 树 =
2242
2270
  `git clone --depth 1 --branch v<v> file://<仓>`)→ `npm-ci` → `prepublish`(`npm run prepublishOnly`)→ `pack` → `publish`(**在 tag 树目录里**
2243
2271
  `npm publish --ignore-scripts`)→ `readback-wait`(轮询 registry 至多 20 min)→ `readback-shasum`(dist.shasum = 本地 tarball 的 sha1)→
2244
- `readback-bytes`(curl 回读 tarball,与本地 `cmp` 逐字节同)→ `githead`(registry 的 gitHead = tag commit)。脚本钉 `umask 022`(文件权限进 tar 头,
2272
+ `readback-bytes`(curl 回读 tarball,与本地 `cmp` 逐字节同;**传输失败**在 `readback-wait` 用的同一只窗里重试、窗满才红,**字节差**不重试、立即红,两种红句分开说)→ `githead`(registry 的 gitHead = tag commit)。脚本钉 `umask 022`(文件权限进 tar 头,
2245
2273
  换 umask 重跑会 pack 出另一份字节);每次 registry 请求有硬上限(60 s),回读等待另有 20 min 墙钟截止,registry 挂住不会挂死链。成功尾行
2246
2274
  `PUBLISH v=<v> shasum=<sha1> gitHead=<sha> bytes=<n> readback=identical`(发车帖 / CHANGELOG 回填 shasum 与 gitHead 从这里取);任一步红 ⇒
2247
- `PUBLISH-FAIL v=<v> step=<步骤>` + 非零退出 + 一句点名步骤的红因。
2275
+ `PUBLISH-FAIL v=<v> step=<步骤>` + 非零退出 + 一句点名步骤的红因(红因与步骤行同走 stdout,链日志里步骤行之后紧跟)。
2248
2276
  - **幂等**:registry 上已有本版 ⇒ 不重发,只做回读比对 —— 与本地 pack 逐字节同且 gitHead 对 ⇒ 绿并说「已在」;不同 ⇒ 红(别人发的字节,版本不可覆盖)。
2249
2277
  门 3.5 红了(tag 已推):修好后单独重跑 `bash scripts/publish-local.sh <v>`,再前台跑 `bash scripts/post-publish-gates.sh <v>`。
2250
2278
  **手跑的前置**:两只脚本开头都封口,封口要 import `@sema-agent/core`(封口的判据 = core `scrubSecretEnv`)⇒ 在本树装着依赖的前提下跑
@@ -2255,7 +2283,7 @@ server 进程(HTTP 服务与 `run-local`)**所有走 undici 的出网跳**都认
2255
2283
  引用)。脚本不读 `~/.npmrc`:每条 npm 调用带临时 `--userconfig`,临时 npmrc 只写 `${NPM_TOKEN}` 引用、不写值;开头封口,`npm ci` 的安装脚本 /
2256
2284
  构建 / pack-hygiene 测试拿不到任何凭据,只有 `npm publish` 那一个进程拿到 `NPM_TOKEN`(且带 `--ignore-scripts`)。令牌从不打印。
2257
2285
  - **门 4 不再等 CI**(同一裁定):`scripts/post-publish-gates.sh` 的门 4 = 一个 45 min 的 npm 窗(公开 packument 上 `versions[<v>]` 在且
2258
- `dist-tags.latest` 追平)。判决行 `GATES456 v=… rc4=… rc5=… rc6=… shasum=… elapsed=… ciTag=… ciMain=… tagRun=… mainRun=… why4=…` 十一键形状不变;
2286
+ `dist-tags.latest` 追平)。判决行 `GATES456 v=… rc4=… rc5=… rc6=… shasum=… elapsed=… ciTag=… ciMain=… tagRun=… mainRun=… why4=… chain=…`:既有十一键形状不变,末尾追加第十二键 `chain=<本轮标识>`(见下一条起链启动器);
2259
2287
  CI 两腿(`ciTag` / `ciMain` / `tagRun` / `mainRun`)是**信息格**,不进 rc4、不进 why4;`why4` 只剩 `propagation_timeout`(rc4 非零)与 `-`(绿)。
2260
2288
  自此门 4 常态首拍即绿,CI 两腿多半还在跑 ⇒ 常落 `unchecked:still-running`,如实。7.105.0 起(S-882 ③)CI 信息格见包后**读一次、不轮询**
2261
2289
  (此前的有界轮询几乎恒白等 10 min 再落同一个 still-running):落了终态记 conclusion;还在跑 ⇒ `unchecked:still-running` + 一句 `gh run view <id>`
@@ -2353,7 +2381,9 @@ server 进程(HTTP 服务与 `run-local`)**所有走 undici 的出网跳**都认
2353
2381
  **从表派生** —— 一门要一个新名字就登记进表,
2354
2382
  启动器自动透传(7.104.0 手抄名单漏了 `DEEPSEEK_API_KEY` ⇒ 门 2.4 停链)。起链 shell 里表外的变量(含 core 名形黑名单不认的
2355
2383
  `*_PASS` / `*_PWD` / `*_COOKIE`)一条都进不去;值经管道进 `env -i` 起的子 shell,不进任何 argv。远端三条 `GATE_*_TEST_URL` 由它显式传
2356
- 共享库(k3s8 NodePort)。链日志按版本追加,启动器只认起链之后新落的行(同版本重起链不会把上一次的判决当成这一次的)。之后转述门 3.5 的 `PUBLISH` 行;release.sh 红而 tag 已在 ⇒ 立即收尾并指路 `publish-local.sh` + `post-publish-gates.sh`;
2384
+ 共享库(k3s8 NodePort)。链日志按版本追加:门 2 / 门 3.5 的行(`GATE2` / `PUBLISH`)只认起链之后新落的行;**判决行只认本轮标识** —— 启动器起链时铸 `<pid>-<epoch>`,经 `release.sh` 的第一个参数交给它起的 `post-publish-gates.sh`
2385
+ (第二个参数),判决行末键 `chain=` 带回,等待方只读 `chain=` 等于本轮标识的那一行。同版本重起链时,上一轮留下的 `post-publish-gates.sh` 迟到追加的判决行(落在起链偏移之后,按偏移认不出)、
2386
+ 十一键老形、手跑的 `chain=-` 行都不是本轮的 —— 没等到本轮的行 ⇒ 退出码 1 并说没出现,不转述别轮的读数。手跑 `release.sh` / `post-publish-gates.sh` 不给标识 = `chain=-`。之后转述门 3.5 的 `PUBLISH` 行;release.sh 红而 tag 已在 ⇒ 立即收尾并指路 `publish-local.sh` + `post-publish-gates.sh`;
2357
2387
  绿 ⇒ 等 `GATES456`(窗 = `GATE4_WINDOW_SEC` + 20 min)后转述 `GATE2` / `PUBLISH` / 判决行。退出码见脚本头注(0 = 全绿)。
2358
2388
  - **wire 变更机器清单**(S-129 ③,7.105.0 起):`node scripts/wire-changes-server.mjs <version>` 生成 `wire-changes/server@<version>.json`
2359
2389
  (形照 core 的 `wire-changes/core@<ver>.json`:`added` / `removed` / `changed` / `narrowed` + `closedSetMembers` + `breaking`)。源 = 契约附录
@@ -133,6 +133,8 @@ data-center:2c/2G/20G)以下 BLOCK——确实无法稳定部署,提前警告不
133
133
  - **registry**:`SEMA_REGISTRY_IMAGE`(GHCR/Hub 的 `sema-registry` 镜像,迁移中,公开仓匿名可拉;
134
134
  SWR 已弃用;仅换 private 仓时才需 k8s 路径凭据=env `IMAGE_PULL_USER`/`IMAGE_PULL_TOKEN`)。
135
135
  - 公共镜像(postgres/minio/gitea)直接拉;国内网络不通时配 dockerd `registry-mirrors`。
136
+ - **MinIO**:钉死的 tag(`minio/minio:RELEASE.…`,chart / cluster 静态清单 / compose 三份同值;k8s `imagePullPolicy: IfNotPresent`,不再 `:latest` + `Always`)。换源 = 换整条引用:
137
+ compose 与 kube-up 读环境变量 `MINIO_IMAGE`(kube-up 给了才递给 chart),chart 直接 `--set minio.image=<源>/minio/minio:<tag>`(预灌镜像的节点 `--set minio.pullPolicy=Never`)。
136
138
 
137
139
  ## 凭据纪律
138
140
 
@@ -37,7 +37,8 @@ spec:
37
37
  spec:
38
38
  containers:
39
39
  - name: minio
40
- image: {{ .Values.minio.image }}
40
+ image: {{ .Values.minio.image | quote }}
41
+ imagePullPolicy: {{ .Values.minio.pullPolicy }}
41
42
  args: ["server", "/data", "--console-address", ":9001"]
42
43
  env:
43
44
  - name: MINIO_ROOT_USER
@@ -32,6 +32,7 @@ spec:
32
32
  - { name: PORT, value: "8090" }
33
33
  - { name: DB_BACKEND, value: pg }
34
34
  - { name: SESSION_BACKEND, value: auto }
35
+ - { name: STORE_LOCK_WAIT_MS, value: {{ .Values.server.storeWaitMs | quote }} }
35
36
  - { name: PG_HOST, value: sema-pg-rw.{{ .Release.Namespace }}.svc }
36
37
  - { name: PG_PORT, value: "5432" }
37
38
  - { name: PG_USER, value: sema }
@@ -43,6 +43,7 @@ spec:
43
43
  - { name: PORT, value: "8090" }
44
44
  - { name: DB_BACKEND, value: pg }
45
45
  - { name: SESSION_BACKEND, value: auto }
46
+ - { name: STORE_LOCK_WAIT_MS, value: {{ .Values.server.storeWaitMs | quote }} }
46
47
  - { name: PG_HOST, value: sema-pg-rw.{{ .Release.Namespace }}.svc }
47
48
  - { name: PG_PORT, value: "5432" }
48
49
  - { name: PG_USER, value: sema }
@@ -26,7 +26,10 @@ pg:
26
26
  bucket: s3://sema-pg-backup/
27
27
 
28
28
  minio:
29
- image: minio/minio:latest
29
+ # 钉死的 tag(不用 `:latest`:k8s 对它的缺省拉取策略是 Always,每次 Pod 重建都拉「今天恰好是什么」)。换国内源 = 换整条引用
30
+ # (`--set minio.image=<源>/minio/minio:<tag>`,同 server.image / gitea.image 的形);cluster 静态清单与 compose 默认写同一个 tag(测试咬合)。
31
+ image: minio/minio:RELEASE.2025-09-07T16-13-09Z
32
+ pullPolicy: IfNotPresent # tar 预灌=Never(同 server.pullPolicy)
30
33
  storageSize: 200Gi
31
34
  storageClass: longhorn
32
35
  rootUser: sema
@@ -37,6 +40,9 @@ server:
37
40
  pullPolicy: IfNotPresent # tar 预灌=Never
38
41
  replicas: 3
39
42
  nodePort: 30890
43
+ # 引擎 boot 期等库的窗(ms,env STORE_LOCK_WAIT_MS):CNPG 与 server Pod 同时起、库初始化常慢于 server 就绪 ——
44
+ # 窗内重试、到窗仍连不上才拒启(拒启后 kubelet 重拉;引擎不会再退成内存起服)。0 = 第一次失败就拒。
45
+ storeWaitMs: 300000
40
46
  serviceAuthToken: "" # REQUIRED
41
47
  modelGatewayBaseurl: "" # REQUIRED
42
48
  modelId: "" # REQUIRED