@sema-agent/server 7.43.0 → 7.44.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 (345) hide show
  1. package/USAGE.md +18 -0
  2. package/dist/adoption/plan.js +31 -192
  3. package/dist/adoption/quiesce.js +3 -86
  4. package/dist/adoption/runner.js +3 -140
  5. package/dist/adoption/sql.js +0 -74
  6. package/dist/adoption/wire.js +0 -80
  7. package/dist/approval-ask-machine.js +0 -75
  8. package/dist/approval-card.js +0 -323
  9. package/dist/approval-deny-reasons.js +0 -42
  10. package/dist/approval-hmac.js +0 -35
  11. package/dist/approval-reconciler.js +8 -166
  12. package/dist/approval.js +4 -67
  13. package/dist/audit.js +1 -44
  14. package/dist/auth-bridge.js +4 -47
  15. package/dist/auth-keys.js +0 -23
  16. package/dist/bake-runner/main.js +4 -65
  17. package/dist/bake-runner/protocol.js +3 -58
  18. package/dist/bake-runner/runner.js +5 -91
  19. package/dist/bench/l8/artifact.js +2 -61
  20. package/dist/bench/l8/escape.js +0 -25
  21. package/dist/bench/l8/index.js +0 -14
  22. package/dist/bench/l8/probes.js +0 -60
  23. package/dist/bench/l8/run-probes.js +2 -45
  24. package/dist/bench/s1/arms.js +14 -189
  25. package/dist/bench/s1/live-deps.js +11 -253
  26. package/dist/bench/s1/oracle.js +1 -36
  27. package/dist/bench/s1/repair-oracle-adapter.js +0 -30
  28. package/dist/bench/s1/reviewer.js +0 -34
  29. package/dist/bench/s1/row.js +0 -62
  30. package/dist/bench/s1/run-firm.js +4 -70
  31. package/dist/bench/s1/runner-ctx.js +0 -40
  32. package/dist/bench/s1/tasks.js +0 -115
  33. package/dist/boot/adoption.js +0 -20
  34. package/dist/boot/budget-tracing.js +3 -55
  35. package/dist/boot/config-center.js +43 -551
  36. package/dist/boot/coordinators.js +1 -69
  37. package/dist/boot/crash-last.js +0 -18
  38. package/dist/boot/deferred-sandbox-path-env.js +3 -119
  39. package/dist/boot/execution-env.js +5 -171
  40. package/dist/boot/governance-seams.js +0 -92
  41. package/dist/boot/leader.js +0 -69
  42. package/dist/boot/limit-sync.js +0 -2
  43. package/dist/boot/memory-boundary.js +3 -91
  44. package/dist/boot/org-memory.js +1 -25
  45. package/dist/boot/parked-revive-gate.js +0 -150
  46. package/dist/boot/permission-rules-audit.js +1 -56
  47. package/dist/boot/reapers.js +15 -273
  48. package/dist/boot/resolve-spec.js +9 -768
  49. package/dist/boot/retention-lane.js +4 -87
  50. package/dist/boot/runner-deps.js +3 -297
  51. package/dist/boot/runtime-caps.js +0 -40
  52. package/dist/boot/session-faces.js +2 -133
  53. package/dist/boot/shutdown.js +9 -83
  54. package/dist/boot/side-query-lane.js +2 -137
  55. package/dist/boot/stores.js +15 -215
  56. package/dist/boot/task-list-lane.js +0 -18
  57. package/dist/boot/webfetch-summarize-lane.js +0 -47
  58. package/dist/boot/workflow-orchestration.js +3 -99
  59. package/dist/boot-reclaim.js +0 -20
  60. package/dist/bounded-session-map.js +0 -19
  61. package/dist/brain.js +2 -139
  62. package/dist/budget.js +9 -244
  63. package/dist/capabilities/builtin-tools.js +0 -3
  64. package/dist/capabilities/center-plugins.js +6 -51
  65. package/dist/capabilities/center-prompts.js +6 -71
  66. package/dist/capabilities/code-review-council.js +7 -40
  67. package/dist/capabilities/collab-workflows.js +1 -44
  68. package/dist/capabilities/hands-lane.js +0 -65
  69. package/dist/capabilities/memory-notice.js +0 -70
  70. package/dist/capabilities/prompt.js +0 -14
  71. package/dist/capabilities/prompts/code-review.js +0 -14
  72. package/dist/capabilities/prompts/identity.js +0 -6
  73. package/dist/capabilities/prompts/team.js +0 -4
  74. package/dist/capabilities/repo-tools.js +0 -34
  75. package/dist/capabilities/sandbox-file-send.js +3 -92
  76. package/dist/capabilities/scenarios.d.ts +0 -1
  77. package/dist/capabilities/scenarios.js +18 -279
  78. package/dist/capabilities/select-environment-tool.js +0 -32
  79. package/dist/capabilities/send-user-file-tool.js +2 -66
  80. package/dist/capabilities/skills.d.ts +1 -2
  81. package/dist/capabilities/skills.js +3 -21
  82. package/dist/capabilities/team.d.ts +3 -11
  83. package/dist/capabilities/team.js +3 -51
  84. package/dist/capabilities/tool-defer.js +0 -3
  85. package/dist/config-center/apply-effective.js +15 -330
  86. package/dist/config-center/apply-ledger.js +5 -38
  87. package/dist/config-center/facade.js +0 -41
  88. package/dist/config-center/hot-keys-registry.js +0 -20
  89. package/dist/config-center/http-client.js +2 -125
  90. package/dist/config-center/mcp-revocation.js +2 -34
  91. package/dist/config-center/read-face.js +0 -59
  92. package/dist/config-center/restart-signal.js +1 -85
  93. package/dist/config-center/skills-mcp.d.ts +1 -1
  94. package/dist/config-center/skills-mcp.js +9 -76
  95. package/dist/config-center/stage-limits.js +8 -35
  96. package/dist/config-invariants.js +0 -16
  97. package/dist/config-lkg.js +0 -42
  98. package/dist/config-provider.js +3 -186
  99. package/dist/config-types.js +0 -5
  100. package/dist/config.js +107 -1145
  101. package/dist/degenerate-instrument.js +3 -67
  102. package/dist/deployment-governance.js +0 -124
  103. package/dist/digest-form.js +0 -12
  104. package/dist/elicitation.js +3 -86
  105. package/dist/env-facts.js +7 -75
  106. package/dist/fleet/fleet-bus.js +34 -507
  107. package/dist/fleet/fleet-reconciler.js +9 -149
  108. package/dist/fleet/fleet-terminal-window.js +10 -178
  109. package/dist/fleet/subagent-tail-bus.js +3 -72
  110. package/dist/fleet-client.js +10 -70
  111. package/dist/fleet-lease.js +5 -79
  112. package/dist/git-api-kind.js +0 -3
  113. package/dist/governance-ask-marks.js +2 -78
  114. package/dist/hooks/branch-transcript.js +0 -74
  115. package/dist/hooks/cc-agent-hook-prompt.js +0 -29
  116. package/dist/hooks/cc-stop-prompt.js +1 -46
  117. package/dist/hooks/hook-llm.js +1 -53
  118. package/dist/hooks/hook-runner.js +20 -414
  119. package/dist/http/active-run-conflict.js +4 -129
  120. package/dist/http/cursor-fingerprint.d.ts +5 -0
  121. package/dist/http/cursor-fingerprint.js +5 -0
  122. package/dist/http/idempotency.js +0 -37
  123. package/dist/http/principal-gate.js +3 -40
  124. package/dist/http/route-ctx.js +0 -9
  125. package/dist/http/routes/a2a-serve.js +7 -319
  126. package/dist/http/routes/admin-config-refresh.js +0 -4
  127. package/dist/http/routes/admin-drain.js +0 -10
  128. package/dist/http/routes/adoption.js +1 -29
  129. package/dist/http/routes/agents-roster.js +1 -47
  130. package/dist/http/routes/approvals-assistant.js +32 -395
  131. package/dist/http/routes/attachments.js +4 -22
  132. package/dist/http/routes/capabilities.js +3 -420
  133. package/dist/http/routes/diagnostics.js +1 -81
  134. package/dist/http/routes/fleet.js +9 -185
  135. package/dist/http/routes/images.js +14 -239
  136. package/dist/http/routes/leader.js +0 -13
  137. package/dist/http/routes/memory-bundle.js +1 -61
  138. package/dist/http/routes/memory-policy.js +9 -99
  139. package/dist/http/routes/notify-wake.js +3 -37
  140. package/dist/http/routes/observability.js +2 -19
  141. package/dist/http/routes/retention-ops.js +2 -34
  142. package/dist/http/routes/rules.js +1 -93
  143. package/dist/http/routes/runs.js +64 -873
  144. package/dist/http/routes/session-sync.js +19 -258
  145. package/dist/http/routes/sessions-list.js +17 -43
  146. package/dist/http/routes/sessions.js +34 -226
  147. package/dist/http/routes/shared-memory.js +5 -36
  148. package/dist/http/routes/side-query.js +1 -87
  149. package/dist/http/routes/tasks.js +52 -722
  150. package/dist/http/routes/trace-usage.js +32 -195
  151. package/dist/http/routes/workflows.js +19 -195
  152. package/dist/http/run-meta.js +0 -6
  153. package/dist/http/send.js +0 -32
  154. package/dist/http/server.js +92 -1597
  155. package/dist/http/sse-lifecycle.js +2 -13
  156. package/dist/http/sse-log.js +3 -48
  157. package/dist/http/tar.js +5 -21
  158. package/dist/http/verify-rounds.js +0 -5
  159. package/dist/http/wire-gate.js +0 -9
  160. package/dist/http/workspace-content.js +0 -10
  161. package/dist/images/bake-validate.js +1 -70
  162. package/dist/images/manifest.js +1 -6
  163. package/dist/index.js +0 -21
  164. package/dist/key-resolver.js +2 -17
  165. package/dist/leader/diffout.js +1 -20
  166. package/dist/leader/diffup.js +0 -47
  167. package/dist/leader/endpoint.js +2 -57
  168. package/dist/leader/fanout.js +3 -45
  169. package/dist/leader/grader-env-factory.js +3 -72
  170. package/dist/leader/leader.js +5 -156
  171. package/dist/leader/merge.js +8 -115
  172. package/dist/leader/planner.js +3 -54
  173. package/dist/leader/repair-oracle.js +1 -60
  174. package/dist/leader/repair-wire.js +2 -79
  175. package/dist/leader/wire.js +8 -307
  176. package/dist/lsp/e2b-bridge.js +4 -64
  177. package/dist/lsp/e2b-manager.js +6 -94
  178. package/dist/lsp/lsp-frames.js +0 -12
  179. package/dist/lsp/manager.js +4 -96
  180. package/dist/lsp/ws-transport.js +5 -55
  181. package/dist/lsp-evict.js +1 -15
  182. package/dist/main.js +57 -765
  183. package/dist/memory-bundle-engine.js +0 -55
  184. package/dist/memory-export.js +0 -4
  185. package/dist/memory-posture.js +1 -15
  186. package/dist/memory-scope.js +11 -148
  187. package/dist/memory-sync-client.js +2 -44
  188. package/dist/memory-sync.js +1 -80
  189. package/dist/model-select.js +3 -80
  190. package/dist/observability/cost-quota.js +1 -17
  191. package/dist/observability/cost-taxonomy.js +0 -34
  192. package/dist/observability/fail-open.js +7 -86
  193. package/dist/observability/logger.js +0 -6
  194. package/dist/observability/metrics.js +0 -94
  195. package/dist/observability/otel-exporter.js +3 -13
  196. package/dist/observability/principal-context.js +0 -9
  197. package/dist/observability/prompt-manifest.js +1 -37
  198. package/dist/observability/rate-limit.js +0 -4
  199. package/dist/observability/secret-env-scrub.js +2 -56
  200. package/dist/observability/tool-trace.js +1 -70
  201. package/dist/orchestration/hardened-vm-runner.js +4 -118
  202. package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
  203. package/dist/orchestration/hardened-vm-worker.js +0 -27
  204. package/dist/orchestration/subagent-steer.js +1 -45
  205. package/dist/orchestration/workflow-agent-steer.js +1 -80
  206. package/dist/orchestration/workflow-completion-inbox.js +32 -285
  207. package/dist/orchestration/workflow-notify-journal.js +16 -259
  208. package/dist/org-memory-admission.js +3 -47
  209. package/dist/parent-watch.js +2 -48
  210. package/dist/parked-decide.js +1 -109
  211. package/dist/per-task-image.js +0 -57
  212. package/dist/plan-cache-probe.js +3 -27
  213. package/dist/plugins/adoption-log-sql.js +2 -119
  214. package/dist/plugins/approval-ask-store-memory.js +3 -38
  215. package/dist/plugins/approval-ask-store-sql.js +6 -188
  216. package/dist/plugins/approval-exemption-store.js +2 -28
  217. package/dist/plugins/background-agent-store-sql.js +4 -105
  218. package/dist/plugins/background-shell-support.js +14 -122
  219. package/dist/plugins/blob-backend.js +6 -169
  220. package/dist/plugins/breaker-state-sql.js +8 -46
  221. package/dist/plugins/caching-session-store.js +4 -106
  222. package/dist/plugins/checkpoint-store-sql.js +18 -505
  223. package/dist/plugins/e2b-orphan-reclaim.js +0 -45
  224. package/dist/plugins/file-outcome-sink.js +0 -9
  225. package/dist/plugins/file-resume-anchor-store.js +4 -43
  226. package/dist/plugins/file-run-store.js +26 -364
  227. package/dist/plugins/file-snapshot-store-sql.js +10 -181
  228. package/dist/plugins/fork-routing-session-store.js +8 -111
  229. package/dist/plugins/host-platform.js +2 -91
  230. package/dist/plugins/image-bake-store-sql.js +7 -250
  231. package/dist/plugins/image-index-sql.js +4 -123
  232. package/dist/plugins/k8s-bg-scripts.js +4 -88
  233. package/dist/plugins/k8s-exec-protocol.js +0 -41
  234. package/dist/plugins/leader-run-store-sql.js +0 -101
  235. package/dist/plugins/local-checkpoint-store.js +8 -128
  236. package/dist/plugins/local-session-store.js +34 -296
  237. package/dist/plugins/local-task-attachment-store.js +2 -16
  238. package/dist/plugins/mailbox-store-sql.js +8 -65
  239. package/dist/plugins/memory-embedder-fingerprint.js +5 -166
  240. package/dist/plugins/memory-embedder.js +4 -70
  241. package/dist/plugins/memory-engine-pg.js +6 -170
  242. package/dist/plugins/memory-engine-tidb.js +7 -155
  243. package/dist/plugins/memory-engine-vector-util.js +0 -10
  244. package/dist/plugins/memory-key-guards.js +0 -34
  245. package/dist/plugins/memory-origin-law.js +0 -187
  246. package/dist/plugins/memory-resume-anchor-store.js +0 -17
  247. package/dist/plugins/memory-run-store.js +12 -89
  248. package/dist/plugins/memory-session-policy-store.js +0 -17
  249. package/dist/plugins/memory-sync-store-pg.js +4 -49
  250. package/dist/plugins/memory-sync-store-tidb.js +3 -35
  251. package/dist/plugins/outcome-ledger-sql.js +3 -97
  252. package/dist/plugins/permission-rule-store-file.js +5 -133
  253. package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
  254. package/dist/plugins/permission-rule-store-sql.js +50 -307
  255. package/dist/plugins/pg-cost-quota.js +0 -7
  256. package/dist/plugins/pg-pool.js +0 -92
  257. package/dist/plugins/pg-rate-limiter.js +2 -13
  258. package/dist/plugins/pg-safe-json.js +4 -40
  259. package/dist/plugins/pg-session-storage.js +25 -189
  260. package/dist/plugins/posix-shell-fs.js +1 -31
  261. package/dist/plugins/remote-env-adb.js +12 -101
  262. package/dist/plugins/remote-env-e2b.js +40 -371
  263. package/dist/plugins/remote-env-file-error.js +0 -37
  264. package/dist/plugins/remote-env-host.js +63 -483
  265. package/dist/plugins/remote-env-k8s.js +32 -326
  266. package/dist/plugins/remote-env-local-docker.js +24 -157
  267. package/dist/plugins/remote-env-ssh.js +20 -128
  268. package/dist/plugins/remote-scratchpad.js +2 -32
  269. package/dist/plugins/remote-shell.js +1 -32
  270. package/dist/plugins/resume-anchor-store-sql.js +0 -11
  271. package/dist/plugins/retention-lane-store-sql.js +0 -108
  272. package/dist/plugins/retention-store-sql.js +4 -383
  273. package/dist/plugins/roster-store-sql.js +0 -55
  274. package/dist/plugins/run-store-sql.js +14 -251
  275. package/dist/plugins/s3-presign.js +2 -49
  276. package/dist/plugins/scheduler-support.js +3 -80
  277. package/dist/plugins/send-file-ledger.js +4 -53
  278. package/dist/plugins/send-user-file.js +4 -94
  279. package/dist/plugins/session-placement.js +1 -89
  280. package/dist/plugins/session-policy-store-sql.js +3 -81
  281. package/dist/plugins/session-store.js +0 -57
  282. package/dist/plugins/shared-memory-store-sql.js +4 -167
  283. package/dist/plugins/sql-driver.js +0 -17
  284. package/dist/plugins/sql-errors.js +0 -7
  285. package/dist/plugins/sql-escape.js +0 -8
  286. package/dist/plugins/sql-row-helpers.js +0 -25
  287. package/dist/plugins/store-backend.js +42 -222
  288. package/dist/plugins/store-contracts.js +2 -46
  289. package/dist/plugins/task-attachment-store.js +3 -49
  290. package/dist/plugins/task-list-store-sql.js +0 -79
  291. package/dist/plugins/tidb-cost-quota.js +1 -4
  292. package/dist/plugins/tidb-pool.js +1 -207
  293. package/dist/plugins/tidb-rate-limiter.js +3 -9
  294. package/dist/plugins/tidb-session-storage.js +4 -70
  295. package/dist/plugins/tidb-session-store.js +19 -341
  296. package/dist/plugins/tool-result-store-sql.js +4 -179
  297. package/dist/plugins/usage-window-store-sql.js +0 -8
  298. package/dist/plugins/web-search.js +10 -132
  299. package/dist/plugins/workflow-journal-store-sql.js +2 -58
  300. package/dist/plugins/workflow-run-store-sql.js +6 -91
  301. package/dist/plugins/worktree-isolation.js +6 -126
  302. package/dist/plugins/write-behind-counter.js +16 -75
  303. package/dist/principal-jwt.js +5 -60
  304. package/dist/project-memory.js +15 -146
  305. package/dist/prompts-domain-validate.js +1 -51
  306. package/dist/question.js +2 -111
  307. package/dist/resource-suspend.js +0 -18
  308. package/dist/router/route-orchestration.js +0 -77
  309. package/dist/rules-consent.d.ts +44 -5
  310. package/dist/rules-consent.js +75 -215
  311. package/dist/run-local.js +14 -381
  312. package/dist/runs.js +29 -617
  313. package/dist/runtime-caps-resolver.js +6 -132
  314. package/dist/runtime-governance.js +1 -232
  315. package/dist/sandbox-pkg-source.js +0 -37
  316. package/dist/sealed-key.js +3 -68
  317. package/dist/security.js +6 -291
  318. package/dist/session-leaf-bus.js +0 -32
  319. package/dist/session-sync-content.js +1 -69
  320. package/dist/session-sync-kernel.js +3 -60
  321. package/dist/session-sync.js +3 -66
  322. package/dist/session-titler.js +7 -41
  323. package/dist/session-watch.js +9 -73
  324. package/dist/shared-memory-scope-authorizer.js +0 -18
  325. package/dist/sighup-idle.js +1 -12
  326. package/dist/spec-fields.js +4 -143
  327. package/dist/store-live-probe.js +3 -49
  328. package/dist/task-a2a.js +1 -126
  329. package/dist/task-cwd.js +1 -103
  330. package/dist/task-mcp.js +1 -89
  331. package/dist/task-settings.js +10 -300
  332. package/dist/task-workflow.js +8 -75
  333. package/dist/tool-approval.d.ts +28 -1
  334. package/dist/tool-approval.js +70 -1430
  335. package/dist/trace/artifacts.js +5 -20
  336. package/dist/trace/engine-notice-wire.js +6 -138
  337. package/dist/trace/ledger-sink.js +7 -96
  338. package/dist/trace/project.js +4 -448
  339. package/dist/trace/redact.js +9 -77
  340. package/dist/turn-activity.js +1 -27
  341. package/dist/usage-analytics.js +4 -34
  342. package/dist/wall-clock-jump-guard.js +1 -68
  343. package/package.json +3 -3
  344. package/dist/capabilities/scenario-alias.d.ts +0 -27
  345. package/dist/capabilities/scenario-alias.js +0 -61
