@sema-agent/core 7.11.1 → 7.12.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 (78) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/core/auto-mode-arming.d.ts +10 -14
  3. package/dist/core/auto-mode-arming.js +3 -9
  4. package/dist/core/auto-mode-defaults.d.ts +0 -2
  5. package/dist/core/auto-mode-defaults.js +0 -1
  6. package/dist/core/auto-mode-rebuild.d.ts +6 -13
  7. package/dist/core/auto-mode-rebuild.js +0 -2
  8. package/dist/core/auto-mode.d.ts +30 -89
  9. package/dist/core/auto-mode.js +12 -59
  10. package/dist/core/checkpoint-store.d.ts +1 -3
  11. package/dist/core/gate-fold.js +1 -9
  12. package/dist/core/gate-lanes.js +15 -9
  13. package/dist/core/hooks.d.ts +6 -0
  14. package/dist/core/runner/compaction-knobs.d.ts +45 -0
  15. package/dist/core/runner/compaction-knobs.js +3 -0
  16. package/dist/core/runner/contracts.d.ts +49 -10
  17. package/dist/core/runner/denial-limit-arms.d.ts +10 -13
  18. package/dist/core/runner/denial-limit-arms.js +9 -7
  19. package/dist/core/runner/gate-exit.js +9 -1
  20. package/dist/core/runner/prepare-caps-and-workflow.d.ts +1 -1
  21. package/dist/core/runner/prepare-caps-and-workflow.js +0 -5
  22. package/dist/core/runner/prepare-suspend-saga.d.ts +0 -2
  23. package/dist/core/runner/prepare-suspend-saga.js +2 -10
  24. package/dist/core/runner/prepare-task.js +1 -1
  25. package/dist/core/runner/prepare-wiring-manifest.d.ts +1 -1
  26. package/dist/core/runner/prepare-wiring-manifest.js +1 -8
  27. package/dist/core/runner/run-attachment-seats.d.ts +22 -0
  28. package/dist/core/runner/run-attachment-seats.js +187 -0
  29. package/dist/core/runner/run-brain-sinks.d.ts +29 -0
  30. package/dist/core/runner/run-brain-sinks.js +61 -0
  31. package/dist/core/runner/run-clock-and-content.d.ts +52 -0
  32. package/dist/core/runner/run-clock-and-content.js +26 -0
  33. package/dist/core/runner/run-compaction-machinery.d.ts +35 -0
  34. package/dist/core/runner/run-compaction-machinery.js +98 -0
  35. package/dist/core/runner/run-git-lane.d.ts +64 -0
  36. package/dist/core/runner/run-git-lane.js +102 -0
  37. package/dist/core/runner/run-identity-wiring.d.ts +77 -0
  38. package/dist/core/runner/run-identity-wiring.js +85 -0
  39. package/dist/core/runner/run-leg.d.ts +106 -0
  40. package/dist/core/runner/run-leg.js +462 -0
  41. package/dist/core/runner/run-notification-lane.d.ts +55 -0
  42. package/dist/core/runner/run-notification-lane.js +128 -0
  43. package/dist/core/runner/run-reasoning-seat.d.ts +27 -0
  44. package/dist/core/runner/run-reasoning-seat.js +48 -0
  45. package/dist/core/runner/run-recovery-lanes.d.ts +54 -0
  46. package/dist/core/runner/run-recovery-lanes.js +180 -0
  47. package/dist/core/runner/run-settle-and-teardown.d.ts +109 -0
  48. package/dist/core/runner/run-settle-and-teardown.js +324 -0
  49. package/dist/core/runner/run-stop-and-final-verify.d.ts +32 -0
  50. package/dist/core/runner/run-stop-and-final-verify.js +159 -0
  51. package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +38 -0
  52. package/dist/core/runner/run-telemetry-and-budget-seats.js +159 -0
  53. package/dist/core/runner/run-terminal-adoption.d.ts +99 -0
  54. package/dist/core/runner/run-terminal-adoption.js +120 -0
  55. package/dist/core/runner/run-tool-mount-facts.d.ts +26 -0
  56. package/dist/core/runner/run-tool-mount-facts.js +58 -0
  57. package/dist/core/runner/run-turn-boundary.d.ts +0 -35
  58. package/dist/core/runner/run-turn-boundary.js +1 -3
  59. package/dist/core/runner/runtask.d.ts +14 -3
  60. package/dist/core/runner/runtask.js +125 -2102
  61. package/dist/core/runner-deps.d.ts +4 -14
  62. package/dist/core/store-contracts/workflow-journal-store-contract.d.ts +7 -0
  63. package/dist/core/store-contracts/workflow-journal-store-contract.js +85 -0
  64. package/dist/core/tool-policy.d.ts +37 -93
  65. package/dist/core/tool-policy.js +1 -11
  66. package/dist/core/trace.d.ts +6 -7
  67. package/dist/core/wiring-manifest.d.ts +5 -22
  68. package/dist/core/wiring-manifest.js +3 -11
  69. package/dist/core/workflow-journal-store.d.ts +35 -4
  70. package/dist/core/workflow-journal-store.js +19 -2
  71. package/dist/index.d.ts +3 -2
  72. package/dist/index.js +3 -2
  73. package/dist/orchestration/workflow.js +2 -0
  74. package/dist/stores/file/workflow-journal-store.d.ts +7 -10
  75. package/dist/stores/file/workflow-journal-store.js +2 -4
  76. package/dist/tools/fs/fs-bash.js +3 -2
  77. package/package.json +1 -1
  78. package/test/export-surface.snapshot.json +10 -10
