@sema-agent/server 7.141.0 → 7.142.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 (171) hide show
  1. package/MIGRATION.md +215 -0
  2. package/USAGE.md +67 -12
  3. package/dist/approval-ask-audit-store.d.ts +54 -38
  4. package/dist/approval-ask-audit-store.js +58 -39
  5. package/dist/approval-card.d.ts +8 -8
  6. package/dist/approval-crash-convergence.d.ts +17 -7
  7. package/dist/approval-crash-convergence.js +94 -22
  8. package/dist/approval-deny-reasons.d.ts +34 -30
  9. package/dist/approval-deny-reasons.js +21 -6
  10. package/dist/approval-policy-settlement.d.ts +1 -1
  11. package/dist/approval-reconciler.d.ts +33 -1
  12. package/dist/approval-reconciler.js +43 -40
  13. package/dist/auth-bridge.d.ts +9 -6
  14. package/dist/auth-bridge.js +9 -3
  15. package/dist/boot/adoption.js +2 -1
  16. package/dist/boot/budget-tracing.d.ts +16 -0
  17. package/dist/boot/budget-tracing.js +10 -6
  18. package/dist/boot/device-lane.d.ts +1 -1
  19. package/dist/boot/execution-env.d.ts +24 -4
  20. package/dist/boot/execution-env.js +26 -23
  21. package/dist/boot/execution-lane.js +2 -1
  22. package/dist/boot/exit-boundary.js +5 -4
  23. package/dist/boot/memory-consolidation.js +2 -1
  24. package/dist/boot/parked-revive-gate.js +2 -1
  25. package/dist/boot/permission-rules-audit.d.ts +0 -28
  26. package/dist/boot/permission-rules-audit.js +2 -1
  27. package/dist/boot/read-deny-anchor-audit.js +2 -1
  28. package/dist/boot/reapers.js +4 -3
  29. package/dist/boot/resolve-spec.js +11 -2
  30. package/dist/boot/retention-lane.js +5 -3
  31. package/dist/boot/runner-deps.js +16 -13
  32. package/dist/boot/runtime-caps.js +3 -2
  33. package/dist/boot/session-faces.js +4 -3
  34. package/dist/boot/shutdown.js +3 -2
  35. package/dist/boot/stage-04-stores.js +3 -2
  36. package/dist/boot/stage-05-execution-env.d.ts +1 -1
  37. package/dist/boot/stage-05-execution-env.js +8 -6
  38. package/dist/boot/stage-06-runners.d.ts +1 -1
  39. package/dist/boot/stage-06-runners.js +2 -1
  40. package/dist/boot/stage-07-capability-layer.d.ts +1 -1
  41. package/dist/boot/stage-07-capability-layer.js +2 -1
  42. package/dist/boot/stage-08-reapers.d.ts +1 -1
  43. package/dist/boot/stage-08-reapers.js +3 -2
  44. package/dist/boot/stage-09-leader.d.ts +1 -1
  45. package/dist/boot/stage-10-http-server.d.ts +1 -1
  46. package/dist/boot/stage-10-http-server.js +5 -4
  47. package/dist/boot/stores.js +3 -2
  48. package/dist/boot/web-admin.js +17 -17
  49. package/dist/boot/webfetch-summarize-lane.js +2 -1
  50. package/dist/boot/workflow-orchestration.js +3 -2
  51. package/dist/capabilities/builtin-tools.d.ts +2 -1
  52. package/dist/capabilities/builtin-tools.js +2 -4
  53. package/dist/capabilities/composite-members.d.ts +2 -1
  54. package/dist/capabilities/composite-members.js +2 -1
  55. package/dist/capability-facts.d.ts +24 -0
  56. package/dist/capability-facts.js +23 -0
  57. package/dist/config-catalog.js +1 -0
  58. package/dist/config-types.d.ts +15 -4
  59. package/dist/config.d.ts +6 -0
  60. package/dist/config.js +17 -3
  61. package/dist/execution-lane-caps.d.ts +2 -1
  62. package/dist/fleet/fleet-bus.d.ts +6 -3
  63. package/dist/hooks/hook-runner.d.ts +6 -0
  64. package/dist/hooks/hook-runner.js +30 -1
  65. package/dist/http/dispatch.js +4 -2
  66. package/dist/http/execution-context.d.ts +2 -2
  67. package/dist/http/idempotency.d.ts +1 -1
  68. package/dist/http/principal-gate.d.ts +3 -1
  69. package/dist/http/principal-gate.js +17 -8
  70. package/dist/http/resume-legs.js +5 -9
  71. package/dist/http/route-table.js +2 -0
  72. package/dist/http/routes/attachments.js +18 -0
  73. package/dist/http/routes/capabilities.js +5 -2
  74. package/dist/http/routes/memory-policy.js +26 -6
  75. package/dist/http/routes/runs.js +3 -3
  76. package/dist/http/routes/session-memory-sync.d.ts +63 -0
  77. package/dist/http/routes/session-memory-sync.js +157 -0
  78. package/dist/http/routes/sessions-list.d.ts +2 -1
  79. package/dist/http/routes/tasks.js +7 -5
  80. package/dist/http/run-read-face.d.ts +7 -0
  81. package/dist/http/run-read-face.js +9 -3
  82. package/dist/http/send.d.ts +5 -0
  83. package/dist/http/send.js +9 -2
  84. package/dist/http/server.d.ts +14 -8
  85. package/dist/http/server.js +2 -4
  86. package/dist/http/submit-replay.d.ts +4 -3
  87. package/dist/http/submit-replay.js +2 -2
  88. package/dist/http/sync-submit-body.d.ts +21 -4
  89. package/dist/http/sync-submit-body.js +13 -0
  90. package/dist/identity-source.d.ts +21 -2
  91. package/dist/identity-source.js +6 -0
  92. package/dist/memory-incident-sink.d.ts +19 -6
  93. package/dist/memory-incident-sink.js +8 -4
  94. package/dist/memory-sync-client.d.ts +21 -21
  95. package/dist/memory-sync-client.js +50 -42
  96. package/dist/memory-sync-identity.d.ts +24 -0
  97. package/dist/memory-sync-identity.js +57 -0
  98. package/dist/memory-sync.d.ts +7 -1
  99. package/dist/memory-sync.js +6 -3
  100. package/dist/observability/fail-open.d.ts +27 -10
  101. package/dist/observability/fail-open.js +20 -7
  102. package/dist/observability/run-terminal.d.ts +92 -34
  103. package/dist/observability/run-terminal.js +167 -162
  104. package/dist/plugins/approval-ask-store-file.d.ts +14 -4
  105. package/dist/plugins/approval-ask-store-file.js +48 -7
  106. package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
  107. package/dist/plugins/approval-ask-store-memory.js +9 -8
  108. package/dist/plugins/approval-ask-store-sql.d.ts +42 -10
  109. package/dist/plugins/approval-ask-store-sql.js +49 -19
  110. package/dist/plugins/file-run-store.d.ts +6 -1
  111. package/dist/plugins/file-run-store.js +11 -1
  112. package/dist/plugins/local-session-store.d.ts +36 -7
  113. package/dist/plugins/local-session-store.js +76 -32
  114. package/dist/plugins/local-task-attachment-store.d.ts +12 -2
  115. package/dist/plugins/local-task-attachment-store.js +67 -43
  116. package/dist/plugins/memory-key-guards.d.ts +3 -3
  117. package/dist/plugins/memory-run-store.d.ts +6 -1
  118. package/dist/plugins/memory-run-store.js +11 -1
  119. package/dist/plugins/one-time-migrations.d.ts +27 -1
  120. package/dist/plugins/one-time-migrations.js +57 -3
  121. package/dist/plugins/remote-env-e2b.d.ts +23 -4
  122. package/dist/plugins/remote-env-e2b.js +111 -28
  123. package/dist/plugins/remote-env-k8s.d.ts +31 -5
  124. package/dist/plugins/remote-env-k8s.js +86 -19
  125. package/dist/plugins/remote-env-local-docker.d.ts +15 -3
  126. package/dist/plugins/remote-env-local-docker.js +40 -12
  127. package/dist/plugins/run-store-sql.d.ts +9 -1
  128. package/dist/plugins/run-store-sql.js +9 -1
  129. package/dist/plugins/store-backend.d.ts +10 -7
  130. package/dist/plugins/store-backend.js +1 -0
  131. package/dist/plugins/store-contracts.d.ts +26 -1
  132. package/dist/plugins/store-contracts.js +3 -0
  133. package/dist/plugins/task-attachment-store.d.ts +112 -14
  134. package/dist/plugins/task-attachment-store.js +221 -28
  135. package/dist/plugins/workspace-fill.d.ts +60 -0
  136. package/dist/plugins/workspace-fill.js +106 -0
  137. package/dist/project-bundle.d.ts +38 -0
  138. package/dist/project-bundle.js +609 -0
  139. package/dist/run-local.js +2 -8
  140. package/dist/runs.js +1 -2
  141. package/dist/sandbox-egress-policy.d.ts +4 -1
  142. package/dist/sandbox-egress-policy.js +3 -1
  143. package/dist/sandbox-git-plane.d.ts +31 -12
  144. package/dist/sandbox-git-plane.js +24 -4
  145. package/dist/security.d.ts +9 -0
  146. package/dist/security.js +8 -0
  147. package/dist/session-error-wire.d.ts +24 -0
  148. package/dist/session-error-wire.js +7 -0
  149. package/dist/task-settings.d.ts +10 -2
  150. package/dist/task-settings.js +15 -2
  151. package/dist/terminal.d.ts +39 -19
  152. package/dist/terminal.js +65 -22
  153. package/dist/tool-approval.d.ts +4 -3
  154. package/dist/tool-approval.js +12 -14
  155. package/dist/trace/core-keyset-guard.d.ts +123 -6
  156. package/dist/trace/engine-notice-wire.d.ts +8 -2
  157. package/dist/trace/engine-notice-wire.js +4 -2
  158. package/dist/trace/exit-seat.d.ts +2 -1
  159. package/dist/trace/ledger-sink.d.ts +8 -4
  160. package/dist/trace/ledger-sink.js +2 -2
  161. package/dist/trace/project.d.ts +18 -1
  162. package/dist/trace/project.js +16 -0
  163. package/dist/trace/self-minted-wire.d.ts +251 -2
  164. package/dist/trace/self-minted-wire.js +82 -0
  165. package/dist/trace/wire-projection-faces.d.ts +1 -1
  166. package/dist/trace/wire-projection-faces.js +1 -1
  167. package/dist/web-admin-client/apply-effective.js +21 -1
  168. package/dist/web-admin-client/effective-keys.js +2 -0
  169. package/dist/web-admin-client/types.d.ts +4 -0
  170. package/package.json +1 -1
  171. package/wire-changes/server@7.142.0.json +639 -0
