@sema-agent/server 7.151.0 → 7.152.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 (286) hide show
  1. package/MIGRATION.md +266 -0
  2. package/README.md +15 -3
  3. package/README.zh-CN.md +10 -2
  4. package/USAGE.md +93 -64
  5. package/deploy/sema-up/README.md +5 -4
  6. package/deploy/sema-up/cluster-up.sh +6 -6
  7. package/deploy/sema-up/kube-up.sh +5 -5
  8. package/deploy/sema-up/lib/flagshape.sh +36 -8
  9. package/deploy/sema-up/machines.example.yaml +3 -1
  10. package/deploy/sema-up/sema-up.sh +22 -16
  11. package/dist/approval-ask-audit-store.d.ts +1 -1
  12. package/dist/approval-ask-machine.d.ts +4 -10
  13. package/dist/approval-ask-retention.d.ts +1 -1
  14. package/dist/approval-card.d.ts +10 -12
  15. package/dist/approval-gate-currency.d.ts +8 -8
  16. package/dist/approval-gate-currency.js +3 -4
  17. package/dist/approval-hmac.d.ts +17 -1
  18. package/dist/approval-hmac.js +7 -1
  19. package/dist/approval-queue-kind.d.ts +1 -1
  20. package/dist/approval.d.ts +2 -2
  21. package/dist/approval.js +2 -2
  22. package/dist/bind-host.d.ts +1 -3
  23. package/dist/bind-host.js +2 -3
  24. package/dist/boot/coordinators.d.ts +1 -1
  25. package/dist/boot/coordinators.js +9 -10
  26. package/dist/boot/device-lane.js +2 -0
  27. package/dist/boot/execution-env.d.ts +1 -0
  28. package/dist/boot/execution-env.js +31 -5
  29. package/dist/boot/leader.d.ts +31 -30
  30. package/dist/boot/leader.js +38 -28
  31. package/dist/boot/leg-path-adjudication.d.ts +40 -0
  32. package/dist/boot/leg-path-adjudication.js +18 -0
  33. package/dist/boot/parked-revive-gate.js +3 -15
  34. package/dist/boot/reapers.d.ts +1 -1
  35. package/dist/boot/reapers.js +1 -1
  36. package/dist/boot/resolve-spec.js +12 -26
  37. package/dist/boot/runner-deps.d.ts +39 -35
  38. package/dist/boot/runner-deps.js +92 -89
  39. package/dist/boot/shutdown.d.ts +6 -0
  40. package/dist/boot/shutdown.js +10 -4
  41. package/dist/boot/stage-02-web-admin-pull.js +4 -5
  42. package/dist/boot/stage-05-execution-env.js +2 -1
  43. package/dist/boot/stage-06-runners.d.ts +2 -0
  44. package/dist/boot/stage-06-runners.js +10 -4
  45. package/dist/boot/stage-07-capability-layer.d.ts +1 -0
  46. package/dist/boot/stage-07-capability-layer.js +1 -1
  47. package/dist/boot/stage-08-reapers.d.ts +1 -0
  48. package/dist/boot/stage-09-leader.d.ts +1 -0
  49. package/dist/boot/stage-09-leader.js +2 -2
  50. package/dist/boot/stage-10-http-server.d.ts +8 -0
  51. package/dist/boot/stage-10-http-server.js +11 -15
  52. package/dist/boot/stores.js +7 -4
  53. package/dist/boot/web-admin.js +2 -7
  54. package/dist/capabilities/approvals-push.d.ts +3 -3
  55. package/dist/capabilities/scenarios.d.ts +28 -13
  56. package/dist/capabilities/scenarios.js +2 -2
  57. package/dist/capability-facts.d.ts +22 -20
  58. package/dist/capability-facts.js +18 -34
  59. package/dist/card-rule-offers.d.ts +32 -0
  60. package/dist/card-rule-offers.js +130 -0
  61. package/dist/config-catalog.js +12 -11
  62. package/dist/config-dir-trust.js +4 -2
  63. package/dist/config-invariants.d.ts +18 -7
  64. package/dist/config-invariants.js +51 -8
  65. package/dist/config-lkg.d.ts +5 -19
  66. package/dist/config-lkg.js +8 -62
  67. package/dist/config-provider.d.ts +1 -1
  68. package/dist/config-root-migration.d.ts +2 -2
  69. package/dist/config-types.d.ts +28 -38
  70. package/dist/config.d.ts +7 -5
  71. package/dist/config.js +55 -16
  72. package/dist/control-socket.d.ts +51 -0
  73. package/dist/control-socket.js +115 -0
  74. package/dist/deployment-governance.d.ts +2 -1
  75. package/dist/device-ws-hub.d.ts +7 -0
  76. package/dist/device-ws-hub.js +7 -1
  77. package/dist/elicitation.js +2 -2
  78. package/dist/entry-problems.d.ts +40 -3
  79. package/dist/entry-problems.js +3 -1
  80. package/dist/execution-env-refusal.d.ts +8 -2
  81. package/dist/execution-env-refusal.js +3 -0
  82. package/dist/fleet/engine-signal.d.ts +12 -29
  83. package/dist/fleet/engine-signal.js +1 -7
  84. package/dist/fleet-client.js +2 -5
  85. package/dist/gate-settlement.d.ts +1 -8
  86. package/dist/gate-settlement.js +0 -4
  87. package/dist/history-anchor.js +1 -1
  88. package/dist/hitl-delivery.d.ts +2 -3
  89. package/dist/hitl-delivery.js +0 -2
  90. package/dist/hooks/hook-llm.js +4 -2
  91. package/dist/hooks/hook-runner.d.ts +3 -2
  92. package/dist/hooks/hook-runner.js +16 -1
  93. package/dist/host-decision.d.ts +11 -11
  94. package/dist/http/active-run-conflict.d.ts +35 -2
  95. package/dist/http/active-run-conflict.js +48 -19
  96. package/dist/http/approval-sweeps.d.ts +1 -1
  97. package/dist/http/approval-sweeps.js +5 -2
  98. package/dist/http/checkpoint-ctx.d.ts +2 -0
  99. package/dist/http/checkpoint-ctx.js +1 -0
  100. package/dist/http/dispatch.d.ts +6 -2
  101. package/dist/http/dispatch.js +25 -5
  102. package/dist/http/durable-run-leg.js +0 -1
  103. package/dist/http/health-reader.d.ts +16 -9
  104. package/dist/http/health-reader.js +2 -1
  105. package/dist/http/health-self-report.js +1 -1
  106. package/dist/http/owner-access.d.ts +2 -3
  107. package/dist/http/owner-access.js +3 -4
  108. package/dist/http/principal-gate.d.ts +49 -21
  109. package/dist/http/principal-gate.js +61 -11
  110. package/dist/http/request-channel.d.ts +23 -0
  111. package/dist/http/request-channel.js +9 -0
  112. package/dist/http/request-guards.js +17 -2
  113. package/dist/http/resume-ctx-read.js +20 -3
  114. package/dist/http/resume-legs.d.ts +2 -2
  115. package/dist/http/resume-legs.js +111 -41
  116. package/dist/http/route-ctx.d.ts +73 -7
  117. package/dist/http/route-ctx.js +17 -0
  118. package/dist/http/route-table.d.ts +4 -0
  119. package/dist/http/route-table.js +4 -0
  120. package/dist/http/routes/admin-config-refresh.js +3 -3
  121. package/dist/http/routes/admin-drain.d.ts +3 -0
  122. package/dist/http/routes/admin-drain.js +7 -7
  123. package/dist/http/routes/admin-memory-optout.js +3 -3
  124. package/dist/http/routes/adoption.js +3 -5
  125. package/dist/http/routes/approvals-assistant.d.ts +2 -1
  126. package/dist/http/routes/approvals-assistant.js +95 -48
  127. package/dist/http/routes/capabilities.d.ts +4 -0
  128. package/dist/http/routes/capabilities.js +30 -5
  129. package/dist/http/routes/config-catalog.js +2 -2
  130. package/dist/http/routes/devices.js +1 -1
  131. package/dist/http/routes/diagnostics.d.ts +3 -3
  132. package/dist/http/routes/diagnostics.js +19 -7
  133. package/dist/http/routes/images.js +10 -10
  134. package/dist/http/routes/memory-bundle.js +3 -3
  135. package/dist/http/routes/memory-compliance.js +3 -3
  136. package/dist/http/routes/memory-consolidation.js +2 -2
  137. package/dist/http/routes/memory-origin.js +3 -3
  138. package/dist/http/routes/memory-policy.js +2 -2
  139. package/dist/http/routes/observability.js +3 -3
  140. package/dist/http/routes/retention-ops.js +3 -3
  141. package/dist/http/routes/rules.js +5 -5
  142. package/dist/http/routes/run-permission-mode.d.ts +2 -1
  143. package/dist/http/routes/run-permission-mode.js +2 -2
  144. package/dist/http/routes/runs.d.ts +4 -9
  145. package/dist/http/routes/runs.js +73 -33
  146. package/dist/http/routes/session-sync.d.ts +18 -6
  147. package/dist/http/routes/session-sync.js +38 -36
  148. package/dist/http/routes/sessions.js +20 -9
  149. package/dist/http/routes/tasks.js +28 -37
  150. package/dist/http/routes/workflows.js +1 -1
  151. package/dist/http/server.d.ts +9 -3
  152. package/dist/http/server.js +15 -3
  153. package/dist/http/submit-ctx.d.ts +1 -1
  154. package/dist/http/submit-ctx.js +2 -0
  155. package/dist/human-facing-args.d.ts +33 -0
  156. package/dist/human-facing-args.js +33 -0
  157. package/dist/index.d.ts +1 -1
  158. package/dist/k8s-api-credentials.d.ts +29 -0
  159. package/dist/k8s-api-credentials.js +25 -0
  160. package/dist/lane-home-probe.d.ts +1 -1
  161. package/dist/lane-home-probe.js +2 -1
  162. package/dist/leader/diffup.d.ts +19 -0
  163. package/dist/leader/diffup.js +7 -0
  164. package/dist/leader/leader.d.ts +114 -10
  165. package/dist/leader/leader.js +69 -5
  166. package/dist/leader/repair-wire.js +2 -2
  167. package/dist/leader/run-entry.d.ts +2 -1
  168. package/dist/leader/run-entry.js +1 -1
  169. package/dist/leader/wire.d.ts +42 -132
  170. package/dist/leader/wire.js +81 -67
  171. package/dist/memory-scope.d.ts +20 -9
  172. package/dist/memory-scope.js +15 -3
  173. package/dist/model-entry-headers.d.ts +9 -10
  174. package/dist/model-entry-headers.js +0 -4
  175. package/dist/model-entry-refusal.d.ts +7 -4
  176. package/dist/model-entry-refusal.js +11 -5
  177. package/dist/model-plane-face.d.ts +1 -1
  178. package/dist/model-plane-face.js +3 -1
  179. package/dist/model-route-endpoint.d.ts +13 -7
  180. package/dist/model-route-endpoint.js +14 -11
  181. package/dist/model-select.d.ts +22 -0
  182. package/dist/model-select.js +9 -0
  183. package/dist/model-unreachable-notice.d.ts +11 -0
  184. package/dist/model-unreachable-notice.js +4 -4
  185. package/dist/object-store-buckets.d.ts +640 -6
  186. package/dist/object-store-buckets.js +166 -0
  187. package/dist/observability/fail-open.d.ts +20 -16
  188. package/dist/observability/fail-open.js +20 -16
  189. package/dist/observability/logger.js +3 -1
  190. package/dist/operator-roster.d.ts +68 -0
  191. package/dist/operator-roster.js +32 -0
  192. package/dist/orchestration/workflow-park-decision.d.ts +1 -1
  193. package/dist/outbound-dispatcher.d.ts +71 -8
  194. package/dist/outbound-dispatcher.js +48 -7
  195. package/dist/owner-faces.d.ts +1 -1
  196. package/dist/parked-decide.d.ts +2 -2
  197. package/dist/permission-mode-admission.d.ts +59 -26
  198. package/dist/permission-mode-admission.js +37 -10
  199. package/dist/plugins/approval-ask-store-file.d.ts +4 -0
  200. package/dist/plugins/approval-ask-store-file.js +3 -0
  201. package/dist/plugins/approval-ask-store-memory.d.ts +4 -0
  202. package/dist/plugins/approval-ask-store-memory.js +7 -1
  203. package/dist/plugins/approval-ask-store-sql.d.ts +26 -2
  204. package/dist/plugins/approval-ask-store-sql.js +12 -1
  205. package/dist/plugins/blob-backend.d.ts +83 -7
  206. package/dist/plugins/blob-backend.js +57 -6
  207. package/dist/plugins/checkpoint-index-columns.d.ts +20 -16
  208. package/dist/plugins/checkpoint-index-columns.js +3 -6
  209. package/dist/plugins/checkpoint-store-face.d.ts +119 -61
  210. package/dist/plugins/checkpoint-store-face.js +134 -55
  211. package/dist/plugins/landed-attendants.d.ts +2 -2
  212. package/dist/plugins/local-checkpoint-store.d.ts +13 -3
  213. package/dist/plugins/local-checkpoint-store.js +38 -49
  214. package/dist/plugins/one-time-migrations.d.ts +51 -21
  215. package/dist/plugins/one-time-migrations.js +67 -45
  216. package/dist/plugins/pg-pool.js +11 -9
  217. package/dist/plugins/remote-env-host.d.ts +29 -10
  218. package/dist/plugins/remote-env-host.js +99 -19
  219. package/dist/plugins/remote-env-k8s.d.ts +68 -2
  220. package/dist/plugins/remote-env-k8s.js +107 -24
  221. package/dist/plugins/replica-registration.d.ts +1 -1
  222. package/dist/plugins/replica-registration.js +1 -1
  223. package/dist/plugins/s3-presign.d.ts +23 -0
  224. package/dist/plugins/s3-presign.js +37 -3
  225. package/dist/plugins/scheduler-support.d.ts +2 -2
  226. package/dist/plugins/scheduler-support.js +0 -3
  227. package/dist/plugins/schema-indexes.js +2 -0
  228. package/dist/plugins/send-file-ledger.d.ts +2 -2
  229. package/dist/plugins/send-file-ledger.js +2 -2
  230. package/dist/plugins/send-user-file.d.ts +3 -4
  231. package/dist/plugins/send-user-file.js +56 -52
  232. package/dist/plugins/session-blob-claim-token.d.ts +15 -0
  233. package/dist/plugins/session-blob-claim-token.js +20 -0
  234. package/dist/plugins/session-blob-namespace.d.ts +46 -0
  235. package/dist/plugins/session-blob-namespace.js +116 -0
  236. package/dist/plugins/snapshot-bucket-presence.d.ts +25 -2
  237. package/dist/plugins/snapshot-bucket-presence.js +8 -2
  238. package/dist/plugins/sql-json-column.d.ts +1 -1
  239. package/dist/plugins/store-backend.d.ts +44 -2
  240. package/dist/plugins/store-backend.js +75 -53
  241. package/dist/plugins/task-attachment-store.d.ts +2 -1
  242. package/dist/plugins/task-attachment-store.js +5 -1
  243. package/dist/plugins/tidb-pool.js +19 -24
  244. package/dist/plugins/workspace-custody.d.ts +4 -3
  245. package/dist/question.d.ts +2 -1
  246. package/dist/question.js +5 -13
  247. package/dist/request-form.d.ts +6 -1
  248. package/dist/request-form.js +4 -1
  249. package/dist/rule-issue-reading.d.ts +1 -1
  250. package/dist/rules-consent.d.ts +1 -1
  251. package/dist/run-local.js +8 -9
  252. package/dist/runs.d.ts +17 -8
  253. package/dist/runs.js +15 -2
  254. package/dist/runtime-governance.d.ts +2 -2
  255. package/dist/sema-config-dir.d.ts +2 -1
  256. package/dist/session-sync.d.ts +27 -11
  257. package/dist/session-sync.js +15 -7
  258. package/dist/stores-sql/blob-index-sql.d.ts +30 -9
  259. package/dist/stores-sql/blob-index-sql.js +40 -21
  260. package/dist/stores-sql/checkpoint-store-sql.d.ts +54 -27
  261. package/dist/stores-sql/checkpoint-store-sql.js +70 -146
  262. package/dist/stores-sql/file-history-store-sql.d.ts +64 -16
  263. package/dist/stores-sql/file-history-store-sql.js +100 -28
  264. package/dist/stores-sql/retention-store-sql.js +3 -9
  265. package/dist/task-settings.d.ts +6 -5
  266. package/dist/task-settings.js +3 -3
  267. package/dist/tool-approval.d.ts +342 -518
  268. package/dist/tool-approval.js +498 -1207
  269. package/dist/trace/core-keyset-guard.d.ts +1 -1
  270. package/dist/trace/ledger-events.d.ts +3 -3
  271. package/dist/trace/project.d.ts +2 -3
  272. package/dist/trace/redact.js +3 -2
  273. package/dist/trace/self-minted-wire.d.ts +287 -157
  274. package/dist/trace/self-minted-wire.js +93 -48
  275. package/dist/web-admin-client/apply-effective.js +2 -2
  276. package/dist/web-admin-client/read-warnings.d.ts +2 -2
  277. package/dist/web-admin-client/read-warnings.js +1 -1
  278. package/dist/web-admin-client/types.d.ts +1 -1
  279. package/dist/workspace-space.d.ts +10 -4
  280. package/dist/workspace-space.js +4 -1
  281. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +1 -1
  282. package/package.json +6 -2
  283. package/scripts/embed-minimal.mjs +3 -3
  284. package/wire-changes/server@7.152.0.json +900 -0
  285. package/dist/config-d-migration.d.ts +0 -37
  286. package/dist/config-d-migration.js +0 -60
