@sema-agent/server 7.141.0 → 7.143.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 (341) hide show
  1. package/MIGRATION.md +657 -0
  2. package/USAGE.md +102 -30
  3. package/dist/adoption/plan.js +22 -0
  4. package/dist/approval-ask-audit-store.d.ts +61 -43
  5. package/dist/approval-ask-audit-store.js +69 -44
  6. package/dist/approval-ask-machine.d.ts +15 -7
  7. package/dist/approval-ask-machine.js +10 -4
  8. package/dist/approval-card.d.ts +126 -27
  9. package/dist/approval-card.js +103 -23
  10. package/dist/approval-crash-convergence.d.ts +17 -7
  11. package/dist/approval-crash-convergence.js +94 -22
  12. package/dist/approval-deny-reasons.d.ts +34 -30
  13. package/dist/approval-deny-reasons.js +21 -6
  14. package/dist/approval-policy-settlement.d.ts +1 -1
  15. package/dist/approval-queue-kind.d.ts +27 -0
  16. package/dist/approval-queue-kind.js +6 -0
  17. package/dist/approval-reconciler.d.ts +41 -9
  18. package/dist/approval-reconciler.js +45 -42
  19. package/dist/audit.js +4 -2
  20. package/dist/auth-bridge.d.ts +9 -6
  21. package/dist/auth-bridge.js +9 -3
  22. package/dist/boot/adoption.js +2 -1
  23. package/dist/boot/budget-tracing.d.ts +17 -1
  24. package/dist/boot/budget-tracing.js +10 -6
  25. package/dist/boot/device-lane.d.ts +1 -1
  26. package/dist/boot/execution-env.d.ts +24 -4
  27. package/dist/boot/execution-env.js +26 -23
  28. package/dist/boot/execution-lane.js +2 -1
  29. package/dist/boot/exit-boundary.js +5 -4
  30. package/dist/boot/governance-seams.d.ts +26 -41
  31. package/dist/boot/governance-seams.js +61 -28
  32. package/dist/boot/late-binds.d.ts +7 -4
  33. package/dist/boot/late-binds.js +3 -1
  34. package/dist/boot/leader.js +2 -4
  35. package/dist/boot/memory-consolidation.d.ts +0 -8
  36. package/dist/boot/memory-consolidation.js +5 -4
  37. package/dist/boot/parked-revive-gate.js +2 -1
  38. package/dist/boot/permission-rules-audit.d.ts +0 -28
  39. package/dist/boot/permission-rules-audit.js +2 -1
  40. package/dist/boot/read-deny-anchor-audit.js +2 -1
  41. package/dist/boot/reapers.js +9 -7
  42. package/dist/boot/resolve-spec.d.ts +1 -1
  43. package/dist/boot/resolve-spec.js +19 -10
  44. package/dist/boot/retention-lane.js +5 -3
  45. package/dist/boot/runner-deps.d.ts +10 -7
  46. package/dist/boot/runner-deps.js +20 -29
  47. package/dist/boot/runtime-caps.js +3 -2
  48. package/dist/boot/session-faces.js +4 -3
  49. package/dist/boot/shutdown.d.ts +9 -0
  50. package/dist/boot/shutdown.js +19 -4
  51. package/dist/boot/stage-02-web-admin-pull.js +3 -0
  52. package/dist/boot/stage-03-store-backend.d.ts +4 -1
  53. package/dist/boot/stage-03-store-backend.js +1 -2
  54. package/dist/boot/stage-04-stores.d.ts +5 -2
  55. package/dist/boot/stage-04-stores.js +6 -7
  56. package/dist/boot/stage-05-execution-env.d.ts +6 -3
  57. package/dist/boot/stage-05-execution-env.js +8 -6
  58. package/dist/boot/stage-06-runners.d.ts +6 -3
  59. package/dist/boot/stage-06-runners.js +3 -4
  60. package/dist/boot/stage-07-capability-layer.d.ts +6 -3
  61. package/dist/boot/stage-07-capability-layer.js +7 -2
  62. package/dist/boot/stage-08-reapers.d.ts +6 -3
  63. package/dist/boot/stage-08-reapers.js +3 -2
  64. package/dist/boot/stage-09-leader.d.ts +6 -3
  65. package/dist/boot/stage-10-http-server.d.ts +6 -3
  66. package/dist/boot/stage-10-http-server.js +14 -5
  67. package/dist/boot/stores.d.ts +5 -2
  68. package/dist/boot/stores.js +10 -9
  69. package/dist/boot/web-admin.d.ts +5 -1
  70. package/dist/boot/web-admin.js +48 -36
  71. package/dist/boot/webfetch-summarize-lane.js +2 -1
  72. package/dist/boot/workflow-orchestration.js +3 -2
  73. package/dist/capabilities/builtin-tools.d.ts +2 -1
  74. package/dist/capabilities/builtin-tools.js +2 -4
  75. package/dist/capabilities/composite-members.d.ts +2 -1
  76. package/dist/capabilities/composite-members.js +2 -1
  77. package/dist/capabilities/hands-lane.d.ts +9 -39
  78. package/dist/capabilities/hands-lane.js +8 -13
  79. package/dist/capabilities/mcp-probe.d.ts +3 -2
  80. package/dist/capabilities/scenarios.d.ts +11 -1
  81. package/dist/capabilities/scenarios.js +1 -0
  82. package/dist/capabilities/skills.d.ts +1 -17
  83. package/dist/capabilities/skills.js +0 -8
  84. package/dist/capability-facts.d.ts +31 -7
  85. package/dist/capability-facts.js +27 -4
  86. package/dist/card-void.d.ts +6 -9
  87. package/dist/config-catalog.js +2 -1
  88. package/dist/config-dir-trust.js +58 -29
  89. package/dist/config-invariants.d.ts +7 -1
  90. package/dist/config-invariants.js +7 -0
  91. package/dist/config-provider.d.ts +10 -0
  92. package/dist/config-provider.js +15 -4
  93. package/dist/config-root-migration.d.ts +13 -0
  94. package/dist/config-root-migration.js +99 -0
  95. package/dist/config-types.d.ts +19 -8
  96. package/dist/config.d.ts +10 -3
  97. package/dist/config.js +18 -4
  98. package/dist/delegated-session-owner.d.ts +5 -3
  99. package/dist/delegated-session-owner.js +1 -1
  100. package/dist/device-ws-protocol.d.ts +1 -0
  101. package/dist/device-ws-protocol.js +2 -1
  102. package/dist/env-model-table-gate.d.ts +53 -0
  103. package/dist/env-model-table-gate.js +53 -0
  104. package/dist/execution-lane-caps.d.ts +2 -1
  105. package/dist/fleet/fleet-bus.d.ts +34 -55
  106. package/dist/fleet/fleet-bus.js +35 -100
  107. package/dist/fleet/fleet-reconciler.d.ts +4 -4
  108. package/dist/fleet/fleet-reconciler.js +1 -1
  109. package/dist/fleet/subagent-tail-bus.d.ts +14 -11
  110. package/dist/fleet/subagent-tail-bus.js +15 -28
  111. package/dist/hooks/hook-runner.d.ts +6 -0
  112. package/dist/hooks/hook-runner.js +58 -8
  113. package/dist/host-decision.d.ts +54 -20
  114. package/dist/host-decision.js +42 -14
  115. package/dist/http/admission.d.ts +1 -1
  116. package/dist/http/admission.js +7 -3
  117. package/dist/http/approval-sweeps.js +11 -8
  118. package/dist/http/caller-trust.d.ts +1 -1
  119. package/dist/http/dispatch.js +4 -2
  120. package/dist/http/durable-run-leg.js +2 -2
  121. package/dist/http/engine-refusal.d.ts +16 -0
  122. package/dist/http/engine-refusal.js +17 -0
  123. package/dist/http/execution-context.d.ts +2 -2
  124. package/dist/http/idempotency.d.ts +1 -1
  125. package/dist/http/memory-compliance-failure-reply.d.ts +1 -3
  126. package/dist/http/memory-compliance-failure-reply.js +4 -6
  127. package/dist/http/owner-access.d.ts +11 -0
  128. package/dist/http/owner-access.js +13 -0
  129. package/dist/http/principal-gate.d.ts +3 -1
  130. package/dist/http/principal-gate.js +17 -8
  131. package/dist/http/resume-legs.d.ts +21 -7
  132. package/dist/http/resume-legs.js +76 -58
  133. package/dist/http/resume-restatement.d.ts +39 -0
  134. package/dist/http/resume-restatement.js +25 -0
  135. package/dist/http/route-ctx.d.ts +35 -10
  136. package/dist/http/route-ctx.js +1 -1
  137. package/dist/http/route-table.js +2 -0
  138. package/dist/http/routes/approvals-assistant.d.ts +9 -1
  139. package/dist/http/routes/approvals-assistant.js +142 -31
  140. package/dist/http/routes/attachments.js +18 -0
  141. package/dist/http/routes/capabilities.js +16 -4
  142. package/dist/http/routes/memory-policy.d.ts +2 -2
  143. package/dist/http/routes/memory-policy.js +27 -7
  144. package/dist/http/routes/notify-wake.js +23 -25
  145. package/dist/http/routes/rules.js +48 -13
  146. package/dist/http/routes/runs.js +12 -11
  147. package/dist/http/routes/session-memory-sync.d.ts +91 -0
  148. package/dist/http/routes/session-memory-sync.js +174 -0
  149. package/dist/http/routes/session-sync.js +13 -20
  150. package/dist/http/routes/sessions-list.d.ts +2 -1
  151. package/dist/http/routes/sessions.d.ts +2 -2
  152. package/dist/http/routes/sessions.js +22 -23
  153. package/dist/http/routes/tasks.js +34 -54
  154. package/dist/http/run-read-face.d.ts +7 -0
  155. package/dist/http/run-read-face.js +9 -3
  156. package/dist/http/send.d.ts +5 -0
  157. package/dist/http/send.js +9 -2
  158. package/dist/http/server.d.ts +27 -15
  159. package/dist/http/server.js +4 -6
  160. package/dist/http/stored-body-replay.js +15 -4
  161. package/dist/http/submit-replay.d.ts +4 -3
  162. package/dist/http/submit-replay.js +2 -2
  163. package/dist/http/sync-submit-body.d.ts +21 -4
  164. package/dist/http/sync-submit-body.js +13 -0
  165. package/dist/http/wire-types.d.ts +17 -7
  166. package/dist/identity-source.d.ts +21 -2
  167. package/dist/identity-source.js +6 -0
  168. package/dist/leader/leader.js +5 -2
  169. package/dist/leader/wire.d.ts +7 -1
  170. package/dist/leader/wire.js +6 -5
  171. package/dist/leg-facts.d.ts +37 -0
  172. package/dist/leg-facts.js +15 -0
  173. package/dist/memory-bundle-engine.d.ts +1 -4
  174. package/dist/memory-bundle-engine.js +2 -2
  175. package/dist/memory-incident-sink.d.ts +19 -6
  176. package/dist/memory-incident-sink.js +8 -4
  177. package/dist/memory-operator-faces.d.ts +30 -66
  178. package/dist/memory-operator-faces.js +17 -88
  179. package/dist/memory-scope-stores.d.ts +38 -40
  180. package/dist/memory-scope-stores.js +51 -36
  181. package/dist/memory-scope.d.ts +0 -9
  182. package/dist/memory-scope.js +0 -9
  183. package/dist/memory-sync-client.d.ts +21 -21
  184. package/dist/memory-sync-client.js +62 -43
  185. package/dist/memory-sync-identity.d.ts +24 -0
  186. package/dist/memory-sync-identity.js +57 -0
  187. package/dist/memory-sync.d.ts +27 -5
  188. package/dist/memory-sync.js +29 -7
  189. package/dist/model-plane-face.d.ts +13 -7
  190. package/dist/model-plane-face.js +2 -1
  191. package/dist/observability/fail-open.d.ts +47 -14
  192. package/dist/observability/fail-open.js +37 -11
  193. package/dist/observability/run-terminal.d.ts +92 -34
  194. package/dist/observability/run-terminal.js +171 -162
  195. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -19
  196. package/dist/orchestration/workflow-completion-inbox.js +0 -12
  197. package/dist/orchestration/workflow-notify-journal.d.ts +3 -2
  198. package/dist/orchestration/workflow-park-decision.d.ts +8 -3
  199. package/dist/orchestration/workflow-park-decision.js +5 -3
  200. package/dist/org-delivery-gate.d.ts +11 -0
  201. package/dist/org-delivery-gate.js +19 -1
  202. package/dist/parked-decide.d.ts +13 -9
  203. package/dist/parked-decide.js +7 -7
  204. package/dist/parked-revive-surface.js +1 -1
  205. package/dist/peer-inbound-face.d.ts +127 -0
  206. package/dist/peer-inbound-face.js +158 -0
  207. package/dist/permission-rule-list.d.ts +13 -9
  208. package/dist/permission-rule-list.js +7 -3
  209. package/dist/plugins/approval-ask-store-file.d.ts +14 -4
  210. package/dist/plugins/approval-ask-store-file.js +64 -11
  211. package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
  212. package/dist/plugins/approval-ask-store-memory.js +17 -14
  213. package/dist/plugins/approval-ask-store-sql.d.ts +70 -23
  214. package/dist/plugins/approval-ask-store-sql.js +79 -30
  215. package/dist/plugins/checkpoint-store-sql.d.ts +4 -4
  216. package/dist/plugins/checkpoint-store-sql.js +9 -7
  217. package/dist/plugins/file-history-store-sql.d.ts +24 -18
  218. package/dist/plugins/file-history-store-sql.js +24 -19
  219. package/dist/plugins/file-run-store.d.ts +35 -1
  220. package/dist/plugins/file-run-store.js +108 -9
  221. package/dist/plugins/imported-layer-root-retirement.d.ts +82 -0
  222. package/dist/plugins/imported-layer-root-retirement.js +96 -0
  223. package/dist/plugins/local-checkpoint-store.d.ts +1 -1
  224. package/dist/plugins/local-checkpoint-store.js +2 -2
  225. package/dist/plugins/local-session-store.d.ts +42 -9
  226. package/dist/plugins/local-session-store.js +83 -33
  227. package/dist/plugins/local-task-attachment-store.d.ts +12 -2
  228. package/dist/plugins/local-task-attachment-store.js +67 -43
  229. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +9 -31
  230. package/dist/plugins/mailbox-recipient-lifecycle.js +4 -0
  231. package/dist/plugins/mailbox-store-sql.d.ts +12 -8
  232. package/dist/plugins/mailbox-store-sql.js +76 -7
  233. package/dist/plugins/memory-key-guards.d.ts +3 -3
  234. package/dist/plugins/memory-run-store.d.ts +6 -1
  235. package/dist/plugins/memory-run-store.js +11 -1
  236. package/dist/plugins/memory-sync-store-pg.d.ts +15 -0
  237. package/dist/plugins/memory-sync-store-pg.js +21 -0
  238. package/dist/plugins/memory-sync-store-tidb.d.ts +2 -0
  239. package/dist/plugins/memory-sync-store-tidb.js +13 -1
  240. package/dist/plugins/one-time-migrations.d.ts +58 -2
  241. package/dist/plugins/one-time-migrations.js +161 -4
  242. package/dist/plugins/org-delivery-quota-store.d.ts +1 -1
  243. package/dist/plugins/org-delivery-quota-store.js +2 -12
  244. package/dist/plugins/park-commit-watch.d.ts +1 -1
  245. package/dist/plugins/permission-rule-store-file.d.ts +10 -1
  246. package/dist/plugins/permission-rule-store-file.js +15 -2
  247. package/dist/plugins/permission-rule-store-sql.d.ts +31 -1
  248. package/dist/plugins/permission-rule-store-sql.js +35 -2
  249. package/dist/plugins/remote-env-device.d.ts +8 -11
  250. package/dist/plugins/remote-env-device.js +6 -3
  251. package/dist/plugins/remote-env-e2b.d.ts +23 -4
  252. package/dist/plugins/remote-env-e2b.js +111 -28
  253. package/dist/plugins/remote-env-k8s.d.ts +61 -22
  254. package/dist/plugins/remote-env-k8s.js +133 -33
  255. package/dist/plugins/remote-env-local-docker.d.ts +15 -3
  256. package/dist/plugins/remote-env-local-docker.js +40 -12
  257. package/dist/plugins/retention-store-sql.d.ts +2 -2
  258. package/dist/plugins/run-store-sql.d.ts +9 -1
  259. package/dist/plugins/run-store-sql.js +9 -1
  260. package/dist/plugins/sandbox-read-floor.d.ts +5 -7
  261. package/dist/plugins/sandbox-read-floor.js +2 -7
  262. package/dist/plugins/sql-errors.d.ts +10 -1
  263. package/dist/plugins/sql-errors.js +13 -0
  264. package/dist/plugins/store-backend.d.ts +33 -35
  265. package/dist/plugins/store-backend.js +10 -1
  266. package/dist/plugins/store-contracts.d.ts +50 -1
  267. package/dist/plugins/store-contracts.js +6 -0
  268. package/dist/plugins/task-attachment-store.d.ts +112 -14
  269. package/dist/plugins/task-attachment-store.js +207 -29
  270. package/dist/plugins/tool-result-store-sql.d.ts +9 -8
  271. package/dist/plugins/tool-result-store-sql.js +23 -2
  272. package/dist/plugins/wiring-section-codec.d.ts +32 -0
  273. package/dist/plugins/wiring-section-codec.js +55 -0
  274. package/dist/plugins/workspace-fill.d.ts +60 -0
  275. package/dist/plugins/workspace-fill.js +106 -0
  276. package/dist/project-bundle.d.ts +38 -0
  277. package/dist/project-bundle.js +609 -0
  278. package/dist/rule-entry-list.d.ts +3 -2
  279. package/dist/rule-entry-list.js +5 -1
  280. package/dist/rules-consent.d.ts +28 -5
  281. package/dist/rules-consent.js +40 -28
  282. package/dist/run-local.js +11 -13
  283. package/dist/runs.d.ts +12 -8
  284. package/dist/runs.js +6 -8
  285. package/dist/runtime-governance.d.ts +1 -1
  286. package/dist/sandbox-egress-policy.d.ts +4 -1
  287. package/dist/sandbox-egress-policy.js +3 -1
  288. package/dist/sandbox-git-plane.d.ts +31 -12
  289. package/dist/sandbox-git-plane.js +24 -4
  290. package/dist/security.d.ts +9 -0
  291. package/dist/security.js +8 -0
  292. package/dist/server-secret-env.d.ts +4 -0
  293. package/dist/session-error-wire.d.ts +36 -0
  294. package/dist/session-error-wire.js +13 -0
  295. package/dist/session-incarnation.d.ts +1 -1
  296. package/dist/session-notify-door.d.ts +106 -0
  297. package/dist/session-notify-door.js +93 -0
  298. package/dist/session-sync.d.ts +9 -14
  299. package/dist/session-sync.js +5 -5
  300. package/dist/session-workspace.d.ts +3 -13
  301. package/dist/session-workspace.js +0 -9
  302. package/dist/standing-answer-grant.d.ts +5 -0
  303. package/dist/standing-answer-grant.js +10 -0
  304. package/dist/stores-sql/session-policy-store-sql.d.ts +4 -2
  305. package/dist/stores-sql/session-policy-store-sql.js +3 -13
  306. package/dist/task-mcp.d.ts +1 -1
  307. package/dist/task-settings.d.ts +21 -10
  308. package/dist/task-settings.js +46 -13
  309. package/dist/terminal.d.ts +39 -19
  310. package/dist/terminal.js +65 -22
  311. package/dist/tool-approval.d.ts +56 -37
  312. package/dist/tool-approval.js +78 -71
  313. package/dist/trace/core-keyset-guard.d.ts +134 -14
  314. package/dist/trace/engine-notice-wire.d.ts +9 -3
  315. package/dist/trace/engine-notice-wire.js +7 -3
  316. package/dist/trace/exit-seat.d.ts +9 -2
  317. package/dist/trace/exit-seat.js +1 -0
  318. package/dist/trace/ledger-sink.d.ts +10 -6
  319. package/dist/trace/ledger-sink.js +2 -2
  320. package/dist/trace/model-segment.d.ts +55 -25
  321. package/dist/trace/model-segment.js +101 -33
  322. package/dist/trace/project.d.ts +25 -5
  323. package/dist/trace/project.js +22 -0
  324. package/dist/trace/redact.d.ts +61 -9
  325. package/dist/trace/redact.js +1490 -468
  326. package/dist/trace/self-minted-wire.d.ts +745 -24
  327. package/dist/trace/self-minted-wire.js +239 -9
  328. package/dist/trace/wire-projection-faces.d.ts +2 -2
  329. package/dist/trace/wire-projection-faces.js +2 -2
  330. package/dist/verifier-pause-card.d.ts +33 -5
  331. package/dist/verifier-pause-card.js +15 -10
  332. package/dist/web-admin-client/apply-effective.d.ts +12 -16
  333. package/dist/web-admin-client/apply-effective.js +109 -9
  334. package/dist/web-admin-client/effective-keys.js +2 -0
  335. package/dist/web-admin-client/types.d.ts +4 -0
  336. package/dist/workspace-space.d.ts +19 -3
  337. package/dist/workspace-space.js +8 -4
  338. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -1
  339. package/package.json +3 -2
  340. package/wire-changes/server@7.142.0.json +639 -0
  341. package/wire-changes/server@7.143.0.json +1167 -0
