@sema-agent/core 5.57.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 (183) hide show
  1. package/CHANGELOG.md +48 -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 +2 -2
  9. package/dist/agents/subagent-steps.d.ts +1 -1
  10. package/dist/agents/subagent.d.ts +13 -13
  11. package/dist/agents/team.d.ts +5 -5
  12. package/dist/agents/tool-filter.d.ts +2 -2
  13. package/dist/agents/verify.d.ts +1 -1
  14. package/dist/bench/metrics.d.ts +35 -35
  15. package/dist/brain/degrading.d.ts +1 -1
  16. package/dist/brain/errors.d.ts +3 -3
  17. package/dist/brain/reasoning.d.ts +2 -2
  18. package/dist/brain/repetition.d.ts +1 -1
  19. package/dist/brain/status-sink.d.ts +2 -2
  20. package/dist/brain/stream-shared.d.ts +1 -1
  21. package/dist/config/catalog.d.ts +5 -5
  22. package/dist/core/arg-summary.d.ts +4 -4
  23. package/dist/core/ask-class.d.ts +2 -2
  24. package/dist/core/ask-question.d.ts +1 -1
  25. package/dist/core/auto-compaction.d.ts +15 -15
  26. package/dist/core/auto-mode.d.ts +5 -5
  27. package/dist/core/background-agent-store.d.ts +20 -20
  28. package/dist/core/background-shell.d.ts +4 -4
  29. package/dist/core/checkpoint-store.d.ts +35 -27
  30. package/dist/core/context-edit.d.ts +1 -1
  31. package/dist/core/context-guard.d.ts +1 -1
  32. package/dist/core/exec-output-tail.d.ts +6 -6
  33. package/dist/core/file-snapshot-store.d.ts +8 -8
  34. package/dist/core/git-worktree-env.d.ts +3 -3
  35. package/dist/core/governance-codes.js +2 -0
  36. package/dist/core/hooks.d.ts +73 -33
  37. package/dist/core/hooks.js +87 -25
  38. package/dist/core/image-downsample.d.ts +1 -1
  39. package/dist/core/locked-config.d.ts +1 -1
  40. package/dist/core/lsp.d.ts +1 -1
  41. package/dist/core/mailbox-store.d.ts +1 -1
  42. package/dist/core/mcp.d.ts +3 -3
  43. package/dist/core/memory-engine/consolidation-driver.d.ts +207 -0
  44. package/dist/core/memory-engine/consolidation-driver.js +378 -0
  45. package/dist/core/memory-engine/consolidation.d.ts +46 -2
  46. package/dist/core/memory-engine/consolidation.js +1 -0
  47. package/dist/core/memory-engine/data-plane.d.ts +1 -1
  48. package/dist/core/memory-engine/distiller.d.ts +550 -0
  49. package/dist/core/memory-engine/distiller.js +598 -0
  50. package/dist/core/memory-engine/dual-root.d.ts +1 -1
  51. package/dist/core/memory-engine/engine.d.ts +47 -3
  52. package/dist/core/memory-engine/engine.js +37 -3
  53. package/dist/core/memory-engine/file-backend.d.ts +1 -1
  54. package/dist/core/memory-engine/index.d.ts +4 -2
  55. package/dist/core/memory-engine/index.js +4 -2
  56. package/dist/core/memory-engine/origin-clearance.d.ts +1 -1
  57. package/dist/core/memory-engine/scope-contract.d.ts +4 -4
  58. package/dist/core/memory-engine/sync-client.d.ts +16 -16
  59. package/dist/core/memory-engine/sync.d.ts +4 -4
  60. package/dist/core/memory-recall.d.ts +1 -1
  61. package/dist/core/memory.d.ts +2 -2
  62. package/dist/core/permission-rule-consent.d.ts +185 -36
  63. package/dist/core/permission-rule-consent.js +219 -44
  64. package/dist/core/permission-rule-model.d.ts +194 -31
  65. package/dist/core/permission-rule-model.js +93 -35
  66. package/dist/core/permission-rules.d.ts +9 -9
  67. package/dist/core/remote-env.d.ts +8 -8
  68. package/dist/core/roles.d.ts +3 -3
  69. package/dist/core/roles.js +1 -0
  70. package/dist/core/runner/assemble-result.d.ts +2 -2
  71. package/dist/core/runner/compaction-call-options.d.ts +3 -3
  72. package/dist/core/runner/memory-consolidation-driver.d.ts +49 -0
  73. package/dist/core/runner/memory-consolidation-driver.js +60 -0
  74. package/dist/core/runner/memory-consolidation.d.ts +1 -1
  75. package/dist/core/runner/prepare-config-doors.d.ts +3 -3
  76. package/dist/core/runner/prepare-task.d.ts +21 -21
  77. package/dist/core/runner/prepare-task.js +21 -14
  78. package/dist/core/runner/prepare-workspace-restore.d.ts +2 -2
  79. package/dist/core/runner/runtask.d.ts +11 -11
  80. package/dist/core/runner/session-rule-policy.d.ts +1 -1
  81. package/dist/core/runner/teardown-bounded.d.ts +1 -1
  82. package/dist/core/runner/tool-disclosure.d.ts +2 -2
  83. package/dist/core/runner/turn-attachments.d.ts +11 -11
  84. package/dist/core/scheduler.d.ts +5 -5
  85. package/dist/core/secret-env.d.ts +1 -1
  86. package/dist/core/sensitive-path-policy.d.ts +1 -1
  87. package/dist/core/session-policy-store.d.ts +2 -2
  88. package/dist/core/session-reconcile.d.ts +2 -2
  89. package/dist/core/session-store.d.ts +3 -3
  90. package/dist/core/session.d.ts +1 -1
  91. package/dist/core/shutdown-debug.d.ts +2 -2
  92. package/dist/core/side-query.d.ts +2 -2
  93. package/dist/core/spec-contract.d.ts +1 -1
  94. package/dist/core/store-contracts/contract-harness.d.ts +2 -2
  95. package/dist/core/store-contracts/contract-kit-version.d.ts +2 -2
  96. package/dist/core/store-contracts/mailbox-store-contract.d.ts +1 -1
  97. package/dist/core/store-contracts/mailbox-store-contract.js +1 -1
  98. package/dist/core/task-notification.d.ts +5 -5
  99. package/dist/core/task-registry-agent.d.ts +12 -12
  100. package/dist/core/task-registry-monitor.d.ts +1 -1
  101. package/dist/core/task-registry-shared.d.ts +41 -41
  102. package/dist/core/task-registry.d.ts +12 -12
  103. package/dist/core/tool-detach.d.ts +2 -2
  104. package/dist/core/tool-errors.d.ts +3 -3
  105. package/dist/core/tool-policy.d.ts +55 -28
  106. package/dist/core/tool-result-budget.d.ts +1 -1
  107. package/dist/core/tool-result-store.d.ts +2 -2
  108. package/dist/core/tools.d.ts +1 -1
  109. package/dist/core/trace.d.ts +26 -23
  110. package/dist/core/types.d.ts +123 -70
  111. package/dist/core/untrusted-egress.d.ts +1 -1
  112. package/dist/core/untrusted-text.d.ts +7 -7
  113. package/dist/core/wiring-manifest.d.ts +5 -5
  114. package/dist/core/workflow-journal-store.d.ts +14 -14
  115. package/dist/core/workflow-run-store-contract.d.ts +1 -1
  116. package/dist/core/workflow-run-store-contract.js +1 -1
  117. package/dist/core/workflow-run-store.d.ts +4 -4
  118. package/dist/engine/compaction/compaction.d.ts +3 -3
  119. package/dist/engine/compaction/utils.d.ts +2 -2
  120. package/dist/engine/execution-env/kill-tree.d.ts +1 -1
  121. package/dist/engine/execution-env/node-execution-env.d.ts +8 -8
  122. package/dist/engine/harness/agent-harness.d.ts +6 -6
  123. package/dist/engine/harness/messages.d.ts +1 -1
  124. package/dist/engine/harness/types.d.ts +10 -10
  125. package/dist/engine/llm/types.d.ts +14 -14
  126. package/dist/engine/loop/agent-loop.d.ts +3 -3
  127. package/dist/engine/loop/types.d.ts +4 -4
  128. package/dist/engine/lsp/node-lsp-manager.d.ts +2 -2
  129. package/dist/engine/session/import-validate.d.ts +1 -1
  130. package/dist/engine/session/log-digest.d.ts +1 -1
  131. package/dist/engine/session/memory-repo.d.ts +2 -2
  132. package/dist/engine/session/session.d.ts +4 -4
  133. package/dist/fixtures/index.d.ts +4 -4
  134. package/dist/index.d.ts +5 -4
  135. package/dist/index.js +3 -2
  136. package/dist/orchestration/goal.d.ts +1 -1
  137. package/dist/orchestration/run-spec.d.ts +1 -1
  138. package/dist/orchestration/run-workflow-tool.d.ts +12 -12
  139. package/dist/orchestration/workflow-governance.d.ts +4 -4
  140. package/dist/orchestration/workflow-observe.d.ts +1 -1
  141. package/dist/orchestration/workflow-script-runner.d.ts +1 -1
  142. package/dist/orchestration/workflow-script-store.d.ts +9 -9
  143. package/dist/orchestration/workflow-size-guideline.d.ts +1 -1
  144. package/dist/orchestration/workflow-types.d.ts +5 -5
  145. package/dist/orchestration/workflow.d.ts +10 -10
  146. package/dist/prompt-assembly/artifact-store.d.ts +1 -1
  147. package/dist/prompt-assembly/artifact.d.ts +1 -1
  148. package/dist/prompt-assembly/assemble.d.ts +1 -1
  149. package/dist/prompt-assembly/composer.d.ts +2 -2
  150. package/dist/prompt-assembly/epoch.d.ts +2 -2
  151. package/dist/prompt-assembly/event-registry.d.ts +1 -1
  152. package/dist/prompt-assembly/explain.d.ts +3 -3
  153. package/dist/prompt-assembly/tool-catalog.d.ts +1 -1
  154. package/dist/prompt-assembly/turn-snapshot.d.ts +4 -4
  155. package/dist/prompt-assembly/types.d.ts +12 -12
  156. package/dist/prompts/coordinator.d.ts +1 -1
  157. package/dist/prompts/default.d.ts +8 -8
  158. package/dist/prompts/simple-sections.d.ts +3 -3
  159. package/dist/prompts/supervisor.d.ts +2 -2
  160. package/dist/scenarios/full-body.d.ts +3 -3
  161. package/dist/scenarios/scenario-registry.d.ts +1 -1
  162. package/dist/stores/cc/sidecar-transcript.d.ts +3 -3
  163. package/dist/stores/file/fs-atomic.d.ts +2 -2
  164. package/dist/stores/file/index.d.ts +1 -1
  165. package/dist/stores/file/session-store.d.ts +2 -2
  166. package/dist/stores/file/workflow-journal-store.d.ts +4 -4
  167. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
  168. package/dist/tools/fs/encoding.d.ts +4 -4
  169. package/dist/tools/fs/fs-bash.d.ts +3 -3
  170. package/dist/tools/fs/fs-pdf.d.ts +1 -1
  171. package/dist/tools/fs/fs-shared.d.ts +6 -6
  172. package/dist/tools/fs/index.d.ts +2 -2
  173. package/dist/tools/fs/notebook.d.ts +1 -1
  174. package/dist/tools/fs/pdf.d.ts +1 -1
  175. package/dist/tools/fs/read-deny.d.ts +1 -1
  176. package/dist/tools/fs/safety.d.ts +9 -9
  177. package/dist/tools/fs/search.d.ts +2 -2
  178. package/dist/tools/monitor.d.ts +3 -3
  179. package/dist/tools/task-list.d.ts +2 -2
  180. package/dist/tools/web.d.ts +4 -4
  181. package/dist/tools/worktree.d.ts +5 -5
  182. package/package.json +1 -1
  183. package/test/export-surface.snapshot.json +56 -3
