@sema-agent/core 7.47.0 → 7.49.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 (431) hide show
  1. package/CHANGELOG.md +472 -0
  2. package/README.md +2 -2
  3. package/dist/agents/cascade.d.ts +2 -5
  4. package/dist/agents/child-failure.d.ts +20 -0
  5. package/dist/agents/child-failure.js +28 -0
  6. package/dist/agents/cross-session-ref.d.ts +11 -3
  7. package/dist/agents/cross-session-ref.js +6 -0
  8. package/dist/agents/cumulative-stats.d.ts +12 -15
  9. package/dist/agents/cumulative-stats.js +1 -1
  10. package/dist/agents/extra-tool-names.js +1 -1
  11. package/dist/agents/inherited-tool-pool.d.ts +17 -13
  12. package/dist/agents/inherited-tool-pool.js +4 -3
  13. package/dist/agents/peer-session-drain.d.ts +3 -2
  14. package/dist/agents/peer-session-drain.js +5 -4
  15. package/dist/agents/send-message-tool.js +2 -9
  16. package/dist/agents/subagent.d.ts +10 -29
  17. package/dist/agents/subagent.js +65 -53
  18. package/dist/agents/suspend-guard.d.ts +1 -1
  19. package/dist/agents/tool-filter.d.ts +76 -65
  20. package/dist/agents/tool-filter.js +122 -92
  21. package/dist/agents/verify.d.ts +4 -7
  22. package/dist/agents/verify.js +6 -7
  23. package/dist/bench/metrics.d.ts +1 -2
  24. package/dist/brain/anthropic.js +133 -79
  25. package/dist/brain/errors.d.ts +53 -6
  26. package/dist/brain/errors.js +22 -2
  27. package/dist/brain/input-too-long.d.ts +40 -19
  28. package/dist/brain/input-too-long.js +3 -16
  29. package/dist/brain/open-responses.js +19 -10
  30. package/dist/brain/openai.js +123 -32
  31. package/dist/brain/provider-dialect.d.ts +214 -0
  32. package/dist/brain/provider-dialect.js +186 -0
  33. package/dist/brain/request-params.d.ts +27 -8
  34. package/dist/brain/request-params.js +7 -12
  35. package/dist/brain/request-route.d.ts +84 -0
  36. package/dist/brain/request-route.js +41 -0
  37. package/dist/brain/retry.d.ts +8 -0
  38. package/dist/brain/retry.js +10 -4
  39. package/dist/brain/route-adjudicator.d.ts +7 -0
  40. package/dist/brain/route-adjudicator.js +5 -1
  41. package/dist/brain/status-sink.d.ts +20 -0
  42. package/dist/brain/status-sink.js +11 -0
  43. package/dist/brain/stream-engine.d.ts +52 -1
  44. package/dist/brain/stream-engine.js +81 -24
  45. package/dist/brain/stream-shared.d.ts +57 -11
  46. package/dist/brain/stream-shared.js +28 -2
  47. package/dist/core/a2a.d.ts +1 -0
  48. package/dist/core/agent-config-dir.js +3 -2
  49. package/dist/core/allow-entry.d.ts +21 -12
  50. package/dist/core/allow-entry.js +39 -32
  51. package/dist/core/arg-summary.d.ts +17 -24
  52. package/dist/core/arg-summary.js +169 -68
  53. package/dist/core/ask-origin.d.ts +42 -24
  54. package/dist/core/ask-origin.js +18 -13
  55. package/dist/core/auto-mode-prompt-assets.d.ts +2 -2
  56. package/dist/core/auto-mode-prompt-assets.js +1 -1
  57. package/dist/core/auto-mode-prompt.d.ts +11 -1
  58. package/dist/core/auto-mode-prompt.js +13 -10
  59. package/dist/core/auto-mode-rebuild.d.ts +7 -0
  60. package/dist/core/auto-mode-rebuild.js +4 -1
  61. package/dist/core/canonical-json.d.ts +3 -0
  62. package/dist/core/canonical-json.js +2 -1
  63. package/dist/core/checkpoint-requirements.d.ts +892 -0
  64. package/dist/core/checkpoint-requirements.js +333 -0
  65. package/dist/core/checkpoint-store.d.ts +95 -66
  66. package/dist/core/checkpoint-store.js +14 -26
  67. package/dist/core/child-clamp-seats.d.ts +228 -69
  68. package/dist/core/child-clamp-seats.js +211 -17
  69. package/dist/core/classifier-unavailable-arm.d.ts +5 -1
  70. package/dist/core/classifier-unavailable-arm.js +3 -3
  71. package/dist/core/compliance.d.ts +3 -0
  72. package/dist/core/compliance.js +9 -4
  73. package/dist/core/credential-vocabulary.d.ts +140 -0
  74. package/dist/core/credential-vocabulary.js +131 -0
  75. package/dist/core/decision-receipt.js +1 -1
  76. package/dist/core/effective-path-target.d.ts +73 -16
  77. package/dist/core/effective-path-target.js +63 -33
  78. package/dist/core/engine-notice.d.ts +32 -7
  79. package/dist/core/file-history-store.js +2 -2
  80. package/dist/core/fs-write-gate-policy.d.ts +4 -0
  81. package/dist/core/fs-write-gate-policy.js +22 -5
  82. package/dist/core/gate-bounded-await.d.ts +28 -13
  83. package/dist/core/gate-bounded-await.js +41 -4
  84. package/dist/core/gate-fold.d.ts +1 -1
  85. package/dist/core/gate-fold.js +24 -25
  86. package/dist/core/gate-lanes.js +16 -15
  87. package/dist/core/gate-outcome.d.ts +20 -5
  88. package/dist/core/gate-outcome.js +5 -0
  89. package/dist/core/governance-codes.d.ts +11 -4
  90. package/dist/core/governance-codes.js +14 -3
  91. package/dist/core/hooks.d.ts +4 -0
  92. package/dist/core/locked-config.d.ts +8 -0
  93. package/dist/core/locked-config.js +14 -2
  94. package/dist/core/lsp.js +3 -5
  95. package/dist/core/mailbox-store.d.ts +3 -4
  96. package/dist/core/mcp-connection-pool.d.ts +75 -0
  97. package/dist/core/mcp-connection-pool.js +128 -0
  98. package/dist/core/mcp-failure.d.ts +2 -1
  99. package/dist/core/mcp-failure.js +13 -2
  100. package/dist/core/mcp-server-spec.d.ts +1 -1
  101. package/dist/core/mcp.d.ts +4 -0
  102. package/dist/core/mcp.js +70 -43
  103. package/dist/core/memory-engine/content-origin.js +2 -14
  104. package/dist/core/memory-engine/engine.d.ts +32 -22
  105. package/dist/core/memory-engine/engine.js +103 -55
  106. package/dist/core/memory-engine/file-backend.d.ts +13 -30
  107. package/dist/core/memory-engine/file-backend.js +37 -252
  108. package/dist/core/memory-engine/index.d.ts +1 -1
  109. package/dist/core/memory-engine/index.js +1 -1
  110. package/dist/core/memory-engine/layout.d.ts +139 -9
  111. package/dist/core/memory-engine/layout.js +350 -7
  112. package/dist/core/memory-engine/scope-contract.d.ts +10 -0
  113. package/dist/core/memory-engine/scope-contract.js +8 -0
  114. package/dist/core/memory-engine/session-incarnation.d.ts +14 -4
  115. package/dist/core/memory-engine/session-incarnation.js +5 -2
  116. package/dist/core/memory-engine/types.d.ts +10 -1
  117. package/dist/core/memory.js +5 -17
  118. package/dist/core/model-seat.d.ts +4 -4
  119. package/dist/core/model-seat.js +1 -1
  120. package/dist/core/mounted-mcp.d.ts +57 -0
  121. package/dist/core/mounted-mcp.js +19 -0
  122. package/dist/core/namespaced-rule-name.d.ts +29 -0
  123. package/dist/core/namespaced-rule-name.js +52 -0
  124. package/dist/core/park-selfcheck.d.ts +1 -1
  125. package/dist/core/park-selfcheck.js +16 -6
  126. package/dist/core/path-guard-rows.d.ts +99 -0
  127. package/dist/core/path-guard-rows.js +45 -0
  128. package/dist/core/path-identity.d.ts +22 -16
  129. package/dist/core/path-identity.js +10 -10
  130. package/dist/core/path-rule-binding.d.ts +2 -1
  131. package/dist/core/path-rule-binding.js +3 -1
  132. package/dist/core/pause-registry.d.ts +16 -2
  133. package/dist/core/pause-registry.js +6 -0
  134. package/dist/core/permission-rule-consent.js +0 -7
  135. package/dist/core/permission-rule-model.d.ts +68 -24
  136. package/dist/core/permission-rule-model.js +105 -24
  137. package/dist/core/permission-rule-store.js +2 -2
  138. package/dist/core/permission-rules.d.ts +3 -30
  139. package/dist/core/permission-rules.js +4 -54
  140. package/dist/core/read-root-candidate.d.ts +1 -3
  141. package/dist/core/read-root-candidate.js +4 -15
  142. package/dist/core/real-approval.d.ts +4 -3
  143. package/dist/core/retired-keys.d.ts +13 -0
  144. package/dist/core/retired-keys.js +14 -0
  145. package/dist/core/roles.d.ts +2 -3
  146. package/dist/core/roles.js +15 -6
  147. package/dist/core/runner/assemble-result.d.ts +0 -7
  148. package/dist/core/runner/assemble-result.js +19 -31
  149. package/dist/core/runner/caller-roster.d.ts +78 -0
  150. package/dist/core/runner/caller-roster.js +219 -0
  151. package/dist/core/runner/checkpoint-scope.d.ts +16 -16
  152. package/dist/core/runner/checkpoint-scope.js +2 -2
  153. package/dist/core/runner/child-tree.js +2 -2
  154. package/dist/core/runner/contracts.d.ts +68 -35
  155. package/dist/core/runner/decide-continuation.d.ts +51 -3
  156. package/dist/core/runner/decide-continuation.js +76 -21
  157. package/dist/core/runner/denial-limit-arms.d.ts +17 -5
  158. package/dist/core/runner/denial-limit-arms.js +11 -9
  159. package/dist/core/runner/extra-body-dropped-seat.d.ts +8 -0
  160. package/dist/core/runner/extra-body-dropped-seat.js +22 -0
  161. package/dist/core/runner/finish-reason-seat.d.ts +9 -0
  162. package/dist/core/runner/finish-reason-seat.js +40 -0
  163. package/dist/core/runner/gate-exit.d.ts +44 -32
  164. package/dist/core/runner/gate-exit.js +112 -25
  165. package/dist/core/runner/inherited-ask-grants.js +6 -4
  166. package/dist/core/runner/mcp-redial.d.ts +3 -3
  167. package/dist/core/runner/mcp-redial.js +3 -4
  168. package/dist/core/runner/park-commit.d.ts +1 -1
  169. package/dist/core/runner/permission-rule-lanes.d.ts +1 -2
  170. package/dist/core/runner/permission-rule-lanes.js +5 -4
  171. package/dist/core/runner/prepare-artifact.d.ts +4 -0
  172. package/dist/core/runner/prepare-artifact.js +2 -1
  173. package/dist/core/runner/prepare-ask-lane.d.ts +2 -0
  174. package/dist/core/runner/prepare-ask-lane.js +12 -6
  175. package/dist/core/runner/prepare-boundary-parks.d.ts +5 -8
  176. package/dist/core/runner/prepare-boundary-parks.js +4 -7
  177. package/dist/core/runner/prepare-caps-and-workflow.d.ts +11 -15
  178. package/dist/core/runner/prepare-caps-and-workflow.js +19 -27
  179. package/dist/core/runner/prepare-config-doors.d.ts +9 -9
  180. package/dist/core/runner/prepare-config-doors.js +10 -5
  181. package/dist/core/runner/prepare-defer-classify.d.ts +2 -2
  182. package/dist/core/runner/prepare-defer-classify.js +5 -3
  183. package/dist/core/runner/prepare-delegation-surface.d.ts +1 -1
  184. package/dist/core/runner/prepare-delegation-surface.js +3 -3
  185. package/dist/core/runner/prepare-gate-stations.d.ts +4 -4
  186. package/dist/core/runner/prepare-gate-stations.js +9 -7
  187. package/dist/core/runner/prepare-hands-readface.d.ts +7 -7
  188. package/dist/core/runner/prepare-hands-readface.js +7 -7
  189. package/dist/core/runner/prepare-inherited-gate.d.ts +7 -6
  190. package/dist/core/runner/prepare-inherited-gate.js +14 -15
  191. package/dist/core/runner/prepare-listings.d.ts +7 -24
  192. package/dist/core/runner/prepare-listings.js +4 -5
  193. package/dist/core/runner/prepare-memory-engine-session.d.ts +2 -0
  194. package/dist/core/runner/prepare-memory-engine-session.js +4 -3
  195. package/dist/core/runner/prepare-memory.d.ts +6 -0
  196. package/dist/core/runner/prepare-memory.js +13 -13
  197. package/dist/core/runner/prepare-offload-wrappers.d.ts +5 -0
  198. package/dist/core/runner/prepare-offload-wrappers.js +3 -3
  199. package/dist/core/runner/prepare-park-ask.d.ts +7 -8
  200. package/dist/core/runner/prepare-park-ask.js +17 -10
  201. package/dist/core/runner/prepare-policy-chain.js +24 -44
  202. package/dist/core/runner/prepare-project-context.d.ts +1 -1
  203. package/dist/core/runner/prepare-project-context.js +3 -4
  204. package/dist/core/runner/prepare-protocol-tools.d.ts +9 -3
  205. package/dist/core/runner/prepare-protocol-tools.js +3 -4
  206. package/dist/core/runner/prepare-run-refs.d.ts +3 -0
  207. package/dist/core/runner/prepare-run-refs.js +2 -1
  208. package/dist/core/runner/prepare-safety-scan.d.ts +4 -8
  209. package/dist/core/runner/prepare-safety-scan.js +1 -2
  210. package/dist/core/runner/prepare-suspend-saga.d.ts +7 -1
  211. package/dist/core/runner/prepare-suspend-saga.js +28 -12
  212. package/dist/core/runner/prepare-task.d.ts +1 -1
  213. package/dist/core/runner/prepare-task.js +67 -56
  214. package/dist/core/runner/prepare-wiring-manifest.d.ts +11 -10
  215. package/dist/core/runner/prepare-wiring-manifest.js +5 -8
  216. package/dist/core/runner/resume-apply.js +7 -14
  217. package/dist/core/runner/resume-checkpoint-screen.js +11 -4
  218. package/dist/core/runner/resume-internals-and-config.js +2 -2
  219. package/dist/core/runner/resume-policy-outcome.js +5 -32
  220. package/dist/core/runner/resume-review-outcome.js +4 -1
  221. package/dist/core/runner/retired-runner-deps.d.ts +6 -0
  222. package/dist/core/runner/retired-runner-deps.js +10 -1
  223. package/dist/core/runner/run-brain-sinks.d.ts +4 -0
  224. package/dist/core/runner/run-brain-sinks.js +5 -2
  225. package/dist/core/runner/run-harness-handlers.js +27 -34
  226. package/dist/core/runner/run-leg.d.ts +0 -2
  227. package/dist/core/runner/run-leg.js +32 -33
  228. package/dist/core/runner/run-terminal-adoption.js +4 -3
  229. package/dist/core/runner/runtask.js +8 -8
  230. package/dist/core/runner/session-rule-policy.js +5 -5
  231. package/dist/core/runner/stream-settle-backstop.js +7 -4
  232. package/dist/core/runner/sub-agent-deny-lists.d.ts +51 -12
  233. package/dist/core/runner/sub-agent-deny-lists.js +111 -80
  234. package/dist/core/runner/terminal-account.d.ts +48 -0
  235. package/dist/core/runner/terminal-account.js +186 -0
  236. package/dist/core/runner/tool-end-body.d.ts +54 -17
  237. package/dist/core/runner/tool-end-body.js +27 -2
  238. package/dist/core/runner/tool-face-overlay.d.ts +3 -1
  239. package/dist/core/runner/tool-face-overlay.js +6 -4
  240. package/dist/core/runner/tool-occurrence.d.ts +106 -0
  241. package/dist/core/runner/tool-occurrence.js +123 -0
  242. package/dist/core/runner/tool-progress-lane.d.ts +16 -8
  243. package/dist/core/runner/tool-progress-lane.js +25 -34
  244. package/dist/core/runner/turn-attachments.d.ts +1 -1
  245. package/dist/core/runner/write-protection-station.d.ts +21 -9
  246. package/dist/core/runner/write-protection-station.js +20 -17
  247. package/dist/core/runner-deps.d.ts +12 -22
  248. package/dist/core/safety-axis-vocab.d.ts +1 -1
  249. package/dist/core/safety-merge-corpus.d.ts +1 -1
  250. package/dist/core/secret-env.d.ts +16 -5
  251. package/dist/core/secret-env.js +23 -63
  252. package/dist/core/sensitive-path-policy.d.ts +19 -18
  253. package/dist/core/sensitive-path-policy.js +73 -49
  254. package/dist/core/session-policy-store.d.ts +6 -3
  255. package/dist/core/session-policy-store.js +28 -10
  256. package/dist/core/session-reconcile.d.ts +3 -0
  257. package/dist/core/session-reconcile.js +5 -4
  258. package/dist/core/session-store.d.ts +5 -0
  259. package/dist/core/session-store.js +12 -0
  260. package/dist/core/session.d.ts +13 -0
  261. package/dist/core/shared-memory/normalize.d.ts +17 -4
  262. package/dist/core/shared-memory/normalize.js +7 -7
  263. package/dist/core/shell-lexer.js +5 -4
  264. package/dist/core/shell-wrapper-table.d.ts +2 -2
  265. package/dist/core/shell-wrapper-table.js +1 -1
  266. package/dist/core/skill-grant.js +1 -1
  267. package/dist/core/store-contracts/checkpoint-store-contract.js +13 -0
  268. package/dist/core/store-contracts/mailbox-store-contract.d.ts +7 -14
  269. package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +27 -0
  270. package/dist/core/store-contracts/session-store-incarnation-contract.js +92 -0
  271. package/dist/core/stub-env.d.ts +2 -0
  272. package/dist/core/stub-env.js +1 -0
  273. package/dist/core/synthetic-read-verdict.js +2 -2
  274. package/dist/core/task-event.d.ts +59 -5
  275. package/dist/core/task-result.d.ts +1 -1
  276. package/dist/core/task-spec.d.ts +42 -9
  277. package/dist/core/terminal-cause.d.ts +94 -11
  278. package/dist/core/tighten-task-spec.d.ts +5 -4
  279. package/dist/core/tighten-task-spec.js +20 -25
  280. package/dist/core/tool-catalog-entries.d.ts +5 -7
  281. package/dist/core/tool-catalog-entries.js +14 -15
  282. package/dist/core/tool-face.d.ts +47 -0
  283. package/dist/core/tool-face.js +5 -0
  284. package/dist/core/tool-model-gate.d.ts +5 -5
  285. package/dist/core/tool-name-aliases.d.ts +35 -10
  286. package/dist/core/tool-name-aliases.js +15 -0
  287. package/dist/core/tool-name-list.d.ts +30 -0
  288. package/dist/core/tool-name-list.js +55 -0
  289. package/dist/core/tool-name-readings.d.ts +1 -9
  290. package/dist/core/tool-name-readings.js +1 -3
  291. package/dist/core/tool-policy.d.ts +195 -85
  292. package/dist/core/tool-policy.js +165 -381
  293. package/dist/core/tool-registry.d.ts +44 -1
  294. package/dist/core/tool-registry.js +26 -0
  295. package/dist/core/tool-roster.d.ts +19 -1
  296. package/dist/core/tool-roster.js +21 -2
  297. package/dist/core/tool-spec-members.d.ts +45 -0
  298. package/dist/core/tool-spec-members.js +169 -0
  299. package/dist/core/tool-spec.d.ts +46 -8
  300. package/dist/core/tools.d.ts +6 -2
  301. package/dist/core/tools.js +8 -4
  302. package/dist/core/untrusted-egress.js +29 -26
  303. package/dist/core/untrusted-text.d.ts +13 -10
  304. package/dist/core/untrusted-text.js +6 -44
  305. package/dist/core/workflow-journal-store.d.ts +4 -1
  306. package/dist/core/write-protect.d.ts +65 -29
  307. package/dist/core/write-protect.js +235 -177
  308. package/dist/engine/compaction/compaction.d.ts +2 -2
  309. package/dist/engine/execution-env/node-execution-env.d.ts +5 -23
  310. package/dist/engine/execution-env/node-execution-env.js +16 -38
  311. package/dist/engine/execution-env/symlink-chain.d.ts +23 -0
  312. package/dist/engine/execution-env/symlink-chain.js +32 -0
  313. package/dist/engine/harness/types.d.ts +7 -0
  314. package/dist/engine/llm/tool-turns.d.ts +3 -0
  315. package/dist/engine/llm/tool-turns.js +1 -0
  316. package/dist/engine/llm/types.d.ts +59 -23
  317. package/dist/engine/loop/agent-loop.js +16 -2
  318. package/dist/engine/session/memory-repo.js +11 -3
  319. package/dist/engine/session/repo-utils.d.ts +8 -0
  320. package/dist/engine/session/repo-utils.js +13 -0
  321. package/dist/index.d.ts +20 -23
  322. package/dist/index.js +17 -20
  323. package/dist/internal/harness.d.ts +1 -1
  324. package/dist/internal/harness.js +1 -1
  325. package/dist/internal/llm.d.ts +2 -2
  326. package/dist/internal/llm.js +1 -1
  327. package/dist/orchestration/governance-baseline-validity.js +1 -1
  328. package/dist/orchestration/run-spec.js +3 -3
  329. package/dist/orchestration/run-workflow-tool.d.ts +16 -35
  330. package/dist/orchestration/run-workflow-tool.js +38 -78
  331. package/dist/orchestration/workflow-governance.d.ts +21 -25
  332. package/dist/orchestration/workflow-governance.js +38 -78
  333. package/dist/orchestration/workflow-primitives.d.ts +7 -22
  334. package/dist/orchestration/workflow-primitives.js +5 -12
  335. package/dist/orchestration/workflow-types.d.ts +1 -1
  336. package/dist/orchestration/workflow.d.ts +61 -11
  337. package/dist/orchestration/workflow.js +191 -119
  338. package/dist/prompt-assembly/types.d.ts +1 -1
  339. package/dist/scenarios/full-body.d.ts +1 -1
  340. package/dist/shell/lex.d.ts +0 -21
  341. package/dist/shell/lex.js +278 -70
  342. package/dist/shell/shell-words.d.ts +35 -0
  343. package/dist/shell/shell-words.js +65 -0
  344. package/dist/stores/file/checkpoint-store.d.ts +5 -5
  345. package/dist/stores/file/checkpoint-store.js +5 -2
  346. package/dist/stores/file/index.d.ts +0 -8
  347. package/dist/stores/file/index.js +0 -12
  348. package/dist/stores/file/session-store.d.ts +3 -0
  349. package/dist/stores/file/session-store.js +44 -19
  350. package/dist/tools/artifact/artifact-tool.js +2 -0
  351. package/dist/tools/fs/bash-program-position.d.ts +17 -3
  352. package/dist/tools/fs/bash-program-position.js +25 -10
  353. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
  354. package/dist/tools/fs/bash-readonly-classifier.js +8 -7
  355. package/dist/tools/fs/containment-face.d.ts +4 -0
  356. package/dist/tools/fs/containment-face.js +3 -0
  357. package/dist/tools/fs/fs-shared.d.ts +10 -0
  358. package/dist/tools/fs/fs-shared.js +18 -0
  359. package/dist/tools/fs/fs-write.js +4 -2
  360. package/dist/tools/fs/index.d.ts +3 -2
  361. package/dist/tools/fs/index.js +2 -2
  362. package/dist/tools/fs/program-read-model.d.ts +99 -23
  363. package/dist/tools/fs/program-read-model.js +129 -55
  364. package/dist/tools/fs/program-read-model.rows.d.ts +0 -14
  365. package/dist/tools/fs/program-read-model.rows.js +18 -10
  366. package/dist/tools/fs/read-deny-home.d.ts +35 -30
  367. package/dist/tools/fs/read-deny-home.js +45 -38
  368. package/dist/tools/fs/read-deny.d.ts +35 -27
  369. package/dist/tools/fs/read-deny.js +126 -92
  370. package/dist/tools/fs/removal-targets.d.ts +56 -23
  371. package/dist/tools/fs/removal-targets.js +74 -87
  372. package/dist/tools/fs/safety.d.ts +29 -22
  373. package/dist/tools/fs/safety.js +17 -11
  374. package/dist/tools/fs/search.js +10 -8
  375. package/dist/tools/fs/shell-read-argv.d.ts +4 -7
  376. package/dist/tools/fs/shell-read-argv.js +6 -8
  377. package/dist/tools/fs/shell-read-bindings.d.ts +203 -46
  378. package/dist/tools/fs/shell-read-bindings.js +488 -144
  379. package/dist/tools/fs/shell-read-builtins.d.ts +35 -0
  380. package/dist/tools/fs/shell-read-builtins.js +43 -0
  381. package/dist/tools/fs/shell-read-check.d.ts +15 -1
  382. package/dist/tools/fs/shell-read-check.js +51 -42
  383. package/dist/tools/fs/shell-read-establish.js +9 -10
  384. package/dist/tools/fs/shell-read-functions.d.ts +22 -0
  385. package/dist/tools/fs/shell-read-functions.js +29 -0
  386. package/dist/tools/fs/shell-read-judge.d.ts +48 -18
  387. package/dist/tools/fs/shell-read-judge.js +33 -4
  388. package/dist/tools/fs/shell-read-plan.d.ts +13 -20
  389. package/dist/tools/fs/shell-read-plan.js +200 -194
  390. package/dist/tools/fs/shell-read-start.d.ts +80 -0
  391. package/dist/tools/fs/shell-read-start.js +99 -0
  392. package/dist/tools/fs/shell-read-word.d.ts +35 -6
  393. package/dist/tools/fs/shell-read-word.js +89 -9
  394. package/dist/tools/fs/shell-read-workdir.d.ts +156 -0
  395. package/dist/tools/fs/shell-read-workdir.js +281 -0
  396. package/dist/tools/fs/shell-read-write-targets.d.ts +5 -2
  397. package/dist/tools/fs/shell-read-write-targets.js +14 -5
  398. package/dist/tools/fs/shell-reentry-text.d.ts +26 -26
  399. package/dist/tools/fs/shell-reentry-text.js +31 -34
  400. package/dist/tools/task-list.d.ts +1 -2
  401. package/dist/tools/web.js +1 -1
  402. package/package.json +4 -1
  403. package/test/export-surface.snapshot.json +107 -143
  404. package/wire-changes/core@7.48.0.json +252 -0
  405. package/wire-changes/core@7.49.0.json +4765 -0
  406. package/dist/agents/teacher.d.ts +0 -159
  407. package/dist/agents/teacher.js +0 -577
  408. package/dist/agents/team.d.ts +0 -226
  409. package/dist/agents/team.js +0 -257
  410. package/dist/core/strategy-store.d.ts +0 -238
  411. package/dist/core/strategy-store.js +0 -253
  412. package/dist/orchestration/builtin-workflows.d.ts +0 -133
  413. package/dist/orchestration/builtin-workflows.js +0 -159
  414. package/dist/scenarios/env.d.ts +0 -40
  415. package/dist/scenarios/env.js +0 -39
  416. package/dist/scenarios/scenario-registry.d.ts +0 -110
  417. package/dist/scenarios/scenario-registry.js +0 -140
  418. package/dist/scenarios/teacher-quickstart.d.ts +0 -40
  419. package/dist/scenarios/teacher-quickstart.js +0 -12
  420. package/dist/stores/cc/lockfile.d.ts +0 -10
  421. package/dist/stores/cc/lockfile.js +0 -98
  422. package/dist/stores/cc/mailbox-store.d.ts +0 -20
  423. package/dist/stores/cc/mailbox-store.js +0 -255
  424. package/dist/stores/cc/roster-adapter.d.ts +0 -12
  425. package/dist/stores/cc/roster-adapter.js +0 -117
  426. package/dist/stores/cc/sidecar-transcript.d.ts +0 -97
  427. package/dist/stores/cc/sidecar-transcript.js +0 -264
  428. package/dist/stores/cc/task-list-store.d.ts +0 -2
  429. package/dist/stores/cc/task-list-store.js +0 -125
  430. package/dist/stores/file/strategy-store.d.ts +0 -97
  431. package/dist/stores/file/strategy-store.js +0 -341