@@ -2,19 +2,7 @@ import { fetchPrincipalCaps } from "./config-center/facade.js";
2
2
  import { HttpError } from "./security.js";
3
3
  import { createLogger } from "./observability/logger.js";
4
4
  const defaultLogger = createLogger();
5
- /** #157 / [2762]§五(sema-comms `audits/failopen-governance-176.md` §7 "已知在办 1 条"):module-scope,
6
- * once-per-process record of which {@link gateExecutionLane} fail-open DEFAULT form has already been traced.
7
- * Two forms are counted SEPARATELY (both currently resolve to the same "pass" default, but they are different
8
- * facts about the world — a caller must be able to tell "we never got a ruling for this principal" apart from
9
- * "center ruled, and explicitly said not-required"):
10
- * - `"no_ruling"` — `ruling === undefined` (no principal / center absent-or-blip / older center missing the key).
11
- * - `"required_false"` — a ruling WAS resolved and it explicitly says `required: false`.
12
- * Each form logs ONCE per process at first hit (a debug line — this is an expected, non-alarming default, not a
13
- * warning); every subsequent hit of the SAME form only increments the counter (no repeat log line — this is a
14
- * per-task gate, so without the throttle a busy deployment would log once per task forever). Pure record-keeping:
15
- * never changes {@link gateExecutionLane}'s pass/deny outcome. */
16
5
  const executionLaneDefaultTrace = new Map();