@@ -3,7 +3,7 @@ import { redactSecrets } from "../trace/redact.js";
3
3
  import { isProviderEchoShape } from "@sema-agent/core";
4
4
  import { isModelTextLedgerEventType, isParkLedgerEventType } from "../trace/ledger-events.js";
5
5
  import { isUnsuccessfulRunStatus } from "../plugins/store-contracts.js";
6
- import { mapPauseSeats, PAUSE_SEATS, sealResultForExit } from "../terminal.js";
6
+ import { mapPauseSeats, PAUSE_SEATS, sealResultForExit, sealStoredResult } from "../terminal.js";
7
7
  import { errorText } from "./err-text.js";
8
8
  import { recordFailOpen } from "./fail-open.js";
9
9
  import { toolEndMediaAsDescriptors } from "../trace/tool-output-media.js";
@@ -128,43 +128,138 @@ function shapeEchoMembers(node, table, seat) {
128
128
  }
129
129
  return out ?? node;
130
130
  }
131
- function shapeEchoRows(rows, table, seat) {
132
- if (!Array.isArray(rows))
133
- return rows;
134
- const next = rows.map((row) => shapeEchoMembers(row, table, seat));
135
- return next.every((x, i) => x === rows[i]) ? rows : next;
136
- }
137
131
  const MAX_EDITED_FILES = 1000;