@@ -0,0 +1,35 @@
1
+ /**
2
+ * WHETHER A BUILTIN RUNS BY ITS NAME — a binding of the same ordered thread as a variable's or a function's
3
+ * (`shell-read-bindings.ts`). `enable -n NAME` disables a builtin (the word then finds no command: "command not found"),
4
+ * `enable NAME` enables it again, and every builtin starts a text enabled. The thread keeps this the way it keeps a
5
+ * function's definition, under a key neither a variable nor a function can spell, and a reader at a segment asks the one
6
+ * ordered rule ({@link BuiltinState}) before it takes a word for the builtin of its name; this module is the vocabulary of
7
+ * that key and the reading of an `enable` into bindings.
8
+ */
9
+ import type { LexedWord } from "../../shell/lex.js";
10
+ import type { Binding, BoundValue, NameState } from "./shell-read-bindings.js";
11
+ /** What a builtin is at a reader: `enabled` — it certainly runs by its name; `disabled` — it certainly does not (`enable -n`:
12
+ * "command not found"); `maybe` — an `enable` that may not have run, a call that may have run one, one for every builtin. */
13
+ export type BuiltinState = "enabled" | "maybe" | "disabled";
14
+ /** The builtin whose words are read into the availability of the others. */
15
+ export declare const ENABLE_BUILTIN = "enable";
16
+ export declare const builtinKey: (name: string) => string;
17
+ export declare const isBuiltinKey: (name: string) => boolean;
18
+ /** Every builtin at once (`enable -f FILE` loads one over any name; a name the text computes may be any). */
19
+ export declare const BUILTINS_ALL: string;
20
+ export declare const BUILTIN_ENABLED: NameState;
21
+ export declare const isDisabledValue: (v: BoundValue) => boolean;
22
+ /** What the shell finds looking up each word of a chain it runs as builtins (`builtin` / `command` in front, then the name): the
23
+ * same reading ({@link BuiltinState}) for every word, since the chain runs no further than its first word that finds no command —
24
+ * one that certainly does not run makes the chain certainly not run, and one that may not makes it so. `ask` absent: all enabled. */
25
+ export declare function chainAvailability(words: readonly string[], ask: ((builtin: string) => BuiltinState) | undefined): BuiltinState;
26
+ /** Does an entry (or a made-unreadable name) with this name speak for `name`: no name (every name), the name itself, or —
27
+ * for a builtin's key — every builtin at once. */
28
+ export declare const speaksFor: (subject: string | undefined, name: string) => boolean;
29
+ /** The bindings an `enable [-a] [-dnps] [-f FILE] [NAME…]` makes: `enable NAME` enables each name, `enable -n NAME` disables it
30
+ * (a literal name: its key); any other switch — `-f FILE` loads a builtin over any name, `-d`, the listing switches — or a
31
+ * name the text computes may change every builtin; a bare listing (`enable`, `enable -n`) changes none. The words in front of
32
+ * the first name are read as the shell reads them: a word opening with `-` (but a lone `-`, which is a name) is switches, `--`
33
+ * ends them, and a letter outside the switches is an invalid option — the shell rejects the whole call (status 2) and nothing
34
+ * changes, whatever else the call says; a word after the first name is a name. */
35
+ export declare function enableBindings(args: readonly LexedWord[]): Binding[];
@@ -0,0 +1,43 @@
1
+ export const ENABLE_BUILTIN = "enable";
2
+ const BUILTIN_KEY_PREFIX = "[]";
3
+ export const builtinKey = (name) => BUILTIN_KEY_PREFIX + name;
4
+ export const isBuiltinKey = (name) => name.startsWith(BUILTIN_KEY_PREFIX);
5
+ export const BUILTINS_ALL = builtinKey("*");
6
+ export const BUILTIN_ENABLED = [{ spelled: "enabled" }];
7
+ const BUILTIN_DISABLED = [{ spelled: "disabled" }];
8
+ export const isDisabledValue = (v) => v.spelled === "disabled";
9
+ export function chainAvailability(words, ask) {
10
+ const states = words.map((w) => ask?.(w) ?? "enabled");
11
+ return states.includes("disabled") ? "disabled" : states.includes("maybe") ? "maybe" : "enabled";
12
+ }
13
+ export const speaksFor = (subject, name) => subject === undefined || subject === name || (subject === BUILTINS_ALL && isBuiltinKey(name));
14
+ const ENABLE_SWITCHES = "adfnps";
15
+ export function enableBindings(args) {
16
+ const computed = (w) => w.pieces.some((p) => p.expansion !== undefined || p.literal === undefined);
17
+ let disable = false;
18
+ let other = false;
19
+ let at = 0;
20
+ for (; at < args.length; at++) {
21
+ const w = args[at];
22
+ if (w.folded === "--") {
23
+ at++;
24
+ break;
25
+ }
26
+ if (computed(w) || w.folded.length < 2 || !w.folded.startsWith("-"))
27
+ break;
28
+ for (const letter of w.folded.slice(1)) {
29
+ if (!ENABLE_SWITCHES.includes(letter))
30
+ return [];
31
+ if (letter === "f")
32
+ return [{ name: BUILTINS_ALL, state: "unknown" }];
33
+ if (letter === "n")
34
+ disable = true;
35
+ else
36
+ other = true;
37
+ }
38
+ }
39
+ const names = args.slice(at);
40
+ if (other || names.some(computed))
41
+ return [{ name: BUILTINS_ALL, state: "unknown" }];
42
+ return names.map((w) => ({ name: builtinKey(w.folded), state: disable ? BUILTIN_DISABLED : BUILTIN_ENABLED }));
43
+ }
@@ -44,6 +44,9 @@ export type ShellReadRefusalDetails = NonNullable<ReturnType<typeof violationDet
44
44
  export interface ShellReadVerdict {
45
45
  readonly disposition: ShellReadDisposition;
46
46
  readonly finding: ShellReadFinding;
47
+ /** `write` ⇔ the finding is about where the command WRITES — its witness is a write target (a redirection's, or a word
48
+ * the program's row writes), judged against the write roots. Absent ⇒ a read's finding (or `vouch`). */
49
+ readonly face?: "write";
47
50
  /** `refuse`: the refusal text (`Error (<tool>): …`, the five-segment deny sentence or the containment
48
51
  * sentence). `mandated` / `ordinary`: the ACCOUNT of the finding, for the approval card's disclosure seat
49
52
  * (never the ask's own message — that is the auto-mode classifier's input). Absent for `vouch`. */
@@ -58,7 +61,7 @@ export interface ShellReadVerdict {
58
61
  /** A FACT beside the disposition, for the mode that answers a file command itself: every write target this command
59
62
  * names was established inside the boundary's WRITE roots at every seat the program writes it (the entry, where it
60
63
  * lands, or both — `WriteSeat`, shell-read-write-targets.ts), off the write-protection table and off every deployment
61
- * write guard (`ShellReadBoundary.writeProtectionJudge` / `writeTargetGuard`), no removal (`rm` / `rmdir`) targets a
64
+ * guarded write row (the leg's one write judgment, `ShellReadBoundary.writeTargetJudge`), no removal (`rm` / `rmdir`) targets a
62
65
  * write root or a directory containing one, every program the command runs is in its text (no `sed -f` file, no
63
66
  * re-entered shell, no unreadable text), and the line's order keeps each segment's establishment true
64
67
  * ({@link lineOrderPlaced}). Absent ⇒ not established — the mode does not answer; the station's own table is unchanged. */
@@ -67,6 +70,11 @@ export interface ShellReadVerdict {
67
70
  * the table's own judge, as a write tool's target is — so the command's ask carries that table's class, whichever
68
71
  * mode reads it. Absent ⇒ no target the station established is covered (or no judge is mounted). */
69
72
  readonly writeProtected?: true;
73
+ /** A FACT beside the disposition: a target this command's program WRITES was placed and the environment could not say
74
+ * where it leads ({@link EstablishedRead.unresolved}) — the reading's `target_unresolved` fact, the same one a write
75
+ * tool's unresolved target states, so the command's ask carries that class (no automatic lane answers it; the shell face
76
+ * releases it under bypassPermissions). Absent ⇒ every written target resolved, or was not placed for another reason. */
77
+ readonly targetUnresolved?: true;
70
78
  /** A FACT beside `writesPlaced`'s absence: the command is one of the mode's file commands whose writes the station would
71
79
  * have placed but for an OPTION WORD on a `cp` / `mv` segment ({@link UNPLACED_OPTIONS}: any word starting with `-` — the
72
80
  * reference implementation's rule, "with flags requires manual approval to ensure path safety"). Names the first such
@@ -149,6 +157,12 @@ export interface ShellReadStationOptions {
149
157
  /** The home views of the placement the boundary's deny seats were compiled at (read-deny-home.ts) — the Runner's. Absent,
150
158
  * the declared home alone. */
151
159
  readonly homeViews?: HomeViewSource;
160
+ /** The engine's account of a REFUSED admission, called with the call's id before the refusal is handed back to the tool
161
+ * — the one trusted channel by which a refusal after the gate reaches the call's record (the tool's result is the
162
+ * tool's own word, and a refusal must not read as a tool failure under the gate's `allowed`). Every shell tool this
163
+ * station admits (both Bash faces, Monitor) refuses through {@link ShellReadStation.admit}, so this is the one
164
+ * place to hear it. The Runner's; absent outside one. */
165
+ readonly onAdmissionRefused?: (toolCallId: string) => void;
152
166
  }
153
167
  /** What a consumer hands {@link judgeShellRead}: the command as written, the environment whose filesystem
154
168
  * answers where its words land (symlinks resolved, patterns expanded — a `NodeExecutionEnv` over a real
@@ -9,10 +9,11 @@ import { createShellReadEstablisher, establishShellReadsLexically } from "./shel
9
9
  import { declaredReadDenyHomes, homeViewsAt } from "./read-deny-home.js";
10
10
  import { programArgs } from "./shell-read-write-targets.js";
11
11
  import { programRow, selectedOptions } from "./program-read-model.js";
12
- import { allFindings, decideShellRead, denyRowAtOrBelow, foldShellReadFindings, judgeEstablishedPlan, readRootGrantCandidateOf, stationGuards, } from "./shell-read-judge.js";
12
+ import { allFindings, anyFaceGuards, decideShellRead, denyRowAtOrBelow, foldShellReadFindings, judgeEstablishedPlan, readRootGrantCandidateOf, stationGuards, writeFaceGuards, } from "./shell-read-judge.js";
13
13
  import { seatOf } from "../../core/tool-name-readings.js";
14
14
  import { normalizeReadRootCandidate } from "../../core/read-root-candidate.js";
15
15
  import { argvOf, lexShellText } from "../../shell/lex.js";
16
+ import { assertNoRetiredShellReadBoundaryKeys } from "../../core/retired-keys.js";
16
17
  function containmentDetails(paths) {
17
18
  return { type: "readonly_out_of_root", code: "readonly_out_of_root", paths };
18
19
  }
@@ -75,7 +76,7 @@ function writesPlacedOf(judged, boundary) {
75
76
  if (f.read.candidate.face !== "write")
76
77
  continue;
77
78
  const target = f.read.target;
78
- if (f.read.established === "unestablishable" || target === undefined || isPattern(f) || !withinAnyRoot(writeRoots, target))
79
+ if (f.read.established === "unestablishable" || target === undefined || isPattern(f) || f.kind !== "none" || (f.containment !== "inside" && f.containment !== "open"))
79
80
  return false;
80
81
  if (beneath && denyRowAtOrBelow(f.read, boundary, !REMOVAL_COMMANDS.has(verb)))
81
82
  return false;
@@ -97,10 +98,9 @@ function unplacedOptionsOf(judged) {
97
98
  }
98
99
  return undefined;
99
100
  }
100
- async function writeGuardsOf(judged, boundary, placed, env, signal) {
101
- const protection = boundary.writeProtectionJudge;
102
- const guard = placed ? boundary.writeTargetGuard : undefined;
103
- if (protection === undefined && guard === undefined)
101
+ async function writeGuardsOf(judged, boundary, env, signal) {
102
+ const judge = boundary.writeTargetJudge;
103
+ if (judge === undefined)
104
104
  return { unplaced: false, writeProtected: false };
105
105
  const { runs, concurrent } = runOrderOf(judged);
106
106
  const asKey = new Map();
@@ -155,25 +155,17 @@ async function writeGuardsOf(judged, boundary, placed, env, signal) {
155
155
  const dir = fold(parentOf(view));
156
156
  return writtenBy.some((paths, i) => i !== at && (i < at || concurrent) && paths.some((w) => withinAnyRoot([fold(w)], dir) || withinAnyRoot([dir], fold(w))));
157
157
  };
158
- const answer = async (ask) => {
159
- try {
160
- return (await ask()) === true;
161
- }
162
- catch {
163
- return undefined;
164
- }
165
- };
158
+ const answer = async (view, key) => (await judge(view, { ...(signal !== undefined ? { signal } : {}), ...(key ? { canonical: true } : {}) })) ?? "clear";
166
159
  const asked = [...[...asKey].map(([view, v]) => [view, v, true]), ...[...asSpelling].filter(([view]) => !landingKeys.has(view)).map(([view, v]) => [view, v, false])];
167
160
  let unplaced = false;
168
161
  let writeProtected = false;
169
162
  for (const [view, v, key] of asked) {
170
163
  if (!(await writable(view, v)) && !changedBefore(view, v.at))
171
164
  continue;
172
- const covered = protection === undefined ? false : await answer(() => protection(view, { ...(signal !== undefined ? { signal } : {}), ...(key ? { canonical: true } : {}) }));
173
- const guarded = guard === undefined ? false : await answer(() => guard(view, signal));
174
- if (covered === true)
165
+ const found = await answer(view, key);
166
+ if (found === "protected")
175
167
  writeProtected = true;
176
- if (covered !== false || guarded !== false)
168
+ if (found !== "clear")
177
169
  unplaced = true;
178
170
  if (writeProtected)
179
171
  break;
@@ -206,15 +198,21 @@ const CAUSE_SPELLING_MAX = 64;
206
198
  function fitCause(cause) {
207
199
  return cause.replace(/^"([^"]*)"/, (_m, spelling) => `"${fitMiddle(spelling, CAUSE_SPELLING_MAX)}"`);
208
200
  }
209
- function findingAccount(fold, roots, face) {
210
- const allowed = fitMiddle(roots.length > 0 ? roots.join(", ") : "(none)", ACCOUNT_PART_MAX);
201
+ function findingAccount(fold, boundary) {
211
202
  const w = fold.witness;
203
+ const writes = w?.read.candidate.face === "write";
204
+ const roots = writes ? (boundary.writeRoots ?? []) : boundary.roots;
205
+ const allowed = fitMiddle(roots.length > 0 ? roots.join(", ") : "(none)", ACCOUNT_PART_MAX);
212
206
  switch (fold.finding) {
213
207
  case "outside_roots":
214
- return `reads outside the allowed directories for this session: "${fitMiddle(w?.read.target ?? "?", ACCOUNT_PART_MAX)}" is not under ${allowed} ${NOT_AUTO_ALLOWED}`;
208
+ return `${writes ? "writes" : "reads"} outside the allowed directories for this session: "${fitMiddle(w?.read.target ?? "?", ACCOUNT_PART_MAX)}" is not under ${allowed} ${NOT_AUTO_ALLOWED}`;
215
209
  case "unestablishable":
210
+ if (writes && boundary.writeRoots === undefined)
211
+ return `this command writes "${fitMiddle(w.read.target ?? w.read.candidate.spelled, ACCOUNT_PART_MAX)}", and this session declares no directories it may write ${NOT_AUTO_ALLOWED}`;
212
+ if (writes)
213
+ return `where this command writes could not be established${w.read.cause !== undefined ? `: ${fitCause(w.read.cause)}` : ""}, so it cannot be confirmed to write only inside the allowed directories for this session (${allowed}) ${NOT_AUTO_ALLOWED}`;
216
214
  if (w?.read.cause !== undefined)
217
- return `where this command reads could not be established: ${fitCause(w.read.cause)}, so it cannot be confirmed ${face === "open" ? "not to read a path the deny list names" : `to read only inside the allowed directories for this session (${allowed})`} ${NOT_AUTO_ALLOWED}`;
215
+ return `where this command reads could not be established: ${fitCause(w.read.cause)}, so it cannot be confirmed ${boundary.face === "open" ? "not to read a path the deny list names" : `to read only inside the allowed directories for this session (${allowed})`} ${NOT_AUTO_ALLOWED}`;
218
216
  if (fold.declined?.baseMove !== undefined)
219
217
  return `"${fitMiddle(fold.declined.text.trim(), ACCOUNT_PART_MAX)}" moves the working directory somewhere this command's text does not settle ${NOT_AUTO_ALLOWED}`;
220
218
  if (fold.declined?.unreadProgramText !== undefined)
@@ -243,7 +241,7 @@ function outsidePathsOf(judged) {
243
241
  }
244
242
  return out;
245
243
  }
246
- function refusalOf(tool, fold, roots, paths, face) {
244
+ function refusalOf(tool, fold, boundary, paths) {
247
245
  const w = fold.witness;
248
246
  if (fold.finding === "deny_hit" && w?.deny !== undefined) {
249
247
  const target = w.read.target ?? w.deny.matchedView;
@@ -252,7 +250,7 @@ function refusalOf(tool, fold, roots, paths, face) {
252
250
  const sentence = `Error (${tool}): ${core} ${write ? WRITE_DENY_REFUSAL_NOTE : DENY_REFUSAL_NOTE}`;
253
251
  return { sentence, ...(write ? {} : { details: violationDetails({ code: "read_path_denied", message: sentence, target, pattern: w.deny.pattern }) }) };
254
252
  }
255
- const account = findingAccount(fold, roots, face);
253
+ const account = findingAccount(fold, boundary);
256
254
  const sentence = `Error (${tool}): ${account}. ${BASH_READONLY_CONFINEMENT_NOTE}`;
257
255
  return { sentence, details: findingDetails(fold.finding, paths) };
258
256
  }
@@ -268,37 +266,40 @@ function verdictOf(tool, seat, fold, judged, boundary) {
268
266
  return { disposition, finding: fold.finding };
269
267
  const paths = outsidePathsOf(judged);
270
268
  const unestablished = fold.unestablished === true ? { unestablished: true } : {};
269
+ const writes = fold.witness?.read.candidate.face === "write" ? { face: "write" } : {};
271
270
  if (disposition === "refuse") {
272
- const r = refusalOf(tool, fold, boundary.roots, paths, boundary.face);
273
- return { disposition, finding: fold.finding, sentence: r.sentence, ...(r.details !== undefined ? { details: r.details } : {}) };
271
+ const r = refusalOf(tool, fold, boundary, paths);
272
+ return { disposition, finding: fold.finding, ...writes, sentence: r.sentence, ...(r.details !== undefined ? { details: r.details } : {}) };
274
273
  }
275
274
  const grant = fold.finding === "outside_roots" ? readRootGrantCandidateOf(judged, boundary) : undefined;
276
275
  const containment = fold.finding === "outside_roots" || fold.finding === "unestablishable";
277
276
  return {
278
277
  disposition,
279
278
  finding: fold.finding,
279
+ ...writes,
280
280
  ...unestablished,
281
- sentence: findingAccount(fold, boundary.roots, boundary.face),
281
+ sentence: findingAccount(fold, boundary),
282
282
  ...(disposition === "mandated" && containment ? { details: findingDetails(fold.finding, paths) } : {}),
283
283
  ...(grant !== undefined ? { readRootCandidate: { dir: grant.dir, ...(grant.covers !== undefined ? { covers: grant.covers } : {}), clearsThisAsk: true } } : {}),
284
284
  };
285
285
  }
286
- function declarationsOf(boundary, readers) {
287
- return { readers, cwd: boundary.cwd ?? boundary.roots[0] ?? "", roots: boundary.roots, ...(boundary.homeDir !== undefined ? { homeDir: boundary.homeDir } : {}), ...(boundary.face !== undefined ? { face: boundary.face } : {}) };
286
+ function declarationsOf(boundary, readers, cdPath) {
287
+ return { readers, cwd: boundary.cwd ?? boundary.roots[0] ?? "", roots: boundary.roots, cdPath, ...(boundary.homeDir !== undefined ? { homeDir: boundary.homeDir } : {}), ...(boundary.face !== undefined ? { face: boundary.face } : {}) };
288
288
  }
289
289
  function judgePlan(plan, established, boundary) {
290
290
  return judgeEstablishedPlan(plan, established.reads, boundary, (n) => {
291
291
  const nested = established.nested.get(n);
292
- return nested === undefined ? { plan: n, segments: [], guards: stationGuards(boundary) } : judgePlan(n, nested, boundary);
292
+ return nested === undefined ? { plan: n, segments: [], guards: stationGuards(boundary), writeGuards: writeFaceGuards(boundary) } : judgePlan(n, nested, boundary);
293
293
  });
294
294
  }
295
295
  async function applyAlsoInside(judged, boundary) {
296
296
  const alsoInside = boundary.alsoInside;
297
297
  if (alsoInside === undefined)
298
298
  return judged;
299
+ const outsideRead = (f) => f.kind === "outside_roots" && f.read.candidate.face !== "write" && f.read.target !== undefined;
299
300
  const rescued = new Set();
300
301
  for (const f of allFindings(judged)) {
301
- if (f.kind !== "outside_roots" || f.read.target === undefined || rescued.has(f.read.target))
302
+ if (!outsideRead(f) || rescued.has(f.read.target))
302
303
  continue;
303
304
  if (await alsoInside(f.read.target))
304
305
  rescued.add(f.read.target);
@@ -308,28 +309,31 @@ async function applyAlsoInside(judged, boundary) {
308
309
  const rewrite = (j) => ({
309
310
  plan: j.plan,
310
311
  guards: j.guards,
312
+ writeGuards: j.writeGuards,
311
313
  segments: j.segments.map((s) => ({
312
314
  segment: s.segment,
313
- findings: s.findings.map((f) => (f.kind === "outside_roots" && f.read.target !== undefined && rescued.has(f.read.target) ? { ...f, kind: "none", containment: "inside" } : f)),
315
+ findings: s.findings.map((f) => (outsideRead(f) && rescued.has(f.read.target) ? { ...f, kind: "none", containment: "inside" } : f)),
314
316
  nested: s.nested.map(rewrite),
315
317
  })),
316
318
  });
317
319
  return rewrite(judged);
318
320
  }
319
321
  async function runShellReadStation(tool, command, declared, seat, readers, establisher, env, signal) {
320
- const plan = planShellReads(command, declarationsOf(declared, readers));
322
+ const plan = planShellReads(command, declarationsOf(declared, readers, env.cdPath));
321
323
  const established = await establisher.establish(plan, declared, signal);
322
324
  const boundary = { ...declared, roots: established.roots };
323
325
  const judged = await applyAlsoInside(judgePlan(plan, established, boundary), boundary);
324
326
  const judgedVerdict = verdictOf(tool, seat, foldShellReadFindings(judged), judged, boundary);
325
327
  const unplaced = unplacedOptionsOf(judged);
326
328
  const placed = writesPlacedOf(judged, boundary);
327
- const guarded = judgedVerdict.disposition === "refuse" ? { unplaced: false, writeProtected: false } : await writeGuardsOf(judged, boundary, placed, env, signal);
328
- const facts = guarded.writeProtected ? { ...judgedVerdict, writeProtected: true } : judgedVerdict;
329
+ const guarded = judgedVerdict.disposition === "refuse" ? { unplaced: false, writeProtected: false } : await writeGuardsOf(judged, boundary, env, signal);
330
+ const unresolved = judgedVerdict.disposition !== "refuse" && runOrderOf(judged).runs.some((s) => s.findings.some((f) => f.read.written !== undefined && f.read.unresolved === true));
331
+ const facts = { ...judgedVerdict, ...(guarded.writeProtected ? { writeProtected: true } : {}), ...(unresolved ? { targetUnresolved: true } : {}) };
329
332
  const verdict = placed && !guarded.unplaced ? { ...facts, writesPlaced: true } : unplaced !== undefined ? { ...facts, unplacedOptions: unplaced } : facts;
330
333
  return { plan, established, judged, boundary, verdict };
331
334
  }
332
335
  export async function judgeShellRead(input) {
336
+ assertNoRetiredShellReadBoundaryKeys(input.boundary);
333
337
  const readers = input.readers ?? new Set(bashClassifyDefaultAllow());
334
338
  const run = await runShellReadStation(input.tool ?? "Bash", input.command, input.boundary, input.seat, readers, createShellReadEstablisher(input.env, input.boundary.roots, { homeViews: homeViewsAt(input.env, input.readDenyHomes === undefined ? declaredReadDenyHomes(input.env) : checkedReadDenyHomes(input.readDenyHomes)) }), input.env, input.signal);
335
339
  const offer = normalizeReadRootCandidate(run.verdict.readRootCandidate);
@@ -341,8 +345,9 @@ export async function judgeShellRead(input) {
341
345
  };
342
346
  }
343
347
  export function judgeShellReadLexically(command, boundary, seat, opts) {
348
+ assertNoRetiredShellReadBoundaryKeys(boundary);
344
349
  const readers = opts?.readers ?? new Set(bashClassifyDefaultAllow());
345
- const plan = planShellReads(command, declarationsOf(boundary, readers));
350
+ const plan = planShellReads(command, declarationsOf(boundary, readers, []));
346
351
  const established = establishShellReadsLexically(plan, boundary);
347
352
  const judged = judgePlan(plan, established, boundary);
348
353
  return verdictOf(opts?.tool ?? "Bash", seat, foldShellReadFindings(judged), judged, boundary);
@@ -393,7 +398,7 @@ export function createShellReadStation(opts) {
393
398
  const member = memberOf(call) ?? toolName;
394
399
  const seat = opts.seatOf(member);
395
400
  const boundary = opts.boundaryOf(member, undefined);
396
- if (boundary === undefined || !(stationGuards(boundary) || boundary.writeDenyMatch !== undefined))
401
+ if (boundary === undefined || !(anyFaceGuards(boundary) || boundary.writeTargetJudge !== undefined))
397
402
  return { disposition: "vouch", finding: "none" };
398
403
  const fold = { finding: "unestablishable" };
399
404
  const disposition = decideShellRead("unestablishable", seat);
@@ -411,8 +416,12 @@ export function createShellReadStation(opts) {
411
416
  if (a === undefined)
412
417
  return undefined;
413
418
  admissions.delete(toolCallId);
419
+ const refused = (r) => {
420
+ opts.onAdmissionRefused?.(toolCallId);
421
+ return r;
422
+ };
414
423
  if (a.toolName !== toolName || a.command !== command) {
415
- return errorResult(`Error (${toolName}): the command that would run is not the one the read station judged for this call — refused before the spawn; re-submit it as a fresh call.`, containmentDetails([]));
424
+ return refused(errorResult(`Error (${toolName}): the command that would run is not the one the read station judged for this call — refused before the spawn; re-submit it as a fresh call.`, containmentDetails([])));
416
425
  }
417
426
  const establisher = establisherFor(a.boundary.roots);
418
427
  const { changed } = await establisher.reestablish(a.established, a.boundary, signal);
@@ -422,13 +431,13 @@ export function createShellReadStation(opts) {
422
431
  const judged = await applyAlsoInside(judgePlan(a.established.plan, again, a.boundary), a.boundary);
423
432
  const fold = foldShellReadFindings(judged);
424
433
  if (fold.finding === "deny_hit") {
425
- const r = refusalOf(toolName, fold, a.boundary.roots, outsidePathsOf(judged));
426
- return errorResult(r.sentence, r.details);
434
+ const r = refusalOf(toolName, fold, a.boundary, outsidePathsOf(judged));
435
+ return refused(errorResult(r.sentence, r.details));
427
436
  }
428
437
  const moved = changed[0];
429
438
  const identity = { finding: "identity_changed", witness: { read: moved, kind: "none", containment: "unknown" } };
430
- const r = refusalOf(toolName, identity, a.boundary.roots, []);
431
- return errorResult(r.sentence, containmentDetails(changed.map((c) => c.target).filter((t) => t !== undefined)));
439
+ const r = refusalOf(toolName, identity, a.boundary, []);
440
+ return refused(errorResult(r.sentence, containmentDetails(changed.map((c) => c.target).filter((t) => t !== undefined))));
432
441
  };
433
442
  return { check, admit };
434
443
  }
@@ -1,4 +1,4 @@
1
- import { isAbsoluteForFamily, normalizeAbsPathLexically, pathFamilyOf } from "./safety.js";
1
+ import { anchorSpelling, normalizeAbsPathLexically, pathFamilyOf } from "./safety.js";
2
2
  import { boundaryIsTheSandbox, canonicalPathIsAuthoritative } from "../../core/remote-env.js";
3
3
  import { resolveOperandSpellings } from "./bash-readonly-classifier.js";
4
4
  import { judgedCandidatesOf, sedBackupNames } from "./shell-read-write-targets.js";
@@ -57,11 +57,10 @@ function parseBracketExpression(pattern, start) {
57
57
  return { source: anyChar ? "[^/]" : `[${negate ? "^" : ""}${body}]`, end: i };
58
58
  }
59
59
  if (ch === "[" && (pattern[i + 1] === ":" || pattern[i + 1] === "." || pattern[i + 1] === "=")) {
60
- const close = pattern.indexOf(`${pattern[i + 1]}]`, i + 2);
61
- if (close < 0)
62
- return undefined;
63
60
  anyChar = true;
64
- i = close + 1;
61
+ const close = pattern.indexOf(`${pattern[i + 1]}]`, i + 2);
62
+ if (close >= 0 && (pattern[i + 1] === ":" ? /^[A-Za-z]+$/.test(pattern.slice(i + 2, close)) : close === i + 3))
63
+ i = close + 1;
65
64
  continue;
66
65
  }
67
66
  body += ch === "-" ? "-" : ch.replace(/[\]\\^]/g, "\\$&");
@@ -225,7 +224,7 @@ export function createShellReadEstablisher(env, readRoots, opts) {
225
224
  const entry = await env.fileInfo(at, signal);
226
225
  const link = entry.ok && entry.value.kind === "symlink" && linkHops > 0 ? await env.readLink?.(at, signal) : undefined;
227
226
  if (link?.ok === true) {
228
- const landing = isAbsoluteForFamily(family, link.value) ? link.value : joinUnder(parts.slice(0, -1).join("/") || "/", link.value);
227
+ const landing = anchorSpelling(family, parts.slice(0, -1).join("/") || "/", link.value);
229
228
  return await resolve([landing, ...[...tail].reverse()].join("/"), signal, true, linkHops - 1);
230
229
  }
231
230
  if (entry.ok || entry.error.code !== "not_found")
@@ -417,11 +416,11 @@ export function createShellReadEstablisher(env, readRoots, opts) {
417
416
  const real = canonicalKnown !== undefined ? { value: canonicalKnown, exact: true } : seat === "entry" ? await entryOf(spelling, signal) : await resolve(spelling, signal, candidate.face === "write");
418
417
  if (real === undefined) {
419
418
  if (resolverIsAuthoritative)
420
- return { candidate, established: "unestablishable", spellings, ...(origin !== undefined ? { origin } : {}), cause: `"${spelling}" cannot be resolved to verify where it reads` };
419
+ return { candidate, established: "unestablishable", spellings, ...(origin !== undefined ? { origin } : {}), cause: `"${spelling}" cannot be resolved to verify where it reads`, unresolved: true };
421
420
  return { candidate, established: "lexical", target: normalizeAbsPathLexically(spelling), spellings, ...(origin !== undefined ? { origin } : {}) };
422
421
  }
423
422
  if (!real.exact && real.missing !== true) {
424
- return { candidate, established: "unestablishable", spellings, ...(origin !== undefined ? { origin } : {}), cause: `"${spelling}" cannot be resolved to verify where it reads` };
423
+ return { candidate, established: "unestablishable", spellings, ...(origin !== undefined ? { origin } : {}), cause: `"${spelling}" cannot be resolved to verify where it reads`, unresolved: true };
425
424
  }
426
425
  return { candidate, established, target: real.value, spellings, ...(origin !== undefined ? { origin } : {}) };
427
426
  };
@@ -452,7 +451,7 @@ export function createShellReadEstablisher(env, readRoots, opts) {
452
451
  if (link?.ok !== true)
453
452
  return undefined;
454
453
  const dir = name.slice(0, name.search(/[^/]*$/));
455
- name = isAbsoluteForFamily(family, link.value) ? link.value : `${dir}${link.value}`;
454
+ name = dir === "" ? link.value : anchorSpelling(family, dir, link.value);
456
455
  }
457
456
  return undefined;
458
457
  };
@@ -559,7 +558,7 @@ export function createShellReadEstablisher(env, readRoots, opts) {
559
558
  };
560
559
  for (const [i, lexical] of lexicalReadsOf(candidate, boundary).entries()) {
561
560
  if (lexical.established !== "lexical") {
562
- out.push(lexical);
561
+ out.push(writtenAt(lexical, writtenSeats, writtenSeats.written.includes("landing") ? "landing" : "entry"));
563
562
  continue;
564
563
  }
565
564
  const folded = lexical.target;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * THE FUNCTION READING — whether a command's program word is a function of the text where a reader stands. A function is a
3
+ * binding of its name in the binding thread (`shell-read-bindings.ts`: defined by its head, unbound by an `unset`, visible
4
+ * and certain by the one ordered rule); this module asks that thread's {@link FunctionState} for the word the shell looks up
5
+ * first, and names what the answer means for the walk. The text's functions (`functions`) are only the CANDIDATE names: the
6
+ * ones its definition heads show, and the ones a literal `eval` text defines.
7
+ */
8
+ import { type LexedWord } from "../../shell/lex.js";
9
+ import { type FunctionState } from "./shell-read-bindings.js";
10
+ /** Does a command name one of the text's `functions` where a reader stands: the word the shell looks up first — the program,
11
+ * or the launcher in front of it (`command`, `nice`, … are words like any, and a function of their name runs instead) —
12
+ * `named`: it is a function's name and defined there (bash looks functions up before builtins: it shadows a builtin of the
13
+ * name); `maybe`: it may be one (the definition may not have run, the word is one the text computes, or an external
14
+ * launcher stands in front of a program word that may be); `undefined`: it is not (`builtin` / `command` skip functions).
15
+ * The whole word names a function (`x/f` included: a `/` tells a function from a path in no way). `stateOf`: what a
16
+ * function of a name is there ({@link BindingTable.functionState}) — the binding thread's one rule; absent, any
17
+ * definition may be the one (the question put before the walk, where no reader stands yet). */
18
+ export type FunctionCall = "named" | "maybe";
19
+ export declare function functionCallOf(argv: readonly LexedWord[], functions: ReadonlySet<string>, stateOf?: (name: string) => FunctionState): FunctionCall | undefined;
20
+ /** The functions a literal `eval` text defines — the shell that runs it is this one, so they stand after it like any: names
21
+ * the text's own definition heads do not show (a text the plan cannot read hands none; the names it makes unknown are all). */
22
+ export declare function evalDefinedFunctions(argvs: readonly (readonly LexedWord[])[], depth?: number): string[];
@@ -0,0 +1,29 @@
1
+ import { lexShell, literalTextOf } from "../../shell/lex.js";
2
+ import { readProgramPosition } from "./bash-program-position.js";
3
+ import { SPECIAL_BUILTINS, projectSegment } from "./shell-read-bindings.js";
4
+ import { reentryText } from "./shell-reentry-text.js";
5
+ export function functionCallOf(argv, functions, stateOf = () => "maybe") {
6
+ const pos = readProgramPosition(argv);
7
+ const headAt = pos.launchers[0]?.index ?? pos.program?.index;
8
+ if (headAt === undefined || functions.size === 0)
9
+ return undefined;
10
+ const stateOfWord = (w) => (w.pieces.some((p) => p.expansion !== undefined || p.literal === undefined) ? ([...functions].some((name) => stateOf(name) !== "absent") ? "maybe" : "absent") : functions.has(w.folded) ? stateOf(w.folded) : "absent");
11
+ const headWord = argv[headAt];
12
+ const head = stateOfWord(headWord);
13
+ if (head !== "absent")
14
+ return head === "defined" && !SPECIAL_BUILTINS.has(headWord.folded) ? "named" : "maybe";
15
+ if (pos.program === undefined || pos.launchers.length === 0 || pos.launchers.some((l) => l.name === "builtin" || l.name === "command"))
16
+ return undefined;
17
+ return stateOfWord(argv[pos.program.index]) === "absent" ? undefined : "maybe";
18
+ }
19
+ export function evalDefinedFunctions(argvs, depth = 0) {
20
+ return depth > 4 ? [] : argvs.flatMap((argv) => {
21
+ const pos = readProgramPosition(argv);
22
+ const words = pos.program?.basename === "eval" ? argv.slice(pos.program.index + 1).map((w) => literalTextOf(w.pieces)) : [];
23
+ const lexed = words.length > 0 && words.every((t) => t !== undefined) ? lexShell(reentryText("eval", words) ?? "") : undefined;
24
+ if (lexed === undefined || "parseFailure" in lexed)
25
+ return [];
26
+ const inner = lexed.segments.map((segment) => projectSegment(segment));
27
+ return [...inner.flatMap((p) => p.defines), ...evalDefinedFunctions(inner.map((p) => p.argv), depth + 1)];
28
+ });
29
+ }
@@ -8,7 +8,8 @@
8
8
  * of a candidate that could not be established), then containment over the canonical target alone. The
9
9
  * deny judge is the boundary's own `denyMatch` closure (`readDenySeat`, read-deny.ts) — the same function
10
10
  * object every read face mounts; nothing here compiles a pattern or re-derives a view set. The containment
11
- * judge is `withinAnyRoot` over the roots the boundary declares.
11
+ * judge is `withinAnyRoot` over the roots the boundary declares — a WRITE target's over the write roots, at the
12
+ * seat it is written, under the write face in force ({@link judgeWrite}).
12
13
  * · `foldShellReadFindings` folds a command's findings — every segment, every nested plan — to ONE word by
13
14
  * the priority `identity_changed > deny_hit > outside_roots > unestablishable > declined > none`.
14
15
  * · `SHELL_READ_DECISION` is the total table (finding × seat → disposition) that is the ONLY source of
@@ -22,6 +23,7 @@
22
23
  import type { BashReadonlyRootBoundary } from "./bash-readonly-classifier.js";
23
24
  import type { ReadCandidate, ShellReadPlan, ShellReadSegment } from "./shell-read-plan.js";
24
25
  import type { WriteSeat } from "./shell-read-write-targets.js";
26
+ import { type ContainmentFace } from "./containment-face.js";
25
27
  import type { AssertAllKeysHandled } from "../../core/ask-origin.js";
26
28
  /** WHAT the judge found about a command — a closed set, registered in `docs/CLOSED-SETS.md`. */
27
29
  export declare const SHELL_READ_FINDINGS: readonly ["deny_hit", "outside_roots", "unestablishable", "declined", "identity_changed", "none"];
@@ -122,21 +124,27 @@ export interface ShellReadBoundary extends BashReadonlyRootBoundary {
122
124
  /** The read table's rows AT or BELOW a path (`readDenySeat`'s `denyAtOrBelow`, read-deny.ts): read by the station's
123
125
  * `writesPlaced` fact only, through {@link denyRowAtOrBelow}. */
124
126
  readonly denyAtOrBelow?: (resolvedPath: string) => string | null;
125
- /** The WRITE roots (canonical) — where a write may land without a question under a mode that accepts file commands
126
- * (the task root and every additional working directory; never a read-only grant). Read for the station's
127
- * `writesPlaced` fact only; the read judgment never reads it. Absent ⇒ no write is placed. */
127
+ /** The WRITE roots (canonical) — the task root and every additional working directory, never a read-only grant: where a
128
+ * command's write targets must land ({@link judgeRead}'s write face: a target written outside them is an
129
+ * `outside_roots` finding), and so where a mode that accepts file commands may place them. Absent ⇒ where a write may
130
+ * land is unknown: a written target is `unestablishable` and nothing is placed. */
128
131
  readonly writeRoots?: readonly string[];
129
- /** The write-protection TABLE's judge — the one a write tool's target meets (its spelling, its real target and every hop
130
- * of its symlink chain): `true` ⇔ a row covers this ABSOLUTE write target; `canonical` ⇔ the path is a key the station
131
- * established (judged as a key, never as a spelling). Read by the station's write facts only: a covered target is not
132
- * placed, and the verdict says it was found ({@link import("./shell-read-check.js").ShellReadVerdict.writeProtected}). */
133
- readonly writeProtectionJudge?: (target: string, opts?: {
132
+ /** The WRITE face in force — the same seat the structured write tools read (the run's resolved write face, read once per
133
+ * judgment where the boundary is built): `"open"` skips the write roots' containment for a finding, as it skips the
134
+ * structured tools' root fence ({@link skipsRootsContainment}; absent ⇒ roots). Placement still reads the write roots:
135
+ * a mode accepting file commands accepts them inside its own directories, whatever the face. */
136
+ readonly writeFace?: ContainmentFace;
137
+ /** The leg's ONE write judgment of an ABSOLUTE write target — the one a write tool's target meets (its spelling, its real
138
+ * target and every hop of its symlink chain, under the leg's one exempt root), over the write-protection TABLE and the
139
+ * deployment's guarded write rows (`ToolPolicy.writeGuardRows`, every policy of the leg's chain) together; `canonical` ⇔
140
+ * the path is a key the station established (judged as a key, never as a spelling). `"protected"`: the table covers it —
141
+ * the target is not placed and the verdict says so ({@link import("./shell-read-check.js").ShellReadVerdict.writeProtected});
142
+ * `"guarded"`: a deployment row covers it — the target is not placed; `undefined`: nothing covers it. Read by the
143
+ * station's write facts only. A judgment that throws fails the station's answer (the caller's fail-closed arm). */
144
+ readonly writeTargetJudge?: (target: string, opts?: {
134
145
  readonly signal?: AbortSignal;
135
146
  readonly canonical?: true;
136
- }) => Promise<boolean>;
137
- /** The deployment's write-target guard (`ToolPolicy.writeTargetGuard`, every policy of the leg's chain): `true` ⇔ a
138
- * policy guards this ABSOLUTE write target. Read by the station's `writesPlaced` fact only: a guarded target is not placed. */
139
- readonly writeTargetGuard?: (target: string, signal?: AbortSignal) => Promise<boolean>;
147
+ }) => Promise<"protected" | "guarded" | undefined>;
140
148
  }
141
149
  /** How far one candidate got before it was judged. `lexical` = the folded spelling is the judged target
142
150
  * (an isolated sandbox, or a resolver that declares no authority and did not answer); `canonical` = the
@@ -163,6 +171,11 @@ export interface EstablishedRead {
163
171
  readonly walked?: true;
164
172
  /** Why the candidate could not be established. */
165
173
  readonly cause?: string;
174
+ /** The candidate's spelling WAS placed and the environment could not resolve where it leads (the whole chain unanswered by
175
+ * a resolver that declares itself authoritative, or a component it could not follow) — the reading's
176
+ * `target_unresolved` fact, as opposed to a text the station cannot read (a rewritten word, an unsettled working
177
+ * directory). Present only on an `unestablishable` read. */
178
+ readonly unresolved?: true;
166
179
  /** WHERE the command's program writes this target — present only on a write target the program itself writes, at the
167
180
  * seat this read was established at (`landing`: through the path's links; `entry`: the directory entry, a link there
168
181
  * not followed). Absent on a read, and on a write-face target the program only names (a copy's source). `child`: the
@@ -235,16 +248,30 @@ export interface JudgedSegment {
235
248
  export interface JudgedPlan {
236
249
  readonly plan: ShellReadPlan;
237
250
  readonly segments: readonly JudgedSegment[];
238
- /** {@link stationGuards} of the boundary the plan was judged against. */
251
+ /** {@link stationGuards} of the boundary the plan was judged against — what a READ finding folds by. */
239
252
  readonly guards: boolean;
253
+ /** {@link writeFaceGuards} of the same boundary — what a WRITE target's finding folds by. */
254
+ readonly writeGuards: boolean;
240
255
  }
