@sema-agent/core 5.56.0 → 5.58.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 (208) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/dist/agents/cascade.d.ts +1 -1
  3. package/dist/agents/cumulative-stats.d.ts +1 -1
  4. package/dist/agents/observer.d.ts +2 -2
  5. package/dist/agents/peer-admission.d.ts +1 -1
  6. package/dist/agents/retain-ledger.d.ts +2 -2
  7. package/dist/agents/roster-store.d.ts +8 -8
  8. package/dist/agents/send-message-tool.d.ts +13 -2
  9. package/dist/agents/send-message-tool.js +34 -12
  10. package/dist/agents/subagent-steps.d.ts +1 -1
  11. package/dist/agents/subagent.d.ts +13 -13
  12. package/dist/agents/team.d.ts +15 -6
  13. package/dist/agents/team.js +1 -0
  14. package/dist/agents/tool-filter.d.ts +2 -2
  15. package/dist/agents/verify.d.ts +1 -1
  16. package/dist/bench/metrics.d.ts +35 -35
  17. package/dist/brain/anthropic.js +15 -5
  18. package/dist/brain/circuit-breaker.js +2 -1
  19. package/dist/brain/degrading.d.ts +1 -1
  20. package/dist/brain/degrading.js +4 -1
  21. package/dist/brain/errors.d.ts +3 -3
  22. package/dist/brain/failover.js +16 -1
  23. package/dist/brain/open-responses.js +15 -5
  24. package/dist/brain/openai.js +16 -5
  25. package/dist/brain/reasoning.d.ts +2 -2
  26. package/dist/brain/repetition.d.ts +1 -1
  27. package/dist/brain/request-params.d.ts +30 -27
  28. package/dist/brain/request-params.js +1 -7
  29. package/dist/brain/route-adjudicator.d.ts +190 -0
  30. package/dist/brain/route-adjudicator.js +189 -0
  31. package/dist/brain/route-conformance.d.ts +55 -0
  32. package/dist/brain/route-conformance.js +136 -0
  33. package/dist/brain/routing.js +8 -3
  34. package/dist/brain/status-sink.d.ts +2 -2
  35. package/dist/brain/stream-shared.d.ts +1 -1
  36. package/dist/config/catalog.d.ts +5 -5
  37. package/dist/core/arg-summary.d.ts +4 -4
  38. package/dist/core/ask-class.d.ts +2 -2
  39. package/dist/core/ask-question.d.ts +1 -1
  40. package/dist/core/auto-compaction.d.ts +15 -15
  41. package/dist/core/auto-mode.d.ts +5 -5
  42. package/dist/core/background-agent-store.d.ts +20 -20
  43. package/dist/core/background-shell.d.ts +4 -4
  44. package/dist/core/checkpoint-store.d.ts +35 -27
  45. package/dist/core/context-edit.d.ts +1 -1
  46. package/dist/core/context-guard.d.ts +1 -1
  47. package/dist/core/exec-output-tail.d.ts +6 -6
  48. package/dist/core/file-snapshot-store.d.ts +8 -8
  49. package/dist/core/git-worktree-env.d.ts +3 -3
  50. package/dist/core/governance-codes.js +2 -0
  51. package/dist/core/hooks.d.ts +73 -33
  52. package/dist/core/hooks.js +87 -25
  53. package/dist/core/image-downsample.d.ts +1 -1
  54. package/dist/core/locked-config.d.ts +1 -1
  55. package/dist/core/lsp.d.ts +1 -1
  56. package/dist/core/mailbox-store.d.ts +1 -1
  57. package/dist/core/mcp.d.ts +3 -3
  58. package/dist/core/mcp.js +4 -4
  59. package/dist/core/memory-engine/consolidation-driver.d.ts +207 -0
  60. package/dist/core/memory-engine/consolidation-driver.js +378 -0
  61. package/dist/core/memory-engine/consolidation.d.ts +46 -2
  62. package/dist/core/memory-engine/consolidation.js +1 -0
  63. package/dist/core/memory-engine/data-plane.d.ts +1 -1
  64. package/dist/core/memory-engine/distiller.d.ts +550 -0
  65. package/dist/core/memory-engine/distiller.js +598 -0
  66. package/dist/core/memory-engine/dual-root.d.ts +1 -1
  67. package/dist/core/memory-engine/engine.d.ts +62 -8
  68. package/dist/core/memory-engine/engine.js +40 -4
  69. package/dist/core/memory-engine/file-backend.d.ts +1 -1
  70. package/dist/core/memory-engine/index.d.ts +4 -2
  71. package/dist/core/memory-engine/index.js +4 -2
  72. package/dist/core/memory-engine/origin-clearance.d.ts +1 -1
  73. package/dist/core/memory-engine/scope-contract.d.ts +4 -4
  74. package/dist/core/memory-engine/sync-client.d.ts +16 -16
  75. package/dist/core/memory-engine/sync.d.ts +4 -4
  76. package/dist/core/memory-recall.d.ts +1 -1
  77. package/dist/core/memory.d.ts +2 -2
  78. package/dist/core/permission-rule-consent.d.ts +230 -36
  79. package/dist/core/permission-rule-consent.js +258 -54
  80. package/dist/core/permission-rule-model.d.ts +295 -97
  81. package/dist/core/permission-rule-model.js +132 -41
  82. package/dist/core/permission-rules.d.ts +9 -9
  83. package/dist/core/remote-env.d.ts +8 -8
  84. package/dist/core/roles.d.ts +3 -3
  85. package/dist/core/roles.js +1 -0
  86. package/dist/core/runner/assemble-result.d.ts +2 -2
  87. package/dist/core/runner/compaction-call-options.d.ts +3 -3
  88. package/dist/core/runner/memory-consolidation-driver.d.ts +49 -0
  89. package/dist/core/runner/memory-consolidation-driver.js +60 -0
  90. package/dist/core/runner/memory-consolidation.d.ts +1 -1
  91. package/dist/core/runner/prepare-config-doors.d.ts +3 -3
  92. package/dist/core/runner/prepare-task.d.ts +21 -21
  93. package/dist/core/runner/prepare-task.js +54 -18
  94. package/dist/core/runner/prepare-workspace-restore.d.ts +2 -2
  95. package/dist/core/runner/runtask.d.ts +15 -12
  96. package/dist/core/runner/runtask.js +48 -0
  97. package/dist/core/runner/session-rule-policy.d.ts +1 -1
  98. package/dist/core/runner/teardown-bounded.d.ts +1 -1
  99. package/dist/core/runner/tool-disclosure.d.ts +2 -2
  100. package/dist/core/runner/turn-attachments.d.ts +11 -11
  101. package/dist/core/scheduler.d.ts +10 -5
  102. package/dist/core/secret-env.d.ts +1 -1
  103. package/dist/core/sensitive-path-policy.d.ts +1 -1
  104. package/dist/core/session-policy-store.d.ts +2 -2
  105. package/dist/core/session-reconcile.d.ts +2 -2
  106. package/dist/core/session-store.d.ts +3 -3
  107. package/dist/core/session.d.ts +1 -1
  108. package/dist/core/shutdown-debug.d.ts +2 -2
  109. package/dist/core/side-query.d.ts +14 -7
  110. package/dist/core/spec-contract.d.ts +1 -1
  111. package/dist/core/store-contracts/contract-harness.d.ts +2 -2
  112. package/dist/core/store-contracts/contract-kit-version.d.ts +2 -2
  113. package/dist/core/store-contracts/mailbox-store-contract.d.ts +1 -1
  114. package/dist/core/store-contracts/mailbox-store-contract.js +1 -1
  115. package/dist/core/task-notification.d.ts +5 -5
  116. package/dist/core/task-registry-agent.d.ts +12 -12
  117. package/dist/core/task-registry-monitor.d.ts +1 -1
  118. package/dist/core/task-registry-shared.d.ts +41 -41
  119. package/dist/core/task-registry.d.ts +12 -12
  120. package/dist/core/tool-detach.d.ts +2 -2
  121. package/dist/core/tool-errors.d.ts +3 -3
  122. package/dist/core/tool-policy.d.ts +55 -28
  123. package/dist/core/tool-result-budget.d.ts +1 -1
  124. package/dist/core/tool-result-store.d.ts +2 -2
  125. package/dist/core/tools.d.ts +1 -1
  126. package/dist/core/trace.d.ts +26 -23
  127. package/dist/core/types.d.ts +155 -70
  128. package/dist/core/untrusted-egress.d.ts +1 -1
  129. package/dist/core/untrusted-text.d.ts +7 -7
  130. package/dist/core/wiring-manifest.d.ts +5 -5
  131. package/dist/core/workflow-journal-store.d.ts +14 -14
  132. package/dist/core/workflow-run-store-contract.d.ts +1 -1
  133. package/dist/core/workflow-run-store-contract.js +1 -1
  134. package/dist/core/workflow-run-store.d.ts +4 -4
  135. package/dist/engine/compaction/compaction.d.ts +3 -3
  136. package/dist/engine/compaction/utils.d.ts +2 -2
  137. package/dist/engine/execution-env/kill-tree.d.ts +1 -1
  138. package/dist/engine/execution-env/node-execution-env.d.ts +8 -8
  139. package/dist/engine/harness/agent-harness.d.ts +6 -6
  140. package/dist/engine/harness/agent-harness.js +26 -1
  141. package/dist/engine/harness/messages.d.ts +1 -1
  142. package/dist/engine/harness/types.d.ts +15 -11
  143. package/dist/engine/llm/types.d.ts +79 -14
  144. package/dist/engine/loop/agent-loop.d.ts +3 -3
  145. package/dist/engine/loop/types.d.ts +4 -4
  146. package/dist/engine/lsp/node-lsp-manager.d.ts +2 -2
  147. package/dist/engine/session/import-validate.d.ts +1 -1
  148. package/dist/engine/session/log-digest.d.ts +1 -1
  149. package/dist/engine/session/memory-repo.d.ts +2 -2
  150. package/dist/engine/session/session.d.ts +4 -4
  151. package/dist/fixtures/index.d.ts +4 -4
  152. package/dist/index.d.ts +8 -4
  153. package/dist/index.js +5 -2
  154. package/dist/internal/llm.d.ts +1 -1
  155. package/dist/orchestration/goal.d.ts +1 -1
  156. package/dist/orchestration/run-spec.d.ts +1 -1
  157. package/dist/orchestration/run-workflow-tool.d.ts +12 -12
  158. package/dist/orchestration/workflow-governance.d.ts +4 -4
  159. package/dist/orchestration/workflow-observe.d.ts +1 -1
  160. package/dist/orchestration/workflow-script-runner.d.ts +1 -1
  161. package/dist/orchestration/workflow-script-store.d.ts +9 -9
  162. package/dist/orchestration/workflow-size-guideline.d.ts +1 -1
  163. package/dist/orchestration/workflow-types.d.ts +5 -5
  164. package/dist/orchestration/workflow.d.ts +10 -10
  165. package/dist/prompt-assembly/artifact-store.d.ts +1 -1
  166. package/dist/prompt-assembly/artifact.d.ts +1 -1
  167. package/dist/prompt-assembly/assemble.d.ts +1 -1
  168. package/dist/prompt-assembly/composer.d.ts +2 -2
  169. package/dist/prompt-assembly/epoch.d.ts +2 -2
  170. package/dist/prompt-assembly/event-registry.d.ts +1 -1
  171. package/dist/prompt-assembly/explain.d.ts +3 -3
  172. package/dist/prompt-assembly/tool-catalog.d.ts +1 -1
  173. package/dist/prompt-assembly/turn-snapshot.d.ts +4 -4
  174. package/dist/prompt-assembly/types.d.ts +12 -12
  175. package/dist/prompts/coordinator.d.ts +1 -1
  176. package/dist/prompts/default.d.ts +10 -10
  177. package/dist/prompts/default.js +2 -0
  178. package/dist/prompts/simple-sections.d.ts +3 -3
  179. package/dist/prompts/supervisor.d.ts +2 -2
  180. package/dist/scenarios/full-body.d.ts +3 -3
  181. package/dist/scenarios/scenario-registry.d.ts +6 -2
  182. package/dist/scenarios/scenario-registry.js +4 -2
  183. package/dist/stores/cc/sidecar-transcript.d.ts +3 -3
  184. package/dist/stores/file/fs-atomic.d.ts +2 -2
  185. package/dist/stores/file/index.d.ts +1 -1
  186. package/dist/stores/file/session-store.d.ts +2 -2
  187. package/dist/stores/file/workflow-journal-store.d.ts +4 -4
  188. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
  189. package/dist/tools/fs/encoding.d.ts +4 -4
  190. package/dist/tools/fs/fs-bash.d.ts +3 -3
  191. package/dist/tools/fs/fs-pdf.d.ts +1 -1
  192. package/dist/tools/fs/fs-shared.d.ts +6 -6
  193. package/dist/tools/fs/index.d.ts +2 -2
  194. package/dist/tools/fs/index.js +8 -1
  195. package/dist/tools/fs/notebook.d.ts +1 -1
  196. package/dist/tools/fs/pdf.d.ts +1 -1
  197. package/dist/tools/fs/read-deny.d.ts +1 -1
  198. package/dist/tools/fs/safety.d.ts +9 -9
  199. package/dist/tools/fs/search.d.ts +2 -2
  200. package/dist/tools/monitor.d.ts +3 -3
  201. package/dist/tools/scheduler-tools.js +28 -6
  202. package/dist/tools/task-list.d.ts +2 -2
  203. package/dist/tools/web.d.ts +19 -4
  204. package/dist/tools/web.js +8 -2
  205. package/dist/tools/worktree.d.ts +5 -5
  206. package/dist/tools/worktree.js +2 -2
  207. package/package.json +1 -1
  208. package/test/export-surface.snapshot.json +74 -3