@@ -4,6 +4,7 @@ import vm from "node:vm";
4
4
  import { hostShell, resolveHostShell, adoptHostForegroundChild, killTreeSoft, killTreeHard } from "../plugins/host-platform.js";
5
5
  import { classifyHooksConfig, DEFAULT_HOOK_TIMEOUT_SECONDS, HOOK_ENTRY_TYPES, HOOK_EVENTS, HOOK_EVENT_OWNER, HOOK_EVENT_MATCHER_FIELD, hookMatcherForm, hookMatcherSelects, interpolateHookEnvVars, } from "@sema-agent/settings-schema/hooks";
6
6
  import { findUrlCredential, formatConfigPath, outgoingPath } from "@sema-agent/settings-schema";
7
+ import { httpHookEnvVarsAllowed, httpHookUrlVerdict } from "@sema-agent/settings-schema";
7
8
  import { ENGINE_OWNED_HOOK_EVENTS } from "@sema-agent/settings-schema/hooks";
8
9
  import { describeToolCatalog } from "@sema-agent/core";
9
10
  import { hangClassic } from "./classic-stations.js";
@@ -511,6 +512,7 @@ export function hookIfMatches(cond, payload) {
511
512
  return "unevaluable";
512
513
  }
513
514
  }
515
+ const SERVER_WORKSPACE_TRUST = "no_trust_step";
514
516
  const MAX_LLM_ENTRIES_PER_EVENT = 4;
