@sema-agent/server 7.120.0 → 7.122.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 (149) hide show
  1. package/MIGRATION.md +173 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +3 -1
  4. package/USAGE.md +97 -29
  5. package/deploy/sema-up/chart/templates/server-v4pro.yaml +2 -2
  6. package/deploy/sema-up/chart/templates/server.yaml +2 -2
  7. package/deploy/sema-up/stack/sema-stack-cluster.yaml +1 -1
  8. package/dist/approval-reconciler.d.ts +8 -0
  9. package/dist/approval-reconciler.js +72 -52
  10. package/dist/auto-mode-face.d.ts +9 -2
  11. package/dist/auto-mode-face.js +5 -3
  12. package/dist/bench/s1/live-deps.js +1 -0
  13. package/dist/boot/config-center.d.ts +5 -7
  14. package/dist/boot/config-center.js +33 -9
  15. package/dist/boot/engine-lease.d.ts +1 -1
  16. package/dist/boot/leader.js +1 -0
  17. package/dist/boot/reapers.d.ts +3 -0
  18. package/dist/boot/reapers.js +2 -1
  19. package/dist/boot/resolve-spec.js +2 -4
  20. package/dist/boot/shutdown.d.ts +9 -10
  21. package/dist/boot/shutdown.js +59 -16
  22. package/dist/boot/stage-08-reapers.js +2 -2
  23. package/dist/boot/stage-09-leader.d.ts +2 -9
  24. package/dist/boot/stage-10-http-server.d.ts +1 -9
  25. package/dist/boot/stage-10-http-server.js +31 -8
  26. package/dist/capabilities/composite-members.d.ts +1 -1
  27. package/dist/capabilities/composite-members.js +1 -1
  28. package/dist/capabilities/memory-notice.d.ts +1 -1
  29. package/dist/config-catalog.js +12 -6
  30. package/dist/config-center/center-request.d.ts +13 -1
  31. package/dist/config-center/center-request.js +25 -10
  32. package/dist/config-types.d.ts +44 -5
  33. package/dist/config-types.js +6 -0
  34. package/dist/config.d.ts +35 -6
  35. package/dist/config.js +113 -33
  36. package/dist/fleet/drain-entry.d.ts +112 -0
  37. package/dist/fleet/drain-entry.js +38 -0
  38. package/dist/fleet/engine-signal.d.ts +144 -0
  39. package/dist/fleet/engine-signal.js +60 -0
  40. package/dist/fleet/serving-lease.d.ts +47 -0
  41. package/dist/fleet/serving-lease.js +233 -0
  42. package/dist/fleet-client.d.ts +35 -19
  43. package/dist/fleet-client.js +49 -25
  44. package/dist/governance-overrides.d.ts +7 -2
  45. package/dist/http/admission.js +7 -5
  46. package/dist/http/approval-sweeps.js +5 -7
  47. package/dist/http/checkpoint-ctx.d.ts +60 -11
  48. package/dist/http/checkpoint-ctx.js +23 -9
  49. package/dist/http/dispatch.js +33 -16
  50. package/dist/http/health-self-report.d.ts +60 -0
  51. package/dist/http/health-self-report.js +53 -0
  52. package/dist/http/principal-gate.d.ts +8 -1
  53. package/dist/http/principal-gate.js +1 -0
  54. package/dist/http/readiness.d.ts +25 -5
  55. package/dist/http/readiness.js +31 -6
  56. package/dist/http/resume-legs.js +9 -9
  57. package/dist/http/route-ctx.d.ts +25 -7
  58. package/dist/http/route-table.d.ts +6 -0
  59. package/dist/http/route-table.js +8 -1
  60. package/dist/http/routes/a2a-serve.d.ts +1 -1
  61. package/dist/http/routes/a2a-serve.js +5 -5
  62. package/dist/http/routes/admin-drain.d.ts +15 -31
  63. package/dist/http/routes/admin-drain.js +28 -10
  64. package/dist/http/routes/approvals-assistant.d.ts +4 -3
  65. package/dist/http/routes/approvals-assistant.js +9 -9
  66. package/dist/http/routes/capabilities.js +7 -5
  67. package/dist/http/routes/devices.js +3 -2
  68. package/dist/http/routes/fleet.d.ts +1 -0
  69. package/dist/http/routes/fleet.js +1 -1
  70. package/dist/http/routes/images.d.ts +1 -0
  71. package/dist/http/routes/images.js +1 -1
  72. package/dist/http/routes/leader.d.ts +1 -1
  73. package/dist/http/routes/leader.js +7 -1
  74. package/dist/http/routes/memory-consolidation.d.ts +1 -1
  75. package/dist/http/routes/memory-consolidation.js +1 -1
  76. package/dist/http/routes/notify-wake.d.ts +1 -1
  77. package/dist/http/routes/notify-wake.js +1 -1
  78. package/dist/http/routes/rules.js +4 -3
  79. package/dist/http/routes/runs.d.ts +4 -2
  80. package/dist/http/routes/runs.js +7 -6
  81. package/dist/http/routes/sessions.d.ts +1 -0
  82. package/dist/http/routes/sessions.js +1 -1
  83. package/dist/http/routes/side-query.d.ts +1 -1
  84. package/dist/http/routes/side-query.js +1 -1
  85. package/dist/http/routes/tasks.d.ts +2 -2
  86. package/dist/http/routes/tasks.js +3 -3
  87. package/dist/http/routes/trace-usage.d.ts +1 -0
  88. package/dist/http/routes/trace-usage.js +1 -1
  89. package/dist/http/routes/workflows.d.ts +1 -0
  90. package/dist/http/routes/workflows.js +1 -1
  91. package/dist/http/send.d.ts +13 -0
  92. package/dist/http/send.js +9 -0
  93. package/dist/http/server.d.ts +10 -21
  94. package/dist/http/server.js +20 -1
  95. package/dist/http/stored-body-replay.d.ts +27 -0
  96. package/dist/http/stored-body-replay.js +23 -0
  97. package/dist/http/wire-types.d.ts +7 -1
  98. package/dist/leader/leader.d.ts +22 -1
  99. package/dist/leader/leader.js +4 -1
  100. package/dist/leader/repair-wire.d.ts +1 -1
  101. package/dist/leader/repair-wire.js +2 -2
  102. package/dist/leader/run-entry.d.ts +27 -0
  103. package/dist/leader/run-entry.js +29 -0
  104. package/dist/leader/wire.d.ts +10 -0
  105. package/dist/leader/wire.js +15 -10
  106. package/dist/model-reach-scope.d.ts +15 -3
  107. package/dist/model-reach-scope.js +3 -2
  108. package/dist/model-route-url.d.ts +13 -20
  109. package/dist/model-route-url.js +28 -24
  110. package/dist/model-unreachable-notice.d.ts +2 -1
  111. package/dist/model-unreachable-notice.js +1 -0
  112. package/dist/observability/fail-open.d.ts +35 -2
  113. package/dist/observability/fail-open.js +20 -0
  114. package/dist/plugins/approval-ask-store-file.d.ts +1 -0
  115. package/dist/plugins/approval-ask-store-file.js +3 -0
  116. package/dist/plugins/approval-ask-store-memory.d.ts +1 -0
  117. package/dist/plugins/approval-ask-store-memory.js +6 -0
  118. package/dist/plugins/approval-ask-store-sql.d.ts +12 -0
  119. package/dist/plugins/approval-ask-store-sql.js +4 -0
  120. package/dist/plugins/one-time-migrations.js +14 -1
  121. package/dist/plugins/park-commit-watch.d.ts +19 -5
  122. package/dist/plugins/park-commit-watch.js +24 -17
  123. package/dist/plugins/permission-rule-store-sql.d.ts +2 -1
  124. package/dist/plugins/remote-env-device.js +0 -1
  125. package/dist/request-key-closure.js +1 -1
  126. package/dist/router/route-orchestration.d.ts +4 -2
  127. package/dist/router/route-orchestration.js +2 -1
  128. package/dist/rules-consent.d.ts +6 -2
  129. package/dist/rules-consent.js +3 -0
  130. package/dist/run-local.js +3 -1
  131. package/dist/runs.js +1 -1
  132. package/dist/runtime-governance.d.ts +44 -32
  133. package/dist/runtime-governance.js +23 -9
  134. package/dist/sighup-idle.d.ts +7 -1
  135. package/dist/sighup-idle.js +4 -4
  136. package/dist/task-settings.d.ts +37 -14
  137. package/dist/task-settings.js +20 -20
  138. package/dist/tool-approval.d.ts +13 -0
  139. package/dist/tool-approval.js +17 -2
  140. package/dist/trace/core-keyset-guard.d.ts +2 -2
  141. package/dist/trace/redact.d.ts +3 -3
  142. package/dist/trace/self-minted-wire.d.ts +64 -0
  143. package/dist/trace/self-minted-wire.js +30 -0
  144. package/dist/unattended-permission-mode.d.ts +17 -13
  145. package/dist/unattended-permission-mode.js +3 -1
  146. package/dist/url-shape.d.ts +31 -0
  147. package/dist/url-shape.js +41 -0
  148. package/package.json +3 -3
  149. package/scripts/drain-check-local.mjs +1 -1
package/MIGRATION.md CHANGED
@@ -7,6 +7,179 @@
7
7
  > ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
8
8
  > 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。
9
9
 
