@sema-agent/core 5.20.0 → 5.21.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 (312) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/agents/agent-definition.d.ts +7 -0
  3. package/dist/agents/agent-transcript-tool.d.ts +30 -0
  4. package/dist/agents/builtin-agents.d.ts +60 -0
  5. package/dist/agents/cascade.d.ts +107 -0
  6. package/dist/agents/cumulative-stats.d.ts +56 -0
  7. package/dist/agents/observer.d.ts +162 -0
  8. package/dist/agents/peer-admission.d.ts +95 -0
  9. package/dist/agents/repair-loop.d.ts +226 -0
  10. package/dist/agents/retain-ledger.d.ts +141 -0
  11. package/dist/agents/roster-store.d.ts +105 -0
  12. package/dist/agents/send-message-tool.d.ts +92 -0
  13. package/dist/agents/session-util.d.ts +5 -0
  14. package/dist/agents/subagent-steps.d.ts +66 -0
  15. package/dist/agents/subagent.d.ts +600 -0
  16. package/dist/agents/suspend-guard.d.ts +29 -0
  17. package/dist/agents/teacher.d.ts +75 -0
  18. package/dist/agents/team.d.ts +120 -1
  19. package/dist/agents/tool-filter.d.ts +34 -0
  20. package/dist/agents/verify.d.ts +198 -0
  21. package/dist/bench/metrics.d.ts +455 -0
  22. package/dist/brain/anthropic.d.ts +30 -0
  23. package/dist/brain/circuit-breaker.d.ts +33 -0
  24. package/dist/brain/context-overflow.d.ts +60 -3
  25. package/dist/brain/degrading.d.ts +67 -0
  26. package/dist/brain/errors.d.ts +42 -0
  27. package/dist/brain/failover.d.ts +15 -0
  28. package/dist/brain/media-degrade.d.ts +39 -0
  29. package/dist/brain/model-presets.d.ts +31 -0
  30. package/dist/brain/open-responses.d.ts +19 -0
  31. package/dist/brain/openai.d.ts +46 -0
  32. package/dist/brain/reasoning.d.ts +106 -1
  33. package/dist/brain/repetition.d.ts +83 -0
  34. package/dist/brain/request-params.d.ts +56 -0
  35. package/dist/brain/retry.d.ts +46 -0
  36. package/dist/brain/routing.d.ts +12 -0
  37. package/dist/brain/status-sink.d.ts +30 -1
  38. package/dist/brain/stream-engine.d.ts +147 -0
  39. package/dist/brain/stream-shared.d.ts +34 -0
  40. package/dist/brain/terminal-cause.d.ts +31 -0
  41. package/dist/brain/timeout.d.ts +107 -0
  42. package/dist/brain/tool-call-id.d.ts +20 -0
  43. package/dist/brain/tool-call-repair.d.ts +13 -0
  44. package/dist/config/catalog.d.ts +47 -0
  45. package/dist/config/defaults.d.ts +33 -0
  46. package/dist/core/a2a-task-state.d.ts +53 -0
  47. package/dist/core/a2a.d.ts +51 -0
  48. package/dist/core/arg-summary.d.ts +62 -0
  49. package/dist/core/ask-question.d.ts +272 -2
  50. package/dist/core/auto-compaction.d.ts +467 -0
  51. package/dist/core/auto-compaction.js +20 -6
  52. package/dist/core/auto-mode-prompt-assets.d.ts +8 -0
  53. package/dist/core/auto-mode-prompt.d.ts +27 -0
  54. package/dist/core/auto-mode.d.ts +54 -3
  55. package/dist/core/auto-promote.d.ts +100 -0
  56. package/dist/core/background-agent-store.d.ts +292 -0
  57. package/dist/core/background-shell.d.ts +110 -0
  58. package/dist/core/cache-break-detector.d.ts +34 -0
  59. package/dist/core/canonical-json.d.ts +57 -0
  60. package/dist/core/checkpoint-store.d.ts +1574 -16
  61. package/dist/core/compliance.d.ts +30 -0
  62. package/dist/core/consolidate-scope.d.ts +75 -0
  63. package/dist/core/context-edit.d.ts +99 -0
  64. package/dist/core/context-guard.d.ts +46 -0
  65. package/dist/core/exec-gate.d.ts +44 -0
  66. package/dist/core/exec-output-tail.d.ts +61 -0
  67. package/dist/core/file-snapshot-store.d.ts +104 -0
  68. package/dist/core/fs-write-gate-policy.d.ts +36 -0
  69. package/dist/core/git-worktree-env.d.ts +31 -0
  70. package/dist/core/governance-codes.d.ts +19 -0
  71. package/dist/core/hooks.d.ts +608 -1
  72. package/dist/core/human-input-projection.d.ts +37 -0
  73. package/dist/core/human-input-projection.js +13 -0
  74. package/dist/core/image-downsample.d.ts +74 -0
  75. package/dist/core/locked-config.d.ts +37 -0
  76. package/dist/core/lsp-diagnostics.d.ts +77 -0
  77. package/dist/core/lsp-protocol.d.ts +29 -0
  78. package/dist/core/lsp-session.d.ts +60 -1
  79. package/dist/core/lsp.d.ts +150 -1
  80. package/dist/core/mailbox-store.d.ts +57 -0
  81. package/dist/core/mcp.d.ts +381 -0
  82. package/dist/core/media-byte-cap.d.ts +21 -0
  83. package/dist/core/memory-admission.d.ts +71 -0
  84. package/dist/core/memory-engine/content-origin.d.ts +64 -0
  85. package/dist/core/memory-engine/data-plane.d.ts +43 -0
  86. package/dist/core/memory-engine/dual-root.d.ts +24 -0
  87. package/dist/core/memory-engine/engine.d.ts +230 -0
  88. package/dist/core/memory-engine/engine.js +103 -35
  89. package/dist/core/memory-engine/file-backend.d.ts +155 -0
  90. package/dist/core/memory-engine/frontmatter.d.ts +16 -0
  91. package/dist/core/memory-engine/layout.d.ts +217 -0
  92. package/dist/core/memory-engine/memory-backend-contract.d.ts +12 -0
  93. package/dist/core/memory-engine/migrate.d.ts +9 -0
  94. package/dist/core/memory-engine/scan.d.ts +12 -0
  95. package/dist/core/memory-engine/scope-contract.d.ts +61 -1
  96. package/dist/core/memory-engine/sync-client.d.ts +87 -0
  97. package/dist/core/memory-engine/sync.d.ts +60 -0
  98. package/dist/core/memory-engine/tools.d.ts +41 -0
  99. package/dist/core/memory-engine/types.d.ts +188 -0
  100. package/dist/core/memory-recall.d.ts +141 -0
  101. package/dist/core/memory-vector.d.ts +20 -0
  102. package/dist/core/memory.d.ts +458 -0
  103. package/dist/core/message-utils.d.ts +6 -0
  104. package/dist/core/oracle-isolation.d.ts +69 -0
  105. package/dist/core/permission-rule-consent.d.ts +138 -0
  106. package/dist/core/permission-rule-model.d.ts +122 -0
  107. package/dist/core/permission-rule-store.d.ts +119 -3
  108. package/dist/core/permission-rules.d.ts +87 -1
  109. package/dist/core/present-plan-tool.d.ts +20 -0
  110. package/dist/core/pricing.d.ts +26 -0
  111. package/dist/core/property-harness.d.ts +86 -0
  112. package/dist/core/protocol-naming.d.ts +38 -0
  113. package/dist/core/protocol-table.d.ts +61 -0
  114. package/dist/core/push-queue.d.ts +1 -0
  115. package/dist/core/remote-env.d.ts +383 -1
  116. package/dist/core/retention-policy.d.ts +33 -0
  117. package/dist/core/retention.d.ts +51 -0
  118. package/dist/core/roles.d.ts +59 -0
  119. package/dist/core/runner/active-skill-scope.d.ts +34 -0
  120. package/dist/core/runner/assemble-result.d.ts +134 -0
  121. package/dist/core/runner/compaction-call-options.d.ts +97 -1
  122. package/dist/core/runner/grounding-signal.d.ts +10 -0
  123. package/dist/core/runner/image.d.ts +17 -0
  124. package/dist/core/runner/image.js +29 -15
  125. package/dist/core/runner/memory-consolidation.d.ts +60 -0
  126. package/dist/core/runner/prepare-memory.d.ts +59 -0
  127. package/dist/core/runner/prepare-task.d.ts +1011 -2
  128. package/dist/core/runner/prepare-task.js +12 -11
  129. package/dist/core/runner/prompt-suggestions.d.ts +7 -0
  130. package/dist/core/runner/runtask.d.ts +304 -3
  131. package/dist/core/runner/session-file-state-replay.d.ts +49 -0
  132. package/dist/core/runner/session-rule-policy.d.ts +58 -0
  133. package/dist/core/runner/strict-output-schema.d.ts +18 -3
  134. package/dist/core/runner/synthetic-tools.d.ts +113 -0
  135. package/dist/core/runner/teardown-bounded.d.ts +34 -0
  136. package/dist/core/runner/tool-disclosure.d.ts +227 -0
  137. package/dist/core/runner/tool-output-projection.d.ts +6 -0
  138. package/dist/core/runner/turn-attachments.d.ts +634 -0
  139. package/dist/core/runner/usage-accounting.d.ts +32 -0
  140. package/dist/core/runtime.d.ts +9 -0
  141. package/dist/core/safe-notify.d.ts +64 -0
  142. package/dist/core/safety-axis-vocab.d.ts +23 -0
  143. package/dist/core/safety-merge-corpus.d.ts +37 -0
  144. package/dist/core/scheduler.d.ts +121 -0
  145. package/dist/core/secret-env.d.ts +32 -0
  146. package/dist/core/select-model.d.ts +15 -0
  147. package/dist/core/sensitive-path-policy.d.ts +42 -0
  148. package/dist/core/session-policy-store.d.ts +94 -0
  149. package/dist/core/session-reconcile.d.ts +80 -0
  150. package/dist/core/session-store.d.ts +85 -0
  151. package/dist/core/session.d.ts +153 -0
  152. package/dist/core/shared-memory/contract.d.ts +22 -0
  153. package/dist/core/shared-memory/normalize.d.ts +123 -2
  154. package/dist/core/shared-memory/tools.d.ts +14 -0
  155. package/dist/core/shared-memory/types.d.ts +105 -0
  156. package/dist/core/shutdown-debug.d.ts +6 -0
  157. package/dist/core/side-query.d.ts +38 -0
  158. package/dist/core/side-query.js +6 -1
  159. package/dist/core/skill-tool-specifier.d.ts +72 -0
  160. package/dist/core/skills-directory.d.ts +100 -1
  161. package/dist/core/spec-contract.d.ts +89 -0
  162. package/dist/core/store-contracts/background-agent-store-contract.d.ts +31 -0
  163. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +26 -0
  164. package/dist/core/store-contracts/contract-harness.d.ts +20 -0
  165. package/dist/core/store-contracts/contract-kit-version.d.ts +27 -0
  166. package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +8 -0
  167. package/dist/core/store-contracts/mailbox-store-contract.d.ts +42 -0
  168. package/dist/core/store-contracts/tool-result-store-contract.d.ts +13 -0
  169. package/dist/core/strategy-store.d.ts +37 -0
  170. package/dist/core/stub-env.d.ts +7 -0
  171. package/dist/core/surrogate-safe-slice.d.ts +35 -0
  172. package/dist/core/task-notification.d.ts +202 -0
  173. package/dist/core/task-outcome.d.ts +53 -0
  174. package/dist/core/task-registry-agent.d.ts +337 -1
  175. package/dist/core/task-registry-monitor.d.ts +12 -0
  176. package/dist/core/task-registry-shared.d.ts +540 -0
  177. package/dist/core/task-registry.d.ts +343 -0
  178. package/dist/core/task-registry.js +13 -2
  179. package/dist/core/task-tool-shape.d.ts +44 -0
  180. package/dist/core/tighten-task-spec.d.ts +21 -0
  181. package/dist/core/tool-detach.d.ts +21 -0
  182. package/dist/core/tool-errors.d.ts +131 -0
  183. package/dist/core/tool-errors.js +4 -0
  184. package/dist/core/tool-name-aliases.d.ts +27 -0
  185. package/dist/core/tool-policy.d.ts +555 -0
  186. package/dist/core/tool-result-budget.d.ts +32 -0
  187. package/dist/core/tool-result-store.d.ts +174 -1
  188. package/dist/core/tools.d.ts +45 -0
  189. package/dist/core/trace.d.ts +323 -0
  190. package/dist/core/types.d.ts +3859 -2
  191. package/dist/core/untrusted-egress.d.ts +8 -0
  192. package/dist/core/untrusted-text.d.ts +156 -0
  193. package/dist/core/usage-window-store.d.ts +95 -0
  194. package/dist/core/version.d.ts +1 -0
  195. package/dist/core/warm-resume.d.ts +17 -0
  196. package/dist/core/wiring-manifest.d.ts +169 -0
  197. package/dist/core/with-retry.d.ts +24 -0
  198. package/dist/core/workflow-journal-store.d.ts +160 -0
  199. package/dist/core/workflow-run-store-contract.d.ts +25 -0
  200. package/dist/core/workflow-run-store.d.ts +119 -0
  201. package/dist/engine/compaction/compaction.d.ts +256 -1
  202. package/dist/engine/compaction/utils.d.ts +94 -0
  203. package/dist/engine/execution-env/kill-tree.d.ts +29 -0
  204. package/dist/engine/execution-env/node-execution-env.d.ts +94 -0
  205. package/dist/engine/harness/agent-harness.d.ts +116 -0
  206. package/dist/engine/harness/agent-harness.js +3 -14
  207. package/dist/engine/harness/messages.d.ts +15 -0
  208. package/dist/engine/harness/types.d.ts +464 -2
  209. package/dist/engine/llm/diagnostics.d.ts +4 -0
  210. package/dist/engine/llm/event-stream.d.ts +3 -0
  211. package/dist/engine/llm/index.d.ts +7 -0
  212. package/dist/engine/llm/types.d.ts +500 -3
  213. package/dist/engine/llm/validation.d.ts +3 -0
  214. package/dist/engine/loop/agent-loop.d.ts +87 -2
  215. package/dist/engine/loop/runtime-deps.d.ts +7 -0
  216. package/dist/engine/loop/types.d.ts +424 -0
  217. package/dist/engine/lsp/frame-decoder.d.ts +13 -0
  218. package/dist/engine/lsp/node-lsp-manager.d.ts +107 -0
  219. package/dist/engine/lsp/stdio-lsp-transport.d.ts +7 -0
  220. package/dist/engine/session/import-validate.d.ts +27 -0
  221. package/dist/engine/session/log-digest.d.ts +93 -0
  222. package/dist/engine/session/memory-repo.d.ts +6 -0
  223. package/dist/engine/session/memory-storage.d.ts +2 -0
  224. package/dist/engine/session/session.d.ts +75 -0
  225. package/dist/engine/session/storage-base.d.ts +8 -0
  226. package/dist/fixtures/index.d.ts +36 -0
  227. package/dist/index.d.ts +16 -1
  228. package/dist/index.js +0 -1
  229. package/dist/internal/harness-types.d.ts +6 -0
  230. package/dist/internal/harness.d.ts +11 -0
  231. package/dist/internal/llm.d.ts +6 -0
  232. package/dist/orchestration/builtin-workflows.d.ts +53 -0
  233. package/dist/orchestration/dev-vm-script-runner.d.ts +19 -0
  234. package/dist/orchestration/goal.d.ts +57 -0
  235. package/dist/orchestration/goal.js +3 -0
  236. package/dist/orchestration/run-spec.d.ts +42 -0
  237. package/dist/orchestration/run-workflow-tool.d.ts +169 -0
  238. package/dist/orchestration/workflow-governance.d.ts +61 -0
  239. package/dist/orchestration/workflow-meta.d.ts +28 -0
  240. package/dist/orchestration/workflow-observe.d.ts +60 -0
  241. package/dist/orchestration/workflow-primitives.d.ts +23 -1
  242. package/dist/orchestration/workflow-sandbox-conformance.d.ts +38 -0
  243. package/dist/orchestration/workflow-script-runner.d.ts +88 -0
  244. package/dist/orchestration/workflow-script-store.d.ts +98 -0
  245. package/dist/orchestration/workflow-size-guideline.d.ts +67 -0
  246. package/dist/orchestration/workflow-types.d.ts +169 -2
  247. package/dist/orchestration/workflow.d.ts +358 -0
  248. package/dist/prompt-assembly/artifact-store.d.ts +33 -0
  249. package/dist/prompt-assembly/artifact.d.ts +25 -0
  250. package/dist/prompt-assembly/assemble.d.ts +20 -0
  251. package/dist/prompt-assembly/composer.d.ts +29 -0
  252. package/dist/prompt-assembly/epoch.d.ts +55 -1
  253. package/dist/prompt-assembly/event-registry.d.ts +35 -0
  254. package/dist/prompt-assembly/explain.d.ts +12 -0
  255. package/dist/prompt-assembly/tool-catalog.d.ts +38 -0
  256. package/dist/prompt-assembly/turn-snapshot.d.ts +40 -0
  257. package/dist/prompt-assembly/types.d.ts +115 -0
  258. package/dist/prompts/coordinator.d.ts +27 -0
  259. package/dist/prompts/default.d.ts +539 -0
  260. package/dist/prompts/simple-sections.d.ts +45 -0
  261. package/dist/prompts/supervisor.d.ts +66 -0
  262. package/dist/scenarios/env.d.ts +28 -0
  263. package/dist/scenarios/full-body.d.ts +50 -0
  264. package/dist/scenarios/scenario-registry.d.ts +60 -0
  265. package/dist/scenarios/teacher-quickstart.d.ts +27 -0
  266. package/dist/server/http.d.ts +17 -0
  267. package/dist/stores/cc/lockfile.d.ts +6 -0
  268. package/dist/stores/cc/mailbox-store.d.ts +8 -0
  269. package/dist/stores/cc/roster-adapter.d.ts +4 -0
  270. package/dist/stores/cc/sidecar-transcript.d.ts +66 -0
  271. package/dist/stores/file/background-agent-store.d.ts +24 -0
  272. package/dist/stores/file/checkpoint-store.d.ts +38 -0
  273. package/dist/stores/file/file-snapshot-store.d.ts +25 -0
  274. package/dist/stores/file/fs-atomic.d.ts +155 -0
  275. package/dist/stores/file/index.d.ts +89 -0
  276. package/dist/stores/file/mailbox-store.d.ts +36 -0
  277. package/dist/stores/file/memory-store.d.ts +82 -0
  278. package/dist/stores/file/permission-rule-store.d.ts +79 -0
  279. package/dist/stores/file/session-policy-store.d.ts +28 -0
  280. package/dist/stores/file/session-store.d.ts +40 -0
  281. package/dist/stores/file/shared-ledger.d.ts +83 -0
  282. package/dist/stores/file/tool-result-store.d.ts +11 -0
  283. package/dist/stores/file/usage-window-store.d.ts +18 -0
  284. package/dist/stores/file/workflow-journal-store.d.ts +100 -0
  285. package/dist/stores/file/workflow-run-store.d.ts +11 -0
  286. package/dist/tools/fs/bash-readonly-classifier.d.ts +282 -0
  287. package/dist/tools/fs/encoding.d.ts +60 -0
  288. package/dist/tools/fs/fs-bash.d.ts +133 -0
  289. package/dist/tools/fs/fs-pdf.d.ts +28 -0
  290. package/dist/tools/fs/fs-search-tools.d.ts +5 -0
  291. package/dist/tools/fs/fs-shared.d.ts +360 -0
  292. package/dist/tools/fs/fs-write.d.ts +16 -0
  293. package/dist/tools/fs/gh-rate-limit.d.ts +22 -0
  294. package/dist/tools/fs/index.d.ts +79 -0
  295. package/dist/tools/fs/notebook.d.ts +56 -0
  296. package/dist/tools/fs/pdf.d.ts +70 -0
  297. package/dist/tools/fs/pdf.js +8 -2
  298. package/dist/tools/fs/repo-map.d.ts +10 -0
  299. package/dist/tools/fs/safety.d.ts +376 -0
  300. package/dist/tools/fs/search.d.ts +131 -0
  301. package/dist/tools/loop-tick.d.ts +24 -0
  302. package/dist/tools/monitor.d.ts +55 -0
  303. package/dist/tools/scheduler-tools.d.ts +51 -0
  304. package/dist/tools/sql-adapters.d.ts +18 -0
  305. package/dist/tools/sql.d.ts +25 -0
  306. package/dist/tools/task-list.d.ts +77 -0
  307. package/dist/tools/todo.d.ts +8 -0
  308. package/dist/tools/web.d.ts +184 -0
  309. package/dist/tools/worktree.d.ts +81 -0
  310. package/package.json +2 -2
  311. package/dist/tools/gitea-issue.d.ts +0 -13
  312. package/dist/tools/gitea-issue.js +0 -75