@@ -687,17 +687,10 @@ export interface RunnerDeps {
687
687
  sessionContext?: readonly string[];
688
688
  /** Transcript-window bounds for the classify prompt (defaults: 40 entries / 2000 chars each). */
689
689
  window?: import("./auto-mode-prompt.js").AutoModeWindowOptions;
690
- /** Classify round-trip cap, ms (default 15_000). */
690
+ /** Classify round-trip cap, ms (default 15_000). A round past the cap is `unavailable(timeout)`: the call is
691
+ * DENIED with a sentence that says so (CC 2.1.250 form), and the next call is classified afresh — there is no
692
+ * failure count and no session latch. */
691
693
  timeoutMs?: number;
692
- /** Consecutive-failure threshold opening the one-way session breaker (default 3). */
693
- failureThreshold?: number;
694
- /** Fired ONCE when the session breaker opens (the "退回非 auto" alarm — surface it to the operator). The engine
695
- * records the trip on the session's breaker read face (`WiringManifest.autoMode.breaker`, #616) BEFORE calling
696
- * this hook, so a deployment that wires nothing here still gets the read face. */
697
- onBreakerOpen?: (info: {
698
- consecutiveFailures: number;
699
- lastCause: import("./auto-mode.js").AutoModeBreakerCause;
700
- }) => void;
701
694
  /**
702
695
  * The classifier DENIAL LIMIT (CC 2.1.250 `FO`/`AKe`): a run whose classifier keeps blocking falls
703
696
  * back to a PERSON instead of being denied without end. Per run: a `block` first increments the
@@ -736,10 +729,7 @@ export interface RunnerDeps {
736
729
  * line); a deployment that treats those as sensitive at rest should keep them out of the arming
737
730
  * face rather than out of this flag.
738
731
  *
739
- * Recording is CONDITIONAL on the session breaker still being closed at park time: a session that
740
- * already fell back to non-auto hands nothing forward (the redemption must never run wider than the
741
- * ancestor it inherits). A `true` here with a non-serializable face announces once and records
742
- * nothing (`phase:"config"`).
732
+ * A `true` here with a non-serializable face announces once and records nothing (`phase:"config"`).
743
733
  */
744
734
  persistArming?: boolean;
745
735
  /**
@@ -0,0 +1,7 @@
1
+ import { type WorkflowJournalStore } from "../workflow-journal-store.js";
2
+ import { type ContractAssertionRunner } from "./contract-harness.js";
3
+ /**
4
+ * The full cross-backend contract for {@link WorkflowJournalStore}. Every case builds its own store via
5
+ * `make()`; with no runner the cases run sequentially and the first failure throws.
6
+ */
7
+ export declare function workflowJournalStoreContract(make: () => WorkflowJournalStore, runAssertion?: ContractAssertionRunner): Promise<void>;
@@ -0,0 +1,85 @@
1
+ import { strict as assert } from "node:assert";
2
+ import { JOURNAL_OVERSIZE_ERROR_CODE, MAX_JOURNAL_RESULT_BYTES, } from "../workflow-journal-store.js";
3
+ import { beginContract } from "./contract-harness.js";
4
+ const key = (ordinal) => `${ordinal}:h`;
5
+ const completed = (ordinal, over = {}) => ({
6
+ taskId: `t-${ordinal}`,
7
+ sessionId: `s-${ordinal}`,
8
+ terminal: { kind: "completed" },
9
+ result: `r${ordinal}`,
10
+ structuredOutput: { ordinal, nested: { ok: true } },
11
+ stats: { turns: 1, tokens: 1, costMicroUsd: 0, nested: { tokens: 0, turns: 0, tasks: 0, costMicroUsd: 0 } },
12
+ ...over,
13
+ });
14
+ const parked = (ordinal, over = {}) => ({
15
+ taskId: `t-${ordinal}`,
16
+ sessionId: `s-${ordinal}`,
17
+ terminal: { kind: "paused", token: `tok-${ordinal}`, checkpointId: `cp-${ordinal}`, gate: { kind: "human", reason: "approve", toolName: "Write" } },
18
+ result: "",
19
+ structuredOutput: { partial: ordinal },
20
+ stats: { turns: 1, tokens: 1, costMicroUsd: 0, nested: { tokens: 3, turns: 2, tasks: 1, costMicroUsd: 1 } },
21
+ ...over,
22
+ });
23
+ const OVER_CAP = "x".repeat(MAX_JOURNAL_RESULT_BYTES + 1024);
24
+ const isOversizeRefusal = (e) => e instanceof Error && e.code === JOURNAL_OVERSIZE_ERROR_CODE;
25
+ export async function workflowJournalStoreContract(make, runAssertion) {
26
+ const { run, settle } = beginContract(runAssertion);
27
+ run("append → load returns entries ASCENDING by ordinal, both arms verbatim; a re-append per ordinal OVERWRITES", async () => {
28
+ const store = make();
29
+ await store.append("run-1", "tenant-a", { callKey: key(10), result: completed(10) });
30
+ await store.append("run-1", "tenant-a", { callKey: key(1), parked: parked(1) });
31
+ await store.append("run-1", "tenant-a", { callKey: key(0), result: completed(0) });
32
+ await store.append("run-1", "tenant-a", { callKey: key(2), result: completed(2) });
33
+ await store.append("run-1", "tenant-a", { callKey: key(0), result: completed(0, { result: "overwritten" }) });
34
+ const entries = await store.load("run-1", "tenant-a");
35
+ assert.deepEqual(entries.map((e) => e.callKey), [key(0), key(1), key(2), key(10)]);
36
+ assert.deepEqual(entries[0], { callKey: key(0), result: completed(0, { result: "overwritten" }) });
37
+ assert.deepEqual(entries[1], { callKey: key(1), parked: parked(1) });
38
+ });
39
+ run("scope wall: a cross-scope load is EMPTY — before and after the owner's own load, and after a refused cross-scope append — on both arms", async () => {
40
+ const store = make();
41
+ await store.append("run-1", "alice", { callKey: key(0), result: completed(0) });
42
+ await store.append("run-1", "alice", { callKey: key(1), parked: parked(1) });
43
+ assert.deepEqual(await store.load("run-1", "mallory"), []);
44
+ assert.equal((await store.load("run-1", "alice")).length, 2);
45
+ assert.deepEqual(await store.load("run-1", "mallory"), []);
46
+ await assert.rejects(store.append("run-1", "mallory", { callKey: key(2), result: completed(2) }));
47
+ await assert.rejects(store.append("run-1", "mallory", { callKey: key(3), parked: parked(3) }));
48
+ assert.deepEqual(await store.load("run-1", "mallory"), []);
49
+ assert.deepEqual((await store.load("run-1", "alice")).map((e) => e.callKey), [key(0), key(1)]);
50
+ });
51
+ run(`oversize RESULT arm ⇒ append rejects with code "${JOURNAL_OVERSIZE_ERROR_CODE}"; nothing journaled; the store stays usable`, async () => {
52
+ const store = make();
53
+ await store.append("run-1", "tenant-a", { callKey: key(0), result: completed(0) });
54
+ await assert.rejects(store.append("run-1", "tenant-a", { callKey: key(1), result: completed(1, { result: OVER_CAP }) }), isOversizeRefusal);
55
+ assert.deepEqual((await store.load("run-1", "tenant-a")).map((e) => e.callKey), [key(0)]);
56
+ await store.append("run-1", "tenant-a", { callKey: key(2), result: completed(2) });
57
+ assert.deepEqual((await store.load("run-1", "tenant-a")).map((e) => e.callKey), [key(0), key(2)]);
58
+ });
59
+ run(`oversize PARKED arm ⇒ append rejects with code "${JOURNAL_OVERSIZE_ERROR_CODE}"; nothing journaled (never a silent skip — a lost park is a resume running the ordinal live)`, async () => {
60
+ const store = make();
61
+ await store.append("run-1", "tenant-a", { callKey: key(0), result: completed(0) });
62
+ await assert.rejects(store.append("run-1", "tenant-a", { callKey: key(1), parked: parked(1, { result: OVER_CAP }) }), isOversizeRefusal);
63
+ assert.deepEqual((await store.load("run-1", "tenant-a")).map((e) => e.callKey), [key(0)]);
64
+ await store.append("run-1", "tenant-a", { callKey: key(1), parked: parked(1) });
65
+ assert.equal((await store.load("run-1", "tenant-a"))[1].parked?.terminal.kind, "paused");
66
+ });
67
+ run("the cap is inclusive: a payload of EXACTLY the cap's byte count is accepted on both arms", async () => {
68
+ const store = make();
69
+ const fit = (payload) => {
70
+ const base = Buffer.byteLength(JSON.stringify({ ...payload, result: "" }), "utf8");
71
+ return { ...payload, result: "y".repeat(MAX_JOURNAL_RESULT_BYTES - base) };
72
+ };
73
+ const r = fit(completed(0));
74
+ const p = fit(parked(1));
75
+ assert.equal(Buffer.byteLength(JSON.stringify(r), "utf8"), MAX_JOURNAL_RESULT_BYTES);
76
+ assert.equal(Buffer.byteLength(JSON.stringify(p), "utf8"), MAX_JOURNAL_RESULT_BYTES);
77
+ await store.append("run-1", "tenant-a", { callKey: key(0), result: r });
78
+ await store.append("run-1", "tenant-a", { callKey: key(1), parked: p });
79
+ const entries = await store.load("run-1", "tenant-a");
80
+ assert.deepEqual(entries.map((e) => e.callKey), [key(0), key(1)]);
81
+ assert.deepEqual(entries[0].result, r);
82
+ assert.deepEqual(entries[1].parked, p);
83
+ });
84
+ await settle();
85
+ }
@@ -207,20 +207,12 @@ export type PermissionResult = {
207
207
  * same safe direction as `matchedAskRule`); it cannot state a window of its own, because the
208
208
  * route it would be a window for has not been chosen yet. */
209
209
  denialLimitFallback?: import("./auto-mode.js").UnarmedDenialLimitFallback;
210
- /** #616 (additive): the auto-mode classifier was CONSULTED on this ask and could not run the ask
211
- * flows the original chain exactly as it would have (routing, origin, bit and members unchanged),
212
- * carrying the station FACT beside them so a card can say "asked because the classifier was
213
- * unavailable (timeout)" instead of reading as ordinary hesitation. `cause` is the verdict's own
214
- * word ({@link import("./auto-mode.js").AutoModeUnavailableCause}: `error` / `timeout` /
215
- * `breaker_open`). A FACT, not an origin: CC 2.1.250 marks the same condition as a denial KIND on
216
- * the outcome (`automode-unavailable`) beside the decision's provenance, never in place of it, and
217
- * this engine's divergence (the ask reaches a person instead of being denied) does not move the
218
- * fact onto the origin axis either. ENGINE-STAMPED at the classifier stations (the gate's own and
219
- * the inherited-lane arms) only when the verdict was `unavailable`; `parse_error` stamps nothing
220
- * (the classifier ran and answered outside its contract — a different sentence). Display metadata:
221
- * nothing reads it to decide anything, so a policy that self-declares it can only put its own
222
- * sentence on its own card. Carried onto the approval request and the durable row by the carry
223
- * stations. */
210
+ /** #616 (additive): the classifier-unavailable FACT on an ASK `cause` is the verdict's own word
211
+ * ({@link import("./auto-mode.js").AutoModeUnavailableCause}). Since #661 the ENGINE no longer writes
212
+ * it here: an `unavailable` verdict is a DENY (the deny arm's member above), never an ask handed to a
213
+ * person. The member stays on the ask arm as display metadata a policy may self-declare on its own
214
+ * ask (nothing reads it to decide anything); the carry stations still copy it onto the approval
215
+ * request and the durable row when present. Retirement candidate for the next wire window. */
224
216
  classifierUnavailable?: {
225
217
  readonly cause: import("./auto-mode.js").AutoModeUnavailableCause;
226
218
  };
@@ -298,6 +290,16 @@ export type PermissionResult = {
298
290
  updatedInput?: unknown;
299
291
  message?: string;
300
292
  decisionReason?: DecisionReason;
293
+ /** #661 (additive): this deny is the classifier's UNAVAILABILITY — the auto-mode classifier was consulted
294
+ * and could not run, and the call is refused with a sentence that says so (CC 2.1.250 `Ze.unavailable` ⇒
295
+ * `deny`, `x1t`). `cause` is the verdict's own word ({@link import("./auto-mode.js").AutoModeUnavailableCause}).
296
+ * ENGINE-STAMPED beside `decisionReason: "classifier"` at the classifier stations (the gate's own and the
297
+ * inherited-lane arms) on an `unavailable` verdict only; a `block` and a `parse_error` deny carry nothing
298
+ * here. Read by the deny observer's payload ({@link import("./hooks.js").PermissionDeniedPayload}) and by
299
+ * nothing that decides. */
300
+ classifierUnavailable?: {
301
+ readonly cause: import("./auto-mode.js").AutoModeUnavailableCause;
302
+ };
301
303
  };
302
304
  /**
303
305
  * design/252 G-2 — WHY a piece of rule-provenance evidence is not on an ask.
@@ -540,100 +542,42 @@ export declare function constraintChainEntryOf(policy: ToolPolicy, meta?: Constr
540
542
  /**
541
543
  * One inherited-chain LAYER as both chain-entry mint sites see it — structurally an
542
544
  * `InheritedGate.parentConstraints[]` element, typed here structurally so this module (which owns the
543
- * digest) does not depend on the runner's shapes.
545
+ * digest) does not depend on the runner's shapes. The classifier itself is not read: a decider carries no
546
+ * state between rounds, so nothing about its health enters a chain entry.
544
547
  */
545
548
  export interface ConstraintChainLayerView {
546
549
  policy: ToolPolicy;
547
550
  autoMode?: {
548
- decider: {
549
- breakerOpen: () => boolean;
550
- consecutiveFailures?: () => number;
551
- };
552
551
  arming?: unknown;
553
552
  };
554
553
  durableMandate?: boolean;
555
554
  contentMandate?: boolean;
556
555
  }
557
556
  /**
558
- * The WRITE-side mint: the chain entry a durable PARK records for a live inherited layer.
559
- *
560
- * #503 safety bound lives HERE, at the persistence point rather than at arming time: the recipe is
561
- * carried only while the layer's breaker is UNTRIPPED AND UNTOUCHED. An open breaker is the ancestor
562
- * session's one-way "fell back to non-auto" latch — its arming has been withdrawn, and handing that
563
- * arming to a redemption would let the redeemed leg run auto-classified where the ancestor no longer does
564
- * (a redemption WIDER than the ancestor it inherits). The latch is read at the moment the row is written,
565
- * so an ancestor whose classifier died mid-run parks WITHOUT a recipe even though it was armed when the
566
- * chain was assembled.
567
- *
568
- * `breakerOpen()` alone is NOT the whole latch, and a closed-only test left a real widening: a rebuilt
569
- * decider starts a FRESH failure streak, so an ancestor sitting at 2 failures out of 3 — one bad
570
- * classification from dropping the whole session to non-auto — would hand a redemption the full budget
571
- * again, and the redeemed leg stays auto-classified through failures the ancestor would not have
572
- * survived. So the streak must read ZERO as well. A decider that does not expose it at all is UNKNOWN
573
- * state, and unknown takes the same arm as tripped: nothing is recorded (which is why a host that hand
574
- * rolls a decider cannot use the persisted-arming path — it must mint through `createAutoModeDecider`,
575
- * on both the arming and the redeeming side).
576
- *
577
- * Every read here is guarded, and every failure to read is treated as "withdrawn" — the opposite default
578
- * from the gate's demote-to-ask breaker reader, and for the same reason both are right: there, an
579
- * unreadable latch must not suppress a tighten; here, it must not license a carry-forward.
580
- *
581
- * ⚠️ WRITE SIDE ONLY. This gate reads MUTABLE state (a one-way latch, plus a streak that both grows and
582
- * resets to zero on any healthy classification). The resume re-supply must NOT re-run it — it samples a
583
- * different instant, and the two instants disagree for perfectly ordinary reasons: an ancestor keeps
584
- * running while its delegated child sits parked awaiting a human. See
585
- * {@link constraintChainEntryOfSuppliedLayer} for the verify side and the full account.
586
- *
587
- * ⚖️ WHAT THIS BOUND DOES NOT DO (recorded, precise shape). A withdrawn arming leaves the entry at
588
- * `autoModeArmed: true` with no recipe — digest-identical to an armed layer whose deployment never opted
589
- * in. So the digest does not, and cannot, stop a re-supplier from handing back a FRESH healthy decider of
590
- * its own: that is the pre-existing re-supply trust posture (see `resumeStream`'s contract — a caller
591
- * able to invoke it already holds full Runner authority, and the chain checks catch shape MISTAKES, not
592
- * a hostile trusted caller), and it is unchanged here. What this bound governs is what CORE hands
593
- * forward: a withdrawn ancestor's CRITERIA never travel, so a redemption cannot reconstruct the
594
- * ancestor's classifier from the row. For a fleet running `persistArming` everywhere, the pair
595
- * (`autoModeArmed` present, `autoModeArming` absent) is itself the readable signal that the ancestor's
596
- * arming was withdrawn or unrecordable, and such a host should re-supply a refusing decider rather than a
597
- * fresh one. Making the WITHDRAWAL itself digest-bound is a coherent stronger design — it needs a new
598
- * marker plus a new re-supply obligation to reproduce it (a withdrawn row carries no recipe, so the
599
- * marker cannot be derived from one), which is a scope decision for this seam's owner, not a silent
600
- * mechanism change.
557
+ * The WRITE-side mint: the chain entry a durable PARK records for a live inherited layer. The recipe is
558
+ * carried whenever the layer has one (the deployment opted into `persistArming`). The former gate — "only
559
+ * while the ancestor's breaker is untripped and untouched" went with the breaker: a decider carries no
560
+ * state between rounds, so there is no ancestor health to sample here and nothing that could make a
561
+ * redemption run wider than the ancestor it inherits.
562
+ *
563
+ * ⚖️ WHAT THE ROW DOES NOT SAY. An armed layer whose deployment never opted in leaves the entry at
564
+ * `autoModeArmed: true` with no recipe. The digest does not, and cannot, stop a re-supplier from handing
565
+ * back a FRESH decider of its own: that is the pre-existing re-supply trust posture (see `resumeStream`'s
566
+ * contract — a caller able to invoke it already holds full Runner authority, and the chain checks catch
567
+ * shape MISTAKES, not a hostile trusted caller). What CORE hands forward is the recipe when there is one,
568
+ * and nothing otherwise.
601
569
  */
602
570
  export declare function constraintChainEntryOfLayer(layer: ConstraintChainLayerView): ConstraintChainEntry;
603
571
  /**
604
572
  * The VERIFY-side mint: the entry a resume's RE-SUPPLIED layer must reproduce for the row it is
605
573
  * redeeming. Same body as the park mint, with the arming carry taken from the ROW rather than from the
606
- * layer's live classifier health so the digest equality is over bytes the row itself recorded, and
607
- * over nothing that can move while the row sits parked.
608
- *
609
- * WHY THE CARRY CANNOT BE RE-DERIVED HERE. Safety bound (see {@link constraintChainEntryOfLayer}) is a
610
- * statement about the ancestor at the instant the row was WRITTEN. Re-running it against the same live
611
- * layer at redemption time samples a different instant, and both directions of the disagreement strand an
612
- * approval a human already granted:
613
- * - the ancestor was healthy at the park (the row carries the recipe) and its breaker opened while the
614
- * child sat parked ⇒ the re-supply mints WITHOUT the recipe, the digests differ, and because the
615
- * latch is ONE-WAY the row is refused `resume.parent_constraint_mismatch` forever;
616
- * - the ancestor was one failure into its streak at the park (the row carries no recipe) and the streak
617
- * cleared on its next healthy classification ⇒ the re-supply mints WITH a recipe the row never
618
- * carried, same refusal.
619
- * Neither needs a host mistake: the ancestor and its siblings keep running while the child is parked, and
620
- * the same-process `resume()` verb re-supplies the very same live layer objects out of the Runner's own
621
- * parked-constraint registry. Nothing about the row changed; only a classifier's health did.
622
- *
623
- * WHAT THIS COSTS, NAMED. A row that recorded NO recipe no longer refuses a re-supply that hands one
624
- * back. That is not a tunable — the re-supply side literally cannot tell "the row withdrew a recipe this
625
- * same live layer still declares" (the second bullet above, a legitimate redemption) from "a re-supplier
626
- * is putting a recipe onto a row that withdrew one": both are the identical pair of inputs. It is also
627
- * thinner than it looks. The recipe is not an enforcement object — the DECIDER classifies, and the
628
- * re-supply trust posture already permits handing back a fresh healthy one, which is the capability that
629
- * would matter. What the row's absence still means is unchanged and is the property bound ① was written
630
- * for: a withdrawn ancestor's criteria never travel ON THE ROW, so no redemption can reconstruct that
631
- * ancestor's classifier from the checkpoint.
632
- *
633
- * WHAT THE DIGEST STILL BINDS. A row that DID record a recipe must have that recipe handed back: the
634
- * carry is on, so a re-supply that drops the arming, or hands back a different one, mints a different
635
- * entry and is refused pre-CAS exactly as before. That is the whole of the documented re-supply contract
636
- * — "hand back what the row carries".
574
+ * layer — the digest equality is over bytes the row itself recorded, never over what a live object says at
575
+ * a second instant. A row that DID record a recipe must have that recipe handed back: a re-supply that drops
576
+ * the arming, or hands back a different one, mints a different entry and is refused pre-CAS
577
+ * (`resume.parent_constraint_mismatch`). A row that recorded none does not refuse a re-supply that hands one
578
+ * back the recipe is not an enforcement object (the DECIDER classifies), and the re-supply trust posture
579
+ * already permits a fresh decider; what the row's absence still means is that no redemption can reconstruct
580
+ * the ancestor's classifier FROM THE CHECKPOINT.
637
581
  */
638
582
  export declare function constraintChainEntryOfSuppliedLayer(layer: ConstraintChainLayerView, recorded: ConstraintChainEntry | undefined): ConstraintChainEntry;
639
583
  /**
@@ -128,17 +128,7 @@ function chainEntryOfLayerCarrying(layer, carryArming) {
128
128
  });
129
129
  }
130
130
  export function constraintChainEntryOfLayer(layer) {
131
- let armingLive = false;
132
- if (layer.autoMode?.arming !== undefined) {
133
- try {
134
- const d = layer.autoMode.decider;
135
- armingLive = d.breakerOpen() !== true && typeof d.consecutiveFailures === "function" && d.consecutiveFailures() === 0;
136
- }
137
- catch {
138
- armingLive = false;
139
- }
140
- }
141
- return chainEntryOfLayerCarrying(layer, armingLive);
131
+ return chainEntryOfLayerCarrying(layer, layer.autoMode?.arming !== undefined);
142
132
  }
143
133
  export function constraintChainEntryOfSuppliedLayer(layer, recorded) {
144
134
  return chainEntryOfLayerCarrying(layer, recorded?.autoModeArming !== undefined);
@@ -529,11 +529,10 @@ export type TraceEvent = {
529
529
  } | {
530
530
  /**
531
531
  * The auto-mode classifier decided (or declined to decide) one pending ask — one frame per
532
- * `decide` call, including the breaker-open short-circuit, so a consumer can read the gate's
533
- * wall-clock cost per tool call and the failure cause behind an ask that reached a person. A
534
- * TRACE frame rather than a task event: it fires once per gated call, it is an operator's read
535
- * (why did that ask take four seconds / why did it fall back), and the session-level breaker
536
- * read face a shell renders is a separate, additive wire face. `ms` is the wall time from the
532
+ * `decide` call, so a consumer can read the gate's wall-clock cost per tool call and the failure
533
+ * cause behind a call the classifier could not evaluate. A TRACE frame rather than a task event: it
534
+ * fires once per gated call and it is an operator's read (why did that ask take four seconds / why
535
+ * was that call denied). `ms` is the wall time from the
537
536
  * decide call to its verdict (timeout included: a timed-out round reads the deadline).
538
537
  */
539
538
  kind: "auto_mode.classified";
@@ -547,8 +546,8 @@ export type TraceEvent = {
547
546
  ms: number;
548
547
  /** The verdict kind the gate acted on (`allow` / `block` / `unavailable` / `parse_error`). */
549
548
  verdict: "allow" | "block" | "unavailable" | "parse_error";
550
- /** Present iff `verdict === "unavailable"`: `error` / `timeout` / `breaker_open`. */
551
- cause?: "error" | "timeout" | "breaker_open";
549
+ /** Present iff `verdict === "unavailable"`: `error` / `timeout`. */
550
+ cause?: "error" | "timeout";
552
551
  ts: number;
553
552
  } | {
554
553
  /** C1 — the failover brain served this call from a FALLBACK entry (`createFailoverBrain`): the
@@ -71,13 +71,8 @@ export type ParkLaneReason = "no_checkpoint_store" | "no_durable_approval_opt_in
71
71
  * verdict; the same fail-closed outcome as `denied`, named apart so a consumer
72
72
  * can tell "policy said no" from "the entitlement source was unreachable".
73
73
  * - `armed` — all three arms held; the classifier decider was minted for this leg.
74
- * - `latch_open` — the session's one-way "back to non-auto" breaker has tripped. A leg-START
75
- * manifest never carries it (a decider is minted fresh at prepare and its latch
76
- * is closed by construction; a tripped ancestor hands no intent forward, which
77
- * reads as `no_intent`); it is in the vocabulary so a consumer switching on the
78
- * closed set is complete for a mid-leg re-read face, which nothing mints today.
79
74
  */
80
- export declare const AUTO_MODE_ARM_REASONS: readonly ["armed", "no_intent", "no_face", "denied", "resolver_fault", "latch_open"];
75
+ export declare const AUTO_MODE_ARM_REASONS: readonly ["armed", "no_intent", "no_face", "denied", "resolver_fault"];
81
76
  export type AutoModeArmReason = (typeof AUTO_MODE_ARM_REASONS)[number];
82
77
  /**
83
78
  * One declared MCP server's materialization result for THIS leg — the per-server connection-state
@@ -243,22 +238,13 @@ export interface WiringManifest {
243
238
  * per-leg READ face of the auto-mode arming decision: `armed` ⇔ `reason === "armed"` (the
244
239
  * classifier decider was minted for this leg), and `reason` names the first arm that failed
245
240
  * otherwise — see {@link AUTO_MODE_ARM_REASONS}. A serving layer that used to infer the mode from
246
- * spec shape reads it here instead.
247
- *
248
- * `breaker` (#616) the SESSION-level breaker read face: the most recent one-way breaker trip recorded
249
- * for this session on this Runner (an EARLIER leg's decider — the latch is a per-run fact and a leg-start
250
- * manifest's own decider is closed by construction, which is why there is no `open` boolean here: it
251
- * would read `false` on every mint; the reason vocabulary reserves `latch_open` for a mid-leg re-read
252
- * face nothing mints today). Present ⇔ a trip was recorded (never tripped, evicted or a standalone
253
- * prepare ⇒ absent). `{ openedAtMs, lastCause, failures, runId }` — see
254
- * {@link import("./auto-mode.js").AutoModeBreakerTrip}. A per-leg observation like `mcp`, so it is NOT
255
- * part of {@link configFingerprint}: legs of one assembly must fingerprint alike whether or not a
256
- * classifier was down.
241
+ * spec shape reads it here instead. The classifier's availability is a per-CALL fact (an unavailable
242
+ * round is a deny that says so, and the next round is classified afresh), so there is no session-level
243
+ * breaker face here and nothing about availability enters {@link configFingerprint}.
257
244
  */
258
245
  autoMode?: {
259
246
  armed: boolean;
260
247
  reason: AutoModeArmReason;
261
- breaker?: import("./auto-mode.js").AutoModeBreakerTrip;
262
248
  };
263
249
  /**
264
250
  * EFFECTIVE half only, and ALWAYS present on an engine-minted effective manifest (the `autoMode`
@@ -347,13 +333,10 @@ export interface WiringFacts {
347
333
  restore: string;
348
334
  };
349
335
  /** Effective half only — see {@link WiringManifest.autoMode}; the static half has no leg to arm.
350
- * `armed` must agree with `reason` (`armed ⇔ reason === "armed"`); a contradicting pair is refused.
351
- * `breaker` (#616) is the session's most recent recorded trip, or absent; a trip whose cause is outside
352
- * the closed set or whose numbers are not finite is refused. */
336
+ * `armed` must agree with `reason` (`armed ⇔ reason === "armed"`); a contradicting pair is refused. */
353
337
  autoMode?: {
354
338
  armed: boolean;
355
339
  reason: AutoModeArmReason;
356
- breaker?: import("./auto-mode.js").AutoModeBreakerTrip;
357
340
  };
358
341
  /** Effective half only — see {@link WiringManifest.mcp}; the static half materializes nothing.
359
342
  * Copied entry-wise onto the manifest (the caller's array is never aliased). */
@@ -1,9 +1,8 @@
1
1
  import { createHash } from "node:crypto";
2
- import { AUTO_MODE_BREAKER_CAUSES, isAutoModeBreakerCause } from "./auto-mode.js";
3
2
  import { canonicalize } from "./canonical-json.js";
4
3
  import { resolveCheckpointStore } from "./checkpoint-store.js";
5
4
  import { isLiveQuestionFace } from "./ask-question.js";
6
- export const AUTO_MODE_ARM_REASONS = ["armed", "no_intent", "no_face", "denied", "resolver_fault", "latch_open"];
5
+ export const AUTO_MODE_ARM_REASONS = ["armed", "no_intent", "no_face", "denied", "resolver_fault"];
7
6
  export function resolveDeclaredDurability(store, storeName) {
8
7
  const declared = store?.durability;
9
8
  if (declared === undefined)
@@ -72,13 +71,7 @@ function readAutoModeFact(fact) {
72
71
  if (!AUTO_MODE_ARM_REASONS.includes(fact.reason) || fact.armed !== (fact.reason === "armed")) {
73
72
  throw new Error(`the auto-mode wiring fact is inconsistent (armed=${String(fact.armed)}, reason=${JSON.stringify(fact.reason)}) — armed must hold exactly when reason is "armed", and reason must be one of ${AUTO_MODE_ARM_REASONS.join("|")}`);
74
73
  }
75
- const b = fact.breaker;
76
- if (b === undefined)
77
- return { armed: fact.armed, reason: fact.reason };
78
- if (!isAutoModeBreakerCause(b.lastCause) || !Number.isFinite(b.openedAtMs) || !Number.isInteger(b.failures) || b.failures < 1 || typeof b.runId !== "string" || b.runId === "") {
79
- throw new Error(`the auto-mode breaker fact is malformed (${JSON.stringify(b)}) — lastCause must be one of ${AUTO_MODE_BREAKER_CAUSES.join("|")}, openedAtMs finite, failures a positive integer, runId a non-empty string`);
80
- }
81
- return { armed: fact.armed, reason: fact.reason, breaker: { openedAtMs: b.openedAtMs, lastCause: b.lastCause, failures: b.failures, runId: b.runId } };
74
+ return { armed: fact.armed, reason: fact.reason };
82
75
  }
83
76
  export function deriveWiringManifest(facts) {
84
77
  if (facts.half === "static" && facts.leg !== undefined) {
@@ -140,9 +133,8 @@ export function deriveWiringManifest(facts) {
140
133
  const { leg: _leg, mcp: _mcp, tools: _tools, ...assembly } = manifest;
141
134
  const { provenance: _askSeat, ...askForHash } = assembly.ask;
142
135
  const { provenance: _questionSeat, ...questionForHash } = assembly.question;
143
- const { breaker: _breaker, ...autoModeForHash } = assembly.autoMode ?? {};
144
136
  manifest.configFingerprint = createHash("sha256")
145
- .update(canonicalize({ ...assembly, ask: askForHash, question: questionForHash, ...(assembly.autoMode !== undefined ? { autoMode: autoModeForHash } : {}) }))
137
+ .update(canonicalize({ ...assembly, ask: askForHash, question: questionForHash }))
146
138
  .digest("hex")
147
139
  .slice(0, 16);
148
140
  }
@@ -162,11 +162,42 @@ export declare function callKeyOrdinal(callKey: string): number;
162
162
  * say WHY an ordinal never replays. `failed` + this code lands on the T2A-4 no-replay arm, so replay
163
163
  * behavior is identical to the entry simply being absent (the pre-tombstone shape). */
164
164
  export declare const JOURNAL_OVERSIZE_ERROR_CODE = "workflow.journal_oversize";
165
+ /**
166
+ * The refusal every {@link WorkflowJournalStore.append} answers an oversize entry with (#672): a stable
167
+ * `code` ({@link JOURNAL_OVERSIZE_ERROR_CODE} — the same word the engine's result-arm tombstone carries, so
168
+ * one code means "too big for the journal" on every face) and a message a person can act on (the bytes, the
169
+ * cap, the entry's callKey). A backend outside this package throws THIS class from its own `append` — the
170
+ * contract kit asserts the code only, so the class is the convenience, the code is the contract.
171
+ */
172
+ export declare class WorkflowJournalOversizeError extends Error {
173
+ readonly callKey: string;
174
+ readonly bytes: number;
175
+ readonly cap: number;
176
+ readonly code = "workflow.journal_oversize";
177
+ constructor(callKey: string, bytes: number, cap?: number);
178
+ }
179
+ /**
180
+ * @contract workflow.journal_oversize.refused — every {@link WorkflowJournalStore.append} REFUSES an entry whose
181
+ * payload (the `parked` arm when present, else the `result` arm) serializes to more than
182
+ * {@link MAX_JOURNAL_RESULT_BYTES} UTF-8 bytes: it throws {@link WorkflowJournalOversizeError} and journals
183
+ * nothing, on BOTH arms alike — no backend degrades an oversize entry to a silent skip, and no backend
184
+ * judges the size by a rule of its own. A backend calls this first, before it touches its medium.
185
+ *
186
+ * Why a refusal and not the earlier skip (RB-168 "every backend degrades identically"): the skip was written
187
+ * for the `result` arm, where the engine measures the SAME serialization first and journals a small
188
+ * tombstone (RB-243) — a completed result never reaches this refusal. The `parked` arm (#642) is the child's
189
+ * whole paused TaskResult, which can exceed the cap; a park the journal dropped silently is a resume that
190
+ * runs the ordinal live beside the pinned child, and the write is the one moment the deployment can be told.
191
+ * A bad value answered with a default is the failure mode this repository refuses by default; the loud
192
+ * answer is the rule, the engine's tombstone the one place that pre-empts it.
193
+ */
194
+ export declare function assertJournalEntryFits(entry: WorkflowJournalEntry): void;
165
195
  /** RB-243: mint the tiny FAILED entry the ENGINE journals in place of an oversize result. The
166
- * tombstone is minted in the engine, NOT in a store — the stores' skip semantics are unchanged (the
167
- * §4.1 backend-parity pin depends on that layering; a store-side tombstone would re-create the RB-168
168
- * dialect divergence). Never copies `result.result`/`structuredOutput` (the oversize payload); keeps
169
- * taskId/sessionId/stats so the journal read-face still shows the session handle and the paid spend. */
196
+ * tombstone is minted in the engine, NOT in a store — a store REFUSES an oversize entry
197
+ * ({@link assertJournalEntryFits}) and never invents one; the engine measures the durable copy first, so
198
+ * the store sees the tombstone, not the payload. Never copies `result.result`/`structuredOutput` (the
199
+ * oversize payload); keeps taskId/sessionId/stats so the journal read-face still shows the session handle
200
+ * and the paid spend. Its own serialization is a few hundred bytes — always under the cap (pinned). */
170
201
  export declare function journalOversizeTombstone(result: TaskResult, bytes: number): TaskResult;
171
202
  /**
172
203
  * RB-367 (dual-process probe, core-side judgment) — the TTL a resume claim {@link
@@ -9,6 +9,24 @@ export function callKeyOrdinal(callKey) {
9
9
  return Number.isFinite(n) ? n : 0;
10
10
  }
11
11
  export const JOURNAL_OVERSIZE_ERROR_CODE = "workflow.journal_oversize";
12
+ export class WorkflowJournalOversizeError extends Error {
13
+ callKey;
14
+ bytes;
15
+ cap;
16
+ code = JOURNAL_OVERSIZE_ERROR_CODE;
17
+ constructor(callKey, bytes, cap = MAX_JOURNAL_RESULT_BYTES) {
18
+ super(`workflow journal: entry "${callKey}" is ${bytes} bytes, over the ${cap}-byte per-entry cap — refused, nothing journaled`);
19
+ this.callKey = callKey;
20
+ this.bytes = bytes;
21
+ this.cap = cap;
22
+ this.name = "WorkflowJournalOversizeError";
23
+ }
24
+ }
25
+ export function assertJournalEntryFits(entry) {
26
+ const serialized = JSON.stringify(entry.parked !== undefined ? entry.parked : entry.result);
27
+ if (oversizeJournalResult(serialized))
28
+ throw new WorkflowJournalOversizeError(entry.callKey, Buffer.byteLength(serialized, "utf8"));
29
+ }
12
30
  export function journalOversizeTombstone(result, bytes) {
13
31
  const message = `[journal] result omitted: ${bytes} bytes exceeds the ${MAX_JOURNAL_RESULT_BYTES}-byte per-entry journal cap; ` +
14
32
  `the agent's real terminal status was "${terminalProjection(result.terminal).status}". A resume re-runs this agent (and everything after it) live.`;
@@ -65,8 +83,7 @@ export class InMemoryWorkflowJournalStore {
65
83
  return [...rec.byOrdinal.entries()].sort((a, b) => a[0] - b[0]).map(([, e]) => snapshot(e));
66
84
  }
67
85
  async append(runId, scope, entry) {
68
- if (oversizeJournalResult(JSON.stringify(entry.parked !== undefined ? entry.parked : entry.result)))
69
- return;
86
+ assertJournalEntryFits(entry);
70
87
  let rec = this.runs.get(runId);
71
88
  if (!rec) {
72
89
  rec = { scope, byOrdinal: new Map() };
package/dist/index.d.ts CHANGED
@@ -168,7 +168,7 @@ export { type AskOrigin, ASK_ORIGINS, isAskOrigin, classifierMayAnswer, ORIGIN_I
168
168
  export { SETTLEMENT_KINDS, type SettlementKind, isSettlementKind, type Settlement, SETTLEMENT_IS_REFUSAL, DENIED_BY_VALUES, type DeniedBy, isDeniedBy, DENIED_BY_MAY_VETO, type GateDisposition, type GateOutcome, screenGateOutcome } from "./core/gate-outcome.js";
169
169
  export { type AskCarry } from "./core/hooks.js";
170
170
  export { SWAPPABLE_DEP_SEATS, SWAP_SEAT_NOTICE, isSwappableDepSeat, screenSwappableDeps, type SwappableDepSeat, type SwappableDeps } from "./core/swappable-deps.js";
171
- export { parseAutoModeResponse, createAutoModeDecider, AUTO_MODE_UNAVAILABLE_CAUSES, isAutoModeUnavailableCause, AUTO_MODE_BREAKER_CAUSES, isAutoModeBreakerCause, AutoModeBreakerLedger, type AutoModeUnavailableCause, type AutoModeBreakerCause, type AutoModeBreakerTrip, type AutoModeVerdict, type AutoModeDecider, type AutoModeDeciderOptions, type AutoModeClassified, type AutoModeClassifyFn, type AutoModeClassifyInput, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow, type AutoModeDenialTracker, type AutoModeDenialLimitOptions, type DenialLimitCounts, type DenialLimitFallbackFace, type UnarmedDenialLimitFallback, type DenialLimitFallback, type DenialLimitVerdict, } from "./core/auto-mode.js";
171
+ export { parseAutoModeResponse, createAutoModeDecider, AUTO_MODE_UNAVAILABLE_CAUSES, isAutoModeUnavailableCause, classifierUnavailableDenyMessage, CLASSIFIER_PARSE_FAILURE_DENY_MESSAGE, type AutoModeUnavailableCause, type AutoModeVerdict, type AutoModeDecider, type AutoModeDeciderOptions, type AutoModeClassified, type AutoModeClassifyFn, type AutoModeClassifyInput, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow, type AutoModeDenialTracker, type AutoModeDenialLimitOptions, type DenialLimitCounts, type DenialLimitFallbackFace, type UnarmedDenialLimitFallback, type DenialLimitFallback, type DenialLimitVerdict, } from "./core/auto-mode.js";
172
172
  export { AUTO_MODE_DENIAL_LIMIT_DEFAULTS, AUTO_MODE_DENIAL_AUTO_DENY_DEFAULT_MS, AUTO_MODE_CLASSIFIER_MAX_TOKENS } from "./core/auto-mode-defaults.js";
173
173
  export { buildAutoModePrompt, renderAutoModeWindow, renderAutoModeAction, AUTO_MODE_DEFAULTS_SENTINEL, type AutoModeRules, type BuildAutoModePromptOptions, type AutoModeWindowOptions, } from "./core/auto-mode-prompt.js";
174
174
  export { AUTO_MODE_BASE_PROMPT, AUTO_MODE_PERMISSIONS_EXTERNAL } from "./core/auto-mode-prompt-assets.js";
@@ -259,6 +259,7 @@ export { checkpointStoreContract } from "./core/store-contracts/checkpoint-store
259
259
  export { sessionRepoContract } from "./core/store-contracts/session-repo-contract.js";
260
260
  export { toolResultStoreContract } from "./core/store-contracts/tool-result-store-contract.js";
261
261
  export { fileHistoryStoreContract } from "./core/store-contracts/file-history-store-contract.js";
262
+ export { workflowJournalStoreContract } from "./core/store-contracts/workflow-journal-store-contract.js";
262
263
  export { permissionRuleSyncContract, type PermissionRuleSyncContractHooks } from "./core/store-contracts/permission-rule-sync-contract.js";
263
264
  export { MAILBOX_CONTRACT_SCOPE, mailboxStoreContract, mailboxAckOwnershipContract, mailboxBundledOnlyContract, mailboxTombstonedRecipientContract, type MailboxTombstonedRecipientContractHooks, mailboxCrossProcessContract, type MailboxCrossProcessContractHooks, } from "./core/store-contracts/mailbox-store-contract.js";
264
265
  export { BACKGROUND_AGENT_CONTRACT_SCOPE, backgroundAgentStoreContract, backgroundAgentStoreScopesContract, } from "./core/store-contracts/background-agent-store-contract.js";
@@ -267,7 +268,7 @@ export { serveDurableAgentRowLane, buildAgentPollDetails, type AgentPollDetailsI
267
268
  export type { UnifiedTaskResult } from "./core/task-registry-shared.js";
268
269
  export { FileBackgroundAgentStore, type FileBackgroundAgentStoreOptions } from "./stores/file/background-agent-store.js";
269
270
  export { A2A_TASK_STATES, type A2ATaskState, type A2ATaskStateReversal, type A2ATaskStateReversalFaithful, type A2ATaskStateReversalLossy, toA2ATaskState, fromA2ATaskState, } from "./core/a2a-task-state.js";
270
- export { type WorkflowJournalStore, type WorkflowJournalEntry, InMemoryWorkflowJournalStore, callKeyOrdinal, JOURNAL_OVERSIZE_ERROR_CODE, } from "./core/workflow-journal-store.js";
271
+ export { type WorkflowJournalStore, type WorkflowJournalEntry, InMemoryWorkflowJournalStore, callKeyOrdinal, JOURNAL_OVERSIZE_ERROR_CODE, WorkflowJournalOversizeError, assertJournalEntryFits, } from "./core/workflow-journal-store.js";
271
272
  export { untrustedEgressForHuman, redactHostLeaks, redactSecrets, boundedRedactedSummary } from "./core/untrusted-egress.js";
272
273
  export type { RedactionFinding, RedactionReport, RedactionConfidence, RedactionSource, SummarizableFinding, } from "./core/untrusted-egress.js";
273
274
  export { summarizeRedactions } from "./core/untrusted-egress.js";
package/dist/index.js CHANGED
@@ -142,7 +142,7 @@ export { ASK_ORIGINS, isAskOrigin, classifierMayAnswer, ORIGIN_IMPLIES_REAL_APPR
142
142
  export { SETTLEMENT_KINDS, isSettlementKind, SETTLEMENT_IS_REFUSAL, DENIED_BY_VALUES, isDeniedBy, DENIED_BY_MAY_VETO, screenGateOutcome } from "./core/gate-outcome.js";
143
143
  export {} from "./core/hooks.js";
144
144
  export { SWAPPABLE_DEP_SEATS, SWAP_SEAT_NOTICE, isSwappableDepSeat, screenSwappableDeps } from "./core/swappable-deps.js";
145
- export { parseAutoModeResponse, createAutoModeDecider, AUTO_MODE_UNAVAILABLE_CAUSES, isAutoModeUnavailableCause, AUTO_MODE_BREAKER_CAUSES, isAutoModeBreakerCause, AutoModeBreakerLedger, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow, } from "./core/auto-mode.js";
145
+ export { parseAutoModeResponse, createAutoModeDecider, AUTO_MODE_UNAVAILABLE_CAUSES, isAutoModeUnavailableCause, classifierUnavailableDenyMessage, CLASSIFIER_PARSE_FAILURE_DENY_MESSAGE, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow, } from "./core/auto-mode.js";
146
146
  export { AUTO_MODE_DENIAL_LIMIT_DEFAULTS, AUTO_MODE_DENIAL_AUTO_DENY_DEFAULT_MS, AUTO_MODE_CLASSIFIER_MAX_TOKENS } from "./core/auto-mode-defaults.js";
147
147
  export { buildAutoModePrompt, renderAutoModeWindow, renderAutoModeAction, AUTO_MODE_DEFAULTS_SENTINEL, } from "./core/auto-mode-prompt.js";
148
148
  export { AUTO_MODE_BASE_PROMPT, AUTO_MODE_PERMISSIONS_EXTERNAL } from "./core/auto-mode-prompt-assets.js";
@@ -217,6 +217,7 @@ export { checkpointStoreContract } from "./core/store-contracts/checkpoint-store
217
217
  export { sessionRepoContract } from "./core/store-contracts/session-repo-contract.js";
218
218
  export { toolResultStoreContract } from "./core/store-contracts/tool-result-store-contract.js";
219
219
  export { fileHistoryStoreContract } from "./core/store-contracts/file-history-store-contract.js";
220
+ export { workflowJournalStoreContract } from "./core/store-contracts/workflow-journal-store-contract.js";
220
221
  export { permissionRuleSyncContract } from "./core/store-contracts/permission-rule-sync-contract.js";
221
222
  export { MAILBOX_CONTRACT_SCOPE, mailboxStoreContract, mailboxAckOwnershipContract, mailboxBundledOnlyContract, mailboxTombstonedRecipientContract, mailboxCrossProcessContract, } from "./core/store-contracts/mailbox-store-contract.js";
222
223
  export { BACKGROUND_AGENT_CONTRACT_SCOPE, backgroundAgentStoreContract, backgroundAgentStoreScopesContract, } from "./core/store-contracts/background-agent-store-contract.js";
@@ -224,7 +225,7 @@ export { canAccessAgentRecord, STALE_RUNNING_REAP_ATTRIBUTION, BackgroundAgentSt
224
225
  export { serveDurableAgentRowLane, buildAgentPollDetails, } from "./core/task-registry-agent.js";
225
226
  export { FileBackgroundAgentStore } from "./stores/file/background-agent-store.js";
226
227
  export { A2A_TASK_STATES, toA2ATaskState, fromA2ATaskState, } from "./core/a2a-task-state.js";
227
- export { InMemoryWorkflowJournalStore, callKeyOrdinal, JOURNAL_OVERSIZE_ERROR_CODE, } from "./core/workflow-journal-store.js";
228
+ export { InMemoryWorkflowJournalStore, callKeyOrdinal, JOURNAL_OVERSIZE_ERROR_CODE, WorkflowJournalOversizeError, assertJournalEntryFits, } from "./core/workflow-journal-store.js";
228
229
  export { untrustedEgressForHuman, redactHostLeaks, redactSecrets, boundedRedactedSummary } from "./core/untrusted-egress.js";
229
230
  export { summarizeRedactions } from "./core/untrusted-egress.js";
230
231
  export { MemoryRosterStore, FileRosterStore } from "./agents/roster-store.js";
@@ -923,6 +923,8 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
923
923
  }
924
924
  catch (err) {
925
925
  lastErr = err;
926
+ if (err?.code === JOURNAL_OVERSIZE_ERROR_CODE)
927
+ break;
926
928
  if (attempt < 2)
927
929
  await new Promise((r) => timers.setTimeout(r, 25 * (attempt + 1)));
928
930
  }