10
+ ## 7.122.0 —— 配置型 URL 拒句零回显(两种模型路由形改拒启)· 排空口的期限有上界(`FLEET_LEASE_OUTAGE_MAX_MS`)· 服务租约能力位撤掉再宣告不重置失联钟 · 改路停驻的审批在停驻当拍收口(迟到作答的 202 写真)· 排空不被只读订阅流挂住 · plan 模式一只判官(运维 `AUTONOMY=plan` = 提交入口的模式词地板)
11
+
12
+ **升级前必读**:按拒句文字匹配配置拒启的脚本改按拒启码匹配(第 1 条);模型路由三键里没有 `ftp://` 一类非 http(s) 根(被消费的根修前已拒启,不被消费的那一根升级后也拒启)、没有以空 `#` 结尾的根(第 2 条,升级后拒启);
13
+ 调 `POST /v1/admin/drain` 的编排脚本 / 执行器发的 `deadlineAt` 不超过「此刻 + `FLEET_LEASE_OUTAGE_MAX_MS`」(缺省 24 h)(第 3 条);
14
+ 设了 `AUTONOMY=plan` 的部署核一遍它要的是「plan 模式」还是「整条 run 只读」,升级窗内有停驻的 run 时先读第 7 条;开了 leader 端点的这类部署上 `POST /v1/leader` 答 `422 request.autonomy_lane_refused`(第 7 条);
15
+ 请求 `settings.permissions.deny` / `.ask` 里没有 `Monitor(<命令>)`(改写成 `Bash(<命令>)`;停驻中带这一形的任务取消后按改正的规则重提)(第 8 条);
16
+ `Bash(...)` 命令规则现在也判 `Monitor`,allow 也清它的普通问 —— 核一遍自己的 allow 规则(第 9 条);
17
+ 跑在无人值守腿(leader、无终端的 `run-local`)上的脚本里没有带命令的 `sudo -s|-i`(升级后那是一张没人答的 `probe` 卡 ⇒ 拒,第 10 条)。
18
+
19
+ 1. **配置型 URL 的拒句只点名规则,不再回显值里解析出的片段**:中心基址三席、`FLEET_ADVERTISE_ADDRESS`、请求期中心基址检查、中心凭证续签的 issuer、
20
+ 模型路由三键(`MODEL_GATEWAY_BASEURL` / `MODEL_GATEWAY_FALLBACK_URLS` 逐项 / `ANTHROPIC_BASE_URL`)、`MEMORY_EMBEDDER_ENDPOINT`、`A2A_SERVE_URL` 同一只判官、同一种拒句:
21
+ `<键> <规则词>; got "<值的样子>"` —— 规则词不含 scheme,合格的 http(s) 形只展示到 origin(其后一律 `/…`),其余整只 `«redacted:url»`。
22
+ 旧拒句文字(`scheme must be http or https (got "…")`、`must be http(s) (got`、`is not a parseable absolute URL`、`must be a parseable http(s) URL`、
23
+ `must not carry credentials in the URL`,以及 userinfo / fragment 规则词的旧括号句 `(credentials never ride in a base URL: …)` / `(request paths are appended to this base)`)不再出现;
24
+ 带码的四席拒启码不变(`config.fleet_advertise_address_invalid` / `config.model_route_url_invalid` / `config.gateway_url_userinfo` / `config.model_route_url_fragment`)—— 按码匹配
25
+ (一处角形换了码:模型路由根是层级式非 http(s) **又**带 userinfo 或 fragment 时,见第 2 条);
26
+ 中心基址三席、`MEMORY_EMBEDDER_ENDPOINT`、`A2A_SERVE_URL` 的拒启句修前修后都不带机读码 —— 这五席按规则词前缀匹配
27
+ (`is not a parseable http(s) URL` / `must not carry userinfo` / `must not carry a query` / `must not carry a fragment`),不匹配括号内的解释句。拒句里不再有路径:要认是哪一条 URL,看自己的配置。
28
+ 2. **模型路由三键的两种形**:层级式非 http(s)(`ftp://…` 一类)⇒ 声明期拒启、`config.model_route_url_invalid`(被消费的根修前已由引擎的路由构造器拒启 —— 引擎的句、无本仓码、整串回显;本部署不消费的那一根修前照常起服,升级后同样拒启);
29
+ 这一形**同时**带 userinfo 或 fragment(`ftp://u:p@gw/v1`、`ftp://gw/v1#x`)⇒ 同样先判形、答 `config.model_route_url_invalid` —— 修前分别答 `config.gateway_url_userinfo` /
30
+ `config.model_route_url_fragment`,按这两个码认这类根的告警改认 `config.model_route_url_invalid`;
31
+ 根以一个空的 `#` 结尾(`https://gw/v1#`)⇒ `config.model_route_url_fragment`(修前放行)—— 删掉那个 `#`。query 照收。
32
+ 3. **排空口 `deadlineAt` 离引擎此刻超过 `FLEET_LEASE_OUTAGE_MAX_MS` ⇒ `400 request.field_invalid`**,什么都不变(没进排空、在飞不动);拒句点名 `deadlineAt` 与上界的值。
33
+ 修前任何期限都收(`此刻 + 365 天` ⇒ 200,实例不就绪直到在飞归零或第二发停止信号)。上界与服务租约的失联容忍是同一席(一台实例活着而不服务的最长时长);
34
+ 非 fleet 成员同样生效(没设就是缺省 24 h)。要发更远的期限 ⇒ 调大这台部署的 `FLEET_LEASE_OUTAGE_MAX_MS`(它同时放宽租约失联容忍)。期限按引擎的挂钟量:
35
+ 调用方与引擎时钟有偏差时,不要贴着上界发。已过的期限照旧收(进入即到期)。只在这一发会进入排空时判:已在排空(或硬停已在进行)时的重发(含换了期限的)照回 `already_draining`。
36
+ 4. **服务租约能力位撤掉再宣告,不再重置失联钟**(只影响 fleet 成员 ∧ 中心宣告过 `servingLease` 的部署;零动作):钟只由授予与本进程第一次见到能力位写。
37
+ 中心降级到不宣告能力位超过 `FLEET_LEASE_OUTAGE_MAX_MS` 再升回来时,一直没被确认的成员在位再出现后即拒新工作(`503 state.lease_expired`),中心授予即恢复 ——
38
+ 修前位再出现那一拍重新起钟。
39
+ 5. **改路停驻的审批在停驻当拍收口;迟到作答的 `202` 写真**(零动作;按 202 处置的壳核一遍):排空进入或卡窗到期把活卡改路耐久停驻之后,审批行在
40
+ 引擎把停车行写进店的那一拍就收口(修前要等对账周期扫,缺省 60 s 一拍;local 车道实测排空窗内 / 窗到期后整窗收不了)。于是对旧 approvalId 的
41
+ `POST /v1/tool-approvals/:id/respond` 修后通常直接 `200 accepted:"parked_redeemed"`。仍可能出现的 `202 reason:"parking_in_progress"`(停驻与写店之间那一段)
42
+ **不是结算 —— 作答没有生效**:体多两键 `message`(`parking in progress — not applied; retry after retryAfterMs or answer through decidePath`)与
43
+ `decidePath`(耐久回决口 `/v1/approvals/<sessionId>/decide` 的路径串)。壳按 `retryAfterMs` 重试一次,或改走 `decidePath`;把 202 当已结算的壳改掉这一处。
44
+ `decidePath` 只在停车行所在会话读得准时在场:委派子代腿与无会话的临时腿上它缺席,`message` 改为 `… or answer through the parent session's durable decide`。
45
+ 6. **排空不再被只读订阅流挂住**(零动作;读 `/health.inflight` 的监控核一遍):`/health.inflight`(排空等的同一只读数)只数在飞的工作 —— run 腿、计费提交、
46
+ HITL 应答与普通请求;只读订阅流(`/v1/approvals/stream`、`/v1/fleet/stream`、`/v1/sessions/:id/events`、`/v1/runs/:id/events`、`/v1/tasks/:id/stream`、
47
+ `/v1/runs/:id/subagents/:sub/stream`、`/v1/workflows/:id/stream`、`/v1/images/bakes/:id/events`)不再计入,读数变小。修前壳附着(常开审批订阅流)时 SIGTERM
48
+ 排空跑满整窗、再等强断才退出;修后订阅流在排空进入那一拍收到一帧 `event: error` + `{type:"error", errorCode:"draining", message}`(与流帽那一帧同一套重连协议)
49
+ 后被服务端关闭,排空只等真在飞的工作。消费端按「服务端收流 ⇒ 重连」处置(到替换实例)。排空期间新开的订阅照常服务,硬停起点同样收这一帧。
50
+ SIGHUP 空闲窗照旧把只挂着订阅流的壳当「有人在用」。
51
+ 7. **plan 模式一只判官**:用户选的 plan(顶层 `permissionMode:"plan"` / `settings.permissions.defaultMode:"plan"`)、运维 `AUTONOMY=plan`、模型自己调 `EnterPlanMode`
52
+ 都只落引擎的 `permissionMode:"plan"`。工具表与 `default` 逐名相同(委派工具 `Agent`、`Write` / `Edit` 都在),写与非只读调用在调用时由引擎的 plan 门问(有人可答)/
53
+ 拒(无人可答);委派子代以 plan 为天花板继承同一道门。带 `agents` 的 plan 任务不再以 `config.agents.no_delegation_tool` 整轮失败。
54
+ · **依赖「plan 下工具表里没有 `Agent` / `Write` / `Edit`」的消费方**:改按调用结果判(plan 门的问 / 拒),不按工具表判。
55
+ · **run 的模式词只有一个源 —— 存储的提交体顶层 `permissionMode`**:写者是每一个出生点(新的持久化写)与 `plan_review` 写回,治理层不再按段重折模式词。
56
+ · **`AUTONOMY=plan` = 提交入口的模式词地板**:不再是只读手。每个新提交的任务以 `plan` 起步(请求不带模式词 ⇒ `plan`;带任何词 ⇒ 钳成 `plan`),
57
+ 一次写在有人可答时出一张卡(人批了就写);批准(或 `edit`)一份计划之后,整条 run 按批准时选的档(`permissionModeAfter`,缺席 = `default`)跑完,
58
+ 此后每一次挂起→续跑都不回 plan;`permissionModeAfter` 对这类任务照收。热改 `AUTONOMY` 只影响之后提交的任务,在飞的 run 保持它的入口词。
59
+ 地板盖在每一个 spec 出生点:run-local 盖在它自建的 spec 上,leader run 盖在它出生时的入口体上。**跑 leader 的这类部署:`POST /v1/leader` 答
60
+ `422 request.autonomy_lane_refused`**,一条 run 都不建(上一版:受理,worker 首跑不经治理、写照落,只有 `/decide` 续跑按段折只读手)——
61
+ plan 靠人审通道(`plan_review`)出口,leader 的 worker 会话没有那条通道。出路:要 leader 照跑 ⇒ `AUTONOMY=read-only`(续跑只读手)或 `ask`;
62
+ 要计划审阅 ⇒ 改走 `POST /v1/runs`(计划经 `POST /v1/assistant/tasks/:id/plan_review` 批准)。能力位 `leader` 在这类部署上答 `false`(值变键不变),提交方按位预判;按码分支。
63
+ `ROUTER_ENABLED` 上非隔离车道按可写任务升级到监督姿态。
64
+ 请求 `dontAsk` 的无人调用方在这类部署上同样被钳成 `plan`(引擎的模式秩里 `dontAsk` 高于 `plan`):写与交计划撞 plan 门的卡(无人可答 ⇒ 拒;配耐久审批 ⇒ 停驻等人),
65
+ 批准计划时 `permissionModeAfter` 只有 `default` / `acceptEdits` 两档、回不到 `dontAsk`(上一版:只读手 + `dontAsk`,问一律由模式拒、不停驻)。
66
+ 能力面:`GET /v1/capabilities` 的 `permissionModeAuto.armed` 在这类部署上答 `false`(`reason:"mode_not_auto"`,在这里提交 auto 会被钳成 plan),`taskWriteFaceOpen` 照旧 `false`,
67
+ `leader` 由 `true` 变 `false`(车道挂着但不受理;既有 leader run 的 `GET` 照常)—— 三位读同一只地板钳。
68
+ 托管部署(`REQUIRE_PRINCIPAL=true`,未放开 `TASK_WRITE_FACE_ESCALATION`)上 `AUTONOMY=plan` × `bypassPermissions` 的请求由 `422` 变为受理并在 plan 里起步。
69
+ 要「整条 run 一个字节都不写」⇒ 改设 `AUTONOMY=read-only`。
70
+ · **升级窗内停驻着的 run × `AUTONOMY=plan`**:上一版写下的续跑上下文体里是请求原词,运维的 plan 那时由服务在每一次续跑重新施加(只读手)。
71
+ 本版不按原词重放它们:续跑(`/decide`、`plan_review`、`resume`、`wake`、终态 steer)⇒ `409 checkpoint.unsupported_version`,行字节不动、卡仍待决
72
+ (审批卡停驻的行在 `wake` / `resume` 上先被既有的人审门拒 —— `409 wake.gate_pending` / `gate_not_resumable` —— 本码在 `/decide` 与 `plan_review` 上读到),
73
+ SLA 到期拒扫跳过这类行。出路:升级前把它们决掉;或取消后作为新任务重提;或升级窗内把部署设成 `AUTONOMY=read-only`(按段的只读围栏,
74
+ 与上一版的结局相同,不触发这条拒),它们收尾之后再改回 `plan`。没设 `AUTONOMY=plan` 的部署不受影响;体词已是 `plan` 的 `plan_review` 审阅行照常批准,批准后按批准的档续跑。
75
+ 随包的排空检查(`scripts/drain-check-local.mjs` 与两份 `drain-check.*.sql`)本版**不数**这一类行(判据下一版补);升级前自查:`GET /v1/approvals` 列出的待决行,
76
+ 逐条看它所在任务提交时的模式词(顶层 `permissionMode` / `settings.permissions.defaultMode`;无词也算)—— 不是 `plan` 的那些,升级后在 `AUTONOMY=plan` 部署上续跑会被拒。
77
+ · **`AUTONOMY=read-only`**:仍是只读手 + 写面 `roots`,委派工具不再被剥 —— 子代同样只读(引擎把只读手夹进子代),带 `agents` 的任务照常起跑。
78
+ · **`plan_review` 的 `edit`**:按人改过的计划开干,与 `approve` 同:续跑解到 `permissionModeAfter`(缺席 = `default`),`edit` 现在也收这一位(修前 `400`)——
79
+ 修前 `edit` 之后仍是只读手;要「改了计划但继续只读研究」请用 `reject` + `reason`。
80
+ · **`plan_review` 的两句 `400 request.field_conflict` 改了文字**(码不变,按码匹配):错门句 `permissionModeAfter is only valid with a decision that carries the plan out ('approve' or 'edit'; got 'reject')`;
81
+ 存储模式词不是 plan 的任务送它 ⇒ `permissionModeAfter does not apply to this task: it was not started in plan mode (its stored permission mode is not "plan"), so approving the plan returns it to the mode it was in before planning`。
82
+ 8. **`Monitor(<命令>)` 不是规则拼法:请求 `settings.permissions.deny` / `.ask` 里写它 ⇒ 提交 `422`**(引擎 7.47.0 的规则编译器拒它)。修前 `Monitor(touch:*)` /
83
+ `Monitor(git push:*)` / `Monitor(timeout:5)` / `Monitor(command:touch*)` 照收、编译成一条什么都匹配不上的规则(以为拦住了 `Monitor` 的命令,其实没有);
84
+ `Monitor(touch k1)` 一类已是 422,句子换了。现在句子点名条目、引擎的码(`invalid.command_rule` / `invalid.primary_field`)与引擎的句(命令拼法给出该写的
85
+ `Bash(<命令>)`)。**出路**:改写成 `Bash(<命令>)` —— 它同时判 `Bash` 与 `Monitor`(第 9 条);`Monitor(description:…)` 与裸 `Monitor` 照收。
86
+ 停驻中的任务、请求体带这一形的:升级后续跑重解析同一份请求体,答 `409 resume_blocked_by_policy`(`blockedBy: "request.unprocessable"`)。出路 = 取消它、按改正的
87
+ `Bash(<命令>)` 重提。在 7.121.0 上把它决完也能走,但那一形在 7.121.0 上什么都不匹配 —— 决完 = 这条任务在没有那条 deny / ask 的情况下跑完
88
+ (`bypassPermissions` 下被它点名的命令照跑、不问),只在接受这一点时才选。
89
+ 同一只「编不成规则」的指引(请求 deny / ask 的 422 句、`APPROVAL_REQUIRE` / `APPROVAL_DENY` / `APPROVAL_NEVER_AUTO` 的拒启句、中心下发 `governance.approvalRequire`
90
+ 的 warn detail)不再缀「Tool names carry no backslashes and are far shorter than the 512-character rule cap; check the spelling, and use mcp__<server>__* …」那一句,
91
+ 只带引擎的码与句 —— 按那句匹配的脚本改按码匹配。其余受理口不变:请求 allow 桶照收(引擎开跑时通告读成空)、`APPROVAL_GRANT` 拒启、
92
+ `POST /v1/rules` 400 `rule_rejected`、CC 导入不成候选。
93
+ 9. **`Bash(...)` 命令规则判每一只跑 shell 命令的工具,`Monitor` 在内**(引擎行为,零动作;核一遍自己的规则):deny / ask —— `deny Bash(touch:*)` 下 `Monitor`
94
+ 跑 `touch k1` 在每一种模式都被拒(修前 `default` 一张卡、`bypassPermissions` 下照跑),`ask Bash(git push:*)` 下是一张 `bypassPermissions` 不放的卡;
95
+ **allow(放宽)** —— 一条 `Bash(...)` allow 现在也清 `Monitor` 的普通问,`dontAsk` 下也是(`Bash(tail:*)` 允许 `Monitor` 跑 `tail -f app.log` 不再出卡)。
96
+ 来源不分:请求的三只桶、`APPROVAL_GRANT`、持久规则行(卡上「总是允许」、`POST /v1/rules`、两条导入车道)、技能授予。裸 `Bash` 规则仍只管名叫 `Bash`
97
+ 的工具 —— 不要 shell 就 `Bash` 与 `Monitor` 两个名字都拒。
98
+ 子代 `agents[].denyTools` / `allowTools` 是**工具名表**,不是规则文法(引擎的子代定义:`Tool(content)` 把整只 `Tool` 对该子代藏起):同一串 `Bash(touch:*)` 写在请求 deny / ask / allow 里按命令判 `Bash` 与 `Monitor`,写在子代 `denyTools` 里是对该子代藏起整只 `Bash`、够不到 `Monitor`(`Monitor` 等 hands 之外的引擎工具不受这两张表管;要按命令拦而保留工具,写调用方权限策略的 deny 规则)。
99
+ 10. **经 `sudo -s|-i` / `su` / `runuser` 的删除与读(引擎行为;无人值守腿上的脚本要核一遍)**:`sudo -s rm -rf '$HOME'`、`su root -c 'rm -rf ~'`、`runuser -u root -- rm -rf ~` 一类删受保护位置的命令,
100
+ 在 `bypassPermissions` 与 `Bash` allow 下也停在一张要人的 `dangerous_delete` 卡上(修前:`'$HOME'` 形是不点名家目录的 `unverifiable_delete`,`su` / `runuser` 形
101
+ allow 下一张 `probe` 卡、`bypassPermissions` 零卡照跑);每个带命令的 `sudo -s|-i` 都按 `bash -c` 读 —— 无害的 `sudo -s ls` 在 `default` 与 `Bash` allow 下
102
+ 多一张卡(对照 `sudo ls` 照放)。**无人值守腿上这张卡没人答 ⇒ 拒**:leader 的各条腿与无终端的 `run-local` 按类作答的答集只有 `ordinary`(`probe` 不在其中),
103
+ 所以 `Bash` allow 下的 `sudo -s ls` 被拒(修前这张卡不存在,放行);leader 配了耐久审批的部署上改为停驻、等人经 `/decide` 答;`run-local` 有终端时问人。
104
+ 要免问地以他人身份跑命令:字面写出目标。括号类里带引号 / 反斜杠的字符(`["a"]*`)现在按「任一字符」判,读拒与删除守卫会多拦几个 bash 本不碰的名字。
105
+ `tool_end.gate.disposition.released.reason` 多一个词 `write_protection_unresolved`(线名为 `Bash` / `Monitor`、声明写路径目标的调用方工具在 `bypassPermissions`
106
+ 下的释放;引擎自带的两只工具铸不出它)—— 按词分派的消费方认这个词。
107
+
108
+ ## 7.121.0 —— 编排器片 2(引擎侧):`/readyz` 唯一探针 · 排空口契约体 · 服务租约客户端 · fleet 成员必须能被围栏
109
+
110
+ **升级前核查(一句话)**:探针改指 `/readyz`(第 1 条);调 `POST /v1/admin/drain {reason}` 的改发契约体,多租引擎的排空请求带它的 `scope`(第 2 条);读 `/health.drainReason` / `modelPlaneDeferred` 的删读点(第 2 / 5 条,两键都已删);
111
+ 设了 `FLEET_GENERATION` 的部署核中心三件与 `FLEET_ADVERTISE_ADDRESS` 都在、渲染器为多租引擎渲 `FLEET_SCOPE`(第 4 条);中心在真能答续租端点之前不宣告 `servingLease`(第 7 条);
112
+ 三只中心基址不带凭据 / query / fragment、`FLEET_ADVERTISE_ADDRESS` 设了就是 http(s)(第 7 条,没接中心也拒启)。
113
+
114
+ 1. **探针路径只有 `/readyz`**:镜像 `HEALTHCHECK` 与 sema-up 的 chart / cluster 清单 readinessProbe 改指 `/readyz`(排空中、roster 未落、服务租约被拒 ⇒ 503);
115
+ `/health` 只做诊断,恒 200。自己写探针的部署把 readinessProbe / LB 健康检查改成 `/readyz`(该口 7.102.0 起就在);要保留存活探针的用 TCP 探端口,
116
+ **不要**拿 `/readyz` 当「重启我」的信号(中心宕机时整队副本同卡 roster 未落,会一起重启)。仍探 `/health` 的老执行器 / 老编排器:把底线抬到能读 `/readyz` 的版本(≥7.102.0 的引擎都答它)之后再退役 `/health` 探针。
117
+ 2. **排空口 `POST /v1/admin/drain` 的体换成契约形**(settings-schema `DrainRequest`):`{ actionId, worker, deadlineAt, scope?, expectedGeneration?, instanceId?, term? }`,
118
+ 回执 `{ actionId, since, deadlineAt, state: "draining" | "already_draining" }`。**它现在真的进入排空**(修前只记一句停机因由,排空靠随后的 SIGTERM)。
119
+ 身份逐字段比,不符 ⇒ 409 `conflict.fleet_identity`,什么都不变:`scope` 两侧都读成值再比 —— 引擎这一侧 = env `FLEET_SCOPE`(第 4 条),请求不带 `scope`
120
+ 与引擎没设 `FLEET_SCOPE` 都读成缺省 scope `global`(所以单租部署不带 `scope` 照常通;多租引擎的请求要带它自己的 scope);`worker` / `expectedGeneration`
121
+ 缺席对缺席(非 fleet 成员不带 `expectedGeneration`);`instanceId` 在场才比(从本机 `GET /health` 读)。同一动作号重发 ⇒ 200 `already_draining`,回显第一次的
122
+ `since` / `deadlineAt`。人手排空:`actionId` 自取(如 `manual:<uuid>`)。没配 `SEMA_REGISTRY_WORKER` 的引擎没有 worker 身份,排空口对它恒 409 —— 用 SIGTERM。
123
+ **排空之后的停止信号**:「第二发停止信号 = 立即硬停」锚的是**本次排空的第一发停止信号**。SIGTERM 进的排空,那一发就是第一发,再隔
124
+ ≥ `DRAIN_SECOND_SIGNAL_MIN_GAP_MS`(缺省 250 ms)的一发硬停(与 7.120.0 同)。经排空口(或服务租约被拒)进的排空,之后到达的**第一发**停止信号被吸收:
125
+ 期限仍是请求定死的那一个,一行 info `drain_signal_after_request_drain_ignored`;再隔 ≥ 250 ms 的一发才硬停。k8s 例:`preStop` 调排空口(200)→ kubelet 发一发
126
+ SIGTERM(吸收,在飞照常跑到期限)→ `terminationGracePeriodSeconds` 到点 SIGKILL —— 把 `terminationGracePeriodSeconds` 设得不短于排空请求的期限。
127
+ **退役**:`{reason}` 声明形(400 `request.field_invalid`)、`/health.drainReason`、503 draining 体的 `reason` 键。读这两键的消费方删读点。
128
+ 3. **排空期间放行的请求**:取消 / 停止、HITL 应答(含耐久 `/decide`、PARKED 停驻卡赎回、计划审阅)、续跑(助手 resume、子代 resume)、读面、重挂;
129
+ 其余计费提交照旧 503 `draining`。**变化**:HITL 应答与续跑修前也吃排空 503,现在放行 —— 它们在排空的引擎上开出的腿于下一个干净回合边界让出
130
+ (可抢占的部署:`RESOURCE_SUSPEND` 打开 ∧ 耐久店 ∧ 远端执行环境,腿耐久停驻、可在替补上续;不可抢占的部署:腿跑到期限,到期硬停走既有的崩溃臂)。
131
+ 进入排空那一拍,本副本可抢占的在飞腿让出、活的审批卡改路耐久停驻(修前到期限硬停那一拍才收场)。🔴 开了 `RESOURCE_SUSPEND` 的部署:每一次排空
132
+ (含 SIGTERM 滚动重启)里在飞的可抢占 run 不再跑完,而是耐久停驻(`resource_limit` / `preempt`),要调 `POST /v1/assistant/tasks/:id/resume` 续 ——
133
+ 没有调度方替你续的部署,升级后把这一步加进滚动重启的收尾(列 `GET /v1/assistant/tasks` 里停驻的,逐条续)。没有停驻设施(无 checkpoint 店)的部署上,进入排空那一拍
134
+ 未决的审批卡、以及之后新出的卡按**拒**处置(与断连同一条路:无处停驻就无头自动拒,fail-closed)。排空期限在进入那一拍定死:中心热改 `limits.drainGraceMs`
135
+ 不再改在跑的排空,只影响**下一次**由停止信号(或附着租约自退)起的排空的窗。
136
+ 4. **fleet 成员与服务租约**(新旋钮):`FLEET_GENERATION`(配置渲染器写;规范十进制串 `"0"` / `"7"`,空串 / 前导零 / 符号 / 空白拒启;缺席 = 非 fleet 成员)、
137
+ `FLEET_LEASE_OUTAGE_MAX_MS`(服务租约中心不可达的容忍上限,缺省 86400000 = 24 h,< 60000 / 0 / 非整数拒启)。🔴 **设了 `FLEET_GENERATION` 就必须能被围栏**:
138
+ 中心三件(`SEMA_REGISTRY_URL` + 凭证 + `SEMA_REGISTRY_WORKER`)与 `FLEET_ADVERTISE_ADDRESS` 缺一 ⇒ 监听之前拒启 `[config.fleet_member_unfenced]`
139
+ (能力位只在心跳答复里,不公告就听不到;判在 `loadConfig`,早于任何装配)。不打算受围栏的单机引擎不设 `FLEET_GENERATION`。
140
+ 🔴 **`FLEET_SCOPE`(新旋钮)**:引擎所在的租户 scope —— 配置渲染器 / 运维请为**多租引擎**渲它(值 = 这只引擎的 per-worker 令牌绑的那个 scope);
141
+ 缺席 = 缺省 scope `global`,与 `FLEET_GENERATION` 不联动(成员不设也不拒启)。在场必须是 scope id 形(小写字母 / 数字 / 内部连字符,1–64;空串拒启)。
142
+ 它只进排空口的身份比对(第 2 条);续租体与心跳**不带**它(那两处的 scope 由中心凭证绑定)。settings-schema 8.3.0 起它是保留 worker env 名(`FLEET_SCOPE_ENV`)。
143
+ **`FLEET_ADVERTISE_ADDRESS` 改由 `loadConfig` 无条件解析一次**:设了就必须是 http(s) URL、不带 userinfo / query / fragment(与中心基址三席同一条判据,
144
+ 第 7 条),没接中心也拒启(修前只在中心车道配齐时判、只判 http(s));空串 = 未设。
145
+ fleet 面拒启句的码挂在异常 `code` 上(fatal 行印成 `Error [config.fleet_…]: …`):`config.fleet_generation_invalid` / `config.fleet_scope_invalid` /
146
+ `config.fleet_advertise_address_invalid` / `config.fleet_member_unfenced`。
147
+ fleet 成员的心跳与 `/health` 多三键 `generation` / `leaseState`(`none` / `granted` / `expired` / `denied`)/ `leaseUntil`(只在 `granted`);心跳体就是契约
148
+ `InstanceAnnouncement` 的形(settings-schema ≥ 8.2.0 才类型化这三键 —— 更老的契约包按 strip 形读,三键读不到也不拒)。租约只在中心于心跳答复里宣告
149
+ `capabilities.servingLease: true` 之后生效;没宣告 ⇒ `leaseState: "none"`、零续租、零 503(与 7.120.0 同)。只有解析成 JSON 对象的答复体才算一次能力读数:
150
+ 答复体读不出 / 不是对象(空体、截断、`null`、数组)⇒ 没有读数,上一次的位与租约钟原样,一段连拍一行 warn `fleet_announce_reply_unreadable`。
151
+ 中心没确认续租(不可达 / `unknown`)与能力读数里没宣告 `servingLease` 两条放行逐次进 fail-open 计数(`server.fleet.serving-lease-unconfirmed` /
152
+ `server.fleet.serving-lease-capability-absent`)。续租打 `POST /api/fleet/live/serving-lease`(契约
153
+ `FLEET_PATHS.servingLease`)。中心以正面证据拒(被取代 / 已退役)⇒ 排空、到期自停,并在数据根记一只 `fleet-serving-lease-denied.json` —— 同 worker 同代际
154
+ 重启后起步即排空,第一条心跳就是排空态;要让这只实例重新服务,由中心换代(渲染器写新 `FLEET_GENERATION`),不要手删那只文件。那只文件**写不进**(数据根只读 /
155
+ 盘满)⇒ 本进程照拒照停,但重启后不记得、在第一次续租答复之前会接单 —— 响亮:error 行 `fleet_serving_lease_denial_not_persisted` + fail-open 计数
156
+ `server.fleet.denied-marker-unpersisted`;数据根要可写。中心不可达 / 答 `unknown` ⇒ 不排空(每段连败一行运维 warn `fleet_serving_lease_unconfirmed`);租约过期满 `FLEET_LEASE_OUTAGE_MAX_MS` ⇒ 新工作 503
157
+ `state.lease_expired`(`Retry-After: 60`;放行同第 3 条,`/readyz` 不翻)。
158
+ 5. **扣着的候选改由 `restart.withheld: true` 上心跳与 `/health`**(扣着而有被扣的重启片时;合成:已发布的重启信号恒胜,只差发布重试的不铸);
159
+ `/health.restartRequired` 只在可执行时在场(被扣的不算,读数与 7.120.0 同)。🔴 **`/health.modelPlaneDeferred` 本版删除**(零别名),它的两半各归一张脸:
160
+ 「要重启但扣着」读 `restart.withheld`;「应用着的配置没有耐久接力」(原 `noHandoff`)读新键 **`/health.configHandoff = { state: "missing", version, since }`**
161
+ (只在 `/health`;在场 ⇔ 部署声明了耐久接力 —— `CONFIG_LKG_DURABLE=true` 且 LKG 启用 —— 而应用着的那一代没在下一次起服将用的凭证身份的 LKG 桶里确认耐久落盘,
162
+ 含部分应用的那一代与凭据换了人的情形;
163
+ 没有耐久声明的部署恒缺席:那里重启靠中心重拉是部署形本身,7.120.x 的 `modelPlaneDeferred` 在这种部署上同样只在保留候选时出现)。迁移读法:原来 `modelPlaneDeferred` 在场 ⇒ 现在 `configHandoff` 在场;原来带 `blockedReasons` ⇒ 现在 `restart.withheld: true`
164
+ 且 `restart.reasons` = 那几片;只差发布重试那一形由 `configHandoff` 承载,`restart.withheld` 不在场。
165
+ 6. **能力位**:`GET /v1/capabilities` 的 `fleet` 对象 +`leaseClient: true` / `generationEcho: true`(随 `fleet` 对象在场)。中心据此知道引擎带服务租约客户端与代际回声。
166
+ 引擎这一位刻意不叫 `servingLease`:那是中心在心跳答复里宣告「我答续租」的位,两边同名会把「客户端在」读成「租约在执行」。
167
+ 7. **活性基址 `SEMA_REGISTRY_LIVE_URL`(新旋钮,可选)与版本矩阵**:心跳 / 注销与续租(settings-schema `FLEET_LIVE_PATHS`)打它,缺席 = `SEMA_REGISTRY_URL`;
168
+ 用量、配额租约、拉配置始终打 `SEMA_REGISTRY_URL`。🔴 中心基址三席(`SEMA_REGISTRY_URL` / `SEMA_REGISTRY_LIVE_URL` / `SEMA_REGISTRY_WEB_URL`)同一条判据:
169
+ 可解析的 http(s) URL,**不带**凭据 / query / fragment(基址后面要拼路径;`http://x?a=1` 拼出来是 `http://x?a=1/api/…`),坏形拒启,没接中心也拒 ——
170
+ 修前 `SEMA_REGISTRY_URL` 起服不判、活性基址只判 http(s)。请求期对同一份基址再判一次(自拼 `ServiceConfig` 的嵌入方同规)。版本矩阵:引擎 ≥ 7.121.0 带续租客户端;
171
+ 中心宣告 `servingLease` **必须**与它真能答 `/api/fleet/live/serving-lease` 同版 —— 端点还没有的中心只宣告了位,引擎每次续租得 404,按不可达处置:
172
+ 不排空,但租约过期满 `FLEET_LEASE_OUTAGE_MAX_MS` 后新工作 503 `state.lease_expired`。中心停止宣告(位缺席)⇒ 下一条心跳起回到 `none`,503 随之解除。
173
+ 8. **7.120.0 余项的三处文本与一处嵌入方类型席**:① `POST /v1/rules/cc-import/prepare` 某层 `root` 不是绝对路径时,`400 request.body_shape` 的文本从 scope 那一句换成
174
+ `layers[<i>].root must be an absolute path`(`<i>` 从 0 起,第一个被拒的层;码与状态不变);② `POST /v1/devices/enroll-tokens` 与 `POST /v1/devices/enroll` 对不带租户 scope 的身份
175
+ (含运维主体)的 `403 auth.forbidden` 文本改说「入籍要带租户 scope 的注册中心登录身份,运维主体不带 scope、不能入籍」,不再指向配置 `OPERATOR_PRINCIPALS`(设备管理的其余口原句不变);
176
+ ③ 一次性迁移对没有活 approval 的坏 scope 规则行(`kind: "revoked-rule"`)的逐行 warn 文本改成不依赖墓碑与态的一句。三处都只动文本 —— 判错误请用 `errorCode` 与状态码,不要整句匹配。
177
+ ④ **嵌入方**(自己拼 `ServiceConfig` 的宿主):新增必填席 `configLkgEnabled: boolean`(`CONFIG_LKG_ENABLED` 的值,缺省开;远程配置的 LKG 盘缓存开关),由 `loadConfig` 一次铸出;
178
+ config-center 的两个读点只读这一席,起服之后改进程环境变量不再影响它。内部的读函数 `configLkgEnabled()` 已删(它从未在包根导出里,包的 `exports` 也不开放深路径);
179
+ 自己拼配置的宿主在配置里补这一席,照原先环境变量的取值给 `true` / `false`。同版另两只必填席:`fleetLeaseOutageMaxMs: number`(缺省 86 400 000)、
180
+ `fleetScope: string`(缺省 `"global"`);可选席 `fleetAdvertiseAddress?: string`(公告环与围栏判官读它,不再读进程环境变量)。`ServiceDeps.drainState` 的型换成
181
+ `DrainState`(排空事实四键 `draining` / `since` / `actionId` / `deadlineAt` + 入口 `enter`;没有 `reason`),`ServiceDeps.planeDeferredState` 席已删。
182
+
10
183
  ## 7.120.0 —— 引擎 7.46.0:子代工具清单回到开跑前拒退役拼法、管到内建工具 · 配置根搬家后同护 · 回显条目一种渲染 · 路径规则锚在装入处