@@ -1,34 +1,134 @@
1
1
  import { type WorkflowJournalEntry, type WorkflowJournalStore, type ResumeClaimArgs } from "../../core/workflow-journal-store.js";
2
+ /**
3
+ * SVC-2 — the shared size bound for a journaled workflow-agent result, so ALL backends DEGRADE IDENTICALLY on
4
+ * an oversize TaskResult (no dialect divergence). Inlined from service's `workflow-journal-limits` on
5
+ * onboarding — the 5 MiB figure aligns with TiDB's ~6 MiB per-row txn-entry-size-limit (a row
6
+ * over it is REJECTED with "entry too large"); a LOAD-BEARING `append` that hit that would THROW and kill the
7
+ * whole workflow. PostgreSQL TEXT has no such low limit, so without a shared guard the same large result would
8
+ * journal fine on PG but fail the workflow on TiDB. All backends instead skip-journal a result whose serialized
9
+ * size exceeds {@link MAX_JOURNAL_RESULT_BYTES} (a no-op, never an insert/throw): the agent's ordinal is simply
10
+ * not cached, so a resume re-runs that one agent live. Resume is an optimization — a too-big-to-cache result is
11
+ * a graceful degrade, not a failure. Measured in BYTES (UTF-8) — a multibyte result can be larger than its
12
+ * `.length` in chars.
13
+ */
2
14
  export { MAX_JOURNAL_RESULT_BYTES, oversizeJournalResult } from "../../core/workflow-journal-store.js";
