@sema-agent/server 7.149.0 → 7.150.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 (194) hide show
  1. package/MIGRATION.md +122 -9
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +39 -15
  5. package/dist/approval-card.d.ts +1 -1
  6. package/dist/approval-gate-currency.d.ts +1 -1
  7. package/dist/audit.d.ts +1 -1
  8. package/dist/audit.js +2 -2
  9. package/dist/background-session-reap.d.ts +1 -1
  10. package/dist/boot/deferred-sandbox-path-env.d.ts +5 -3
  11. package/dist/boot/deferred-sandbox-path-env.js +1 -2
  12. package/dist/boot/parked-revive-gate.d.ts +5 -5
  13. package/dist/boot/parked-revive-gate.js +2 -3
  14. package/dist/boot/resolve-spec.d.ts +4 -5
  15. package/dist/boot/resolve-spec.js +61 -25
  16. package/dist/boot/retention-lane.d.ts +1 -1
  17. package/dist/boot/session-faces.js +21 -21
  18. package/dist/boot/stage-03-store-backend.js +2 -2
  19. package/dist/boot/stage-06-runners.js +1 -2
  20. package/dist/boot/stage-07-capability-layer.js +1 -1
  21. package/dist/boot/stage-10-http-server.d.ts +33 -1
  22. package/dist/boot/stage-10-http-server.js +9 -4
  23. package/dist/boot/web-admin.d.ts +1 -1
  24. package/dist/capability-facts.d.ts +65 -56
  25. package/dist/capability-facts.js +42 -35
  26. package/dist/config-catalog.js +1 -1
  27. package/dist/config-types.d.ts +12 -11
  28. package/dist/deployment-governance.d.ts +7 -28
  29. package/dist/deployment-governance.js +1 -14
  30. package/dist/device-ws-hub.d.ts +1 -0
  31. package/dist/device-ws-hub.js +64 -28
  32. package/dist/device-ws-protocol.d.ts +20 -6
  33. package/dist/device-ws-protocol.js +9 -2
  34. package/dist/engine-identity.d.ts +7 -1
  35. package/dist/engine-identity.js +5 -0
  36. package/dist/field-conflict.d.ts +2 -2
  37. package/dist/fleet/engine-signal.d.ts +14 -1
  38. package/dist/fleet/engine-signal.js +5 -1
  39. package/dist/fleet/fleet-reconciler.d.ts +1 -1
  40. package/dist/fleet/serving-lease.d.ts +18 -3
  41. package/dist/fleet/serving-lease.js +81 -35
  42. package/dist/fleet-client.d.ts +13 -11
  43. package/dist/fleet-client.js +53 -33
  44. package/dist/history-anchor.js +1 -1
  45. package/dist/hitl-delivery.d.ts +1 -3
  46. package/dist/hitl-delivery.js +1 -1
  47. package/dist/http/active-run-conflict.d.ts +3 -7
  48. package/dist/http/active-run-conflict.js +2 -2
  49. package/dist/http/admission.js +2 -2
  50. package/dist/http/approval-sweeps.js +2 -1
  51. package/dist/http/caller-trust.d.ts +2 -2
  52. package/dist/http/caller-trust.js +3 -3
  53. package/dist/http/checkpoint-ctx.d.ts +18 -63
  54. package/dist/http/checkpoint-ctx.js +1 -24
  55. package/dist/http/claim-heartbeat.d.ts +4 -4
  56. package/dist/http/claim-heartbeat.js +3 -3
  57. package/dist/http/dispatch.js +2 -2
  58. package/dist/http/durable-run-leg.js +4 -4
  59. package/dist/http/owner-access.d.ts +243 -19
  60. package/dist/http/owner-access.js +184 -5
  61. package/dist/http/readiness.d.ts +4 -2
  62. package/dist/http/resume-ctx-read.js +16 -1
  63. package/dist/http/resume-legs.js +18 -14
  64. package/dist/http/route-ctx.d.ts +23 -11
  65. package/dist/http/routes/a2a-serve.js +15 -14
  66. package/dist/http/routes/approvals-assistant.d.ts +3 -2
  67. package/dist/http/routes/approvals-assistant.js +8 -9
  68. package/dist/http/routes/capabilities.js +4 -3
  69. package/dist/http/routes/config-catalog.d.ts +1 -1
  70. package/dist/http/routes/mcp-reconnect.js +2 -2
  71. package/dist/http/routes/memory-origin.d.ts +1 -1
  72. package/dist/http/routes/notify-wake.js +2 -3
  73. package/dist/http/routes/projects.d.ts +1 -0
  74. package/dist/http/routes/projects.js +5 -3
  75. package/dist/http/routes/retention-ops.d.ts +1 -1
  76. package/dist/http/routes/run-permission-mode.d.ts +1 -1
  77. package/dist/http/routes/run-permission-mode.js +2 -2
  78. package/dist/http/routes/runs.d.ts +1 -1
  79. package/dist/http/routes/runs.js +23 -30
  80. package/dist/http/routes/session-memory-sync.js +4 -5
  81. package/dist/http/routes/session-sync.js +3 -7
  82. package/dist/http/routes/sessions.d.ts +1 -1
  83. package/dist/http/routes/sessions.js +18 -44
  84. package/dist/http/routes/tasks.js +9 -8
  85. package/dist/http/routes/trace-usage.js +14 -14
  86. package/dist/http/routes/workflows.d.ts +1 -0
  87. package/dist/http/routes/workflows.js +20 -38
  88. package/dist/http/server.d.ts +8 -4
  89. package/dist/http/server.js +5 -1
  90. package/dist/http/stored-body-replay.d.ts +3 -24
  91. package/dist/http/stored-body-replay.js +2 -12
  92. package/dist/http/submit-ctx.d.ts +1 -2
  93. package/dist/http/submit-ctx.js +3 -2
  94. package/dist/http/submit-replay.d.ts +15 -2
  95. package/dist/http/submit-replay.js +10 -4
  96. package/dist/http/wire-types.d.ts +6 -3
  97. package/dist/leader/endpoint.js +9 -8
  98. package/dist/leg-facts.d.ts +8 -2
  99. package/dist/leg-facts.js +1 -0
  100. package/dist/mcp-authorization-face.d.ts +18 -14
  101. package/dist/mcp-authorization-face.js +3 -2
  102. package/dist/memory-store-refusal.d.ts +1 -1
  103. package/dist/observability/corrupt-read-seat.d.ts +1 -1
  104. package/dist/observability/fail-open.d.ts +5 -5
  105. package/dist/observability/fail-open.js +5 -5
  106. package/dist/observability/logger.d.ts +7 -0
  107. package/dist/observability/logger.js +3 -1
  108. package/dist/observability/run-terminal.d.ts +1 -1
  109. package/dist/owner-faces.d.ts +27 -3
  110. package/dist/owner-faces.js +2 -0
  111. package/dist/peer-seat.d.ts +1 -1
  112. package/dist/peer-seat.js +52 -59
  113. package/dist/plugins/exec-path-law.d.ts +34 -0
  114. package/dist/plugins/exec-path-law.js +13 -0
  115. package/dist/plugins/file-run-store.js +1 -1
  116. package/dist/plugins/local-checkpoint-store.d.ts +4 -1
  117. package/dist/plugins/local-checkpoint-store.js +3 -0
  118. package/dist/plugins/memory-run-store.js +1 -1
  119. package/dist/plugins/one-time-migrations.d.ts +74 -49
  120. package/dist/plugins/one-time-migrations.js +134 -69
  121. package/dist/plugins/pg-pool.js +4 -0
  122. package/dist/plugins/posix-shell-fs.js +4 -2
  123. package/dist/plugins/remote-env-adb.js +3 -2
  124. package/dist/plugins/remote-env-device.js +8 -6
  125. package/dist/plugins/remote-env-e2b.d.ts +3 -1
  126. package/dist/plugins/remote-env-e2b.js +11 -8
  127. package/dist/plugins/remote-env-host.d.ts +1 -1
  128. package/dist/plugins/remote-env-host.js +20 -11
  129. package/dist/plugins/remote-env-k8s.js +3 -2
  130. package/dist/plugins/remote-env-local-docker.js +3 -2
  131. package/dist/plugins/remote-env-ssh.js +4 -3
  132. package/dist/plugins/remote-shell.d.ts +3 -1
  133. package/dist/plugins/remote-shell.js +3 -3
  134. package/dist/plugins/replica-registration.d.ts +35 -0
  135. package/dist/plugins/replica-registration.js +48 -0
  136. package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
  137. package/dist/plugins/run-store-sql.d.ts +1 -1
  138. package/dist/plugins/run-store-sql.js +2 -2
  139. package/dist/plugins/scenario-prefix-switch-m42.d.ts +6 -12
  140. package/dist/plugins/scenario-prefix-switch-m42.js +7 -5
  141. package/dist/plugins/schema-consistency.d.ts +7 -5
  142. package/dist/plugins/schema-consistency.js +29 -16
  143. package/dist/plugins/schema-drift-sentence.js +1 -1
  144. package/dist/plugins/serving-requirement.d.ts +49 -0
  145. package/dist/plugins/serving-requirement.js +53 -0
  146. package/dist/plugins/stop-then-start-sentence.d.ts +42 -3
  147. package/dist/plugins/stop-then-start-sentence.js +44 -0
  148. package/dist/plugins/store-backend.d.ts +1 -1
  149. package/dist/plugins/store-contracts.d.ts +12 -2
  150. package/dist/plugins/store-contracts.js +3 -0
  151. package/dist/plugins/tidb-pool.js +4 -0
  152. package/dist/process-instance.d.ts +3 -0
  153. package/dist/process-instance.js +7 -0
  154. package/dist/project-context.d.ts +66 -0
  155. package/dist/project-context.js +70 -0
  156. package/dist/question.d.ts +9 -21
  157. package/dist/question.js +28 -19
  158. package/dist/run-cancel-card.d.ts +1 -1
  159. package/dist/run-cancel-card.js +2 -1
  160. package/dist/security.d.ts +1 -1
  161. package/dist/session-agent.d.ts +1 -10
  162. package/dist/session-agent.js +0 -6
  163. package/dist/session-incarnation.d.ts +18 -4
  164. package/dist/session-incarnation.js +4 -1
  165. package/dist/session-notify-door.d.ts +4 -4
  166. package/dist/session-notify-door.js +5 -6
  167. package/dist/session-rules-seat.d.ts +4 -3
  168. package/dist/session-rules-seat.js +3 -11
  169. package/dist/session-sync.js +3 -3
  170. package/dist/stores-sql/checkpoint-store-sql.d.ts +21 -2
  171. package/dist/stores-sql/checkpoint-store-sql.js +106 -18
  172. package/dist/stores-sql/retention-store-sql.d.ts +1 -1
  173. package/dist/task-cwd.d.ts +4 -3
  174. package/dist/task-settings.d.ts +13 -0
  175. package/dist/task-settings.js +13 -7
  176. package/dist/tool-approval.d.ts +7 -6
  177. package/dist/tool-approval.js +41 -37
  178. package/dist/trace/engine-notice-wire.d.ts +14 -0
  179. package/dist/trace/engine-notice-wire.js +18 -1
  180. package/dist/trace/project.d.ts +4 -3
  181. package/dist/trace/project.js +3 -2
  182. package/dist/trace/projection-table.d.ts +33 -2
  183. package/dist/trace/self-minted-wire.d.ts +150 -17
  184. package/dist/trace/self-minted-wire.js +54 -16
  185. package/dist/trace/tool-card.d.ts +3 -0
  186. package/dist/trace/tool-card.js +24 -0
  187. package/dist/trace/tool-output-media.d.ts +2 -1
  188. package/dist/unattended-permission-mode.d.ts +1 -1
  189. package/dist/web-admin-client/apply-effective.d.ts +1 -1
  190. package/dist/web-admin-client/effective-keys.d.ts +1 -1
  191. package/dist/web-admin-client/skills-mcp.d.ts +1 -1
  192. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -29
  193. package/package.json +2 -2
  194. package/wire-changes/server@7.150.0.json +335 -0