11
184
 
12
185
  **升级前核查(一句话)**:设了 `SEMA_CONFIG_DIR` 的部署确认那个目录能建出来、不是空串;`CONFIG_LKG_PATH` 不是空串;
package/README.md CHANGED
@@ -227,7 +227,7 @@ and the tool-level error forms) is documented in `USAGE.md` §9.5.
227
227
  ## Security & privacy note
228
228
 
229
229
  > 🔴 **Trace redaction ≠ egress control** (anchor: 脱敏 ≠ 出站控制). Trace redaction (the `«redacted»` markers you see in the
230
- > ledger / SSE / turns / approval previews) only rewrites the **persistence and display** surfaces —
230
+ > ledger / SSE / turns / approval previews) only rewrites the **read / display faces** (ledger, SSE, turns, approval previews, `GET /v1/sessions/:id`); it does **not** touch the engine's session archive (the audit source of truth — stored as written, redacted only on the HTTP read face, and replayed to the model route as written on every later turn) nor the session-sync faces `/v1/sessions/:id/sync/*` (the durable log goes in and out verbatim) — those two are **original text** —
231
231
  > **the request body carrying a tool result to the model's actual route is the original, unredacted
232
232
  > text.** That route is not just `MODEL_GATEWAY_BASEURL`: it can also be the fallback gateway list