15
+ /**
16
+ * RB-242 — how long a resume claim survives WITHOUT an engine release. This is the backstop for an engine that
17
+ * died holding one (crash / kill -9 / box reboot): it never reaches its `finally`, so nothing but expiry can
18
+ * ever free the row, and a source run whose resume is wedged forever is a worse failure than the fork this
19
+ * guards. One hour is the balance: comfortably longer than the gap between a resume's admission and its first
20
+ * journal write (the window in which a fork actually duplicates work), and short enough that a crashed box does
21
+ * not lock a run out for a working day. A LIVE engine's own release is the normal path and is prompt — this
22
+ * value is only ever reached by a run that is no longer running.
23
+ */
3
24
  export declare const RESUME_CLAIM_TTL_MS: number;
25
+ /**
26
+ * RB-356② — a release REFRAINS from unlinking its own claim once the claim is this close to (or past) its
27
+ * expiry. Rationale: a peer may legitimately take a claim over the instant it expires, and `release` is a
28
+ * read-then-unlink pair with no compare-and-delete syscall between them — so the one situation where the
29
+ * unlink could destroy a SUCCESSOR's claim is precisely the one where the record we just read is already
30
+ * takeable. Refusing to unlink there is free: an expired claim blocks nobody (the next acquirer takes it over)
31
+ * and the sweep below reaps it. The margin only has to cover the scheduling gap between this method's read and
32
+ * its unlink; seconds is orders of magnitude more than that, and losing the last five seconds of a one-hour
33
+ * lease costs nothing.
34
+ */
4
35
  export declare const RESUME_CLAIM_RELEASE_GRACE_MS = 5000;