@@ -18,9 +18,10 @@
18
18
  * - **`modelRefusals?`([ref])**:在场 ⇔ 本副本对最近一次被判的中心候选目录有可报的拒(逐条名 → 码 / 模型面组拒码)—— 取自
19
19
  * `/health.modelRefusals` **同一只**取值口(装配层接 `deployment.modelRefusalsState`),同一个对象,形 = settings-schema
20
20
  * `ModelRefusalsSignal`;缺席即键缺席(同 `restart`)。
21
- * - **自报状态翻转即刻再 announce**(第一时间自摘流 / 自挂流 / 报重启 / 报拒因,比任何观测都快):draining 翻转由收尾链直调
22
- * `announceNow`;roster 落地、restart 信号与模型拒因变化由装配层把 web-admin 的 `onSelfReportChange` 接到 `announceNow`
23
- * (两处 = 两类输入各自的唯一写者,触发都是 `announceNow`)。退出前 DELETE(显式注销,幂等)。
21
+ * - **自报状态翻转即刻再 announce**(第一时间自摘流 / 自挂流 / 报重启 / 报拒因 / 报租约态,比任何观测都快):draining 翻转由收尾链直调
22
+ * `announceNow`;roster 落地、restart 信号与模型拒因变化由装配层把 web-admin 的 `onSelfReportChange` 接到 `announceNow`;租约态由事件引起的
23
+ * 翻转(推定的终结 / 续租判决 / 被拒 —— 不自激的那几样,见 `fleet/serving-lease.ts` 规则 2′)由装配层把服务租约客户端的 `onViewChange` 接到
24
+ * `announceNow`(三处 = 三类输入各自的唯一写者,触发都是 `announceNow`)。退出前 DELETE(显式注销,幂等)。
24
25
  * - **usage 批报**:tracer 的 `brain.call` 已带 ALS principal —— FleetUsageAccumulator 按 principal×model
25
26
  * 累计,每窗口(默认 60s,半开 [windowStartMs, windowEndMs))POST /api/fleet/usage。幂等键