138
- const RESULT_KEY_TREATMENT = {
139
- terminal: "redacted",
140
- remoteEnvFailures: "redacted",
141
- editedFiles: "redacted",
142
- responseModel: "provider-echo",
143
- apiFailure: "provider-echo",
144
- strandedHumanAnswers: "provider-echo",
132
+ export const RESULT_TEXT_LEAVES = {
145
133
  taskId: "verbatim",
146
134
  runId: "verbatim",
147
135
  sessionId: "verbatim",
148
136
  model: "verbatim",
149
- compaction: "verbatim",
150
- effectiveReasoning: "verbatim",
151
- result: "redacted",
152
- salvagedOutput: "redacted",
153
- retryAfterMs: "verbatim",
154
- degraded: "verbatim",
137
+ "terminal.code": "outward-code",
138
+ "terminal.message": "error-text",
139
+ "terminal.reason": "error-text",
140
+ "terminal.detail": "terminal-detail",
141
+ "terminal.checkpointId": "verbatim",
142
+ "terminal.requestedByCallId": "provider-join-key",
143
+ "terminal.nestedPause.checkpointId": "verbatim",
144
+ "terminal.nestedPause.requestedByCallId": "provider-join-key",
145
+ result: "model-text",
146
+ salvagedOutput: "model-text",
147
+ "remoteEnvFailures.[].message": "error-text",
148
+ "rewindNotes.[].message": "error-text",
149
+ "editedFiles.[].path": "model-text",
150
+ responseModel: "provider-echo",
151
+ apiFailure: "api-failure",
152
+ "strandedHumanAnswers.[]": "stranded-answer",
153
+ "degraded.from": "verbatim",
154
+ "degraded.to": "verbatim",
155
+ "degraded.chain.[]": "verbatim",
156
+ "effectiveReasoning.endpoint": "verbatim",
157
+ "resultEntryIds.[]": "verbatim",
158
+ "pendingApproval.askIds.[]": "verbatim",
159
+ "effectiveReadDenyPatterns.[].pattern": "verbatim",
160
+ "effectiveMemoryScopes.writeScope": "verbatim",
161
+ "effectiveMemoryScopes.scopes.[].scope": "verbatim",
162
+ "effectiveMemoryScopes.projectKey.scope": "verbatim",
163
+ "effectiveMemoryScopes.materializedResidue.[]": "verbatim",
155
164
  structuredOutput: "verbatim",
156
- resultEntryIds: "verbatim",
157
- rewindNotes: "redacted",
158
- haltedOnUserRejection: "verbatim",
159
- haltedByUser: "verbatim",
160
- pendingApproval: "verbatim",
161
- approvalWaitedMs: "verbatim",
162
- effectiveReadFace: "verbatim",
163
- effectiveWriteFace: "verbatim",
164
- effectiveReadDenyPatterns: "verbatim",
165
- effectiveMemoryScopes: "verbatim",
166
- stats: "redacted",
165
+ stats: "stats-tree",
166
+ "verification.findings.[]": "model-text",
167
+ "verification.evidence": "model-text",
168
+ "verification.verifierPause.sessionId": "verbatim",
169
+ "attempts.[].model": "verbatim",
170
+ "attempts.[].errorCode": "outward-code",
171
+ "attempts.[].gateError": "error-text",
172
+ "attempts.[].diagnostics": "model-text",
173
+ "cancelContext.lastEventKind": "verbatim",
174
+ "cancelContext.lastBrainStatus.phase": "verbatim",
175
+ "cancelContext.lastBrainStatus.errClass": "verbatim",
167
176
  };
177
+ const DROP_LEAF = Symbol("drop-leaf");
178
+ function textLeafAct(treatment) {
179
+ switch (treatment) {
180
+ case "verbatim":
181
+ case "provider-join-key":
182
+ case "outward-code":
183
+ return undefined;
184
+ case "model-text":
185
+ return (v) => (typeof v === "string" ? redactModelText(v) : v);
186
+ case "error-text":
187
+ return (v) => (typeof v === "string" ? redactErrorMessage(v) : v);
188
+ case "provider-echo":
189
+ return (v, _holder, seat) => (admitProviderEcho(v, seat) ? v : DROP_LEAF);
190
+ case "stats-tree":
191
+ return (v) => redactStats(v);
192
+ case "terminal-detail":
193
+ return (v, holder) => shapeTerminalDetail(v, holder);
194
+ case "api-failure":
195
+ return (v) => shapeEchoMembers(v, API_FAILURE_TREATMENT, "apiFailure");
196
+ case "stranded-answer":
197
+ return (v) => shapeEchoMembers(v, STRANDED_ANSWER_TREATMENT, "strandedHumanAnswers[]");
198
+ default: {
199
+ const unhandled = treatment;
200
+ throw new Error(`unhandled text-leaf treatment: ${String(unhandled)}`);
201
+ }
202
+ }
203
+ }
204
+ const ACTIVE_TEXT_LEAVES = Object.entries(RESULT_TEXT_LEAVES).flatMap(([seat, treatment]) => {
205
+ const act = textLeafAct(treatment);
206
+ return act === undefined ? [] : [{ seat, segments: seat.split("."), act }];
207
+ });
208
+ function mapTextLeaf(node, segments, depth, holder, act, seat) {
209
+ if (depth === segments.length)
210
+ return act(node, holder, seat);
211
+ if (node === null || typeof node !== "object")
212
+ return node;
213
+ const segment = segments[depth];
214
+ if (segment === "[]") {
215
+ if (!Array.isArray(node))
216
+ return node;
217
+ let changed = false;
218
+ const next = [];
219
+ for (const el of node) {
220
+ const n = el === undefined ? el : mapTextLeaf(el, segments, depth + 1, node, act, seat);
221
+ if (n === DROP_LEAF) {
222
+ changed = true;
223
+ continue;
224
+ }
225
+ if (!Object.is(n, el))
226
+ changed = true;
227
+ next.push(n);
228
+ }
229
+ return changed ? next : node;
230
+ }
231
+ if (Array.isArray(node))
232
+ return node;
233
+ const obj = node;
234
+ let out;
235
+ for (const key of segment === "*" ? Object.keys(obj) : Object.hasOwn(obj, segment) ? [segment] : []) {
236
+ const child = obj[key];
237
+ if (child === undefined)
238
+ continue;
239
+ const n = mapTextLeaf(child, segments, depth + 1, obj, act, seat);
240
+ if (n === DROP_LEAF)
241
+ delete (out ??= { ...obj })[key];
242
+ else if (!Object.is(n, child))
243
+ (out ??= { ...obj })[key] = n;
244
+ }
245
+ return out ?? node;
246
+ }
247
+ function applyResultTextLeaves(result) {
248
+ let out = result;
249
+ for (const row of ACTIVE_TEXT_LEAVES)
250
+ out = mapTextLeaf(out, row.segments, 0, undefined, row.act, row.seat);
251
+ return out;
252
+ }
253
+ function shapeTerminalDetail(detail, terminal) {
254
+ const kind = terminal !== null && typeof terminal === "object" ? terminal.kind : undefined;
255
+ if (kind === "failed")
256
+ return shapeArmedDetail(detail, TERMINAL_FAILURE_DETAIL_TREATMENT);
257
+ if (kind === "completed")
258
+ return shapeArmedDetail(detail, TERMINAL_COMPLETED_DETAIL_TREATMENT);
259
+ if (kind === "blocked")
260
+ return shapeTerminalBlockedDetail(detail);
261
+ return shapeArmedDetail(detail, {});
262
+ }
168
263
  const API_FAILURE_TREATMENT = {
169
264
  status: "verbatim",
170
265
  requestId: "provider-echo",
@@ -209,12 +304,15 @@ function redactStringLeaves(node, depth = 0) {
209
304
  }
210
305
  const OFF_ARM_MEMBER_TREATMENT = "free-text-tree";
211
306
  const redactDetailMember = (v) => redactStringLeaves(v, 1);
212
- function shapeTerminalFailureDetail(detail) {
307
+ const TERMINAL_COMPLETED_DETAIL_TREATMENT = {
308
+ finish_reason_unknown: { kind: "verbatim", finishReasonUnknown: "provider-echo" },
309
+ };
310
+ function shapeArmedDetail(detail, arms) {
213
311
  if (detail === null || typeof detail !== "object" || Array.isArray(detail))
214
312
  return detail;
215
313
  const d = detail;
216
314
  const kind = d.kind;
217
- const arm = typeof kind === "string" && Object.hasOwn(TERMINAL_FAILURE_DETAIL_TREATMENT, kind) ? TERMINAL_FAILURE_DETAIL_TREATMENT[kind] : {};
315
+ const arm = typeof kind === "string" && Object.hasOwn(arms, kind) ? arms[kind] : {};
218
316
  let out;
219
317
  for (const key of Object.keys(d)) {
220
318
  const v = d[key];
@@ -237,7 +335,7 @@ function shapeTerminalFailureDetail(detail) {
237
335
  }
238
336
  default: {
239
337
  const unhandled = treatment;
240
- throw new Error(`unhandled failure-detail treatment: ${String(unhandled)}`);
338
+ throw new Error(`unhandled detail-member treatment: ${String(unhandled)}`);
241
339
  }
242
340
  }
243
341
  }
@@ -282,153 +380,60 @@ function shapeTerminalBlockedDetail(detail) {
282
380
  }
283
381
  return out ?? detail;
284
382
  }
285
- const PROVIDER_ECHO_SHAPERS = {
286
- responseModel: (v) => (admitProviderEcho(v, "responseModel") ? v : undefined),
287
- apiFailure: (v) => shapeEchoMembers(v, API_FAILURE_TREATMENT, "apiFailure"),
288
- strandedHumanAnswers: (v) => shapeEchoRows(v, STRANDED_ANSWER_TREATMENT, "strandedHumanAnswers[]"),
289
- };
290
- {
291
- const judged = Object.entries(RESULT_KEY_TREATMENT).filter(([, t]) => t === "provider-echo").map(([k]) => k).sort();
292
- const wired = Object.keys(PROVIDER_ECHO_SHAPERS).sort();
293
- if (judged.join(",") !== wired.join(","))
294
- throw new Error(`run-terminal: provider-echo classification [${judged.join(",")}] ≠ shaper table [${wired.join(",")}]`);
295
- }
296
383
  const LEGACY_RESULT_LEAVES = ["errorMessage", "blockedReason", "checkpointGate"];
297
384
  export const TREATED_RESULT_LEAVES = [
298
385
  ...new Set([
299
- ...Object.entries(RESULT_KEY_TREATMENT).filter(([, t]) => t !== "verbatim").map(([k]) => k),
386
+ ...ACTIVE_TEXT_LEAVES.map((row) => row.segments[0]),
300
387
  ...LEGACY_RESULT_LEAVES,
301
388
  ...PAUSE_SEATS.map((seat) => seat.path[0]),
389
+ "editedFiles",
302
390
  ]),
303
391
  ];
304
392
  export function redactTerminalResult(result) {
305
393
  if (result === undefined || result === null)
306
394
  return result;
307
- const r = result;
308
- let changed = false;
309
- const out = { ...r };
310
- if (typeof r.errorMessage === "string") {
311
- out.errorMessage = redactErrorMessage(r.errorMessage);
312
- changed = true;
313
- }
314
- if (typeof r.blockedReason === "string") {
315
- out.blockedReason = redactErrorMessage(r.blockedReason);
316
- changed = true;
317
- }
318
- if (r.checkpointGate !== undefined) {
319
- const legacyGate = redactCheckpointGate(r.checkpointGate);
320
- if (legacyGate !== r.checkpointGate) {
321
- out.checkpointGate = legacyGate;
322
- changed = true;
323
- }
324
- }
325
- if (r.terminal !== null && typeof r.terminal === "object") {
326
- const t = r.terminal;
327
- let nt;
328
- if (t.kind === "failed" && typeof t.message === "string")
329
- nt = { ...t, message: redactErrorMessage(t.message) };
330
- else if (t.kind === "blocked" && typeof t.reason === "string")
331
- nt = { ...t, reason: redactErrorMessage(t.reason) };
332
- if (t.kind === "blocked") {
333
- const cur = nt ?? t;
334
- const detail = cur.detail;
335
- const shaped = shapeTerminalBlockedDetail(detail);
336
- if (shaped !== detail)
337
- nt = { ...cur, detail: shaped };
338
- }
339
- const base = nt ?? t;
340
- if (t.kind === "failed") {
341
- const cur = nt ?? base;
342
- const detail = cur.detail;
343
- const shaped = shapeTerminalFailureDetail(detail);
344
- if (shaped !== detail)
345
- nt = { ...cur, detail: shaped };
346
- }
347
- if (nt !== undefined) {
348
- out.terminal = nt;
349
- changed = true;
350
- }
351
- }
352
- const gated = mapPauseSeats(out, (seat, keys) => {
395
+ return redactLegacyPlaneLeaves(redactPauseSeatGates(applyResultTextLeaves(capEditedFiles(result))));
396
+ }
397
+ function capEditedFiles(result) {
398
+ const files = result.editedFiles;
399
+ if (!Array.isArray(files) || files.length <= MAX_EDITED_FILES)
400
+ return result;
401
+ return { ...result, editedFiles: files.slice(0, MAX_EDITED_FILES) };
402
+ }
403
+ function redactPauseSeatGates(result) {
404
+ return mapPauseSeats(result, (seat, keys) => {
353
405
  if (!Object.hasOwn(seat, keys.gate))
354
406
  return seat;
355
407
  const g = seat[keys.gate];
356
408
  const ng = redactCheckpointGate(g);
357
409
  return ng === g ? seat : { ...seat, [keys.gate]: ng };
358
410
  });
359
- if (gated !== out) {
360
- Object.assign(out, gated);
361
- changed = true;
362
- }
363
- if (r.remoteEnvFailures !== undefined) {
364
- out.remoteEnvFailures = Array.isArray(r.remoteEnvFailures)
365
- ? r.remoteEnvFailures.map((f) => (f && typeof f === "object" && typeof f.message === "string" ? { ...f, message: redactErrorMessage(f.message) } : f))
366
- : r.remoteEnvFailures;
367
- changed = true;
368
- }
369
- if (r.editedFiles !== undefined) {
370
- out.editedFiles = Array.isArray(r.editedFiles)
371
- ? r.editedFiles
372
- .slice(0, MAX_EDITED_FILES)
373
- .map((f) => {
374
- if (f === null || typeof f !== "object")
375
- return f;
376
- const path = f.path;
377
- return typeof path === "string" ? { ...f, path: redactSecrets(path) } : f;
378
- })
379
- : r.editedFiles;
380
- changed = true;
381
- }
382
- if (typeof r.result === "string") {
383
- const next = redactModelText(r.result);
384
- if (next !== r.result) {
385
- out.result = next;
386
- changed = true;
387
- }
388
- }
389
- if (typeof r.salvagedOutput === "string") {
390
- const next = redactModelText(r.salvagedOutput);
391
- if (next !== r.salvagedOutput) {
392
- out.salvagedOutput = next;
393
- changed = true;
394
- }
411
+ }
412
+ function redactLegacyPlaneLeaves(result) {
413
+ const r = result;
414
+ let out;
415
+ if (typeof r.errorMessage === "string") {
416
+ const next = redactErrorMessage(r.errorMessage);
417
+ if (next !== r.errorMessage)
418
+ (out ??= { ...result }).errorMessage = next;
395
419
  }
396
- if (r.rewindNotes !== undefined) {
397
- out.rewindNotes = Array.isArray(r.rewindNotes)
398
- ? r.rewindNotes.map((n) => {
399
- if (n === null || typeof n !== "object")
400
- return n;
401
- const message = n.message;
402
- return typeof message === "string" ? { ...n, message: redactErrorMessage(message) } : n;
403
- })
404
- : r.rewindNotes;
405
- changed = true;
406
- }
407
- if (r.stats !== undefined) {
408
- const next = redactStats(r.stats);
409
- if (next !== r.stats) {
410
- out.stats = next;
411
- changed = true;
412
- }
420
+ if (typeof r.blockedReason === "string") {
421
+ const next = redactErrorMessage(r.blockedReason);
422
+ if (next !== r.blockedReason)
423
+ (out ??= { ...result }).blockedReason = next;
413
424
  }
414
- const rr = result;
415
- for (const [key, shape] of Object.entries(PROVIDER_ECHO_SHAPERS)) {
416
- const v = rr[key];
417
- if (v === undefined || shape === undefined)
418
- continue;
419
- const next = shape(v);
420
- if (next === v)
421
- continue;
422
- if (next === undefined)
423
- delete out[key];
424
- else
425
- out[key] = next;
426
- changed = true;
425
+ if (r.checkpointGate !== undefined) {
426
+ const next = redactCheckpointGate(r.checkpointGate);
427
+ if (next !== r.checkpointGate)
428
+ (out ??= { ...result }).checkpointGate = next;
427
429
  }
428
- return changed ? out : result;
430
+ return out === undefined ? result : out;
431
+ }
432
+ export function sealRunRowResult(result) {
433
+ return result === null ? null : redactTerminalResult(sealResultForExit(result));
429
434
  }
430
- export function sealRunRowResult(result, readSeat) {
431
- return result === null ? null : redactTerminalResult(sealResultForExit(result, readSeat));
435
+ export function sealStoredRunRowResult(result, seat) {
436
+ return result === null ? null : redactTerminalResult(sealStoredResult(result, seat));
432
437
  }
433
438
  export function redactLedgerEventData(type, data) {
434
439
  if (data === null || typeof data !== "object" || Array.isArray(data))
@@ -897,7 +902,7 @@ export function memoRedactedRead(seat, raw, redact) {
897
902
  export function memoRedactTerminalResult(seat, raw) {
898
903
  if (raw === undefined || raw === null)
899
904
  return raw;
900
- const result = sealResultForExit(raw, seat);
905
+ const result = sealStoredResult(raw, seat);
901
906
  const r = result;
902
907
  if (TREATED_RESULT_LEAVES.every((k) => r[k] === undefined))
903
908
  return result;
@@ -126,6 +126,10 @@ export declare class FileApprovalAskStore implements ApprovalAskStore {
126
126
  private replayRejectedCount;
127
127
  /** 重放期被丢弃的坏行数(撕尾不计:那是本格式存在的理由,由 {@link corruptQuarantinePath} 单独说)。 */
128
128
  private corruptLineCount;
129
+ /** 本次启动重放到的 v1 信封记录数(随 M34;{@link upgradeJournalEnvelope} 的读数)。 */
130
+ private journalV1Records;
131
+ /** 盘上还有 v1 信封记录(随 M34):重放到 v1 记录时置位,任何一次压实(整本重写成 v2)清位 —— 已被压实过的账本不再为升级重写第二次。 */
132
+ private journalV1OnDisk;
129
133
  /** 本次启动隔离出去的原件路径(无坏字节 ⇒ `undefined`)。 */
130
134
  readonly corruptQuarantinePath: string | undefined;
131
135
  /**
@@ -179,8 +183,8 @@ export declare class FileApprovalAskStore implements ApprovalAskStore {
179
183
  * 🔴 [ref] —— **出域的一次决议不落账本**。作用域的属主维(`owner`,判据 {@link askRowCarriesOwner})不在记录上:行此刻不带着门判过的那位属主
180
184
  * ⇒ 内核上这一笔必输、零写(读钟也在判定之后,不开账本窗)⇒ 没有要重放的东西,直调内核拿它的输者答案(与留存两动词「空的一次不落」同一条先例)。
181
185
  * 判定、落账、内核调用都在同一个同步前缀里,其间行不可能换属主。于是**落了账的每一条决议都在域内**,重放按行自己的属主喂属主维
182
- * ({@link FileApprovalAskStore.replayOp})—— 记录形一个字节不变:升级前写下的账本、升级后写下的账本、降级回老版本时老读者读新账本,三者同读,
183
- * 零迁移、零双读。
186
+ * ({@link FileApprovalAskStore.replayOp})—— 决议记录的形一个字节不变(没有属主格):升级前写下的决议记录与升级后写下的同读,零迁移、零双读
187
+ * (整本账的信封号另算 —— 见顶注「信封版本」)。
184
188
  */
185
189
  decideAsk(askId: string, batchId: string, owner: string | null, decision: DecideAskInput): Promise<DecideResult>;
186
190
  claimTerminal(askId: string, batchId: string, intent: AskTerminalClaimIntent, opts?: AskTerminalClaimOpts): Promise<AskTerminalClaimOutcome>;
@@ -208,14 +212,20 @@ export declare class FileApprovalAskStore implements ApprovalAskStore {
208
212
  corruptLines: number;
209
213
  records: number;
210
214
  };
211
- /** 释放追加 fd(`LocalBackend.close` → 优雅重启重开)。不包 try/catch 的理由同
212
- * {@link FileApprovalNonceStore.dispose}:core 的 `AppendLog.close()` 自己就是全函数。 */
215
+ /**
216
+ * 问行成因两列的 local 腿(一次性迁移 M34,窗口见 `plugins/one-time-migrations.ts` 登记表;`LocalBackend.runOneTimeMigrations` 调):本次启动重放到的 v1 信封记录
217
+ * 已由读侧半场按意义译进内存({@link translateJournalV1Record}),这里**一次压实**把整本账重写成 v2 快照(压实 = 既有整本原子重写)。返回 v1 记录数;没有 ⇒ 零写、0。
218
+ * 本次启动里账本已被别的压实整本重写过(撕尾 / 坏行收拢、M32 的车道回填)⇒ 盘上已无 v1,不再重写,读数照报。
219
+ */
220
+ upgradeJournalEnvelope(): number;
213
221
  /**
214
222
  * 执行放置切换 ④b 的 local 腿(一次性迁移登记表 M32;`LocalBackend.runOneTimeMigrations` 在任何读者之前调):升级前写下的问(日志 `ensureAsk` 记录行 /
215
223
  * 压实快照行没有 `executionLane`)记成本部署车道表的唯一项,然后**一次压实**把整本账重写成带车道的快照(压实 = 既有整本原子重写)。
216
224
  * 没有缺车道的行 ⇒ 零写。车道表不是一元而还有缺车道的行 ⇒ 抛与 SQL 同一只拒(`backfill_ambiguous`,由调用方铸),一个字节没写。
217
225
  */
218
226
  backfillExecutionLanes(lanes: readonly ExecutionLane[], refuse: (pending: number) => never): number;
227
+ /** 释放追加 fd(`LocalBackend.close` → 优雅重启重开)。不包 try/catch 的理由同
228
+ * {@link FileApprovalNonceStore.dispose}:core 的 `AppendLog.close()` 自己就是全函数。 */
219
229
  dispose(): void;
220
230
  }
221
231
  export {};
@@ -30,6 +30,29 @@ const JOURNAL_OP_WORDS = new Set(JOURNAL_OPS);
30
30
  export const JOURNAL_OP_TABLE_GATE = true;
31
31
  const JOURNAL_V1_CLAIM_INTENTS = ["expire", "cancel"];
32
32
  const JOURNAL_V1_CLAIM_INTENT_WORDS = new Set(JOURNAL_V1_CLAIM_INTENTS);
33
+ const JOURNAL_ENVELOPE = 2;
34
+ const JOURNAL_V1_ENVELOPE = 1;
35
+ const V1_ENDING_STATES = new Set(["VOID", "DENIED"]);
36
+ const V1_TRIGGER_STATES = new Set(["PARKING", "PARKED"]);
37
+ function translateJournalV1Record(raw, refuse) {
38
+ const rec = raw;
39
+ if (rec.k === "ask" && rec.row !== null && typeof rec.row === "object") {
40
+ const { deniedReason, ...row } = rec.row;
41
+ const cause = typeof deniedReason === "string" ? deniedReason : null;
42
+ if (cause !== null && row.state === "STREAM_PENDING") {
43
+ refuse(`an envelope-1 snapshot row in STREAM_PENDING carries the single cause field ${JSON.stringify(cause)} — no released server wrote that shape and it has no two-column reading`);
44
+ }
45
+ return { ...raw, row: { ...row, endingCause: V1_ENDING_STATES.has(row.state) ? cause : null, parkTrigger: V1_TRIGGER_STATES.has(row.state) ? cause : null } };
46
+ }
47
+ if (rec.k === "op" && (rec.op === "transitionAsk" || rec.op === "resolveProvisional") && rec.patch !== null && typeof rec.patch === "object" && "deniedReason" in rec.patch) {
48
+ const { deniedReason, ...patch } = rec.patch;
49
+ if (!V1_ENDING_STATES.has(rec.to) || (typeof deniedReason !== "string" && deniedReason !== null)) {
50
+ refuse(`an envelope-1 ${String(rec.op)} record sets the single cause field on a transition to ${JSON.stringify(rec.to)} with ${JSON.stringify(deniedReason)} — no released server wrote that shape and it has no two-column reading`);
51
+ }
52
+ return { ...raw, patch: { ...patch, endingCause: deniedReason } };
53
+ }
54
+ return raw;
55
+ }
33
56
  const ASK_JOURNAL_SHAPE = {
34
57
  what: "approval-ask journal record",
35
58
  read: (record) => record,
@@ -57,6 +80,8 @@ export class FileApprovalAskStore {
57
80
  pinnedNow = null;
58
81
  replayRejectedCount = 0;
59
82
  corruptLineCount = 0;
83
+ journalV1Records = 0;
84
+ journalV1OnDisk = false;
60
85
  corruptQuarantinePath;
61
86
  constructor(root) {
62
87
  const dir = join(root, APPROVAL_ASK_DIR);
@@ -95,7 +120,7 @@ export class FileApprovalAskStore {
95
120
  }
96
121
  write(call, run) {
97
122
  const t = Date.now();
98
- const record = { v: 1, k: "op", t, ...call };
123
+ const record = { v: JOURNAL_ENVELOPE, k: "op", t, ...call };
99
124
  const encoded = encodeStoreJsonPayload(`${call.op} call`, record);
100
125
  this.logFor().append(encoded, true);
101
126
  this.records += 1;
@@ -125,22 +150,29 @@ export class FileApprovalAskStore {
125
150
  }
126
151
  compact(snapshot = this.core.exportRows()) {
127
152
  const lines = [
128
- ...snapshot.batches.map((row) => `${JSON.stringify({ v: 1, k: "batch", row })}\n`),
129
- ...snapshot.asks.map((row) => `${JSON.stringify({ v: 1, k: "ask", row })}\n`),
153
+ ...snapshot.batches.map((row) => `${JSON.stringify({ v: JOURNAL_ENVELOPE, k: "batch", row })}\n`),
154
+ ...snapshot.asks.map((row) => `${JSON.stringify({ v: JOURNAL_ENVELOPE, k: "ask", row })}\n`),
130
155
  ];
131
156
  this.log?.closeForSwap();
132
157
  atomicWriteFile(this.tmpDir, this.path, lines.join(""));
158
+ this.journalV1OnDisk = false;
133
159
  this.records = lines.length;
134
160
  }
135
- replayRecord(raw) {
136
- const probe = raw;
161
+ replayRecord(disk) {
162
+ const head = disk;
137
163
  const refuse = (why) => {
138
164
  throw new StoreBootRefusalError(`FileApprovalAskStore: approval ask journal ${this.path} carries a record this build cannot replay — ${why} ` +
139
- `(v=${JSON.stringify(probe.v)} k=${JSON.stringify(probe.k)} op=${JSON.stringify(probe.op)}). ` +
165
+ `(v=${JSON.stringify(head.v)} k=${JSON.stringify(head.k)} op=${JSON.stringify(head.op)}). ` +
140
166
  "Replaying only the records it understands would resurrect decided asks or drop a human's approval. " +
141
167
  "Run the server version that wrote this journal, or move the file aside (every approval on it is abandoned) to continue.");
142
168
  };
143
- if (probe.v !== 1)
169
+ if (head.v === JOURNAL_V1_ENVELOPE) {
170
+ this.journalV1Records += 1;
171
+ this.journalV1OnDisk = true;
172
+ }
173
+ const raw = head.v === JOURNAL_V1_ENVELOPE ? translateJournalV1Record(disk, refuse) : disk;
174
+ const probe = raw;
175
+ if (probe.v !== JOURNAL_ENVELOPE && probe.v !== JOURNAL_V1_ENVELOPE)
144
176
  refuse("unknown envelope version");
145
177
  if (probe.k === "ask" || probe.k === "batch") {
146
178
  const row = probe.row;
@@ -296,6 +328,15 @@ export class FileApprovalAskStore {
296
328
  replayStats() {
297
329
  return { rejected: this.replayRejectedCount, corruptLines: this.corruptLineCount, records: this.records };
298
330
  }
331
+ upgradeJournalEnvelope() {
332
+ const upgraded = this.journalV1Records;
333
+ if (upgraded === 0)
334
+ return 0;
335
+ if (this.journalV1OnDisk)
336
+ this.compact();
337
+ this.journalV1Records = 0;
338
+ return upgraded;
339
+ }
299
340
  backfillExecutionLanes(lanes, refuse) {
300
341
  const pending = this.core.exportRows().asks.filter((r) => !isExecutionLane(String(r.executionLane ?? ""))).length;
301
342
  if (pending === 0)
@@ -101,7 +101,8 @@ export declare class InMemoryApprovalAskStore implements ApprovalAskStore {
101
101
  * 单调性论证)。
102
102
  */
103
103
  claimTerminal(askId: string, batchId: string, intent: AskTerminalClaimIntent, opts?: AskTerminalClaimOpts): Promise<AskTerminalClaimOutcome>;
104
- /** `cause`(可选)= 转 park 的触发成因,赢下 PARKING 的同一步落进 `deniedReason`(SQL twin 同一条 UPDATE 的镜像;接口上的 `expireAsk` 不带它,只有 `claimTerminal(expire, { cause })` 带)。 */
104
+ /** `cause`(可选)= 转 park 的触发成因,赢下 PARKING 的同一步落进 `parkTrigger`(SQL twin 同一条 UPDATE 的镜像;接口上的 `expireAsk` 不带它,只有 `claimTerminal(expire, { cause })` 带)——
105
+ * `parkTrigger` 的唯一写点:此后的转移(补丁里没有这一键)不再碰它。 */
105
106
  expireAsk(askId: string, batchId: string, cause?: AskTerminalClaimOpts["cause"]): Promise<ExpireResult>;
106
107
  bindBatch(batchId: string, askId: string, gate: BindGateInput): Promise<BindResult>;
107
108
  deferReconcile(askId: string, expectedState: AskState, expectedRev: number, nowMs: number): Promise<boolean>;
@@ -11,8 +11,8 @@ function applyPatch(row, patch) {
11
11
  row.decisionNote = patch.decisionNote;
12
12
  if (patch.decidedAtMs !== undefined)
13
13
  row.decidedAtMs = patch.decidedAtMs;
14
- if (patch.deniedReason !== undefined)
15
- row.deniedReason = patch.deniedReason;
14
+ if (patch.endingCause !== undefined)
15
+ row.endingCause = patch.endingCause;
16
16
  if (patch.gateToken !== undefined)
17
17
  row.gateToken = patch.gateToken;
18
18
  if (patch.gateBoundCallId !== undefined)
@@ -99,7 +99,8 @@ export class InMemoryApprovalAskStore {
99
99
  decisionNote: null,
100
100
  settledBy: null,
101
101
  decidedAtMs: null,
102
- deniedReason: null,
102
+ endingCause: null,
103
+ parkTrigger: null,
103
104
  gateToken: null,
104
105
  gateBoundCallId: null,
105
106
  gateBoundInputHash: null,
@@ -181,7 +182,7 @@ export class InMemoryApprovalAskStore {
181
182
  const scoped = this.asks.get(askId);
182
183
  if (scoped !== undefined && scoped.batchId !== batchId)
183
184
  return { claimed: false, current: { state: "absent" } };
184
- const won = await this.transitionAsk(askId, "STREAM_PENDING", "VOID", { updatedAtMs: this.now(), ...(opts?.cause !== undefined ? { deniedReason: opts.cause } : {}) });
185
+ const won = await this.transitionAsk(askId, "STREAM_PENDING", "VOID", { updatedAtMs: this.now(), ...(opts?.cause !== undefined ? { endingCause: opts.cause } : {}) });
185
186
  if (won)
186
187
  return { claimed: true, voidedSiblings: [] };
187
188
  break;
@@ -222,7 +223,7 @@ export class InMemoryApprovalAskStore {
222
223
  return { won: false, voidedSiblings: [] };
223
224
  ask.state = "PARKING";
224
225
  if (cause !== undefined)
225
- ask.deniedReason = cause;
226
+ ask.parkTrigger = cause;
226
227
  ask.rev += 1;
227
228
  ask.updatedAtMs = now;
228
229
  if (batch.state === "OPEN") {
@@ -234,7 +235,7 @@ export class InMemoryApprovalAskStore {
234
235
  for (const [id, r] of this.asks) {
235
236
  if (r.batchId === batchId && r.state === "STREAM_PENDING" && id !== askId) {
236
237
  r.state = "VOID";
237
- r.deniedReason = APPROVAL_ENDING_CAUSES.BATCH_SIBLING_WITHDRAWN;
238
+ r.endingCause = APPROVAL_ENDING_CAUSES.BATCH_SIBLING_WITHDRAWN;
238
239
  r.rev += 1;
239
240
  r.updatedAtMs = now;
240
241
  voided.push(id);
@@ -267,7 +268,7 @@ export class InMemoryApprovalAskStore {
267
268
  for (const [id, r] of this.asks) {
268
269
  if (r.batchId === batchId && r.state === "PARKING" && id !== askId) {
269
270
  r.state = "VOID";
270
- r.deniedReason = APPROVAL_ENDING_CAUSES.BATCH_BOUND_ELSEWHERE;
271
+ r.endingCause = APPROVAL_ENDING_CAUSES.BATCH_BOUND_ELSEWHERE;
271
272
  r.rev += 1;
272
273
  r.updatedAtMs = now;
273
274
  voidedSiblings.push(id);
@@ -295,7 +296,7 @@ export class InMemoryApprovalAskStore {
295
296
  for (const [id, r] of this.asks) {
296
297
  if (r.batchId === batchId && r.state === "STREAM_PENDING") {
297
298
  r.state = "VOID";
298
- r.deniedReason = APPROVAL_ENDING_CAUSES.BATCH_ABORTED;
299
+ r.endingCause = APPROVAL_ENDING_CAUSES.BATCH_ABORTED;
299
300
  r.rev += 1;
300
301
  r.updatedAtMs = now;
301
302
  voided.push(id);