36
+ /**
37
+ * RB-356③ — how often (per store instance) an acquisition also SWEEPS the claims directory. The header's
38
+ * "the TTL reaps any orphan" was an aspiration, not a mechanism: nothing ever reaped anything. An expired
39
+ * claim was only removed if a LATER resume off the SAME `(sourceRunId, scope)` came along to take it over —
40
+ * and the normal shape is exactly one resume per source run, so a crash (or any release that could not
41
+ * complete) leaked one small file per source run for the lifetime of the data root. The sweep makes the
42
+ * comment true. Throttled because it is a `readdir` on the claims directory: it is opportunistic hygiene, not
43
+ * a correctness path (takeover, not the sweep, is what unblocks a contended claim promptly).
44
+ */
5
45
  export declare const RESUME_CLAIM_SWEEP_INTERVAL_MS = 60000;
46
+ /**
47
+ * RB-356③ — the sweep only reaps a claim that has been expired for at least this long. A just-expired claim
48
+ * is the exclusive business of whoever is taking it over right now; the sweep has no deadline to meet, so it
49
+ * stays well clear of that handover rather than adding a second unlinker to it.
50
+ */
6
51
  export declare const RESUME_CLAIM_REAP_GRACE_MS: number;
7
52
  export declare class FileWorkflowJournalStore implements WorkflowJournalStore {
8
53
  private readonly fsyncEnabled;
9
54
  private readonly dir;
55
+ /** RB-242: resume claims live in their own subdirectory so a claim file can never be mistaken for a run
56
+ * ledger by anything that enumerates `workflow-journal/` (GC, the torn-tail replay, an operator's `ls`). */
10
57
  private readonly claimsDir;
11
58
  private readonly shared;
12
59
  private readonly sharedKey;
13
60
  private closed;
61
+ /** RB-356③: last {@link sweepExpiredClaims} timestamp — throttles the reap to one `readdir` per interval.
62
+ * Per INSTANCE, not per directory: the sweep is idempotent hygiene, so a second instance doing its own
63
+ * first sweep is harmless (unlike the fd/scope caches above, which are authority over the directory). */
14
64
  private lastClaimSweepAt;
65
+ /** RB-61: one AppendLog per run (torn-tail truncation at open) — replaces the raw fd map. */
15
66
  private get logs();
67
+ /** Per-run committed scope, cached at first touch — the append-path scope guard must NOT re-read the whole
68
+ * ledger per append (a journaled result can be MBs; a many-agent workflow would go O(n²) on disk). One replay
69
+ * discovers it; every later append compares against the cache. */
16
70
  private get scopes();
17
71
  constructor(root: string, fsyncEnabled?: boolean);
18
72
  private pathFor;
73
+ /** [1409]③ CC local form: the run's on-disk journal path for the notification diagnostics line.
74
+ * Traversal-shaped ids (LLM-controlled on some paths) degrade to undefined, never throw. */
19
75
  locator(runId: string): string | undefined;
76
+ /** Replay a run's ledger: last-writer-wins per ordinal; a torn/unparsable line is skipped (uncommitted). */
20
77
  private replay;
21
78
  append(runId: string, scope: string, entry: WorkflowJournalEntry): Promise<void>;
22
79
  load(runId: string, scope: string): Promise<WorkflowJournalEntry[]>;
80
+ /**
81
+ * RB-242 — the claim file for `(sourceRunId, scope)`. Keyed by BOTH ([1981]): a cross-scope caller already
82
+ * resolves to an empty journal (it cannot fork anything), so letting it share one claim key would only hand
83
+ * any tenant a denial-of-resume lever over another's runs. The scope is folded in as a digest rather than
84
+ * a sanitized literal so two distinct scopes can never collapse onto one filename by sanitization.
85
+ * Throws (via `sanitizePathComponent`) on a traversal-shaped runId — callers degrade, see below.
86
+ */
23
87
  private claimPathFor;
88
+ /**
89
+ * Read a claim file, KEEPING the exact bytes it was parsed from. A missing / truncated / malformed record
90
+ * reads as NO claim (takeable).
91
+ *
92
+ * RB-356② — the raw bytes are the record's identity token. POSIX has no compare-and-delete, so the only way
93
+ * this store can check "the file I decided about is still the file on disk" immediately before removing one
94
+ * is to re-read and compare (see {@link unlinkIfUnchanged}). Since RB-354 a claim is published whole, so
95
+ * "unparsable" no longer includes "a peer is mid-acquire" — it means external truncation/corruption, which
96
+ * genuinely is not a live claim.
97
+ */
24
98
  private readClaimRaw;
99
+ /**
100
+ * RB-356② — remove a claim ONLY if the bytes on disk are still the ones the decision to remove it was made
101
+ * on. This narrows the decide→delete window to two adjacent syscalls with no user-space work between them,
102
+ * and everything a peer can publish into that window is published atomically (RB-354), so the re-read sees
103
+ * either the bytes we judged (safe to unlink) or a peer's complete new claim (we back off and re-contend).
104
+ * `expectedRaw === undefined` = the file did not parse: there is no live claim to destroy.
105
+ */
25
106
  private unlinkIfUnchanged;
107
+ /**
108
+ * RB-356③ — reap claims nobody can be holding any more (see {@link RESUME_CLAIM_SWEEP_INTERVAL_MS}).
109
+ * Best-effort throughout: a sweep failure must never turn an admission into an error. Also drops the
110
+ * `.tmp` scratch a crash could strand mid-publish, on the same TTL horizon.
111
+ */
26
112
  private sweepExpiredClaims;
113
+ /**
114
+ * RB-242 ([1981]) — admission for a resume off `sourceRunId`. The LOCAL (TOC) twin of server's SQL
115
+ * `idem_key UNIQUE` + TTL: publishing the claim file is one atomic namespace operation that fails EEXIST
116
+ * against anyone who got there first, so two processes racing on the same box (the shape the in-process
117
+ * `startWorkflow` guards cannot see — a shell restart resuming a run a still-live process is already
118
+ * resuming) produce exactly one winner.
119
+ */
27
120
  resumeClaim(input: ResumeClaimArgs): Promise<{
28
121
  granted: boolean;
29
122
  holder?: string;
30
123
  }>;
124
+ /** RB-242 ([1981]) — release ONLY a claim this `newRunId` still holds. A claim that is already gone (TTL-reaped
125
+ * and possibly taken over) is a no-op: unlinking a successor's claim would re-open the very fork this prevents. */
31
126
  releaseResumeClaim(input: ResumeClaimArgs): Promise<void>;
127
+ /** GC (service extra, mirrors the SQL twins): drop the run's ledger once the run is terminal + retained. */
32
128
  deleteByRun(runId: string): Promise<number>;
129
+ /** Release ledger fds (graceful shutdown; parity with the other file stores' dispose).
130
+ * Refcounted: the fds are shared with any other instance on the same directory, so the LAST holder
131
+ * closes them. Idempotent — a second dispose() on the same instance must not decrement twice and pull
132
+ * the ledger out from under a live peer. */
33
133
  dispose(): void;
34
134
  }
@@ -1,17 +1,24 @@
1
1
  import { type WorkflowRunListByScopeOptions, type WorkflowRunStore, type WorkflowRunSummary } from "../../core/workflow-run-store.js";
2
2
  import type { WorkflowRun } from "../../orchestration/workflow.js";
3
3
  export interface FileWorkflowRunStoreOptions {
4
+ /** When false, a ledger append is NOT fsync'd (the observability store is non-engine-critical). Default true. */
4
5
  fsync?: boolean;
6
+ /** Compact the ledger into a snapshot once it exceeds this many events (then truncate). Default 1000. */
5
7
  compactEvery?: number;
6
8
  }
7
9
  export declare class FileWorkflowRunStore implements WorkflowRunStore {
8
10
  private readonly fsyncEnabled;
9
11
  private readonly compactEvery;
12
+ /** RB-59: authoritative state is SHARED per canonical directory (see the note above the class). */
10
13
  private readonly ledger;
14
+ /** RB-145: this instance's own close latch — the refcount belongs to the DIRECTORY, not to callers. */
11
15
  private closed;
12
16
  private get runs();
13
17
  constructor(root: string, opts?: FileWorkflowRunStoreOptions);
18
+ /** Serialize an op behind any in-flight op on the same id (the in-process per-id async mutex). */
14
19
  private withLock;
20
+ /** Commit one ledger event (append + fsync) THEN flip the in-memory map (crash-safe ordering), then
21
+ * the best-effort housekeeping compaction (RB-150/RB-167) — all three steps in the base. */
15
22
  private commit;
16
23
  put(id: string, run: WorkflowRun): Promise<void>;
17
24
  get(id: string): Promise<WorkflowRun | null>;
@@ -23,7 +30,11 @@ export declare class FileWorkflowRunStore implements WorkflowRunStore {
23
30
  maxAgeMs?: number;
24
31
  keep?: number;
25
32
  }): Promise<number>;
