@sema-agent/core 7.52.0 → 7.54.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 (357) hide show
  1. package/CHANGELOG.md +470 -0
  2. package/LICENSE +3 -1
  3. package/README.md +3 -1
  4. package/dist/agents/cross-session-envelope.d.ts +8 -0
  5. package/dist/agents/cross-session-envelope.js +6 -0
  6. package/dist/agents/cross-session-judge.d.ts +21 -1
  7. package/dist/agents/cross-session-judge.js +8 -0
  8. package/dist/agents/cross-session-ref.d.ts +5 -0
  9. package/dist/agents/cross-session-ref.js +1 -0
  10. package/dist/agents/org-delivery-gate.d.ts +122 -0
  11. package/dist/agents/org-delivery-gate.js +69 -0
  12. package/dist/agents/peer-held-queue.d.ts +9 -4
  13. package/dist/agents/peer-held-queue.js +6 -5
  14. package/dist/agents/peer-session-drain.d.ts +35 -6
  15. package/dist/agents/peer-session-drain.js +271 -182
  16. package/dist/agents/read-inbox-tool.d.ts +7 -0
  17. package/dist/agents/read-inbox-tool.js +5 -1
  18. package/dist/agents/send-message-tool.js +3 -4
  19. package/dist/agents/subagent.d.ts +6 -5
  20. package/dist/agents/subagent.js +117 -83
  21. package/dist/agents/tool-filter.d.ts +25 -4
  22. package/dist/agents/tool-filter.js +13 -3
  23. package/dist/brain/anthropic.d.ts +6 -1
  24. package/dist/brain/anthropic.js +4 -4
  25. package/dist/brain/model-presets.js +2 -1
  26. package/dist/brain/route-adjudicator.d.ts +6 -0
  27. package/dist/brain/routing.d.ts +24 -1
  28. package/dist/brain/routing.js +54 -19
  29. package/dist/brain/timeout.js +5 -4
  30. package/dist/core/a2a.d.ts +5 -2
  31. package/dist/core/a2a.js +33 -14
  32. package/dist/core/allow-entry.d.ts +3 -1
  33. package/dist/core/allow-entry.js +3 -2
  34. package/dist/core/approval-object.d.ts +87 -0
  35. package/dist/core/approval-object.js +85 -0
  36. package/dist/core/arg-summary.d.ts +3 -2
  37. package/dist/core/arg-summary.js +17 -11
  38. package/dist/core/ask-origin.d.ts +43 -27
  39. package/dist/core/ask-origin.js +20 -11
  40. package/dist/core/ask-question.d.ts +0 -1
  41. package/dist/core/ask-question.js +4 -67
  42. package/dist/core/ask-unresolvable-notice.d.ts +3 -2
  43. package/dist/core/auto-compaction.d.ts +106 -50
  44. package/dist/core/auto-compaction.js +69 -53
  45. package/dist/core/auto-mode-prompt.d.ts +4 -2
  46. package/dist/core/auto-mode-prompt.js +4 -3
  47. package/dist/core/canonical-json.d.ts +69 -0
  48. package/dist/core/canonical-json.js +130 -8
  49. package/dist/core/checkpoint-requirements.d.ts +13 -7
  50. package/dist/core/checkpoint-requirements.js +4 -1
  51. package/dist/core/checkpoint-store.d.ts +60 -36
  52. package/dist/core/checkpoint-store.js +7 -6
  53. package/dist/core/child-clamp-seats.d.ts +45 -6
  54. package/dist/core/child-clamp-seats.js +56 -4
  55. package/dist/core/compliance.js +3 -1
  56. package/dist/core/consolidate-scope.d.ts +1 -1
  57. package/dist/core/cross-boundary-taint.d.ts +213 -0
  58. package/dist/core/cross-boundary-taint.js +196 -0
  59. package/dist/core/decision-receipt.d.ts +44 -1
  60. package/dist/core/decision-receipt.js +16 -0
  61. package/dist/core/effective-path-target.d.ts +28 -6
  62. package/dist/core/effective-path-target.js +28 -2
  63. package/dist/core/engine-notice.d.ts +47 -4
  64. package/dist/core/engine-notice.js +17 -1
  65. package/dist/core/engine-storage-backend.d.ts +73 -0
  66. package/dist/core/engine-storage-backend.js +1 -0
  67. package/dist/core/file-history-store.d.ts +6 -2
  68. package/dist/core/file-history-store.js +1 -0
  69. package/dist/core/gate-fold.js +13 -4
  70. package/dist/core/gate-lanes.js +41 -34
  71. package/dist/core/gate-outcome.d.ts +18 -9
  72. package/dist/core/governance-codes.d.ts +9 -2
  73. package/dist/core/governance-codes.js +9 -0
  74. package/dist/core/hands-band-screen.d.ts +7 -0
  75. package/dist/core/hands-band-screen.js +32 -0
  76. package/dist/core/hooks.d.ts +21 -19
  77. package/dist/core/hooks.js +6 -5
  78. package/dist/core/locked-config.d.ts +110 -31
  79. package/dist/core/locked-config.js +155 -42
  80. package/dist/core/lsp.d.ts +1 -1
  81. package/dist/core/mcp-probe.js +1 -1
  82. package/dist/core/mcp-server-spec.d.ts +152 -17
  83. package/dist/core/mcp-server-spec.js +223 -40
  84. package/dist/core/mcp.d.ts +21 -14
  85. package/dist/core/mcp.js +59 -54
  86. package/dist/core/memory-engine/engine.d.ts +14 -19
  87. package/dist/core/memory-engine/engine.js +9 -6
  88. package/dist/core/memory-engine/export-bundle.d.ts +1 -1
  89. package/dist/core/memory-engine/export-bundle.js +1 -1
  90. package/dist/core/memory-engine/file-backend.d.ts +2 -6
  91. package/dist/core/memory-engine/file-backend.js +1 -12
  92. package/dist/core/memory.d.ts +3 -3
  93. package/dist/core/mode-policy.d.ts +16 -26
  94. package/dist/core/mode-policy.js +5 -18
  95. package/dist/core/mods/kernel/dispatch.js +13 -15
  96. package/dist/core/mods/kernel/events.d.ts +9 -6
  97. package/dist/core/mods/kernel/events.js +2 -2
  98. package/dist/core/mods/kernel/receive.d.ts +41 -11
  99. package/dist/core/mods/kernel/receive.js +124 -54
  100. package/dist/core/mods/kernel/site-rules.d.ts +80 -15
  101. package/dist/core/mods/kernel/site-rules.js +232 -40
  102. package/dist/core/mods/stations.d.ts +0 -6
  103. package/dist/core/mods/stations.js +0 -7
  104. package/dist/core/mounted-mcp.d.ts +57 -0
  105. package/dist/core/mounted-mcp.js +71 -2
  106. package/dist/core/own-property-receipt.d.ts +13 -0
  107. package/dist/core/own-property-receipt.js +3 -1
  108. package/dist/core/parent-spec-seat.d.ts +12 -15
  109. package/dist/core/parent-spec-seat.js +14 -6
  110. package/dist/core/pause-registry.d.ts +35 -1
  111. package/dist/core/pause-registry.js +10 -0
  112. package/dist/core/permission-rule-consent.d.ts +18 -5
  113. package/dist/core/permission-rule-consent.js +13 -8
  114. package/dist/core/permission-rule-model.d.ts +87 -15
  115. package/dist/core/permission-rule-model.js +87 -8
  116. package/dist/core/permission-rule-org.d.ts +200 -33
  117. package/dist/core/permission-rule-org.js +217 -91
  118. package/dist/core/permission-rule-session.js +2 -2
  119. package/dist/core/permission-rule-store.d.ts +8 -0
  120. package/dist/core/permission-rule-store.js +1 -0
  121. package/dist/core/permission-rule-sync.js +3 -2
  122. package/dist/core/permission-rules.d.ts +7 -1
  123. package/dist/core/permission-rules.js +37 -8
  124. package/dist/core/read-only-shell.d.ts +1 -1
  125. package/dist/core/read-only-shell.js +30 -9
  126. package/dist/core/remote-env.d.ts +41 -1
  127. package/dist/core/remote-env.js +18 -0
  128. package/dist/core/resume-startup-cause.d.ts +18 -0
  129. package/dist/core/resume-startup-cause.js +12 -0
  130. package/dist/core/retention.js +7 -2
  131. package/dist/core/retired-keys.d.ts +8 -2
  132. package/dist/core/retired-keys.js +12 -1
  133. package/dist/core/roles.js +60 -10
  134. package/dist/core/rule-lane-reads.js +1 -1
  135. package/dist/core/runner/approval-feedback.d.ts +46 -0
  136. package/dist/core/runner/approval-feedback.js +41 -0
  137. package/dist/core/runner/compaction-call-options.d.ts +3 -3
  138. package/dist/core/runner/compaction-call-options.js +2 -2
  139. package/dist/core/runner/contracts.d.ts +34 -41
  140. package/dist/core/runner/cross-boundary-lane.d.ts +87 -0
  141. package/dist/core/runner/cross-boundary-lane.js +190 -0
  142. package/dist/core/runner/decide-continuation.d.ts +4 -4
  143. package/dist/core/runner/decide-continuation.js +3 -1
  144. package/dist/core/runner/gate-exit.d.ts +32 -12
  145. package/dist/core/runner/gate-exit.js +77 -34
  146. package/dist/core/runner/inherited-ask-grants.js +2 -2
  147. package/dist/core/runner/mcp-redial.d.ts +52 -10
  148. package/dist/core/runner/mcp-redial.js +38 -9
  149. package/dist/core/runner/permission-rule-lanes.d.ts +5 -2
  150. package/dist/core/runner/permission-rule-lanes.js +22 -10
  151. package/dist/core/runner/person-path-rows.d.ts +5 -3
  152. package/dist/core/runner/person-path-rows.js +2 -2
  153. package/dist/core/runner/prepare-acquire-reconcile.d.ts +3 -4
  154. package/dist/core/runner/prepare-acquire-reconcile.js +3 -3
  155. package/dist/core/runner/prepare-ask-lane.js +3 -3
  156. package/dist/core/runner/prepare-caps-and-workflow.d.ts +6 -5
  157. package/dist/core/runner/prepare-caps-and-workflow.js +3 -3
  158. package/dist/core/runner/prepare-config-doors.d.ts +58 -15
  159. package/dist/core/runner/prepare-config-doors.js +167 -146
  160. package/dist/core/runner/prepare-context-lane.js +6 -3
  161. package/dist/core/runner/prepare-defer-classify.js +4 -7
  162. package/dist/core/runner/prepare-delegation-surface.d.ts +5 -4
  163. package/dist/core/runner/prepare-delegation-surface.js +8 -7
  164. package/dist/core/runner/prepare-execution-env.d.ts +1 -2
  165. package/dist/core/runner/prepare-execution-env.js +2 -1
  166. package/dist/core/runner/prepare-gate-stations.d.ts +2 -0
  167. package/dist/core/runner/prepare-gate-stations.js +4 -3
  168. package/dist/core/runner/prepare-hands-readface.d.ts +3 -3
  169. package/dist/core/runner/prepare-hands-readface.js +3 -2
  170. package/dist/core/runner/prepare-inherited-gate.d.ts +2 -0
  171. package/dist/core/runner/prepare-inherited-gate.js +3 -2
  172. package/dist/core/runner/prepare-listings.d.ts +1 -1
  173. package/dist/core/runner/prepare-listings.js +4 -2
  174. package/dist/core/runner/prepare-memory-engine-session.d.ts +0 -6
  175. package/dist/core/runner/prepare-memory-engine-session.js +19 -6
  176. package/dist/core/runner/prepare-memory.d.ts +3 -2
  177. package/dist/core/runner/prepare-memory.js +6 -2
  178. package/dist/core/runner/prepare-offload-wrappers.d.ts +4 -4
  179. package/dist/core/runner/prepare-offload-wrappers.js +2 -1
  180. package/dist/core/runner/prepare-park-ask.d.ts +1 -1
  181. package/dist/core/runner/prepare-park-ask.js +9 -3
  182. package/dist/core/runner/prepare-policy-chain.d.ts +1 -1
  183. package/dist/core/runner/prepare-policy-chain.js +14 -19
  184. package/dist/core/runner/prepare-project-context.d.ts +1 -1
  185. package/dist/core/runner/prepare-project-context.js +6 -4
  186. package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
  187. package/dist/core/runner/prepare-prompt-assembly.js +20 -16
  188. package/dist/core/runner/prepare-prompt-inputs.d.ts +12 -6
  189. package/dist/core/runner/prepare-prompt-inputs.js +6 -3
  190. package/dist/core/runner/prepare-protocol-tools.d.ts +7 -5
  191. package/dist/core/runner/prepare-protocol-tools.js +19 -12
  192. package/dist/core/runner/prepare-question-face.d.ts +4 -2
  193. package/dist/core/runner/prepare-question-face.js +4 -3
  194. package/dist/core/runner/prepare-safety-scan.d.ts +7 -2
  195. package/dist/core/runner/prepare-safety-scan.js +34 -27
  196. package/dist/core/runner/prepare-suspend-saga.d.ts +2 -2
  197. package/dist/core/runner/prepare-suspend-saga.js +6 -2
  198. package/dist/core/runner/prepare-task.js +22 -19
  199. package/dist/core/runner/prepare-turn-wiring.d.ts +10 -5
  200. package/dist/core/runner/prepare-turn-wiring.js +14 -49
  201. package/dist/core/runner/prepare-wiring-manifest.d.ts +10 -5
  202. package/dist/core/runner/prepare-wiring-manifest.js +9 -6
  203. package/dist/core/runner/prepare-workspace-restore.js +2 -2
  204. package/dist/core/runner/resume-admission.d.ts +4 -1
  205. package/dist/core/runner/resume-admission.js +16 -14
  206. package/dist/core/runner/resume-apply.js +4 -4
  207. package/dist/core/runner/resume-internals-and-config.d.ts +3 -3
  208. package/dist/core/runner/resume-internals-and-config.js +3 -8
  209. package/dist/core/runner/resume-policy-outcome.d.ts +2 -2
  210. package/dist/core/runner/resume-policy-outcome.js +18 -15
  211. package/dist/core/runner/resume-preflight.js +2 -2
  212. package/dist/core/runner/resume-review-outcome.js +3 -3
  213. package/dist/core/runner/retired-runner-deps.d.ts +5 -6
  214. package/dist/core/runner/retired-runner-deps.js +1 -4
  215. package/dist/core/runner/run-leg.js +97 -43
  216. package/dist/core/runner/run-recovery-lanes.js +5 -3
  217. package/dist/core/runner/run-settle-and-teardown.js +2 -0
  218. package/dist/core/runner/run-terminal-adoption.js +21 -9
  219. package/dist/core/runner/run-turn-boundary.js +9 -3
  220. package/dist/core/runner/runtask.js +1 -1
  221. package/dist/core/runner/session-file-state-replay.d.ts +5 -2
  222. package/dist/core/runner/session-file-state-replay.js +3 -2
  223. package/dist/core/runner/skills-seat.d.ts +15 -0
  224. package/dist/core/runner/skills-seat.js +57 -0
  225. package/dist/core/runner/stream-steer-verb.js +2 -2
  226. package/dist/core/runner/sub-agent-deny-lists.d.ts +13 -11
  227. package/dist/core/runner/sub-agent-deny-lists.js +7 -5
  228. package/dist/core/runner/synthetic-tools.d.ts +5 -2
  229. package/dist/core/runner/synthetic-tools.js +2 -12
  230. package/dist/core/runner/tool-face-overlay.d.ts +24 -6
  231. package/dist/core/runner/tool-face-overlay.js +24 -15
  232. package/dist/core/runner-deps.d.ts +37 -22
  233. package/dist/core/session-policy-store.d.ts +19 -0
  234. package/dist/core/session-policy-store.js +22 -2
  235. package/dist/core/session-store.d.ts +35 -16
  236. package/dist/core/session-store.js +20 -12
  237. package/dist/core/{shell-lexer.d.ts → shell-run-candidates.d.ts} +36 -12
  238. package/dist/core/{shell-lexer.js → shell-run-candidates.js} +145 -225
  239. package/dist/core/shell-wrapper-table.d.ts +5 -3
  240. package/dist/core/shell-wrapper-table.js +1 -1
  241. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
  242. package/dist/core/store-contracts/mailbox-store-contract.js +13 -0
  243. package/dist/core/store-contracts/session-store-contract.d.ts +49 -0
  244. package/dist/core/store-contracts/session-store-contract.js +639 -0
  245. package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +12 -0
  246. package/dist/core/store-contracts/session-store-incarnation-contract.js +5 -2
  247. package/dist/core/task-event.d.ts +44 -6
  248. package/dist/core/task-notification.js +2 -2
  249. package/dist/core/task-registry-agent.js +1 -0
  250. package/dist/core/task-registry-shared.d.ts +5 -0
  251. package/dist/core/task-registry.d.ts +7 -0
  252. package/dist/core/task-registry.js +17 -0
  253. package/dist/core/task-result.d.ts +1 -1
  254. package/dist/core/task-spec.d.ts +7 -7
  255. package/dist/core/tighten-task-spec.d.ts +6 -4
  256. package/dist/core/tighten-task-spec.js +2 -2
  257. package/dist/core/tool-catalog-entries.d.ts +1 -1
  258. package/dist/core/tool-catalog-entries.js +52 -52
  259. package/dist/core/tool-face-reader.d.ts +49 -0
  260. package/dist/core/tool-face-reader.js +94 -0
  261. package/dist/core/tool-face.d.ts +17 -8
  262. package/dist/core/tool-face.js +1 -0
  263. package/dist/core/tool-policy.d.ts +67 -23
  264. package/dist/core/tool-policy.js +72 -60
  265. package/dist/core/tool-roster.d.ts +46 -28
  266. package/dist/core/tool-roster.js +91 -14
  267. package/dist/core/tool-spec-members.js +2 -2
  268. package/dist/core/tool-spec.d.ts +20 -4
  269. package/dist/core/tools.js +2 -0
  270. package/dist/core/trace.d.ts +24 -2
  271. package/dist/core/types.d.ts +3 -3
  272. package/dist/core/types.js +1 -1
  273. package/dist/core/untrusted-text.d.ts +23 -0
  274. package/dist/core/untrusted-text.js +10 -10
  275. package/dist/core/user-attachment.d.ts +56 -2
  276. package/dist/core/user-attachment.js +139 -56
  277. package/dist/core/wiring-manifest.d.ts +4 -2
  278. package/dist/engine/compaction/compaction.d.ts +2 -2
  279. package/dist/engine/compaction/utils.d.ts +2 -2
  280. package/dist/engine/harness/agent-harness.js +2 -1
  281. package/dist/engine/harness/types.d.ts +10 -4
  282. package/dist/engine/llm/types.d.ts +7 -2
  283. package/dist/engine/llm/types.js +2 -1
  284. package/dist/engine/loop/agent-loop.d.ts +12 -1
  285. package/dist/engine/loop/agent-loop.js +17 -5
  286. package/dist/engine/loop/types.d.ts +7 -1
  287. package/dist/engine/session/repo-utils.d.ts +5 -0
  288. package/dist/engine/session/repo-utils.js +9 -0
  289. package/dist/engine/session/storage-base.js +2 -2
  290. package/dist/index.d.ts +37 -17
  291. package/dist/index.js +31 -14
  292. package/dist/internal/harness.d.ts +1 -0
  293. package/dist/internal/harness.js +1 -0
  294. package/dist/internal/llm.d.ts +1 -1
  295. package/dist/internal/llm.js +1 -1
  296. package/dist/orchestration/goal.d.ts +24 -1
  297. package/dist/orchestration/goal.js +3 -2
  298. package/dist/orchestration/run-workflow-tool.d.ts +3 -0
  299. package/dist/orchestration/run-workflow-tool.js +1 -0
  300. package/dist/orchestration/workflow-governance.d.ts +1 -1
  301. package/dist/orchestration/workflow.d.ts +3 -0
  302. package/dist/orchestration/workflow.js +2 -1
  303. package/dist/prompt-assembly/artifact-store.d.ts +44 -9
  304. package/dist/prompt-assembly/artifact-store.js +38 -12
  305. package/dist/prompt-assembly/artifact.d.ts +12 -11
  306. package/dist/prompt-assembly/artifact.js +52 -21
  307. package/dist/prompt-assembly/assemble.d.ts +2 -0
  308. package/dist/prompt-assembly/assemble.js +2 -1
  309. package/dist/prompt-assembly/catalog/index.js +2 -1
  310. package/dist/prompt-assembly/catalog/memory.d.ts +13 -0
  311. package/dist/prompt-assembly/catalog/memory.js +6 -0
  312. package/dist/prompt-assembly/catalog/schema.d.ts +19 -2
  313. package/dist/prompt-assembly/catalog/schema.js +3 -0
  314. package/dist/prompt-assembly/composer.d.ts +2 -0
  315. package/dist/prompt-assembly/composer.js +1 -1
  316. package/dist/prompt-assembly/overlay.d.ts +21 -11
  317. package/dist/prompt-assembly/overlay.js +57 -19
  318. package/dist/prompt-assembly/select.d.ts +21 -6
  319. package/dist/prompt-assembly/select.js +41 -4
  320. package/dist/prompts/default.d.ts +1 -12
  321. package/dist/prompts/default.js +0 -3
  322. package/dist/shell/lex.d.ts +87 -8
  323. package/dist/shell/lex.js +283 -49
  324. package/dist/stores/file/index.d.ts +8 -6
  325. package/dist/stores/file/permission-rule-store.d.ts +1 -1
  326. package/dist/stores/file/permission-rule-store.js +2 -1
  327. package/dist/tools/artifact/artifact-tool.js +14 -3
  328. package/dist/tools/fs/bash-program-position.d.ts +3 -15
  329. package/dist/tools/fs/bash-program-position.js +3 -10
  330. package/dist/tools/fs/bash-readonly-classifier.js +2 -0
  331. package/dist/tools/fs/fs-bash.d.ts +3 -3
  332. package/dist/tools/fs/fs-bash.js +72 -101
  333. package/dist/tools/fs/fs-read.d.ts +12 -0
  334. package/dist/tools/fs/fs-read.js +10 -9
  335. package/dist/tools/fs/fs-shared.d.ts +27 -31
  336. package/dist/tools/fs/fs-shared.js +12 -15
  337. package/dist/tools/fs/fs-write.js +1 -1
  338. package/dist/tools/fs/index.js +2 -2
  339. package/dist/tools/fs/read-deny-home.d.ts +1 -1
  340. package/dist/tools/fs/read-deny-home.js +5 -4
  341. package/dist/tools/fs/read-deny.d.ts +16 -3
  342. package/dist/tools/fs/read-deny.js +12 -9
  343. package/dist/tools/fs/safety.d.ts +7 -6
  344. package/dist/tools/fs/shell-read-bindings.d.ts +5 -3
  345. package/dist/tools/fs/shell-read-bindings.js +15 -16
  346. package/dist/tools/fs/shell-read-device.js +1 -1
  347. package/dist/tools/fs/shell-read-plan.js +36 -47
  348. package/dist/tools/fs/shell-read-word.d.ts +1 -6
  349. package/dist/tools/fs/shell-read-word.js +2 -20
  350. package/dist/tools/fs/shell-reentry-text.js +1 -1
  351. package/dist/tools/web.d.ts +2 -2
  352. package/package.json +4 -1
  353. package/test/export-surface.snapshot.json +5651 -5457
  354. package/wire-changes/core@7.53.0.json +1968 -0
  355. package/wire-changes/core@7.54.0.json +2870 -0
  356. package/dist/core/shell-scan.d.ts +0 -60
  357. package/dist/core/shell-scan.js +0 -183