26
27
  * (worker,instanceId,windowStartMs) 重发安全;**发送失败 → merge 回累计器**,并入下一窗(失败窗从未入账,
@@ -30,8 +31,9 @@
30
31
  * 不在下一个人的凭证下上报(不记到别人头上);它留在原箱,那个身份回来时照常报出去,收尾时还压着就响亮一行。
31
32
  * - 全部 fire-and-forget + swallow-guarded:fleet 面是运维旁路,绝不影响任务面;网络错误只 warn 一次/连败类。
32
33
  */
33
- import { UsageReport, type CapabilityReading, type WebAdminCapabilities, type ModelRefusalsSignal } from "@sema-agent/settings-schema/fleet";
34
+ import { UsageReport, type CapabilityReading, type ModelRefusalsSignal } from "@sema-agent/settings-schema/fleet";
34
35
  import { type EngineSignalFacts } from "./fleet/engine-signal.js";
36
+ import type { HeartbeatHearing } from "./fleet/serving-lease.js";
35
37
  import { type CenterEndpoint } from "./web-admin-client/center-request.js";
36
38
  import { type CenterCredentialSource } from "./center-credential.js";
37
39
  /** Bound on distinct principal×model keys per window — a runaway/multi-tenant burst collapses into
@@ -118,10 +120,10 @@ export interface FleetClientOpts {
118
120
  /** [ref]:模型拒因信号(装配层接 `/health` 同一只取值口 `deployment.modelRefusalsState`)。每条公告现读一次;`undefined` ⇒ 公告不带
119
121
  * `modelRefusals` 键。必填:与 `restart` 同 —— 心跳面没有「不知道有没有拒」这一形(老引擎不发这个键,中心按 `version` 判「不可知」)。 */
120
122
  modelRefusals: () => ModelRefusalsSignal | undefined;
121
- /** 编排器片 2:每一次**能力读数**交出去一次(装配层接服务租约客户端的 `noteCenterCapabilities`)。规则见 {@link capabilityReadingOf}:
122
- * 只有 `declared`(交出宣告的位)与 `undeclared`(交 `undefined` = 老中心没宣告)是读数;`unreadable`(体读不出 / 不是对象 /
123
- * `capabilities` 键在场而形坏)不是读数 ⇒ 不调,上一次读数与它的钟原样。失败的公告同样不调(没听到 ≠ 没有)。 */
124
- onCenterCapabilities?: (caps: WebAdminCapabilities | undefined) => void;
123
+ /** 编排器片 2:**每一次公告尝试恰好交一只**听到的东西(装配层接服务租约客户端的 `noteHeartbeat`;只有 fleet 成员接)。`reading` = 中心的显式
124
+ * 声明(规则见 {@link capabilityReadingOf});其余一律 `silent`(`why` 闭集:`HTTP <状态>` / 请求抛出的那句 / `reply-unreadable:<因>` /
125
+ * `not-sent:<那句>`)—— 没听到不是「没有」,租约客户端按沉默处置。没接这一只 ⇒ 不读答复体(非成员的心跳答复不进任何租约臂)。 */
126
+ onHeartbeat?: (hearing: HeartbeatHearing) => void;
125
127
  /** [ref]:按凭证身份分箱的用量账(缺席 = 用量环不装)。 */
126
128
  usage?: FleetUsageBins;
127
129
  heartbeatMs?: number;
@@ -149,13 +151,13 @@ export interface FleetClient {
149
151
  flushFinalUsage: () => Promise<void>;
150
152
  }
151
153
  /**
152
- * 心跳 2xx 答复读不成能力读数的原因 —— 一张闭集:`body` = 答复体本身读不出(空体 / 截断 / 非 JSON),其余两词 = 契约读器
153
- * `readCapabilityReading` 的 `unreadable` 两因(`not-an-object` / `invalid-capabilities`)。
154
+ * 心跳 2xx 答复读不成能力读数的原因 —— 一张闭集:`body` = 答复体本身读不出(空体 / 截断 / 非 JSON),其余三词 = 契约读器
155
+ * `readCapabilityReading` 的 `unreadable` 三因(`not-an-object` / `capabilities-absent` / `invalid-capabilities`)。
154
156
  */
155
157
  export type AnnounceReplyFlaw = "body" | Extract<CapabilityReading, {
156
158
  kind: "unreadable";
157
159
  }>["why"];
158
- /** 本仓对一次心跳答复的读法:契约三态,`unreadable` 的原因扩成 {@link AnnounceReplyFlaw}(`detail` = 读不出体时的那句错误,当数据交出)。 */
160
+ /** 本仓对一次心跳答复的读法:契约两态,`unreadable` 的原因扩成 {@link AnnounceReplyFlaw}(`detail` = 读不出体时的那句错误,当数据交出)。 */
159
161
  export type AnnounceReplyReading = Exclude<CapabilityReading, {
160
162
  kind: "unreadable";
161
163
  }> | {
@@ -110,9 +110,40 @@ export function startFleetClient(opts) {
110
110
  let failStreak = 0;
111
111
  let unreadableStreak = 0;
112
112
  const address = opts.advertiseAddress;
113
+ const announceFailed = (error) => {
114
+ failStreak += 1;
115
+ if (failStreak === 1)
116
+ opts.logger?.warn("fleet_announce_failed", { error });
117
+ };
118
+ const hearReply = async (res, leaseState) => {
119
+ const reading = await capabilityReadingOf(res);
120
+ switch (reading.kind) {
121
+ case "declared":
122
+ unreadableStreak = 0;
123
+ return { kind: "reading", capabilities: reading.capabilities };
124
+ case "unreadable":
125
+ recordFailOpen("server.fleet.announce-reply-unreadable", `why=${reading.why} leaseState=${leaseState}`);
126
+ unreadableStreak += 1;
127
+ if (unreadableStreak === 1) {
128
+ opts.logger?.warn("fleet_announce_reply_unreadable", {
129
+ status: res.status,
130
+ why: reading.why,
131
+ ...(reading.detail !== undefined ? { error: reading.detail } : {}),
132
+ note: "the fleet center's heartbeat reply is not a capability declaration (unreadable body, not an object, no capabilities key, or a malformed capabilities value) — no reading taken; the serving lease keeps its state and outage clock (a member that has heard no declaration stays inside the presumed fence)",
133
+ });
134
+ }
135
+ return { kind: "silent", why: `reply-unreadable:${reading.why}` };
136
+ default: {
137
+ const unhandled = reading;
138
+ throw new Error(`heartbeat reply: unknown capability reading ${JSON.stringify(unhandled)}`);
139
+ }
140
+ }
141
+ };
113
142
  const announceOnce = async () => {
114
143
  if (stopped || address === undefined)
115
144
  return;
145
+ let hearing;
146
+ let sending = false;
116
147
  try {
117
148
  const signal = opts.signal();
118
149
  const engineSignal = buildEngineSignal(signal);
@@ -129,43 +160,32 @@ export function startFleetClient(opts) {
129
160
  ...fleetMembershipKeys(signal),
130
161
  [ENGINE_SIGNAL_KEY]: engineSignal,
131
162
  };
163
+ sending = true;
132
164
  const { res } = await send("fleet-instances", "POST", body);
133
- if (!res.ok)
134
- throw new Error(`announce ${res.status}${await rejectionSnippet(res)}`);
135
- if (opts.onCenterCapabilities !== undefined) {
136
- const reading = await capabilityReadingOf(res);
137
- switch (reading.kind) {
138
- case "declared":
139
- case "undeclared":
140
- unreadableStreak = 0;
141
- opts.onCenterCapabilities(reading.kind === "declared" ? reading.capabilities : undefined);
142
- break;
143
- case "unreadable":
144
- recordFailOpen("server.fleet.announce-reply-unreadable", `why=${reading.why} leaseState=${signal.lease.state}`);
145
- unreadableStreak += 1;
146
- if (unreadableStreak === 1) {
147
- opts.logger?.warn("fleet_announce_reply_unreadable", {
148
- status: res.status,
149
- why: reading.why,
150
- ...(reading.detail !== undefined ? { error: reading.detail } : {}),
151
- note: "the fleet center's heartbeat reply is not a capability reading (unreadable body, not an object, or a malformed capabilities value) — no reading taken; the last one stands (the serving lease keeps its state and outage clock)",
152
- });
153
- }
154
- break;
155
- default: {
156
- const unhandled = reading;
157
- throw new Error(`heartbeat reply: unknown capability reading ${JSON.stringify(unhandled)}`);
158
- }
159
- }
165
+ if (!res.ok) {
166
+ hearing = { kind: "silent", why: `HTTP ${res.status}` };
167
+ announceFailed(`announce ${res.status}${await rejectionSnippet(res)}`);
160
168
  }
161
- if (failStreak > 0)
162
- opts.logger?.info("fleet_announce_recovered", { after: failStreak });
163
- failStreak = 0;
169
+ else {
170
+ if (failStreak > 0)
171
+ opts.logger?.info("fleet_announce_recovered", { after: failStreak });
172
+ failStreak = 0;
173
+ if (opts.onHeartbeat !== undefined)
174
+ hearing = await hearReply(res, signal.lease.state);
175
+ }
176
+ }
177
+ catch (err) {
178
+ const error = err instanceof Error ? err.message : String(err);
179
+ hearing = { kind: "silent", why: sending ? error : `not-sent:${error}` };
180
+ announceFailed(error);
181
+ }
182
+ if (hearing === undefined || opts.onHeartbeat === undefined)
183
+ return;
184
+ try {
185
+ opts.onHeartbeat(hearing);
164
186
  }
165
187
  catch (err) {
166
- failStreak += 1;
167
- if (failStreak === 1)
168
- opts.logger?.warn("fleet_announce_failed", { error: err instanceof Error ? err.message : String(err) });
188
+ opts.logger?.warn("fleet_heartbeat_listener_failed", { error: err instanceof Error ? err.message : String(err), note: "the serving-lease client threw while taking this heartbeat's hearing — this beat did not reach the fence; the announce loop goes on" });
169
189
  }
170
190
  };
171
191
  let inFlight;
@@ -10,7 +10,7 @@ async function unknownAnchorCause(readers, reader, sessionId, handle) {
10
10
  if (reader === undefined)
11
11
  return NO_ANCHOR;
12
12
  try {
13
- const run = await readVisibleRun(reader, readers.runs, handle);
13
+ const run = (await readVisibleRun(reader, readers.runs, handle))?.row;
14
14
  if (run === undefined || run.sessionId !== sessionId)
15
15
  return NO_ANCHOR;
16
16
  return (await readers.anchors.resolve(sessionId, handle, run.owner)) === undefined ? PROMPT_WITHOUT_ANCHOR : NO_ANCHOR;
@@ -29,7 +29,7 @@ export interface HitlSinks {
29
29
  readonly live?: boolean;
30
30
  }
31
31
  /**
32
- * 一条腿的传输(无状态)。`deliver` 与 `reachable` 是**属性**(不是方法签名)—— 帧型参数因此逆变。腿上装出的那一只是全族型(`HitlTransport<HitlFrame>`,
32
+ * 一条腿的传输(无状态)。`deliver` 是**属性**(不是方法签名)—— 帧型参数因此逆变。腿上装出的那一只是全族型(`HitlTransport<HitlFrame>`,
33
33
  * 一腿一只、三族共用);**窄化发生在装进族上下文那一步**:审批上下文的判别联合把传输型钉到本族帧(呈卡族收不进旧族帧),竞速机的目标从族上下文取
34
34
  * ⇒ 混族的 (transport, frame) 对编译不过。直接拿全族型传输喂竞速机的代码不受这道型约束(三只协调器都从族上下文取,`P14` 编译期钉)。
35
35
  */
@@ -39,8 +39,6 @@ export interface HitlTransport<F extends HitlFrame> {
39
39
  * 写抛 / 已死 ⇒ 直写没接下(不碰死连接)。③ 交回每只涉及的 sink 接下与否。**永不 reject。**
40
40
  */
41
41
  readonly deliver: (frame: F, onto?: HitlSinks) => Promise<HitlSinks>;
42
- /** 判决面谓词:有直写 ⇒ 直写此刻活着;只有账本 ⇒ 恒真(账本 tail 随时可被接上)。谓词读不出 ⇒ 假(判决面 fail-closed 到停驻)。 */
43
- readonly reachable: () => boolean;
44
42
  }
45
43
  /** 直写面:一个具名帧写 + 「此刻还活着吗」。 */
46
44
  export interface HitlLiveSink<F extends HitlFrame> {
@@ -66,7 +66,7 @@ export function createHitlTransport(sinks) {
66
66
  }
67
67
  return ledgerAccepted !== undefined ? { ledger: ledgerAccepted, live: liveAccepted } : { live: liveAccepted };
68
68
  };
69
- return { deliver, reachable: () => live === undefined || liveAlive(live) };
69
+ return { deliver };
70
70
  }
71
71
  export function createHitlOpenRace(args) {
72
72
  const opens = args.targets.map((t) => t.transport.deliver(t.frame));
@@ -15,7 +15,7 @@
15
15
  */
16
16
  import { type GovernancePosture } from "../runtime-governance.js";
17
17
  import type { ShellGateDoctrine } from "@sema-agent/core";
18
- import { type RunRecord } from "../plugins/store-contracts.js";
18
+ import type { RunStore } from "../plugins/store-backend.js";
19
19
  import { type PendingRowBinding } from "../plugins/checkpoint-store-face.js";
20
20
  /** 与 runs.ts/tasks.ts 三个 409 位共享的旧文案(byte-frozen:api-error-text-freeze 门认这句)。 */
21
21
  export declare const ACTIVE_RUN_CONFLICT_BASE_TEXT = "session already has an active run \u2014 POST /v1/runs/{activeTaskId}/cancel stops it (same-instance interactive runs abort immediately)";
@@ -113,12 +113,8 @@ interface ConflictProbeDeps<TToken> {
113
113
  /** 结构形入参,但 `status` 取 run 行的**闭词表**(合并码重扫):判 park 要走词表属主
114
114
  * `isParkedRunStatus`,而它的入参是穷举联合 —— 这一格写成开放 `string` 就只能在调用点裸 cast,
115
115
  * 绕开那道编译期执法(core 加一个 park 类成员时本文件不会红)。 */
116
- /** 持有者那一行(只读 `status`;409 体只点名**活着**的持有者 —— 心跳过期的持有者在认领冲突处已被判死、认领已放,见 run 店 `createRun`)。 */
117
- runStore?: {
118
- getRun?: (id: string) => Promise<{
119
- status?: RunRecord["status"];
120
- } | null | undefined>;
121
- } | undefined;
116
+ /** 持有者那一行的状态(不带属主的一格事实,经属主存取器 `runFactsOf` 读;409 体只点名**活着**的持有者 —— 心跳过期的持有者在认领冲突处已被判死、认领已放,见 run 店 `createRun`)。 */
117
+ runStore?: Pick<RunStore, "getRun"> | undefined;
122
118
  checkpointStore?: {
123
119
  peekPendingScope?: (sessionId: string, binding?: PendingRowBinding) => Promise<string | null | undefined>;
124
120
  findPendingTokenBySession?: (sessionId: string, scope?: string, binding?: PendingRowBinding) => Promise<TToken | null | undefined>;
@@ -1,5 +1,6 @@
1
1
  import { governanceOriginOf } from "../runtime-governance.js";
2
2
  import { isParkedRunStatus } from "../plugins/store-contracts.js";
3
+ import { runFactsOf } from "./owner-access.js";
3
4
  import { LIVE_RUN_GATE_KINDS } from "../plugins/checkpoint-store-face.js";
4
5
  import { recordFailOpen } from "../observability/fail-open.js";
5
6
  import { refusalBody } from "./refusal-body.js";
@@ -46,8 +47,7 @@ export async function buildActiveRunConflict(deps, sessionId, activeTaskId) {
46
47
  if (!activeTaskId)
47
48
  return base;
48
49
  try {
49
- const row = await deps.runStore?.getRun?.(activeTaskId);
50
- const status = row?.status;
50
+ const status = deps.runStore === undefined ? undefined : (await runFactsOf(deps.runStore, activeTaskId))?.status;
51
51
  if (status === undefined || (!isParkedRunStatus(status) && status !== "running"))
52
52
  return base;
53
53
  if (status === "running") {
@@ -2,7 +2,7 @@ import { APPROVAL_REACH, IMAGE_ADMISSION, isApprovalReach, isPermissionMode, isT
2
2
  import { isAtMentionable, matchCatalogModel, refusedCatalogEntry, selectableCatalog } from "../model-select.js";
3
3
  import { echoModelRef, modelUnavailableMessage } from "../model-entry-refusal.js";
4
4
  import { HttpError, sessionIdShapeIssue } from "../security.js";
5
- import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS, PERMISSION_MODE_WORDS, autoCompactEnabledOk, parseTaskSettings, readSettingsEnv, settingsEnvComposeIssue, settingsEnvCredentialIssue, settingsPermissionBucketIssue, skillListingBudgetFractionOk, skillListingMaxDescOk, hooksKeyIssue, taskHooksHonored, taskSettingsKeyIssue } from "../task-settings.js";
5
+ import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, appendFold, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS, PERMISSION_MODE_WORDS, autoCompactEnabledOk, parseTaskSettings, readSettingsEnv, settingsEnvComposeIssue, settingsEnvCredentialIssue, settingsPermissionBucketIssue, skillListingBudgetFractionOk, skillListingMaxDescOk, hooksKeyIssue, taskHooksHonored, taskSettingsKeyIssue } from "../task-settings.js";
6
6
  import { serverCredentialValueSources } from "../server-secret-env.js";
7
7
  import { tightenOnlySeatIssue } from "../tighten-only-seats.js";
8
8
  import { approvalFrameFamiliesIssue } from "../tool-approval.js";
@@ -214,7 +214,7 @@ export function createAdmission(ctx) {
214
214
  sendError(res, 400, "request.field_invalid", `settings.outputStyle must be at most ${MAX_SETTINGS_OUTPUT_STYLE_CHARS} characters`, limitFacts("settings.outputStyle", "characters", st.outputStyle.length, MAX_SETTINGS_OUTPUT_STYLE_CHARS));
215
215
  return null;
216
216
  }
217
- const foldedChars = typeof st.outputStyle === "string" && st.outputStyle.length > 0 && typeof body.appendSystemPrompt === "string" ? body.appendSystemPrompt.length + 2 + st.outputStyle.length : 0;
217
+ const foldedChars = typeof st.outputStyle === "string" && st.outputStyle.length > 0 && typeof body.appendSystemPrompt === "string" ? appendFold([body.appendSystemPrompt, st.outputStyle]).chars : 0;
218
218
  if (foldedChars > MAX_SYSTEM_PROMPT_CHARS) {
219
219
  sendError(res, 400, "request.field_conflict", `appendSystemPrompt + settings.outputStyle fold into one system-prompt block — combined they must be at most ${MAX_SYSTEM_PROMPT_CHARS} characters (including the 2-char joiner)`, fieldConflictFacts("system_prompt_fold", ["appendSystemPrompt", "settings.outputStyle"], { count: foldedChars, cap: MAX_SYSTEM_PROMPT_CHARS }));
220
220
  return null;
@@ -11,6 +11,7 @@ import { gatedPerson } from "./principal-gate.js";
11
11
  import { hasServiceAuth } from "../identity-source.js";
12
12
  import { costQuotaRefusalOf } from "./request-guards.js";
13
13
  import { admissionRefusalOf, readinessOf } from "./readiness.js";
14
+ import { memberLeaseOf } from "../fleet/engine-signal.js";
14
15
  import { rowReadRefusalAnswer } from "./resume-legs.js";
15
16
  import { readResumeCtx } from "./resume-ctx-read.js";
16
17
  import { isResumeBlockedByPolicy } from "./checkpoint-ctx.js";
@@ -218,7 +219,7 @@ export function createApprovalSweeps(ctx) {
218
219
  if (!hasServiceAuth(deps.config) && !deps.config.allowUnauthedWrites) {
219
220
  return { status: 503, body: refusalBody({ errorCode: "auth.service_token_required", message: "this worker requires a service auth token (set SERVICE_AUTH_TOKEN) before accepting task submissions" }) };
220
221
  }
221
- const refusal = admissionRefusalOf(readinessOf(deps), deps.servingLease?.(), "hitl-answer");
222
+ const refusal = admissionRefusalOf(readinessOf(deps), memberLeaseOf(deps), "hitl-answer");
222
223
  if (refusal !== undefined)
223
224
  return { status: refusal.status, body: refusal.body };
224
225
  if (deps.costQuota && req.httpReq) {
@@ -11,7 +11,7 @@
11
11
  * · 多租 · 部署的 service 凭据 ⇒ `global`:部署自己的凭据是运维档(契约给「启动方自己的行为」—— `SETTINGS_SOURCE_CONTRACT.flagSettings` —— 的档),持它的前端为自己转发的内容负责;
12
12
  * 出示的 Bearer 就是 service 凭据时,哪怕它同时也被鉴权桥验成登录令牌,也按 service 凭据读 —— 这不是本表的判:来源只有一只判定次序(`verifiedIdentitySource`,
13
13
  * service 凭据先;凭据门的 401 / 归因 / run 行身份元组读同一次的答案),本表只按来源词分档;
14
- * · 多租 · 中心登录令牌,scope 角色 ≥ 契约 `domainWriteRole("governance")` ⇒ `global`:能写 scope 治理域(autonomy / commandPolicy / approvalRequire,全组织的免问面)
14
+ * · 多租 · 中心登录令牌,scope 角色 ≥ 契约 `writeRoleFor("governance", [])` ⇒ `global`:能写 scope 治理域(autonomy / commandPolicy / approvalRequire,全组织的免问面)
15
15
  * 的角色,放宽自己这一 run 是它的子集;门槛读契约,不手抄角色词;
16
16
  * · 多租 · 其余(登录令牌角色低于它 / 不带 / 认不出;直连门 principal 令牌只证身份不证角色;没有验过的凭据)⇒ `project`:契约给非运维档作者的仓控来源的档,
17
17
  * 收紧成员照收、放宽成员不收 —— 没有可验的角色就落到这里(fail-closed)。
@@ -20,7 +20,7 @@
20
20
  * 自带技能的两只清单(引擎 `seedSkillGrants`:人点的名就是表态,不出 `Skill` 卡 ⇒ `allowedTools` 是 `allow` 成员的孪生、`disallowedTools` 是 `deny` 的孪生,键 → 成员一张表
21
21
  * {@link SKILL_LIST_MEMBER})。没点用的自带技能不判:它的清单要经 `Skill` 卡上的人点头才生效 —— 那正是契约给低于门槛的放宽指定的同意出口。
22
22
  * **部署 / 中心的技能不判**(点名了也不判):它们的两只清单是运维授权,与 `APPROVAL_GRANT` 同级 —— 来处只有部署的 `SKILLS_DIR` / 本地配置源与中心的 `skills` · `plugins` 域,
23
- * 契约给这两个域的写角色(`domainWriteRole`)不低于本表 `global` 的门槛(治理写角色),同名时部署的那只胜出、请求体那只被丢(`buildRunSkills`)⇒ 调用方没有改写它们清单的路;
23
+ * 契约给这两个域的写角色(`writeRoleFor(域, [])`)不低于本表 `global` 的门槛(治理写角色),同名时部署的那只胜出、请求体那只被丢(`buildRunSkills`)⇒ 调用方没有改写它们清单的路;
24
24
  * 点名只是选用。契约哪天把那两个域的写角色压到治理写角色之下,`test/caller-trust.test.ts` 的角色阶钉红,这条读法要重审。
25
25
  * 准入只经契约导出:桶非空且 `!isAtLeast(档, memberAdmission(…).minTrust)` ⇒ 不准入;不按方向开特例(契约的层准入同样先判 floor)。
26
26
  * 不准入 ⇒ 受理口 400,体与条目问题同形(`entry-problems.ts`:根上的 `field` + `problems` + `total`),原因词 = 契约 `LAYER_ADMISSION_VERDICTS` 的 `below_trust_floor`,
@@ -1,4 +1,4 @@
1
- import { domainWriteRole, isAtLeast, isRole, memberAdmission, membersOf, roleAtLeast, USER_ROLES, } from "@sema-agent/settings-schema";
1
+ import { isAtLeast, isRole, memberAdmission, membersOf, roleAtLeast, USER_ROLES, writeRoleFor, } from "@sema-agent/settings-schema";
2
2
  import { entryProblem, entryProblemsFacts } from "../entry-problems.js";
3
3
  import { ssoVerifiedRole } from "../security.js";
4
4
  import { ruleTextOf } from "../permission-rule-list.js";
@@ -14,7 +14,7 @@ export function callerTrust(req, config) {
14
14
  const role = ssoVerifiedRole(req);
15
15
  if (role === undefined || !isRole(role))
16
16
  return { trust: "project", basis: "sign_in_role_unrecognized" };
17
- return { trust: roleAtLeast(role, domainWriteRole("governance")) ? "global" : "project", basis: "sign_in_role", role };
17
+ return { trust: roleAtLeast(role, writeRoleFor("governance", [])) ? "global" : "project", basis: "sign_in_role", role };
18
18
  }
19
19
  case "direct_door_jwt":
20
20
  return { trust: "project", basis: "identity_without_role" };
@@ -117,7 +117,7 @@ function directionLoosens(direction) {
117
117
  }
118
118
  }
119
119
  function basisClause(reading) {
120
- const governanceRole = domainWriteRole("governance");
120
+ const governanceRole = writeRoleFor("governance", []);
121
121
  const reach = `the deployment's service credential, or a registry sign-in token whose role in its active scope is "${governanceRole}" or higher (the role that may write the scope's governance), is admitted at "global"`;
122
122
  switch (reading.basis) {
123
123
  case "single_user":
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 续跑重建输入(`checkpoint_ctx` 的行体,{@link CheckpointCtx})的**写形**,以及「这条停驻行的持久化体在本版上能否重放」的四问与它们的答复。
2
+ * 续跑重建输入(`checkpoint_ctx` 的行体,{@link CheckpointCtx})的**写形**,以及「这条停驻行的持久化体在本版上能否重放」的三问与它们的答复。
3
3
  *
4
4
  * ## allow 读法问(常设规则,按行的写者版本;不是迁移、不读记号)
5
5
  * `settings.permissions.allow` 的读法换过两次:≤7.114.x 是「名单」(名单外的工具一律拒);7.115.0–7.116.x 是「表态」(只回答没人表态的那一问);
@@ -17,30 +17,20 @@
17
17
  * 7.42 什么也不拒的条目)、子代定义的 `denyTools` / `allowTools`、顶层 `excludeTools` / `allowTools`(7.131.0:限制侧近似名 / 大小写孪生、退役名、文法读不出,放行侧只近似名)—— 有引擎读成空的条目 ⇒ 提交 400(`resolveSpec`,拒点);
18
18
  * 存量停驻体重放 ⇒ 409 `resume_blocked_by_policy`(`blockedBy` = 该层的拒码)。判据函数 = `rule-entry-list.ts` `inertToolListEntryOf`。
19
19
  *
20
- * ## 模式词问(7.122.0):体里模式词的读法记号
21
- * 7.122.0 起一条 run 的模式词只有一个源 —— 存储的提交体顶层 `permissionMode`(提交入口把运维 `AUTONOMY=plan` 的地板盖进去一次;`plan_review` 写回改它),
22
- * 治理层不再在每一次续跑重钳。更早版本写下的行体里是**请求的原词**,运维的 plan 由治理层每段重新折上(7.121.x 及以前:只读手 + 写面 roots)。
23
- * 那种行在本版上按体里的原词重放 = 静默放宽(`acceptEdits` / `bypassPermissions` 原词直接生效;无词 ⇒ 引擎读 checkpoint 记下的词)。
24
- * ⇒ 同一条规则:**写模式词的人盖读法记号** —— 模式词的写者:每一个出生点(新的持久化写都经 {@link buildCheckpointCtx})与 `plan_review` 写回(经 {@link withPermissionModeRewritten}),各自写词的同一拍在 ctx 上写记号
25
- `permissionModeReading`(闭集只有当前一词 {@link PERMISSION_MODE_READINGS});续跑重放之前,**本部署此刻有入口地板**
26
- * (`runtime-governance.ts` `autonomyModeFloor`)而行上没有当前记号、且体的生效词被地板钳会变 ⇒ 拒(行字节不动)。没有地板的部署、体的词本就在地板之下(`plan`)的行
27
- * 不受影响:那两种行在新旧两种读法下答同一个词。出路:作为新任务重提(仍停驻的先取消),或在它们收尾之前把 `AUTONOMY` 设成 `read-only`(按段的只读围栏,
28
- * 与 7.121.x 的 `AUTONOMY=plan` 同一个结局,且没有入口地板 ⇒ 不拒)。删除条件登记在 `plugins/one-time-migrations.ts` 的 M12 行。
29
- *
30
20
  * ## 退役请求键问(7.137.0):体里的退役请求键
31
21
  * 请求键退役之后({@link RETIRED_REQUEST_KEYS}:今天两只 —— `promptProfile` 换成布尔 `promptLean`,`rewindFiles` 是退役的旧文件还原拼法),新提交带它在键闭集门 400。存体里的旧键怎么办,闭集里每个键有
32
22
  * **一个处置格**,住在哪张表就是哪个处置:**拒**表({@link REPLAY_REFUSED_RETIRED_KEYS},今天 `promptProfile`)是塑形过任务本身的键,**惰性**表({@link INERT_RETIRED_KEYS},今天 `rewindFiles`)是一次性键。
33
23
  * · 拒表的键:存体里的旧键由一次性迁移在起服时原地改写
34
24
  * (M30,`stored-request-prompt-profile-m30.ts`,行改写经本模块的 {@link rewriteStoredBodyKeys})。改写不到的残留行(窗内从没起过服的部署;起服扫描之后滚动升级里
35
- * 旧副本才写下的行)体里仍带旧键 ⇒ 拒({@link retiredRequestKeyRefusal},码同模式词读法拒:`checkpoint.unsupported_version`)。**没有**续跑时按旧键读的第二种读法,
25
+ * 旧副本才写下的行)体里仍带旧键 ⇒ 拒({@link retiredRequestKeyRefusal},码同 allow 读法拒:`checkpoint.unsupported_version`)。**没有**续跑时按旧键读的第二种读法,
36
26
  * 也**没有**「旧键无人读、静默续跑」的臂:旧键还在 = 这份体没被改写过,它当年要的形本版读不出来。
37
27
  * · 惰性表的键:一次性键 —— 只在提交的那一拍起作用(文件还原的一次性动作),续跑重建里没有任何一条腿读它,存体里留着它不要求任何事、不改变重放要做的事 ⇒ 不拒、不改写、续跑照常。
38
28
  * 这不是上面那条「旧键无人读、静默续跑」的臂:那一臂的前提是读不出旧键会让续跑做一件提交时没要的事,惰性格的判据恰是「没有这个键,重放做的事一字不差」。
39
29
  * 加一个退役键必须选格 —— 进拒表 ⇒ {@link retiredRequestKeyRefusal} 的 switch 与 `stored-body-replay.ts` 的迁移 id 表编译红,直到给它一句拒句和改写它的迁移;进惰性表 ⇒ 改动里要写明它为什么是一次性的。
40
30
  *
41
31
  * ## 一只判据、五个问点(`http/stored-body-replay.ts` 的 `storedBodyReplayRefusal` / 本模块的 {@link storedBodyReplayAnswer})
42
- * 「这条停驻行的持久化体在本版上能否重放」只有**一个**读法:上面四问(加 `settings.env` 凭据那一臂,规则在 `stored-request-env-credentials-m38.ts`)合在 `stored-body-replay.ts` 的 `storedBodyReplayRefusal` 一处(allow 读法问的谓词在本模块;工具名清单问另读请求设置的解析层与
43
- * 执行车道 / 配置根两个属主、模式词问读请求设置与治理两个属主 —— 本模块的值依赖要留在 node 内建 / 引擎包根之内,随包排空脚本经编译产物读它),答复合在 {@link storedBodyReplayAnswer} 一处。
32
+ * 「这条停驻行的持久化体在本版上能否重放」只有**一个**读法:上面三问(加 `settings.env` 凭据那一臂,规则在 `stored-request-env-credentials-m38.ts`)合在 `stored-body-replay.ts` 的 `storedBodyReplayRefusal` 一处(allow 读法问的谓词在本模块;工具名清单问另读请求设置的解析层与
33
+ * 执行车道 / 配置根两个属主 —— 本模块的值依赖要留在 node 内建 / 引擎包根之内,随包排空脚本经编译产物读它),答复合在 {@link storedBodyReplayAnswer} 一处。
44
34
  * 问点五个,同调同答:续跑驱动段(`resume-legs.ts` `resolveResumeSpecOrBlocked`,另四条腿的唯一判点)· plan_review 的 prepare 段(读类拒,在 nonce 消费之前)·
45
35
  * SLA 拒扫腿的跳过臂(`approval-sweeps.ts`)· 终态 run 的 steer 铸唤醒卡之前(`routes/runs.ts`:铸卡前先证明它赎得回,证不出 ⇒ 不铸、当场同一个拒)·
46
36
  * `resolveSpec` 本身留作真正的拒点(提交腿 400;续跑腿在到它之前已被本判据答掉)。再开第六个问点必须调这两只,不许各判各的 —— 此前铸卡前只判了
@@ -50,9 +40,9 @@
50
40
  * 体里请求面凭据位上的值在 ctx 里只以封签存在 —— 写:{@link buildCheckpointCtx} / {@link sealCheckpointCtx} 一只封口;读:续跑上下文读口里一只开口
51
41
  * {@link openCheckpointCtx};开不开 ⇒ `409 conflict.checkpoint_ctx_unsealable`。规则全文在本文件末段「凭据位密封」的头注。
52
42
  */
53
- import type { PermissionMode } from "@sema-agent/core";
54
43
  import type { CheckpointCtx } from "./route-ctx.js";
55
44
  import type { TaskRequestBody } from "./wire-types.js";
45
+ import type { ProjectContext } from "../project-context.js";
56
46
  import type { InertToolListEntry } from "../rule-entry-list.js";
57
47
  import { type Refusal } from "./refusal-body.js";
58
48
  /** allow 层读法从这一版起(7.117.0):此前的写者对 `settings.permissions.allow` 是另一种读法(≤7.114.x 名单 / 7.115.0–7.116.x 表态)。规则见顶注「allow 读法问」。 */
@@ -76,16 +66,10 @@ export declare function allowListWriterUnproven(ctx: CheckpointCtx): boolean;
76
66
  * 🔴 形是给冻结门看的:`status: <三位数>, body: refusalBody({ errorCode: "<码>", message: "<整句双引号字面>"`,中间只许有空白。
77
67
  */
78
68
  export declare function allowListReadingRefusal(): ResumeRefusalAnswer;
79
- /** 体里模式词的读法记号闭集(一词 = 当前读法):这一行体的顶层 `permissionMode` 是这条 run 模式词**唯一的源**(提交入口盖过运维地板、`plan_review` 写回改它),
80
- * 续跑照它读、不再由治理层按段重钳。写点两个 = {@link buildCheckpointCtx}(每一次新的持久化写都写)与 {@link withPermissionModeRewritten}(`plan_review` 写回改词的那一拍写);
81
- * **不改词**的续跑腿写回传原 ctx ⇒ 记号随行。 */
82
- export declare const PERMISSION_MODE_READINGS: readonly ["entry_word"];
83
- export type PermissionModeReading = (typeof PERMISSION_MODE_READINGS)[number];
84
69
  /**
85
- * 一次**新的**持久化写的 ctx(提交腿 / `POST /v1/runs` 族 / leader worker)—— 写记号的唯一口。
70
+ * 一次**新的**持久化写的 ctx(提交腿 / `POST /v1/runs` 族 / leader worker)—— 构造口。
86
71
  *
87
- * 续跑腿的**写回**不经本函数:**不改词**的写回传读进来的 ctx(记号随行,不重新判 —— 在写回口重新判会把一条没记号的旧行「洗」成新行);
88
- * **改词**的写回(只有 `plan_review` 的批准 / 编辑)经 {@link withPermissionModeRewritten}。两种写回落盘之前都过同一只封口 {@link sealCheckpointCtx}。
72
+ * 续跑腿的**写回**不经本函数:写回传读进来的 ctx(或改了体的展开,其余位随行),落盘之前都过同一只封口 {@link sealCheckpointCtx}。
89
73
  *
90
74
  * `sealer` / `sessionId`:凭据位密封的两件(本文件末段)—— `sessionId` 是这一行的会话(绑进封签);`sealer` 必传,缺席也要说出来(`undefined`)。
91
75
  */
@@ -94,31 +78,9 @@ export declare function buildCheckpointCtx(fields: {
94
78
  memoryScope?: string | undefined;
95
79
  unattendedLeg?: true;
96
80
  promptScreened?: true;
81
+ projectContext?: ProjectContext | undefined;
97
82
  writerVersion: string;
98
83
  }, sealer: CheckpointCtxSealer | undefined, sessionId: string): SealedCheckpointCtx;
99
- /**
100
- * 模式词的**第二个写者** —— `plan_review` 的批准 / 编辑把这条 run 的体词从 `plan` 写成批准的档(`resume-legs.ts` `drivePlanReview`,调用点只有那一处;
101
- * `test/allow-list-reading-park-refusal.test.ts` 枚举调用点,多一处即红)。与 {@link buildCheckpointCtx} **同一条规则**:按当前读法写下的词,盖当前读法记号。
102
- *
103
- * 为什么记号必须随词一起写,而不是留给读的一侧去豁免:模式词问问的是「体的词是不是按当前读法写下的」,记号就是这句话的证据。改写之后体里的词是本版的 `plan_review`
104
- * 刚写下的(一个人刚批准的档),它确实是 `entry_word` 读法的词;若只改体、记号缺席,重建段的同一问把它读成「7.121.x 的请求原词」,被地板钳回 `plan` ≠ 所写词 ⇒ 误拒
105
- * 一条刚被批准的 run。判据侧(`stored-body-replay.ts`)**不为「plan_review 之后」另开特判臂** —— 读的一侧只有一个读法,写的一侧各自负责盖章。
106
- *
107
- * 为什么这不是在「洗」旧行:走到写回口的行,其**原体**已过 prepare 段的同一问(词被地板钳不变:词已是 `plan`,或本部署没有地板);原体的词会被地板钳的旧行
108
- * (`acceptEdits` / `bypassPermissions` / 无词)在 prepare 段就被拒,到不了这里。入参 `ctx` 的其余位(memoryScope / 无人值守标)原样随行,只换体与模式词记号。
109
- *
110
- * 本模块零值依赖的闭包纪律不变(随包排空脚本经编译产物 import;`test/drain-check-local.test.ts`):本函数只用本模块内的东西。
111
- */
112
- export declare function withPermissionModeRewritten(ctx: CheckpointCtx, body: TaskRequestBody): CheckpointCtx;
113
- /** 行上带的是不是当前的模式词读法记号(模式词问的记号那一半;另一半 —— 体的生效词被地板钳会不会变 —— 读请求设置的解析层,住在 `http/stored-body-replay.ts`)。
114
- * 记号缺席或认不得 ⇒ 不是「按当前读法写下」的证据。 */
115
- export declare function permissionModeReadingStated(ctx: CheckpointCtx): boolean;
116
- /**
117
- * 模式词问的拒答复 —— 码 `checkpoint.unsupported_version`,409(卡仍 pending 时体上的 `retriable:true` 由 {@link resumeRefusalWithCard} 追加,本铸点不带)。
118
- * 句子说原因(更早的版本在每次续跑重钳运维的 AUTONOMY、没把它记进任务;本版只在提交时记一次 ⇒ 续跑会按任务提交时的原词跑)与两条出路。
119
- * 🔴 形是给冻结门看的:`status: <三位数>, body: refusalBody({ errorCode: "<码>", message: "<整句双引号字面>"`,中间只许有空白。
120
- */
121
- export declare function permissionModeReadingRefusal(): ResumeRefusalAnswer;
122
84
  /**
123
85
  * 退役请求键问:**退役的请求键**闭集 = 两张处置表之并(新提交在键闭集门 400;存体里还带着它 = 这份体是更早的写者写下的)。键住在哪张表就是它的处置格,规则全文见头注「退役请求键问」。
124
86
  * 拒表:存体仍带它 ⇒ 续跑重放拒({@link retiredRequestKeyRefusal});加一个 ⇒ 加一行,那只函数的 switch 编译红直到给它一句拒句。今天一只:`promptProfile`(7.137.0 起换成布尔 `promptLean`;存体由 M30 起服改写)。
@@ -132,7 +94,7 @@ export type RetiredRequestKey = (typeof RETIRED_REQUEST_KEYS)[number];
132
94
  /** 存体顶层带的第一只**需要拒**的退役请求键(拒表序);惰性键不算,没有 ⇒ `undefined`。只看自有键(原型上的同名不算)。 */
133
95
  export declare function retiredRequestKeyOf(body: unknown): ReplayRefusedRetiredKey | undefined;
134
96
  /**
135
- * 退役请求键问的拒答复 —— 码与落点同模式词读法拒(`checkpoint.unsupported_version`,409;卡仍 pending 时的 `retriable:true` 由 {@link resumeRefusalWithCard} 追加,本铸点不带)。
97
+ * 退役请求键问的拒答复 —— 码与落点同 allow 读法拒(`checkpoint.unsupported_version`,409;卡仍 pending 时的 `retriable:true` 由 {@link resumeRefusalWithCard} 追加,本铸点不带)。
136
98
  * 句子说键、它换成了什么、为什么不续跑,与两条出路(运维在一次性迁移的窗内重启本服务 ⇒ 起服改写存体、同一请求可再发;否则作为新任务重提,仍停驻的先取消)。不回显值。
137
99
  * 🔴 形是给冻结门看的:`status: <三位数>, body: refusalBody({ errorCode: "<码>", message: <整句双引号字面 或 一只模板串>`,中间只许有空白(模板串形冻结的是骨架,`${…}` 折成一个洞)。
138
100
  * **窗的日期不手写**:`rewriteWindowEnd` 由调用方交 —— 判据(`stored-body-replay.ts`)按「退役键 → 改写它的迁移」那张表读登记表该行的 `removableAfter`,窗只写在登记表一处;
@@ -142,7 +104,7 @@ export declare function retiredRequestKeyOf(body: unknown): ReplayRefusedRetired
142
104
  export declare function retiredRequestKeyRefusal(key: ReplayRefusedRetiredKey, rewriteWindowEnd: string): ResumeRefusalAnswer;
143
105
  /**
144
106
  * 一条停驻行的持久化体在本版上**不能**重放时,是哪一条规则拒的(闭集;加一条 ⇒ {@link storedBodyReplayAnswer} 的 switch 编译红)。
145
- * 答 `checkpoint.unsupported_version` 的三臂随带这一行的写者版本(续跑上下文的 `writerVersion`,缺席 = 本版之前写下的行)—— 那一码的两版本键由它铸({@link buildCheckpointVersionKeys})。
107
+ * 答 `checkpoint.unsupported_version` 的两臂随带这一行的写者版本(续跑上下文的 `writerVersion`,缺席 = 本版之前写下的行)—— 那一码的两版本键由它铸({@link buildCheckpointVersionKeys})。
146
108
  */
147
109
  export type StoredBodyReplayRefusal =
148
110
  /** 体里带非空 `settings.permissions.allow`、写者版本证不出 ≥ {@link ALLOW_LAYER_READING_SINCE}(顶注「allow 读法问」)。 */
@@ -165,11 +127,6 @@ export type StoredBodyReplayRefusal =
165
127
  readonly refusal: Refusal;
166
128
  readonly members: readonly string[];
167
129
  readonly memberCount: number;
168
- } | {
169
- readonly kind: "permission_mode_reading";
170
- readonly stored: PermissionMode | undefined;
171
- readonly floor: PermissionMode;
172
- readonly writerVersion?: string;
173
130
  } | {
174
131
  readonly kind: "retired_request_key";
175
132
  readonly key: ReplayRefusedRetiredKey;
@@ -178,8 +135,8 @@ export type StoredBodyReplayRefusal =
178
135
  readonly writerVersion?: string;
179
136
  };
180
137
  /**
181
- * 🔴 **`checkpoint.unsupported_version` 的 409 体上两个版本键 —— 唯一铸法;本码每一个铸点同一条律**(core 原码镜像 = `resume-legs.ts` 的 catch 臂;本服务自铸的三只拒
182
- * = {@link storedBodyReplayAnswer} 的 allow 读法 / 模式词读法 / 退役请求键三臂):续跑上下文带写者版本 ⇒ `{ writerVersion, readerVersion }`;不带(本版之前写下的行)⇒ `{}`,不编值。
138
+ * 🔴 **`checkpoint.unsupported_version` 的 409 体上两个版本键 —— 唯一铸法;本码每一个铸点同一条律**(core 原码镜像 = `resume-legs.ts` 的 catch 臂;本服务自铸的两只拒
139
+ * = {@link storedBodyReplayAnswer} 的 allow 读法 / 退役请求键两臂):续跑上下文带写者版本 ⇒ `{ writerVersion, readerVersion }`;不带(本版之前写下的行)⇒ `{}`,不编值。
183
140
  * 两值相等也照带 —— 那是事实(不是版本问题,出路照码与报文);写者比读者新才是「回到写它的那一版去决」。`readerVersion` 由调用方传本进程的服务版本
184
141
  * (本模块零值依赖,不自取)。纯数据。
185
142
  */
@@ -203,7 +160,7 @@ export interface ResumeRefusalAnswer {
203
160
  *
204
161
  * 🔴 **句尾点名出口**:这一拒不消费卡(卡仍 pending、run 行留停驻),被挡在一张 deny 不掉的卡前面的调用方需要知道怎么收场 ——
205
162
  * `POST /v1/runs/:id/cancel` 对停驻的 run 直接结算(CAS 过期该卡再终局 run 行,不跑模型轮)。句子写「若仍停驻」而不是随卡有无换话:有卡 / 无卡(终态 run 的 steer 在铸唤醒卡之前拒)
206
- * 两种处境除 `retriable` 外逐字同体(同判同答),与另两类存量体拒(allow 读法 / 模式词读法)句子里的出口是同一个端点、同一种写法。
163
+ * 两种处境除 `retriable` 外逐字同体(同判同答),与 allow 读法那一类存量体拒句子里的出口是同一个端点、同一种写法。
207
164
  *
208
165
  * 本铸点**不带** `retriable`:那是调用点的事实(有没有一张待批卡仍在等),由 {@link resumeRefusalWithCard} 一处追加 —— 续跑入口(卡仍 pending、run 行留 `suspended`)
209
166
  * 带 `retriable:true`,终态 run 的 steer 在铸唤醒卡之前拒(没有卡)不带。键集 = `errorCode` · `error` · `blockedBy` · (`field` · `reason` · 其余 `extra`,有才在)
@@ -241,16 +198,14 @@ export declare function storedBodyReplayLogFields(refusal: StoredBodyReplayRefus
241
198
  kind: StoredBodyReplayRefusal["kind"];
242
199
  writerVersion?: string;
243
200
  layer?: InertToolListEntry["layer"];
244
- storedMode?: PermissionMode | null;
245
- floor?: PermissionMode;
246
201
  key?: ReplayRefusedRetiredKey;
247
202
  members?: readonly string[];
248
203
  memberCount?: number;
249
204
  note: string;
250
205
  };
251
206
  /**
252
- * `stored-body-replay.ts` `storedBodyReplayRefusal` 的答复:每条规则一只既有铸点(allow 读法 ⇒ {@link allowListReadingRefusal};模式词记号 ⇒ {@link permissionModeReadingRefusal};
253
- * 退役请求键 ⇒ {@link retiredRequestKeyRefusal};这三只答 `checkpoint.unsupported_version`,体按那一码的键律补两版本键,`readerVersion` = 调用方传的本进程服务版本;工具名清单 ⇒ {@link resumeBlockedByPolicyAnswer},与提交腿的 400
207
+ * `stored-body-replay.ts` `storedBodyReplayRefusal` 的答复:每条规则一只既有铸点(allow 读法 ⇒ {@link allowListReadingRefusal};
208
+ * 退役请求键 ⇒ {@link retiredRequestKeyRefusal};这两只答 `checkpoint.unsupported_version`,体按那一码的键律补两版本键,`readerVersion` = 调用方传的本进程服务版本;工具名清单 ⇒ {@link resumeBlockedByPolicyAnswer},与提交腿的 400
254
209
  * (该层的拒码:`request.field_invalid` / `config.agent.unknown_tool` / `config.tool_face_control`)同码同句 —— 体上只有这一个问题时;存量体同时还踩中 `resolveSpec` 更早的拒〔三桶超帽 / 托管姿态〕时,提交侧答更早那条、续跑侧答本句,两边都是拒,指路不同)。
255
210
  *
256
211
  * 🔴 `cardPending` —— **调用方交出的事实**:这一次拒的时候,有没有一张待批卡仍在等。`retriable:true` 说的恰是「卡仍 pending」,所以它只在卡在时在场(经
@@ -268,7 +223,7 @@ export declare function storedBodyReplayAnswer(refusal: StoredBodyReplayRefusal,
268
223
  * 封签里装的是那个 JSON 值本身,开封逐字还原。字典位上的容器本身不是字典(串 / 数组 / `null` / 封签形对象)⇒ 整只容器当一个值封。
269
224
  * 于是落盘形里凭据位上**只有本封口铸的封签**,没有哪个值能「原样存下、续跑时被开成别的东西」(调用方送一只封签形对象 ⇒ 存的是再封一层,
270
225
  * 开出来还是那只对象 —— 与新提交腿看到的同一个值)。没有封签器而有值要封 ⇒ 抛 {@link CheckpointCtxUnsealableError}(`no_key_store`,调用方在写之前拒)。
271
- * · **读法记号**(与本文件另两只记号同一条「写的人盖章」):封口铸出的每一份落盘形都带 `credentialSeal: CHECKPOINT_CTX_SEAL_ALG` 与
226
+ * · **读法记号**(与写者版本同一条「写的人盖章」):封口铸出的每一份落盘形都带 `credentialSeal: CHECKPOINT_CTX_SEAL_ALG` 与
272
227
  * `credentialSlotGeneration`(= 这一行里**带值的**凭据位最新的那一代,最小 1)—— 这一行的凭据位全部由本封口封过,且它用到的位不比这一代新。
273
228
  * 代数取「用到的」而不取「本版的」:不带新位值的行与上一代的写者写下的同形,回滚到上一代照读;代数比读者新 ⇒ 这一行**一定**带着读者不认识的位 ⇒ 读者整行拒。没有记号的行是 7.134.0 之前的写者写下的:
274
229
  * 那时没有任何写者封值,所以它凭据位上的封签形对象只可能是调用方原样送来的,不是封签。代数缺席的带记号行是 7.134.0 的写者写下的 —— 它封的是第 1 代
@@ -447,7 +402,7 @@ export declare function sealedCredentialFieldOf(stored: unknown): string | undef
447
402
  * · 带当前记号、而某一位比行封过的代数新的行(7.134.0 的写者封第 1 代,没封第 2 代的 `plugin.options`)—— 那一位上的值不论长什么样都是明文 ⇒ 封
448
403
  * (恰好长成封签形的选项字典按字典容器整只封,开出来还是那只字典);行封过的那一代里的封签不动。改写后的行盖「带值的位最新的那一代」(与封口同一条)。
449
404
  * 读口会答 `seal` / `unrecognized_seal` 的值不动(认不得的留给读口拒);一个值都没封 ⇒ `undefined`(不写:第二跑零命中;记号认不得的行因此一个字节不动);
450
- * 体不是对象 ⇒ `undefined`(读口自己的形判接住)。其余位(另两只记号、写者版本、非凭据字段)原样。
405
+ * 体不是对象 ⇒ `undefined`(读口自己的形判接住)。其余位(写者版本、非凭据字段)原样。
451
406
  */
452
407
  export declare function sealStoredCheckpointCtxM28(stored: object, sealer: CheckpointCtxSealer, sessionId: string): {
453
408
  readonly ctx: SealedCheckpointCtx;
@@ -455,7 +410,7 @@ export declare function sealStoredCheckpointCtxM28(stored: object, sealer: Check
455
410
  } | undefined;
456
411
  /**
457
412
  * 🔴 **一次性迁移对存量行非凭据顶层体键的改写**(品牌铸点之三;今天的调用方 = M30 的行改写 `stored-request-prompt-profile-m30.ts`):在一行落盘形的体上删 `drop` 里的顶层键、
458
- * 设 `set` 里的顶层键,其余一个字节不动 —— 行上的读法记号(凭据封口的 `credentialSeal` / `credentialSlotGeneration` 与另两只记号)与凭据位上的值原样随行:封好的行仍是封好的,
413
+ * 设 `set` 里的顶层键,其余一个字节不动 —— 行上的读法记号(凭据封口的 `credentialSeal` / `credentialSlotGeneration`)与凭据位上的值原样随行:封好的行仍是封好的,
459
414
  * 更早写者留下的明文行仍是明文(读口照旧拒 `plaintext`,本口不替它洗白)。`drop` / `set` 碰到凭据位的根键(闭集各行路径的第一段)或 `__proto__` ⇒ 抛(程序错,不是数据错)。
460
415
  * 体不是对象 ⇒ 抛(店的判形保证 ctx 是对象;体坏形的行由调用方先判、不交来)。
461
416
  */
@@ -36,7 +36,6 @@ export function allowListReadingRefusal() {
36
36
  body: refusalBody({ errorCode: "checkpoint.unsupported_version", message: "this task's stored request carries settings.permissions.allow and was written by a server version that is earlier than 7.117.0 or that did not record its version (versions before 7.124.0 did not), so this server cannot tell how that list was meant to be read: 7.114.0 and earlier read it as a whitelist (every tool not on it was refused) and 7.115.0 to 7.116.x as a statement that answered only the question nobody else had answered, while this version hands it to the engine's allow layer, where each entry is a standing yes for the calls it covers, so continuing could give the task more tools or fewer questions than it was submitted with. Nothing was decided and nothing was resumed, and retrying will be refused the same way: submit the work again as a new task, and if this task is still suspended cancel it first (POST /v1/runs/:id/cancel)." }),
37
37
  };
38
38
  }
39
- export const PERMISSION_MODE_READINGS = ["entry_word"];
40
39
  export function buildCheckpointCtx(fields, sealer, sessionId) {
41
40
  const ctx = {
42
41
  body: fields.body,
@@ -44,26 +43,10 @@ export function buildCheckpointCtx(fields, sealer, sessionId) {
44
43
  ...(fields.memoryScope !== undefined ? { memoryScope: fields.memoryScope } : {}),
45
44
  ...(fields.unattendedLeg === true ? { unattendedLeg: true } : {}),
46
45
  ...(fields.promptScreened === true ? { promptScreened: true } : {}),
47
- permissionModeReading: "entry_word",
46
+ ...(fields.projectContext !== undefined ? { projectContext: fields.projectContext } : {}),
48
47
  };
49
48
  return sealCheckpointCtx(ctx, sealer, sessionId);
50
49
  }
51
- export function withPermissionModeRewritten(ctx, body) {
52
- return { ...ctx, body, permissionModeReading: "entry_word" };
53
- }
54
- export function permissionModeReadingStated(ctx) {
55
- const reading = ctx.permissionModeReading;
56
- return typeof reading === "string" && PERMISSION_MODE_READINGS.includes(reading);
57
- }
58
- export function permissionModeReadingRefusal() {
59
- return {
60
- status: 409,
61
- body: refusalBody({
62
- errorCode: "checkpoint.unsupported_version",
63
- message: "this task was suspended by an earlier server version that re-applied this deployment's AUTONOMY on every resume instead of recording it in the task; this version records the operator's plan floor once, when a task is submitted, so continuing would run this task under the permission mode it was submitted with rather than the one it ran under. Nothing was decided and nothing was resumed, and retrying will be refused the same way: submit the work again as a new task, and if this task is still suspended cancel it first (POST /v1/runs/:id/cancel); to let it finish with read-only hands instead, set AUTONOMY=read-only until it does.",
64
- }),
65
- };
66
- }
67
50
  export const REPLAY_REFUSED_RETIRED_KEYS = ["promptProfile"];
68
51
  export const INERT_RETIRED_KEYS = ["rewindFiles"];
69
52
  export const RETIRED_REQUEST_KEYS = [...REPLAY_REFUSED_RETIRED_KEYS, ...INERT_RETIRED_KEYS];
@@ -116,8 +99,6 @@ export function storedBodyReplayLogFields(refusal) {
116
99
  return { kind: refusal.kind, note: "this session's stored request carries credentials in settings.env (a credential variable name of this engine, or a value shaped like a secret) and the one-time window that dropped them on resume has closed; it is not replayed — the task stays suspended until someone cancels it and resubmits with the credentials moved into the engine's environment" };
117
100
  case "unread_members":
118
101
  return { kind: refusal.kind, members: refusal.members, memberCount: refusal.memberCount, note: "this session's stored request carries members this version's readers cannot take as written (named in the refusal: settings.hooks / settings.env / an MCP server's transport / an A2A peer's headers …) and this deployment would apply them; replaying it would run without them, so it is not replayed — the task stays suspended until someone cancels it and resubmits with those members in the current form" };
119
- case "permission_mode_reading":
120
- return { kind: refusal.kind, storedMode: refusal.stored ?? null, floor: refusal.floor, note: "this session's stored request was written before the operator's AUTONOMY plan floor was recorded in the task (earlier versions re-applied it on every resume); replaying it now would run under its submitted permission mode — the task stays suspended until someone cancels it, or AUTONOMY is set to read-only so it can finish with read-only hands" };
121
102
  case "retired_request_key":
122
103
  return { kind: refusal.kind, key: refusal.key, note: "this session's stored request still carries a request key this version retired, and the one-time rewrite at startup did not reach it (no startup of this version inside its window since the row was written); it is not replayed — the task stays suspended until someone cancels it, or a restart of this version inside the window rewrites the row" };
123
104
  default: {
@@ -140,10 +121,6 @@ function storedBodyReplayArmAnswer(refusal, readerVersion) {
140
121
  case "settings_env_credential":
141
122
  case "unread_members":
142
123
  return resumeBlockedByPolicyAnswer(refusal.refusal);
143
- case "permission_mode_reading": {
144
- const answer = permissionModeReadingRefusal();
145
- return { status: answer.status, body: { ...answer.body, ...buildCheckpointVersionKeys(refusal.writerVersion, readerVersion) } };
146
- }
147
124
  case "retired_request_key": {
148
125
  const answer = retiredRequestKeyRefusal(refusal.key, refusal.rewriteWindowEnd);
149
126
  return { status: answer.status, body: { ...answer.body, ...buildCheckpointVersionKeys(refusal.writerVersion, readerVersion) } };