33
+ /** Force a compaction now (test/inspection): rewrite the snapshot from the authoritative map and
34
+ * truncate the live ledger (RB-167's closeForSwap recipe lives in the base). */
26
35
  compactNow(): void;
36
+ /** Test/inspection helper: number of stored runs. */
27
37
  get size(): number;
38
+ /** Release the append handle (best-effort). The boot LOCK is released by the backend factory. */
28
39
  close(): void;
29
40
  }
@@ -1,30 +1,312 @@
1
+ /**
2
+ * Bare command names `bash_readonly` permits out of the box, ALSO the default reversible set for the
3
+ * design/80 D-2 {@link import("./fs-bash.js").bashReversibilityProbe} classifier. **Coarse first filter, NOT a security
4
+ * boundary**: {@link coarseReadonlyCheck} matches only the command NAME, so a listed command with a
5
+ * writing flag (e.g. `find -delete`, `sort -o`, `tee`) would still write. Such commands are therefore
6
+ * kept OFF this default — the list is curated to commands with NO write/mutation mode under ANY args, so
7
+ * the `effect:"read"` declaration (relied on by wake/resume reconcile + the verifier read-only boundary)
8
+ * AND the classifier's "reversible" promise both stay truthful. The deployment's tool-policy gate is the
9
+ * authoritative control (design/44 §5, council blocker #1); anything that can mutate state or run arbitrary
10
+ * code belongs on the full `bash` (effect:write, gated), not here. A deployment may widen this list,
11
+ * accepting that responsibility.
12
+ *
13
+ * design/80 D-2 final-council MAJOR: `date` (`-s`/`--set` → CLOCK_SETTIME), `hostname` (`<name>` → kernel
14
+ * hostname), and `file` (`-C -m` → compiles/writes a magic file) were REMOVED — each is read-only by NAME
15
+ * but state-MUTATING with args, which an argv[0]-only filter cannot tell apart. Leaving them in defeated
16
+ * both the `effect:read` truthfulness here and the classifier's irreversibility promise (a `date -s` would
17
+ * auto-allow an irreversible clock jump under `shellGate:"classify"`).
18
+ */
19
+ /** The verdict every unresolvable / out-of-root / unbounded finding ends on. The findings differ in what
20
+ * they found; the consequence is one consequence, and a copy that drifts reads as a second rule. */
1
21
  export declare const NOT_AUTO_ALLOWED = "\u2014 not auto-allowed";
2
22
  export declare const BASH_READONLY_DEFAULT_ALLOW: readonly string[];
23
+ /**
24
+ * G2 — options for {@link parseLeadingCommandName}. Omitting them keeps the parser byte-identical to
25
+ * what every existing caller (the `bash_readonly` declaration face, the coarse command-name policy, the
26
+ * permission-rule floor, the skill tool specifier) has always received.
27
+ */
3
28
  export interface LeadingCommandNameOptions {
29
+ /**
30
+ * Read a shell operator that sits inside a QUOTED region as ordinary argument TEXT rather than as an
31
+ * operator, so `grep -E "a|b" f.log` parses as the single simple command it is.
32
+ *
33
+ * OPT-IN, and used by ONE caller: the compound classify face ({@link classifyCompoundReadonlyDetailed}),
34
+ * which has already rejected the whole command for backslashes / `$` / backticks / subshells / line
35
+ * breaks and so is inside {@link quoteMask}'s exactness envelope. The declaration and rule faces do
36
+ * NOT set it — their contract is "a single simple command with no operator characters at all", which
37
+ * is a spelling rule about what a person can read at a glance, not a claim about bash's grammar.
38
+ *
39
+ * Fail-closed on unbalanced quoting: with a region left open, the mask is not trusted and every
40
+ * operator character counts, exactly as without this option.
41
+ */
4
42
  quotedOperatorsAreText?: boolean;
5
43
  }
44
+ /**
45
+ * The SINGLE fail-closed simple-command parser shared by `bash_readonly` ({@link coarseReadonlyCheck}), the
46
+ * `bash` reversibility classifier ({@link import("./fs-bash.js").bashReversibilityProbe}), and the coarse command-name policy
47
+ * ({@link import("../../core/tool-policy.js").createCoarseCommandNamePolicy}). It extracts the leading
48
+ * `argv[0]` command NAME of a SINGLE simple command, rejecting anything that could chain past or escape an
49
+ * argv[0]-name filter: shell operators (pipes / redirects / `;` / `&&` / `$(…)` / subshells / backticks /
50
+ * newlines / backslash), a path-prefixed command (`/usr/bin/foo`), or a leading env-assignment (`FOO=bar cmd`).
51
+ *
52
+ * Returns `{ name }` for a parseable single bare command, or `{ reject }` with a human reason otherwise. It
53
+ * does NOT inspect ARGUMENTS for write flags or consult any allowlist — that is the caller's job (the
54
+ * allowlist for `bash_readonly`, the allow/deny lists for the coarse policy). Keeping ONE parser is the whole
55
+ * point: a second argv[0] parser would drift from this one and silently open a bypass.
56
+ */
6
57
  export declare function parseLeadingCommandName(command: string, options?: LeadingCommandNameOptions): {
7
58
  name: string;
8
59
  } | {
9
60
  reject: string;
10
61
  };
62
+ /** Coarse pre-execution check for `bash_readonly`: the command must be a single bare allowlisted command
63
+ * with no shell operators. It validates the NAME only (+ blocks operators/path-prefix/env-assignment); it
64
+ * does NOT inspect arguments for write flags — keeping the allowlist free of write-capable commands and
65
+ * the policy gate are what make `effect:"read"` truthful. Returns a reason when rejected, else undefined.
66
+ * design/154: this `effect:"read"` DECLARATION face deliberately stays strict-single-command; only the
67
+ * classify face ({@link classifyCompoundReadonly} via {@link import("./fs-bash.js").bashReversibilityProbe}) segments compounds. */
11
68
  export declare function coarseReadonlyCheck(command: string, allow: ReadonlySet<string>, options?: LeadingCommandNameOptions): string | undefined;
69
+ /**
70
+ * RB-412 — the READ-BOUNDARY face of the read-only classification (opt-in).
71
+ *
72
+ * The allowlist vets `argv[0]` only, so until this face existed every listed reader could name ANY
73
+ * path: `ls /etc` and `cat /etc/passwd` classified read-only, and a deployment whose manual-mode shell
74
+ * gate auto-allows the read-only verdict served the whole filesystem with no approval prompt. CC 2.1.220
75
+ * demotes exactly these shapes (its per-command path extractor + working-directory containment check,
76
+ * pretty220 @457568) and renders an approval option naming the directory (@905642).
77
+ *
78
+ * Supplying this object turns the check ON; omitting it leaves the classifier byte-identical to its
79
+ * pre-RB-412 behaviour, so a consumer pinned to an older calling convention is unaffected.
80
+ */
12
81
  export interface BashReadonlyRootBoundary {
82
+ /** Canonical containment roots — the primary root plus any additional allowed directories. An EMPTY
83
+ * array means "no directory is allowed", so every path-shaped operand demotes (a bare `cat a.txt`
84
+ * still does not: a name with no separator can only reach the working directory, which is the
85
+ * caller's to govern). A caller that wants the check OFF omits the whole object instead. */
13
86
  roots: readonly string[];
87
+ /** Base for resolving relative operands. Defaults to `roots[0]`. Callers tracking an observable `cd`
88
+ * should pass the LIVE working directory, or a `..` operand will be judged against the wrong base. */
14
89
  cwd?: string;
90
+ /** Expansion for a leading `~`. Without it a `~`-rooted operand is unresolvable and demotes (with no
91
+ * out-of-root signal — the classifier does not know where it points, which is a different verdict
92
+ * from knowing it points outside). */
15
93
  homeDir?: string;
16
94
  }