@@ -1,5 +1,11 @@
1
- import { escapeForDisclosure, parseRuleText, programRunReachOf, ruleReachesProgramRun } from "./permission-rule-model.js";
1
+ import { COMMAND_RULE_TOOL, PATH_RULE_FACES, escapeForDisclosure, parseRuleText, programRunReachOf, ruleReachesProgramRun, ruleToolGrammarOf } from "./permission-rule-model.js";
2
+ import { isNameableToolName, ruleSpellingOf } from "./permission-rule-syntax.js";
3
+ import { isShellFaceToolName, pathTargetOf } from "./tool-registry.js";
2
4
  import { writerOf } from "./permission-rule-store.js";
5
+ import { canonicalize } from "./canonical-json.js";
6
+ import { types } from "node:util";
7
+ import { describeFault, receive } from "./mods/kernel/receive.js";
8
+ import { DEFAULT_CAPS } from "./permission-rules.js";
3
9
  export const ORG_UNAVAILABLE_DECISION_REASON = "org_unavailable";
4
10
  export const ORG_ADJUDICATION_TIMEOUT_MS = 15_000;
5
11
  export function settleOrgVerdictWithin(p, fallback, opts) {
@@ -48,7 +54,7 @@ export function createOrgRulePartition(cfg) {
48
54
  let memoryState;
49
55
  const readState = async () => {
50
56
  const state = cfg.persistence !== undefined ? await cfg.persistence.readOrgState() : memoryState;
51
- return state === undefined ? undefined : structuredClone(state);
57
+ return state === undefined ? undefined : orgStateCopyOf(state);
52
58
  };
53
59
  const writeState = async (state) => {
54
60
  if (cfg.persistence !== undefined)
@@ -75,7 +81,7 @@ export function createOrgRulePartition(cfg) {
75
81
  throw new Error(`the org snapshot source did not answer within ${ORG_ADJUDICATION_TIMEOUT_MS}ms`);
76
82
  if ("e" in settled)
77
83
  throw settled.e;
78
- fetched = structuredClone(settled.v);
84
+ fetched = settled.v === undefined ? undefined : orgSnapshotCopyOf(settled.v);
79
85
  }
80
86
  catch (err) {
81
87
  fetchFailed = true;
@@ -96,29 +102,30 @@ export function createOrgRulePartition(cfg) {
96
102
  const persisted = await guardedRead("persistence");
97
103
  if (fetched === undefined)
98
104
  return persisted;
99
- const invalid = validateOrgSnapshot(fetched, nowMs);
100
- if (invalid !== undefined) {
101
- disclosures.push(`org snapshot refused: ${invalid}; the previously installed snapshot (if any) remains in force`);
105
+ const judged = validateOrgSnapshot(fetched, nowMs);
106
+ if ("refused" in judged) {
107
+ disclosures.push(`org snapshot refused: ${judged.refused}; the previously installed snapshot (if any) remains in force`);
102
108
  return persisted;
103
109
  }
110
+ const incoming = judged.admitted;
104
111
  const persistedFloor = persisted === undefined ? undefined : antiRollbackFloor(persisted);
105
112
  if (persisted !== undefined && !persistedMarkIsCoherent(persisted)) {
106
113
  disclosures.push(`the durable org state carries a high-water mark (${escapeForDisclosure(persisted.revisionHighWater)}) that is not a finite number at or above its own snapshot revision (${escapeForDisclosure(persisted.snapshot?.revision)}) — the anti-rollback fence is being held at ${escapeForDisclosure(persistedFloor)} instead, and the durable state should be repaired`);
107
114
  }
108
- if (persistedFloor !== undefined && fetched.revision < persistedFloor) {
109
- disclosures.push(`org snapshot revision ${fetched.revision} is below the installed high-water mark ${escapeForDisclosure(persistedFloor)} — refusing the rollback; the current deny set is unchanged`);
115
+ if (persistedFloor !== undefined && incoming.revision < persistedFloor) {
116
+ disclosures.push(`org snapshot revision ${incoming.revision} is below the installed high-water mark ${escapeForDisclosure(persistedFloor)} — refusing the rollback; the current deny set is unchanged`);
110
117
  return persisted;
111
118
  }
112
- if (persisted !== undefined && fetched.revision === persisted.snapshot.revision && sameOrgPolicyContent(fetched, persisted.snapshot) && fetched.fetchedAtMs <= persisted.snapshot.fetchedAtMs) {
119
+ if (persisted !== undefined && incoming.revision === persisted.snapshot.revision && sameOrgPolicyAsHeld(incoming, persisted.snapshot) && incoming.fetchedAtMs <= persisted.snapshot.fetchedAtMs) {
113
120
  return persisted;
114
121
  }
115
- if (persisted !== undefined && fetched.revision === persisted.snapshot.revision && !sameOrgPolicyContent(fetched, persisted.snapshot)) {
116
- disclosures.push(`org snapshot revision ${fetched.revision} equals the installed revision but carries DIFFERENT policy content — refusing the swap; the current deny set is unchanged`);
122
+ if (persisted !== undefined && incoming.revision === persisted.snapshot.revision && !sameOrgPolicyAsHeld(incoming, persisted.snapshot)) {
123
+ disclosures.push(`org snapshot revision ${incoming.revision} equals the installed revision but carries DIFFERENT policy content — refusing the swap; the current deny set is unchanged`);
117
124
  return persisted;
118
125
  }
119
126
  const state = {
120
- revisionHighWater: Math.max(persistedFloor !== undefined && Number.isFinite(persistedFloor) ? persistedFloor : 0, fetched.revision),
121
- snapshot: fetched,
127
+ revisionHighWater: Math.max(persistedFloor !== undefined && Number.isFinite(persistedFloor) ? persistedFloor : 0, incoming.revision),
128
+ snapshot: incoming,
122
129
  installedAtMs: nowMs,
123
130
  };
124
131
  try {
@@ -142,21 +149,25 @@ export function createOrgRulePartition(cfg) {
142
149
  disclosures.push(`the installed org snapshot claims a FUTURE observation time (${escapeForDisclosure(installed.snapshot.fetchedAtMs)} vs now ${decisionMs}) — org adjudication is UNAVAILABLE; the consuming gate must tighten every terminal allow to a real-approval ask`);
143
150
  return { source: "org", status: "unavailable", rules: [], disclosures };
144
151
  }
145
- const servingRefusal = validateOrgSnapshot(installed.snapshot, decisionMs);
146
- if (servingRefusal !== undefined) {
147
- disclosures.push(`the installed org snapshot (revision ${escapeForDisclosure(installed.snapshot.revision)}) does not meet this version's validator: ${servingRefusal} — org adjudication is UNAVAILABLE; the consuming gate must tighten every terminal allow to a real-approval ask`);
152
+ const serving = validateOrgSnapshot(installed.snapshot, decisionMs);
153
+ if ("refused" in serving) {
154
+ disclosures.push(`the installed org snapshot (revision ${escapeForDisclosure(installed.snapshot.revision)}) does not meet this version's validator: ${serving.refused} — org adjudication is UNAVAILABLE; the consuming gate must tighten every terminal allow to a real-approval ask`);
148
155
  return { source: "org", status: "unavailable", rules: [], disclosures };
149
156
  }
150
- if (withinBound(installed.snapshot)) {
157
+ const served = serving.admitted;
158
+ if (withinBound(served)) {
151
159
  const freshlyInstalled = fetched !== undefined && installed.snapshot === fetched;
152
160
  if (!freshlyInstalled && !fetchFailed && fetched === undefined) {
153
161
  disclosures.push("org snapshot source returned no snapshot — continuing on the last-known-good");
154
162
  }
163
+ const degraded = describeUnreadableOrgRows(readListedOrgRules(served.rules).unreadable, served.revision);
164
+ if (degraded !== undefined)
165
+ disclosures.push(degraded);
155
166
  return {
156
167
  source: "org",
157
168
  status: freshlyInstalled ? "fresh" : "last-known-good",
158
- rules: structuredClone(installed.snapshot.rules),
159
- revision: installed.snapshot.revision,
169
+ rules: structuredClone(served.rules),
170
+ revision: served.revision,
160
171
  disclosures,
161
172
  };
162
173
  }
@@ -188,98 +199,213 @@ function persistedMarkIsCoherent(state) {
188
199
  Number.isFinite(state.snapshot.revision) &&
189
200
  state.revisionHighWater >= state.snapshot.revision);
190
201
  }
191
- function sameOrgPolicyContent(a, b) {
202
+ export function sameOrgPolicyContent(a, b) {
192
203
  if (a.rules.length !== b.rules.length)
193
204
  return false;
194
- return a.rules.every((r, i) => b.rules[i]?.rule === r.rule && b.rules[i]?.behavior === r.behavior);
205
+ const policy = (r) => canonicalize(orgRowPolicyOf(r));
206
+ return a.rules.every((r, i) => policy(b.rules[i]) === policy(r));
195
207
  }
196
- const DISCLOSED_UNENFORCEABLE_RULES = 20;
197
- export function unenforceableOrgRules(rules) {
198
- const out = [];
199
- for (const r of rules) {
200
- if (typeof r?.rule !== "string" || r.rule === "")
201
- continue;
202
- const parsed = parseRuleText(r.rule, r.behavior);
203
- if ("reject" in parsed) {
204
- out.push({ rule: r.rule, code: parsed.reject.code });
205
- continue;
206
- }
207
- if (parsed.rule.tool !== "Bash")
208
- out.push({ rule: r.rule, code: "unsupported.tool" });
208
+ function sameOrgPolicyAsHeld(a, b) {
209
+ return !("refused" in b) && sameOrgPolicyContent(a, b);
210
+ }
211
+ const DISCLOSED_UNREADABLE_ROWS = 20;
212
+ function reachOfRowHead(text, behavior) {
213
+ const head = typeof text === "string" ? ruleSpellingOf(text)?.toolName : undefined;
214
+ if (head === undefined || !isNameableToolName(head))
215
+ return "every";
216
+ if (head === COMMAND_RULE_TOOL || isShellFaceToolName(head))
217
+ return { words: [COMMAND_RULE_TOOL], faces: [] };
218
+ if (ruleToolGrammarOf(head) === undefined)
219
+ return "every";
220
+ const column = Object.hasOwn(PATH_RULE_FACES, head) ? PATH_RULE_FACES[head]?.[behavior] : undefined;
221
+ return { words: [head], faces: column ?? [] };
222
+ }
223
+ function rowReaches(reach, words, face) {
224
+ if (reach === "every")
225
+ return true;
226
+ return reach.words.some((w) => words.includes(w)) || (face !== undefined && reach.faces.includes(face));
227
+ }
228
+ function listRefusalOf(rules) {
229
+ if (typeof rules === "object" && rules !== null && types.isProxy(rules))
230
+ return "rules is a Proxy, not an array";
231
+ if (!Array.isArray(rules))
232
+ return "rules is not an array";
233
+ const cap = DEFAULT_CAPS.maxRules;
234
+ return rules.length > cap ? `rules list has ${rules.length} entries; the cap is ${cap} — refused whole (not truncated)` : undefined;
235
+ }
236
+ export const ORG_ROW_POLICY_MEMBERS = Object.freeze(["rule", "behavior"]);
237
+ const ORG_SNAPSHOT_ENVELOPE_MEMBERS = Object.freeze(["revision", "fetchedAtMs", "signature"]);
238
+ const ORG_STATE_MEMBERS = Object.freeze(["revisionHighWater", "installedAtMs"]);
239
+ function scalarMembersOf(value, members) {
240
+ const held = {};
241
+ if (typeof value !== "object" || value === null)
242
+ return held;
243
+ for (const k of members) {
244
+ const v = Object.hasOwn(value, k) ? value[k] : undefined;
245
+ if (typeof v === "string" || typeof v === "boolean" || v === null || (typeof v === "number" && Number.isFinite(v)))
246
+ held[k] = v;
209
247
  }
210
- return out;
248
+ return held;
249
+ }
250
+ function orgRowPolicyOf(row) {
251
+ return scalarMembersOf(row, ORG_ROW_POLICY_MEMBERS);
211
252
  }
212
- function describeUnenforceable(unenforceable) {
213
- const named = unenforceable
214
- .slice(0, DISCLOSED_UNENFORCEABLE_RULES)
215
- .map((u) => `"${escapeForDisclosure(u.rule)}" (${u.code})`)
253
+ const ORG_RULES_DEPTH = 2;
254
+ const ORG_RULES_RECEIPT = Object.freeze({ accessors: "call-once", depth: ORG_RULES_DEPTH, others: "copy", contain: true, isProxy: (v) => types.isProxy(v) });
255
+ export function orgRuleListOf(rules) {
256
+ const refused = listRefusalOf(rules);
257
+ if (refused !== undefined)
258
+ return { refused };
259
+ const received = receive(rules, ORG_RULES_RECEIPT);
260
+ if (!received.ok)
261
+ return { refused: `rules could not be received (${describeFault(received.fault)})` };
262
+ const copy = received.value;
263
+ return { rows: Array.from({ length: copy.length }, (_, i) => orgRowPolicyOf(Object.hasOwn(copy, i) ? copy[i] : undefined)) };
264
+ }
265
+ export function orgSnapshotCopyOf(v) {
266
+ if (typeof v === "object" && v !== null && types.isProxy(v))
267
+ throw new Error("the snapshot is a Proxy, not data");
268
+ const envelope = scalarMembersOf(v, ORG_SNAPSHOT_ENVELOPE_MEMBERS);
269
+ const rules = typeof v === "object" && v !== null ? v.rules : undefined;
270
+ const refused = listRefusalOf(rules);
271
+ if (refused !== undefined)
272
+ return { ...envelope, refused };
273
+ const list = orgRuleListOf(rules);
274
+ if ("refused" in list)
275
+ throw new Error(`the snapshot's ${list.refused}`);
276
+ return { ...envelope, rules: list.rows };
277
+ }
278
+ function orgStateCopyOf(state) {
279
+ if (typeof state === "object" && state !== null && types.isProxy(state))
280
+ throw new Error("the persisted org state is a Proxy, not data");
281
+ const snapshot = typeof state === "object" && state !== null ? state.snapshot : undefined;
282
+ return { ...scalarMembersOf(state, ORG_STATE_MEMBERS), snapshot: orgSnapshotCopyOf(snapshot ?? null) };
283
+ }
284
+ function readOrgRow(row, index) {
285
+ const { rule: text, behavior: said } = orgRowPolicyOf(row);
286
+ const behavior = said === "ask" ? "ask" : "deny";
287
+ const unreadable = (code, detail) => ({
288
+ kind: "unreadable",
289
+ index,
290
+ rule: typeof text === "string" ? text : text === undefined ? "<unreadable>" : escapeForDisclosure(text),
291
+ code,
292
+ ...(detail !== undefined ? { detail } : {}),
293
+ behavior,
294
+ reach: reachOfRowHead(text, behavior),
295
+ });
296
+ if (typeof text !== "string" || text === "")
297
+ return unreadable("invalid.grammar", "no rule text");
298
+ if (said !== "deny" && said !== "ask")
299
+ return unreadable("invalid.grammar", said === undefined ? "no behavior" : `behavior "${escapeForDisclosure(said)}" is not deny or ask`);
300
+ const parsed = parseRuleText(text, said);
301
+ if ("reject" in parsed)
302
+ return unreadable(parsed.reject.code);
303
+ if (parsed.rule.tool !== COMMAND_RULE_TOOL)
304
+ return unreadable("unsupported.tool");
305
+ return { kind: "read", index, rule: text, behavior: said, parsed: parsed.rule };
306
+ }
307
+ export function readOrgRules(rules) {
308
+ const list = orgRuleListOf(rules);
309
+ if ("refused" in list)
310
+ return list;
311
+ const rows = list.rows.map((row, i) => readOrgRow(row, i + 1));
312
+ const unreadable = rows.filter((r) => r.kind === "unreadable");
313
+ return {
314
+ rows,
315
+ unreadable,
316
+ verdictFor(call) {
317
+ const words = typeof call.tool === "string" ? [call.tool] : call.tool;
318
+ const command = words.includes(COMMAND_RULE_TOOL) && typeof call.command === "string" ? call.command : undefined;
319
+ let ask;
320
+ let unjudged;
321
+ for (const row of rows) {
322
+ if (row.kind === "unreadable") {
323
+ if (!rowReaches(row.reach, words, call.face))
324
+ continue;
325
+ const verdict = { behavior: row.behavior, rule: row.rule, unreadable: unreadableOrgRowReason(row) };
326
+ if (row.behavior === "deny")
327
+ return verdict;
328
+ ask ??= verdict;
329
+ continue;
330
+ }
331
+ if (command === undefined)
332
+ continue;
333
+ const outcome = programRunReachOf(row.parsed, command);
334
+ if (outcome.reach === "not_reached")
335
+ continue;
336
+ if (outcome.reach === "unreadable") {
337
+ unjudged ??= { behavior: "ask", rule: row.rule, unreadable: outcome.reason };
338
+ continue;
339
+ }
340
+ if (row.behavior === "deny")
341
+ return { behavior: "deny", rule: row.rule };
342
+ ask ??= { behavior: "ask", rule: row.rule };
343
+ }
344
+ return unjudged ?? ask;
345
+ },
346
+ };
347
+ }
348
+ export function unenforceableOrgRules(rules) {
349
+ return readListedOrgRules(rules).unreadable.map((u) => ({ rule: u.rule, code: u.code }));
350
+ }
351
+ function readListedOrgRules(rules) {
352
+ const reading = readOrgRules(rules);
353
+ if ("refused" in reading)
354
+ throw new TypeError(`the org rules cannot be read: ${reading.refused}`);
355
+ return reading;
356
+ }
357
+ export function unreadableOrgRowReason(row) {
358
+ return `row ${row.index} of the organization snapshot is not a rule this layer reads (${row.code}${row.detail !== undefined ? `: ${row.detail}` : ""}); republish it in the shell tool's command grammar`;
359
+ }
360
+ export function orgUnreadableClause(reason) {
361
+ return `which could not be read against it (${reason})`;
362
+ }
363
+ export function describeUnreadableOrgRows(unreadable, revision) {
364
+ if (unreadable.length === 0)
365
+ return undefined;
366
+ const named = unreadable
367
+ .slice(0, DISCLOSED_UNREADABLE_ROWS)
368
+ .map((u) => `row ${u.index} "${escapeForDisclosure(u.rule)}" (${u.code}${u.detail !== undefined ? `: ${u.detail}` : ""})`)
216
369
  .join(", ");
217
- const more = unenforceable.length - DISCLOSED_UNENFORCEABLE_RULES;
218
- return more > 0 ? `${named} and ${more} more` : named;
370
+ const more = unreadable.length - DISCLOSED_UNREADABLE_ROWS;
371
+ return (`the org snapshot${revision !== undefined ? ` (revision ${escapeForDisclosure(revision)})` : ""} is served DEGRADED: ${unreadable.length} row(s) this layer cannot read — ${named}${more > 0 ? ` and ${more} more` : ""}; ` +
372
+ `each stands at its strictest reading over the calls it may reach (a deny row, or one whose behavior cannot be read, refuses them; an ask row asks a person), and every readable row stands as published — republish the named rows in the shell tool's command grammar`);
219
373
  }
220
- function validateOrgSnapshot(s, nowMs) {
374
+ export function validateOrgSnapshot(s, nowMs) {
221
375
  if (typeof s.revision !== "number" || !Number.isFinite(s.revision))
222
- return `revision is not a finite number (${escapeForDisclosure(s.revision)})`;
376
+ return { refused: `revision is not a finite number (${escapeForDisclosure(s.revision)})` };
223
377
  if (typeof s.fetchedAtMs !== "number" || !Number.isFinite(s.fetchedAtMs))
224
- return `fetchedAtMs is not a finite number (${escapeForDisclosure(s.fetchedAtMs)})`;
378
+ return { refused: `fetchedAtMs is not a finite number (${escapeForDisclosure(s.fetchedAtMs)})` };
225
379
  if (s.fetchedAtMs > nowMs + ORG_FETCHED_AT_SKEW_ALLOWANCE_MS) {
226
- return `fetchedAtMs is ${s.fetchedAtMs - nowMs}ms in the future (allowance ${ORG_FETCHED_AT_SKEW_ALLOWANCE_MS}ms) — a future-dated observation would satisfy the staleness bound indefinitely`;
380
+ return { refused: `fetchedAtMs is ${s.fetchedAtMs - nowMs}ms in the future (allowance ${ORG_FETCHED_AT_SKEW_ALLOWANCE_MS}ms) — a future-dated observation would satisfy the staleness bound indefinitely` };
227
381
  }
228
- if (!Array.isArray(s.rules))
229
- return "rules is not an array";
230
- for (const r of s.rules) {
231
- if (typeof r?.rule !== "string" || r.rule === "")
232
- return "a rule entry carries no rule text";
233
- if (r.behavior !== "deny" && r.behavior !== "ask") {
234
- return `rule "${escapeForDisclosure(r.rule)}" carries behavior "${escapeForDisclosure(r.behavior)}" — the org layer has no allow bucket`;
235
- }
236
- }
237
- const unenforceable = unenforceableOrgRules(s.rules);
238
- const first = unenforceable[0];
239
- if (first !== undefined) {
240
- const rest = unenforceable.length > 1 ? ` (${unenforceable.length} in all — ${describeUnenforceable(unenforceable)})` : "";
241
- return `rule "${escapeForDisclosure(first.rule)}" does not parse (${first.code}) — an unenforceable deny/ask must not stand as policy${rest}`;
242
- }
243
- return undefined;
382
+ return "refused" in s ? { refused: s.refused } : { admitted: s };
244
383
  }
245
384
  export function orgRuleVerdictFor(rules, call) {
246
- let ask;
247
- let unreadable;
248
- for (const r of rules) {
249
- const parsed = parseRuleText(r.rule, r.behavior);
250
- if ("reject" in parsed)
251
- continue;
252
- if (parsed.rule.tool !== call.tool)
253
- continue;
254
- const outcome = programRunReachOf(parsed.rule, call.command);
255
- if (outcome.reach === "not_reached")
256
- continue;
257
- if (outcome.reach === "unreadable") {
258
- unreadable ??= { behavior: "ask", rule: r.rule, unreadable: outcome.reason };
259
- continue;
260
- }
261
- if (r.behavior === "deny")
262
- return { behavior: "deny", rule: r.rule };
263
- ask ??= { behavior: "ask", rule: r.rule };
264
- }
265
- return unreadable ?? ask;
385
+ return readListedOrgRules(rules).verdictFor(call);
266
386
  }
267
387
  export function orgRuleShadows(orgRules, rule) {
268
388
  return compileOrgShadowPredicate(orgRules)(rule);
269
389
  }
270
390
  export function compileOrgShadowPredicate(orgRules) {
271
- const denies = [];
272
- for (const d of orgRules) {
273
- if (d.behavior !== "deny")
274
- continue;
275
- const parsed = parseRuleText(d.rule, "deny");
276
- if (!("reject" in parsed))
277
- denies.push(parsed.rule);
278
- }
391
+ const { rows } = readListedOrgRules(orgRules);
392
+ const denies = rows.flatMap((r) => (r.kind === "read" && r.behavior === "deny" ? [r.parsed] : []));
393
+ const standing = rows.flatMap((r) => (r.kind === "unreadable" && r.behavior === "deny" ? [r.reach] : []));
279
394
  return (rule) => {
280
- if (rule.tool !== "Bash" || denies.length === 0)
395
+ if (standing.length > 0) {
396
+ const word = isShellFaceToolName(rule.tool) ? COMMAND_RULE_TOOL : rule.tool;
397
+ const target = pathTargetOf(rule.tool);
398
+ const faces = Object.hasOwn(PATH_RULE_FACES, rule.tool)
399
+ ? (PATH_RULE_FACES[rule.tool]?.allow ?? [])
400
+ : target !== undefined
401
+ ? [target.access === "read" ? "read" : "write"]
402
+ : [undefined];
403
+ if (standing.some((reach) => faces.some((face) => rowReaches(reach, [word], face))))
404
+ return true;
405
+ }
406
+ if (rule.tool !== COMMAND_RULE_TOOL || denies.length === 0)
281
407
  return false;
282
- return denies.some((d) => d.tool === rule.tool && ruleReachesProgramRun(d, rule.command));
408
+ return denies.some((d) => ruleReachesProgramRun(d, rule.command));
283
409
  };
284
410
  }
285
411
  export function isWritablePermissionRuleStore(store) {
@@ -1,10 +1,10 @@
1
- import { readRowBehavior } from "./permission-rule-model.js";
1
+ import { readRowBehavior, RULE_ADD_ORIGINS } from "./permission-rule-model.js";
2
2
  import { isValidConsentScope, normalizePersistedRule } from "./permission-rule-store.js";
3
3
  function isValidOverlayAdd(add) {
4
4
  const a = add;
5
5
  if (typeof a?.dot?.actor !== "string" || a.dot.actor === "" || typeof a.dot.counter !== "number" || !Number.isFinite(a.dot.counter))
6
6
  return false;
7
- if (a.origin !== "user" && a.origin !== "imported-cc" && a.origin !== "starter")
7
+ if (!RULE_ADD_ORIGINS.some((word) => a.origin === word))
8
8
  return false;
9
9
  return typeof a.createdAt === "string";
10
10
  }
@@ -45,6 +45,7 @@ import type { PersistedRule, RuleAdd, RuleDot, RuleScope, RuleTombstone } from "
45
45
  import { type RuleBehavior } from "./permission-rule-model.js";
46
46
  import type { RuleSyncDropReason, RuleQuarantineReason } from "./governance-codes.js";
47
47
  import type { StoreDurability, StoreFidelity } from "./checkpoint-store.js";
48
+ import type { AssertAllKeysHandled } from "./ask-origin.js";
48
49
  /** One store read: the rules as persisted, their tombstones, and the revision they were read at. */
49
50
  export interface StoredRules {
50
51
  /** Rules with their FULL add sets — tombstoned dots already removed by the backend on read. */
@@ -92,6 +93,13 @@ export type RuleOwner = {
92
93
  } | {
93
94
  kind: "local-owner";
94
95
  };
96
+ /** The owner discriminants as a value: the `kind` words of {@link RuleOwner}, both directions fenced at compile time
97
+ * ({@link RuleOwnerKindsCoverEveryArm} / {@link RuleOwnerArmsCoverEveryKind}). */
98
+ export declare const RULE_OWNER_KINDS: readonly ["principal", "local-owner"];
99
+ /** Compile fence: an arm whose `kind` is not in {@link RULE_OWNER_KINDS} turns the argument into that word. */
100
+ export type RuleOwnerKindsCoverEveryArm = AssertAllKeysHandled<Exclude<RuleOwner["kind"], (typeof RULE_OWNER_KINDS)[number]>>;
101
+ /** Compile fence, the other direction: a word in {@link RULE_OWNER_KINDS} that no arm carries. */
102
+ export type RuleOwnerArmsCoverEveryKind = AssertAllKeysHandled<Exclude<(typeof RULE_OWNER_KINDS)[number], RuleOwner["kind"]>>;
95
103
  /** Do two owners name the same bucket? */
96
104
  export declare function sameRuleOwner(a: RuleOwner, b: RuleOwner): boolean;
97
105
  /** Resolves one owner's durable partition. `undefined` ⇒ a partition that reports zero rules. This is
@@ -3,6 +3,7 @@ import { canonicalize } from "./canonical-json.js";
3
3
  import { escapeForDisclosure, isRuleBehavior, isRuleLive, parseRuleText } from "./permission-rule-model.js";
4
4
  import { thrownText } from "./thrown-value.js";
5
5
  import { validatePath } from "./effective-path-target.js";
6
+ export const RULE_OWNER_KINDS = Object.freeze(["principal", "local-owner"]);
6
7
  export function sameRuleOwner(a, b) {
7
8
  return a.kind === "principal" ? b.kind === "principal" && a.principal === b.principal : b.kind === "local-owner";
8
9
  }
@@ -1,4 +1,4 @@
1
- import { escapeForDisclosure, isRuleBehavior, parseRuleText } from "./permission-rule-model.js";
1
+ import { escapeForDisclosure, isRuleBehavior, parseRuleText, RULE_ADD_ORIGINS } from "./permission-rule-model.js";
2
2
  import { applyTombstones, errText, isProjectScopeRoot, joinFrontiers, joinRuleStates, normalizePersistedRule, readRuleWriteOutcome, ruleSyncVector, sameRuleIdentity, screenRuleSyncState, writerOf, } from "./permission-rule-store.js";
3
3
  import { emitTrace } from "./trace.js";
4
4
  export const PERMISSION_RULE_SYNC_PATH = "/v1/permission-rules/sync";
@@ -273,6 +273,7 @@ function pickScopeCaptured(v) {
273
273
  function pickScope(v) {
274
274
  return pickScopeCaptured(v).scope;
275
275
  }
276
+ const isRuleAddOrigin = (v) => RULE_ADD_ORIGINS.some((word) => v === word);
276
277
  function pickAdd(v) {
277
278
  const a = v;
278
279
  const dot = pickDot(a?.dot);
@@ -280,7 +281,7 @@ function pickAdd(v) {
280
281
  const createdAt = a?.createdAt;
281
282
  if (dot === undefined)
282
283
  return undefined;
283
- if (origin !== "user" && origin !== "imported-cc" && origin !== "starter")
284
+ if (!isRuleAddOrigin(origin))
284
285
  return undefined;
285
286
  if (typeof createdAt !== "string")
286
287
  return undefined;
@@ -47,7 +47,10 @@
47
47
  * The NAME lane is the same shape for the tools of `NAME_RULE_FACES` (the `Skill` tool): a `Skill(<name>)` / `Skill(<prefix>:*)`
48
48
  * deny or ask — the reference's own spelling — compiles through the same validator (the model's name grammar) and is judged on the
49
49
  * one string the call names (`nameRuleSubjectOf`) with `nameRuleAdmits`, the reader the persisted rule store uses. The parameter
50
- * spelling `Skill(skill:<name>)` those tools used to take is retired: refused loudly, its sentence naming the CC form.
50
+ * spelling `Skill(skill:<name>)` those tools used to take is retired: refused loudly, its sentence naming the CC form. A KEYED
51
+ * value tool (`Artifact(action:<word>)`) takes the same lane for content keyed by its own parameter (`nameGrammarReads`); its
52
+ * other arguments stay on the generic `param:pattern` lane. A call of it that names no value for the key as the tool reads it
53
+ * is refused by the key's deny or ask rule (`keyedValueAbsentReason`, the persisted lane's reading).
51
54
  *
52
55
  * 🔴 与 CC 的有意差(design/127 r2):
53
56
  * - CC 参数级 generic 道无 allow lane(gnn 只有 deny/ask)—— 我们同样,且整个 policy 无 allow 臂。
@@ -126,6 +129,9 @@ export interface PermissionRuleCaps {
126
129
  * 的热路径成本上界。 */
127
130
  maxScalarValueChars: number;
128
131
  }
132
+ /** The caps a rule set is held to unless the caller names its own — `maxRules` is the ONE bound on how many rules a set
133
+ * carries, read by the compiler here and by the org layer's list judgment (which refuses a longer list before it is copied). */
134
+ export declare const DEFAULT_CAPS: Readonly<PermissionRuleCaps>;
129
135
  export interface PermissionRulePolicyOptions {
130
136
  /** Invalid/unsupported rules: `"throw"`(default — 部署作者写错要立刻知道)aggregates ALL issues
131
137
  * into one error; `"skip"` drops them (pair with {@link validatePermissionRules} for a report, and/or
@@ -2,9 +2,10 @@ import { attest } from "./tool-policy.js";
2
2
  import { MCP_NAMESPACE, protocolOf } from "./protocol-table.js";
3
3
  import { catalogRuleFaceOf, isShellFaceToolName, pathTargetOf } from "./tool-registry.js";
4
4
  import { indexOfUnescaped, lastIndexOfUnescaped, parsePermissionRule, ruleSpellingOf } from "./permission-rule-syntax.js";
5
- import { COMMAND_RULE_TOOL, NAME_RULE_FACES, PATH_RULE_BASE_LABEL, PATH_RULE_FACES, callPathFaceOf, isUsablePathBase, laneToolNamesOf, nameRuleAdmits, nameRuleSubjectOf, parseRuleText, programRunReachOf, ruleBasesNeeded, ruleToolGrammarOf } from "./permission-rule-model.js";
5
+ import { COMMAND_RULE_TOOL, NAME_RULE_FACES, PATH_RULE_BASE_LABEL, PATH_RULE_FACES, callPathFaceOf, isUsablePathBase, keyedValueAbsentReason, laneToolNamesOf, nameGrammarReads, nameRuleAdmits, nameRuleSubjectOf, parseRuleText, programRunReachOf, ruleBasesNeeded } from "./permission-rule-model.js";
6
6
  import { pathFamilyOf } from "../tools/fs/safety.js";
7
- import { callTargetReadingOf, callTargetViewsOf } from "./effective-path-target.js";
7
+ import { argsAsTheToolReads, callTargetReadingOf, callTargetViewsOf } from "./effective-path-target.js";
8
+ import { canonicalize, UNCANONICALIZABLE } from "./canonical-json.js";
8
9
  import { bindPathRules, judgeRuleRows, policyAnchors, rowsWhereTheyLead } from "./path-rule-binding.js";
9
10
  import { ruleDenialSentence } from "./rule-denial-sentence.js";
10
11
  import { heldValues } from "./tool-name-readings.js";
@@ -24,7 +25,7 @@ function describeBehavior(v) {
24
25
  default: return v === null ? "null" : "an object";
25
26
  }
26
27
  }
27
- const DEFAULT_CAPS = {
28
+ export const DEFAULT_CAPS = {
28
29
  maxRules: 256,
29
30
  maxRuleLength: 512,
30
31
  maxPatternLength: 256,
@@ -164,7 +165,7 @@ function compile(rules, caps, primaryFieldGeneric, ruleFaces, pathBases = {}) {
164
165
  continue;
165
166
  }
166
167
  const face = resolved.face;
167
- if (ruleToolGrammarOf(toolName) === "name") {
168
+ if (nameGrammarReads(toolName, content)) {
168
169
  const parsedName = parseRuleText(text, verb);
169
170
  if ("reject" in parsedName) {
170
171
  bad(text, "invalid.command_rule", `${parsedName.reject.code}: ${parsedName.reject.message}`);
@@ -242,12 +243,16 @@ export function validatePermissionRules(rules, opts) {
242
243
  function isPlainObjectArgs(args) {
243
244
  return typeof args === "object" && args !== null && !Array.isArray(args);
244
245
  }
245
- function matchParamRules(rules, args, maxScalarValueChars) {
246
+ function matchParamRules(rules, req, maxScalarValueChars) {
247
+ const args = req.args;
246
248
  if (!isPlainObjectArgs(args))
247
249
  return undefined;
248
250
  for (const r of rules) {
249
- if (!Object.hasOwn(args, r.param))
251
+ if (!Object.hasOwn(args, r.param)) {
252
+ if (readingAbsorbs(req, r.param, r.pattern))
253
+ return r;
250
254
  continue;
255
+ }
251
256
  const raw = args[r.param];
252
257
  if (raw === undefined || raw === null)
253
258
  continue;
@@ -262,6 +267,17 @@ function matchParamRules(rules, args, maxScalarValueChars) {
262
267
  }
263
268
  return undefined;
264
269
  }
270
+ function readingAbsorbs(req, param, pattern) {
271
+ if (pattern.includes("*"))
272
+ return false;
273
+ try {
274
+ const read = canonicalize(argsAsTheToolReads(req, { ...req.args, [param]: pattern }));
275
+ return read !== UNCANONICALIZABLE && read === canonicalize(req.args);
276
+ }
277
+ catch {
278
+ return false;
279
+ }
280
+ }
265
281
  function ruleDenial(req, ruleText, source) {
266
282
  return ruleDenialSentence({ source: "permission", tool: req.toolName, rule: ruleText, ...(source ? { origin: source } : {}) });
267
283
  }
@@ -387,6 +403,16 @@ export function createPermissionRulePolicy(rules, opts) {
387
403
  }
388
404
  return unreadable;
389
405
  };
406
+ const keylessRefusal = (entry, req) => {
407
+ for (const r of [...entry.content.deny, ...entry.content.ask]) {
408
+ if (r.parsed.match !== "name" || nameRuleSubjectOf(r.parsed.tool, req.args) !== undefined)
409
+ continue;
410
+ const absent = keyedValueAbsentReason(r.parsed.tool);
411
+ if (absent !== undefined)
412
+ return ruleDenialSentence({ source: "permission", tool: req.toolName, rule: r.ruleText, ...(r.source ? { origin: r.source } : {}), detail: absent });
413
+ }
414
+ return undefined;
415
+ };
390
416
  const unreadableAsk = (hit, lane, req) => ({
391
417
  action: "ask",
392
418
  message: `tool "${req.toolName}" needs approval: the call could not be read against the permission rule ${hit.rule.ruleText} (${hit.unreadable}) — a person must decide`,
@@ -433,11 +459,14 @@ export function createPermissionRulePolicy(rules, opts) {
433
459
  return face !== undefined ? attest(denied, req, { pathDenial: { rule: contentDeny.rule.ruleText, view: contentDeny.view, face } }) : denied;
434
460
  }
435
461
  if (entry) {
436
- const paramDeny = matchParamRules(entry.param.deny, req.args, caps.maxScalarValueChars);
462
+ const paramDeny = matchParamRules(entry.param.deny, req, caps.maxScalarValueChars);
437
463
  if (paramDeny) {
438
464
  return { action: "deny", message: ruleDenial(req, paramDeny.ruleText, paramDeny.source) };
439
465
  }
440
466
  }
467
+ const keyless = entry !== undefined ? keylessRefusal(entry, req) : undefined;
468
+ if (keyless !== undefined)
469
+ return { action: "deny", message: keyless };
441
470
  if (contentDeny?.unreadable !== undefined)
442
471
  return unreadableAsk({ rule: contentDeny.rule, unreadable: contentDeny.unreadable }, "content_deny", req);
443
472
  if (entry) {
@@ -458,7 +487,7 @@ export function createPermissionRulePolicy(rules, opts) {
458
487
  return { action: "ask", message: ruleAskMessage(contentAsk.rule.ruleText, contentAsk.rule.source), matchedAskRule: contentAsk.rule.ruleText };
459
488
  }
460
489
  if (entry) {
461
- const paramAsk = matchParamRules(entry.param.ask, req.args, caps.maxScalarValueChars);
490
+ const paramAsk = matchParamRules(entry.param.ask, req, caps.maxScalarValueChars);
462
491
  if (paramAsk) {
463
492
  return { action: "ask", message: ruleAskMessage(paramAsk.ruleText, paramAsk.source), matchedAskRule: paramAsk.ruleText };
464
493
  }
@@ -1,4 +1,4 @@
1
- import { type ShellCommandShape } from "./shell-lexer.js";
1
+ import { type ShellCommandShape } from "./shell-run-candidates.js";
2
2
  import { type LineDescriptors } from "../tools/fs/shell-read-device.js";
3
3
  /** The reader's answer: read-only, or not — with the first reason the reading stopped on (a sentence a
4
4
  * trace or a card can show; never adjudication input). */