241
256
  /**
242
257
  * Does the station GUARD anything for this boundary — a closed face (roots: containment is what it guards; an absent
243
258
  * face reads as roots, as {@link containmentOf} does), or a deny table with rows (`denyMatch` is present exactly then)?
244
259
  * Where it guards nothing (an open face, no deny row), a read it could not establish has nothing to be confirmed
245
- * against: no finding. The deny judgment itself is untouched (with no row there is nothing to hit).
260
+ * against: no finding. The deny judgment itself is untouched (with no row there is nothing to hit). The READ face's
261
+ * question only: a write target folds by {@link writeFaceGuards}.
246
262
  */
247
263
  export declare function stationGuards(boundary: Pick<ShellReadBoundary, "face" | "denyMatch">): boolean;
264
+ /**
265
+ * Does the station guard anything on the WRITE face — the same shape for the write seats: the write face in force runs
266
+ * the roots-containment step ({@link skipsRootsContainment}, the structured write tools' reading; absent ⇒ roots), or
267
+ * the write deny table has rows. Where it guards nothing (an open write face, no write row), a write target the station
268
+ * could not establish has nothing to be confirmed against — as a structured write under that face is not fenced.
269
+ */
270
+ export declare function writeFaceGuards(boundary: Pick<ShellReadBoundary, "writeFace" | "writeDenyMatch">): boolean;
271
+ /** Does the station guard anything on EITHER face? What a finding about the text's own structure folds by — a text the
272
+ * lexer could not read, a program it cannot name, program text it does not read, a working directory it lost, a text the
273
+ * shell re-enters — since what such a text reads AND what it writes are both unknown. */
274
+ export declare function anyFaceGuards(boundary: Pick<ShellReadBoundary, "face" | "denyMatch" | "writeFace" | "writeDenyMatch">): boolean;
248
275
  /** The command-level fold and the finding that produced it. `witness` is absent for `none`; for `declined`
249
276
  * the segment that declined stands in for a candidate. */