233
233
  > (`MODEL_GATEWAY_FALLBACK_URLS`), an independent Anthropic route for `provider:"anthropic"` models
package/README.zh-CN.md CHANGED
@@ -193,7 +193,9 @@ Web search(部署 env `WEB_SEARCH_*` 七键、per-request `settings.webSearch`
193
193
  ## 安全与隐私提示
194
194
 
195
195
  > 🔴 **trace 脱敏 ≠ 出站控制。** trace 脱敏(你在账本 / SSE / turns / 审批预览里看到的 `«redacted»`
196
- > 标记)只作用于**持久化与展示面**——**发往「该 run 实际选中的模型路由」的请求体是工具结果原文**。
196
+ > 标记)只作用于**读面 / 展示面**(账本 / SSE / turns / 审批预览 / `GET /v1/sessions/:id`);**不碰**引擎会话存档
197
+ > (审计真源:store 里怎么写就怎么回、HTTP 读面才脱敏,之后每一次续轮按原文回送模型路由)与 session-sync 七口
198
+ > `/v1/sessions/:id/sync/*`(逐字 durable log 出入)——这两面是**原形**——**发往「该 run 实际选中的模型路由」的请求体是工具结果原文**。
197
199
  > 这条路由不止 `MODEL_GATEWAY_BASEURL` 一根:还可能是备用网关列表(`MODEL_GATEWAY_FALLBACK_URLS`)、
