@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
@@ -6,6 +6,7 @@ import { buildRevokeFrame } from "./approval-card.js";
6
6
  import { encodeCheckpointScope } from "./security.js";
7
7
  const RECONCILE_STORE_TIMEOUT_MS = 10_000;
8
8
  const RECONCILE_SEGMENT_BUDGET_MS = 30_000;
9
+ const PARK_COMMIT_BIND_BUDGET_MS = 2_000;
9
10
  function withDeadline(op, label, timeoutMs = RECONCILE_STORE_TIMEOUT_MS) {
10
11
  let timer;
11
12
  return Promise.race([
@@ -80,6 +81,13 @@ export function decideReconcileAction(input) {
80
81
  }
81
82
  export function createApprovalReconciler(deps) {
82
83
  const { askStore, checkpoints, runs, logger, metrics, batchLimit, pendingGraceMs, adhocGraceMs, orphanTtlMs, unattendedPolicy, emitRevoke } = deps;
84
+ const warn = (event, fields) => {
85
+ try {
86
+ logger.warn(event, fields);
87
+ }
88
+ catch {
89
+ }
90
+ };
83
91
  const revoke = (row, askIds, reason, nowMs) => {
84
92
  if (askIds.length === 0 || !emitRevoke)
85
93
  return;
@@ -87,39 +95,32 @@ export function createApprovalReconciler(deps) {
87
95
  emitRevoke(buildRevokeFrame(row.batchId, askIds, reason, nowMs, row.taskId), { owner: row.owner, sessionId: row.sessionId, taskId: row.taskId });
88
96
  }
89
97
  catch (err) {
90
- try {
91
- logger.warn("approval_revoke_emit_failed", { batchId: row.batchId, count: askIds.length, err: err instanceof Error ? err.message : String(err) });
92
- }
93
- catch {
94
- }
98
+ warn("approval_revoke_emit_failed", { batchId: row.batchId, count: askIds.length, err: err instanceof Error ? err.message : String(err) });
95
99
  }
96
100
  };
97
- const reconcileOne = async (ask, nowMs) => {
101
+ const bindAtGate = async (ask, nowMs, deadlineMs) => {
102
+ const expired = () => deadlineMs !== undefined && Date.now() >= deadlineMs;
98
103
  const batchRow = await withDeadline(askStore.getBatch(ask.batchId), "getBatch");
99
104
  let batchState = batchRow?.state ?? "MISSING";
100
105
  let batchBoundAskId = batchRow?.boundAskId ?? null;
101
106
  const irreversibleBatch = batchState === "ABORTED" || (batchState === "ROUTING_BOUND" && batchBoundAskId !== null && batchBoundAskId !== ask.askId);
102
107
  let gateMatch;
103
- if (checkpoints && !irreversibleBatch) {
108
+ if (checkpoints && !irreversibleBatch && !expired()) {
104
109
  const candidates = await withDeadline(checkpoints.findCheckpointCandidatesForAsk(encodeCheckpointScope(ask.owner), ask.sessionId, ask.toolCallId, ask.createdAtMs), "findCheckpointCandidatesForAsk");
105
110
  gateMatch = classifyGateMatch(ask, candidates);
106
111
  if (gateMatch.kind === "hash_mismatch") {
107
- try {
108
- logger.warn("approval_reconcile_hash_mismatch", {
109
- askId: ask.askId,
110
- taskId: ask.taskId,
111
- toolCallId: ask.toolCallId,
112
- candidates: gateMatch.candidates,
113
- reason: "ask and checkpoint minted the same bound input under the same (sourceTaskId, toolCallId) identity but the digests differ — " +
114
- "the known causes are deployment-side (a hook/policy mutating an object it still holds a reference to between the two mints, " +
115
- "or a stateful getter returning a fresh value per read), not out-of-band interference; this ask keeps its fail-safe route (no park, no denial)",
116
- });
117
- }
118
- catch {
119
- }
112
+ warn("approval_reconcile_hash_mismatch", {
113
+ askId: ask.askId,
114
+ taskId: ask.taskId,
115
+ toolCallId: ask.toolCallId,
116
+ candidates: gateMatch.candidates,
117
+ reason: "ask and checkpoint minted the same bound input under the same (sourceTaskId, toolCallId) identity but the digests differ — " +
118
+ "the known causes are deployment-side (a hook/policy mutating an object it still holds a reference to between the two mints, " +
119
+ "or a stateful getter returning a fresh value per read), not out-of-band interference; this ask keeps its fail-safe route (no park, no denial)",
120
+ });
120
121
  }
121
122
  const match = gateMatch.kind === "match" ? gateMatch.candidate : undefined;
122
- if (match) {
123
+ if (match && !expired()) {
123
124
  const res = await withDeadline(askStore.bindBatch(ask.batchId, ask.askId, {
124
125
  gateToken: match.token,
125
126
  gateBoundCallId: match.boundCallId,
@@ -127,12 +128,20 @@ export function createApprovalReconciler(deps) {
127
128
  }), "bindBatch");
128
129
  if (res.ok) {
129
130
  revoke(ask, res.voidedSiblings, "superseded_by_park", nowMs);
130
- return { outcome: "parked", match: gateMatch };
131
+ return { bound: true, match: gateMatch };
131
132
  }
132
133
  batchState = res.batchState;
133
134
  batchBoundAskId = res.boundAskId ?? null;
134
135
  }
135
136
  }
137
+ return { bound: false, ...(gateMatch !== undefined ? { match: gateMatch } : {}), batchState, batchBoundAskId, irreversibleBatch };
138
+ };
139
+ const reconcileOne = async (ask, nowMs) => {
140
+ const attempt = await bindAtGate(ask, nowMs);
141
+ if (attempt.bound)
142
+ return { outcome: "parked", match: attempt.match };
143
+ const { batchState, batchBoundAskId, irreversibleBatch } = attempt;
144
+ const gateMatch = attempt.match;
136
145
  const run = runs && !irreversibleBatch ? ((await withDeadline(runs.getRun(ask.taskId), "getRun")) ?? null) : null;
137
146
  const action = decideReconcileAction({ ask, candidates: [], run, batchState, batchBoundAskId, nowMs, adhocGraceMs, orphanTtlMs, allowBind: false });
138
147
  switch (action.kind) {
@@ -149,7 +158,7 @@ export function createApprovalReconciler(deps) {
149
158
  if (!won)
150
159
  return { outcome: "held", match: gateMatch };
151
160
  if (action.reason === VOID_REASONS.ORPHAN_TTL_EXCEEDED) {
152
- logger.warn("approval_ask_orphan_voided", { askId: ask.askId, taskId: ask.taskId, ageMs: nowMs - ask.createdAtMs, ttlMs: orphanTtlMs });
161
+ warn("approval_ask_orphan_voided", { askId: ask.askId, taskId: ask.taskId, ageMs: nowMs - ask.createdAtMs, ttlMs: orphanTtlMs });
153
162
  }
154
163
  revoke(ask, [ask.askId], action.reason === VOID_REASONS.BATCH_BOUND_ELSEWHERE ? "superseded_by_park" : "aborted", nowMs);
155
164
  return { outcome: "voided", match: gateMatch };
@@ -160,6 +169,37 @@ export function createApprovalReconciler(deps) {
160
169
  }
161
170
  }
162
171
  };
172
+ const parkCommits = deps.parkCommits;
173
+ const budgetMs = deps.parkCommitBindBudgetMs ?? PARK_COMMIT_BIND_BUDGET_MS;
174
+ const bindCommittedPark = async (calls) => {
175
+ const deadlineMs = Date.now() + budgetMs;
176
+ const work = (async () => {
177
+ const rows = await askStore.listParkingBySession(calls.sessionId);
178
+ for (const ask of rows) {
179
+ if (!calls.toolCallIds.includes(ask.toolCallId))
180
+ continue;
181
+ if (Date.now() >= deadlineMs) {
182
+ warn("approval_park_commit_bind_failed", { askId: ask.askId, taskId: ask.taskId, err: `park-commit bind budget (${budgetMs}ms) spent before this row`, fallback: "periodic_sweep" });
183
+ return;
184
+ }
185
+ try {
186
+ if ((await bindAtGate(ask, Date.now(), deadlineMs)).bound)
187
+ metrics?.inc("approval_reconciled_total", { outcome: "parked" }, 1);
188
+ }
189
+ catch (err) {
190
+ warn("approval_park_commit_bind_failed", { askId: ask.askId, taskId: ask.taskId, err: err instanceof Error ? err.message : String(err), fallback: "periodic_sweep" });
191
+ }
192
+ }
193
+ })();
194
+ parkCommits?.track(work);
195
+ try {
196
+ await withDeadline(work, "bindCommittedPark", budgetMs);
197
+ }
198
+ catch (err) {
199
+ warn("approval_park_commit_bind_failed", { sessionId: calls.sessionId, err: err instanceof Error ? err.message : String(err), fallback: "periodic_sweep" });
200
+ }
201
+ };
202
+ parkCommits?.onParkCommitted(bindCommittedPark);
163
203
  return {
164
204
  async runOnce(nowMs) {
165
205
  const stats = {
@@ -184,11 +224,7 @@ export function createApprovalReconciler(deps) {
184
224
  for (const ask of parking) {
185
225
  if (Date.now() >= parkingDeadline) {
186
226
  stats.budgetExhausted += 1;
187
- try {
188
- logger.warn("approval_reconcile_budget_exhausted", { segment: "PARKING", processed: stats.parked + stats.denied + stats.voided + stats.held + stats.failed, scanned: stats.scanned, budgetMs: RECONCILE_SEGMENT_BUDGET_MS });
189
- }
190
- catch {
191
- }
227
+ warn("approval_reconcile_budget_exhausted", { segment: "PARKING", processed: stats.parked + stats.denied + stats.voided + stats.held + stats.failed, scanned: stats.scanned, budgetMs: RECONCILE_SEGMENT_BUDGET_MS });
192
228
  break;
193
229
  }
194
230
  if (ask.boundInputHash === null)
@@ -224,11 +260,7 @@ export function createApprovalReconciler(deps) {
224
260
  }
225
261
  catch (err) {
226
262
  stats.failed += 1;
227
- try {
228
- logger.warn("approval_reconcile_row_failed", { askId: ask.askId, taskId: ask.taskId, err: err instanceof Error ? err.message : String(err) });
229
- }
230
- catch {
231
- }
263
+ warn("approval_reconcile_row_failed", { askId: ask.askId, taskId: ask.taskId, err: err instanceof Error ? err.message : String(err) });
232
264
  }
233
265
  }
234
266
  const pending = await withDeadline(askStore.listByState("STREAM_PENDING", batchLimit), "listByState(STREAM_PENDING)");
@@ -236,11 +268,7 @@ export function createApprovalReconciler(deps) {
236
268
  for (const row of pending) {
237
269
  if (Date.now() >= orphanDeadline) {
238
270
  stats.budgetExhausted += 1;
239
- try {
240
- logger.warn("approval_reconcile_budget_exhausted", { segment: "STREAM_PENDING", expired: stats.orphansExpired, scanned: pending.length, budgetMs: RECONCILE_SEGMENT_BUDGET_MS });
241
- }
242
- catch {
243
- }
271
+ warn("approval_reconcile_budget_exhausted", { segment: "STREAM_PENDING", expired: stats.orphansExpired, scanned: pending.length, budgetMs: RECONCILE_SEGMENT_BUDGET_MS });
244
272
  break;
245
273
  }
246
274
  if (row.expiresAtMs + pendingGraceMs >= nowMs)
@@ -254,23 +282,15 @@ export function createApprovalReconciler(deps) {
254
282
  }
255
283
  catch (err) {
256
284
  stats.failed += 1;
257
- try {
258
- logger.warn("approval_orphan_expire_failed", { askId: row.askId, taskId: row.taskId, policy: unattendedPolicy, err: err instanceof Error ? err.message : String(err) });
259
- }
260
- catch {
261
- }
285
+ warn("approval_orphan_expire_failed", { askId: row.askId, taskId: row.taskId, policy: unattendedPolicy, err: err instanceof Error ? err.message : String(err) });
262
286
  }
263
287
  }
264
288
  if (stats.unmatchableNoHash > 0) {
265
- try {
266
- logger.warn("approval_reconcile_rows_unmatchable", {
267
- rows: stats.unmatchableNoHash,
268
- scanned: stats.scanned,
269
- reason: "bound_input_hash is NULL — criterion 1 (identity ∧ hash) cannot match; rows fall through to the run-terminal / adhoc / orphan arms",
270
- });
271
- }
272
- catch {
273
- }
289
+ warn("approval_reconcile_rows_unmatchable", {
290
+ rows: stats.unmatchableNoHash,
291
+ scanned: stats.scanned,
292
+ reason: "bound_input_hash is NULL — criterion 1 (identity ∧ hash) cannot match; rows fall through to the run-terminal / adhoc / orphan arms",
293
+ });
274
294
  }
275
295
  if (stats.parked + stats.denied + stats.voided + stats.orphansExpired > 0) {
276
296
  metrics?.inc("approval_reconciled_total", { outcome: "parked" }, stats.parked);
@@ -15,7 +15,8 @@
15
15
  *
16
16
  * 本模块按**server 侧可知的同三项**算一份「若此刻提交会不会武装」的裁决,供壳(cli `sema doctor permissions`)
17
17
  * 与人自查。三项分别对应三个未武装原因(判序 = 谓词的合取序,先判到的先报):
18
- * · `mode_not_auto` —— 本次意图不是 auto(`?permissionMode=` 查询串给的;缺席 = 按 auto 意图答);
18
+ * · `mode_not_auto` —— 本次意图不是 auto(`?permissionMode=` 查询串给的;缺席 = 按 auto 意图答),或意图经本部署的运维入口地板
19
+ * (`AUTONOMY=plan`)钳过之后不是 auto —— 与受理门同一只钳,提交时那个词到不了引擎;
19
20
  * · `deployment_incapable` —— 装配面没挂 `RunnerDeps.autoMode` 分类器席位(生产装配恒挂;桩/一次性 CLI 不挂);
20
21
  * · `org_denied` —— per-principal caps 的 `autoMode === false` 且本地 deny 未设(center 显式 deny、center
21
22
  * 硬失败的 fail-closed 拒项);
@@ -80,7 +81,7 @@ export declare const AUTO_MODE_UNARMED_REASONS: readonly ["mode_not_auto", "depl
80
81
  export type AutoModeUnarmedReason = (typeof AUTO_MODE_UNARMED_REASONS)[number];
81
82
  /** 锁步表:词表与语义一一对应(加员/删员任一侧不同步 = tsc 红)。语义只供文档/测试,不上 wire。 */
82
83
  export declare const AUTO_MODE_UNARMED_REASON_SEMANTICS: {
83
- readonly mode_not_auto: "本次意图不是 auto(壳发的 permissionMode 不是 \"auto\")";
84
+ readonly mode_not_auto: "本次意图不是 auto(壳发的 permissionMode 不是 \"auto\"),或意图经本部署的运维入口地板(AUTONOMY=plan)钳过之后不是 auto —— 与受理门同一只钳,提交时那个词到不了引擎";
84
85
  readonly deployment_incapable: "装配面没挂 RunnerDeps.autoMode 分类器席位(本部署/本进程形不具备分类器)";
85
86
  readonly org_denied: "per-principal caps.autoMode === false 且本地 deny 未设:center 显式 deny / center 硬失败 fail-closed 拒项";
86
87
  readonly local_denied: "本地 catalog 键 PERMISSIONS_DISABLE_AUTO_MODE=true(center/settings 键名 permissions.disableAutoMode)把 caps.autoMode 折成 false";
@@ -118,11 +119,17 @@ export type AutoModeVerdict = {
118
119
  * 三项合取的 server 侧裁决。`requestedMode` 缺席 = 按 auto 意图答(「若请求会不会武装」);`principal` =
119
120
  * 调用方经门验证的身份(匿名 ⇒ undefined ⇒ center 客户端答 undefined,本地 deny 层照样罩)。
120
121
  *
122
+ * 意图那一项判的是**提交之后到得了引擎的那个词**:本次意图先过本部署的运维入口地板(`floor`,调用方传 `runtime-governance.ts`
123
+ * `autonomyModeFloor(<活 config 的 AUTONOMY>)` —— 与受理门盖章读同一只属主),钳法 = core `clampPermissionMode`(受理门 `stampEntryPermissionMode`
124
+ * 用的同一只钳;地板缺席 ⇒ 原样)。`AUTONOMY=plan` 部署上任何意图都被钳成 `plan` ⇒ `mode_not_auto`(闭集词复用,不另铸)。
125
+ *
121
126
  * 🔴 resolver 抛错**不吞**:合成腿的每一层都自带 fail-closed/缺席臂(center 客户端 catch-all、grant 腿 onFault),
122
127
  * 走到这里还在抛 = 装配缺陷,读面 500 比一句编出来的 reason 诚实。
123
128
  */
124
129
  export declare function judgeAutoModeArming(face: AutoModeFaceDeps | undefined, input: {
125
130
  readonly requestedMode: PermissionMode | undefined;
131
+ /** 本部署此刻的运维入口地板(`autonomyModeFloor(config.autonomy)`;`undefined` = 没有地板)。必填:不传地板不是「没有地板」的合法拼法。 */
132
+ readonly floor: PermissionMode | undefined;
126
133
  readonly principal: string | undefined;
127
134
  /** 本地 `PERMISSIONS_DISABLE_AUTO_MODE`(= catalog/settings 键 `permissions.disableAutoMode`)是否置 true —— caps false 时分词
128
135
  * `local_denied` / `org_denied`(seam ⑤;两者对壳的指路不同:改本机 env vs 找组织)。 */
@@ -1,4 +1,4 @@
1
- import { classifierSeatModel } from "@sema-agent/core";
1
+ import { clampPermissionMode, classifierSeatModel } from "@sema-agent/core";
2
2
  import { resolveMainModel } from "./model-select.js";
3
3
  export function classifierRouteFor(deps) {
4
4
  try {
@@ -11,7 +11,7 @@ export function classifierRouteFor(deps) {
11
11
  }
12
12
  export const AUTO_MODE_UNARMED_REASONS = ["mode_not_auto", "deployment_incapable", "org_denied", "local_denied", "settings_denied", "resolver_fault"];
13
13
  export const AUTO_MODE_UNARMED_REASON_SEMANTICS = {
14
- mode_not_auto: "本次意图不是 auto(壳发的 permissionMode 不是 \"auto\")",
14
+ mode_not_auto: "本次意图不是 auto(壳发的 permissionMode 不是 \"auto\"),或意图经本部署的运维入口地板(AUTONOMY=plan)钳过之后不是 auto —— 与受理门同一只钳,提交时那个词到不了引擎",
15
15
  deployment_incapable: "装配面没挂 RunnerDeps.autoMode 分类器席位(本部署/本进程形不具备分类器)",
16
16
  org_denied: "per-principal caps.autoMode === false 且本地 deny 未设:center 显式 deny / center 硬失败 fail-closed 拒项",
17
17
  local_denied: "本地 catalog 键 PERMISSIONS_DISABLE_AUTO_MODE=true(center/settings 键名 permissions.disableAutoMode)把 caps.autoMode 折成 false",
@@ -21,7 +21,9 @@ export const AUTO_MODE_UNARMED_REASON_SEMANTICS = {
21
21
  export async function judgeAutoModeArming(face, input) {
22
22
  const route = face?.seatMounted ? face.classifierModel?.() : undefined;
23
23
  const model = route !== undefined ? { model: route } : {};
24
- if (input.requestedMode !== undefined && input.requestedMode !== "auto")
24
+ const intent = input.requestedMode ?? "auto";
25
+ const entry = input.floor === undefined ? intent : clampPermissionMode(intent, input.floor).effective;
26
+ if (entry !== "auto")
25
27
  return { armed: false, reason: "mode_not_auto", ...model };
26
28
  if (face === undefined || !face.seatMounted)
27
29
  return { armed: false, reason: "deployment_incapable" };
@@ -370,6 +370,7 @@ async function runLeaderForTrap(rt, brain, models, roles, pricing, trap, cellId,
370
370
  process.env.LEADER_WORKER_BUDGET_USD = String((rt.maxCostUsd ?? 1.0) / 2);
371
371
  const run = createLeaderRunner({
372
372
  brain, models, roles, pricing, e2bApiKey: rt.e2bApiKey, workspace: W,
373
+ autonomy: () => undefined,
373
374
  ...(s1DebugGit() ? { logger: { warn: (m, x) => console.error(`[S1_DEBUG_LEADER warn] ${m}`, x ? JSON.stringify(x).slice(0, 300) : ""), info: (m, x) => console.error(`[S1_DEBUG_LEADER info] ${m}`, x ? JSON.stringify(x).slice(0, 200) : "") } } : {}),
374
375
  });
375
376
  const teamSeedWrite = (trap.seedFiles ?? [])
@@ -14,6 +14,7 @@ import type { UnpricedCeilingVerdict } from "./unpriced-ceiling-verdict.js";
14
14
  import type { ConsolidationSeatWatch } from "./memory-consolidation.js";
15
15
  import type { DegradeRouteWatch } from "../brain.js";
16
16
  import { type EngineRestartSignal } from "../config-center/facade.js";
17
+ import { type ConfigHandoff } from "../fleet/engine-signal.js";
17
18
  import { type ConfigApplyLedgerView } from "../config-center/apply-ledger.js";
18
19
  import type { CenterPullView } from "../config-center/pull-state.js";
19
20
  import type { LimitSync } from "./limit-sync.js";
@@ -108,17 +109,14 @@ export interface ConfigCenterRuntime {
108
109
  onSelfReportChange(listener: (changed: SelfReportChange) => void): void;
109
110
  /** /health.restart 与 fleet 心跳 `restart` 的**同一只**取值口(orchestrator 自动重启信号;settings-schema `RestartSignal` 形)。 */
110
111
  restartState(): EngineRestartSignal | undefined;
112
+ /** `/health.configHandoff` 的取值口(只上 `/health`):在场 ⇔ 部署声明了耐久接力(`CONFIG_LKG_DURABLE` ∧ LKG 启用 —— 判「handoff confirmed」的
113
+ * 同一只谓词)∧ 应用着的那一代 ≠ 最后确认耐久接力的那一代(接力账;合成律 `buildConfigHandoff`)。非耐久部署 / 本地腿 / 没有配置管道 ⇒ 缺席。
114
+ * 两个事实两张脸:「要重启但扣着」归 `restart.withheld`,「声明了耐久接力而这一代没确认」归本键。 */
115
+ configHandoffState(): ConfigHandoff | undefined;
111
116
  /** S-768:`/health.modelRefusals` 与 fleet 心跳 `modelRefusals` 的**同一只**取值口(settings-schema 6.2.0 `ModelRefusalsSignal` 形:
112
117
  * 名 → 码 + 组拒码)。描述**最近一次被判的那份中心候选目录**(每次候选 apply 的报告 `ApplyReport.modelRefusals` 投影;组拒时就是被拒
113
118
  * 的那份 —— 引擎自己的拒因表不换代)。没有可报的拒 / 没有配置管道 / 还没判过候选 ⇒ `undefined`(键缺席)。值变了 ⇒ 通知自报订阅者。 */
114
119
  modelRefusalsState(): ModelRefusalsSignal | undefined;
115
- /** /health.modelPlaneDeferred:被扣着、还没有 next-boot handoff 的候选(codex R18 起的状态机)。S-668 波 0 起
116
- * 模型面不再 defer,键名保留,今天承载的是 durable LKG 发布债与 skills 债(见 `planeDeferredNoHandoff`)。 */
117
- planeDeferredState(): {
118
- version: number;
119
- since: number;
120
- blocked?: string[];
121
- } | undefined;
122
120
  /** [ref] 世代账:/health 的 target/applied/失败持续时长三键 + 诊断端点的 configApply 段(同一份读数)。
123
121
  * `undefined` = 本部署没有配置管道(纯 env worker)⇒ 两个读面的形状**逐字不变**(additive 纪律)。 */
124
122
  configApplyState(): ConfigApplyLedgerView | undefined;
@@ -11,7 +11,7 @@ import { applyCenterPlugins } from "../capabilities/center-plugins.js";
11
11
  import { applyCatalogToSource, withPromptArtifactBackfill } from "../capabilities/center-prompts.js";
12
12
  import { validatePromptsDomain, CORE_ENGINE_VERSION } from "../prompts-domain-validate.js";
13
13
  import { centerScenarios, centerScenarioDetails } from "../capabilities/scenarios.js";
14
- import { configLkgEnabled, isRosterGateArmed } from "../config.js";
14
+ import { isRosterGateArmed } from "../config.js";
15
15
  import { defaultLkgPath, defaultSkillCacheDir, saveLkg, loadLkg, legacyV1LkgPath, migrateV1Lkg } from "../config-lkg.js";
16
16
  import { answerStillCurrent, centerStateBin, credentialIdentityNow, judgeCenterCredentialAtBoot } from "../center-credential.js";
17
17
  import { buildCenterCredentialHealthView, createCenterCredentialRenewer } from "../center-credential-renewer.js";
@@ -20,6 +20,7 @@ import { createConfigProvider, raceBootFetch, serviceConfigSourceKind, BOOT_FETC
20
20
  import { createKeyResolver } from "../key-resolver.js";
21
21
  import { buildSealedKeyPublicFace, ensureSealedKeyStore, reportExecutionPublicKey } from "../sealed-key.js";
22
22
  import { applyEffective, applyCenterReadFace, mutateInPlace, logEffectiveDiff, applyCenterSkills, resolveMcpServers, resolveA2aPeers, restartReasons, modelPlaneChanged, withModelPlaneOf, fullyLandedForLkg, fetchPromptArtifact } from "../config-center/facade.js";
23
+ import { buildConfigHandoff, buildRestartSignal, configHandoffMissing } from "../fleet/engine-signal.js";
23
24
  import { createMcpRevocationLedger, enabledMcpEntries } from "../config-center/mcp-revocation.js";
24
25
  import { SHA256_HEX_RE } from "../digest-form.js";
25
26
  import { BOOT_REJUDGE_OF_REFUSED_GROUP, createConfigApplyLedger, modelPlaneCommitted, refusedGroups } from "../config-center/apply-ledger.js";
@@ -315,9 +316,27 @@ export async function createConfigCenterRuntime(ctx) {
315
316
  }
316
317
  };
317
318
  let bootConfigPending;
318
- const lkgEnabled = configProvider?.kind === "remote" && configLkgEnabled();
319
+ const lkgEnabled = configProvider?.kind === "remote" && config.configLkgEnabled;
319
320
  const lkgPathFor = (identity) => config.configLkgPath ?? defaultLkgPath(config, cc?.worker, identity);
320
321
  const lkgSurvivesRestart = lkgEnabled && process.env.CONFIG_LKG_DURABLE === "true";
322
+ let handoff = {};
323
+ let handoffMissingSince;
324
+ const latchHandoff = () => {
325
+ const bootIdentity = currentIdentity();
326
+ const ledger = { ...handoff, ...(bootIdentity !== undefined ? { bootIdentity } : {}) };
327
+ handoffMissingSince = configHandoffMissing(ledger) ? (handoffMissingSince ?? Date.now()) : undefined;
328
+ return ledger;
329
+ };
330
+ const noteHandoff = (next) => {
331
+ const applied = next.applied ?? handoff.applied;
332
+ const durable = next.durable ?? handoff.durable;
333
+ handoff = { ...(applied !== undefined ? { applied } : {}), ...(durable !== undefined ? { durable } : {}) };
334
+ latchHandoff();
335
+ };
336
+ const markApplied = (gen, version) => {
337
+ applyLedger.applied(gen, version);
338
+ noteHandoff({ applied: version });
339
+ };
321
340
  let lkgBooted = false;
322
341
  let bootLkgCandidate;
323
342
  let lastPrewarmBodiesVerified = false;
@@ -357,6 +376,8 @@ export async function createConfigCenterRuntime(ctx) {
357
376
  }
358
377
  lastPrewarmBodiesVerified = true;
359
378
  await saveLkg(lkgPath, cc?.worker, identity, effectiveObj, etag);
379
+ if (lkgSurvivesRestart)
380
+ noteHandoff({ durable: { version: effectiveObj.version, identity } });
360
381
  return lkgSurvivesRestart;
361
382
  }
362
383
  catch (err) {
@@ -417,7 +438,10 @@ export async function createConfigCenterRuntime(ctx) {
417
438
  recordApply(rep);
418
439
  },
419
440
  });
420
- applyLedger.applied(lkgGen, lkgR.effective.version);
441
+ const lkgIdentity = candidateIdentityOf(lkgR.effective);
442
+ if (lkgSurvivesRestart && lkgIdentity !== undefined)
443
+ noteHandoff({ durable: { version: lkgR.effective.version, identity: lkgIdentity } });
444
+ markApplied(lkgGen, lkgR.effective.version);
421
445
  await adoptCenterPrompts(lkgR.effective, "boot-lkg");
422
446
  const lkgPlane = modelPlaneCommitted(lkgReport);
423
447
  if (lkgPlane)
@@ -485,7 +509,7 @@ export async function createConfigCenterRuntime(ctx) {
485
509
  recordApply(rep);
486
510
  },
487
511
  });
488
- applyLedger.applied(bootGen, r.effective.version);
512
+ markApplied(bootGen, r.effective.version);
489
513
  const bootClean = fullyLandedForLkg(r, bootReport);
490
514
  if (bootClean)
491
515
  ccEtag = r.etag;
@@ -604,9 +628,9 @@ export async function createConfigCenterRuntime(ctx) {
604
628
  onSelfReportChange: (listener) => {
605
629
  selfReportListeners.push(listener);
606
630
  },
607
- restartState: () => pendingRestart,
631
+ restartState: () => buildRestartSignal(pendingRestart, planeDeferredNoHandoff),
632
+ configHandoffState: () => (configProvider === undefined || configProvider.kind === "local" || !lkgSurvivesRestart ? undefined : buildConfigHandoff(latchHandoff(), handoffMissingSince)),
608
633
  modelRefusalsState: () => modelRefusals,
609
- planeDeferredState: () => planeDeferredNoHandoff,
610
634
  configApplyState: () => (configProvider ? applyLedger.view() : undefined),
611
635
  centerPullState: () => {
612
636
  const view = applyLedger.view();
@@ -622,7 +646,7 @@ export async function createConfigCenterRuntime(ctx) {
622
646
  },
623
647
  async applyCenterCapabilities(skills) {
624
648
  if (effective?.skills && config.configCenter) {
625
- skills = await applyCenterSkills(skills, effective.skills, config.configCenter, logger, undefined, configLkgEnabled() ? defaultSkillCacheDir(config) : undefined);
649
+ skills = await applyCenterSkills(skills, effective.skills, config.configCenter, logger, undefined, config.configLkgEnabled ? defaultSkillCacheDir(config) : undefined);
626
650
  }
627
651
  if (effective?.plugins !== undefined) {
628
652
  const pluginOut = await applyCenterPlugins(skills, effective, {
@@ -850,7 +874,7 @@ export async function createConfigCenterRuntime(ctx) {
850
874
  ceilingVerdict?.judge();
851
875
  mcpRevocations.applyEnabledEntries(enabledMcpEntries(r.effective.mcp));
852
876
  applyLiveReadFace(r.effective, swapRunnerDeps, "refresh");
853
- applyLedger.applied(tickGen, r.effective.version);
877
+ markApplied(tickGen, r.effective.version);
854
878
  const tickPlane = modelPlaneCommitted(applyReport);
855
879
  if (tickPlane)
856
880
  appliedPlaneEff = r.effective;
@@ -963,7 +987,7 @@ export async function createConfigCenterRuntime(ctx) {
963
987
  limitSync?.sync();
964
988
  ceilingVerdict?.judge();
965
989
  applyLiveReadFace(r.effective, swapRunnerDeps, "boot-deferred");
966
- applyLedger.applied(lateGen, r.effective.version);
990
+ markApplied(lateGen, r.effective.version);
967
991
  const latePlane = modelPlaneCommitted(applyReportLate);
968
992
  if (latePlane)
969
993
  appliedPlaneEff = r.effective;
@@ -36,7 +36,7 @@
36
36
  * 的请求是毫秒级的,而本腿 15s 采样一拍 —— 采样点几乎必然落在两次轮询之间,`attached` 与
37
37
  * `inflight` **同时读到 0**。只看那两条的实现会在一只**正被使用**的引擎底下把它关掉。租约的
38
38
  * 建立面按设计就是「首个 HTTP/SSE 接触」,不只是 SSE。
39
- * 4. **退出 = 走 `drain`**,即 `boot/shutdown.ts` 里 SIGTERM 用的那一条 `drainThenShutdown`:拒新提交
39
+ * 4. **退出 = 走 `drain`**,即 `boot/shutdown.ts` 里 SIGTERM 用的那一只唯一排空入口 `enterDrain`(编排器片 2 前名 `drainThenShutdown`):拒新提交
40
40
  * (503+Retry-After)→ 候在途腿跑完 → `hardShutdown`。in-flight run 的结算 / park 语义与人工停机
41
41
  * **逐字一致**。本模块自己**从不** `process.exit`。
42
42
  * 5. **crash-last 判别位**([ref] ④「flush 判别位标 auto-exit 非崩溃」):走上面那条受控退,
@@ -51,6 +51,7 @@ export function createLeaderFace(ctx) {
51
51
  retentionPolicy: governanceSeams.retentionPolicy,
52
52
  },
53
53
  deploymentPosture: () => buildDeploymentPostureSeats(config),
54
+ autonomy: () => config.autonomy,
54
55
  ...(ctx.tracer ? { tracer: ctx.tracer } : {}),
55
56
  ...(config.usageWindows && ctx.usageWindowStore
56
57
  ? { usageWindows: config.usageWindows, usageWindowStore: ctx.usageWindowStore }
@@ -24,6 +24,7 @@ import type { TaskAttachmentStore } from "../plugins/task-attachment-store.js";
24
24
  import type { FileHistoryErasureFace } from "../file-history-capture.js";
25
25
  import type { ApprovalNonceStore, CheckpointStoreFull, CostQuotaStore, ImageBake, RateLimiterStore, ServiceWorkflowJournalStore, StoreBackend, ToolResultStoreFull } from "../plugins/store-backend.js";
26
26
  import type { ToolApprovalCoordinator } from "../tool-approval.js";
27
+ import type { ParkCommitWatch } from "../plugins/park-commit-watch.js";
27
28
  /** [ref] / [ref]§五(sema-comms `audits/failopen-governance-176.md` §7 "Z 类 1 条"):default consecutive-failure
28
29
  * count before a reaper leg's swallowed error gets a `warn` line. The ~15 `.catch(() => undefined)` sites
29
30
  * below are correct in DIRECTION (a hiccuping periodic sweep must never crash the whole tick loop — one bad
@@ -68,6 +69,8 @@ export interface ReapersCtx {
68
69
  subRunner: Runner;
69
70
  runStore: ReturnType<StoreBackend["run"]> | undefined;
70
71
  checkpointStore: CheckpointStoreFull | undefined;
72
+ /** S-1280:checkpoint 店的 park 提交观察层(stage-06 与店同一处铸;在场 ⇔ 有 checkpoint 店)。对账收敛器订阅它,在停车行提交那一拍收口 PARKING。 */
73
+ parkCommits: ParkCommitWatch | undefined;
71
74
  rateLimiter: RateLimiter | RateLimiterStore | undefined;
72
75
  costQuota: CostQuota | CostQuotaStore | undefined;
73
76
  toolResultStore: ToolResultStoreFull | undefined;
@@ -56,7 +56,7 @@ export function createPlacementAwareReapSessions(deps) {
56
56
  };
57
57
  }
58
58
  export function startReapers(ctx) {
59
- const { config, logger, metrics, localRoot, backend, subRunner, runStore, checkpointStore, rateLimiter, costQuota, toolResultStore, fileHistoryErasure, taskAttachmentStore, imageBakes, worktreeReap, workflowNotifyGate, workflowRecoverOpts, workflowJournalStore, sqlWorkflowRunStore, workflowNotifyJournal, rosterStore, backgroundAgentStore, mailboxStore, toolApproval, permissionRuleStores, approvalNonceStore, getRunDenySweep, userInputLedger, } = ctx;
59
+ const { config, logger, metrics, localRoot, backend, subRunner, runStore, checkpointStore, parkCommits, rateLimiter, costQuota, toolResultStore, fileHistoryErasure, taskAttachmentStore, imageBakes, worktreeReap, workflowNotifyGate, workflowRecoverOpts, workflowJournalStore, sqlWorkflowRunStore, workflowNotifyJournal, rosterStore, backgroundAgentStore, mailboxStore, toolApproval, permissionRuleStores, approvalNonceStore, getRunDenySweep, userInputLedger, } = ctx;
60
60
  const leg = (p) => {
61
61
  if (p !== undefined)
62
62
  trackMaintenanceLeg(p);
@@ -122,6 +122,7 @@ export function startReapers(ctx) {
122
122
  orphanTtlMs: config.streamApproval.orphanTtlMs,
123
123
  unattendedPolicy: config.unattendedApprovalPolicy,
124
124
  ...(toolApproval ? { emitRevoke: (frame, target) => void toolApproval.emitRevoke(frame, target) } : {}),
125
+ parkCommits,
125
126
  });
126
127
  })();
127
128
  const reapSessions = createPlacementAwareReapSessions({ host: backend?.session(), routed: subRunner.sessions });
@@ -26,7 +26,7 @@ import { resourceSuspendOptIn } from "../resource-suspend.js";
26
26
  import { isIsolatedExecEnv, routeServiceTask, supPostureOverrides } from "../router/route-orchestration.js";
27
27
  import { tightenGoverned } from "../governance-overrides.js";
28
28
  import { gateExecutionLane } from "../runtime-caps-resolver.js";
29
- import { applyRuntimeGovernance, stripDelegationTools } from "../runtime-governance.js";
29
+ import { applyRuntimeGovernance } from "../runtime-governance.js";
30
30
  import { HttpError, createCodedHttpError, encodeCheckpointScope } from "../security.js";
31
31
  import { memoryProjectKeyFor, memorySpecForRequest } from "../memory-scope.js";
32
32
  import { inheritEnvFieldIssue, singleMemberOffFieldIssue } from "../request-value-closure.js";
@@ -331,6 +331,7 @@ export function createResolveSpec(ctx) {
331
331
  objective: attachmentNotice ? `${picked.cleanedObjective}\n\n${attachmentNotice}` : picked.cleanedObjective,
332
332
  systemPrompt: typeof body.systemPrompt === "string" ? body.systemPrompt : undefined,
333
333
  appendSystemPrompt: acceptedAppend,
334
+ ...(body.outputStyle !== undefined ? { outputStyle: body.outputStyle } : {}),
334
335
  sessionId: auth?.sessionId,
335
336
  principal: auth?.principal,
336
337
  images: body.images,
@@ -598,9 +599,6 @@ export function createResolveSpec(ctx) {
598
599
  throw new HttpError(422, `request settings cannot be applied as written: ${e.message}`);
599
600
  }
600
601
  }
601
- if (governed.handsReadOnly === true && Array.isArray(governed.tools)) {
602
- governed = { ...governed, tools: stripDelegationTools(governed.tools) };
603
- }
604
602
  if (shouldDiscloseNoPersistentMemory({
605
603
  memoryFaceMounted: memoryFaceMounted(governed.memory),
606
604
  declaredCapable: effectiveMemoryPersistenceCapable(config),
@@ -26,11 +26,12 @@ import type { Logger } from "../observability/logger.js";
26
26
  import type { startOtlpExporter } from "../observability/otel-exporter.js";
27
27
  import type { CostQuota } from "../observability/cost-quota.js";
28
28
  import type { RateLimiter } from "../observability/rate-limit.js";
29
- import type { startFleetClientFromEnv } from "../fleet-client.js";
29
+ import type { startFleetClientFromConfig } from "../fleet-client.js";
30
30
  import type { BreakerStateStore, CostQuotaStore, RateLimiterStore, StoreBackend } from "../plugins/store-backend.js";
31
31
  import type { WorkflowNotifyJournalStore } from "../orchestration/workflow-notify-journal.js";
32
32
  import type { ToolApprovalCoordinator } from "../tool-approval.js";
33
33
  import type { ParkCommitWatch } from "../plugins/park-commit-watch.js";
34
+ import { type DrainState } from "../fleet/drain-entry.js";
34
35
  export interface ShutdownCtx {
35
36
  config: ServiceConfig;
36
37
  /** [ref]①:崩溃遗言的 version 键(与 /v1/capabilities.version 同源=main 的 serviceVersion())。 */
@@ -61,16 +62,10 @@ export interface ShutdownCtx {
61
62
  subRunner: Runner;
62
63
  lspManager: RunnerDeps["lspManager"];
63
64
  workflowNotifyJournal: WorkflowNotifyJournalStore | undefined;
64
- fleetClient: ReturnType<typeof startFleetClientFromEnv>;
65
+ fleetClient: ReturnType<typeof startFleetClientFromConfig>;
65
66
  backend: StoreBackend | undefined;
66
- drainState: {
67
- draining: boolean;
68
- since?: number;
69
- inflight?: () => number;
70
- lastActivityAt?: () => number;
71
- attachedStreams?: () => number;
72
- lastAttachEndedAt?: () => number;
73
- };
67
+ /** 编排器片 2 件 2:共享排空盒子(`fleet/drain-entry.ts`)—— 本函数往上装**唯一**入口 `enter`。 */
68
+ drainState: DrainState;
74
69
  /** [ref]-2:store 活体探针(main 建)——不停的话 hardShutdown 后仍对正在关闭的池发探针,刷假
75
70
  * store_probe_dead 告警(文件头契约 3 的同族漏网)。缺席 = 该部署形没建探针。 */
76
71
  storeLiveProbe: {
@@ -95,6 +90,10 @@ export interface ShutdownCtx {
95
90
  /** S-929 F1:checkpoint 店的 park 提交观察层(`plugins/park-commit-watch.ts`;在场 ⇔ 本部署有 checkpoint 店)。hardShutdown 在
96
91
  * `backend.close()` 之前等它 quiesce —— 在飞的 `put`(审批 park / 资源挂起…,与审批协调器在不在场无关)不许被抽掉连接池。 */
97
92
  parkCommits: ParkCommitWatch | undefined;
93
+ /** 编排器片 2 件 3:服务租约客户端(在场 ⇔ fleet 成员 ∧ 中心车道配齐)。hardShutdown 起点停它的续租定时器(文件头契约 3 同族)。 */
94
+ servingLease?: {
95
+ stop(): void;
96
+ } | undefined;
98
97
  }
99
98
  /**
100
99
  * 受控退的**后台宽限**还要等多久(ms)—— hardShutdown 软停后台 shell 之后、`process.exit` 之前,给软停过的行**一次**