@@ -27,7 +27,7 @@
27
27
  * this by editing a file backend's file. That is the settings-file trust model, stated rather than
28
28
  * defended against: for a file backend, host = user, no more and no less.
29
29
  */
30
- import { type RuleRejectCode, type RuleScope, type RuleDot } from "./permission-rule-model.js";
30
+ import { type RuleOffer, type RuleRejectCode, type RuleScope, type RuleDot } from "./permission-rule-model.js";
31
31
  import type { PermissionRuleStoreProvider, RuleOwner } from "./permission-rule-store.js";
32
32
  /** One candidate rule inside an approval record: the exact text and where it would apply. */
33
33
  export interface RuleCandidate {
@@ -36,6 +36,37 @@ export interface RuleCandidate {
36
36
  }
37
37
  /** What the approval record is for — it decides the provenance the minted adds carry. */
38
38
  export type RuleApprovalKind = "card" | "import" | "starter";
39
+ /**
40
+ * design/375 §4.1 — one "don't ask again" OPTION as the record stores it: references into the
41
+ * record's `candidates` by index, never rule text of its own. The display triple (rule, match,
42
+ * command) is deliberately NOT here — it is derived at projection time from the referenced
43
+ * candidate's text ({@link ruleOffersOfRecord}), so there is no stored copy to drift from the rule
44
+ * that would actually persist.
45
+ *
46
+ * · `kind: "single"` — one candidate covering the whole command.
47
+ * · `kind: "batch"` — a CONJUNCTION batch: confirming it is one yes to ALL of `candidates` at
48
+ * once, with no per-member sub-selection. On a CARD record the batch carries two MINT-TIME
49
+ * facts that cannot be re-derived from candidate texts and therefore must be stored:
50
+ * `segments` (each member's source segment, index-aligned with `candidates`) and
51
+ * `uncoveredSegments` (the honest surplus count the wire offer disclosed, judged on the
52
+ * prepare-time coverage snapshot). On an import/starter record there is no compound command
53
+ * and no coverage snapshot, so both fields must be ABSENT — honest absence, never an
54
+ * empty-string stand-in. The read-side integrity checks refuse either direction's violation.
55
+ *
56
+ * Cardinality: a batch references 1..N candidates where N is the record's own candidate count.
57
+ * The wire offer's 1..5 is the CARD-lane minting cap — a card record inherits it by being minted
58
+ * there — while an import batch of twenty is a legal record; the two contracts are deliberately
59
+ * separate and neither speaks in the other's name.
60
+ */
61
+ export type RuleOffer2 = {
62
+ readonly kind: "single";
63
+ readonly candidate: number;
64
+ } | {
65
+ readonly kind: "batch";
66
+ readonly candidates: readonly number[];
67
+ readonly segments?: readonly string[];
68
+ readonly uncoveredSegments?: number;
69
+ };
39
70
  /** A durable record of one pending/settled human decision about minting rules.
40
71
  *
41
72
  * Identity (design/182 §4.5, additive): a v1 record carries `principal` and nothing else — it stays
@@ -48,9 +79,26 @@ export interface RuleApprovalRecord {
48
79
  principal?: string;
49
80
  /** Structural owner (additive). Absent on every record a v1 tree minted. */
50
81
  owner?: RuleOwner;
82
+ /**
83
+ * design/375 §4.5 — the record-form version stamp. A stored row WITHOUT it predates this form;
84
+ * such a row confirms and redeems nothing (refused loudly, `record_schema_stale`) and is never
85
+ * read through a compatibility shim: pending approvals are short-lived, and re-triggering the
86
+ * command draws a fresh card. Stores hand pre-form rows back as {@link StaleRuleApprovalRecord}.
87
+ */
88
+ schema: 2;
51
89
  kind: RuleApprovalKind;
52
90
  state: "pending" | "approved" | "redeemed";
53
91
  candidates: RuleCandidate[];
92
+ /**
93
+ * The card's OPTION structure over `candidates` (design/375 §4.1). The flat candidate list stays
94
+ * the redemption/replay anchor — tickets, `redeemedDots` and the store writes all key on
95
+ * candidate indices, unchanged — while confirmation keys on OFFER indices into this array.
96
+ * Import/starter records are the whole-list normalization: one batch offer over every candidate,
97
+ * so all three entries speak one protocol. Offers are pairwise DISJOINT in membership (an
98
+ * unchosen offer's members must stay unredeemable, which overlapping offers could not
99
+ * guarantee); the mint sites satisfy that by construction and the read side refuses violations.
100
+ */
101
+ offers: RuleOffer2[];
54
102
  createdAt: string;
55
103
  /** The ask this record was drawn from, for reconciliation. Advisory metadata; never adjudication input.
56
104
  * `boundInputHash` is ALSO the card-edit binding anchor: an edited-candidate confirmation must echo
@@ -69,23 +117,25 @@ export interface RuleApprovalRecord {
69
117
  * comparing state alone cannot separate two different writes that both leave the state unchanged. */
70
118
  rev: number;
71
119
  /**
72
- * Which candidate the person actually chose, recorded by the confirmation transfer.
120
+ * Which OFFER the person actually chose, recorded by the confirmation transfer (design/375 §4.3;
121
+ * takes the seat the per-candidate selection held before offers existed).
73
122
  *
74
- * A card offers alternatives the exact command and, sometimes, a broader prefix and a human picks
75
- * ONE. Without this, confirming the card would authorize every option on it: a host could redeem the
76
- * exact ticket, then later redeem the wider prefix ticket, and the second rule would exist without
77
- * anyone ever having agreed to it. Absent on batch records, where the confirmation is over the whole
78
- * previewed list by construction.
123
+ * A card offers alternatives of different breadth and a human picks ONE. Without this, confirming
124
+ * the card would authorize every option on it: a host could redeem the narrow ticket, then later
125
+ * the wider one, and the second rule would exist without anyone ever having agreed to it.
126
+ * Choosing a BATCH offer is a conjunction one yes to ALL of its member candidates at once —
127
+ * so every member becomes redeemable and every non-member stays unredeemable forever. Absent
128
+ * exactly while the record is pending.
79
129
  */
80
- selectedCandidate?: number;
130
+ selectedOffer?: number;
81
131
  /**
82
132
  * The person-EDITED candidate this record carries, if any — full provenance for the one candidate
83
133
  * whose text was authored at the card rather than derived by the engine. `index` names the appended
84
134
  * row in `candidates` (whose `rule` holds the CANONICAL spelling); `text` keeps the raw input bytes
85
135
  * exactly as submitted (the idempotency primary key — a client retrying a lost response resends the
86
- * same bytes); `at` is when the edit landed. Present ⇒ `selectedCandidate === index` (the edit and
87
- * the choice are one CAS write). Absent on every record a prior version minted and on every card
88
- * settled through an engine candidate.
136
+ * same bytes); `at` is when the edit landed. Present ⇒ the record's chosen offer is the single
137
+ * offer minted over this candidate (the append, the offer and the choice are one CAS write).
138
+ * Absent on every card settled through an engine candidate.
89
139
  */
90
140
  edited?: {
91
141
  index: number;
@@ -95,12 +145,32 @@ export interface RuleApprovalRecord {
95
145
  /** Dots already minted for this record, keyed by candidate index — the replay anchor. */
96
146
  redeemedDots?: Record<number, RuleDot>;
97
147
  }
148
+ /**
149
+ * design/375 §4.5 — the minimal ENVELOPE a store hands back for a durable row that predates the
150
+ * `schema: 2` record form. A typed read cannot honestly return such a row as a
151
+ * {@link RuleApprovalRecord}, and dropping it at the store layer would misreport "stale" as
152
+ * corruption — so the store contract is this variant instead: `staleSchema: true` plus the
153
+ * IDENTITY fields, which are spelled the same way in every version and therefore safe to read.
154
+ * The identity fields exist so the owner gate can still run, and it runs FIRST: a non-owner
155
+ * asking about a stale row is answered exactly like a non-owner asking about any row (existence
156
+ * is never confirmed); the stale disclosure is for the record's own owner only. Nothing else
157
+ * about the row is surfaced.
158
+ */
159
+ export interface StaleRuleApprovalRecord {
160
+ readonly staleSchema: true;
161
+ readonly id: string;
162
+ readonly principal?: string;
163
+ readonly owner?: RuleOwner;
164
+ }
98
165
  /**
99
166
  * The durable home of approval records. A separate record type on purpose: it is not a checkpoint row and
100
167
  * must not evolve with one.
101
168
  */
102
169
  export interface RuleApprovalRecordStore {
103
- get(id: string): Promise<RuleApprovalRecord | undefined>;
170
+ /** A row whose `schema` is missing or unknown MUST come back as {@link StaleRuleApprovalRecord}
171
+ * (identity fields only) — never thrown away as a parse failure, never widened into a full
172
+ * record the current form cannot vouch for. */
173
+ get(id: string): Promise<RuleApprovalRecord | StaleRuleApprovalRecord | undefined>;
104
174
  /**
105
175
  * Compare-and-set on the record's REVISION, not on its state.
106
176
  *
@@ -130,15 +200,32 @@ export interface RuleConsentDeps {
130
200
  */
131
201
  cardEdits?: boolean;
132
202
  }
133
- /** In-memory approval records — the test backend and the reference CAS semantics. */
203
+ /** In-memory approval records — the test backend and the reference CAS semantics, the stale-row
204
+ * envelope read included. */
134
205
  export declare class InMemoryRuleApprovalRecordStore implements RuleApprovalRecordStore {
135
206
  private readonly rows;
136
- get(id: string): Promise<RuleApprovalRecord | undefined>;
207
+ get(id: string): Promise<RuleApprovalRecord | StaleRuleApprovalRecord | undefined>;
137
208
  create(record: RuleApprovalRecord): Promise<void>;
138
209
  cas(id: string, expectRev: number, next: RuleApprovalRecord): Promise<boolean>;
139
210
  /** Test/inspection helper — how many records exist. The principal negative pins read this. */
140
211
  size(): number;
141
212
  }
213
+ /**
214
+ * design/375 §4.1 — project a record's stored offers into the wire {@link RuleOffer} shape: the
215
+ * AUTHORITATIVE card render source (record-is-what-you-see; the ask-time projection is advisory
216
+ * and may be coverage-narrower). The display triple (rule, match, command) is derived HERE, at
217
+ * projection time, by parsing each referenced candidate's stored text — never stored beside it,
218
+ * never accepted from a caller — so what a card shows is definitionally the rule that would
219
+ * persist; there is no second copy to corrupt. A batch offer's `segment`s and
220
+ * `uncoveredSegments` come from the mint-time facts the record carries.
221
+ *
222
+ * Card records only, in effect: an import/starter record's whole-list batch carries no segment
223
+ * metadata (its person-facing form is the import/starter PREVIEW, not an offer card), and asking
224
+ * for its card projection throws. A candidate text that no longer parses throws too — that is
225
+ * store damage on a row the validator once accepted, and rendering a wrong rule under a "don't
226
+ * ask again" label is the one failure a refusal always beats.
227
+ */
228
+ export declare function ruleOffersOfRecord(rec: Pick<RuleApprovalRecord, "candidates" | "offers">): readonly RuleOffer[];
142
229
  /** An opaque, single-purpose redemption ticket. Its only content is which record and which candidate. */
143
230
  export type RuleTicket = string;
144
231
  export declare function mintRuleTicket(recordId: string, candidateIndex: number): RuleTicket;
@@ -172,9 +259,19 @@ export declare function prepareCardApproval(opts: {
172
259
  boundInputHash?: string;
173
260
  /** Where a redeemed rule would apply. Defaults to global. */
174
261
  scope?: RuleScope;
262
+ /**
263
+ * The working directory of the ADJUDICATED CALL — the same task root the gate's lane judged
264
+ * with — threaded by the caller from the original call context. Never inferred from `scope`
265
+ * (a rule's landing scope is not where the call ran; nested project roots make the two
266
+ * coverage answers diverge) and never from the process. Absent, a project-scope rule covers
267
+ * nothing (the shared predicate's fail-closed arm), so segments lean UNCOVERED and the batch
268
+ * leans wide — the over-offer direction, which redemption's per-rule validation keeps safe.
269
+ */
270
+ cwd?: string;
175
271
  deps: RuleConsentDeps;
176
272
  }): Promise<{
177
273
  approvalId: string;
274
+ offers: readonly RuleOffer[];
178
275
  tickets: RuleTicket[];
179
276
  candidates: RuleCandidate[];
180
277
  } | undefined>;
@@ -191,12 +288,16 @@ export declare function confirmRuleApproval(opts: {
191
288
  /** design/182 §4.5 (additive): the structural owner — only the local-owner path needs it. */
192
289
  owner?: RuleOwner;
193
290
  /**
194
- * REQUIRED for a card record settled through an ENGINE candidate: the index of the option the person
195
- * chose. A card presents alternatives of different breadth, so "they said yes" is not an answer on
196
- * its own "they said yes to THIS one" is. Rejected on a batch record, whose confirmation covers
197
- * the previewed list by construction. Mutually exclusive with `editedCandidate`.
291
+ * The index of the OFFER the person chose into the record's `offers`, the same order
292
+ * `prepareCardApproval` returned them (design/375 §4.3). A card presents alternatives of
293
+ * different breadth, so "they said yes" is not an answer on its own "they said yes to THIS
294
+ * one" is. A single offer authorizes its one candidate; a BATCH offer is a conjunction, one yes
295
+ * to ALL of its member candidates at once, with no per-member sub-selection. May be omitted
296
+ * exactly when the record carries ONE offer (a single-element choice space has no ambiguity —
297
+ * the import/starter whole-list batch lands here); a multi-offer record with no selection
298
+ * refuses `selection_missing`. Mutually exclusive with `editedCandidate`.
198
299
  */
199
- selectedCandidate?: number;
300
+ selectedOffer?: number;
200
301
  /**
201
302
  * The person-EDITED rule text for this card, travelling on the SAME authenticated confirmation
202
303
  * channel as a choice among the engine's candidates (never the un-authenticated prepare entry, which
@@ -205,7 +306,7 @@ export declare function confirmRuleApproval(opts: {
205
306
  * command", an in-process mis-binding fence (a caller holding only a leaked approvalId cannot spell
206
307
  * it), not a cryptographic one. The engine validates the text through the one shared validator,
207
308
  * requires it to still ADMIT the adjudicated command, appends it as a new candidate and binds the
208
- * selection to it, returning the minted ticket. Mutually exclusive with `selectedCandidate`.
309
+ * selection to it, returning the minted ticket. Mutually exclusive with `selectedOffer`.
209
310
  */
210
311
  editedCandidate?: {
211
312
  text: string;
@@ -214,6 +315,13 @@ export declare function confirmRuleApproval(opts: {
214
315
  deps: RuleConsentDeps;
215
316
  }): Promise<ConfirmResult>;
216
317
  /** Why a confirmation did not land. A closed set so a host can branch (re-present, re-fetch, give up).
318
+ * The two `record_*` members past `record_not_found` are the durable-row gates (design/375 §4.5/§4.7),
319
+ * disclosed to the record's own owner only — a non-owner gets `record_not_found` for every row:
320
+ * - `"record_schema_stale"` — the record predates the `schema: 2` form; the decision it carried
321
+ * is void, and the recovery path is re-triggering the command for a fresh card (no
322
+ * compatibility read, no migration — `detail.message` says so);
323
+ * - `"record_malformed"` — the record's offer structure failed the read-side integrity checks
324
+ * (store damage); `detail.message` names the breakage.
217
325
  * The three `edit_*` members are the card-edit face's own refusals:
218
326
  * - `"edit_disabled"` — the deployment has not opted into card edits (`RuleConsentDeps.cardEdits`);
219
327
  * - `"edit_binding_mismatch"` — the confirmation does not echo the record's bound-input digest
@@ -221,7 +329,7 @@ export declare function confirmRuleApproval(opts: {
221
329
  * settled replays included, and never returns a minted ticket;
222
330
  * - `"edit_rejected"` — the edited text failed a gate (validator refusal, coverage, record shape);
223
331
  * `detail` carries the specifics. */
224
- export type ConfirmRefusalReason = "record_not_found" | "selection_missing" | "selection_invalid" | "selection_mismatch" | "batch_takes_no_selection" | "not_pending" | "conflict" | "edit_disabled" | "edit_binding_mismatch" | "edit_rejected";
332
+ export type ConfirmRefusalReason = "record_not_found" | "record_schema_stale" | "record_malformed" | "selection_missing" | "selection_invalid" | "selection_mismatch" | "not_pending" | "conflict" | "edit_disabled" | "edit_binding_mismatch" | "edit_rejected";
225
333
  /** The confirmation outcome: landed, or refused with a named reason.
226
334
  *
227
335
  * `mintedCandidate` (additive) is present exactly when an EDITED candidate settled this confirmation —
@@ -370,20 +478,36 @@ export interface ImportPreview {
370
478
  };
371
479
  };
372
480
  }
373
- /** What the import ACTUALLY did — a different moment and a different contract from the preview, because
374
- * dedup, concurrency and redemption-time validation can all move an entry between the two. */
375
- export interface ImportResult {
376
- persisted: RuleCandidate[];
377
- deduped: RuleCandidate[];
378
- skippedAtRedeem: Array<{
379
- rule: string;
380
- reason: string;
381
- }>;
382
- rev: number;
383
- }
481
+ /**
482
+ * One member row of a batch redemption's FULL table (design/375 §4.6) what actually happened to
483
+ * each candidate of the chosen offer, keyed by its ORIGINAL candidate index. `candidateIndex` is
484
+ * the member's identity: the same rule text can be two candidates in two scopes with two different
485
+ * outcomes, so the bare text is not a key. A successful row always carries its `dot` and the
486
+ * independent `alreadyRedeemed` axis (the single-leg replay marker: a dot-recorded-but-unlanded
487
+ * retry is `alreadyRedeemed: true` AND `persisted`; a rule that pre-existed in the store is
488
+ * `false` AND `deduped` — the two axes answer different questions and neither implies the other).
489
+ * A refused row always carries its `reason`. `deduped` counts as LANDED: an equivalent rule
490
+ * already standing means the consent is already in effect.
491
+ */
492
+ export type RedeemedBatchMember = {
493
+ readonly candidateIndex: number;
494
+ readonly rule: string;
495
+ readonly scope: RuleScope;
496
+ readonly status: "persisted" | "deduped";
497
+ readonly alreadyRedeemed: boolean;
498
+ readonly dot: RuleDot;
499
+ } | {
500
+ readonly candidateIndex: number;
501
+ readonly rule: string;
502
+ readonly scope: RuleScope;
503
+ readonly status: "refused";
504
+ readonly reason: string;
505
+ };
384
506
  /**
385
507
  * Read the allow buckets of the user-editable settings layers and produce a preview plus a PENDING
386
508
  * approval record. Nothing is stored until someone confirms that record and the batch is redeemed.
509
+ * A preview with zero importable candidates returns NO `approvalId` — there is nothing to confirm,
510
+ * and the absence says so (design/375 §4.1).
387
511
  *
388
512
  * Only the allow bucket is IMPORTED. The deny/ask buckets are the tightening direction and have their own
389
513
  * channel; importing them through a loosening lane would be the wrong door. They are still COUNTED and
@@ -398,7 +522,7 @@ export declare function prepareCcImport(opts: {
398
522
  deps: RuleConsentDeps;
399
523
  }): Promise<{
400
524
  preview: ImportPreview;
401
- approvalId: string;
525
+ approvalId?: string;
402
526
  }>;
403
527
  /**
404
528
  * The factory-reviewed starter set, offered when there is nothing to import.
@@ -420,15 +544,40 @@ export declare function prepareStarterBatch(opts: {
420
544
  approvalId: string;
421
545
  }>;
422
546
  /**
423
- * Redeem an approved BATCH record (import or starter) every candidate, one dot each, reporting what
424
- * actually landed rather than what was previewed.
547
+ * Redeem the CHOSEN offer of a confirmed record, member by member reporting what actually landed
548
+ * rather than what was previewed (dedup, concurrency and redemption-time validation can all move a
549
+ * member between the two moments).
550
+ *
551
+ * design/375 §4.1/§4.6 — this is the one-yes walk for every record kind: an import/starter record's
552
+ * whole-list batch, a card's chosen batch (each per-segment rule lands as its own candidate), or a
553
+ * card's chosen single (a one-member walk). Members OUTSIDE the chosen offer are simply not walked
554
+ * — and stay individually unredeemable through the ticket entry, same guarantee, one predicate.
555
+ *
556
+ * The walk contract, spelled out because each clause is load-bearing:
557
+ * · every member goes through the SAME single-ticket redemption leg — dot CAS'd into the record
558
+ * BEFORE the store write (a crash in between leaves a replayable intent), concurrent-loser
559
+ * dot reuse included; the walk adds no concurrency primitive of its own;
560
+ * · a refused member does NOT fold the batch: "all or nothing" is the CONFIRMATION's semantics
561
+ * (one yes covered the whole offer), not the landing's transaction shape — the walk continues
562
+ * and the member's row says refused, with the reason;
563
+ * · resuming a partial walk IS re-walking: already-landed members answer `alreadyRedeemed: true`
564
+ * and reuse their recorded dots (two concurrent walkers converge on one dot set, zero double
565
+ * mints), unlanded members mint on;
566
+ * · the returned table is COMPLETE — exactly one row per member of the chosen offer, in the
567
+ * offer's member order, no extras — so a caller's aggregate is an honest projection: all rows
568
+ * landed (`persisted` or `deduped`, both count as the consent being in effect) is full
569
+ * success, and ANY refused row must project to a non-success aggregate, never to a silent
570
+ * partial one.
425
571
  */
426
572
  export declare function redeemRuleBatch(opts: {
427
573
  approvalId: string;
428
574
  principal?: string;
429
575
  owner?: RuleOwner;
430
576
  deps: RuleConsentDeps;
431
- }): Promise<ImportResult | {
577
+ }): Promise<{
578
+ members: readonly RedeemedBatchMember[];
579
+ rev: number;
580
+ } | {
432
581
  status: "refused";
433
582
  reason: string;
434
583
  }>;