95
+ /**
96
+ * RB-412 — the structured verdict of {@link classifyCompoundReadonlyDetailed}. `reason === undefined`
97
+ * is the read-only verdict; everything else is a demotion.
98
+ */
17
99
  export interface CompoundReadonlyVerdict {
100
+ /** Demotion reason (the same string {@link classifyCompoundReadonly} returns), or undefined. */
18
101
  reason?: string;
102
+ /** Set only when the SOLE obstacle to the read-only verdict was the root boundary: every segment is
103
+ * an allowlisted reader, no blocking/device/backgrounding arm fired, and the offending arguments are
104
+ * statically resolved paths outside the roots. A gate can therefore offer the narrow "allow reading
105
+ * from <dir>" approval instead of a generic shell prompt. Absent for every other demotion — an
106
+ * unresolvable path deliberately does NOT carry it (nothing concrete to grant). */
19
107
  outOfRootRead?: true;
108
+ /** The resolved out-of-root paths, de-duplicated, in first-seen order. Present iff `outOfRootRead`. */
20
109
  outOfRootPaths?: readonly string[];
110
+ /**
111
+ * RB-451 — the paths this scan resolved INSIDE the roots (de-duplicated, first-seen order), i.e. the
112
+ * candidates a caller holding a filesystem should re-check with symlinks resolved. Absent when there
113
+ * are none.
114
+ *
115
+ * Why the ALLOW side is reported at all: containment here is LEXICAL (see
116
+ * {@link resolveOperandLexically}), so "inside the roots" means the written spelling has an in-root
117
+ * prefix — a symlink inside a root that points out of it (`<root>/vendor` → `/etc`) passes. Closing
118
+ * that needs `realpath`, which this function deliberately cannot do (synchronous, pure, no
119
+ * ExecutionEnv). Carrying the candidates is the seam: the ENFORCING caller canonicalizes them and
120
+ * re-compares (`createBashReadonlyTool` does exactly this — a target that resolves out of root is
121
+ * refused there). Typically 0-2 entries per command.
122
+ *
123
+ * Present on BOTH arms, not only the allow arm: a command can name one out-of-root path AND one
124
+ * in-root candidate (`cat /etc/passwd inroot.txt`), and a caller that EXEMPTS the out-of-root path
125
+ * (the engine's own overflow spool, or a non-canonical spelling that resolves back inside) then goes
126
+ * on to execute — with the other candidate still unverified. The field is what that caller checks
127
+ * before running, whichever way the lexical verdict came out.
128
+ *
129
+ * Includes bare-word operands (`cat linkfile.txt`), which are NOT lexical out-of-root candidates —
130
+ * the "a name with no separator can only reach the working directory" argument is true lexically and
131
+ * false under symlinks, so those are exactly the entries a filesystem-holding caller must resolve.
132
+ * They never affect {@link reason}: a bare word cannot demote and does not here either.
133
+ */
21
134
  checkedPaths?: readonly string[];
135
+ /**
136
+ * RB-474 — operands whose spelling carries an UNEXPANDED glob (`*`, `?`, `[…]`, unquoted). Lexical
137
+ * containment is not decidable for them: the shell expands the pattern AFTER this verdict is made, and
138
+ * inside a root an expansion can land on a symlink that points out of it (`cat *` where the directory
139
+ * holds a link to an out-of-root file — reproduced end-to-end, not a theoretical shape).
140
+ *
141
+ * They are NOT in {@link checkedPaths}: a filesystem-holding resolver answers the unexpanded spelling
142
+ * with its deepest EXISTING ancestor — the directory itself — and reports nothing escaping, which is
143
+ * precisely the false "resolved, inside" answer this field exists to prevent.
144
+ *
145
+ * CONSUMER CONTRACT. A gate that auto-allows on containment must treat a verdict carrying this field as
146
+ * UNDECIDED: ask, or expand the patterns itself against the filesystem and judge every match. The
147
+ * read-only judgement itself is unaffected — `ls *` genuinely writes nothing, and {@link reason} stays
148
+ * absent — because a hard refusal here would take a routine idiom away from every caller, including the
149
+ * ones that CAN resolve it. The engine's own `bash_readonly` leg is such a caller: it expands and
150
+ * resolves each match before executing, so it keeps running these commands unprompted.
151
+ *
152
+ * `cd` is the one exception, and it fails closed: a glob there cannot be expanded into the single
153
+ * directory the compound face must track as the new working directory, so it demotes (see {@link reason}).
154
+ */
22
155
  undecidedPaths?: readonly string[];
23
156
  }
157
+ /**
158
+ * RB-412 — the single minting point for the out-of-root-read approval option text, so a gate rendering
159
+ * the prompt and any other consumer cannot drift apart. Shape follows CC 2.1.220 (@905642): the option
160
+ * shows the LAST segment of the grantable directory followed by a separator, not the full path.
161
+ *
162
+ * `directory` is the directory to be granted — a caller holding a filesystem (this classifier does not)
163
+ * resolves a file operand to its parent first; CC does the same with a stat-then-dirname step.
164
+ * The separator is always `/`: this classifier's path handling is POSIX-lexical throughout, where CC
165
+ * uses the host's native separator.
166
+ */
24
167
  export declare function formatOutOfRootReadApprovalOption(directory: string): string;
168
+ /**
169
+ * RB-412 — {@link classifyCompoundReadonly} with the structured verdict. The name/operator/blocking arms
170
+ * run FIRST and unchanged, so an existing demotion keeps its exact reason and never acquires the
171
+ * out-of-root signal; the boundary scan below only ever converts a would-be READ-ONLY verdict into a
172
+ * demotion, which is why omitting `boundary` reproduces the pre-RB-412 behaviour exactly.
173
+ */
25
174
  export declare function classifyCompoundReadonlyDetailed(command: string, allow: ReadonlySet<string>, boundary?: BashReadonlyRootBoundary): CompoundReadonlyVerdict;
175
+ /**
176
+ * RB-413 — the read boundary ALONE, for the `bash_readonly` face.
177
+ *
178
+ * `bash_readonly` declares `effect:"read"` and is admitted by {@link coarseReadonlyCheck}, an argv[0]-NAME
179
+ * filter: path operands were invisible to it, so an allowlisted reader could name any file on the host
180
+ * (`cat /etc/passwd`) and — with no approval gate on the read effect — get it. The compound face above
181
+ * answers a different question for a different consumer (a gate that can ASK); this face answers only
182
+ * "does this command read outside the roots", leaving the availability arms (blocking stdin, `tail -f`,
183
+ * device reads) untouched so a caller wiring it gains the boundary and nothing else.
184
+ *
185
+ * PRECONDITION: `command` has already passed {@link coarseReadonlyCheck}, i.e. it is ONE simple command
186
+ * with no connectors, redirects, substitutions or escapes anywhere. That is what makes a whitespace split
187
+ * exact here. Feeding it an unvetted compound is not unsafe (the connector ends up glued to a token, which
188
+ * only ever produces MORE path candidates and a stricter verdict) but the reason text would name a
189
+ * nonsense command, so callers vet first.
190
+ *
191
+ * Returns the same {@link CompoundReadonlyVerdict} the compound face returns: `reason === undefined` means
192
+ * every path operand resolved inside the roots. A caller with no approval channel treats BOTH demotion
193
+ * kinds — resolved-and-outside (`outOfRootRead`, with `outOfRootPaths`) and could-not-resolve (reason
194
+ * only) — as a refusal; there is nowhere to degrade to.
195
+ *
196
+ * SCOPE OF THE GUARANTEE (RB-448, both directions): "inside the roots" is LEXICAL containment — see
197
+ * {@link resolveOperandLexically}. A symlink inside a root that points out of it reads as in-root here,
198
+ * so this face bounds where a command may NAME a file, not where the kernel will ultimately take it.
199
+ * RB-451 — a deployment that needs filesystem-true containment resolves the operands itself, and this
200
+ * verdict hands it the list to resolve ({@link CompoundReadonlyVerdict.checkedPaths}, on both arms).
201
+ * `createBashReadonlyTool` is that caller for the `bash_readonly` leg and does it on a local env.
202
+ */
26
203
  export declare function classifySimpleCommandReadBoundary(command: string, boundary: BashReadonlyRootBoundary): CompoundReadonlyVerdict;