@@ -16,15 +16,27 @@
16
16
  * write gate not covering the shell tool, stripping would turn a rule as innocuous as `Bash(ls)` into a
17
17
  * licence for `ls > ~/.ssh/authorized_keys`).
18
18
  *
19
- * CONNECTORS are the one construct the lane does speak for, and only in the EXACT form. `./gradlew
20
- * build && ./gradlew test` is one thing a person reads and approves in one glance, and the whole of it
21
- * is written into the rule; nothing is admitted that the rule text does not spell out end to end. The
22
- * PREFIX form stays single-command on both sides — as a rule BODY (`Bash(a && b:*)` is refused) and as
23
- * a MATCH (`Bash(npm:*)` does not admit `npm test && curl evil.example`, which is the whole reason the
24
- * two forms are separated here rather than sharing one matcher arm). Upstream reaches the same
25
- * placement through a per-segment evaluation of its full decision chain, where a segment DENY is
26
- * returned strictly before any whole-string allow; this lane's equivalent is that the deny/ask layer
27
- * (`permission-rule-org.ts`) judges every segment and runs ahead of the allow lane at the gate.
19
+ * CONNECTORS are the one construct the lane does speak for, in two forms. The EXACT form spells a
20
+ * whole chain and admits exactly it: `./gradlew build && ./gradlew test` is one thing a person reads
21
+ * and approves in one glance, and nothing is admitted that the rule text does not spell out end to
22
+ * end. The COMPOUND-PREFIX form (`Bash(cd /tmp && adb pull:*)`) widens that by ONE seam and no other:
23
+ * every leading segment connectors included is matched byte-for-byte like an exact rule, and only
24
+ * the FINAL segment takes appended arguments, so the rule text still reads as the concrete combination
25
+ * the person saw, with the last command's arguments free. A SINGLE-COMMAND prefix body stays
26
+ * single-command on the match side (`Bash(npm:*)` does not admit `npm test && curl evil.example`) —
27
+ * the matcher requires the command to read as exactly as many segments as the rule body names, which
28
+ * is that historical line and the compound form's own containment in one predicate. The compound-prefix
29
+ * rule TEXT is a registered divergence from upstream, which has no compound rule form at all: upstream
30
+ * reaches compound coverage through a per-segment evaluation of its full decision chain, where a
31
+ * segment DENY is returned strictly before any whole-string allow; this lane's equivalent is that the
32
+ * deny/ask layer (`permission-rule-org.ts`) judges every segment and runs ahead of the allow lane at
33
+ * the gate — a compound-prefix allow can therefore never outrun a deny on any segment it admits.
34
+ *
35
+ * design/375 adds upstream's OTHER half of that per-segment evaluation on the allow side: a compound
36
+ * whose EVERY segment is admitted by some eligible rule is covered by the CONJUNCTION of those rules
37
+ * (`findAdmittingRule` answers with the coverage SET). No single rule widens: the matcher predicates
38
+ * above are untouched, and the set form is the decision layer's answer, fenced by the same
39
+ * ahead-running per-segment deny/ask layer.
28
40
  *