250
277
  export interface ShellReadFold {
@@ -256,8 +283,10 @@ export interface ShellReadFold {
256
283
  }
257
284
  /**
258
285
  * Fold a judged plan to ONE finding by {@link SHELL_READ_FINDING_RANK}, over every segment and every nested
259
- * plan. Where the station guards nothing ({@link JudgedPlan.guards}) an `unestablishable` or `declined` finding is no
260
- * finding: there is nothing to confirm the read against. Two structural rules ride the fold:
286
+ * plan. Where the station guards nothing an `unestablishable` or `declined` finding is no finding: there is nothing to
287
+ * confirm it against. Which guard is asked is the face the finding is about — a read candidate's by the read face's
288
+ * ({@link JudgedPlan.guards}), a write target's by the write face's ({@link JudgedPlan.writeGuards}), and a finding about
289
+ * the text's own structure (no candidate stands in for it) by either ({@link anyFaceGuards}). Two structural rules ride the fold:
261
290
  * · a plan the lexer refused (`unreadable`) is `unestablishable`, unconditionally — a quote-blind re-read
262
291
  * yields more words, never zero, so an unreadable text is never "no candidates";
263
292
  * · a SHELL re-entry segment (`sh -c S`, `eval S`, `env -S 'S'`, a launcher handing text to a shell) is at
@@ -289,7 +318,8 @@ export declare function deepestCommonDir(a: string, b: string): string | undefin
289
318
  * The ONE directory whose addition to the declared roots would answer every `outside_roots` finding of a
290
319
  * command — offered only when re-judging the same established reads with that root added leaves no
291
320
  * finding at all (MEASURED, not argued: a deny row or an unestablishable candidate beside the out-of-root
292
- * one leaves the ask standing whatever root is granted, and then nothing is offered). `exact` ⇒ the value
321
+ * one leaves the ask standing whatever root is granted, and then nothing is offered; so does a target written outside
322
+ * the WRITE roots, which no read root answers — a read grant grants no write). `exact` ⇒ the value
293
323
  * is the one path the command NAMED, offered verbatim, because the containing directory would hand out the
294
324
  * home or a root. A `..` spelling gets no offer: the folded and the kernel reading of it can disagree about
295
325
  * whether the target is back inside, which no replay of the text settles.