515
517
  const DEFAULT_PROMPT_HOOK_TIMEOUT_SECONDS = 30;
516
518
  const DEFAULT_AGENT_HOOK_TIMEOUT_SECONDS = 120;
@@ -670,19 +672,37 @@ function noteVerdictUntrusted(ctx, args) {
670
672
  return;
671
673
  emitHookNotice(ctx, notice);
672
674
  }
675
+ const VERDICT_GAP_CAUSE = {
676
+ matcher_invalid: "verdict_unfinished",
677
+ llm_carrier_unavailable: "verdict_unfinished",
678
+ llm_cap: "verdict_unfinished",
679
+ powershell_unsupported: "verdict_unfinished",
680
+ if_unevaluable: "verdict_unfinished",
681
+ budget_exhausted: "verdict_unfinished",
682
+ spawn_failed: "verdict_unfinished",
683
+ timeout: "verdict_unfinished",
684
+ exit_nonzero: "verdict_unfinished",
685
+ bad_json: "verdict_unreadable",
686
+ decision_out_of_set: "verdict_unreadable",
687
+ evidence_unavailable: "verdict_unfinished",
688
+ rewrite_unsettled: "verdict_unfinished",
689
+ url_not_allowed: "verdict_unfinished",
690
+ input_truncated: "verdict_unreadable",
691
+ matcher_over_budget: "verdict_unreadable",
692
+ };
673
693
  function createUnrunLedger(ctx, event, toolName) {
674
- let managedWithoutVerdict = false;
694
+ let managedVerdictGap;
675
695
  return {
676
696
  untrusted(source, entry, reason) {
677
697
  if (hookPhaseOf(source) === "unmanaged")
678
698
  return;
679
- managedWithoutVerdict = true;
699
+ managedVerdictGap ??= VERDICT_GAP_CAUSE[reason];
680
700
  noteVerdictUntrusted(ctx, { event, reason, entry, source, toolName });
681
701
  },
682
702
  unrun(source, entry, reason, detail) {
683
703
  if (source === undefined || hookPhaseOf(source) === "unmanaged")
684
704
  return;
685
- managedWithoutVerdict = true;
705
+ managedVerdictGap ??= VERDICT_GAP_CAUSE[reason];
686
706
  ctx.logger.warn("hook_managed_unrun", { event, reason, source: echoCallerText(source), type: entry.type, ...(toolName !== undefined ? { tool: toolName } : {}) });
687
707
  if (!ctx.onHookNotice)
688
708
  return;
@@ -697,8 +717,8 @@ function createUnrunLedger(ctx, event, toolName) {
697
717
  ...(detail !== undefined ? { detail: boundedNoticeText(detail) } : {}),
698
718
  });
699
719
  },
700
- get managedWithoutVerdict() {
701
- return managedWithoutVerdict;
720
+ get managedVerdictGap() {
721
+ return managedVerdictGap;
702
722
  },
703
723
  };
704
724
  }
@@ -996,8 +1016,29 @@ async function runLlmHook(entry, payload, ctx, extra) {
996
1016
  clearTimeout(hardTopTimer);
997
1017
  }
998
1018
  }
1019
+ function httpHookWithheldSentence(event, block) {
1020
+ const field = `settings.${block.list}`;
1021
+ const [why, exit] = (() => {
1022
+ switch (block.rule) {
1023
+ case "url":
1024
+ return [`its url matches no pattern in ${field}`, `add a pattern that matches the url to ${field}, or point the hook at a url the list allows`];
1025
+ case "empty":
1026
+ return [`${field} is an empty list, so no http hook may run`, `add a pattern that matches the url to ${field}`];
1027
+ case "malformed":
1028
+ return [`${field} is not an array of strings, so it is read as allowing no url`, `write ${field} as an array of url pattern strings`];
1029
+ default: {
1030
+ const unhandled = block.rule;
1031
+ throw new Error(`httpHookWithheldSentence: unhandled rule ${String(unhandled)}`);
1032
+ }
1033
+ }
1034
+ })();
1035
+ return `settings.hooks.${event}: http hook not sent — ${why} (rule: ${block.rule}); ${exit}`;
1036
+ }
999
1037
  const MAX_BLANKED_REFERENCES_LOGGED = 16;