29
41
  * The floor is `parseLeadingCommandName` + `splitShellCompoundSegments` — the one simple-command parser
30
42
  * and the one segmentation, both already shared with the read-only classifier, the reversibility probe,
@@ -92,7 +104,9 @@ export interface RuleAdd {
92
104
  * Organization rules have the opposite polarity (deny/ask only) and never enter this type.
93
105
  */
94
106
  export interface PersistedAllowRule {
95
- /** Canonical rule text: `Bash(git status)` (exact) or `Bash(git status:*)` (prefix). */
107
+ /** Canonical rule text: `Bash(git status)` (exact) or `Bash(git status:*)` (prefix). A prefix body
108
+ * may itself be a connector chain (`Bash(cd /tmp && adb pull:*)`) — see the module note on
109
+ * connectors for what such a rule reaches. */
96
110
  rule: string;
97
111
  tool: PersistedRuleTool;
98
112
  match: PersistedRuleMatch;
@@ -145,6 +159,16 @@ export declare const MAX_RULE_TEXT_CHARS = 512;
145
159
  * `python manage.py migrate:*` (which an import reports as skipped rather than dropping silently), and
146
160
  * an EXACT rule naming a whole interpreter command line stays legal, since it authorizes one command.
147
161
  *
162
+ * TWO READERS, and they consult this table with DIFFERENT questions — do not read either as the other.
163
+ * {@link parseAllowRuleText} asks "is this rule TEXT's own head a row?" and compares the basename as
164
+ * SPELLED, so an alias spelling (`SSH host:*`, `ssh.exe host:*`) is a different name to it and enters
165
+ * the store like any other rule. {@link genericPrefixBody} asks "should the engine PROPOSE a body with
166
+ * this head?" and normalizes first ({@link screenedHeadName}: case, trailing periods, executable
167
+ * suffix), because a proposal is the engine's own act and must not hand a person an alias of a row.
168
+ * The asymmetry is deliberate — normalizing at the validator would also refuse rule texts already
169
+ * stored, imported or hand-authored — and it is the reason the residual paragraph below is about this
170
+ * table's reach rather than about either reader.
171
+ *
148
172
  * TWO groups, and the distinction matters when the table is next edited:
149
173
  * · LANGUAGE interpreters (`node`, `python`, `ruby`, …) — the argument IS a program. This half is
150
174
  * wider than upstream's own set, deliberately, and is the argument the paragraph above makes.
@@ -197,50 +221,49 @@ export declare const BARE_INTERPRETER_NAMES: ReadonlySet<string>;
197
221
  */
198
222
  export declare const SHELL_RESERVED_WORDS: ReadonlySet<string>;
199
223
  /**
200
- * design/185 §1 — the reviewed command/subcommand grammar the PREFIX suggestion is generated from
201
- * (exactly the "reviewed command/subcommand grammar" the generator's history note names as the one
202
- * thing that would let it produce a prefix).
203
- *
204
- * A flat set of BODIES word sequences, each at least two words. A prefix candidate exists for a
205
- * command iff some body here is a word-boundary prefix of its folded form, and the LONGEST hit wins:
206
- * the deeper body is the narrower rule, so listing (or not listing) a deeper body is how this table
207
- * sets suggestion granularity per branch. No groups, no denylist, and no fallback arm: a head outside
208
- * the table, an unreviewed subcommand, a runtime-defined name (a git alias, a `git-<x>`/`cargo-<x>`
209
- * external subcommand, a gh extension, a kubectl plugin), a flag or operand in a body position and a
210
- * quoted token all fail the same way — by not being listed. Closure comes from positive enumeration
211
- * itself, never from an exclusion list racing names that only exist at runtime.
212
- *
213
- * Review criteria every row must pass BOTH axes (the same principle as the interpreter refusal
214
- * above: the rule text must not read narrower than what it grants):
215
- * · "runs what it is told to": a body whose use is fetching or naming a program to execute
216
- * (`npm exec`, `docker run`, `kubectl exec`, `gh extension`, `git submodule foreach`, the install
217
- * family) is refused — one click cannot be read as having granted arbitrary execution. Running the
218
- * WORKSPACE'S OWN pinned content (`npm run`, `npm ci`, `cargo run`, `cargo test`) is inside the
219
- * boundary: the scripts and lockfiles those execute are checked into the repository being worked on.
220
- * · "rewrites what others execute": a body whose main use is writing configuration that changes what
221
- * OTHER commands later run (`git config` hooksPath/pager/alias; `kubectl config`
222
- * exec-credential; `npm config`/`npm set` script-shell; `go env` persisted GOFLAGS/GOBIN) is
223
- * refused its readable width and its real width differ by a whole composition surface.
224
- * Past both axes there is deliberately NO "dangerousness" axis: `git push:*` and `git rebase:*` are
225
- * wide but readable, and a person nodding at that text is granting exactly that.
226
- *
227
- * Residual width, stated rather than hidden (a reviewed trade, not an oversight): a prefix rule
228
- * admits ANY arguments after its body, and some listed bodies carry flags that name a program to
229
- * execute (`go build`/`go test`/`go vet -toolexec`, `git fetch --upload-pack`, `git rebase -x`,
230
- * `git grep -O`, `git push --receive-pack`). The axes judge a body's MAIN use, not every flag
231
- * per-flag grammar is the road this module's history rejected twice, and applied consistently it
232
- * would empty the table. Three standing fences hold that residue: the org deny/ask layer runs ahead
233
- * of the rule lane and cannot be silenced by it; a mandated ask (egress/irreversibility marks,
234
- * shellGate:"always") is not rule-clearable either; and the narrower exact candidate — plus minting
235
- * no rule at all — is always on the same card.
236
- *
237
- * Maintenance: adding a row is a reviewed change keep the per-group reasoning beside it current.
238
- * The integrity pins (every body 2 words, lowercase word shape, no interpreter heads, no
239
- * duplicates) are enforced by this module's test suite. This table and the read-only classifier's
240
- * allowlists are DIFFERENT instruments and must never be merged or cross-referenced: that one is a
241
- * machine auto-allow face whose criterion is "provably read-only"; this one is a human suggestion
242
- * face whose criterion is "width a person can read off the rule text". One guards against a machine
243
- * loosening; the other against a person being misled.
224
+ * design/185 §1 — the DEEPER-THAN-TWO-WORDS half of the prefix suggestion grammar.
225
+ *
226
+ * The prefix candidate itself is produced by {@link genericPrefixBody}: a general two-word rule that
227
+ * covers every command whose second token reads as a subcommand. This table exists for the one thing
228
+ * that rule cannot expressa body DEEPER than two words so every row here makes a suggestion
229
+ * NARROWER than it would otherwise be, and never wider.
230
+ *
231
+ * A flat set of BODIES word sequences, each at least THREE words. A body takes the seat iff it is a
232
+ * word-boundary prefix of the command's folded form, and the LONGEST hit wins; with no hit the
233
+ * two-word generic body is used, and with neither there is no prefix candidate at all.
234
+ *
235
+ * Review criterion, one axis, and it is a PRECISION axis rather than a safety one: list a deeper body
236
+ * when the two-word form names a NAMESPACE rather than an action. `gh pr`, `kubectl rollout`,
237
+ * `docker compose`, `git stash`, `go mod`, `git submodule` and `yarn workspaces` are families of
238
+ * verbs, and the verb is what a person reads as the thing they are approving. Listing a row costs
239
+ * nothing and narrows one family; NOT listing one is not a refusal, since the generic two-word body
240
+ * still fills the seat.
241
+ *
242
+ * WHAT THIS TABLE NO LONGER DOES, stated because two review axes used to live here and their removal
243
+ * is a WIDENING no reader should have to reconstruct. Rows were once gated on "runs what it is told
244
+ * to" (`docker run`, `npm exec`, `cargo install`, `gh extension`) and on "rewrites what others
245
+ * execute" (`git config`, `kubectl config`, `npm config`, `go env`), and a head outside the table
246
+ * produced NO prefix at all. Under the generic rule those bodies are produced like any other, which
247
+ * is upstream's own behaviour and the adjudicated form: a positive table of two-word bodies cannot
248
+ * cover the long tail of real CLIs, and the price of its closure was saying nothing at all for
249
+ * everything outside nine families the field report that drove this change was every `adb`,
250
+ * `gradle` and `terraform` invocation asking forever, with an exact rule for one full command line as
251
+ * the only offer and no reuse the moment an argument changed.
252
+ *
253
+ * What carries that residue is therefore NOT this table, and never was — it is the three standing
254
+ * fences: the org deny/ask layer runs ahead of the rule lane and cannot be silenced by it; a mandated
255
+ * ask (egress/irreversibility marks, shellGate:"always") is not rule-clearable either; and the
256
+ * narrower exact candidate plus minting no rule at all — is always on the same card. A prefix rule
257
+ * also still admits ANY arguments after its body and never a second command, which is the property
258
+ * that makes a body's width readable off its text.
259
+ *
260
+ * Maintenance: adding a row is a one-line narrowing of one family. The integrity pins (every body
261
+ * 3 words, lowercase word shape, no interpreter heads, no duplicates) are enforced by this module's
262
+ * test suite. This table and the read-only classifier's allowlists are DIFFERENT instruments and must
263
+ * never be merged or cross-referenced: that one is a machine auto-allow face whose criterion is
264
+ * "provably read-only"; this one is a human suggestion face whose criterion is "width a person can
265
+ * read off the rule text". One guards against a machine loosening; the other against a person being
266
+ * misled.
244
267
  */
245
268
  export declare const SUGGESTION_LEXICON: readonly string[];
246
269
  /**
@@ -259,6 +282,16 @@ export declare const SUGGESTION_LEXICON: readonly string[];
259
282
  * through, so an ordinary rule text reads normally. The result is length-bounded.
260
283
  */
261
284
  export declare function escapeForDisclosure(value: unknown): string;
285
+ /**
286
+ * Does this text carry a character of the class {@link escapeForDisclosure} exists for — the class
287
+ * {@link CONTROL_CHARS_RE} names? Asked by a caller that must REFUSE such a value rather than render
288
+ * it: the two answers to "these bytes cannot honestly ride a display line" are escape it or do not
289
+ * take it, and a call site with nothing to display picks the second. Reads the same source regex as
290
+ * the escape pass, so the screen and the escape cannot drift into two character classes.
291
+ *
292
+ * Not part of the package's public surface — an internal screen for lane entry points.
293
+ */
294
+ export declare function hasUnrenderableCharacters(text: string): boolean;
262
295
  /**
263
296
  * Parse one rule text into its canonical shape, or refuse it with a reason.
264
297
  *
@@ -288,12 +321,15 @@ export declare function formatAllowRuleText(command: string, match: PersistedRul
288
321
  * caller can match a command this lane has not read. Returns false for every redirection, substitution,
289
322
  * subshell, backgrounded or escaped form.
290
323
  *
291
- * **A PREFIX rule never admits a compound.** This is the single load-bearing line of the connector
292
- * widening, and it is checked on the MATCH side rather than left to the mint side: `Bash(npm:*)` is an
293
- * ordinary, legitimately mintable rule, and if the "does this command start with `npm `" arm were
294
- * allowed to see a compound at all, that rule would admit `npm test && curl evil.example` one stored
295
- * yes to a build command turned into a standing yes to whatever is chained behind it. An EXACT rule has
296
- * no such reach by construction: it admits one string, the one it spells.
324
+ * **A prefix rule reaches past its body only inside its FINAL segment.** This is the single
325
+ * load-bearing line of the connector widening, and it is checked on the MATCH side rather than left to
326
+ * the mint side: the command must read as exactly as many segments as the rule's own body. For the
327
+ * single-command body that is the historical contract verbatim `Bash(npm:*)` is an ordinary,
328
+ * legitimately mintable rule, and if the "does this command start with `npm `" arm were allowed to see
329
+ * a compound at all, that rule would admit `npm test && curl evil.example` — one stored yes to a build
330
+ * command turned into a standing yes to whatever is chained behind it. For a compound-prefix body the
331
+ * same count equality is the containment: `Bash(a && b:*)` admits `a && b x`, never `a && b x && c`.
332
+ * An EXACT rule has no such reach by construction: it admits one string, the one it spells.
297
333
  */
298
334
  export declare function ruleAdmitsCommand(rule: Pick<PersistedAllowRule, "match" | "command">, command: string): boolean;
299
335
  /**
@@ -337,50 +373,196 @@ export declare function scopeCoversCwd(scope: RuleScope, cwd: string | undefined
337
373
  * when tombstones are applied, so a rule with an empty `adds` is a rule that no longer exists. */
338
374
  export declare function isRuleLive(rule: PersistedAllowRule): boolean;
339
375
  /**
340
- * Find the rule that admits this command for a task in `cwd`, or `undefined`.
376
+ * design/375 §5.1 — MAY this rule speak about this call's CONTEXT? Tool identity plus scope coverage,
377
+ * and nothing about the rule's liveness: the CONTEXT question is the half a not-yet-persisted rule (a
378
+ * card's proposed candidate, which has no add dots yet) can already answer.
341
379
  *
342
- * Order is defined only for reporting (the first match wins and its text is what the decision names);
343
- * no rule is stronger than another they are all the same one answer, "the person already said yes to
344
- * this".
380
+ * The tool conjunct is not decoration: more than one tool takes a shell command string, and matching on
381
+ * the command alone would let an approval given on one surface resolve a call on another. The scope
382
+ * conjunct is the project fence — project A's approval is not project B's, and a project rule with no
383
+ * cwd to compare against covers nothing (fail-closed, {@link scopeCoversCwd}).
384
+ *
385
+ * `scope` rides beside the rule rather than inside it for the PROPOSED half's sake: a
386
+ * {@link ParsedAllowRule} carries no scope, so a proposal is judged against the scope it would LAND in.
387
+ */
388
+ export declare function eligibleContext(rule: {
389
+ tool: string;
390
+ scope: RuleScope;
391
+ }, call: {
392
+ tool: string;
393
+ cwd: string | undefined;
394
+ }): boolean;
395
+ /**
396
+ * design/375 §5.1 — MAY this PERSISTED rule participate in admitting this call at all? The liveness
397
+ * layer on top of {@link eligibleContext}: a tombstoned rule (zero surviving adds) is a rule that no
398
+ * longer exists and must not count as coverage anywhere.
399
+ *
400
+ * THE single eligibility predicate for every consumer that reads the persisted store — the gate's
401
+ * conjunction arm and its coverage-set selection here, the suggestion face's coverage judgement and the
402
+ * consent prepare's ({@link segmentCoverageOf} is the shared instrument). One predicate, so a rule that
403
+ * one consumer refuses cannot be a rule another counts: the historically inlined trio of checks
404
+ * (live / tool / scope) is this function, verbatim.
405
+ */
406
+ export declare function eligiblePersisted(rule: PersistedAllowRule, call: {
407
+ tool: string;
408
+ cwd: string | undefined;
409
+ }): boolean;
410
+ /**
411
+ * Find the rules that admit this command for a task in `cwd`, or `undefined`.
412
+ *
413
+ * THE ANSWER IS A COVERAGE SET (design/375 §5.1, the upstream per-segment form): a single rule that
414
+ * admits the whole command is a one-element set — the historical arm, table order preserved verbatim —
415
+ * and a COMPOUND whose every segment is admitted by some eligible rule is covered by the CONJUNCTION of
416
+ * those rules, returned as the set that covered it. The judging ladder, in order:
417
+ * 1. the fail-closed floor (a shape the lane cannot read matches nothing), evaluated once;
418
+ * 2. the single-rule arm — whole-string exact, single/compound-prefix — exactly the historical loop:
419
+ * first table-order match wins and is what the decision names. A single rule naming the command is
420
+ * found BEFORE any conjunction: reporting one rule is narrower than reporting a set;
421
+ * 3. the conjunction arm, compounds only: every segment must be admitted by some eligible rule under
422
+ * the MATCH reading (the allow side's own under-approximating instrument — never the deny side's
423
+ * wider PROGRAM_RUNS reading). Per segment the FIRST table-order admitting rule is selected, and
424
+ * the coverage set is those selections in segment order, de-duplicated — the same command against
425
+ * the same rule table always names the same set (reporting-order determinism, the historical
426
+ * "order is defined only for reporting" contract extended to sets).
427
+ *
428
+ * What the conjunction arm deliberately does NOT change: {@link ruleAdmitsCommand} itself. A
429
+ * single-command prefix body still never admits a chain — `Bash(npm:*)` does not admit
430
+ * `npm test && curl evil.example` — but the chain is now admitted when EVERY segment has a standing
431
+ * yes, which is the upstream decision form (per-segment allow, any-segment deny decided strictly
432
+ * earlier at the gate's org layer). A trailing-terminator spelling (`a && b ;`) keeps the conservative
433
+ * answer by construction: under the MATCH reading its tail is an empty segment the floor refuses, so
434
+ * the whole command is outside the lane and asks.
435
+ *
436
+ * Order is defined only for reporting (what the decision names); no rule is stronger than another —
437
+ * they are all the same one answer, "the person already said yes to this".
345
438
  */
346
439
  export declare function findAdmittingRule(rules: readonly PersistedAllowRule[], call: {
347
440
  tool: string;
348
441
  command: string;
349
442
  cwd: string | undefined;
350
- }): PersistedAllowRule | undefined;
351
- /** A rule the engine offers to mint, as carried on an approval card. */
352
- export interface RuleSuggestion {
443
+ }): readonly PersistedAllowRule[] | undefined;
444
+ /**
445
+ * design/375 §5.2 — the per-segment coverage table for `command`: which segments an eligible rule
446
+ * already admits, judged with the SAME split, the same MATCH reading and the same eligibility
447
+ * predicates as the gate's conjunction arm — one instrument, so the suggestion face and the consent
448
+ * prepare can never call a segment covered that the gate would ask about (or vice versa).
449
+ *
450
+ * TWO SOURCES, two predicates, and the seat labels are the contract:
451
+ * · `persisted` — rules already in the store, judged {@link eligiblePersisted} ∧ admit: a tombstoned
452
+ * rule, another project's rule and another tool's rule are uniformly NOT coverage;
453
+ * · `proposed` — rules of the CURRENT batch that are not yet persisted (no add dots yet), judged
454
+ * {@link eligibleContext} ∧ admit against the scope each would land in. Judging these with the
455
+ * persisted predicate would make "this batch, once redeemed, covers the command" permanently false
456
+ * — the liveness layer is the one thing a proposal legitimately lacks.
457
+ *
458
+ * Returns `undefined` for a command the lane cannot read at all (the fail-closed floor — the caller's
459
+ * honest answer is then "no coverage table", which every consumer must treat as all-uncovered). The
460
+ * reported `segment` texts are the FOLDED spellings, trimmed — display/correlation seats, never
461
+ * adjudication inputs.
462
+ */
463
+ export declare function segmentCoverageOf(command: string, rules: {
464
+ persisted: readonly PersistedAllowRule[];
465
+ proposed?: readonly {
466
+ rule: ParsedAllowRule;
467
+ scope: RuleScope;
468
+ }[];
469
+ }, call: {
470
+ tool: string;
471
+ cwd: string | undefined;
472
+ }): readonly SegmentCoverage[] | undefined;
473
+ /** One row of a per-segment coverage table (see {@link segmentCoverageOf}): the FOLDED, trimmed
474
+ * segment text (a display/correlation seat, never adjudication input) and whether an eligible rule
475
+ * already admits it. */
476
+ export interface SegmentCoverage {
477
+ readonly segment: string;
478
+ readonly covered: boolean;
479
+ }
480
+ /** One member of a {@link RuleOffer} batch: a rule the engine offers to mint for ONE segment of a
481
+ * compound command. `segment` is the FOLDED segment text the member was minted from — a rendering
482
+ * seat, never adjudication input. */
483
+ export interface SegmentRuleSuggestion {
353
484
  /** The canonical rule text the host would redeem. */
354
- rule: string;
355
- match: PersistedRuleMatch;
485
+ readonly rule: string;
486
+ readonly match: PersistedRuleMatch;
356
487
  /** The command pattern in the rule, for a host that wants to render it without re-parsing. */
357
- command: string;
488
+ readonly command: string;
489
+ /** The (folded) segment this suggestion came from. Raw post-rewrite command bytes — a substring
490
+ * of the same call's `args`/`command` values riding beside it on every surface that carries
491
+ * this seat — so render it with contextual escaping, exactly like those. */
492
+ readonly segment: string;
358
493
  }