198
200
  > `provider:"anthropic"` 模型的独立直连路由(缺省根 `https://api.anthropic.com`,除非显式配
199
201
  > `ANTHROPIC_BASEURL`)、或配置中心可对单个目录模型下发的 per-model `baseUrl` 覆盖(见
package/USAGE.md CHANGED
@@ -75,23 +75,23 @@ docker compose up --build --scale service=4 # 4 个无状态 worker,nginx
75
75
 
76
76
  > 拓扑:LB(入口)→ N 个一样的 worker → 共享 SQL 库(数据中心;MySQL 协议或 PG)+ 模型网关。无中央调度器,跨 worker 靠 DB 协调。
77
77
 
78
- **探针:存活看 `/health`,就绪看 `/readyz`(7.102.0+)**。`/health` 恒 200(liveness:进程在答);只认状态码的**摘流**
79
- 探针(k8s `readinessProbe` / Traefik / 负载均衡健康检查)读不到它体里的 `ready:false`,要摘流就指 `GET /readyz`:就绪 ⇒ `200 {"ready":true}`;未就绪 ⇒ `503 {"ready":false,"reasons":[…]}` + `Retry-After: 5`。
80
- `reasons` 闭集两词:`draining`(SIGTERM 后优雅排空中)、`model_roster_pending`(`CONFIG_REQUIRE_ROSTER=true`
81
- 且中心模型名册尚未落地)。两者都免鉴权(与 `/health` 同位,探针带不了 token)。🔴 **库连不上不算未就绪**:
82
- `storeLive:false` 只在 `/health` 体里披露 —— 一次库抖若进就绪门,整队副本会在同一拍被摘光(共模失效)。
83
- ⚠️ **重启语义的探针别指 `/readyz`**(镜像 `HEALTHCHECK` 配 Swarm / autoheal、k8s `livenessProbe`):中心宕机时整队副本
84
- 同卡 `model_roster_pending`,指 `/readyz` 就会一起被重启、再一起卡住 —— 同一种共模失效。它们留在 `/health`(本仓
85
- `Dockerfile` 的 `HEALTHCHECK` 就是 `/health`)。
78
+ **探针路径只有 `/readyz`(7.121.0 起;该口 7.102.0+ 就在)**。`/health` 是诊断体、恒 200,不当探针。只认状态码的**摘流**探针
79
+ (k8s `readinessProbe` / Traefik / 负载均衡健康检查 / 镜像 `HEALTHCHECK`)指 `GET /readyz`:就绪 ⇒ `200 {"ready":true}`;未就绪 ⇒ `503 {"ready":false,"reasons":[…]}` + `Retry-After: 5`。
80
+ `reasons` 闭集两词:`draining`(排空中 —— SIGTERM、排空口、服务租约被拒都进这一态)、`model_roster_pending`(`CONFIG_REQUIRE_ROSTER=true`
81
+ 且中心模型名册尚未落地)。两者都免鉴权(探针带不了 token)。🔴 **库连不上不算未就绪**:
82
+ `storeLive:false` 只在 `/health` 体里披露 —— 一次库抖若进就绪门,整队副本会在同一拍被摘光(共模失效)。服务租约**过期**(中心不可达)同样不翻 `/readyz`。
83
+ ⚠️ **`/readyz` 的 503 是「别往我这儿路由」,不是「重启我」**:中心宕机时整队副本同卡 `model_roster_pending`,拿它当重启信号就会一起被重启、
84
+ 再一起卡住。本仓 `Dockerfile` 的 `HEALTHCHECK` 指 `/readyz`(Docker 的 unhealthy 只影响路由 / 依赖判定);执行器不要按它重启容器。
85
+ 要存活探针的部署用 TCP 探端口。
86
86
  ```yaml
87
- # k8s:liveness 打 /health(进程死才重启),readiness 打 /readyz(不就绪只摘流,不重启)
88
- livenessProbe:
89
- httpGet: { path: /health, port: 8090 }
90
- periodSeconds: 10
87
+ # k8s:readiness 打 /readyz(不就绪只摘流,不重启);要 liveness 就探端口,不打 HTTP
91
88
  readinessProbe:
92
89
  httpGet: { path: /readyz, port: 8090 }
93
90
  periodSeconds: 5
94
91
  failureThreshold: 1 # 排空 / 名册未落要立刻摘,不等三拍
92
+ livenessProbe:
93
+ tcpSocket: { port: 8090 }
94
+ periodSeconds: 10
95
95
  ```
96
96
 
97
97
  **可选 — 多网关 failover + Anthropic 路由(消除单网关 SPOF)**
@@ -488,6 +488,20 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
488
488
  **7.106.0+**(S-810):候选**部分应用**(某组被整组拒、其余组已提交)的拍也照同一律算这一信号 —— 修前那一拍不签;被拒的是审批组 / 模型面组时
489
489
  (新进程 boot 重判可能拒启)信号扣下、只出 warn `restart_signal_withheld_boot_may_refuse`(点名组与差异)。
490
490
  钉:`test/health-readyz.test.ts` / `test/health-readiness-single-source.test.ts`。
491
+ - **配置接力位 `configHandoff`(7.121.0+,只在 `/health`,不上心跳)**:`{ state: "missing", version, since }`,**在场 ⇔ 部署声明了耐久接力(`CONFIG_LKG_DURABLE=true`
492
+ ∧ LKG 启用)∧ 应用着的那一代没在下一次起服将用的凭证身份的 LKG 桶里确认耐久落盘** —— 中心断着时重启按**此刻的凭证身份**选 LKG 桶,那只桶里没有这一代就回到
493
+ 别的配置。三种形:LKG 发布失败(保留候选、启动期技能债)、部分应用(某组被整拒、其余组已热应用,LKG 停在上一个完整世代)、凭据换了人(凭证文件换成别的身份,
494
+ 新身份的桶里还没有这一代)。在那只身份的桶里确认即缺席。**没有耐久声明的部署恒缺席**:那里「重启靠中心重拉」是部署形本身(配置时声明的事实),不是事故;没接中心 / 本地腿
495
+ 也缺席。`version` = 应用着的那一代,`since` = 这一代开始没有确认接力的时刻。与 `restart.withheld`(要重启但扣着)是两个事实两张脸,可同在。7.120.x 的 `modelPlaneDeferred` 已删。
496
+ - **自报位(7.121.0+)**:`/health` 恒带三键 `auth` / `operator` / `center`(缺席 = 老引擎),给壳在**带凭据之前**判出路用,
497
+ 全部是装配事实的投影、不是另立的规则:`auth = { required, kinds, identityAttach }` —— `required` = 全局凭据门武装(配了
498
+ `SERVICE_AUTH_TOKEN(S)`),`kinds` ⊆ `service_token` / `sso`(装了鉴权桥:登记中心的登录令牌)/ `direct_door_jwt`(直连门),
499
+ `identityAttach` = `available`(kinds 里有 `sso` 或 `direct_door_jwt`)/ `unavailable`;`operator` = `present`(`OPERATOR_PRINCIPALS`
500
+ 非空)/ `absent`;`center = { issuer?, webBaseUrl?, sessionsVisible }` —— `issuer` = 鉴权桥的 issuer(`AUTH_BRIDGE_ISSUER`,缺席回退
501
+ `SEMA_REGISTRY_URL`;没有桥 ⇒ 缺席),`webBaseUrl` = `SEMA_REGISTRY_WEB_URL` 原样(缺席即缺席),`sessionsVisible` = `true` /
502
+ `false` / `"unknown"`:中心车道配齐 ∧ `FLEET_ADVERTISE_ADDRESS` 在 ∧ 鉴权桥在 ∧ 会话枚举面在,缺一 ⇒ `false`;齐了而多租户 ⇒ `true`,
503
+ 单用户 ⇒ `"unknown"`(一条会话归谁看提交它的请求带没带身份)。两只 URL 过展示面的同一条脱敏律(凭据 / query 值不上,尾斜杠折掉)。
504
+ ⚠️ 无鉴权面:令牌、运维名单、worker 名、中心凭证都**不上**。钉:`test/health-self-report.test.ts`。
491
505
  - 模型拒因(**7.103.0+**,S-768):`/health.modelRefusals` 与 fleet 心跳 `modelRefusals` 同一只取值口、同一个对象(形 = settings-schema
492
506
  6.2.0 `ModelRefusalsSignal`:`entries` = 目录条目名 → 拒因码,`plane` = 模型面组拒码;值只码)。描述最近一次被判的中心候选目录;
493
507
  没有可报的拒 ⇒ 键缺席。名字是秘密形 / 空串 / `__proto__` 的不可用条目不上这两处(出口过名筛;引擎内部照旧拒它)。消费方读 `version`
@@ -657,10 +671,21 @@ SEMA_REGISTRY_TOKEN=<SERVICE_PULL_TOKEN 的值> # 取自配置控制面主机
657
671
  # limits / 审批名单 …)在新身份第一份候选到达那一拍换代。「换账号即降档 + 当场拉取」候 S-667(不在 7.100.0)。
658
672
  # 用户 JWT 形(CLI 登录)⇒ skill 正文 / prompt 产物走 /api/v1/me/…;盘上 LKG 与 prompt 状态按「worker + 凭证身份」分箱。
659
673
  SEMA_REGISTRY_DRY_RUN=true # 安全灰度:只 LOG 中心配置 vs env 推导的差异,不 apply
674
+ # SEMA_REGISTRY_LIVE_URL=http://<中心活性入口>:3100 # 7.121.0:fleet **活性组**(心跳 / 注销、服务租约 —— settings-schema FLEET_LIVE_PATHS)打的中心基址。
675
+ # 给「管理面在就绪门后、活性面另走一条不看就绪的入口」的部署(k8s:第二只 Service 带
676
+ # publishNotReadyAddresses,Ingress 的活性规则指它)。缺席 = SEMA_REGISTRY_URL。用量、配额租约、
677
+ # 拉配置始终打 SEMA_REGISTRY_URL。中心基址三席(SEMA_REGISTRY_URL / _LIVE_URL / _WEB_URL)同一条判据:
678
+ # 可解析的 http(s) URL,不带凭据 / query / fragment(基址后面要拼路径),坏形拒启(没接中心也拒)。
679
+ # 拒句只点名规则,不回显 scheme:不是合格 http(s) 形的值(`host:8080`、`<令牌>:<口令>@host`、`ftp://…`)
680
+ # 整只成 `«redacted:url»`;合格 http(s) 形只到 `scheme://host[:port]`,后面的凭据 / 路径 / query / fragment 一律 `/…`。
681
+ # SEMA_REGISTRY_WEB_URL=https://<中心 web 面> # 7.121.0:这个中心 **web 面**的固定基址,引擎原样转述到 `/health` 的 `center.webBaseUrl`
682
+ # (壳按它拼会话深链,路径怎么拼归壳)。缺席即缺席,引擎不拼不猜。形判同上(三席一条判据);
683
+ # 只在接了中心(SEMA_REGISTRY_URL)时挂。
660
684
  SEMA_REGISTRY_WORKER=<worker名> # 可选:拉取 /effective?worker=<名> 取该 worker 的 roster(reconciler 按 worker 注);不设=全局 roster(向后兼容)
661
685
  FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启公告环(announce/heartbeat/注销:在中心注册成可路由实例)。
662
- # 必须是可解析的 http(s) base URL——host:port 等坏形 5.13.0 起 boot 响亮拒
663
- # (此前静默每拍 announce 400,worker 永不注册)。
686
+ # 必须是可解析的 http(s) base URL、不带 userinfo / query / fragment(与中心基址三席同一条判据)——
687
+ # host:port 等坏形 boot 响亮拒(拒启码 config.fleet_advertise_address_invalid;
688
+ # 7.121.0 起无条件解析,没接中心也拒;空串 = 未设)。公告环、成员围栏判官、/health.center 都读这一份。
664
689
  # 🔴 7.106.0 起(S-866 ④)用量批报**不再**看本键:中心三件(URL + 凭证 + SEMA_REGISTRY_WORKER)
665
690
  # 配齐即每 60s 向中心 POST /api/fleet/usage(不公告的引擎也报用量;中心据此对租约单调对账)。
666
691
  # 不想报就不配中心三件。caps `centerWiring.usageReport` 可读。
@@ -671,6 +696,30 @@ FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启公
671
696
  # ⇒ true,单用户 ⇒ false;显式值压过派生。布尔八词,词表外拒启。409 not-lease 与耗尽 429 不受它管。
672
697
  # false = 7.105.0 及以前的 AP 姿态(照旧放行,warn fleet_lease_*_fail_open + 计数)。
673
698
  # 生效值 caps `centerWiring.failMode`;只在装了租约管理器的部署上有意义(lease:"absent" 不参与)。
699
+ # FLEET_GENERATION=7 # 7.121.0:fleet 成员的代际(配置渲染器写,手配只在你自己就是渲染器时)。规范十进制串(空串 / 前导零 /
700
+ # 符号 / 空白拒启);缺席 = 非 fleet 成员(单机形:不续服务租约、不受围栏)。回声到心跳与 /health 的
701
+ # generation(连同 leaseState / leaseUntil);排空口按它比 expectedGeneration。
702
+ # 服务租约只在中心于心跳答复里宣告 capabilities.servingLease 之后生效;没宣告 ⇒ leaseState none,
703
+ # 零续租零 503;位撤掉再宣告不重置 T_max 的钟(最后一次授予的到期 / 本进程第一次见到位的时刻照算)。
704
+ # 🔴 设了它就必须配齐中心三件(URL + 凭证 + SEMA_REGISTRY_WORKER)与 FLEET_ADVERTISE_ADDRESS
705
+ # (不公告就听不到能力位),缺一 ⇒ 起服拒启 config.fleet_member_unfenced —— 成员不许悄悄脱离围栏。
706
+ # 续租打 POST /api/fleet/live/serving-lease(契约 FLEET_PATHS.servingLease;活性基址见 SEMA_REGISTRY_LIVE_URL)。
707
+ # 心跳答复体读不出 / 不是 JSON 对象 = 没有能力读数(位与租约钟原样,warn fleet_announce_reply_unreadable)。
708
+ # FLEET_SCOPE=acme # 7.121.0:本引擎所在的**租户 scope**(配置渲染器为多租引擎写 = 它的 per-worker 令牌绑的 scope)。缺席 =
709
+ # 缺省 scope global;在场必须是 scope id 形(小写字母 / 数字 / 内部连字符,1–64;空串拒启
710
+ # config.fleet_scope_invalid)。与 FLEET_GENERATION 不联动。只进排空口的身份比对:请求的 scope 与它比,
711
+ # 请求不带 scope 读成 global。续租体与心跳不带它(scope 由中心凭证绑定)。
712
+ # FLEET_LEASE_OUTAGE_MAX_MS=86400000 # 7.121.0:一台实例**活着而不服务的最长时长**(缺省 24 h;< 60000 / 0 / 非整数拒启)。两处读它:
713
+ # ① 服务租约中心不可达的容忍上限 —— 租约过期(中心不可达 / 答 unknown)后照常服务,过期满这么久 ⇒
714
+ # 新工作 503 state.lease_expired(Retry-After 60;取消 / HITL 应答 / 续跑 / 读面照放行,在飞不动,
715
+ # /readyz 不翻);中心恢复授予即恢复。从没被授予过的,从本进程第一次见到能力位起算。
716
+ # ② 7.122.0 起排空口 POST /v1/admin/drain 的期限上界:deadlineAt 离此刻超过它 ⇒ 400
717
+ # request.field_invalid(非 fleet 成员同样生效 —— 这一席无条件解析)。
718
+ # 中心以正面证据拒(被取代 / 已退役)不受它管:当拍排空、到期自停,数据根记一只
719
+ # fleet-serving-lease-denied.json,同 worker 同代际重启后仍记住、第一条心跳就是排空态(换代由中心做,
720
+ # 不要手删)。那只文件写不进(数据根只读 / 盘满)⇒ 本进程照拒照停,但 error 行
721
+ # fleet_serving_lease_denial_not_persisted + fail-open 计数 server.fleet.denied-marker-unpersisted:
722
+ # 重启后不记得,在第一次续租答复之前会接单(中心那时再拒)。
674
723
  ```
675
724
  - 中心**空/未发布** → `applyEffective` 回落 env + 内建 teams 并 warn `config_center_unpublished`,**不影响在跑的服务**(接了也安全)。
676
725
  - **逐条容错**(S-704 ②,7.101.0 起):目录里一条条目不合形(如 `contextWindow:"abc"`)⇒ **只丢那一条**(warn `config_entry_dropped`,
@@ -902,6 +951,7 @@ APPROVAL_GRANT='mcp__figma__*,a2a__planner__*,Bash(git:*)' # 逗号分隔的 a
902
951
  leader 没有请求体 —— 运维为自己接的服务器、自家信得过的命令,在这里写一次。
903
952
  - **值形(与引擎的文法相同)**:工具名、`mcp__<server>__*` 或两段形 `mcp__<server>`、`a2a__<peer>__*`、`Bash(<命令>)` / `Bash(<前缀>:*)` 命令规则、
904
953
  `Read(//<dir>/**)` 读目录。`auto` 模式下交出任意代码的条目(整只 shell、解释器 / 启动器前缀)交分类器判(引擎的剔除表)。
954
+ 命令规则 `Bash(<命令>)` 按命令判每一只跑 shell 命令的工具(`Bash` 与 `Monitor`;7.122.0 起);`Monitor(<命令>)` 不是规则拼法(引擎读成空 ⇒ 启动拒,见下)。
905
955
  坏值 ⇒ **启动拒**(句子点名那一条、引擎给的原因词、怎么写):引擎读成空的条目(`*`、`mcp__*`、写路径形 `Edit(src/**)`、域名形、带空白的名字;
906
956
  7.118.0 起条目名也过引擎的判官 —— 退役名 / 无前缀短名 / 协议名服务器段铸不出读成 `tool_name`,句子带现名)、工具段为空的协议名(`mcp__srv__`)。
907
957
  内容形里带逗号的规则写不进(这张表的分隔符就是逗号)。
@@ -1027,7 +1077,7 @@ TASK_WRITE_FACE_ESCALATION=allow # 词表 allow|refuse;坏词启动期响亮
1027
1077
  - **它管什么**:`permissionMode:"bypassPermissions"` 派生 `TaskSpec.writeFace:"open"` ——
1028
1078
  也就是说**一个调用方**可以掀掉写围栏(任务根之外也能写)。而引擎的解析序在普通部署上是
1029
1079
  **任务席赢部署席**,引擎里唯一压得住它的两形(org 治理 / 只读 verifier 挂载)在本仓一个结构上不可达、
1030
- 一个要靠 `AUTONOMY=read-only|plan`(那是**整条 run 禁写**,不是等比例旋钮)。⇒ 天花板由 **server 在
1080
+ 一个要靠 `AUTONOMY=read-only`(那是**整条 run 禁写**,不是等比例旋钮)。⇒ 天花板由 **server 在
1031
1081
  受理请求时**执行:不给的时候 **422 响亮拒**(`errorCode: request.host_posture_refused`),而**不是**
1032
1082
  被静默钳回 roots(静默钳是本仓明令要修掉的失败形:调用方明说「别拦我」,却撞上一道它掀不掉
1033
1083
  也看不见的墙)。
@@ -1062,10 +1112,13 @@ TASK_WRITE_FACE_ESCALATION=allow # 词表 allow|refuse;坏词启动期响亮
1062
1112
  上层折成 `409 resume_blocked_by_policy`(`blockedBy: request.host_posture_refused`)。
1063
1113
  运维的解法是 `TASK_WRITE_FACE_ESCALATION=allow`。
1064
1114
  - **能力面披露位**:`GET /v1/capabilities` 的 `taskWriteFaceOpen`(`false` ⇒ 发那个键会拿 422;或 —— 在
1065
- `AUTONOMY=read-only|plan` 的部署上(7.96.0 起)—— 受理但写面被钳回 roots)——壳不必靠踩一次才学会。
1066
- - **`AUTONOMY=read-only|plan` 一行未动**。⚠️ 两者叠加时**本门先说话**:托管形 + `AUTONOMY=read-only`
1115
+ `AUTONOMY=read-only` 的部署上(7.96.0 起)受理但写面被钳回 roots,在 `AUTONOMY=plan` 的部署上(7.122.0 起)受理但模式词
1116
+ 在提交入口被钳成 `plan`)——壳不必靠踩一次才学会。
1117
+ - **`AUTONOMY=read-only` 一行未动**。⚠️ 两者叠加时**本门先说话**:托管形 + `AUTONOMY=read-only`
1067
1118
  + `bypassPermissions` 的请求现在是 422,而 7.94 及以前它会跑起来(写被 autonomy 禁掉)。
1068
1119
  要保留旧结局就 `TASK_WRITE_FACE_ESCALATION=allow`(写面仍被 autonomy 压回 roots)。
1120
+ `AUTONOMY=plan`(7.122.0 起)反过来:它是提交入口的模式词地板,先于本门 —— 托管形 + `AUTONOMY=plan` + `bypassPermissions` 的请求
1121
+ 受理、在 plan 里起步(这一跑的模式词是 `plan`,写面不开,本门无话可说)。
1069
1122
 
1070
1123
  - **MCP 撤销腿(7.39 起)**:MCP 服务器 mount 仍是 restart-to-apply(新增/改址要
1071
1124
  restart 才生效),但**从 enabled 集删除一个服务器即时生效**——refresh 拍收货后,被删服务器的
@@ -1170,10 +1223,13 @@ ENGINE_ORPHAN_LINGER_MS=2000 # 可选,默认 2s:成孤儿之后还要静多久
1170
1223
  生效值**为准:`GET /v1/config/catalog` 里 `DRAIN_GRACE_MS` 那一行的 `effectiveValue` 就是本进程真在用的
1171
1224
  数(它读的是活 config,不是这份文档里的默认值),启动期的夹取/警告行同源。本文档其余各处谈这条排空腿
1172
1225
  时都不带数字,数字只有这一处 —— 照抄默认值去算壳形预算会差一个数量级。
1173
- - **排空期的第二发信号(`DRAIN_SECOND_SIGNAL_MIN_GAP_MS`,缺省 `250`,7.102.0+)**:排空开始后再来一发 `SIGTERM` /
1174
- `SIGINT`,只有与排空起点相隔 ≥ 这个毫秒数才算「别等了,硬停」;更近的是同一拍被投递了两遍(壳心跳误判、编排器
1175
- 重试),打一行 `drain_duplicate_signal_ignored`(带 `signal` / `sinceFirstMs` / `minGapMs`)后照常排空。真要立刻
1176
- 停:隔过这个间隔再发一次,或 `SIGKILL`;设 `0` 回到「每个第二发都硬停」的旧行为。合法域 `[0, 10000]` 整数毫秒
1226
+ - **排空期的第二发信号(`DRAIN_SECOND_SIGNAL_MIN_GAP_MS`,缺省 `250`,7.102.0+)**:排空中再来一发 `SIGTERM` /
1227
+ `SIGINT`,只有与**本次排空的第一发停止信号**相隔 ≥ 这个毫秒数才算「别等了,硬停」;更近的是同一拍被投递了两遍(壳心跳误判、
1228
+ 编排器重试),打一行 `drain_duplicate_signal_ignored`(带 `signal` / `sinceFirstMs` / `minGapMs`)后照常排空。SIGTERM 进的排空,
1229
+ 那一发就是第一发。经排空口 `POST /v1/admin/drain`(或服务租约被拒、附着租约自退)进的排空还没有停止信号:到达的第一发被**吸收**
1230
+ (期限仍是入口定死的那一个,info `drain_signal_after_request_drain_ignored`),再隔 ≥ 这个毫秒数的一发才硬停(7.121.0 起;
1231
+ 修前那一发被读成第二发)。k8s 例:`preStop` 调排空口 → kubelet 发一发 SIGTERM(吸收)→ `terminationGracePeriodSeconds` 到点 SIGKILL。
1232
+ 真要立刻停:隔过这个间隔再发一次,或 `SIGKILL`;设 `0` 回到「每个第二发都硬停」的旧行为。合法域 `[0, 10000]` 整数毫秒
1177
1233
  (上界 = `DRAIN_GRACE_MS` 的地板),坏值拒启。不在排空时 `SIGINT` 仍是立即硬停。
1178
1234
  - **自退留痕 `<数据根>/exit-last.json`**:自退在 **drain 开始那一刻**就把判据写进数据根(`orphan` /
1179
1235
  `reason` / `idleForMs` / `attached` / `inflight` / `lingerMs` + `version` / `at` / `pid`)。壳的 exit
@@ -1404,6 +1460,7 @@ bake-runner 要用 ⇒ `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW` 点名放回。
1404
1460
  | `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` |
1405
1461
  | `repo` / `council` / `debate` | — | `repo` 为 `code-review`/`scan` 必填;`council`/`debate` 仅 `code-review`,见 §5 |
1406
1462
  | `systemPrompt` / `appendSystemPrompt` | — | 调用方自带的系统提示 / 追加段(各 ≤16384 字符,超限 400 `request.field_invalid`;`appendSystemPrompt` 必须非空串)—— 任务自带时优先于角色 / 全局缺省(见 §模型角色) |
1463
+ | `outputStyle` / `settings.outputStyle` | — | 输出风格**两个座位、两件事**:风格的**正文**(指南全文)走 `settings.outputStyle`(折进系统提示的追加段);**每轮一句**走顶层 `outputStyle: { name, turnReminder? }`(7.121.0+,能力位 `outputStyle`)—— 原样交引擎,引擎每个回合用自己的标记帧说「`<name> output style is active. <turnReminder>`」(`turnReminder` 缺席用引擎缺省句),流上回声 `steering_injected` `source:"output_style"`。只在非缺省风格时声明顶层键;它的值形由引擎判(`name` 非空、≤256 字;`turnReminder` 在场即非空串;不许别的键),坏值这一轮终态 `failed`、码 `config.output_style_invalid` |
1407
1464
  | `model` | — | 本次用哪个模型:只能点服务端**已配置目录**里的名字 / 档位词 / 模型 id(目录见 `GET /v1/models`;请求体带不进新模型)。未知 ⇒ 400 `request.unknown_reference`(带 `available` 列表);目录里有、但本部署服务不了 ⇒ 400 `request.model_unavailable`;空串 / 非字符串 ⇒ 400 `request.field_invalid` |
1408
1465
  | ~~tools~~ | 🚫 | **不是受理键**(传了 ⇒ 400 `request.body_shape`,`unknownKeys` 点名)——工具面走场景装配;按请求只能**减**:`excludeTools` / `settings.permissions` 的 deny · ask(`allow` 7.115.0 起是 allow 规则:给出「有人表态」,回答没人表态的服务器工具 / A2A 调用;不加工具、不放掉任何一层自己的问) |
1409
1466
 
@@ -1530,12 +1587,12 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
1530
1587
  | `POST /v1/approvals/<sessionId>/decide` `{"decision":"approve"|"deny","reason":"…"}` | 批/否并恢复挂起任务(CAS,重复决议 409);**仅 operator**(非 operator → 403)。5.0.0 起旧轮询腿 `POST /v1/approvals/<id>` 已退役 |
1531
1588
  | `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) |
1532
1589
  | `GET /v1/capabilities/scenarios/<name>` | 单个场景的只读详情:工具面、提示词概览、**本部署现在跑不跑得动**(见下) |
1533
- | `POST /v1/admin/drain` `{"reason":"…"}` | **停机因由显式声明**(7.35.0+;operator-only —— principal 须在 `OPERATOR_PRINCIPALS` 内,空名单 = 谁都不是 operator ⇒ 恒 403)。设计给编排层(k8s `preStop` / systemd `ExecStop` 等)在发 SIGTERM **之前**调用声明因由;⚠️ **如实**:本仓四条消费树(server/cli/sdk/web-admin)与编排层脚本树 `sema-deploy` 里对它零调用,`sema-comms audits/.md:213` N-18 已把它书面登记为 declined(cli 判定这是 operator 面,不是壳自动打的口)—— 今天它是给**运维手动 curl**(或运维自己写的编排脚本)用的口,不是任何一个内置壳自动调用的。调用后 server 记进程内 `drainState.reason`,draining 期由 `/health` 的 `drainReason` 与 503 `draining` 体的 `reason` 透出(未声明 = 两面键缺席)。声明是**覆盖式**、无 TTL、随进程重启清零 ⇒ 每次停机流程内都要重新声明。`reason` 非空、trim 后 ≤256 字符,坏值 400。无 drain 面装配(嵌入宿主 / 测试)上声明无处可落 ⇒ 501 `capability.drain_state_required`。🔴 **写口是特权的,读口不是**:`/health` 免鉴权(见下一行)且缺省全网卡监听 —— 任何能连到这个端口的人都读得到你写的那句话。**别在 reason 里写工单号、内部主机名或任何内部标识**。 |
1590
+ | `POST /v1/admin/drain` `{"actionId":"…","worker":"…","deadlineAt":…}` | **排空口**(7.121.0 起体 = settings-schema `DrainRequest`;operator-only —— principal 须在 `OPERATOR_PRINCIPALS` 内,空名单 ⇒ 501)。体 `{ actionId, worker, deadlineAt, scope?, expectedGeneration?, instanceId?, term? }`:`actionId` 幂等键(人手形自取,如 `manual:<uuid>`)、`worker` 本引擎的 worker 名(`SEMA_REGISTRY_WORKER`)、`deadlineAt` 到期自停的 epoch ms、`scope` 本引擎的租户 scope(`FLEET_SCOPE`;不带 = 缺省 scope `global`,与没设 `FLEET_SCOPE` 的引擎相符 —— 多租引擎要带它自己的)、`expectedGeneration` fleet 成员的代际(`FLEET_GENERATION`;非成员不带)、`instanceId` 本进程身份(`GET /health` 读;不带则不比)。身份逐字段比(`worker` / `expectedGeneration` 缺席对缺席),不符 ⇒ `409 conflict.fleet_identity`(什么都不变);首次 ⇒ `200 {actionId, since, deadlineAt, state:"draining"}` 并**进入排空**(拒新提交、本副本可抢占的在飞腿当拍让出、活的审批卡改路耐久停驻、在飞归零或到期限即停);再来 ⇒ `200 … state:"already_draining"` 回显第一次的 `since` / `deadlineAt`,不重置。体坏 ⇒ `400 request.field_invalid`;`deadlineAt` 离本引擎此刻超过 `FLEET_LEASE_OUTAGE_MAX_MS`(缺省 24 h,一台实例活着而不服务的最长时长;7.122.0 起)⇒ 同码 400(什么都不变;拒句点名字段与上界的值;只在这一发会进入排空时判 —— 已在排空或硬停已在进行时任何重发都回 `already_draining`),已过的期限照收(进入即到期);嵌入宿主(无收尾链)⇒ `501 capability.drain_state_required`。没有 worker 身份的引擎(没配 `SEMA_REGISTRY_WORKER`)恒 409 —— 用 SIGTERM。经本口进的排空之后,平台随后发的第一发 SIGTERM 被吸收、不缩短期限(第二发判据见「排空期的第二发信号」)。7.120.x 的 `{"reason":"…"}` 声明形与 `/health.drainReason` 已退役。 |
1534
1591
  | `GET /v1/config/catalog` | **配置目录自描述**(operator-only —— `explicitOperatorOk`,空名单=谁都不是)。逐 env 键一行:`{name, domain, type, semantics, writeLanes, effectiveLane, envHeld, envTiming, staticDefault?/derivedDefaultNote?, effectiveValue, valueClass, danger, center?{domain,path,precedence,timing,restartSlice?,managedNow,effectiveNow}}`(🔴 7.106.0 起 `center.domainExists` 删除:座位在加载期对契约 `DOMAIN_SCHEMAS` 核过域 + 路径,核不过即拒启 ⇒ 有 `center` 块 ⇔ 中心可写;readFace 四键 / `PERMISSIONS_DISABLE_AUTO_MODE` / `MODEL_DEGRADE_ON` / `_REACTIVE` 七行的座位随之删除,S-866 ⑤a);顶层带 `restartSlices`(重启片闭集)与 `centerDomains`(settings-schema 现行域表)。**两轴不混**:`writeLanes`=谁能写,`effectiveLane`=现在谁在生效(env-wins 族 env 占位 ⇒ center 腿 `effectiveNow:false`)。secret 类值**永不回传**(只回在场位;opaque 回在场位+长度;URL 剥 userinfo)。完备性由仓内 AST 双向对账门执法(分母=全 `src/` 生产 env 读取点)。旋钮 `CONFIG_CATALOG_ENABLED=false`(合规部署)⇒ 路径 404 + 能力位 `configCatalog:false`。响应 `Cache-Control: no-store`。**7.57.0 起**:`enum` 行的 `effectiveValue` **恒落在该行自己的 `enumValues` 闭集内**(仓内机器门执法)——此前 `SESSION_BACKEND` 回的是**内部标签** `tidb`(自 5.0.0 起是**退役公名**,写回去 boot 拒),现归一成公名 `mysql`;`MODEL_DEFAULT_THINKING` 补上一直被解析器接受、却漏在目录闭集外的 `xhigh`,并改为回**实算值**(`off`/未设 ⇒ `null`)而不是 env 原文。 |
1535
1592
  | `POST /v1/admin/config/refresh` | **配置手动刷新**(operator-only,与上一行同门:空 `OPERATOR_PRINCIPALS` ⇒ 恒 403)。改完中心配置 / `config.d` 之后不想等 60s 轮询就调它:触发**一次既有**的 refresh 拍(在飞则汇入那一拍,绝不并发双拍),返回时该拍已落地。200 体 `{triggered:true,targetVersion,appliedVersion}` —— 两个世代号与 `/health` 的 `configTargetVersion`/`configAppliedVersion` 同源。本部署没有配置管道(纯 env worker:既没配 `CONFIG_CENTER`、也不是 `CONFIG_PROVIDER=local`)⇒ 409 `config.refresh_unavailable` 指路,不会回一个「触发了但什么都没发生」的 200。 |
1536
1593
  | `GET /health` | 健康(无需鉴权)。**7.67.0+** 恒带 `startedAt`(引擎进程起点 epoch ms,一次铸;与 `pid` 并列的换代锚——值变了就是换了一条命)。7.37.0+ 配置管道在场时带世代账键:`configTargetVersion`(本副本**最后见到**的配置版本,身份标)、`configAppliedVersion`(最后一次真落地的版本)、`configTargetOrdinal`/`configAppliedOrdinal`(7.38.0-rc.2 起,**收货序数**——`CONFIG_PROVIDER=local` 下 version 是内容哈希不承诺序,「改了没生效」看序数对不对齐)、`configApplyStaleMs`(target≠applied **持续**时长;收敛时键缺席)—— 编排器据此摘掉「配置持续落后」的副本 |
1537
1594
  | `GET /v1/sessions/:id/background` · `POST /v1/sessions/:id/background/stop` `{"includeRetained":true\|false}` | **会话后台任务的读口与停止口**(**7.102.0+**,S-745;能力位 `background.listFace` / `background.exitFaces`,老 server 404)。读口列**本会话**、**本副本**上的后台任务行(`{ rows: [{ id, description?, type, status, createdAt, sessionScoped, retained? }] }`,`retained:true` = 声明了保留的活服务,正常退出不会停它);停止口「软 → 等一次 `HOST_BG_KILL_GRACE_MS` → 硬」收掉本会话的后台 shell(SIGTERM 处理器先有一次宽限跑完,之后组 SIGKILL,屏蔽 SIGTERM 的循环也活不过去;有后台行时应答慢一次宽限)并逐字返回受据 `{ receipts: [{ id, outcome }] }`:`killed` / `already_gone` / `retained_skipped`(`includeRetained:false` 留下的活服务)/ `no_pgid`(远端 / 沙箱车道没有本机进程组可发,已派普通 kill,「可能还在跑」;host 车道的 `already_gone` 只由观察得出 —— 引擎已收割组长或内核答组已空,7.103.0 起,S-769;darwin 上梯子那一枪先落地、引擎还没收割的那一拍如实答 `no_pgid`,判「收掉没有」读进程)。`includeRetained` 必填:`true` = 连活服务一起停(壳的「Exit and stop tasks」),`false` = 活服务照留(`sema kill`)。鉴权 = 会话属主或显式 operator(未知与他人同 404);停止口在无凭据写门内(没配 service 凭证且未开 `ALLOW_UNAUTHED_WRITES` ⇒ 503 `auth.service_token_required`)。`DELETE /v1/sessions/:id` 走同一只收敛(`includeRetained:false`);run 取消(Esc)**不**收后台 shell。契约 §16 |
1538
- | `GET /readyz` | **就绪探针**(**7.102.0+**,无需鉴权)。就绪 ⇒ `200 {"ready":true}`;未就绪 ⇒ `503 {"ready":false,"reasons":[…]}` + `Retry-After: 5`,`reasons` ⊆ {`draining`, `model_roster_pending`}(按优先序)。与 `/health` 的 `ready` / `readyReason` / `reasons` 同一只判;库连不上**不算**未就绪(`storeLive` 只在 `/health` 披露)。给只认状态码的**摘流**探针用(k8s `readinessProbe.httpGet`、LB 健康检查;重启语义的 `HEALTHCHECK` / `livenessProbe` 留在 `/health`),见 §0「探针」 |
1595
+ | `GET /readyz` | **就绪探针**(**7.102.0+**,无需鉴权;7.121.0 起是唯一的探针路径)。就绪 ⇒ `200 {"ready":true}`;未就绪 ⇒ `503 {"ready":false,"reasons":[…]}` + `Retry-After: 5`,`reasons` ⊆ {`draining`, `model_roster_pending`}(按优先序)。与 `/health` 的 `ready` / `readyReason` / `reasons` 同一只判;库连不上**不算**未就绪(`storeLive` 只在 `/health` 披露)。给只认状态码的**摘流**探针用(k8s `readinessProbe.httpGet`、LB 健康检查、镜像 `HEALTHCHECK`);存活探针用 TCP 探端口,不打 HTTP,见 §0「探针」 |
1539
1596
  | `GET /metrics` | Prometheus 指标(有 token 时需带) |
1540
1597
 
1541
1598
  > **场景可用性(7.7.0 起)**:场景详情里 `enabled` 与 `available` 是**两件事**。`enabled` = 这条场景
@@ -2020,11 +2077,22 @@ GET /v1/capabilities?permissionMode=default
2020
2077
 
2021
2078
  ### 9.4b plan 模式(`permissionMode:"plan"`):批准一份计划时,顺带说「解到哪一档」(7.86.0 起)
2022
2079
 
2023
- 只读起步的任务把计划交给人之后停在 `plan_review` 门上。`POST /v1/assistant/tasks/:id/plan_review` 的
2024
- `decision:"approve"` 可携 **`permissionModeAfter`**(闭集 `"default" | "acceptEdits"`,**缺席 ⇒ `"default"`**):
2025
- `acceptEdits` = 继续跑时对自己工作目录内的写不再逐次征询,`default` = 有手但每次写照常征询;`edit` / `reject`
2026
- 的语义是重做计划,只读**原样保留**,在它们上送这一位是 `400`(词表外的值、以及本来就不是只读起步的任务送它,
2027
- 同样是 `400`)。解除会写回这条会话的续跑输入 —— 此后每一次挂起→续跑都沿用解除后的档,不回落只读。
2080
+ **plan 只有一个判官 —— 引擎的 plan 模式(7.122.0 起)**:请求顶层 `permissionMode:"plan"`、`settings.permissions.defaultMode:"plan"`、
2081
+ 运维 `AUTONOMY=plan` 与模型自己调 `EnterPlanMode`,落到的都是同一个模式词。提交之后,一条 run 的模式词只有一个源 —— 存储的提交体顶层
2082
+ `permissionMode`;写它的只有提交入口与下面的 `plan_review` 写回。运维 `AUTONOMY=plan` 是**提交入口的模式词地板**:受理时盖进提交体
2083
+ (请求不带模式词 ⇒ `plan`;带任何词 ⇒ 钳成 `plan`;bundle 的 `defaultMode` 不动,顶层赢),之后不再按段续跑重钳;热改 `AUTONOMY` 只影响之后提交的任务,
2084
+ 在飞的 run 保持它的入口词。工具表与 `default` 同面(写手、委派工具 `Agent` 都在),
2085
+ 写与非只读调用在**调用时**由引擎的 plan 门问(有人可答)/ 拒(无人可答);委派的子代以 plan 为天花板继承同一道门。所以请求体带
2086
+ `agents`(自定义子代定义)的任务进 plan 照常起跑。此前本服务把用户选的 plan 另翻成「只读手 + 剥委派工具」,带 `agents` 的任务因此整轮被引擎拒
2087
+ (`config.agents.no_delegation_tool`);只读手现在只是 `AUTONOMY=read-only` 一个词的事(子代同受只读,委派照挂)。
2088
+
2089
+ plan 起步的任务把计划交给人之后停在 `plan_review` 门上。`POST /v1/assistant/tasks/:id/plan_review` 里把计划**带出 plan** 的决定
2090
+ —— `decision:"approve"`(按原计划开干)与 `decision:"edit"`(按人改过的计划开干;7.122.0 起)—— 可携 **`permissionModeAfter`**
2091
+ (闭集 `"default" | "acceptEdits"`,**缺席 ⇒ `"default"`**):`acceptEdits` = 继续跑时对自己工作目录内的写不再逐次征询,`default` = 有手但每次写照常征询。
2092
+ `reject` 的语义是重做计划,plan **原样保留**,在它上面送这一位是 `400`(判据与引擎同句:只有 `reject` 不出 plan)。词表外的值、以及存储模式词不是 plan
2093
+ 的任务(模型自己调 `EnterPlanMode` 那一形 —— 批准把它送回进 plan 之前的档)送它,同样是 `400`;`AUTONOMY=plan` 下提交的任务存储词就是 plan,照收。
2094
+ 解除会写回这条会话的续跑输入 —— 此后每一次挂起→续跑都沿用解除后的档,不回落 plan(`AUTONOMY=plan` 也不把它拉回:地板只在提交入口盖一次)。
2095
+ 7.122.0 之前 `edit` 之后仍是只读手(模型被告知开干却没有写手),`edit` 上送这一位是 `400`。
2028
2096
  🔴 **这个体是闭集(7.86.0,S-438)**:受理集 = `decision` / `editedPlan` / `reason` / `permissionModeAfter` /
2029
2097
  `boundCallId` 五键,集外键(含定谳前的旧拼法 `nextPermissionMode`)**响亮拒** `400 request.body_shape` +
2030
2098
  机读两桶逐字点名,**不再静默丢** —— 与 `/v1/approvals/:id/decide` 同一只机器、同码同串。
@@ -2110,7 +2178,7 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
2110
2178
  ### 9.6 trace 脱敏的射程:与出站请求体无关
2111
2179
 
2112
2180
  🔴 **脱敏 ≠ 出站控制**:trace 脱敏(账本 / SSE / turns / 审批预览里的 `«redacted»` 标记)只作用于
2113
- **持久化与展示面**——**工具结果发往「该 run 实际选中的模型路由」的请求体是原文**。⚠️ 「实际选中的路由」
2181
+ **读面 / 展示面**(账本 / SSE / turns / 审批预览 / `GET /v1/sessions/:id`);**不碰**引擎会话存档(审计真源:store 里怎么写就怎么回、HTTP 读面才脱敏,之后每一次续轮按原文回送模型路由)与 session-sync 七口 `/v1/sessions/:id/sync/*`(逐字 durable log 出入)——这两面是**原形**——**工具结果发往「该 run 实际选中的模型路由」的请求体是原文**。⚠️ 「实际选中的路由」
2114
2182
  不等于 `MODEL_GATEWAY_BASEURL` 一根:本部署的模型出口至少四条——默认网关(`MODEL_GATEWAY_BASEURL`)、
2115
2183
  备用网关(`MODEL_GATEWAY_FALLBACK_URLS`,failover 拓扑参与)、`provider:"anthropic"` 模型的**独立直连
2116
2184
  路由**(缺省根 `https://api.anthropic.com`,除非显式配 `ANTHROPIC_BASEURL`)、以及配置中心可对**单个
@@ -71,8 +71,8 @@ spec:
71
71
  - { name: K8S_RUNTIME_CLASS, value: {{ .Values.sandbox.runtimeClass }} }
72
72
  - { name: K8S_SANDBOX_IMAGE, value: {{ .Values.sandbox.image | quote }} }
73
73
  - { name: K8S_TOKEN, valueFrom: { secretKeyRef: { name: sema-runner-token-copy, key: token } } }
74
- readinessProbe:
75
- httpGet: { path: /health, port: 8090 }
74
+ readinessProbe: # the only probe path is /readyz (/health is diagnostic, always 200)
75
+ httpGet: { path: /readyz, port: 8090 }
76
76
  initialDelaySeconds: 5
77
77
  periodSeconds: 5
78
78
  ---
@@ -84,8 +84,8 @@ spec:
84
84
  {{- end }}
85
85
  - { name: K8S_SANDBOX_IMAGE, value: {{ .Values.sandbox.image | quote }} }
86
86
  - { name: K8S_TOKEN, valueFrom: { secretKeyRef: { name: sema-runner-token-copy, key: token } } }
87
- readinessProbe:
88
- httpGet: { path: /health, port: 8090 }
87
+ readinessProbe: # the only probe path is /readyz (/health is diagnostic, always 200)
88
+ httpGet: { path: /readyz, port: 8090 }
89
89
  initialDelaySeconds: 5
90
90
  periodSeconds: 5
91
91
  ---
@@ -185,7 +185,7 @@ spec:
185
185
  # 默认=公开池 code-node(小巧 git+node+python;clay 拍 2026-07-13,重任务 per-task profile 按需选)——裸 OS 镜像无 pkg-source hook,别退回
186
186
  - { name: K8S_SANDBOX_IMAGE, value: "docker.io/claybobby/code-node:20260707-749ab84f5fdd-786f5ef10ffd" }
187
187
  - { name: K8S_TOKEN, valueFrom: { secretKeyRef: { name: sema-runner-token-copy, key: token } } }
188
- readinessProbe: { httpGet: { path: /health, port: 8090 }, initialDelaySeconds: 5, periodSeconds: 5 }
188
+ readinessProbe: { httpGet: { path: /readyz, port: 8090 }, initialDelaySeconds: 5, periodSeconds: 5 } # the only probe path (/health is diagnostic)
189
189
  ---
190
190
  apiVersion: v1
191
191
  kind: Service
@@ -7,6 +7,7 @@ import type { Logger } from "./observability/logger.js";
7
7
  import type { Metrics } from "./observability/metrics.js";
8
8
  import { type DenyReason, type VoidReason } from "./approval-deny-reasons.js";
9
9
  import { type ApprovalRevokeFrame } from "./approval-card.js";
10
+ import type { ParkCommitWatch } from "./plugins/park-commit-watch.js";
10
11
  /** 判据表 v2 的一次判定结果(纯数据;执行器按 kind 分派到店面的一次 CAS)。 */
11
12
  export type ReconcileAction = {
12
13
  kind: "bind";
@@ -230,6 +231,13 @@ export interface ApprovalReconcilerDeps {
230
231
  */
231
232
  unattendedPolicy: UnattendedApprovalPolicy;
232
233
  emitRevoke?: ApprovalRevokeEmitter | undefined;
234
+ /**
235
+ * S-1280:park 提交的唯一观察点(`plugins/park-commit-watch.ts`;在场 ⇔ 本部署有 checkpoint 店)。在场 ⇒ 收敛器订阅它:一只审批停车行
236
+ * 提交的那一拍,对**这次调用**的 `PARKING` 行即时跑判据 1(与周期扫同一只执行体),`put` 等它收完才交还 core。缺席 ⇒ 只有周期扫(修前形)。
237
+ */
238
+ parkCommits?: Pick<ParkCommitWatch, "onParkCommitted" | "track"> | undefined;
239
+ /** {@link PARK_COMMIT_BIND_BUDGET_MS} 的注入口 —— 不接 env(不是部署旋钮,是这一拍拖住 core 停驻的上界),可注入只为让判据观测得到。 */
240
+ parkCommitBindBudgetMs?: number | undefined;
233
241
  }
234
242
  /** {@link createApprovalReconciler} 返回的活对象。 */
235
243
  export interface ApprovalReconciler {