1000
1038
  async function runHttpHook(entry, payload, ctx, event) {
1039
+ const block = httpHookUrlVerdict(entry.url, ctx.httpHookAllowlistLayers, SERVER_WORKSPACE_TRUST);
1040
+ if (block !== undefined)
1041
+ return { code: null, stdout: "", stderr: "", timedOut: false, withheld: block };
1001
1042
  const timeoutMs = Math.min(entry.timeout ?? DEFAULT_HOOK_TIMEOUT_SECONDS, MAX_HOOK_TIMEOUT_SECONDS) * 1000;
1002
1043
  let hookChildVars;
1003
1044
  try {
@@ -1006,7 +1047,7 @@ async function runHttpHook(entry, payload, ctx, event) {
1006
1047
  catch (e) {
1007
1048
  return { code: null, stdout: "", stderr: "", timedOut: false, spawnError: String(e) };
1008
1049
  }
1009
- const interpolated = interpolateHookEnvVars(entry.headers ?? {}, entry.allowedEnvVars ?? [], hookChildVars);
1050
+ const interpolated = interpolateHookEnvVars(entry.headers ?? {}, httpHookEnvVarsAllowed(entry.allowedEnvVars ?? [], ctx.httpHookAllowlistLayers, SERVER_WORKSPACE_TRUST), hookChildVars);
1010
1051
  if (interpolated.blanked.length > 0) {
1011
1052
  ctx.logger.warn("hook_http_header_env_blanked", {
1012
1053
  event,
@@ -1210,6 +1251,12 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
1210
1251
  ctx.logger.warn("hook_command_killed", { event, type: entry.type, killed: true, signal: run.aborted.signal });
1211
1252
  return { kind: "halted" };
1212
1253
  }
1254
+ if (run.withheld !== undefined) {
1255
+ const { list, rule, sources } = run.withheld;
1256
+ ctx.logger.warn("hook_http_withheld", { event, url: entry.type === "http" ? buildDisplaySafeUrl(entry.url) : null, list, rule, sources: [...sources] });
1257
+ noteHookFailure(ctx, { event, entry, reason: "url_not_allowed", detail: httpHookWithheldSentence(event, run.withheld), ...(toolName !== undefined ? { toolName } : {}) });
1258
+ return { kind: "unrun", reason: "url_not_allowed" };
1259
+ }
1213
1260
  if (run.spawnError) {
1214
1261
  ctx.logger.warn(entry.type === "http" ? "hook_http_request_failed" : entry.type === "prompt" || entry.type === "agent" ? "hook_llm_failed" : "hook_command_spawn_failed", { event, type: entry.type, error: seatText("operator-log-error", run.spawnError) });
1215
1262
  if (!run.noticed)
@@ -1465,12 +1512,14 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
1465
1512
  }
1466
1513
  }
1467
1514
  if (asked !== undefined) {
1468
- return { action: "ask", message: asked, ...(rewritten ? { updatedInput: currentInput } : {}), ...contextField() };
1515
+ return { action: "ask", message: asked, questionCause: "asked", ...(rewritten ? { updatedInput: currentInput } : {}), ...contextField() };
1469
1516
  }
1470
- if (ledger.managedWithoutVerdict) {
1517
+ const managedGap = ledger.managedVerdictGap;
1518
+ if (managedGap !== undefined) {
1471
1519
  return {
1472
1520
  action: "ask",
1473
1521
  message: "a managed PreToolUse hook did not run to a verdict — approval required",
1522
+ questionCause: managedGap,
1474
1523
  ...(rewritten ? { updatedInput: currentInput } : {}),
1475
1524
  ...contextField(),
1476
1525
  };
@@ -1479,6 +1528,7 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
1479
1528
  return {
1480
1529
  action: "ask",
1481
1530
  message: "a PreToolUse hook returned an unrecognized permissionDecision — approval required",
1531
+ questionCause: "verdict_unreadable",
1482
1532
  ...(rewritten ? { updatedInput: currentInput } : {}),
1483
1533
  ...contextField(),
1484
1534
  };
@@ -15,7 +15,7 @@
15
15
  * ⇒ 本仓的规则从「记住一张词×决定的相容表」变成「说出你知道的那两件事」。相容性也不再靠记忆:一次
16
16
  * sweep 不可能 `allow`,core pre-CAS 拒。
17
17
  */
18
- import { type AskOutcome, type HostDecider, type PermissionResult, type ResumeOutcome } from "@sema-agent/core";
18
+ import { type AskOutcome, type DecidedVia, type HostDecider, type PermissionResult, type ResumeOutcome } from "@sema-agent/core";
19
19
  /**
20
20
  * 谁结束了这次等待,以及通道自报的归属。
21
21
  * · `decidedBy: "person"` —— 一个人经审批通道做的决定(决定体缺席 `decidedBy` 即此);
@@ -32,7 +32,7 @@ import { type AskOutcome, type HostDecider, type PermissionResult, type ResumeOu
32
32
  * · **铸**(本仓自己观察到的事实):只有 `"timeout"` —— 同步 `onAsk` 腿的等待窗归**宿主**所有(引擎不为
33
33
  * 一个它没排程的回调起表),所以「本仓的 TTL 窗走完了、没人答」这件事引擎结构上观察不到,不自报就
34
34
  * 永远被记成「一个人拒绝了」;
35
- * · **转**(调用方在决定体里陈述的事实):决定体的 `decidedBy:"policy"` 经 {@link liveSettledBy} 落成这一格的
35
+ * · **转**(调用方在决定体里陈述的事实):决定体的 `decidedBy:"policy"` 经 {@link liveHostSource} 落成这一格的
36
36
  * `"policy"` = 答这张卡的是**这台部署自己的策略**,不是人。wire 上的词只有 {@link APPROVAL_DECIDED_BY} 两个,
37
37
  * 映射只有那一只函数 —— 这一格的「转」不再是一个原样过境的串。
38
38
  * 逐词(词表属主恒是 core):
@@ -53,6 +53,15 @@ import { type AskOutcome, type HostDecider, type PermissionResult, type ResumeOu
53
53
  export type HostSettledBy = NonNullable<Extract<AskOutcome, {
54
54
  allow: boolean;
55
55
  }>["settledBy"]>;
56
+ /**
57
+ * **同步座的宿主自报**,一只值 —— `AskOutcome` 对象臂上 `settledBy` 与 `decidedVia`(core 7.61.0 [ref])这两格,交引擎时**原样摊开**
58
+ * (`{ allow, ...source }`),所以结算腿、判决、行读回都传这一只、不各传两格。`decidedVia` 只在 `settledBy: "policy"` 旁
59
+ * (core 的同一条律;本仓两个产地都守它:决定体经 {@link liveHostSource},问账行经店门 `assertDecideSource`);本仓自己的窗只说 `{ settledBy: "timeout" }`。
60
+ */
61
+ export type HostSource = {
62
+ readonly settledBy: HostSettledBy;
63
+ readonly decidedVia?: DecidedVia;
64
+ };
56
65
  /** 直接取 core `ResumeOutcome` policy_ask 臂的 `hostDecision` 型 —— 本仓不复述那几个词(第二铸点)。它的 `decidedBy` 就是 core 的 `HostDecider`
57
66
  * (core 7.45.0 [ref]:三个型位读同一只闭集)。**谁结束了这次等待**的闭集只有 core 包根那一只(`HOST_DECIDERS` / `HostDecider` / `isHostDecider`)。 */
58
67
  export type HostDecision = Extract<ResumeOutcome, {
@@ -62,9 +71,9 @@ export type HostDecision = Extract<ResumeOutcome, {
62
71
  * ══ 决定体的出处位 `decidedBy`:一只闭集、一只读法、一只映射 ═══════════════════════════════════════════════
63
72
  *
64
73
  * 规则一句话:**一个审批决定用一只键说是谁做的** —— `decidedBy ∈ {"person","policy"}`,缺席 = `"person"`;读它的只有
65
- * {@link readDecidedBy}(三条工具审批决定口共读:live `POST /v1/tool-approvals/:id/respond`、耐久 ask 账
74
+ * {@link readDecider}(三条工具审批决定口共读:live `POST /v1/tool-approvals/:id/respond`、耐久 ask 账
66
75
  * `POST /v1/tasks/:id/asks/:askId/decision`、耐久 `POST /v1/approvals/:sessionId/decide`),把它交给引擎的只有
67
- * {@link liveSettledBy}(同步座 `AskOutcome.settledBy`)与 {@link hostDecisionFacts}(停驻座 `hostDecision.decidedBy`)。
76
+ * {@link liveHostSource}(同步座 `AskOutcome.settledBy`)与 {@link hostDecisionFacts}(停驻座 `hostDecision.decidedBy`)。
68
77
  * · 词与决定动词**正交**:策略可以放行也可以拒(core 7.39.0 起引擎铸 `policy_allowed` / `policy_refused`)。唯一的例外是
69
78
  * 引擎自己的那一条 —— 一张要人的卡(`requiresRealApproval`)不归策略答,见 {@link heldAskOf}(判官是 core 的 `policyCannotAnswer`)。
70
79
  * · `"sla_timeout"` **永不从 wire 收**:那是「等待到点、没人答」,只有本服务自己的截止扫说得出来(`approval-sweeps.ts`
@@ -80,10 +89,15 @@ declare const SWEEP_ONLY_DECIDER = "sla_timeout";
80
89
  /** wire 上的出处词(决定体 `decidedBy` 的值域)= core 闭集去掉扫的那一个。 */
81
90
  export type ApprovalDecidedBy = Exclude<HostDecider, typeof SWEEP_ONLY_DECIDER>;
82
91
  /** wire 闭集的运行期值:core `HOST_DECIDERS`([ref])去掉扫的那一个,顺序随 core。core 加一个词 ⇒ 它在这里自动在场(决定口的门照收;两只座的映射
83
- * {@link liveSettledBy} 的穷举 `switch` 编译红,由人裁它在同步座上怎么落)。 */
92
+ * {@link liveHostSource} 的穷举 `switch` 编译红,由人裁它在同步座上怎么落)。 */
84
93
  export declare const APPROVAL_DECIDED_BY: readonly ApprovalDecidedBy[];
85
- /** 闭集外的拒句(三条决定口同一句;码按各口既有约定,见 {@link readDecidedBy})。成员表派生自 {@link APPROVAL_DECIDED_BY}(词表一个来源;今天字节与 7.118.0 同)。 */
94
+ /** 闭集外的拒句(四条决定口同一句;码按各口既有约定,见 {@link readDecider})。成员表派生自 {@link APPROVAL_DECIDED_BY}(词表一个来源;今天字节与 7.118.0 同)。 */
86
95
  export declare const DECIDED_BY_OUTSIDE_CLOSED_SET: string;
96
+ /**
97
+ * 机器座位 `decidedVia` 的拒句(四条决定口同一句,与 {@link DECIDED_BY_OUTSIDE_CLOSED_SET} 同码同位)。成员表派生自 core `DECIDED_VIA`。
98
+ * 一句覆盖两种拒:词表外(含 `null` / 空串 / 非串 —— 在场即陈述,与 `decidedBy` 同一条在场口径),与**旁边不是策略**(`decidedBy` 缺席 = 一个人,人的决定没有机器座位)。
99
+ */
100
+ export declare const DECIDED_VIA_OUTSIDE_SEAT: string;
87
101
  /** 直连门上的拒句(与 `remember` / `updatedInput` / `checkpointToken` 同族:键不在决策证明的签名载荷里)。 */
88
102
  export declare const DECIDED_BY_NOT_IN_PROOF = "decidedBy is not supported on a direct-door worker (not covered by the decision proof)";
89
103
  /** 一张要人的卡上策略的放行(两条决定口同一句;判官 core `policyCannotAnswer`,问形见 {@link heldAskOf})。 */
@@ -115,31 +129,51 @@ export interface RetiredDecisionKeyFacts {
115
129
  };
116
130
  }
117
131
  export declare function retiredDecisionKeyFacts(body: unknown): RetiredDecisionKeyFacts | undefined;
118
- /** {@link readDecidedBy} 的判决(调用点做穷举 `switch` + `never` 兜底,各按自己的码约定发出)。 */
119
- export type DecidedByReading = {
132
+ /**
133
+ * 决定体陈述的**决定者**:谁做的(`decidedBy`),以及 —— 只当答话的是这台部署的策略时 —— 哪一个机器座位答的(`decidedVia`,core 7.61.0 [ref]:
134
+ * `"policy"` = 部署自己的策略,`"permission_request_hook"` = 一只 PermissionRequest 钩子替部署答的;缺席与 `"policy"` 同读)。
135
+ * 只有 {@link readDecider} 铸它;两只座各一只映射({@link liveHostSource} / {@link hostDecisionFacts})。
136
+ */
137
+ export interface ApprovalDecider {
138
+ readonly decidedBy: ApprovalDecidedBy;
139
+ readonly decidedVia?: DecidedVia;
140
+ }
141
+ /** {@link readDecider} 的判决(调用点做穷举 `switch` + `never` 兜底,各按自己的码约定发出;`outside_closed_set` 自带拒句 —— 哪一格越界由读法说,口不猜)。 */
142
+ export type DeciderReading = {
120
143
  kind: "accepted";
121
- decidedBy: ApprovalDecidedBy;
144
+ decider: ApprovalDecider;
122
145
  } | {
123
146
  kind: "not_in_proof";
124
147
  } | {
125
148
  kind: "outside_closed_set";
149
+ message: string;
126
150
  };
127
151
  /**
128
- * 决定体 `decidedBy` 的**唯一读法**。次序即契约:
129
- * ① 缺席 ⇒ `"person"`(缺席律:一条不说话的决定就是一个人按的 —— 与修前逐字同形);
130
- * ② 直连门在场且键在场 ⇒ `not_in_proof`(不论值:它不在签名载荷里,能改线上字节的一方可以把人翻成策略;
152
+ * 决定体决定者两格(`decidedBy` + `decidedVia`)的**唯一读法**。次序即契约:
153
+ * ① `decidedBy` 缺席 ⇒ `"person"`(缺席律:一条不说话的决定就是一个人按的 —— 与修前逐字同形);
154
+ * ② 直连门在场且 `decidedBy` 在场 ⇒ `not_in_proof`(不论值:它不在签名载荷里,能改线上字节的一方可以把人翻成策略;
131
155
  * 第二参给字面 `false` 的调用点 = 那条口没有决策证明,live `/respond` 即此);
132
- * ③ 闭集内 ⇒ 那个词;其余一切(`"sla_timeout"`、结算词、别的串、非串、`null`)⇒ `outside_closed_set`。
156
+ * ③ `decidedBy` 闭集外(`"sla_timeout"`、结算词、别的串、非串、`null`)⇒ `outside_closed_set`({@link DECIDED_BY_OUTSIDE_CLOSED_SET});
157
+ * ④ `decidedVia` 在场(`undefined` 之外一律算在场,`null` 也是 —— 与 `decidedBy` 同一条在场口径)而它不在 core `DECIDED_VIA` 里,或旁边的决定者
158
+ * 没有座位(人;含 ① 落成的缺席 person ⇒ 直连门上只带 `decidedVia` 也走这一臂,直连门从不受理它)⇒ `outside_closed_set`({@link DECIDED_VIA_OUTSIDE_SEAT});
159
+ * ⑤ 受理:`{ decidedBy, decidedVia? }`(座位缺席 ⇒ 读数里没有这一格)。
160
+ * 问账行上 `decided_via` 列的 NULL 不经本读法:那是店的读口,NULL ⇒ 缺席(旧行与不带座位的决定同读 = 部署自己的策略)。
133
161
  */
134
- export declare function readDecidedBy(raw: unknown, directDoorActive: false): Exclude<DecidedByReading, {
162
+ export declare function readDecider(body: {
163
+ readonly decidedBy?: unknown;
164
+ readonly decidedVia?: unknown;
165
+ }, directDoorActive: false): Exclude<DeciderReading, {
135
166
  kind: "not_in_proof";
136
167
  }>;
137
- export declare function readDecidedBy(raw: unknown, directDoorActive: boolean): DecidedByReading;
168
+ export declare function readDecider(body: {
169
+ readonly decidedBy?: unknown;
170
+ readonly decidedVia?: unknown;
171
+ }, directDoorActive: boolean): DeciderReading;
138
172
  /**
139
- * **同步座的映射**(`AskOutcome.settledBy`;ask 账行的 `settled_by` 列存的也是这一格):`person` ⇒ 不铸键(引擎缺席即按人读,
140
- * 与修前逐字同形 —— 人的放行仍是裸 `true`),`policy` ⇒ `"policy"`。总函数,无 cast。
173
+ * **同步座的映射**(`AskOutcome` 的 `settledBy` / `decidedVia`;问账行的 `settled_by` / `decided_via` 两列存的也是这一只值):`person` ⇒ 不自报
174
+ * (引擎缺席即按人读,与修前逐字同形 —— 人的放行仍是裸 `true`),`policy` ⇒ `{ settledBy: "policy", decidedVia? }`。总函数,无 cast。
141
175
  */
142
- export declare function liveSettledBy(decidedBy: ApprovalDecidedBy): Extract<HostSettledBy, "policy"> | undefined;
176
+ export declare function liveHostSource(decider: ApprovalDecider): HostSource | undefined;
143
177
  /**
144
178
  * 引擎自己的那一条例外:**一张要人的卡不归策略答** —— 判官是 core 的 `policyCannotAnswer(question, decidedBy)`([ref] 包根导出;引擎的续跑入口、
145
179
  * 执行带与同步座读同一条)。本仓**只在自己的副作用先于引擎落地的两条口**上于裁决之前问它(live `/respond`:会话放行 / 常驻规则 / 一条按 approve
@@ -155,7 +189,7 @@ export declare function heldAskOf(demandsAPerson: boolean): Extract<PermissionRe
155
189
  /**
156
190
  * 一次 decide 的**停驻座事实**,一处成形(耐久 `/decide` 与活卡 PARKED 赎回席共用;截止扫直接铸 `{decidedBy:"sla_timeout"}`)。
157
191
  *
158
- * `decidedBy` = 已经过 {@link readDecidedBy} 的 wire 词:两个词在停驻座上**就是它自己**(`person` / `policy`,
192
+ * `decidedBy` = 已经过 {@link readDecider} 的 wire 词:两个词在停驻座上**就是它自己**(`person` / `policy`,
159
193
  * 两只词表的交集由上面的编译期门证),所以这里没有 cast,也没有缺席律(缺席已在读法里落成 `"person"`)。
160
194
  *
161
195
  * `principal` = 那条腿已经验过身份的决策者(缺席 = 匿名档的部署,通道确实没有名字可报)。
@@ -167,6 +201,6 @@ export declare function heldAskOf(demandsAPerson: boolean): Extract<PermissionRe
167
201
  * 🔴 归属**不按词分岔**:无论 core 的记录怎么写,**本服务观察到的事实**不变:送出这次请求的那把已验证身份是真的,本仓的
168
202
  * 迟到结算账本(`approval-ask-audit-store.ts`)记的就是它。按词删一格 = 给账本挖一个只在某个词上出现的洞。
169
203
  */
170
- export declare function hostDecisionFacts(decidedBy: ApprovalDecidedBy, principal: string | undefined): HostDecision;
204
+ export declare function hostDecisionFacts(decider: ApprovalDecider, principal: string | undefined): HostDecision;
171
205
  export {};
172
206
  //# sourceMappingURL=host-decision.d.ts.map
@@ -1,10 +1,11 @@
1
- import { HOST_DECIDERS, isHostDecider, screenApproverAttribution } from "@sema-agent/core";
1
+ import { DECIDED_VIA, HOST_DECIDERS, isHostDecider, screenApproverAttribution } from "@sema-agent/core";
2
2
  const SWEEP_ONLY_DECIDER = "sla_timeout";
3
3
  function isApprovalDecidedBy(v) {
4
4
  return isHostDecider(v) && v !== SWEEP_ONLY_DECIDER;
5
5
  }
6
6
  export const APPROVAL_DECIDED_BY = Object.freeze(HOST_DECIDERS.filter(isApprovalDecidedBy));
7
7
  export const DECIDED_BY_OUTSIDE_CLOSED_SET = `decidedBy must be ${APPROVAL_DECIDED_BY.map((w) => `"${w}"`).join(" or ")} when present — who made this decision: a person, or this deployment's own policy (absent = "person"); "${SWEEP_ONLY_DECIDER}" is never accepted from a caller (only this server's own deadline sweep says a wait ended with nobody answering)`;
8
+ export const DECIDED_VIA_OUTSIDE_SEAT = `decidedVia must be ${DECIDED_VIA.map((w) => `"${w}"`).join(" or ")} when present, and only beside decidedBy "policy" — it names which machine seat answered for this deployment's policy (absent = the deployment's own policy); a person's decision has no seat`;
8
9
  export const DECIDED_BY_NOT_IN_PROOF = "decidedBy is not supported on a direct-door worker (not covered by the decision proof)";
9
10
  export const POLICY_CANNOT_ALLOW_PERSON_ASK = 'decidedBy "policy" cannot allow this ask — it demands a person\'s approval (risk.requiresRealApproval), and a policy\'s answer is not a person\'s; answer it as a person, or refuse it as the policy';
10
11
  export const SETTLED_BY_RETIRED = 'settledBy is retired — send decidedBy: "person" | "policy" (who made this decision; absent = "person"); ignoring it would record a policy\'s answer as a person\'s';
@@ -14,30 +15,57 @@ export function retiredDecisionKeyFacts(body) {
14
15
  return undefined;
15
16
  return body[RETIRED_DECISION_KEY] !== undefined ? { error: SETTLED_BY_RETIRED, extra: { field: RETIRED_DECISION_KEY } } : undefined;
16
17
  }
17
- export function readDecidedBy(raw, directDoorActive) {
18
- if (raw === undefined)
19
- return { kind: "accepted", decidedBy: "person" };
20
- if (directDoorActive)
21
- return { kind: "not_in_proof" };
22
- return isApprovalDecidedBy(raw) ? { kind: "accepted", decidedBy: raw } : { kind: "outside_closed_set" };
23
- }
24
- export function liveSettledBy(decidedBy) {
18
+ function deciderHasSeat(decidedBy) {
25
19
  switch (decidedBy) {
26
20
  case "person":
27
- return undefined;
21
+ return false;
28
22
  case "policy":
29
- return "policy";
23
+ return true;
30
24
  default: {
31
25
  const unreachable = decidedBy;
32
- throw new Error(`liveSettledBy: unknown decidedBy ${String(unreachable)}`);
26
+ throw new Error(`deciderHasSeat: unknown decidedBy ${String(unreachable)}`);
27
+ }
28
+ }
29
+ }
30
+ export function readDecider(body, directDoorActive) {
31
+ let decidedBy = "person";
32
+ const rawBy = body.decidedBy;
33
+ if (rawBy !== undefined) {
34
+ if (directDoorActive)
35
+ return { kind: "not_in_proof" };
36
+ if (!isApprovalDecidedBy(rawBy))
37
+ return { kind: "outside_closed_set", message: DECIDED_BY_OUTSIDE_CLOSED_SET };
38
+ decidedBy = rawBy;
39
+ }
40
+ const rawVia = body.decidedVia;
41
+ if (rawVia === undefined)
42
+ return { kind: "accepted", decider: { decidedBy } };
43
+ const via = DECIDED_VIA.find((w) => w === rawVia);
44
+ if (via === undefined || !deciderHasSeat(decidedBy))
45
+ return { kind: "outside_closed_set", message: DECIDED_VIA_OUTSIDE_SEAT };
46
+ return { kind: "accepted", decider: { decidedBy, decidedVia: via } };
47
+ }
48
+ export function liveHostSource(decider) {
49
+ switch (decider.decidedBy) {
50
+ case "person":
51
+ return undefined;
52
+ case "policy":
53
+ return { settledBy: "policy", ...(decider.decidedVia !== undefined ? { decidedVia: decider.decidedVia } : {}) };
54
+ default: {
55
+ const unreachable = decider.decidedBy;
56
+ throw new Error(`liveHostSource: unknown decidedBy ${String(unreachable)}`);
33
57
  }
34
58
  }
35
59
  }
36
60
  export function heldAskOf(demandsAPerson) {
37
61
  return demandsAPerson ? { action: "ask", requiresRealApproval: true } : { action: "ask" };
38
62
  }
39
- export function hostDecisionFacts(decidedBy, principal) {
63
+ export function hostDecisionFacts(decider, principal) {
40
64
  const screened = screenApproverAttribution(principal);
41
- return { decidedBy, ...(screened.approver !== undefined ? { approver: screened.approver } : {}) };
65
+ return {
66
+ decidedBy: decider.decidedBy,
67
+ ...(screened.approver !== undefined ? { approver: screened.approver } : {}),
68
+ ...(decider.decidedVia !== undefined ? { decidedVia: decider.decidedVia } : {}),
69
+ };
42
70
  }
43
71
  //# sourceMappingURL=host-decision.js.map
@@ -68,7 +68,7 @@ export declare function createAdmission(ctx: {
68
68
  deps: FlatServiceDeps;
69
69
  runnerFor: FlatServiceDeps["runnerFor"];
70
70
  } & Pick<RouteHelpers, "readJson">): {
71
- prepareSpec: (req: IncomingMessage, res: ServerResponse, bodyIn?: TaskRequestBody, onTypedFailure?: (err: HttpError) => void, legFacts?: {
71
+ prepareSpec: (req: IncomingMessage, res: ServerResponse, bodyIn?: TaskRequestBody, onTypedFailure?: (err: HttpError) => void, legRoute?: {
72
72
  liveStream?: boolean;
73
73
  }) => Promise<PreparedTaskSubmission | null>;
74
74
  };
@@ -20,6 +20,7 @@ import { echoCallerText, quotedCallerText } from "../caller-text-echo.js";
20
20
  import { MAX_ADDITIONAL_DIRS } from "../task-cwd.js";
21
21
  import { verifyRoundsFromBody } from "./verify-rounds.js";
22
22
  import { skillCarriesRetiredManifest } from "./retired-skill-manifest.js";
23
+ import { legFactsOf } from "../leg-facts.js";
23
24
  import { resolveExecutionContext, stampSpecRoot } from "./execution-context.js";
24
25
  import { callerTrust, requestTrustRefusal } from "./caller-trust.js";
25
26
  import { createRunAcceptance } from "../run-acceptance.js";
@@ -115,7 +116,7 @@ function hostPathListRefusal(field, raw) {
115
116
  const modelIdOf = (model) => (typeof model === "string" ? model : model?.id);
116
117
  export function createAdmission(ctx) {
117
118
  const { deps, readJson, runnerFor } = ctx;
118
- async function prepareSpec(req, res, bodyIn, onTypedFailure, legFacts) {
119
+ async function prepareSpec(req, res, bodyIn, onTypedFailure, legRoute) {
119
120
  const body = bodyIn ?? (await readJson(req));
120
121
  {
121
122
  const refusal = refuseSubmitBody(sqlWallsOf(deps.sqlEngine), body);
@@ -744,13 +745,16 @@ export function createAdmission(ctx) {
744
745
  const auth = await deps.authorize({ req, body: admitted });
745
746
  const execution = await resolveExecutionContext(deps, { kind: "fresh", root: auth.sessionId, principal: auth.principal, judged: auth.head, req, deviceId: admitted.deviceId });
746
747
  const engineNotices = [];
747
- const spec = stampSpecRoot(await deps.resolveSpec(admitted, req, auth, { leg: "fresh", ...(legFacts?.liveStream === true ? { liveStream: true } : {}), onNotice: (n) => engineNotices.push(n), lease, execution }), execution);
748
+ const resolved = await deps.resolveSpec(admitted, req, auth, { leg: "fresh", ...(legRoute?.liveStream === true ? { liveStream: true } : {}), onNotice: (n) => engineNotices.push(n), lease, execution });
749
+ const facts = legFactsOf(resolved);
750
+ const runner = runnerFor(resolved);
751
+ const spec = stampSpecRoot(resolved, execution);
748
752
  const model = modelIdOf(spec.model);
749
753
  const acceptance = createRunAcceptance({ sessionTitler: deps.sessionTitler }, { sessionId: spec.sessionId, objective: spec.objective, ...(model !== undefined ? { model } : {}) });
750
754
  const clampNotice = buildEntryModeClampNotice(entryMode, spec.sessionId);
751
755
  const pendingNotices = clampNotice !== undefined ? [clampNotice, ...engineNotices] : engineNotices;
752
756
  const warnings = allowSelectorWarningsOf(admitted, spec.tools ?? []);
753
- return { spec, runner: runnerFor(spec), auth, verify, cascade, jobId: admitted.jobId, body: admitted, acceptance, peerLease: lease, execution, ...(pendingNotices.length > 0 ? { engineNotices: pendingNotices } : {}), ...(warnings.length > 0 ? { warnings } : {}) };
757
+ return { spec, runner, legFacts: facts, auth, verify, cascade, jobId: admitted.jobId, body: admitted, acceptance, peerLease: lease, execution, ...(pendingNotices.length > 0 ? { engineNotices: pendingNotices } : {}), ...(warnings.length > 0 ? { warnings } : {}) };
754
758
  }
755
759
  catch (err) {
756
760
  lease.release();
@@ -83,26 +83,29 @@ export function createApprovalSweeps(ctx) {
83
83
  const parkSources = deps.backgroundAgentStore !== undefined || deps.workflowAgentSessionIndex !== undefined;
84
84
  const cardOf = async (sessionId) => {
85
85
  let token;
86
+ let binding = {};
86
87
  try {
87
88
  token = (await cs.findPendingTokenBySession(sessionId, undefined, { gateKinds: APPROVAL_GATE_KINDS })) ?? undefined;
88
89
  if (!token)
89
- return { token, parked: false, rev: undefined };
90
+ return { token, parked: false, rev: undefined, binding };
90
91
  const cp = await cs.get(token);
91
92
  if (!cp)
92
- return { token, parked: false, rev: undefined };
93
+ return { token, parked: false, rev: undefined, binding };
94
+ const pa = cp.pendingAction;
95
+ binding = { checkpointToken: token, ...(pa.kind === "tool_approval" ? { boundCallId: pa.toolCallId, boundInputHash: pa.boundInputHash } : {}) };
93
96
  const rev = checkpointRevOf(cp);
94
97
  if (!parkSources)
95
- return { token, parked: false, rev };
98
+ return { token, parked: false, rev, binding };
96
99
  const origin = await findParkOrigin({
97
100
  ...(deps.backgroundAgentStore !== undefined ? { agentStore: deps.backgroundAgentStore } : {}),
98
101
  ...(deps.workflowAgentSessionIndex !== undefined && deps.workflowRunStore !== undefined
99
102
  ? { workflowIndex: deps.workflowAgentSessionIndex, workflowRunStore: deps.workflowRunStore }
100
103
  : {}),
101
104
  }, { token, scope: cp.scope, sessionId: cp.sessionId });
102
- return { token, parked: origin !== undefined, rev };
105
+ return { token, parked: origin !== undefined, rev, binding };
103
106
  }
104
107
  catch {
105
- return { token, parked: false, rev: undefined };
108
+ return { token, parked: false, rev: undefined, binding };
106
109
  }
107
110
  };
108
111
  const storedBodyUnservable = async (sessionId) => {
@@ -140,7 +143,7 @@ export function createApprovalSweeps(ctx) {
140
143
  const replaySkipped = [];
141
144
  const seenCards = new Set();
142
145
  for (const { sessionId } of expired) {
143
- const { token: cardToken, parked, rev: cardRev } = await cardOf(sessionId);
146
+ const { token: cardToken, parked, rev: cardRev, binding: cardBinding } = await cardOf(sessionId);
144
147
  if (cardToken !== undefined)
145
148
  seenCards.add(cardToken);
146
149
  if (parked) {
@@ -154,7 +157,7 @@ export function createApprovalSweeps(ctx) {
154
157
  }
155
158
  let out;
156
159
  try {
157
- out = await resumeCheckpoint(sessionId, "deny", "approval SLA expired — auto-denied", { decidedBy: "sla_timeout" });
160
+ out = await resumeCheckpoint(sessionId, "deny", "approval SLA expired — auto-denied", { decidedBy: "sla_timeout" }, undefined, undefined, cardBinding);
158
161
  }
159
162
  catch (e) {
160
163
  deps.logger?.warn?.("deny_sweep_resume_failed", { sessionId, err: errText(e) });
@@ -228,7 +231,7 @@ export function createApprovalSweeps(ctx) {
228
231
  ...(req.gateBoundInputHash !== null ? { boundInputHash: req.gateBoundInputHash } : {}),
229
232
  ...(req.updatedInput !== undefined ? { updatedInput: req.updatedInput } : {}),
230
233
  };
231
- const out = await resumeCheckpoint(cp.sessionId, req.decision, req.note, hostDecisionFacts(req.decidedBy, req.principal), req.httpReq, undefined, binding, undefined, true, { principal: req.principal, explicitOperator: false });
234
+ const out = await resumeCheckpoint(cp.sessionId, req.decision, req.note, hostDecisionFacts(req.decider, req.principal), req.httpReq, undefined, binding, undefined, true, { principal: req.principal, explicitOperator: false });
232
235
  return { status: out.status, body: out.body };
233
236
  }
234
237
  return { denyExpiredApprovals, redeemParkedAsk };
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * ## 律
5
5
  * 契约(settings-schema `SAFETY_MERGE_SPEC.permissions` 的成员阶梯)按**层的信任档**准入每个成员:`allow` 放宽免问面(`loosen`),要 `global`;`deny` / `ask` 只收紧,
6
- * 文件层最低档 `local` 就收。准入不读层:提交体是合并后的结果,层只在合并端(cli 本地)存在;deny / ask 条目可以带出处层(`{ rule, layer }`),但那个层只选这条规则的
6
+ * 文件层最低档 `local` 就收。准入不读层:提交体是合并后的结果,层只在合并端(cli 本地)存在;三桶的条目可以带出处层(`{ rule, layer }`),但那个层只选这条规则的
7
7
  * `/` 读在哪个根(`permission-rule-list.ts` 的 `layerRuleRoot`),自报的层不可验、**不进本判**。所以本服务准入的「层」= **这次请求验过的凭据**:档只由部署形 + 身份来源
8
8
  * + 登录令牌的 scope 角色推,调用方说什么(含条目上的层)都不改档。
9
9
  *
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { setSsoPrincipal, setSsoScope, setSsoRole, isDestructiveSessionWrite, PRINCIPAL_TOKEN_HEADER, APPROVAL_MAC_HEADER, APPROVAL_MAC_KID_HEADER } from "../security.js";
2
+ import { setSsoPrincipal, setSsoScope, setSsoRole, setSsoPrincipalInc, isDestructiveSessionWrite, PRINCIPAL_TOKEN_HEADER, APPROVAL_MAC_HEADER, APPROVAL_MAC_KID_HEADER } from "../security.js";
3
3
  import { publicStoreProbeError } from "../store-live-probe.js";
4
4
  import { looksLikeJwt } from "@sema-agent/settings-schema/api/auth-bridge";
5
5
  import { admitBridgeIdentity } from "../auth-bridge.js";
@@ -227,6 +227,8 @@ export function createDispatch(ctx) {
227
227
  setSsoScope(req, identity.scope);
228
228
  if (identity.role)
229
229
  setSsoRole(req, identity.role);
230
+ if (identity.principalInc !== undefined)
231
+ setSsoPrincipalInc(req, identity.principalInc);
230
232
  deps.logger?.info?.("auth_bridge_principal_accepted", { principal: identity.principal, scope: identity.scope });
231
233
  }
232
234
  }
@@ -236,7 +238,7 @@ export function createDispatch(ctx) {
236
238
  if (await dispatchSegment(PUBLIC_ROUTES, req, res, url, search, method, allow.pre, ctx))
237
239
  return;
238
240
  const identitySource = verifiedIdentitySource(req, deps.config);
239
- admitRunIdentity(req, identitySource);
241
+ admitRunIdentity(req, identitySource, deps.logger);
240
242
  let source = null;
241
243
  const anyServiceAuth = hasServiceAuth(deps.config);
242
244
  if (anyServiceAuth) {
@@ -15,7 +15,7 @@ import { persistSubmitCtx } from "./submit-ctx.js";
15
15
  import { releaseClaimedRun } from "./claimed-run.js";
16
16
  import { serviceVersion } from "../service-version.js";
17
17
  import { headReadOf } from "../session-incarnation.js";
18
- import { createSessionWorkspaceRecorder, legSessionWitnessOf, sessionWorkspaceStoreOf } from "../session-workspace.js";
18
+ import { createSessionWorkspaceRecorder, sessionWorkspaceStoreOf } from "../session-workspace.js";
19
19
  export async function mintAndDriveDurableRun(ctx, runStore, args) {
20
20
  const { deps } = ctx;
21
21
  const { inflightRuns, preemptableRuns, steerableRuns } = ctx.registry;
@@ -97,7 +97,7 @@ export async function mintAndDriveDurableRun(ctx, runStore, args) {
97
97
  declaredFamilies: declaredApprovalFrameFamilies(args.prepared.body.settings),
98
98
  executionLane: args.prepared.execution.lane,
99
99
  }
100
- : undefined, deps.checkpointStore, args.prepared.engineNotices, trustedInternals, deps.userInputLedger, args.prepared.acceptance, createSessionWorkspaceRecorder(sessionWorkspaceStoreOf(deps.sessionStorage), legSessionWitnessOf(args.prepared.spec))));
100
+ : undefined, deps.checkpointStore, args.prepared.engineNotices, trustedInternals, deps.userInputLedger, args.prepared.acceptance, createSessionWorkspaceRecorder(sessionWorkspaceStoreOf(deps.sessionStorage), args.prepared.legFacts?.sessionWitness), args.prepared.legFacts?.skills));
101
101
  return { status: 202, body: { taskId, sessionId, status: "running", ...(args.prepared.warnings !== undefined ? { warnings: args.prepared.warnings } : {}) } };
102
102
  }
103
103
  //# sourceMappingURL=durable-run-leg.js.map
@@ -0,0 +1,16 @@
1
+ import type { Refusal } from "./refusal-body.js";
2
+ /** 一只引擎拒的应答:状态(出自 {@link resumeAtHttpStatus} 的单表)+ 拒(码 / 句,交 `sendError` 渲)。 */
3
+ export interface EngineRefusalAnswer {
4
+ readonly status: number;
5
+ readonly refusal: Refusal;
6
+ }
7
+ /**
8
+ * 一只抛出的引擎拒按提交腿同一张表读成应答;不是登记过的拒 ⇒ `undefined`。
9
+ * 组句与同步腿零工作拒体同形(`http/routes/tasks.ts` 的 `syncRefusalHttpStatus` 臂):码经 {@link outwardPlane}(词法外的码折叠)与 {@link httpErrorCode},
10
+ * 句经出口座的 `peer-declaration-sentence` 一行出门(引擎判官对 MCP 声明说的句子,逐字带着声明方写的名字;座读本仓脱敏器〔`trace/redact.ts`〕的提案跨度 —— 提交腿上同一句
11
+ * 过的 `redactErrorMessage` 读的是同一只脱敏器);两条腿的同码同状态同句由 `test/prepare-refusal-one-http-shape.test.ts` ⑤ 钉住。
12
+ * ⚠️ 唯一的字面差:提交腿的句子在这之前还过引擎自己把抛出物折成终局句的那一道出口过滤,读面的句子没有(本仓不调引擎的脱敏函数,台账 skip 行)——
13
+ * 句子里若出现凭据形的拼写,两面都把它盖住、字节都不外露,只是记号的写法不同(引擎 `[redacted]` / 本服务 `«redacted:…»`)。
14
+ */
15
+ export declare function engineRefusalAnswerOf(err: unknown): EngineRefusalAnswer | undefined;
16
+ //# sourceMappingURL=engine-refusal.d.ts.map
@@ -0,0 +1,17 @@
1
+ import { errorCodeOf, errorText } from "../observability/err-text.js";
2
+ import { resumeAtHttpStatus } from "../runs.js";
3
+ import { outwardPlane } from "../terminal.js";
4
+ import { seatText } from "../trace/exit-seat.js";
5
+ import { httpErrorCode } from "./send.js";
6
+ export function engineRefusalAnswerOf(err) {
7
+ const code = errorCodeOf(err);
8
+ if (code === undefined)
9
+ return undefined;
10
+ const terminal = { kind: "failed", code, message: errorText(err) };
11
+ const status = resumeAtHttpStatus({ terminal });
12
+ if (status === undefined)
13
+ return undefined;
14
+ const plane = outwardPlane(terminal);
15
+ return { status, refusal: { errorCode: httpErrorCode(status, plane.errorCode), message: seatText("peer-declaration-sentence", plane.errorMessage ?? "engine refusal") } };
16
+ }
17
+ //# sourceMappingURL=engine-refusal.js.map
@@ -85,8 +85,8 @@ export type ExecutionContext = {
85
85
  export type RootedExecutionContext = Extract<ExecutionContext, {
86
86
  readonly root: string;
87
87
  }>;
88
- /** 装配器(`resolveSpec`)收的那一半:根与入口形(它不判放置、不选车道)。 */
89
- export type ExecutionRoot = Pick<RootedExecutionContext, "kind" | "root">;
88
+ /** 装配器(`resolveSpec`)收的那一半:根、入口形与解析器选定的车道(它不判放置、不选车道 —— `lane` 是解析器那一次的读数,装配器只读:项目包的车道门)。 */
89
+ export type ExecutionRoot = Pick<RootedExecutionContext, "kind" | "root" | "lane">;
90
90
  /** 解析器读的部署件(全部取自同一只 `ServiceDeps`)。 */
91
91
  export interface ExecutionContextDeps {
92
92
  readonly config: ServiceConfig;
@@ -117,7 +117,7 @@ export declare function inflightKeyOf(idem: RunIdemBinding, face: SubmitReplayFa
117
117
  * 再走 `/v1/tasks`,合流到的是前者的那份结局。回执若铸在同步腿的体里,这条**确实产了帧**的 run 在非流式面上
118
118
  * 就成了「键缺席」,而 present-iff 的读法会把它读成「这台引擎不给回执」。挂在 outcome 上 = 它是**这条 run 的
119
119
  * 属性**(哪条腿跑的都一样),由各腿自己的应答构造器在送出那一刻按自己的 wire 形挂。
120
- * 落定之后的同键重放不走这里:它读行,体是 run 行读面 + `replay: true`(`http/submit-replay.ts`)—— 回执是起手那一帧
120
+ * 落定之后的同键重放不走这里:它读行,体是首答的信封 + `replay: true`(`http/submit-replay.ts` → `http/sync-submit-body.ts`)—— 回执是起手那一帧
121
121
  * 的活捕获、不落行,所以重放体上**不带** `wiringManifest`(`replay: true` 就是那一形的判别位)。
122
122
  *
123
123
  * 🔴 **租户面安全(别在复审里当成漏)**:合流的那份回执是按**第一位调用方**的 `explicitOperatorOk` 投的,
@@ -8,9 +8,7 @@
8
8
  * core 的另外三只 409 与 `ControlPlaneCorruptError` 全落 500 `internal.error` —— 「同一个 requestId 拿去抹另一个会话」这种**调用方自己
9
9
  * 能改**的错被说成服务器坏了。两份分类 = 两个判官,少的那份恒是静默的一份。现在**码与状态**只在这里判一次,两面同读。
10
10
  *
11
- * ## 规则(三层,与 operator 面既有层序逐字同序;另有本服务自铸的一只,先判)
12
- * ⓪ 本服务的抹除店护栏拒 {@link MemoryErasureStoreUnjudged} —— 409 `memory.erasure_store_unjudged`(抹除落到部署的店之外的那只店:本部署此刻判不了
13
- * 同一个 `requestId` 跨店的幂等;什么都没删)。文案按受众,同下一节的规则;
11
+ * ## 规则(三层,与 operator 面既有层序逐字同序)
14
12
  * ① core 的**本口专属**码(闭集穷举 `switch`,见 {@link sentCoreCode})—— 码与状态两面同;文案按受众(下一节,一条规则);
15
13
  * ② **店形拒**两码 `memory.target_outside_store`(core 7.31.1 [ref])/ `memory.scope_home_not_directory`(core 7.44.0 [ref])—— 跨面 store 状态码,409、码原样
16
14
  * (判据 `memory-store-refusal.ts` 一只;运维行 `memory_store_form_refused { code, path?, form? }` 两受众同一只铸点,路径只在那里);