359
494
  /**
360
- * The 1-2 candidates offered on an approval card for `command`.
361
- *
362
- * **Array order is a documented CONTRACT, not an implementation accident**: display order = array
363
- * order = narrowest first. The EXACT form (this whole command line) is always index 0 whenever
364
- * anything is offered at all; a broader reviewed PREFIX form — at most one — follows at index 1.
365
- * Selection indices and redemption tickets are index-keyed against this order (a card's
366
- * `selectedCandidate` and its `rt.<index>.` tickets), so consumers may rely on it.
367
- *
368
- * The prefix candidate comes ONLY from {@link SUGGESTION_LEXICON} the longest reviewed body that is
369
- * a word-boundary prefix of the folded command. History, and why there is no heuristic arm: two
370
- * rounds of guessing produced two different wrong answers a bare program name (`rm -f x` →
371
- * `Bash(rm:*)`), then an operand mistaken for a subcommand (`rm harmless.txt`
372
- * `Bash(rm harmless.txt:*)`, which admits a second, unnamed target) and both failed the same way:
373
- * nothing in the command TEXT distinguishes a subcommand from an operand without a per-command
374
- * grammar. The lexicon IS that grammar, per reviewed row; anything it does not list (a bare verb, an
375
- * interpreter head, an unreviewed subcommand, a runtime-defined name) yields no prefix, with no
376
- * fallback. Naive spacing note: `folded` keeps quoted whitespace, so splitting on single spaces can
377
- * shear a quoted segment — harmless in this direction, because the sheared pieces carry quote
378
- * characters and can never equal a bare lexicon word; every suspicious shape lands on "no prefix".
495
+ * design/375 §3.1 ONE "don't ask again" option on an approval card. A CLOSED discriminated union
496
+ * (`kind`); consumers parse it per element, and an element whose `kind` they do not know is dropped
497
+ * (single-row degrade) while keeping the ORIGINAL wire index for every element they keep — selection
498
+ * and redemption are keyed on original indices, so a consumer that cannot preserve the mapping must
499
+ * suppress its persistence actions entirely (fail toward asking).
500
+ *
501
+ * · `kind: "single"` one rule covering the WHOLE command (the exact spelling, or a broader
502
+ * reviewed prefix form for a simple command).
503
+ * · `kind: "batch"` a CONJUNCTION batch of per-segment rules for a compound command. Choosing it
504
+ * is one yes to ALL of `rules` at once there is no per-member selection inside a batch; a
505
+ * consumer that wants a narrower answer picks the single offer or approves once without a rule.
506
+ * `rules` has 1..5 members (the card-lane minting cap), in segment order, deduplicated by rule
507
+ * text. `uncoveredSegments` is the honest surplus disclosure of the cap: judged at MINT time
508
+ * against the coverage snapshot the card was drawn from this batch's FINAL (deduplicated,
509
+ * capped) rules, it counts the violating segments still admitted by neither 0 means "once this
510
+ * batch is redeemed, this compound is fully covered by that snapshot's lights". It is a statement
511
+ * about the minting snapshot, not a standing guarantee: a concurrent rule removal can date it.
512
+ *
513
+ * Array contract (`ruleOffers`, the durable park row, and the consent record's projection while
514
+ * the card is PENDING all share it): at most 2 offers; the whole-string exact single is index 0
515
+ * whenever present; the batch offer — at most one — is always last. It is a draw-time contract,
516
+ * not a settled-record invariant: confirming an EDITED candidate appends a third single offer
517
+ * AFTER the batch in the same write that settles the record, so a SETTLED record's projection may
518
+ * carry it. ADVISORY display metadata, never adjudication input, and never a
519
+ * rule by itself: minting is a separate act that goes through the approval-record protocol, so a
520
+ * surface that ignores offers entirely changes nothing about what is allowed. The authoritative
521
+ * render source for a card is the PREPARED record's own offers (record-is-what-you-see); the ask-time
522
+ * projection here may drift from it across a coverage change, and the drift direction is harmless
523
+ * (the record is what gets confirmed).
524
+ */
525
+ export type RuleOffer = {
526
+ readonly kind: "single";
527
+ /** The canonical rule text the host would redeem. */
528
+ readonly rule: string;
529
+ readonly match: PersistedRuleMatch;
530
+ /** The command pattern in the rule, for a host that wants to render it without re-parsing. */
531
+ readonly command: string;
532
+ } | {
533
+ readonly kind: "batch";
534
+ /** 1..5 per-segment rules, segment order, deduplicated by rule text. */
535
+ readonly rules: readonly SegmentRuleSuggestion[];
536
+ /** Mint-time honest surplus: violating segments admitted by neither the coverage snapshot nor
537
+ * this batch's final rules (see the union doc above for the exact arithmetic). */
538
+ readonly uncoveredSegments: number;
539
+ };
540
+ /**
541
+ * The offers presented on an approval card for `command` (design/375 §5.2).
542
+ *
543
+ * **Array order is a documented CONTRACT, not an implementation accident** (see {@link RuleOffer}):
544
+ * the whole-string EXACT single is index 0 whenever present; a SIMPLE command may carry a broader
545
+ * reviewed PREFIX single after it; a COMPOUND carries at most one BATCH offer, always last.
546
+ * Selection indices are keyed against this order, so consumers may rely on it; redemption TICKETS
547
+ * are NOT — they key on the consent record's flat CANDIDATE list, and a chosen offer is redeemed
548
+ * via `redeemRuleBatch` (see the consent record's own doc for the two index spaces).
549
+ *
550
+ * The prefix single is the LONGEST body two sources agree to offer: {@link SUGGESTION_LEXICON}'s
551
+ * deeper-than-two-words rows first, and otherwise {@link genericPrefixBody}'s general two-word form
552
+ * (`argv[0]` + a token shaped like a subcommand). Read those two for the grammar, its adopted origin,
553
+ * and the operand false positive it accepts; the short version is that a bare verb (`git`), an
554
+ * interpreter or wrapper head (`sudo apt`, `node build.js`), a flag in second position
555
+ * (`git -C /repo status`) and a quoted or dotted token all still yield NO prefix. Naive spacing note:
556
+ * `folded` keeps quoted whitespace, so splitting on single spaces can shear a quoted segment —
557
+ * harmless in this direction, because the sheared pieces carry quote characters and can therefore
558
+ * match neither a lexicon word nor the generic subcommand shape; every suspicious form lands on
559
+ * "no prefix".
379
560
  *
380
561
  * Every produced candidate must survive the round trip — parse as a rule, come back as the match form
381
- * this seat is offering, AND admit the very command it was minted from. Enforced on BOTH seats,
382
- * fail-closed: a candidate that would not round-trip is silently not offered, since offering an option
383
- * redemption would refuse is worse than offering one fewer.
562
+ * its seat is offering, AND admit the very text it was minted from (the whole command for a single,
563
+ * its own segment for a batch member). Enforced on EVERY seat, fail-closed: a candidate that would
564
+ * not round-trip is silently not offered, since offering an option redemption would refuse is worse
565
+ * than offering one fewer.
384
566
  *
385
567
  * The FORM half of that check is not decoration. A command may end in the rule grammar's own prefix
386
568
  * marker — `rm :*` is a legal thing to type — and wrapping it as an exact rule produces the text
@@ -390,13 +572,29 @@ export interface RuleSuggestion {
390
572
  * for a lexicon command like `git status :*`, emitted twice — once mislabelled, once as the real prefix
391
573
  * candidate). The seat therefore believes the PARSER about what it got back, never its own request.
392
574
  *
393
- * A COMPOUND (`./gradlew build && ./gradlew test`) fills the exact seat and only that one: the offered
394
- * rule spells the whole chain and admits exactly it. That is the shape this seat was missing the
395
- * ordinary build invocation is a connector chain, and a card that could offer nothing for it made every
396
- * such command a fresh question forever, with no way for an answer to accumulate.
575
+ * A COMPOUND (`./gradlew build && ./gradlew test`) fills the exact single the offered rule spells
576
+ * the whole chain and admits exactly it and a BATCH offer besides: every segment NOT already
577
+ * covered (per `ctx.coverage`) yields its own single-segment best form a prefix when the
578
+ * single-command grammar grants one for that segment's tokens (the interception screens apply
579
+ * unchanged), otherwise the segment's exact — deduplicated by rule text in segment order and capped
580
+ * at FIVE (upstream's own batch cap; the overflow is disclosed via `uncoveredSegments` rather than
581
+ * silently truncated away). A segment whose every form the floor refuses simply contributes no
582
+ * member (and stays counted as uncovered — the honest gap). The COMPOUND-PREFIX candidate this seat
583
+ * used to mint here is deliberately NO LONGER OFFERED (design/375 §5.3): the batch takes its card
584
+ * position, while the compound-prefix GRAMMAR remains a fully supported rule family — the matcher,
585
+ * the hand-written/edited/import channels and every persisted such rule are untouched.
586
+ *
587
+ * `ctx.coverage` is the per-segment coverage table minted by the SAME store read that adjudicated
588
+ * the call (see {@link segmentCoverageOf} and the gate's engine-stamped seat) — positional over this
589
+ * function's own folded segments, belt-checked by text: a table whose length or segment texts do not
590
+ * line up is discarded whole, and an absent or discarded table reads as ALL-UNCOVERED (fail toward
591
+ * over-offering; every offered rule still passes the validator at redemption, so an over-offer widens
592
+ * nothing).
397
593
  *
398
594
  * Returns an empty array for anything the rule lane cannot speak for (redirections, substitutions,
399
595
  * subshells, backgrounding) — the card then simply carries no "don't ask again" option, which is the
400
596
  * honest answer.
401
597
  */
402
- export declare function suggestRulesForCommand(command: string): RuleSuggestion[];
598
+ export declare function suggestRulesForCommand(command: string, ctx?: {
599
+ readonly coverage?: readonly SegmentCoverage[];
600
+ }): RuleOffer[];