204
+ /**
205
+ * design/154 — compound read-only classification, reason-only face. Returns the demotion reason, or
206
+ * undefined when the command classifies read-only. RB-412 added the optional `boundary`: with it, an
207
+ * allowlisted reader whose path arguments leave the allowed directories is demoted too (use
208
+ * {@link classifyCompoundReadonlyDetailed} when the caller wants to know that WHY, e.g. to offer the
209
+ * narrow "allow reading from <dir>" approval); without it the verdict is exactly what it always was.
210
+ */
27
211
  export declare function classifyCompoundReadonly(command: string, allow: ReadonlySet<string>, boundary?: BashReadonlyRootBoundary): string | undefined;
212
+ /**
213
+ * Iteration cap for {@link classifyBoundedReadonlyPollLoop}. The bound must be a LITERAL in the
214
+ * command text (that is what makes "bounded" provable without executing anything); this cap keeps a
215
+ * literal-but-huge bound (`$(seq 1 999999)`) from turning "bounded" into "unbounded in practice".
216
+ * 120 beats × the sleep cap below ≈ 20h worst case — the walltime axis governs actual runtime; this
217
+ * arm's job is only that termination is structurally guaranteed.
218
+ */
28
219
  export declare const POLL_LOOP_MAX_BEATS = 120;
220
+ /**
221
+ * Per-`sleep` cap (seconds) for {@link classifyBoundedReadonlyPollLoop}. A literal `sleep 99999999`
222
+ * is technically bounded but practically a hang — the same hazard class the RB-41 availability arms
223
+ * demote (a green verdict that blocks until the tool timeout is green in name only).
224
+ */
29
225
  export declare const POLL_LOOP_MAX_SLEEP_SECONDS = 600;
226
+ /**
227
+ * The BOUNDED READ-ONLY POLL LOOP arm of the classify face. The compound classifier above rejects any
228
+ * control structure (`for` is not an allowlisted name), which makes `shellGate:"classify"` behave like
229
+ * `"always"` for a monitoring consumer whose whole idiom is `for i in $(seq 1 8); do tail -n 5 x.log;
230
+ * sleep 2; done`. This arm accepts EXACTLY that grammar and nothing else. Returns the rejection
231
+ * reason, or undefined when the command matches the grammar (⇒ safe to treat as reversible).
232
+ *
233
+ * It is ADDITIVE and consulted only after {@link classifyCompoundReadonly} rejected (see
234
+ * {@link import("./fs-bash.js").bashReversibilityProbe}): it can never change a verdict the plain face
235
+ * already gives, and every unparseable shape falls through to a rejection — the default path is
236
+ * untouched.
237
+ *
238
+ * SAFETY ARGUMENT — why this allowance does not increase irreversibility/egress risk. Each pillar
239
+ * maps to a test group in test/poll-loop-classifier.test.ts (G1-G6):
240
+ * G1 BOUNDED TERMINATION. The loop head must be one of three LITERAL forms with a computable
241
+ * iteration count ≤ {@link POLL_LOOP_MAX_BEATS} ({@link pollLoopBeatsOf}); `while`/`until` never
242
+ * match the skeleton. Every body command terminates: non-`sleep` segments inherit the RB-41/84/
243
+ * 108/153 availability arms via {@link classifyCompoundReadonlyDetailed} (blocking stdin, `tail
244
+ * -f`, unbounded devices all reject), and `sleep` must be a literal ≤
245
+ * {@link POLL_LOOP_MAX_SLEEP_SECONDS}. Bounded beats × terminating body ⇒ the loop halts.
246
+ * G2 NO WRITE OR EGRESS CHANNEL. Every non-`sleep` body segment must pass the SAME
247
+ * {@link coarseReadonlyCheck} parser + allowlist as the plain face (one parser, no drift);
248
+ * `sleep` touches neither filesystem nor network. No redirection (`>` `<`), no `$`/backtick
249
+ * substitution, no subshell exists anywhere: the body bans them wholesale
250
+ * ({@link POLL_BODY_HARD_REJECT}), and the head's single `$(seq <int> <int>)` exemption is an
251
+ * exact-spelling match whose two arguments are literal digits.
252
+ * G3 CONNECTOR CLOSURE. `;` is the only connector in the body; `|`/`&`/`&&`/`||` are banned there
253
+ * outright, and a nested control structure surfaces as a `for`/`do`/`done` head token that fails
254
+ * the allowlist. Nothing can chain past the vetted segment heads.
255
+ * G4 READ BOUNDARY UNCHANGED. The reassembled body goes through the boundary scan of
256
+ * {@link classifyCompoundReadonlyDetailed} with the caller's boundary — out-of-root operands
257
+ * reject exactly as on the plain face (RB-412/413, not relaxed by one rule). STRICTER here: a
258
+ * verdict carrying {@link CompoundReadonlyVerdict.undecidedPaths} (an unexpanded glob) rejects,
259
+ * because the probe face is reason-only and a repeated read of a runtime-decided target must not
260
+ * auto-allow.
261
+ * G5 STRUCTURE SOUNDNESS (the parser sees what bash executes). Whitespace is normalized first: any
262
+ * non-ASCII / control whitespace ({@link NON_ASCII_WHITESPACE}) rejects the whole command, so this
263
+ * grammar's `[ \t]`/`\s` word split and bash's `$IFS` split cannot diverge (an NBSP-joined
264
+ * `ls x` never reads as head `ls` here while bash runs one word). Quote pairing is then checked
265
+ * whole-string ({@link pollLoopQuotesBalanced}); the var/iterable region admits no quote character
266
+ * at all; so the `for … ; do … ; done` skeleton the anchored regex matches is exactly the one bash
267
+ * parses — a `done` hidden in quotes either unbalances (reject) or leaves a body token that fails
268
+ * the allowlist. Within the body, the quote-blind `;` split inherits the compound face's
269
+ * soundness argument: with all expansion metacharacters banned, quotes only ever HIDE connectors,
270
+ * so mis-splits are over-fine and the error direction is reject, never widen.
271
+ * G6 ENVIRONMENT INTEGRITY. The loop variable must be a SINGLE LOWERCASE LETTER: the `for`
272
+ * assignment mutates a shell variable, and a name like `PATH`/`IFS`/`LD_PRELOAD`/`BASH_ENV`
273
+ * would change how the body's commands RESOLVE (`for PATH in dir; do cat x; done` would run
274
+ * `dir/cat` — arbitrary code under an auto-allow). No known resolution-affecting variable is a
275
+ * single lowercase letter, and the body cannot read the variable anyway (`$` is banned there),
276
+ * so the counter is write-only and inert.
277
+ *
278
+ * ACCEPTED-COST NOTES (the residuals, recorded rather than silently tolerated — same posture as the
279
+ * RB-84 `head` note above):
280
+ * · `$(seq …)` RESOLVES A COMMAND. This is the ONE command-substitution form the ruling exempts, and
281
+ * it does run whatever `seq` resolves to on PATH (or a shell function/alias of that name). Two
282
+ * facts bound the exposure. FIRST, the trust assumption is ISOMORPHIC to the allowlist itself:
283
+ * `ls`/`cat`/`grep` are equally PATH-resolved, so a party that can shadow `seq` can already shadow
284
+ * `cat` — this arm adds no trust assumption the plain face does not already make, and the file's
285
+ * standing posture is that the tool-policy gate is the authoritative control and command-name
286
+ * resolution to the real coreutils binaries is assumed (see {@link BASH_READONLY_DEFAULT_ALLOW}).
287
+ * SECOND, the beat count is taken from the LITERAL digits, not from seq's output, so a shadowed
288
+ * `seq` emitting more items only loosens the ITERATION bound (never the read-only / no-write /
289
+ * no-egress envelope — every beat still runs allowlisted readers + literal `sleep`), and the
290
+ * walltime axis cascades a hard kill regardless. A deployment wanting NO command-substitution at
291
+ * all can prefer `{<lo>..<hi>}` or a literal list, which bash expands internally with no command
292
+ * run. (Codex adversarial review flagged the substitution as [high] and recommended dropping it;
293
+ * kept per the ruling that names `$(seq <int> <int>)` as the one exemption — recorded for a
294
+ * tightening decision, since the brace form is a drop-in equivalent.)
295
+ * · STDIN-BLOCKING BODY (an availability residual, not a safety one). CLOSED for the shape this note
296
+ * used to record: a quoted multiword operand like `grep "x y"` read, to the then whitespace-blind
297
+ * tokenizer, as two operands meeting grep's file floor, while bash passes ONE pattern and no file and
298
+ * grep blocks on stdin. {@link splitWordsQuoteAware} now counts the operand bash actually passes, so
299
+ * that command demotes on both this arm and the plain face. What REMAINS residual is the general
300
+ * statement: this arm inherits whatever the shared availability arms can see, they are best-effort
301
+ * (an operand that only turns out to be unreadable at run time is not in the text), and a hang is
302
+ * bounded by the tool timeout — outside the irreversibility / egress envelope this arm governs. The
303
+ * G1 termination guarantee is therefore stated for the NON-stdin-blocking body.
304
+ *
305
+ * NON-RESIDUAL (a divergence this arm CLOSES rather than accepts): the JS-`\s`-vs-bash-`$IFS` gap.
306
+ * Because the shared parser splits on JS `\s` (which includes NBSP/U+2000-block/U+3000/form-feed/
307
+ * vertical-tab) but bash's default `$IFS` does not, `ls x` reads as head `ls` here yet is ONE
308
+ * command word to bash. Rather than reason about the (harmless — nonexistent program, exit 127)
309
+ * consequence case by case, the whole command is rejected up front if it carries any such character
310
+ * ({@link NON_ASCII_WHITESPACE}), so the grammar's tokenization and bash's stay identical.
311
+ */
30
312
  export declare function classifyBoundedReadonlyPollLoop(command: string, allow: ReadonlySet<string>, boundary?: BashReadonlyRootBoundary): string | undefined;