17
- /** test-only: reset the once-per-process trace so each test observes a fresh "first hit". */
18
6
  export function resetExecutionLaneDefaultTraceForTest() {
19
7
  executionLaneDefaultTrace.clear();
20
8
  }
@@ -22,14 +10,8 @@ function traceExecutionLaneDefault(form, ownLane, logger) {
22
10
  const hitCount = (executionLaneDefaultTrace.get(form) ?? 0) + 1;
23
11
  executionLaneDefaultTrace.set(form, hitCount);
24
12
  if (hitCount > 1)
25
- return; // subsequent hits: silently counted only (see map above), no repeat log line
13
+ return;
26
14
  try {
27
- // codex 交叉复审第二轮真发现(2026-08-05):a throwing `logger.debug` must NEVER turn this pure
28
- // observability step into gateExecutionLane itself throwing — that would silently convert a fail-open
29
- // PASS into an uncaught exception, the exact inversion this module's whole design fights elsewhere
30
- // (see the FAIL-CLOSED/FAIL-OPEN posture doc above). The hit-count above already advanced, so a retry
31
- // of the SAME form stays silent rather than storming — the trade-off is "lose one trace line", not
32
- // "lose one task admission".
33
15
  logger.debug("execution_lane_default_applied", {
34
16
  dimension: "execution_lane",
35
17
  form,
@@ -41,34 +23,12 @@ function traceExecutionLaneDefault(form, ownLane, logger) {
41
23
  });
42
24
  }
43
25
  catch {
44
- // tracing must never affect the fail-open admission outcome — see comment above.
45
26
  }
46
27
  }
47
- /** Map center's `EntitlementRuntimeCaps` → the subset core's engine enforces. `allowUltracode` (shell/UX) is
48
- * dropped on purpose (not a core primitive); `allowWorkflows` / `forceDurableGate` / `allowFork` are the three the
49
- * engine enforces. Returns undefined when no enforceable cap is set (= no restriction). FAIL-CLOSED defense-in-depth:
50
- * do NOT trust the wire to be boolean. center serializes these as zod-validated booleans, but a schema drift / proxy
51
- * mangling that sent a TRUTHY non-boolean (e.g. the STRING "false") would slip past core's strict `=== false` deny
52
- * check and silently GRANT. So a cap that is PRESENT but not a real boolean is a contract breach → THROW (the
53
- * resolver's catch then fail-closes to deny). `allowFork` (registry-core 0.1.47): the per-principal Fork
54
- * ceiling — core mounts Fork only when `runtimeCaps.allowFork !== false` (prepare-task), tighten-only like allowWorkflows.
55
- *
56
- * `allowObservers`(observer 开闸线,core 1.270.0/1.273.0):**极性与 allow* 相反** —
57
- * core 是 `=== true` 显式 opt-in(默认 OFF,undefined ships dark)。registry-core 0.10.3 起该键已收编进
58
- * `EntitlementRuntimeCaps` 类型(此前 0.10.2 走 unknown 宽读透传,现为类型直读,行为零变)。
59
- * **缺键=不写键**(绝不把缺键补成显式 false —— core 是唯一默认源,补 false 语义上等价但会伪造「center
60
- * 说过话」的显式判决,污染 env-baseline 合成序,见 applyObserverEnvOptIn)。非 boolean 虽然骗不过 core
61
- * 的 `=== true`(不会误 GRANT),但 PRESENT-非-boolean 与 allow* 同属同一 body 的契约破损信号 → 同款 THROW。
62
- *
63
- * `autoMode`(core 1.276.0 `RuntimeCaps.autoMode`):auto-mode 分类器 per-principal
64
- * 开闸线,极性同 allowObservers(`=== true` 显式 opt-in,默认 OFF;真执行还要 deployment 装配
65
- * `RunnerDeps.autoMode` 分类器面=信任门,center 键只是 entitlement 半场)。[2373]B-6a:registry-core
66
- * 0.13.0 已把 autoMode 收进 EntitlementRuntimeCaps(types.d.ts:1786)——宽读逃逸期结束,现为类型直读
67
- * (allowObservers 同款收编先例)。 */
68
28
  export function toCoreRuntimeCaps(ent) {
69
29
  if (!ent)
70
30
  return undefined;
71
- const autoMode = ent.autoMode; // [2373]B-6a:0.13.0 类型直读(宽读逃逸已收编)
31
+ const autoMode = ent.autoMode;
72
32
  const badCap = (v) => v !== undefined && typeof v !== "boolean";
73
33
  if (badCap(ent.allowWorkflows) || badCap(ent.forceDurableGate) || badCap(ent.allowFork) || badCap(ent.allowObservers) || badCap(autoMode)) {
74
34
  throw new Error("runtimeCaps from center carries a non-boolean cap (contract breach — fail closed)");
@@ -86,43 +46,12 @@ export function toCoreRuntimeCaps(ent) {
86
46
  caps.autoMode = autoMode;
87
47
  return Object.keys(caps).length ? caps : undefined;
88
48
  }
89
- /**
90
- * #A5([4147] 件2)—— 「这台 worker 上**存在** per-principal entitlement 源吗」的**单一谓词**。
91
- *
92
- * 判据逐字 = `boot/runtime-caps.ts` 建 caps 客户端的那一条(**配了 center ∧ 非 dry-run**):dry-run 的部署
93
- * 拉取并记录 center 配置但**不应用、不执法**(observe-only),所以它解不出任何 per-principal caps —— 把它
94
- * 算成源会让能力面对着一台永不武装的机器说「有源」。
95
- *
96
- * 为什么要抽成函数:这条判据现在有**两个**读者 —— boot 的装配点,与 `/v1/capabilities` 的
97
- * `permissionModeAuto.entitlementSource` 披露位。两处各写一份条件,就是本仓在册的
98
- * 「诊断页说亮、能力位说暗」病族的入口(装配与披露对同一件事各算各的)。
99
- *
100
- * ⚠️ **辖域**:说的是「源在不在」,不是「某个 principal 被授权了」。授权是 per-principal 的
101
- * (`RuntimeCaps.autoMode === true` 逐 principal 解),任何部署级布尔都对它说不了话。
102
- */
103
49
  export function centerEntitlementSourceWired(config) {
104
50
  return config.configCenter !== undefined && !config.configCenter.dryRun;
105
51
  }
106
- /** A MISCONFIG diagnostic (review MEDIUM): a worker-scoped `wpt_<self>` pull token REQUIRES `?worker=<self>` to
107
- * resolve per-principal caps — so a `wpt_` token with NO `SEMA_REGISTRY_WORKER` set will 403
108
- * on every caps fetch → fail-closed DENY ALL workflows for ALL principals (correct degrade, but otherwise silent
109
- * bar per-task warns). True ⇒ the caller should log a one-shot BOOT warning so the misconfig is diagnosable.
110
- * (The orchestrator normally injects SEMA_REGISTRY_WORKER alongside a wpt_ token, so this is a hand-misconfig.) */
111
52
  export function scopedTokenNeedsWorker(token, worker) {
112
53
  return token.startsWith("wpt_") && !worker;
113
54
  }
114
- /**
115
- * Task-admission enforcement of the per-principal execution policy(stage7 P5 的「真强制」
116
- * service 半场——壳侧锁定是易用性强制可绕,这里是引擎闸)。The lane is a WORKER-PROCESS identity
117
- * (REMOTE_EXEC is boot wiring; there is no per-task lane switch), so the gate is "this worker's lane is not
118
- * permitted for this principal ⇒ reject the task": typed 403 `execution_lane_not_allowed` carrying the
119
- * allowlist + this worker's lane (machine code on the wire, prose stays with the shell/web).
120
- * No ruling / required=false / caps blip ⇒ pass (fail-open + audit). `allowedLanes` is an
121
- * OPEN domain — unknown names simply never match this worker's lane.
122
- *
123
- * `logger` (optional, defaults to this module's `defaultLogger`) is an injection seam purely for the
124
- * once-per-process default trace below (test observability) — it does not change enforcement.
125
- */
126
55
  export function gateExecutionLane(ruling, ownLane, logger = defaultLogger) {
127
56
  if (!ruling) {
128
57
  traceExecutionLaneDefault("no_ruling", ownLane, logger);
@@ -139,13 +68,6 @@ export function gateExecutionLane(ruling, ownLane, logger = defaultLogger) {
139
68
  });
140
69
  }
141
70
  }
142
- /**
143
- * Build the per-principal caps client: `resolveRuntimeCaps` matches core's seam signature
144
- * `(principal) => Promise<RuntimeCaps | undefined>`; `scenarioRuling` rides the SAME fetch/cache/in-flight
145
- * (zero extra center RTT — center attaches the ruling to the caps body). Wire ONLY when a center is
146
- * configured (and not in dry-run) — absent ⇒ core sees `undefined` ⇒ NO per-principal restriction, and the
147
- * scenario gate sees `undefined` ⇒ legacy body-or-default chain.
148
- */
149
71
  export function createPrincipalEntitlementsClient(opts) {
150
72
  const ttlMs = opts.ttlMs ?? 60_000;
151
73
  const denyTtlMs = opts.denyTtlMs ?? Math.min(ttlMs, 10_000);
@@ -155,9 +77,6 @@ export function createPrincipalEntitlementsClient(opts) {
155
77
  const onError = opts.onError ?? ((err, principal) => defaultLogger.warn("runtime_caps_resolve_failed", { principal, err: String(err) }));
156
78
  const cache = new Map();
157
79
  const inflight = new Map();
158
- // Bound cache growth (review L1-1): `principal` is a per-user identity, so a long-lived worker — or a direct-door
159
- // caller spoofing many principals — would accumulate one entry forever. On write, when the map crosses a soft cap,
160
- // sweep logically-expired entries (TTL is a freshness gate, not eviction). Caps retention to ~the active set.
161
80
  const MAX_CACHE = 10_000;
162
81
  const putCache = (key, entry) => {
163
82
  if (cache.size >= MAX_CACHE) {
@@ -168,25 +87,17 @@ export function createPrincipalEntitlementsClient(opts) {
168
87
  }
169
88
  cache.set(key, entry);
170
89
  };
171
- // ONE loader behind both faces: fresh-cached entry → as-is; else fetch (in-flight-deduped) → cache → entry.
172
90
  async function load(principal) {
173
91
  const cached = cache.get(principal);
174
92
  if (cached && cached.expiresAt > now())
175
93
  return cached;
176
- // Collapse concurrent prepare-time lookups for the SAME principal onto one in-flight fetch.
177
94
  const dup = inflight.get(principal);
178
95
  if (dup)
179
96
  return dup;
180
97
  const p = (async () => {
181
98
  try {
182
- // `cached?.etag` is set only on a real prior entry (an error entry clears it) → an `if-none-match` here can
183
- // only 304-confirm a REAL value, never resurrect a stale deny. Pass `opts.worker` as `?worker=<self>` so a
184
- // worker-scoped `wpt_<self>` token can resolve its OWN worker's principal caps; harmless
185
- // on a full token (caps are worker-independent — the worker only scopes the config view we ignore).
186
99
  const r = await fetchCaps(opts.baseUrl, opts.token, principal, cached?.etag, fetchImpl, opts.worker);
187
100
  if (r === null) {
188
- // 304 — unchanged. We only sent if-none-match because `cached` was a real entry → reuse caps AND the
189
- // rulings (same body, same generation), refresh TTL.
190
101
  const entry = {
191
102
  caps: cached?.caps,
192
103
  ...(cached?.scenario ? { scenario: cached.scenario } : {}),
@@ -197,14 +108,11 @@ export function createPrincipalEntitlementsClient(opts) {
197
108
  putCache(principal, entry);
198
109
  return entry;
199
110
  }
200
- // execution ruling shape drift = ruling dropped (fail-open) + THIS audit
201
- // line — the "fail-open + audit" posture now covers the wire-drift direction too (previously a
202
- // drifted allowedLanes coerced to [] = silent fleet-wide deny-all). Routed through onError (never throws).
203
111
  if (r.executionDrift) {
204
112
  try {
205
113
  onError(new Error(`execution ruling wire drift: ${r.executionDrift}`), principal);
206
114
  }
207
- catch { /* observability never breaks the resolver */ }
115
+ catch { }
208
116
  }
209
117
  const entry = {
210
118
  caps: toCoreRuntimeCaps(r.runtimeCaps),
@@ -217,11 +125,6 @@ export function createPrincipalEntitlementsClient(opts) {
217
125
  return entry;
218
126
  }
219
127
  catch (err) {
220
- // codex 交叉复审第三轮真发现(2026-08-05):a poison rejection reason (throwing `Symbol.toPrimitive`/
221
- // `toString`) must not make THIS normalization line itself throw — this catch block exists to
222
- // GUARANTEE the documented FAIL-CLOSED degrade below runs; if normalizing `err` throws, that
223
- // guarantee is exactly what breaks (resolveRuntimeCaps/scenarioRuling/executionRuling would reject
224
- // instead of resolving to their fail-closed/fail-open defaults on a degraded center response).
225
128
  let e;
226
129
  try {
227
130
  e = err instanceof Error ? err : new Error(String(err));
@@ -229,18 +132,12 @@ export function createPrincipalEntitlementsClient(opts) {
229
132
  catch {
230
133
  e = new Error("center fetch failed with an unstringifiable rejection reason");
231
134
  }
232
- // Caps face FAIL CLOSED: deny the OPTIONAL amplification caps (workflows + fork); never force the durable
233
- // gate on error. Scenario face FAIL OPEN: `scenario` stays undefined (no governance — never pin users to
234
- // the default scenario on a center blip; deliberate posture split, see PrincipalEntitlementsClient). Cache the
235
- // deny for a SHORT window (bound center hammering on a sustained outage) WITHOUT an etag — so the next
236
- // fetch is a full 200 pull that re-reads the real caps the moment center recovers (a 304 can't resurrect
237
- // this deny). Write the deny BEFORE onError so a throwing observability hook can't escape before recording.
238
135
  const entry = { caps: { allowWorkflows: false, allowFork: false }, expiresAt: now() + denyTtlMs };
239
136
  putCache(principal, entry);
240
137
  try {
241
138
  onError(e, principal);
242
139
  }
243
- catch { /* an observability hook must never break the resolver */ }
140
+ catch { }
244
141
  return entry;
245
142
  }
246
143
  finally {
@@ -251,48 +148,25 @@ export function createPrincipalEntitlementsClient(opts) {
251
148
  return p;
252
149
  }
253
150
  return {
254
- // No principal = an anonymous/system run with no per-principal identity → no per-principal restriction
255
- // (tighten-only: absence falls back to the deployment-level default; core treats undefined as "no caps").
256
151
  resolveRuntimeCaps: async (principal) => (principal ? (await load(principal)).caps : undefined),
257
152
  scenarioRuling: async (principal) => (principal ? (await load(principal)).scenario : undefined),
258
153
  executionRuling: async (principal) => (principal ? (await load(principal)).execution : undefined),
259
154
  };
260
155
  }
261
- /** Back-compat face for core's seam-only consumers: the caps half of {@link createPrincipalEntitlementsClient}. */
262
156
  export function createEntitlementsResolver(opts) {
263
157
  return createPrincipalEntitlementsClient(opts).resolveRuntimeCaps;
264
158
  }
265
- /**
266
- * observer 开闸线的 env 半场:`EXPERIMENTAL_OBSERVER_AGENTS=true` 时把
267
- * `RuntimeCaps.allowObservers: true` 作为**部署基线**合成进 caps resolver(core 1.270.0/1.273.0 的
268
- * observer 面是 `=== true` 显式 opt-in、默认 OFF —— 命名先例=CC 的 `CLAUDE_CODE_EXPERIMENTAL_OBSERVER_AGENTS`
269
- * env 对儿)。租户姿势(与 FORK_ENABLED/SELF_ORCHESTRATION 同款判别):
270
- * - **单用户部署**(`requirePrincipal !== true`,operator 即用户):env=true 直开 —— 无 center 时静态
271
- * `{allowObservers:true}`;有 center 时作 BASELINE,**center caps 带键则 center 赢**(含显式 false;
272
- * 缺键=center 没说话 → env 基线生效。这依赖 toCoreRuntimeCaps 缺键不补 false 的纪律 —— core 是唯一
273
- * 默认源,center 只有真说了话才覆盖)。fail-closed 错误 entry(deny)不带 allowObservers 键 → 单用户
274
- * env 基线在 center blip 期间维持(operator 在自己盒子上显式 opt-in 过;observers 非 workflows/fork
275
- * 级的放大面,且 deny entry 的两把闸不受影响)。
276
- * - **多租户**(`requirePrincipal === true`):env **不认**,只认 center caps(半配置 fail-loud:置了
277
- * env 却多租户 → 一次 boot warn,行为零变——与 MEMORY_ENGINE_BACKEND 半配置同款姿势)。
278
- * 返回合成后的 resolver;两边都无源(无 center、env 无效)⇒ undefined(RunnerDeps 不接线,零行为差)。
279
- * ⚠️ 调用方注意:`Boolean(centerResolver)` 才是「entitlement resolver wired」语义(enableForkFromBody /
280
- * selfOrchestrationFromBody 的多租户 fail-close 判别),不要改用本函数的返回值判 —— env-only lane 不是
281
- * center 背书的 entitlement 源。
282
- */
283
159
  export function applyObserverEnvOptIn(centerResolver, opts) {
284
160
  if (!opts.experimentalObserverAgents)
285
- return centerResolver; // env 未置 = 纯透传(多数部署路径,零成本)
161
+ return centerResolver;
286
162
  if (opts.requirePrincipal) {
287
- // 多租户不认 env(observer 是 per-principal 治理面,只认 center caps)——半配置 fail-loud 一次。
288
163
  opts.warn?.("observer_env_opt_in_ignored", {
289
164
  reason: "EXPERIMENTAL_OBSERVER_AGENTS=true is single-user only; a multi-tenant deployment (REQUIRE_PRINCIPAL=true) honors ONLY center caps allowObservers — env ignored, behavior unchanged",
290
165
  });
291
166
  return centerResolver;
292
167
  }
293
168
  if (!centerResolver)
294
- return () => ({ allowObservers: true }); // 单用户无 center:env 即静态开闸
295
- // 单用户 + center:env 作 baseline,center 带键(true/false 皆)覆盖;缺键 → baseline 生效。
169
+ return () => ({ allowObservers: true });
296
170
  return async (principal) => ({ allowObservers: true, ...(await centerResolver(principal)) });
297
171
  }
298
172
  //# sourceMappingURL=runtime-caps-resolver.js.map
@@ -1,117 +1,34 @@
1
- /**
2
- * Runtime governance compiler — the "second baton" (center §10, core 1.114.0): turn the
3
- * operator's declarative `runtime.autonomy` + `runtime.commandPolicy` (`@sema-agent/registry-core`,
4
- * carried to the service via sema-registry `EffectiveConfig.runtime`) into TIGHTEN-ONLY overrides on the
5
- * per-task `TaskSpec`. Pure so it is unit-testable in isolation (the spec is otherwise assembled inside
6
- * main.ts's `resolveSpec`).
7
- *
8
- * 🪤 TWO TRAPS this module exists to NOT fall into (see core
9
- * `tool-policy.ts` / `tighten-task-spec.ts` JSDoc):
10
- *
11
- * TRAP #1 — `TaskSpec.toolPolicy` OVERRIDES (not merges) `RunnerDeps.toolPolicy` (the runner does
12
- * `spec.toolPolicy ?? deps.toolPolicy`). A bare `spec.toolPolicy = createCoarseCommandNamePolicy(…)` would
13
- * SILENTLY DISCARD the deployment's approval/durable baseline. We compose via {@link tightenTaskSpec}, whose
14
- * toolPolicy rule is `combinePolicies(base, override)` (deny-wins) — the command gate can only ADD denies/asks
15
- * on top of the baseline, never clear it.
16
- *
17
- * TRAP #2 — `autonomy` must EXPAND INTO the three core primitives (`handsReadOnly` / `shellGate` / a
18
- * `toolPolicy`), NOT become a parallel enum on the spec (design-research §10: a parallel `autonomy` field +
19
- * `handsReadOnly:true` would conflict). The expansion is tighten-only and routed through `tightenTaskSpec`, so
20
- * `auto` (the loosest mode) can never LOOSEN a stricter base — `tightenTaskSpec` THROWS instead.
21
- *
22
- * ⚠️ `commandPolicy` is a COARSE name-level filter, NOT a sandbox. `sh -c …` / `$(…)` / pipes / `env X=…` /
23
- * path-prefixed commands all bypass the argv[0] match (they route to `ask`, fail-closed, but are not blocked by
24
- * name). Real isolation is the `executionEnv` sandbox. Same caveat core documents on `createCoarseCommandNamePolicy`.
25
- */
26
1
  import { combinePolicies, COARSE_SHELL_TOOLS as CORE_COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, DEFAULT_SUBAGENT_TOOL_NAME, parseLeadingCommandName, tightenTaskSpec, } from "@sema-agent/core";
27
2
  import { currentGovernanceAskMarks } from "./governance-ask-marks.js";
28
- /** True when `name` names a DELEGATION tool — the subagent tool(CC-187 canonical `"Agent"`,core
29
- * `DEFAULT_SUBAGENT_TOOL_NAME`)。5.0.0 RB-476:折叠面退役,RAW 比对 core 单源常量——旧名("Task")
30
- * 在 core roster 层响亮 miss,不再需要归一;单源常量本身就防了当年 1.202 改名时裸字符串失配的
31
- * fail-open(那个教训的解=引用常量,不是折叠)。 */
32
3
  export function isDelegationToolName(name) {
33
4
  return name === DEFAULT_SUBAGENT_TOOL_NAME;
34
5
  }
35
- /** The hands-read-only DELEGATION STRIP (the main.ts governed-spec site, extracted here so the actual filter —
36
- * not just the name predicate — is pinned by tests): a read-only (plan-mode) run does not mount a delegation
37
- * tool at all.
38
- *
39
- * 🔴 原始理由已被上游收编(2026-08-05 亲验安装包 core 5.13.x):子任务 spec 现在**继承** `handsReadOnly`
40
- * (`dist/agents/subagent.js:1501` 的 `...(ctx.handsReadOnly === true ? { handsReadOnly: true } : {})`,resume
41
- * 腿 `:405` 同款)与 `shellGate`(`dist/core/runner/prepare-task.js` 的 `inheritedGateForChildren`),所以
42
- * 「子任务会拿到可写的手」这个当年的缺口在 core 侧已经补上。本 strip 因此是**同向的第二层**(更严,不更松):
43
- * 它保证 read-only 一档下连委派工具面都不铸,与继承是否存在无关。拆掉它是一次行为变更(read-only 任务将
44
- * 重新能委派),需要单独决定——不要因为「上游修好了」就顺手删。
45
- *
46
- * Identity (the same array) when nothing matches, so the caller's spec-object churn stays minimal. */
47
6
  export function stripDelegationTools(tools) {
48
7
  const kept = tools.filter((t) => !isDelegationToolName(t.name));
49
8
  return kept.length === tools.length ? tools : kept;
50
9
  }
51
- /** The shell tool names a command-name gate parses — **core 的 {@link COARSE_SHELL_TOOLS} 本尊**,不再是镜像。
52
- * 本仓的 ask-list 策略因此恒门住与 `createCoarseCommandNamePolicy` **同一批**工具。
53
- * Q6 (core 1.161): tool names align to CC — `bash`→`Bash`; the old `bash_readonly` MERGED into `Bash` (read-only is
54
- * now a toolEffect, not a separate tool).
55
- *
56
- * 🔴 A-033.1(#256)换装记:本常量此前是**手抄镜像** `["Bash","Monitor"] as const`,注释自称「core 未导出」。
57
- * 那句话在 core RB-153(server [1708] 请托)之后就过期了 —— core 现在**公开导出**这份名单(index.d.ts:134),
58
- * 它自己的原话是「The list itself is the fact; publishing it removes the guess」。镜像的真实代价有前科:
59
- * core 1.414 把默认从 `["Bash"]` 扩到 `["Bash","Monitor"]`(Monitor 走同一条执行 seam),镜像漏跟一轮 ⇒
60
- * **blocklist 模式下本仓的 ask-list 不覆盖 Monitor 而 core 的 deny 覆盖**(core 端到端实证:同一条
61
- * `rm -rf $(cat targets)`,Bash 被拒、Monitor 零门执行)。换真 import 之后这一整类漏跟在结构上消失。
62
- * `test/coarse-shell-tools-mirror.test.ts` 的钉相应从「行为反推 + 值比对」改成**同源断言**(见那里顶注)。 */
63
10
  const COARSE_SHELL_TOOLS = CORE_COARSE_SHELL_TOOLS;
64
- /** 导出给 `test/coarse-shell-tools-mirror.test.ts` 的同源钉用(见那里的顶注)。 */
65
11
  export const COARSE_SHELL_TOOLS_MIRROR = COARSE_SHELL_TOOLS;
66
- /**
67
- * A per-command ask-list ToolPolicy: a parsed shell command whose `argv[0]` is in `askCommands` tightens to
68
- * `ask`; everything else passes (`allow`). core's `createCoarseCommandNamePolicy` has NO per-command ask list
69
- * (only `allow[]` / `deny[]` / `defaultAction`), so blocklist-mode "ask before THIS command, pass the rest"
70
- * cannot be expressed by it — this thin policy carries the `ask` decisions. It reuses core's
71
- * `parseLeadingCommandName` (the SINGLE source-of-truth simple-command parser used by the `Bash` tool and
72
- * `createCoarseCommandNamePolicy`), so the argv[0]-bypass surface (operators / pipes / `env=` / path-prefix)
73
- * routes to `ask` (fail toward the gate), never silent-allow.
74
- */
75
12
  function createCommandAskListPolicy(askCommands, tools = COARSE_SHELL_TOOLS) {
76
13
  const askSet = new Set(askCommands);
77
14
  const shellTools = new Set(tools);
78
15
  const ask = (message) => ({ action: "ask", message, decisionReason: "rule" });
79
16
  return {
80
17
  check(req) {
81
- // Out of scope for non-shell tools: this gate only speaks about shell command names. RAW(5.0.0):
82
- // live 名恒 canonical,pre-rename 重放由 core 响亮拒——折叠臂退役。
83
18
  if (!shellTools.has(req.toolName))
84
19
  return { action: "allow" };
85
20
  const command = req.args?.command;
86
21
  if (typeof command !== "string")
87
22
  return ask(`tool "${req.toolName}" call has no parseable command string`);
88
23
  const parsed = parseLeadingCommandName(command);
89
- // Un-parseable (operators / path-prefix / env-assignment) = exactly the argv[0]-bypass surface → gate it.
90
24
  if ("reject" in parsed)
91
25
  return ask(`command is not a single simple command (${parsed.reject})`);
92
26
  return askSet.has(parsed.name) ? ask(`command "${parsed.name}" requires approval by policy`) : { action: "allow" };
93
27
  },
94
28
  };
95
29
  }
96
- /** A valid `commandPolicy` rule targets a bare argv[0] command NAME. core's `createCoarseCommandNamePolicy` does
97
- * EXACT `Set` matching (NOT glob, NOT last-match-wins — the §10 verdict deliberately downgraded the original
98
- * `createCommandGlobPolicy` idea), so a glob like `git*` or a path `/usr/bin/rm` or `npm i`
99
- * would SILENTLY never match a parsed argv[0] → the command would slip the gate. We accept only bare-name tokens
100
- * (letters/digits and `. _ - +`, covering `python3.11`/`docker-compose`/`g++`) and reject the rest FAIL-LOUD at
101
- * config-apply. 🔴 Kept ALIGNED with the `@sema-agent/registry-core` contract-layer regex (center fixed the
102
- * glob→exact contract bug in 0.1.26 + publish-validates with this SAME pattern) so the contract gate and this
103
- * defense-in-depth gate agree. [2373]B-11:旧注靠「dep=^0.1.26 additive 窗」论证对齐,地板抬到 ^0.13.0 后
104
- * 该论证失效——现由机器钉逐字节对表(test/runtime-governance.test.ts 读安装包正则比对),lockstep 不再
105
- * 是注释请求。2026-08-02 亲核 0.13.0 dist/types.js:703:两正则逐字节相同。 */
106
30
  const VALID_COMMAND_NAME = /^[A-Za-z0-9._+-]+$/;
107
- /** The three legal per-command decisions (mirrors the registry `runtime.commandPolicy[].decision` enum). */
108
31
  const VALID_DECISIONS = new Set(["allow", "ask", "deny"]);
109
- /** Validate `commandPolicy` rules — the shape (array of {command, decision}), the `command` (a bare argv[0] name,
110
- * see {@link VALID_COMMAND_NAME}), AND the `decision` enum. Returns the list of human-readable errors (empty =
111
- * OK). Called at config-apply (config-center/facade.ts `applyRuntimeHot`) so a malformed rule that BYPASSED the
112
- * registry schema (a hand-edited config.d / a non-conformant publish) is rejected LOUDLY rather than
113
- * silently mis-compiling — review MEDIUM: an out-of-enum `decision` (e.g. "DENY") would otherwise fall through
114
- * {@link compileCommandPolicy}'s `decision==="deny"?…:"ask"` to ASK, silently WEAKENING an intended deny. */
115
32
  export function validateCommandRules(rules) {
116
33
  const errors = [];
117
34
  if (!Array.isArray(rules))
@@ -132,31 +49,9 @@ export function validateCommandRules(rules) {
132
49
  }
133
50
  return errors;
134
51
  }
135
- /**
136
- * Compile `runtime.commandPolicy` (per-command 3-way rules) into a single ToolPolicy, or `undefined` when there
137
- * is nothing to govern. The registry model (per-command `allow`/`ask`/`deny`) and core's helper model
138
- * (`allow[]` + `deny[]` + `defaultAction`) don't map 1:1, so the compilation picks a mode by the rules present:
139
- *
140
- * - **Allowlist mode** — triggered when ANY rule resolves to `allow`. Only `allow` commands pass; `deny` wins;
141
- * EVERY other command — `ask`-decision AND unlisted — falls to `defaultAction:"ask"`.
142
- * 🔴 OPERATOR FOOTGUN (documented contract): adding even one `allow` rule flips the whole policy to a strict
143
- * allowlist — previously-passing unlisted commands now ASK.
144
- * - **Blocklist mode** — no `allow` rules. Only `deny` commands are blocked; the rest pass THROUGH to the
145
- * deployment baseline. Per-command `ask` is carried by {@link createCommandAskListPolicy} (the coarse helper
146
- * can't express it). Un-parseable bypass commands fail toward `ask` in both the deny gate and the ask-list.
147
- *
148
- * Duplicate rules for the SAME command collapse to the STRICTEST decision (`deny > ask > allow`) BEFORE the mode
149
- * split, so e.g. a command listed both `allow` and `ask` resolves to `ask` — honoring `combinePolicies`'
150
- * strictest-wins (without this, the `allow` rule would shadow the `ask` for that command in allowlist mode).
151
- *
152
- * Either way the result only ever ADDS asks/denies — composed onto the baseline via `combinePolicies`
153
- * (deny-wins) by {@link applyRuntimeGovernance}, so it is tighten-only. (Assumes commands are pre-validated by
154
- * {@link validateCommandRules}; an un-matchable command here is simply inert, never a parse error.)
155
- */
156
52
  export function compileCommandPolicy(rules) {
157
53
  if (!rules || rules.length === 0)
158
54
  return undefined;
159
- // Collapse per command to the strictest decision (deny > ask > allow) so a duplicate can't shadow a stricter one.
160
55
  const RANK = { deny: 3, ask: 2, allow: 1 };
161
56
  const strictest = new Map();
162
57
  for (const r of rules) {
@@ -172,20 +67,9 @@ export function compileCommandPolicy(rules) {
172
67
  }
173
68
  const policies = [];
174
69
  if (allow.length > 0) {
175
- // Allowlist mode: `allow` passes, `deny` wins, `ask`-decision + unlisted fall to defaultAction "ask".
176
70
  policies.push(createCoarseCommandNamePolicy({ allow, deny, defaultAction: "ask" }));
177
71
  }
178
72
  else {
179
- // Blocklist mode: only `deny` blocks; per-command `ask` via the ask-list; an unlisted **simple** command
180
- // passes through.
181
- // ⚠️ #184([3038]/[3047] 双探针实证):「unlisted pass through」只对**单纯命令**成立。带 shell 运算符的
182
- // 复合命令(`a && b`、管道、`;`)`parseLeadingCommandName` 判 reject,而 `createCoarseCommandNamePolicy`
183
- // 的 `defaultAction` **缺省就是 `"ask"`**(core tool-policy.js:`opts.defaultAction ?? "ask"`)—— 于是它们
184
- // 在 deny 门上折 **ask**,不是放行(无 command 串的调用同理)。这与 allowlist 模式那句「fall to
185
- // defaultAction」是同一个兜底,只是这里没显式传参所以容易读漏。上方 docblock 的「Un-parseable bypass
186
- // commands fail toward `ask` in both the deny gate and the ask-list」说的就是这一条。零行为码改动。
187
- // (NOTE: never pass `allow: []` to the coarse helper — an empty array is truthy there and would turn it
188
- // into an allowlist that asks on EVERYTHING. Omit allow entirely instead.)
189
73
  if (deny.length > 0)
190
74
  policies.push(createCoarseCommandNamePolicy({ deny }));
191
75
  if (ask.length > 0)
@@ -195,15 +79,6 @@ export function compileCommandPolicy(rules) {
195
79
  return undefined;
196
80
  return policies.length === 1 ? policies[0] : combinePolicies(...policies);
197
81
  }
198
- /**
199
- * Expand an autonomy mode into TIGHTEN-ONLY `TaskSpec` safety overrides (the three primitives — TRAP #2):
200
- * - `read-only` / `plan` → `handsReadOnly: true` (read + propose, never mutate; the §6 verifier read-only
201
- * boundary). `plan` shares the SAME safety boundary as `read-only`; the "present a plan for approval" intent
202
- * is higher up (UX), not a distinct TaskSpec safety field, so both map to read-only hands.
203
- * - `ask` → `shellGate: "always"` (every `Bash` command tightens to an `irreversible_ask` durable suspend —
204
- * the fail-closed default for an unattended deployment with no parsed classifier).
205
- * - `auto` / `undefined` → `{}` (no extra tightening; still subject to the deployment baseline + commandPolicy).
206
- */
207
82
  export function autonomyOverrides(autonomy) {
208
83
  switch (autonomy) {
209
84
  case "read-only":
@@ -216,53 +91,11 @@ export function autonomyOverrides(autonomy) {
216
91
  return {};
217
92
  }
218
93
  }
219
- /**
220
- * #220:**部署级**判据 —— 本部署的治理层自己是否把 shellGate 抬到 `"always"`。
221
- *
222
- * 用途 = 持久门(park 行)的出身归因。活卡腿判「这只 ask 是治理层产的」靠 ALS 标记表(见本文件
223
- * `createGovernanceAskMarkingPolicy` / `createGovernanceShellGateMarkPolicy`),而 park 腿天然跨副本、
224
- * 跨重启,进程内的表在那里结构上够不着;行上唯一的取证格是 core 写的
225
- * `gate.riskDescriptor.shellGateDoctrine`。
226
- *
227
- * 🔴 **为什么单看行上那一格不够**:有效档 `"always"` **不止治理层一个产地** —— `resolve-spec` 的 SUP 路由
228
- * 姿态(`supPostureOverrides`)在 governance **之后**叠,同样产 `"always"`(本文件 `manualModeShellGate`
229
- * 合成段的注释里已如实记着这条时序)。只看行 ⇒ 一条 SUP 路由出来的门会被谎报成「运维治理层强制」,
230
- * 而那恰是这个信号要回答的那个问题。所以归因取**合取**:行上是 `always` **∧** 本部署的治理层本来就
231
- * 要求 `always`(后者成立时,治理层就是一个真成因,SUP 是否也抬过不改变这句话的真假)。
232
- *
233
- * 判据逐字对着两个产地(与 `applyRuntimeGovernance` 里 `overrides.shellGate` 的取值同源):
234
- * `autonomyOverrides(autonomy).shellGate`(`AUTONOMY=ask`)与 `MANUAL_MODE_SHELL_GATE` 旋钮,取「有一个
235
- * 是 always」。`commandPolicy` 不入判据:它产的是 `toolPolicy` 的 ask,不抬 shellGate 档,与本格无关。
236
- *
237
- * ⚠️ **已知残留(codex 对抗复审 2026-08-11 逮到,如实登记而不是掩盖)**:本判据读的是**当下**的
238
- * config,而 `config.autonomy` 是**热改**的(`config-center/apply-effective.ts` 就地写 `config.autonomy`)。
239
- * 于是失真是**双向**的,不是我原先写的「只往缺席方向」:
240
- * · 缺席向(常见):mint 之后运维把 `AUTONOMY=ask` 撤了 ⇒ 老 park 行归不出治理出身,少标一个;
241
- * · **在场向**(窄):一条**纯 SUP 路由**产的门(`ROUTER_ENABLED` 开 ∧ 路由判 supervisor ∧ 当时治理层
242
- * 并不要求 always),之后运维把 autonomy 热改成 `ask`,那条老行会被标成治理出身 —— 五个前提要**同时**
243
- * 成立,且该行仍未决。
244
- * 根治要**在 mint 那一刻把出身写进行里**(core 的 `CheckpointGate` 属主面,不是本仓能单方面做的);
245
- * 在那之前这一位的定位是**分诊提示**,不是裁决输入 —— 它不参与任何门/CAS/resume 判定,消费端也只拿它
246
- * 渲染徽标。登记在此,不加特征化测试(把已知残留钉成契约是另一种病)。
247
- */
248
94
  export function governanceMandatesShellGateAlways(governance) {
249
95
  return autonomyOverrides(governance.autonomy).shellGate === "always" || governance.manualModeShellGate === "always";
250
96
  }
251
- /**
252
- * [2942]/[2943] `governanceForced` 的**写侧** —— 把内层策略(治理层自己合成的那些)产的 `ask` 记进标记表。
253
- *
254
- * 决议本身**逐字透传**:本包装器只观察,不改 action / message / updatedInput / decisionReason,也不吞异常
255
- * (内层抛就照抛,fail-closed 的属主仍是内层)。同步内层保持同步返回(不无谓地把一条同步策略异步化)。
256
- *
257
- * 为什么标在这一层而不是让 `decisionReason` 上 wire:core 的 ask 铸造点不透传 `PermissionResult`
258
- * (侦察结论逐字见 `governance-ask-marks.ts` 顶注),这条缝在当前 core 上根本不存在。
259
- */
260
97
  function createGovernanceAskMarkingPolicy(inner, marksOf) {
261
- // 🔴 codex 交叉复审 round2 [medium](验真):**如实覆写**,不是只加不减 —— core 的
262
- // `recheckApprovedEdit` 拿同一个 `toolCallId` 重跑策略链(人编辑 args 之后),「原始 args 治理层要问 /
263
- // 编辑后治理层放行、由别的策略提第二问」会让第二张卡挂陈旧治理标。理由全文见 GovernanceAskMarks.setMarked。
264
98
  const note = (d, req) => {
265
- // 表在**裁决那一刻**解析(不是构造时):策略闭包铸于 resolveSpec,真正的作用域要到 run 腿跑起来才在。
266
99
  marksOf()?.setMarked(req.toolCallId, d.action === "ask");
267
100
  return d;
268
101
  };
@@ -273,72 +106,22 @@ function createGovernanceAskMarkingPolicy(inner, marksOf) {
273
106
  },
274
107
  };
275
108
  }
276
- /**
277
- * [2942]/[2943] shellGate 腿的写侧 —— **纯观察器**(恒 `allow` = `combinePolicies` 折叠里的弃权,见
278
- * `applyRuntimeGovernance` 顶注对 sensitivePathPolicy 那段 allow 语义的同款说明)。
279
- *
280
- * 只在治理层把 shellGate 抬到 **`"always"`** 时装:该档下 core 对**每一次** shell 调用都要人批
281
- * (`prepare-task.js` 把 Bash/Monitor 的 irreversibility tier 置 `"always"`),所以「这是一次 shell 调用」
282
- * 与「这只 ask 是治理层门出来的」等价——推断是紧的,零错标。
283
- *
284
- * 🔴 `"classify"` 档**故意不装**:那一档由 core 的分类器逐调用裁决,一次 shell ask 可能来自分类器
285
- * (治理),也可能来自 `APPROVAL_REQUIRE` 这类别的门(非治理),在 `AskRequest` 上无从分辨。按本信号
286
- * 「只做正向断言」的语义,分不清就**让键缺席**,不去猜(`governance-ask-marks.ts` 语义 1)。
287
- */
288
109
  function createGovernanceShellGateMarkPolicy(marksOf, tools = COARSE_SHELL_TOOLS) {
289
110
  const shellTools = new Set(tools);
290
111
  return {
291
112
  check(req) {
292
- // 只对 shell 工具**置位**,非 shell 一格不碰(那格的属主是上面的 ask 观察器)。本策略在合成里
293
- // 恒排在 ask 观察器**之后**(见 applyRuntimeGovernance 的 combinePolicies 顺序):`always` 档下
294
- // 每一次 shell 调用都被治理层门住,所以它对 shell 那一格的裁定压过「治理策略这次没产 ask」。
295
113
  if (shellTools.has(req.toolName))
296
114
  marksOf()?.setMarked(req.toolCallId, true);
297
115
  return { action: "allow" };
298
116
  },
299
117
  };
300
118
  }
301
- /** core `tighten-task-spec` 的 SHELL_GATE_RANK 镜像(单一属主:#153 搬家后本文件是 server 侧唯一副本;
302
- * task-settings 的旧副本随 settings 折叠臂一并拆除)。tightenTaskSpec 的契约是「override 自身必须收紧」
303
- * 而非「取两者较严」——低于 base 的 override 直接 throw,所以施加前要按 rank 只升不降。 */
304
119
  const SHELL_GATE_RANK = { off: 0, classify: 1, always: 2 };
305
- /**
306
- * Apply the operator's runtime governance (autonomy + commandPolicy + manualModeShellGate) onto a base
307
- * `TaskSpec`, TIGHTEN-ONLY, in a SINGLE {@link tightenTaskSpec} call: commandPolicy compiles to a `toolPolicy`
308
- * override (combined onto the baseline by `tightenTaskSpec` — TRAP #1), and autonomy expands to
309
- * `handsReadOnly` / `shellGate` overrides (TRAP #2). `tightenTaskSpec` THROWS `TaskSpecTightenError` if any
310
- * override would LOOSEN a stricter base safety field — surfacing a misconfiguration loudly instead of silently
311
- * weakening the gate. Returns `base` untouched when there is nothing to apply.
312
- *
313
- * #153([2713] server 分单②):`MANUAL_MODE_SHELL_GATE` 从 settings 折叠搬到这里**无条件施加**。旧家
314
- * (task-settings deriveSettingsPolicy 的 default/auto/acceptEdits 三臂)让一个**部署级 tighten-only 旋钮
315
- * 的生死由客户端表态在场性决定**:body 无 settings/permissionMode(cli print/headless 腿按设计不 stamp)
316
- * ⇒ 旋钮静默失效(clay 实机 Monitor 案「门根本没铸」);bypassPermissions 一句话掀掉部署闸;非 host lane
317
- * fsWriteGate wiring 不建 ⇒ 沙箱 lane 全失效。governance 层与 autonomy 同拍施加后三者全闭。rank 合成:
318
- * autonomy 派生值与旋钮取大(ask→"always" 压过 "classify"),再对 base 只升不降(防 tightenTaskSpec throw)。
319
- *
320
- * #177([2951] issue #29,**邻仓 test AI 的 46 格真机矩阵**实证——不是本仓的钉,别到本仓文件里数 46;
321
- * 本仓的类级门是 test/operator-knob-client-posture-matrix.test.ts 的 旋钮×表态×lane 矩阵):
322
- * `SENSITIVE_WRITE_PATTERNS` 是 #153 那次搬家漏下的兄弟旋钮——同一个病灶原样复发在 sensitive-path
323
- * DENY 腿上(唯一合成点在 deriveSettingsPolicy 的 fsWriteGate 闭包,bypassPermissions/键缺席/settings
324
- * 整缺席三形整条腿不建)。同方修:调用方(resolve-spec)把 operator 守卫集预铸成 `sensitivePathPolicy`
325
- * 从这里**无条件施加**——它只在守卫段上产 deny(core 语义 deny/"safety"),对其余目标返回的是
326
- * `action:"allow"`(**不是**「无意见」这种第三态:core 的 ToolPolicy 没有弃权值。它在
327
- * `combinePolicies` 的 deny/ask 优先折叠里不构成一票,所以在**折叠语境**下等价于弃权;若日后有人把
328
- * 这条策略单独当成唯一 policy 用,那个 allow 就是真放行——别照抄这句话去别的语境)。于是对 bypass
329
- * 只挡守卫段、不给普通目标加 ask 门([820] 表第四行「bypass=不加门非开门」保持逐字)。策略在这里
330
- * 只组合不构造:裁决 env 的 lane 分形(host 真 fs / 沙箱 deferred 代理)是 resolve-spec 的属主知识,
331
- * governance 层不重复它。
332
- */
333
120
  export function applyRuntimeGovernance(base, governance) {
334
- // 显式注入(测试/将来的多实例形)优先;否则**每次裁决时**取当前 run 腿的 ALS 表(顶注「作用域」段)。
335
121
  const marksOf = () => governance.askMarks ?? currentGovernanceAskMarks();
336
122
  const overrides = { ...autonomyOverrides(governance.autonomy) };
337
123
  const coarse = compileCommandPolicy(governance.commandPolicy);
338
124
  const governancePolicies = [coarse, governance.sensitivePathPolicy].filter((p) => p !== undefined);
339
- // [2942]/[2943]:治理层自己产的 `ask` 打标 —— 包在**合成之后**,于是 commandPolicy 的两模式
340
- // (allowlist 的 defaultAction:"ask" / blocklist 的 ask-list)与 sensitivePathPolicy 走同一个铸点,
341
- // 不会漏掉将来加进这个数组的第四条腿。
342
125
  const governancePolicy = governancePolicies.length > 0
343
126
  ? createGovernanceAskMarkingPolicy(governancePolicies.length === 1 ? governancePolicies[0] : combinePolicies(...governancePolicies), marksOf)
344
127
  : undefined;
@@ -348,31 +131,17 @@ export function applyRuntimeGovernance(base, governance) {
348
131
  const candidate = overrides.shellGate !== undefined && SHELL_GATE_RANK[overrides.shellGate] >= SHELL_GATE_RANK[governance.manualModeShellGate]
349
132
  ? overrides.shellGate
350
133
  : governance.manualModeShellGate;
351
- // base 已更严 ⇒ 省略,让 base 原样保留(省略=行为等价,不 throw)。**仍然装配上不可达、纯防御**
352
- // ——但论证已随 design/201 换了一条:
353
- // · 前提(改):resolveSpec 的 spec 字面量**现在会写 shellGate** —— 显式 permissionMode 的翻译档
354
- // (`shellGateForMode`,阶段④,governance 之前),所以 base.shellGate 不再恒缺席。
355
- // · 结论(不变):这一支照旧不可达。翻译表的**上限是 classify**(rank 1),而能走到这里的 candidate
356
- // 下限也是 classify(旋钮词表只有 classify/always,且 candidate 已取过与 autonomy 派生值的较大者)
357
- // ⇒ candidate ≥ base 恒成立。SUP 路由姿态(resolve-spec 的 supPostureOverrides)是 governance 之
358
- // **后**才叠的,够不到这里的 base。
359
- // 若将来有人给 base 种上更严的值(例:翻译表新增一个 ⇒ always 的模式词),这里省略而不是让
360
- // tightenTaskSpec 因「override 更松」throw——candidate 已是 autonomy 派生值与旋钮的较大者,省略它不会
361
- // 丢掉 autonomy 那一半(autonomy 只产 "always",即最高 rank,永远不会落进这一支)。
362
134
  if (SHELL_GATE_RANK[candidate] >= SHELL_GATE_RANK[base.shellGate ?? "off"])
363
135
  overrides.shellGate = candidate;
364
136
  else
365
137
  delete overrides.shellGate;
366
138
  }
367
- // [2942]/[2943] shellGate 腿:治理层**自己**把门抬到 `"always"` 时,挂上纯观察器给 shell 调用打标
368
- // (理由与 `"classify"` 为何不装,见 createGovernanceShellGateMarkPolicy 顶注)。判据取
369
- // `overrides.shellGate` 而不是有效档 —— base 已经更严时上面那支会 `delete`,那时门不是治理层给的。
370
139
  if (overrides.shellGate === "always") {
371
140
  const shellObserver = createGovernanceShellGateMarkPolicy(marksOf);
372
141
  overrides.toolPolicy = governancePolicy === undefined ? shellObserver : combinePolicies(governancePolicy, shellObserver);
373
142
  }
374
143
  if (overrides.toolPolicy === undefined && overrides.handsReadOnly === undefined && overrides.shellGate === undefined) {
375
- return base; // nothing to govern → avoid a needless tightenTaskSpec pass
144
+ return base;
376
145
  }
377
146
  return tightenTaskSpec(base, overrides);
378
147
  }