@@ -0,0 +1,68 @@
1
+ /**
2
+ * 🔴 **运维名册的档表**(唯一一张)——「这位验过的人在这台部署上持哪几只运维权力」只在这里答。
3
+ *
4
+ * ## 律(一句)
5
+ * 一张闭集档表、一只读法({@link rosterTierOf}:读验过的人、读部署配置里的名册);档表一行 = 名单在哪格配置(`field` / `env`)· 这一档持哪几只权力(`powers`)·
6
+ * 持有者是不是人(`person`)。**不是人的档**(服务主体)的身份只在声明了它所持权力的路由上算数(路由声明行的 `power` 列,`http/route-ctx.ts`),别处整次请求
7
+ * 403 `auth.principal_confined`(判官 `http/principal-gate.ts` 的 `confinedTierOf`:请求的人、决定证明推出的决定者、设备行的属主三个读点同判 —— 最后一个在设备 hello 准入,
8
+ * 设备车道不经 HTTP、没有声明过权力的路由,落在非人档的属主登记的设备连不上),`GET /health` 按没出示凭据分档。
9
+ * 全运维档(`operator`)持 {@link OPERATOR_POWERS} 全集 —— 加一只权力词,现有运维不掉权。
10
+ *
11
+ * ## 读者
12
+ * · 档:{@link rosterTierOf}(人 → 档;单用户声明 `OPERATOR_SINGLE_USER=true` ⇒ 每个调用方落 `operator` 档)。
13
+ * · 授权 / 有无 / 圈禁 / 出路句:`http/principal-gate.ts` 的 `explicitOperatorOk(人, 名册, 权力)` / `operatorRosterPresent(名册, 权力)` / `confinedTierOf` /
14
+ * `buildOperatorRosterWayOut(权力)`,都经本表的 {@link rosterTierOf} / {@link tierHolds} / {@link rosterNamesOf},不另写一份「哪几格是名单」。
15
+ * · 起服不变量:`config-invariants.ts` 的 `operator-roster-well-formed`(名单是名字列表 · 非人档的名字是主体名 · 一个名字只在一档)与 `operator-single-user`(单用户声明与任一档名单互斥)按本表逐档读。
16
+ *
17
+ * 叶子:只取型(`config-types` / `owner-brands`),不 import http 层 —— 配置层(不变量)与门(`principal-gate`)读同一张表。
18
+ */
19
+ import type { ServiceConfig } from "./config-types.js";
20
+ import type { Person } from "./owner-brands.js";
21
+ /** 运维权力(闭集)。加词 ⇒ 档表与每个按权力穷举的读者编译红。`operate` = 运维面的全部门与放宽位;`drain` = 排空口 `POST /v1/admin/drain`。 */
22
+ export declare const OPERATOR_POWERS: readonly ["operate", "drain"];
23
+ export type OperatorPower = (typeof OPERATOR_POWERS)[number];
24
+ /** 名册读的几格配置:各档的名单 + 单用户声明(只住在部署配置里;env 独有、起服冻结)。 */
25
+ export type OperatorRoster = Pick<ServiceConfig, "operatorPrincipals" | "operatorDrainPrincipals" | "operatorSingleUser">;
26
+ /** 档表(闭集;一档一格配置、一格 env)。 */
27
+ export declare const ROSTER_TIERS: {
28
+ readonly operator: {
29
+ readonly field: "operatorPrincipals";
30
+ readonly env: "OPERATOR_PRINCIPALS";
31
+ readonly holders: "operator principals";
32
+ readonly powers: readonly ["operate", "drain"];
33
+ readonly person: true;
34
+ };
35
+ readonly drain: {
36
+ readonly field: "operatorDrainPrincipals";
37
+ readonly env: "OPERATOR_DRAIN_PRINCIPALS";
38
+ readonly holders: "drain-only service principals";
39
+ readonly powers: readonly ["drain"];
40
+ readonly person: false;
41
+ };
42
+ };
43
+ export type RosterTier = keyof typeof ROSTER_TIERS;
44
+ /** 不是人的档(圈禁的那几档)。 */
45
+ export type NonPersonTier = {
46
+ [T in RosterTier]: (typeof ROSTER_TIERS)[T]["person"] extends false ? T : never;
47
+ }[RosterTier];
48
+ /** 档的规范序(出路句按它列档;判档时人档排在非人档之后,见 {@link rosterTierOf})。漏一档 / 名册多一格名单不进表 ⇒ 编译红。 */
49
+ export declare const ROSTER_TIER_ORDER: readonly ["operator", "drain"];
50
+ /**
51
+ * 一档的名单(缺席 ≡ 空 = 这一档没人)。名单必须是列表:绕过类型手搓的配置在这一格放一串字符串 ⇒ 响亮抛(`includes` 会把它读成子串匹配 = 放宽;
52
+ * 读成空又会让非人档的服务主体落回普通身份 = 也是放宽)。装配座与起服在此之前已按 `operator-roster-well-formed` 拒,这一臂只是读法本身不说假话。
53
+ */
54
+ export declare function rosterNamesOf(tier: RosterTier, roster: OperatorRoster): readonly string[];
55
+ /** 这一档持不持这只权力。 */
56
+ export declare function tierHolds(tier: RosterTier, power: OperatorPower): boolean;
57
+ /** 这一档是不是人档。 */
58
+ export declare function tierIsPerson(tier: RosterTier): boolean;
59
+ /**
60
+ * 🔴 **人 → 档的唯一读法**。`person` 必须是验过的人(`gatedPerson` / `personOf(主体)` / 决定证明推出的人),绝不是自述头,也不是带场景的主体(名册登记的是人)。
61
+ * · 人在某档名单里 ⇒ 那一档(非人档先判,见 {@link TIER_MATCH_ORDER});
62
+ * · 否则单用户声明 `OPERATOR_SINGLE_USER=true` ⇒ `operator`(单用户形只有一位用户,能到这台部署的就是他;没有人也成立 —— 与改前 `explicitOperatorOk` 同读);
63
+ * · 否则 ⇒ 不在名册(空名单 = 没人在)。
64
+ */
65
+ export declare function rosterTierOf(person: Person | undefined, roster: OperatorRoster): RosterTier | undefined;
66
+ /** 名册里有没有**非人档**的名字(圈禁判只在它为真时读人 —— 没声明非人档的部署零新读、逐字节同今天)。 */
67
+ export declare function rosterHasNonPersonNames(roster: OperatorRoster): boolean;
68
+ //# sourceMappingURL=operator-roster.d.ts.map
@@ -0,0 +1,32 @@
1
+ export const OPERATOR_POWERS = ["operate", "drain"];
2
+ export const ROSTER_TIERS = {
3
+ operator: { field: "operatorPrincipals", env: "OPERATOR_PRINCIPALS", holders: "operator principals", powers: OPERATOR_POWERS, person: true },
4
+ drain: { field: "operatorDrainPrincipals", env: "OPERATOR_DRAIN_PRINCIPALS", holders: "drain-only service principals", powers: ["drain"], person: false },
5
+ };
6
+ export const ROSTER_TIER_ORDER = ["operator", "drain"];
7
+ const TIER_MATCH_ORDER = [...ROSTER_TIER_ORDER.filter((t) => !ROSTER_TIERS[t].person), ...ROSTER_TIER_ORDER.filter((t) => ROSTER_TIERS[t].person)];
8
+ export function rosterNamesOf(tier, roster) {
9
+ const names = roster[ROSTER_TIERS[tier].field];
10
+ if (names === undefined)
11
+ return [];
12
+ if (!Array.isArray(names))
13
+ throw new Error(`operator roster: ${ROSTER_TIERS[tier].env} is not a list of principal names (invariant operator-roster-well-formed)`);
14
+ return names;
15
+ }
16
+ export function tierHolds(tier, power) {
17
+ return ROSTER_TIERS[tier].powers.includes(power);
18
+ }
19
+ export function tierIsPerson(tier) {
20
+ return ROSTER_TIERS[tier].person;
21
+ }
22
+ export function rosterTierOf(person, roster) {
23
+ if (person !== undefined)
24
+ for (const tier of TIER_MATCH_ORDER)
25
+ if (rosterNamesOf(tier, roster).includes(person))
26
+ return tier;
27
+ return roster.operatorSingleUser === true ? "operator" : undefined;
28
+ }
29
+ export function rosterHasNonPersonNames(roster) {
30
+ return ROSTER_TIER_ORDER.some((t) => !ROSTER_TIERS[t].person && rosterNamesOf(t, roster).length > 0);
31
+ }
32
+ //# sourceMappingURL=operator-roster.js.map
@@ -55,7 +55,7 @@ export type WorkflowRecordFenceStore = import("../plugins/workflow-run-store-sql
55
55
  */
56
56
  export declare function holderFenceAdmits(run: WorkflowRun, d: Pick<WorkflowParkDecision, "runId" | "token">, restated: boolean): "not_held" | "restated" | "land";
57
57
  /**
58
- * 决定行的店契约(SQL 两方言 / local 文件 / 内存三形同律)。写者两个:`/decide` 与应答口的 workflow 车道(记录栅栏那一笔 / 续跑车道开跑之前那一笔)、
58
+ * 决定行的店契约(SQL 两方言 / local 文件 / 内存三形同律)。写者两个:`/decide` 与决断口停驻形的 workflow 车道(7.151.x 及以前:应答口的)(记录栅栏那一笔 / 续跑车道开跑之前那一笔)、
59
59
  * run 收场送达的转交(续跑车道那一笔,带条件);读者是起跑读者、持有进程的拉取腿、收场送达与 `/decide` 自己的回执幂等判;删除只有 SQL 收割的级联。
60
60
  */
61
61
  export interface WorkflowParkDecisionStore {
@@ -163,18 +163,45 @@ export declare function createOutboundDispatcher(env?: NodeJS.ProcessEnv): {
163
163
  env: OutboundProxyEnv;
164
164
  };
165
165
  /**
166
- * 建连期失败码 —— **闭集**(码 → 给运维的一句读法)。只收**只可能**在建立连接时出现的码:undici 的建连截止
166
+ * 建连失败**类** —— 闭集(三类),说「这一次建连失败的出路在哪一头」,由 {@link OUTBOUND_CONNECT_FAILURE_CODES} 的每个码各认一类(`failureClass`):
167
+ * · `refused` —— 对端把连接拒了(那个地址上没人监听 / 防火墙复位):出路在**地址那一头**(服务在不在、地址配得对不对);
168
+ * · `unresolved` —— 主机名解析不了(地址写错,或 DNS / VPN 不通):出路在**地址写法与名字解析**;
169
+ * · `unreachable` —— 建连没在时限内完成 / 无路由 / 网络不可达:出路在**网络路径**(VPN / 代理)。
170
+ * 建连失败的出路取自这三类上的一张表({@link MODEL_UNREACHABLE_REMEDIES},选句 {@link connectFailureRemedyOf}):用户面通告 `server.model_unreachable`
171
+ * (`model-unreachable-notice.ts`)与运维日志 `outbound_connect_failed` 的 `hint` 同读它,都不另写码 → 类的映射。
172
+ */
173
+ export type OutboundConnectFailureClass = "refused" | "unresolved" | "unreachable";
174
+ /**
175
+ * 建连期失败码 —— **闭集**(码 → { 失败类,给运维的一句读法 })。只收**只可能**在建立连接时出现的码:undici 的建连截止
167
176
  * (`UND_ERR_CONNECT_TIMEOUT`,TCP + TLS 没在传输层的建连时限内完成)与 OS 的拒连 / 解析不了 / 无路由。可能出现在**已建立**
168
177
  * 连接上的码(`ECONNRESET` / `ETIMEDOUT` / `UND_ERR_SOCKET`)刻意不收 —— 它们判不出「够不着」,收进来就会把一次中途断流
169
- * 说成「模型不可达、去查 VPN」。加一个码 = 加一行并写明它为什么只可能在建连时出现。
178
+ * 说成「模型不可达、去查 VPN」。加一个码 = 加一行并写明它为什么只可能在建连时出现、它属哪一失败类(类缺 ⇒ 编译红)。
170
179
  */
171
180
  export declare const OUTBOUND_CONNECT_FAILURE_CODES: {
172
- readonly UND_ERR_CONNECT_TIMEOUT: "the TCP + TLS connect did not complete within the transport's connect deadline";
173
- readonly ECONNREFUSED: "the peer actively refused the connection (nothing listening there, or a firewall reset it)";
174
- readonly ENOTFOUND: "the host name did not resolve";
175
- readonly EAI_AGAIN: "the DNS lookup failed temporarily";
176
- readonly EHOSTUNREACH: "there is no route to the host";
177
- readonly ENETUNREACH: "the network is unreachable";
181
+ readonly UND_ERR_CONNECT_TIMEOUT: {
182
+ readonly failureClass: "unreachable";
183
+ readonly reading: "the TCP + TLS connect did not complete within the transport's connect deadline";
184
+ };
185
+ readonly ECONNREFUSED: {
186
+ readonly failureClass: "refused";
187
+ readonly reading: "the peer actively refused the connection (nothing listening there, or a firewall reset it)";
188
+ };
189
+ readonly ENOTFOUND: {
190
+ readonly failureClass: "unresolved";
191
+ readonly reading: "the host name did not resolve";
192
+ };
193
+ readonly EAI_AGAIN: {
194
+ readonly failureClass: "unresolved";
195
+ readonly reading: "the DNS lookup failed temporarily";
196
+ };
197
+ readonly EHOSTUNREACH: {
198
+ readonly failureClass: "unreachable";
199
+ readonly reading: "there is no route to the host";
200
+ };
201
+ readonly ENETUNREACH: {
202
+ readonly failureClass: "unreachable";
203
+ readonly reading: "the network is unreachable";
204
+ };
178
205
  };
179
206
  export type OutboundConnectFailureCode = keyof typeof OUTBOUND_CONNECT_FAILURE_CODES;
180
207
  /** 一次建连失败的事实(纯数据:日志行、诊断段、模型调用结局同一份)。`proxyInUse` = 这一跳走的代理**变量名**(不打值),直连 ⇒ `null`。 */
@@ -183,6 +210,42 @@ export interface OutboundConnectFailure {
183
210
  readonly code: OutboundConnectFailureCode;
184
211
  readonly proxyInUse: string | null;
185
212
  }
213
+ /**
214
+ * 一个码的失败类({@link OutboundConnectFailureClass});闭集之外的码 ⇒ `undefined`(调用方必须处置这一臂,不是静默的一类)。纯函数、不抛。
215
+ * 类型上到不了 `undefined`(`OutboundConnectFailure.code` 是闭集型),但读者收 `string`:手造的失败事实、将来码型变宽,都落在这一臂上而不是 `undefined.failureClass` 抛。
216
+ */
217
+ export declare function connectFailureClassOf(code: string): OutboundConnectFailureClass | undefined;
218
+ /**
219
+ * 一次建连失败的**出路** —— 同一件事的两种写法:`advice` 是给人读的一句(用户面通告的正文末句),`remedy` 是短句(通告的 `detail.remedy`,
220
+ * 也是运维日志 `outbound_connect_failed` 的 `hint` 尾句)。三张脸读**同一行**,不各写各的。
221
+ */
222
+ export interface ConnectFailureRemedy {
223
+ readonly advice: string;
224
+ readonly remedy: string;
225
+ }
226
+ /**
227
+ * **失败类上的一张闭表**(键集 = {@link OutboundConnectFailureClass},加一类不补一行 ⇒ 编译红)。行说的是**模型端点**(端点地址、模型网关地址配置):
228
+ * 用户面通告只为模型调用铸,运维日志只在失败的请求属一次模型调用时读类行。契约附录 D.3 与 USAGE 列的就是这三行
229
+ * (`test/model-unreachable-notice.test.ts` 的「文书与表同源」一格读本表对文书)。
230
+ */
231
+ export declare const MODEL_UNREACHABLE_REMEDIES: {
232
+ readonly refused: {
233
+ readonly advice: "Check that a service is listening at that address and that the model gateway address is configured correctly.";
234
+ readonly remedy: "check that a service is listening at the model endpoint address; check the model gateway address configuration";
235
+ };
236
+ readonly unresolved: {
237
+ readonly advice: "Check that the model gateway address is written correctly in the configuration and that DNS (and the VPN, if one is in use) can resolve it.";
238
+ readonly remedy: "check the model gateway address configuration; check DNS / VPN";
239
+ };
240
+ readonly unreachable: ConnectFailureRemedy;
241
+ };
242
+ /**
243
+ * 选句 —— **唯一一处**(用户面通告与运维日志 `hint` 同调),规则先到先得:
244
+ * ① 失败的请求不属一次模型调用(`request: "other"`:中心、工具、MCP …)⇒ 通用句(类行说的是模型端点与网关地址配置,对别的主机不成立);
245
+ * ② 这一跳走了代理(`proxyInUse` 非空)⇒ 通用句(拒连 / 解析不了可能是**代理**那一头的事,出路仍是「查 VPN / 代理」);
246
+ * ③ 否则按失败类读闭表;码不在闭集里(类型上到不了,读者收 `string`)⇒ 通用句 —— 声明的未分类臂,不抛、不静默。
247
+ */
248
+ export declare function connectFailureRemedyOf(failure: OutboundConnectFailure, request: "model_call" | "other"): ConnectFailureRemedy;
186
249
  /** 一只请求错误的建连期失败码与它走的代理变量名(`undefined` = 不是建连失败)。原句不在这里 —— 它只给运维日志。 */
187
250
  export declare function classifyOutboundConnectFailure(error: unknown): {
188
251
  readonly code: OutboundConnectFailureCode;
@@ -339,14 +339,55 @@ export function createOutboundDispatcher(env = process.env) {
339
339
  return { dispatcher: new OutboundDispatcher(new Agent(), proxied), posture: postureOf(v), env: v };
340
340
  }
341
341
  export const OUTBOUND_CONNECT_FAILURE_CODES = {
342
- UND_ERR_CONNECT_TIMEOUT: "the TCP + TLS connect did not complete within the transport's connect deadline",
343
- ECONNREFUSED: "the peer actively refused the connection (nothing listening there, or a firewall reset it)",
344
- ENOTFOUND: "the host name did not resolve",
345
- EAI_AGAIN: "the DNS lookup failed temporarily",
346
- EHOSTUNREACH: "there is no route to the host",
347
- ENETUNREACH: "the network is unreachable",
342
+ UND_ERR_CONNECT_TIMEOUT: { failureClass: "unreachable", reading: "the TCP + TLS connect did not complete within the transport's connect deadline" },
343
+ ECONNREFUSED: { failureClass: "refused", reading: "the peer actively refused the connection (nothing listening there, or a firewall reset it)" },
344
+ ENOTFOUND: { failureClass: "unresolved", reading: "the host name did not resolve" },
345
+ EAI_AGAIN: { failureClass: "unresolved", reading: "the DNS lookup failed temporarily" },
346
+ EHOSTUNREACH: { failureClass: "unreachable", reading: "there is no route to the host" },
347
+ ENETUNREACH: { failureClass: "unreachable", reading: "the network is unreachable" },
348
348
  };
349
349
  const isConnectFailureCode = (c) => typeof c === "string" && Object.hasOwn(OUTBOUND_CONNECT_FAILURE_CODES, c);
350
+ export function connectFailureClassOf(code) {
351
+ return isConnectFailureCode(code) ? OUTBOUND_CONNECT_FAILURE_CODES[code].failureClass : undefined;
352
+ }
353
+ const NETWORK_PATH_REMEDY = {
354
+ advice: "Check the VPN / proxy; if the proxy changed since the server started, restart it (proxy settings are read once at startup).",
355
+ remedy: "check VPN / proxy; restart if the proxy changed since boot",
356
+ };
357
+ export const MODEL_UNREACHABLE_REMEDIES = {
358
+ refused: {
359
+ advice: "Check that a service is listening at that address and that the model gateway address is configured correctly.",
360
+ remedy: "check that a service is listening at the model endpoint address; check the model gateway address configuration",
361
+ },
362
+ unresolved: {
363
+ advice: "Check that the model gateway address is written correctly in the configuration and that DNS (and the VPN, if one is in use) can resolve it.",
364
+ remedy: "check the model gateway address configuration; check DNS / VPN",
365
+ },
366
+ unreachable: NETWORK_PATH_REMEDY,
367
+ };
368
+ export function connectFailureRemedyOf(failure, request) {
369
+ switch (request) {
370
+ case "other":
371
+ return NETWORK_PATH_REMEDY;
372
+ case "model_call":
373
+ break;
374
+ default: {
375
+ const unhandled = request;
376
+ throw new Error(`connectFailureRemedyOf: unhandled request kind ${String(unhandled)}`);
377
+ }
378
+ }
379
+ if (failure.proxyInUse !== null)
380
+ return NETWORK_PATH_REMEDY;
381
+ const failureClass = connectFailureClassOf(failure.code);
382
+ switch (failureClass) {
383
+ case undefined:
384
+ return NETWORK_PATH_REMEDY;
385
+ case "refused":
386
+ case "unresolved":
387
+ case "unreachable":
388
+ return MODEL_UNREACHABLE_REMEDIES[failureClass];
389
+ }
390
+ }
350
391
  function scanConnectFailure(error) {
351
392
  const queue = [error];
352
393
  let proxyInUse = null;
@@ -438,7 +479,7 @@ function startConnectObserver(logger, nowMs) {
438
479
  ...(found.proxyHop !== null ? { proxyHop: found.proxyHop } : {}),
439
480
  proxyReadAtMs: current.proxyReadAtMs,
440
481
  sinceBootMs: Math.round(process.uptime() * 1000),
441
- hint: `${OUTBOUND_CONNECT_FAILURE_CODES[failure.code]} — ${failure.proxyInUse !== null ? `this hop went through the proxy from ${failure.proxyInUse} (read once at boot)` : "this hop was a direct connection"}; if the VPN / proxy changed since boot, restart the server so it re-reads HTTP(S)_PROXY / NO_PROXY`,
482
+ hint: `${OUTBOUND_CONNECT_FAILURE_CODES[failure.code].reading} — ${failure.proxyInUse !== null ? `this hop went through the proxy from ${failure.proxyInUse} (read once at boot)` : "this hop was a direct connection"}; ${connectFailureRemedyOf(failure, call !== undefined ? "model_call" : "other").remedy}`,
442
483
  });
443
484
  if (call !== undefined)
444
485
  call.last = { kind: "connect_failed", failure };
@@ -77,7 +77,7 @@ export declare function ownerKeyOf(persisted: string | null): DurableOwnerKey |
77
77
  */
78
78
  export declare function ownerCellOf(owner: string | null | undefined): SessionOwnerCell | undefined;
79
79
  /**
80
- * **收纯串的协议内部口**的主体读法(恢复管线;不是铸点):活登记的应答口 / 按问读口(`tool-approval.ts` 协调器)、活问的应答口(`elicitation.ts` / `question.ts`)、
80
+ * **收纯串的协议内部口**的主体读法(恢复管线;不是铸点):活登记的按问读口 / 决断口的停驻赎回席(`tool-approval.ts` 协调器)、活问的应答口(`elicitation.ts` / `question.ts`)、
81
81
  * 迟到决定的赎回席(`http/approval-sweeps.ts`)、停驻完成收件箱的排空门、peer 目录席 —— 这几扇口经纯串收「上游已铸的那一只主体」(HTTP 路由层的 `gatedSubject`
82
82
  * 读数、续跑腿交来的 run 属主、引擎交来的 scope)。串就是一只已落成字节的属主键,按 {@link ownerKeyOf} → `partsOf` 恢复(坏形的 `_scn:` 串响亮抛),
83
83
  * 不重判保留前缀 / 在册 / 宽度(那三判只在外部管线)。公开面改收 `Subject` 品牌是另一车(这几扇口的测试调用面数以百计)。
@@ -250,7 +250,7 @@ export interface ParkedDecideDeps {
250
250
  * 「本会话放行」落在**记不住的车道**上怎么答(闭集;两扇门之间唯一允许的差 —— 其余两口同读同答,铸点只有续跑腿一只):
251
251
  * · `refuse` —— `/decide` 体上的 `remember:"session"`:那是一个硬意图键,认领之前 400 点名车道(`decide.workflow_remember_unsupported` /
252
252
  * `decide.parked_remember_unsupported`),去掉它再决即可,零副作用;
253
- * · `decide_once` —— 应答口命中停驻行的 `allow_session`:那是卡上的一个选项,主内容是「放行」。照常放行这一次、不落豁免、回执
253
+ * · `decide_once` —— 决断口命中停驻行的 `remember:"session"`(7.151.x 及以前是应答口的 `allow_session`):那是卡上的一个选项,主内容是「放行」。照常放行这一次、不落豁免、回执
254
254
  * `rememberApplied:false` —— 与活卡上「记不住的卡点 allow_session」同一答法;不把一次已经成立的批准改成 400(窗到期那一拍的按键今天是成的)。
255
255
  * 记不住的车道 = workflow 车道(它的 200 证不出引擎已提交这次决定)、无会话锚的 bg 停驻行(豁免无处可锚)。
256
256
  */
@@ -283,7 +283,7 @@ export interface ParkedDecideRequest {
283
283
  /** D-1 决断绑定:决断方点名它看到的那一对(必在 —— 续跑腿的判官 `decidedActionOf` 判过才交下来)与决定体的其余两位;
284
284
  * server 绝不重算、绝不从停驻行补,core resume 侧做 fail-closed 等值校验 —— 与 legacy 腿同姿势。 */
285
285
  binding: Omit<DecideBinding, "checkpointToken" | "boundCallId" | "boundInputHash"> & DecidedAction;
286
- /** 这次决定带着「本会话放行」请求(`/decide` 的 `remember:"session"` / 应答口命中停驻行的 `allow_session`,两口同一只请求;
286
+ /** 这次决定带着「本会话放行」请求(`/decide` 的 `remember:"session"` / 决断口命中停驻行的 `remember:"session"`,两口同一只请求;
287
287
  * 铸点 = 续跑腿 `resumeCheckpoint`)时,续跑腿交下来的**锚点记录器** + 记不住时怎么答({@link ParkedRemember})。 */
288
288
  remember?: ParkedRemember;
289
289
  /** [ref](core 5.7.0,F6 翻案):AskUserQuestion 的 approve 携操作员答案——原样透传进
@@ -9,7 +9,7 @@
9
9
  * ① 只动 `auto`、③ 只看开写面的词,两者互不相交。
10
10
  *
11
11
  * ## 三口的调用形(同一组函数、同一个序;提交口与切档口:同一个词 × 同一份部署事实 × 同一份任务 settings ⇒ 同一个结果,钉 `test/run-permission-mode-admission.test.ts` 的两口表;
12
- * 续跑口与它们只差地板的出口词一个事实,钉 `test/entry-permission-mode.test.ts` 的续跑口表)
12
+ * 续跑口与它们只差一张写据,钉 `test/entry-permission-mode.test.ts` 的续跑口表)
13
13
  * · 在飞 run 的切档口(`http/routes/run-permission-mode.ts`:`POST /v1/runs/:id/permission-mode` 的 `mode`,本任务 settings 读存储体)调整只判官 {@link admitPermissionModeWord}。
14
14
  * · 提交口(`POST /v1/tasks|/v1/tasks/stream|/v1/runs` 的词 = 顶层 `permissionMode` ?? bundle `settings.permissions.defaultMode`)把两步落在**上一版就在的两处**:
15
15
  * 入口一步在受理门盖章({@link stampSubmissionEntryMode},`http/admission.ts`:值形门之后、装配之前 —— 首跑装配与 `buildCheckpointCtx` 读的都是盖过章的体);
@@ -17,21 +17,22 @@
17
17
  * 属主 / 会话 / 执行上下文与装配期其余值判之前答(拒序变了 —— 钉同一文件的拒序组)。
18
18
  * · 续跑口(一条 HTTP 车道停驻 run 的存储体被重放之前;`http/resume-legs.ts` 的 `replayCtxOf`,五个续跑读点同调;leader 车道的行按那条车道「一 run 一读」照写下的体重放,
19
19
  * 规则在 `replayCtxOf` 头注)调 {@link stampReplayEntryMode}:体上的词过与提交口**同一只**入口一步
20
- * ({@link bodyEntry}:同一只折叠、同一只 `entryPermissionMode`、地板读同一份**活** config)、盖同一只章 —— 提交之后运维把 `AUTONOMY` 改严,或体是更早的写者留下的请求原词,
20
+ * ({@link bodyEntry}:同一只折叠、同一只 `entryPermissionMode`、地板读同一份**活** config)—— 提交之后运维把 `AUTONOMY` 改严,或体是更早的写者留下的请求原词,
21
21
  * 重放照**现**地板钳;被钳 = 钳到地板放行(与提交口同形,不拒、不新增码),生效词由这条腿的 `wiring_manifest.permissionMode` 说;钳过的体随 CAS 赢点写回,
22
- * 存储体仍是这条 run 模式词唯一的源。与提交口只差一个**事实**(不是续跑专用臂,见下一节):体**顶层** `permissionMode` 上写着的是这块地板的出口词({@link floorExitWords})⇒ 原样读。
22
+ * 存储体仍是这条 run 模式词唯一的源。与提交口只差一张**写据**(不是续跑专用臂,见下一节):写据说体顶层那一格是这块地板的出口写下的、且仍是它写下的那一格 ⇒ 原样读。
23
23
  * 托管一步照旧在装配期判。
24
24
  *
25
- * ## 为什么地板的出口词在续跑口原样读
26
- * 存储体上的模式词有三个写者:提交口(盖过地板)、切档口(盖过地板)、`plan_review` 的批准 / 编辑写回(`http/resume-legs.ts` `drivePlanReview`:把计划带出 plan,
27
- * 写 `PLAN_APPROVE_PERMISSION_MODES_AFTER` 里的词 —— 这就是 plan 地板的出口:一个人批了这份计划)。提交口与切档口收的是**调用方**的词,出口词照钳(没有人批过计划);
28
- * 续跑口读的是**存储体**的词,出口词是批准写下的 ⇒ 对它也钳 = 每一次挂起→续跑都把批过的计划拉回 plan(上一版撤掉按段重钳模式词的那条理由,`runtime-governance.ts`
29
- * `autonomyOverrides` 顶注)。出口词以外的词(`bypassPermissions` / `auto` / `dontAsk` / 缺席)在这块地板下没有任何写者写得出来 —— 只能是更松的地板(改严之前)
30
- * 或更早的写者(7.121.x 及以前的请求原词)留下的 ⇒ 照现地板钳。出口写者只写**顶层** `permissionMode`(`drivePlanReview` 的写回;bundle 的 `defaultMode` 不动),
31
- * 所以出口判读的是顶层那一格的**原字**(严格读):只在 bundle 拼法里的词、经本任务 kill-switch 折出来的词(顶层 `auto` × `disableAutoMode` ⇒ `default`)、
32
- * 词表外的串(重放的宽读把它折成 `default`)都不是出口写下的,照钳。
33
- * ⚠️ 射程如实:顶层的出口词本身读不出是谁写的 —— 改严之前提交的顶层 `default` / `acceptEdits`、7.121.x 及以前的同字原词与批准写回逐字相同,原样重放
34
- * (MIGRATION 如实写);要分开它们得给行补一个「谁写的」事实,本模块不猜。
25
+ * ## 写据:存储体上的模式词谁写的(一条规则)
26
+ * 存储体顶层 `permissionMode` 那一格谁写,谁就在同一笔写里留一张写据({@link PermissionModeWrite} `{ by, mode }`,`CheckpointCtx.permissionModeWrite`:服务端写、
27
+ * 客户端够不着)。写者闭集四个({@link PERMISSION_MODE_WRITERS}):`submission`(提交写口 `buildCheckpointCtx`,经 `http/submit-ctx.ts` `persistSubmissionCtx`;leader worker 的续跑体)、
28
+ * `switch`(在飞切档的写回)、`replay`(本模块续跑口的钳)、`plan_review`(`http/resume-legs.ts` `drivePlanReview` 批准 / 编辑把计划带出 plan 的写回 —— plan 地板的出口:一个人批了这份计划)。
29
+ * 新的持久化写经构造口(写据是它的必填键,漏写 = 编译红);改写那一格经一只改写口 {@link withStoredPermissionMode}(一笔换词、换写据)。
30
+ * 读者只有一个:续跑口。地板在场时只问「这块地板的出口是谁」({@link floorExitWriter})—— 写据的 `by` 就是它、且写据的 `mode` 仍等于体顶层的原值 ⇒ 原样;
31
+ * 其余一律照现地板钳(缺写据 = 7.152.0 之前写下的行,或从这样的行照抄的体;别的写者写下的同字词;bundle 拼法;kill-switch 折出来的词;词表外的串 —— 都落在这一个「否」里)。
32
+ * `mode` 绑定为什么承重:更早版本的副本(滚动升级窗、回滚再升回)读 ctx 时把不认识的兄弟键展开带走,把词改成另一个词却不改写据 ⇒ 写据对不上那一格 ⇒ 照钳(失效关闭,不读版本号)。
33
+ * ⚠️ 射程如实:旧副本把词切走又切回写据上的同一个词(批准写下 `acceptEdits` 之后在旧副本上 `acceptEdits → default → acceptEdits` 切档),写据仍对得上 ⇒ 原样 ——
34
+ * 与 7.151.0 同读(不比上一版宽);只看 `mode` 分不出这一形,要分开得让写据记下「最后写这一行的进程认得写据」(未闭项,MIGRATION 如实写)。
35
+ * 无地板时写据不参与判定;读口照样判形(读不出 = 坏体,不论地板)。写据的形只有一种读法({@link permissionModeWriteOf},续跑上下文读口经它判)。
35
36
  *
36
37
  * ## 为什么切档口必须经这只判官(而不是靠引擎的 deny 位)
37
38
  * 引擎在根腿上只认两只模式拒位(`RuntimeCaps.bypassPermissions` / `RuntimeCaps.autoMode`,本仓由 center 与本地 kill-switch 写);上面三问
@@ -44,6 +45,7 @@
44
45
  * `foldedBy`(本判官把词改了的那一站;没改 ⇒ 缺席。① ② 都改了 ⇒ `entry_floor`:落定的词是地板给的)。
45
46
  */
46
47
  import type { PermissionMode } from "@sema-agent/core";
48
+ import type { CheckpointCtx } from "./http/route-ctx.js";
47
49
  import { type Autonomy, type EntryPermissionMode } from "./runtime-governance.js";
48
50
  import { HOST_POSTURE_REFUSED_CODE, type TaskWriteFaceInput } from "./hosted-posture.js";
49
51
  import { type ParsedTaskSettings } from "./task-settings.js";
@@ -83,22 +85,53 @@ export declare function stampSubmissionEntryMode<B extends {
83
85
  readonly body: B;
84
86
  readonly entry: EntryPermissionMode;
85
87
  };
88
+ /** 存储体顶层 `permissionMode` 那一格的写者闭集(顶注「写据」)。加一个写者 ⇒ 加一词;它默认不是任何地板的出口(失效关闭),要当出口必须改 {@link floorExitWriter} 的穷尽表。 */
89
+ export declare const PERMISSION_MODE_WRITERS: readonly ["submission", "switch", "replay", "plan_review"];
90
+ export type PermissionModeWriter = (typeof PERMISSION_MODE_WRITERS)[number];
86
91
  /**
87
- * 一块入口地板的**出口词** —— 存储体上由地板自己的出口写下的词(续跑口原样读它们,理由见顶注「为什么地板的出口词在续跑口原样读」)。
88
- * 闭集穷尽 switch:引擎的模式词表加一词 ⇒ 编译红,逼着回答「这个词当地板时靠什么出口」。`plan` 的出口是人审(`plan_review` 的批准 / 编辑把计划带出 plan,
89
- * 写回的词表 = `task-settings.ts` `PLAN_APPROVE_PERMISSION_MODES_AFTER`,那张表是唯一的源);其余五词今天不是任何档的地板(`runtime-governance.ts` `autonomyModeFloor`
90
- * 只答 `plan` 或缺席),当地板也没有出口 ⇒ 空(失效关闭:比它松的词照钳)。
92
+ * 写据:谁写了存储体顶层 `permissionMode` 那一格、写下的是什么(`mode` = 写完那一刻顶层的原值;缺席 = 那一格写完是空的,或不是引擎的模式词)。
93
+ * 每个写者都写自己的那一张(一条规则,不分写者);读者只在 `by` 是这块地板的出口时拿 `mode` 去对体顶层(绑定,见顶注)。
91
94
  */
92
- export declare function floorExitWords(floor: PermissionMode): readonly PermissionMode[];
95
+ export interface PermissionModeWrite {
96
+ readonly by: PermissionModeWriter;
97
+ readonly mode?: PermissionMode;
98
+ }
99
+ /**
100
+ * 一块入口地板的**出口写者** —— 存储体上由它写下的那一格,续跑口原样读(顶注「写据」)。闭集穷尽 switch:引擎的模式词表加一词 ⇒ 编译红,
101
+ * 逼着回答「这个词当地板时靠什么出口」。`plan` 的出口是人审(`plan_review` 的批准 / 编辑把计划带出 plan);其余五词今天不是任何档的地板
102
+ * (`runtime-governance.ts` `autonomyModeFloor` 只答 `plan` 或缺席),当地板也没有出口 ⇒ 缺席(失效关闭:比它松的词照钳)。
103
+ */
104
+ export declare function floorExitWriter(floor: PermissionMode): PermissionModeWriter | undefined;
105
+ /** 提交写者的那一张写据:`mode` = 这份要落盘的体顶层的原值(严格读;不是引擎的模式词 ⇒ 缺席)。调用点 = 两只构造口调用方(`http/submit-ctx.ts` `persistSubmissionCtx`、`leader/wire.ts` 的 worker 续跑体)。 */
106
+ export declare function submissionPermissionModeWrite(body: {
107
+ readonly permissionMode?: unknown;
108
+ }): PermissionModeWrite;
109
+ /**
110
+ * **改写口**(唯一):把存储体顶层 `permissionMode` 那一格换成 `mode`,同一笔换上写者 `by` 的写据;其余位(含 bundle 拼法、无人值守标、写者版本)随行。
111
+ * 三个调用点:切档写回(`switch`)、续跑口的钳(`replay`,本模块 {@link stampReplayEntryMode})、`plan_review` 的出口写回(`plan_review`)。提交写者不经它(新行经构造口)。
112
+ */
113
+ export declare function withStoredPermissionMode(ctx: CheckpointCtx, mode: PermissionMode, by: Exclude<PermissionModeWriter, "submission">): CheckpointCtx;
114
+ /** {@link permissionModeWriteOf} 的结局:缺席 / 读得出 / 形不对(读口答坏体)。 */
115
+ export type PermissionModeWriteReading = {
116
+ readonly kind: "absent";
117
+ } | {
118
+ readonly kind: "ok";
119
+ readonly write: PermissionModeWrite;
120
+ } | {
121
+ readonly kind: "malformed";
122
+ };
123
+ /**
124
+ * 落盘写据(持久化 JSON,不信型面)的**唯一**读法:缺席 ⇒ `absent`;对象、键只在 `by` / `mode` 之内、`by` 在写者闭集里、`mode` 缺席或是引擎的模式词 ⇒ `ok`(交读法铸的那一份);
125
+ * 其余(不是对象、多键、`by` 不认得 —— 含更新版本加的写者、`mode` 在场却不是模式词)⇒ `malformed`。唯一调用方 = 续跑上下文读口(`http/resume-ctx-read.ts`:`malformed` ⇒ 坏体)。
126
+ */
127
+ export declare function permissionModeWriteOf(stored: unknown): PermissionModeWriteReading;
93
128
  /**
94
- * 续跑口的入口一步:一条停驻 run 的存储体被重放之前,体上的词过 {@link bodyEntry}(与提交口同一只,地板读此刻的活 config),入口词盖进体
95
- * (同一只 `stampEntryPermissionMode`);体顶层 `permissionMode` 的原字是 `bodyEntry` 读到的那块地板的出口词({@link floorExitWords})⇒ 体**原物**交回
96
- * (出口写者只写顶层;那个词已经过了地板)。
97
- * 地板缺席 ⇒ 体原物(零字节变化)。不拒:钳到地板放行,与提交口同形(见顶注「三口的调用形」)。
129
+ * 续跑口的入口一步:一条停驻 run 的存储体被重放之前,体上的词过 {@link bodyEntry}(与提交口同一只,地板读此刻的活 config)。
130
+ * · 地板缺席 ⇒ ctx **原物**(零字节变化;写据不被读);
131
+ * · 写据说体顶层那一格是这块地板的出口写下的({@link floorExitWriter})、且 `mode` 仍等于顶层原值 ⇒ ctx **原物**(那一格已经过了地板);
132
+ * · 其余 ⇒ 经改写口盖入口词、写据换成 `replay`(顶注「写据」:缺写据、别的写者写下的同字词、bundle 拼法、折出来的词都在这里)。
133
+ * 不拒:钳到地板放行,与提交口同形(见顶注「三口的调用形」)。入参是 ctx(写据是体的兄弟键)。
98
134
  * 唯一调用点:`http/resume-legs.ts` 的 `replayCtxOf`(五个续跑读点同调;之后的存体预判、重建、plan_review 的模式词门与驱动段、CAS 赢点的写回读的都是它交回的那一份)。
99
135
  */
100
- export declare function stampReplayEntryMode<B extends {
101
- permissionMode?: unknown;
102
- settings?: unknown;
103
- }>(body: B, deployment: PermissionModeAdmissionFacts["deployment"]): B;
136
+ export declare function stampReplayEntryMode(ctx: CheckpointCtx, deployment: PermissionModeAdmissionFacts["deployment"]): CheckpointCtx;
104
137
  //# sourceMappingURL=permission-mode-admission.d.ts.map
@@ -1,6 +1,6 @@
1
1
  import { autonomyModeFloor, entryPermissionMode } from "./runtime-governance.js";
2
2
  import { HOST_POSTURE_REFUSED_CODE, permissionModeWriteFaceRefusal } from "./hosted-posture.js";
3
- import { PLAN_APPROVE_PERMISSION_MODES_AFTER, coercePermissionModeStrict, foldDisableAutoMode, parseTaskSettings, requestedPermissionMode, stampEntryPermissionMode } from "./task-settings.js";
3
+ import { coercePermissionModeStrict, foldDisableAutoMode, parseTaskSettings, requestedPermissionMode, stampEntryPermissionMode } from "./task-settings.js";
4
4
  export const PERMISSION_MODE_ADMISSION_FOLDS = ["entry_floor", "disable_auto_mode"];
5
5
  function entryStep(word, facts) {
6
6
  const folded = foldDisableAutoMode(word, facts.settings);
@@ -23,27 +23,54 @@ export function stampSubmissionEntryMode(body, deployment) {
23
23
  const entry = bodyEntry(body, deployment);
24
24
  return { body: stampEntryPermissionMode(body, entry), entry };
25
25
  }
26
- export function floorExitWords(floor) {
26
+ export const PERMISSION_MODE_WRITERS = ["submission", "switch", "replay", "plan_review"];
27
+ export function floorExitWriter(floor) {
27
28
  switch (floor) {
28
29
  case "plan":
29
- return PLAN_APPROVE_PERMISSION_MODES_AFTER;
30
+ return "plan_review";
30
31
  case "default":
31
32
  case "acceptEdits":
32
33
  case "auto":
33
34
  case "bypassPermissions":
34
35
  case "dontAsk":
35
- return [];
36
+ return undefined;
36
37
  default: {
37
38
  const unhandled = floor;
38
39
  throw new Error(`entry floor "${String(unhandled)}" has no exit arm`);
39
40
  }
40
41
  }
41
42
  }
42
- export function stampReplayEntryMode(body, deployment) {
43
- const entry = bodyEntry(body, deployment);
44
- const top = coercePermissionModeStrict(body.permissionMode);
45
- if (entry.floor !== undefined && top !== undefined && floorExitWords(entry.floor).includes(top))
46
- return body;
47
- return stampEntryPermissionMode(body, entry);
43
+ export function submissionPermissionModeWrite(body) {
44
+ const mode = coercePermissionModeStrict(body.permissionMode);
45
+ return { by: "submission", ...(mode !== undefined ? { mode } : {}) };
46
+ }
47
+ export function withStoredPermissionMode(ctx, mode, by) {
48
+ return { ...ctx, body: { ...ctx.body, permissionMode: mode }, permissionModeWrite: { by, mode } };
49
+ }
50
+ export function permissionModeWriteOf(stored) {
51
+ if (stored === undefined)
52
+ return { kind: "absent" };
53
+ if (typeof stored !== "object" || stored === null || Array.isArray(stored))
54
+ return { kind: "malformed" };
55
+ if (!Object.keys(stored).every((k) => k === "by" || k === "mode"))
56
+ return { kind: "malformed" };
57
+ const by = Object.hasOwn(stored, "by") ? stored["by"] : undefined;
58
+ const raw = Object.hasOwn(stored, "mode") ? stored["mode"] : undefined;
59
+ const writer = PERMISSION_MODE_WRITERS.find((w) => w === by);
60
+ if (writer === undefined)
61
+ return { kind: "malformed" };
62
+ if (raw === undefined)
63
+ return { kind: "ok", write: { by: writer } };
64
+ const mode = coercePermissionModeStrict(raw);
65
+ return mode === undefined ? { kind: "malformed" } : { kind: "ok", write: { by: writer, mode } };
66
+ }
67
+ export function stampReplayEntryMode(ctx, deployment) {
68
+ const { floor, effective } = bodyEntry(ctx.body, deployment);
69
+ if (floor === undefined || effective === undefined)
70
+ return ctx;
71
+ const write = ctx.permissionModeWrite;
72
+ if (write !== undefined && write.by === floorExitWriter(floor) && write.mode !== undefined && write.mode === ctx.body.permissionMode)
73
+ return ctx;
74
+ return withStoredPermissionMode(ctx, effective, "replay");
48
75
  }
49
76
  //# sourceMappingURL=permission-mode-admission.js.map
@@ -235,6 +235,10 @@ export declare class FileApprovalAskStore implements ApprovalAskStore {
235
235
  listPendingBySession(sessionId: string, owner: string | null, signal?: AbortSignal): Promise<AskRow[]>;
236
236
  listByCall(call: AskCallCoordinate): Promise<AskRow[]>;
237
237
  listParkingBySourceSession(sourceSessionId: string): Promise<AskRow[]>;
238
+ listAsksAtGate(sourceSessionId: string, binding: {
239
+ readonly boundCallId: string;
240
+ readonly boundInputHash: string;
241
+ }): Promise<AskRow[]>;
238
242
  getAsk(askId: string): Promise<AskRow | null>;
239
243
  getByIdempotencyKey(taskId: string, key: string): Promise<AskRow | null>;
240
244
  getBatch(batchId: string): Promise<BatchRow | null>;
@@ -386,6 +386,9 @@ export class FileApprovalAskStore {
386
386
  listParkingBySourceSession(sourceSessionId) {
387
387
  return this.core.listParkingBySourceSession(sourceSessionId);
388
388
  }
389
+ listAsksAtGate(sourceSessionId, binding) {
390
+ return this.core.listAsksAtGate(sourceSessionId, binding);
391
+ }
389
392
  getAsk(askId) {
390
393
  return this.core.getAsk(askId);
391
394
  }
@@ -129,6 +129,10 @@ export declare class InMemoryApprovalAskStore implements ApprovalAskStore {
129
129
  listPendingBySession(sessionId: string, owner: string | null, signal?: AbortSignal): Promise<AskRow[]>;
130
130
  listByCall(call: AskCallCoordinate): Promise<AskRow[]>;
131
131
  listParkingBySourceSession(sourceSessionId: string): Promise<AskRow[]>;
132
+ listAsksAtGate(sourceSessionId: string, binding: {
133
+ readonly boundCallId: string;
134
+ readonly boundInputHash: string;
135
+ }): Promise<AskRow[]>;
132
136
  getAsk(askId: string): Promise<AskRow | null>;
133
137
  getByIdempotencyKey(taskId: string, key: string): Promise<AskRow | null>;
134
138
  resolveProvisional(askId: string, from: AskState, to: AskState, patch: AskTransitionPatch): Promise<boolean>;
@@ -1,6 +1,6 @@
1
1
  import { ASK_TERMINAL, canAskTransition } from "../approval-ask-machine.js";
2
2
  import { APPROVAL_ENDING_CAUSES } from "../approval-deny-reasons.js";
3
- import { askAttachedToSession, askRowCarriesOwner, askRowOnCall, assertDecideSource, assertIdempotencyKeyShape, assertIntegralEpochMs, assertReapBounds, ApprovalAskReadAbortedError, claimTargetState, POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth, projectDecideReading } from "./approval-ask-store-sql.js";
3
+ import { askAtGate, askAttachedToSession, askRowCarriesOwner, askRowOnCall, assertDecideSource, assertIdempotencyKeyShape, assertIntegralEpochMs, assertReapBounds, ApprovalAskReadAbortedError, claimTargetState, POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth, projectDecideReading } from "./approval-ask-store-sql.js";
4
4
  function applyDecisionRecord(row, record, now) {
5
5
  row.decision = record.decision;
6
6
  row.decisionActor = record.decisionActor ?? null;
@@ -384,6 +384,12 @@ export class InMemoryApprovalAskStore {
384
384
  .sort((a, b) => a.createdAtMs - b.createdAtMs || (a.askId < b.askId ? -1 : a.askId > b.askId ? 1 : 0))
385
385
  .map((r) => ({ ...r }));
386
386
  }
387
+ async listAsksAtGate(sourceSessionId, binding) {
388
+ return [...this.asks.values()]
389
+ .filter((r) => askAtGate(r, sourceSessionId, binding))
390
+ .sort((a, b) => a.createdAtMs - b.createdAtMs || (a.askId < b.askId ? -1 : a.askId > b.askId ? 1 : 0))
391
+ .map((r) => ({ ...r }));
392
+ }
387
393
  async getAsk(askId) {
388
394
  const r = this.asks.get(askId);
389
395
  return r ? { ...r } : null;