@@ -1,20 +1,80 @@
1
+ /**
2
+ * 编码 parity 批(docs/CC-TOOL-PARITY-SWEEP-2026-07.md P0)— file encoding + line-ending detection
3
+ * for the Read/Edit/Write band, ported from CC's `utils/fileRead.ts` mechanism and hardened where CC
4
+ * is lossy:
5
+ *
6
+ * - BOM detection: `FF FE` → utf16le, `EF BB BF` → utf8-with-BOM, else plain utf8. (CC 88→2.1.187
7
+ * dynamic-verified: detection is BOM-only; UTF-16BE / heuristic sniffing deliberately NOT done —
8
+ * a wrong guess silently corrupts, and the BOM-less case stays a refusal at the Read layer.)
9
+ * - The MODEL-FACING text is BOM-stripped and CRLF-normalized (what the model quotes in `old_string`
10
+ * must match what Read showed it).
11
+ * RB-285 (encoding LOW, 归因收窄 — CC 220 亲核): the blanket "(CC parity)" that used to sit on this
12
+ * line was half wrong, because CC has TWO read lanes and they disagree with each other.
13
+ * · CRLF-normalize — parity on both lanes (`Zr_` @276877 and `Mqe` @58197 both `replaceAll`).
14
+ * · BOM-strip — parity on CC's READ lane only (`Zr_` @276877: `charCodeAt(0) === 65279 → slice(1)`).
15
+ * CC's EDIT/WRITE lane (`Mqe`) does NOT strip: it hands the model text with the leading U+FEFF
16
+ * still in it, and only the staleness/hash coordinate (`X9` = `CTe` + CRLF, @365630-365635)
17
+ * strips. sema strips in ONE shared decoder for every lane, so the model-facing view and the
18
+ * hash coordinate cannot drift apart — an improvement over CC's split, recorded as such rather
19
+ * than claimed as parity.
20
+ * - Write-back re-encodes with the ORIGINAL encoding, PRESERVES the BOM (CC drops it on Write — its
21
+ * lossy face, dynamic-verified W1; we keep it, recorded as a deliberate improvement), and restores
22
+ * the file's dominant line endings for Edit (Write keeps the model's endings as-is, CC decision).
23
+ */
24
+ /** Detected byte-level encoding of a text file (BOM-driven; utf8 is the no-BOM default). */
1
25
  export interface DetectedFileEncoding {
2
26
  encoding: "utf8" | "utf16le";
3
27
  hadBom: boolean;
4
28
  }
5
29
  export type DetectedLineEndings = "CRLF" | "LF";
6
30
  export interface DecodedTextFile {
31
+ /** Model-facing text: BOM stripped, `\r\n` normalized to `\n`. The staleness-hash coordinate. */
7
32
  text: string;
8
33
  encoding: DetectedFileEncoding;
34
+ /** Dominant line ending of the ORIGINAL bytes (majority vote — CC `CKt` @58184 同算法).
35
+ * Deliberately NOT per-line lossless: a mixed-endings file is unified to the dominant ending on
36
+ * the first edit (codex 镜头1/2 LOW, accepted non-goal — CC behaves the same way).
37
+ * RB-285 (encoding LOW): the COUNTING RULE is CC's; the SAMPLE is not. CC votes on the first 4096
38
+ * characters only (`CKt(s.slice(0, 4096))` @58201) while {@link detectLineEndings} below votes on
39
+ * the whole file — so a file whose first 4KB is LF and whose bulk is CRLF gets opposite verdicts.
40
+ * sema's whole-file vote is kept on purpose: the verdict decides what an Edit WRITES BACK across
41
+ * the entire file, so sampling a prefix is exactly the case where the cheap answer flips the
42
+ * remaining 99% of the lines. Registered as a deliberate delta, not parity. */
9
43
  endings: DetectedLineEndings;
44
+ /** codex 双镜头 MED: a utf16le body with an ODD byte count is TRUNCATED/corrupt — decoding would
45
+ * silently drop the dangling byte and a later write-back would destroy it permanently. Flagged so
46
+ * the tool layer fails closed instead of "repairing" the file. */
10
47
  malformed?: true;
11
48
  }
49
+ /** BOM-only encoding detection (CC `detectEncodingForResolvedPath` parity — no content heuristics). */
12
50
  export declare function detectFileEncoding(bytes: Uint8Array): DetectedFileEncoding;
51
+ /** Decode file bytes into the model-facing normalized form + the metadata needed to write back losslessly. */
13
52
  export declare function decodeTextBytes(bytes: Uint8Array): DecodedTextFile;
53
+ /**
54
+ * Re-encode text for writing back to a file that was decoded with {@link decodeTextBytes}.
55
+ *
56
+ * @param text normalized (`\n`) text when `endings` is CRLF/LF (Edit path — restore the file's
57
+ * dominant endings); pass `endings:"preserve"` to write the text's OWN line endings untouched
58
+ * (Write path — CC decision: the model sent explicit endings in `content` and meant them).
59
+ * @returns a plain string for the no-BOM utf8 + LF-or-preserve fast path (byte-compatible with the
60
+ * pre-batch writer), else the exact bytes (BOM + encoded body).
61
+ */
14
62
  export declare function encodeTextForFile(text: string, encoding: DetectedFileEncoding, endings: DetectedLineEndings | "preserve"): string | Uint8Array;
63
+ /** Normalize model-supplied match/replacement text the same way the file text was normalized
64
+ * (CC FileEditTool parity: `old_string`/`new_string` are CRLF-normalized before matching).
65
+ * Deliberately does NOT strip a leading U+FEFF: an `old_string` starting with one may be quoting
66
+ * genuine file content — use {@link normalizeFileText} only where the string stands for a whole file. */
15
67
  export declare function normalizeEditText(s: string): string;
68
+ /** RB-222 ([1937] W2) — split a leading U+FEFF off MODEL-SUPPLIED whole-file text (CC `CTe` parity,
69
+ * 220 @365630). The BOM is a file-level byte marker, not text: decodeTextBytes strips it on the way
70
+ * in, so it can never live in the normalized coordinate. Only for strings that stand for a WHOLE
71
+ * file (Write `content`, readFileState seeding) — a decoded file text's leading U+FEFF is genuine
72
+ * content and must stay. */
16
73
  export declare function splitLeadingBom(text: string): {
17
74
  hadBom: boolean;
18
75
  text: string;
19
76
  };
77
+ /** RB-222 ([1937] W2) — CC `X9` parity (220 @365635 = BOM-strip + CRLF-normalize): THE coordinate
78
+ * every readFileState hash lives in (Read/Edit/Write staleness all hash `decodeTextBytes(...).text`).
79
+ * A hash taken on raw model content with a leading U+FEFF can never agree with any later re-read. */
20
80
  export declare function normalizeFileText(s: string): string;