@rulvar/core 1.34.0 → 1.36.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 (3) hide show
  1. package/dist/index.d.ts +173 -36
  2. package/dist/index.js +450 -90
  3. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -28,7 +28,7 @@ type WireError = {
28
28
  * 'agent' is carried by the AgentError value projection, not by a
29
29
  * RulvarError subclass.
30
30
  */
31
- type ErrorCode = "agent" | "config" | "non_serializable_value" | "script_rejected" | "journal_compat" | "invalid_resolution" | "journal_order_violation" | "plan_invariant" | "replay_plan_hash_mismatch" | "orchestrator_cap_config" | "journal_miss" | "budget_exhausted" | "admission_rejected" | "sandbox_limit" | "lease_held" | "knowledge_cas";
31
+ type ErrorCode = "agent" | "config" | "non_serializable_value" | "script_rejected" | "journal_compat" | "invalid_resolution" | "journal_order_violation" | "plan_invariant" | "replay_plan_hash_mismatch" | "orchestrator_cap_config" | "journal_miss" | "budget_exhausted" | "fail_run" | "admission_rejected" | "sandbox_limit" | "lease_held" | "knowledge_cas";
32
32
  /** An alias for the registry type; both names are public. */
33
33
  type RulvarErrorCode = ErrorCode;
34
34
  /**
@@ -191,6 +191,23 @@ declare class BudgetExhaustedError extends RulvarError {
191
191
  });
192
192
  }
193
193
  /**
194
+ * A declared fail-run policy engaged and closed the run as a failure
195
+ * (v1.35.0 review P2-1): `budget.atCap: 'fail-run'` after the journaled
196
+ * orchestrator cap decision, or `guards.fallback: 'fail-run'` after the
197
+ * journaled guard verdict. The run outcome is 'error' with this code;
198
+ * `data.source` names the policy ('orchestrator_budget_cap' or
199
+ * 'plan_guards') and `data` carries the decision entry reference, so the
200
+ * outcome is a pure roll forward of the journal on resume: no second
201
+ * decision, no model call, no spend.
202
+ */
203
+ declare class FailRunError extends RulvarError {
204
+ readonly code = "fail_run";
205
+ constructor(message: string, opts?: {
206
+ data?: Json;
207
+ cause?: unknown;
208
+ });
209
+ }
210
+ /**
194
211
  * A structural admission rejection (maxDepth, maxChildrenPerNode,
195
212
  * maxTotalSpawns) from the AdmissionController (M6-T06). The rejection verdict is embedded in
196
213
  * the carrying spawn-admission decision entry and replays identically;
@@ -893,6 +910,14 @@ interface LeasableStore extends JournalStore {
893
910
  acquire(runId: string, owner: string): Promise<Lease>;
894
911
  renew(l: Lease): Promise<void>;
895
912
  release(l: Lease): Promise<void>;
913
+ /**
914
+ * Optional TTL introspection (v1.35.0 review P2-4): the configured
915
+ * lease ttl in milliseconds. A store exposing it lets createWorker
916
+ * VERIFY at construction that the worker's renew cadence matches the
917
+ * store's expiry instead of trusting two config sources to agree;
918
+ * stores without it are accepted with the worker's own ttl.
919
+ */
920
+ readonly leaseTtlMs?: number;
896
921
  }
897
922
  //#endregion
898
923
  //#region src/l0/spi/transcript.d.ts
@@ -1667,7 +1692,13 @@ declare function applyClaimOps(claims: readonly ModelClaim[], ops: readonly Clai
1667
1692
  interface FileModelKnowledgeStoreOptions {
1668
1693
  /** Default './rulvar.models.json'. */
1669
1694
  path?: string;
1670
- /** Active claims per (model, taskClass); default 8. */
1695
+ /**
1696
+ * Active claims per (model, taskClass); default 8. A nonnegative
1697
+ * integer (zero refuses every active claim), validated at
1698
+ * construction: the enforcement compares `count > cap`, and every
1699
+ * comparison with NaN is false, so an unvalidated NaN or Infinity
1700
+ * silently disabled the cap (v1.35.0 review P2-5).
1701
+ */
1671
1702
  activeClaimsCap?: number;
1672
1703
  }
1673
1704
  declare class FileModelKnowledgeStore implements ModelKnowledgeStore {
@@ -2556,9 +2587,11 @@ declare class KeyedLimiter {
2556
2587
  pending(key: string): number;
2557
2588
  /**
2558
2589
  * Runs `fn` under the key's semaphore; keys without a configured cap
2559
- * run unlimited (no queueing, no overhead).
2590
+ * run unlimited (no queueing, no overhead). An aborted `signal` frees
2591
+ * a queued caller without a slot (the Semaphore contract), so run
2592
+ * cancellation drains provider queues too (v1.34.0 review P2-4).
2560
2593
  */
2561
- withSlot<T>(key: string, fn: () => Promise<T>, onQueued?: () => void): Promise<T>;
2594
+ withSlot<T>(key: string, fn: () => Promise<T>, onQueued?: () => void, signal?: AbortSignal): Promise<T>;
2562
2595
  }
2563
2596
  //#endregion
2564
2597
  //#region src/model/floors.d.ts
@@ -2790,7 +2823,11 @@ interface EscalationOptions {
2790
2823
  deadlineMs?: number;
2791
2824
  /** Applied by the timeout resolution (by: 'timeout'); default accept. */
2792
2825
  defaultDecision?: EscalationDecision;
2793
- /** In-run minimum spend before scope_bigger; default 0 (M3-T09). */
2826
+ /**
2827
+ * In-run minimum spend before scope_bigger; default 0 (M3-T09). A
2828
+ * finite number >= 0, validated before any LLM call: the gate
2829
+ * compares spend against it, and a NaN would silently disable it.
2830
+ */
2794
2831
  minSpendUsd?: number;
2795
2832
  }
2796
2833
  /** The model-facing request: the report minus the runtime-filled fields. */
@@ -2881,15 +2918,6 @@ declare class NoProgressDetector {
2881
2918
  }
2882
2919
  //#endregion
2883
2920
  //#region src/runtime/usage-limits.d.ts
2884
- /**
2885
- * UsageLimits (M1-T06): normative limit vocabulary and the per-spawn merge.
2886
- *
2887
- * Full contract: https://docs.rulvar.com/guide/agents. Expiry of maxTurns, maxToolCalls,
2888
- * or timeoutMs produces the terminal status 'limit' (paid partial work);
2889
- * streamIdleTimeoutMs expiry is a retryable transport-class AgentError,
2890
- * never 'limit'. The run-level deadline is RunOptions.deadlineAt, not a
2891
- * UsageLimits field.
2892
- */
2893
2921
  interface UsageLimits {
2894
2922
  /** Default 32. */
2895
2923
  maxTurns?: number;
@@ -2924,6 +2952,19 @@ interface EffectiveUsageLimits {
2924
2952
  * defaults.limits.
2925
2953
  */
2926
2954
  declare function mergeUsageLimits(call?: UsageLimits, profile?: UsageLimits, engine?: UsageLimits): EffectiveUsageLimits;
2955
+ /**
2956
+ * Validates one UsageLimits layer at its intake boundary (v1.34.0
2957
+ * review P2-3): a malformed field (NaN, Infinity, a negative, a
2958
+ * fraction) is a typed ConfigError before the merge, before any journal
2959
+ * entry, and before any provider dispatch. `site` names the layer in the
2960
+ * error text (e.g. `RunOptions.limits`). Counts are positive integers
2961
+ * (maxToolCalls may be 0: a spawn that must not call tools).
2962
+ * streamIdleTimeoutMs is handed to setTimeout as-is, so it is bounded by
2963
+ * the Node timer maximum like RetryPolicy delays; timeoutMs is a
2964
+ * wall-clock comparison, so it has no upper bound. Every present field
2965
+ * is checked; absent fields keep their defaults.
2966
+ */
2967
+ declare function validateUsageLimits(limits: UsageLimits, site: string): void;
2927
2968
  //#endregion
2928
2969
  //#region src/runtime/agent-loop.d.ts
2929
2970
  type AgentStatus = "ok" | "error" | "limit" | "cancelled" | "skipped" | "escalated";
@@ -3111,8 +3152,10 @@ interface RunAgentOptions<S extends SchemaSpec = JsonSchema> {
3111
3152
  /**
3112
3153
  * Per-provider keyed limiter hook (M4-T07): wraps every wire dispatch
3113
3154
  * under the serving adapter's key; absent = unlimited (Appendix A).
3155
+ * `signal` is the agent-level abort: an aborted caller leaves the
3156
+ * key's queue without a slot (v1.34.0 review P2-4).
3114
3157
  */
3115
- providerSlot?: <T>(key: string, fn: () => Promise<T>) => Promise<T>;
3158
+ providerSlot?: <T>(key: string, fn: () => Promise<T>, signal?: AbortSignal) => Promise<T>;
3116
3159
  /** The resolved toolset; absent = no tools declared. */
3117
3160
  tools?: ToolRuntime;
3118
3161
  /**
@@ -4799,7 +4842,17 @@ interface RunOptions {
4799
4842
  budgetUsd?: number;
4800
4843
  /** Run-level defaults merged over engine defaults. */
4801
4844
  limits?: UsageLimits;
4802
- /** Run-level deadline (ISO 8601); crossing cancels the run. */
4845
+ /**
4846
+ * Run-level deadline: an ISO 8601 date-time with an explicit UTC
4847
+ * designator or offset (e.g. `2026-07-21T10:00:00Z` or
4848
+ * `2026-07-21T12:00:00+02:00`); crossing it cancels the run. Any
4849
+ * other string is a typed ConfigError thrown synchronously by
4850
+ * engine.run, before any journal entry or provider dispatch (v1.34.0
4851
+ * review P2-1). A deadline already in the past cancels immediately:
4852
+ * a crossed deadline is a valid deadline. Deadlines beyond the Node
4853
+ * timer maximum are honored through sliced timers, never truncated
4854
+ * (v1.34.0 review P2-2).
4855
+ */
4803
4856
  deadlineAt?: string;
4804
4857
  name?: string;
4805
4858
  tags?: string[];
@@ -5226,6 +5279,18 @@ interface OrchestratorExtensionIO {
5226
5279
  */
5227
5280
  replayed?: boolean;
5228
5281
  }): void;
5282
+ /**
5283
+ * A deterministic run failure declared by the extension (v1.35.0 review P2-1):
5284
+ * the first call stores the error and aborts the orchestrator loop;
5285
+ * the orchestrate settle boundary rethrows it, so the run fails with
5286
+ * the given typed error instead of asking the model to finish. Later
5287
+ * calls do nothing. The intended producer is a journaled
5288
+ * policy verdict (the PlanRunner guards fallback 'fail-run'): boot
5289
+ * terminates again from the journal on resume, so the failure rolls
5290
+ * forward without another decision or model call. Optional so
5291
+ * IO implementations built before v1.36 keep compiling.
5292
+ */
5293
+ terminate?(error: Error): void;
5229
5294
  }
5230
5295
  /**
5231
5296
  * The extension contract. PlanRunner implements it in @rulvar/plan; the
@@ -5272,17 +5337,43 @@ interface OrchestratorExtension {
5272
5337
  */
5273
5338
  interface OrchestratorBudgetSpec {
5274
5339
  /**
5275
- * Absolute bound in USD. It never REPLACES the fraction bound:
5340
+ * Absolute bound in USD: a finite number >= 0, validated before any
5341
+ * journal entry or dispatch (a malformed value is a ConfigError). It
5342
+ * never REPLACES the fraction bound:
5276
5343
  * effectiveCap = min(capUsd, (capFraction ?? 0.2) * ceiling), so an
5277
5344
  * explicit capUsd larger than the default fraction of the run ceiling
5278
5345
  * is still cut to that fraction (and a warn log says so). Pass
5279
5346
  * capFraction: 1.0 to make capUsd the sole bound.
5280
5347
  */
5281
5348
  capUsd?: number;
5282
- /** default 0.2; effectiveCap = min of the given bounds */
5349
+ /**
5350
+ * A fraction in (0, 1], default 0.2; effectiveCap = min of the given
5351
+ * bounds. Zero does not lift the cap (it would make every turn
5352
+ * unpayable): anything outside (0, 1] is a ConfigError before any
5353
+ * journal entry or dispatch.
5354
+ */
5283
5355
  capFraction?: number;
5356
+ /**
5357
+ * A finite number >= 0, validated before any journal entry or
5358
+ * dispatch. The reserve is SUBTRACTED from the soft boundary, so a
5359
+ * negative value would widen the cap instead of reserving.
5360
+ */
5284
5361
  finalizeReserveUsd?: number;
5362
+ /**
5363
+ * A positive integer, validated before any journal entry or dispatch:
5364
+ * the turn limit of the reserved final wake.
5365
+ */
5285
5366
  finalizeTurns?: number;
5367
+ /**
5368
+ * The policy at the cap, validated as exactly one of the two literals
5369
+ * even at a plain JS/JSON boundary. 'finish-with-partial' (default)
5370
+ * runs the reserved finalizer and returns its partial result with run
5371
+ * outcome 'ok'. 'fail-run' skips the finalizer entirely: the run
5372
+ * fails with outcome 'error' carrying FailRunError (code 'fail_run',
5373
+ * data.source 'orchestrator_budget_cap', data.capDecisionRef); resume
5374
+ * rolls the same failure forward from the journaled cap decision
5375
+ * without another model call.
5376
+ */
5286
5377
  atCap?: "finish-with-partial" | "fail-run";
5287
5378
  }
5288
5379
  /** Options for orchestrate(engine, goal, o?). */
@@ -5290,13 +5381,19 @@ interface OrchestrateOptions {
5290
5381
  model?: ModelSpec;
5291
5382
  /** Registered profile names to advertise; default: every profile. */
5292
5383
  profiles?: string[];
5293
- /** Per-orchestrate spawn cap; the engine lifetime cap applies regardless. */
5384
+ /**
5385
+ * Per-orchestrate spawn cap: a nonnegative integer (zero admits no
5386
+ * spawns), validated before any journal entry or dispatch. The engine
5387
+ * lifetime cap applies regardless.
5388
+ */
5294
5389
  maxSpawns?: number;
5295
5390
  /** The orchestrator's own budget sub-account (cap enforcement layers only in M6). */
5296
5391
  budget?: OrchestratorBudgetSpec;
5297
5392
  /**
5298
- * Deterministic digest render bound: each
5299
- * TaskDigest outputSummary is clamped to this many CHARACTERS (the
5393
+ * Deterministic digest render bound: a nonnegative integer, validated
5394
+ * before any journal entry or dispatch. Each TaskDigest outputSummary
5395
+ * is truncated to AT MOST this many CHARACTERS, the truncation marker
5396
+ * included (a budget below 3 keeps the bound with a bare slice; the
5300
5397
  * model-independent measure; OQ-04 closed at M10 entry). Default
5301
5398
  * WAKE_SUMMARY_RENDER_BUDGET_CHARS.
5302
5399
  */
@@ -5334,31 +5431,52 @@ declare function makeOrchestratorWorkflow(goal: string, opts?: OrchestrateOption
5334
5431
  declare function orchestrate(engine: Engine, goal: string, opts?: OrchestrateOptions, runOptions?: RunOptions): RunHandle<unknown>;
5335
5432
  //#endregion
5336
5433
  //#region src/engine/scheduler.d.ts
5337
- /**
5338
- * Scheduler and concurrency (M1-T08): the per-run semaphore with a FIFO
5339
- * queue (default 12 concurrent model calls). The engine lifetime spawn cap
5340
- * is enforced by the budget layer at admission; parallel/pipeline
5341
- * composition semantics live with ctx.
5342
- * Per-provider concurrency keys land with M4.
5343
- */
5344
5434
  /** FIFO semaphore; default per-run width is 12. */
5345
5435
  declare const DEFAULT_PER_RUN_CONCURRENCY = 12;
5346
5436
  declare class Semaphore {
5347
5437
  private readonly limit;
5348
5438
  private active;
5349
5439
  private readonly waiters;
5440
+ /**
5441
+ * `limit` must be a positive integer: anything else (NaN included) is
5442
+ * a typed ConfigError. Before this gate a NaN limit made
5443
+ * `active < limit` permanently false, so the first acquire queued
5444
+ * forever and the run could not settle, not even through cancel()
5445
+ * (v1.34.0 review P2-4). Unlimited is expressed by not constructing a
5446
+ * semaphore, never by a sentinel limit.
5447
+ */
5350
5448
  constructor(limit: number);
5351
5449
  get pending(): number;
5352
5450
  /**
5353
5451
  * Acquires a slot, resolving in FIFO order. `onQueued` fires only when
5354
5452
  * the caller actually has to wait (feeds the agent:queued event).
5355
- */
5356
- acquire(onQueued?: () => void): Promise<() => void>;
5357
- withSlot<T>(fn: () => Promise<T>, onQueued?: () => void): Promise<T>;
5453
+ * An aborted `signal` releases the caller from the queue without a
5454
+ * slot: the returned release is a no-op, the remaining waiters keep
5455
+ * their FIFO positions, and the caller proceeds to observe its own
5456
+ * aborted signal (the model layers refuse dispatch under an aborted
5457
+ * signal, so no provider call follows). Cancellation can therefore
5458
+ * always drain a queued run (v1.34.0 review P2-4).
5459
+ */
5460
+ acquire(onQueued?: () => void, signal?: AbortSignal): Promise<() => void>;
5461
+ withSlot<T>(fn: () => Promise<T>, onQueued?: () => void, signal?: AbortSignal): Promise<T>;
5358
5462
  private release;
5359
5463
  }
5360
5464
  //#endregion
5361
5465
  //#region src/engine/external.d.ts
5466
+ /**
5467
+ * The rejection carrier of an aborted flavor B decision wait (v1.35.0
5468
+ * review P1): the parked `awaitDecision` observes the branch/run
5469
+ * AbortSignal, releases its held activity, removes its waiter, and
5470
+ * rejects with this class so cancel, host abort, the run deadline, and
5471
+ * failed sibling aborts all settle the run in bounded time.
5472
+ * Deliberately not a RulvarError: the abort is cancellation intent, not
5473
+ * a registry failure class; the suspension entry stays OPEN, so a later
5474
+ * resume parks the decision again and the durable deadline still applies.
5475
+ */
5476
+ declare class EscalationDecisionAbortedError extends Error {
5477
+ readonly entryRef: number;
5478
+ constructor(message: string, entryRef: number);
5479
+ }
5362
5480
  /** The resolution value shape of a tool-approval suspension (M3-T03). */
5363
5481
  interface ApprovalDecision {
5364
5482
  decision: "allow" | "deny";
@@ -5459,6 +5577,13 @@ declare class ExternalRegistry {
5459
5577
  toolName: string;
5460
5578
  input: Json;
5461
5579
  deadlineAt: string;
5580
+ /**
5581
+ * The branch/run signal: an abort while parked releases the held
5582
+ * activity, removes the waiter, and rejects with
5583
+ * EscalationDecisionAbortedError (v1.35.0 review P1). The suspension
5584
+ * entry stays open for resume.
5585
+ */
5586
+ signal?: AbortSignal;
5462
5587
  onPending?: (entry: JournalEntry, replayed: boolean) => void;
5463
5588
  }): Promise<{
5464
5589
  value: Json;
@@ -5523,7 +5648,8 @@ interface AgentProfile {
5523
5648
  /**
5524
5649
  * Per-profile compaction threshold; default 0.8 of the loop model's
5525
5650
  * contextWindow (M4-T03). Compaction is ON by
5526
- * default; history-processor plumbing stays engine-internal.
5651
+ * default; history-processor plumbing stays engine-internal. The
5652
+ * threshold is a fraction in (0, 1], validated at createEngine.
5527
5653
  */
5528
5654
  compaction?: {
5529
5655
  threshold?: number;
@@ -5962,8 +6088,12 @@ declare function compileVerifiedLayer(claims: readonly ModelClaim[], ladders: re
5962
6088
  /**
5963
6089
  * The deterministic card render. Pure: same filtered
5964
6090
  * claims and ladders give byte-identical text. The render budget is
5965
- * 4096 chars; over it, the OLDEST-observed notes
5966
- * withhold first behind an explicit marker.
6091
+ * 4096 chars by default; over it, the OLDEST-observed notes withhold
6092
+ * first behind an explicit marker, and the budget is a HARD upper bound
6093
+ * of the returned string: a card whose mandatory sections alone exceed
6094
+ * it is truncated with the shared marker (v1.35.0 review P2-5: a budget
6095
+ * of 32 used to return the full 136-char header form). budgetChars is a
6096
+ * nonnegative integer, validated as a ConfigError.
5967
6097
  */
5968
6098
  declare function modelKnowledgeCard(claims: readonly ModelClaim[], ladders: readonly DeclaredLadder[], options?: {
5969
6099
  budgetChars?: number;
@@ -6141,7 +6271,14 @@ interface GitWorktreeProviderOptions {
6141
6271
  * requests keep on dispose. Default false.
6142
6272
  */
6143
6273
  keepOnError?: boolean;
6144
- /** Pin cap shared by park/unpark and retainWorktree (default 4). */
6274
+ /**
6275
+ * Pin cap shared by park/unpark and retainWorktree (default 4). A
6276
+ * nonnegative integer (zero retains nothing), validated at
6277
+ * construction: the retention compares `pinned.size < cap`, and every
6278
+ * comparison with NaN is false, so an unvalidated NaN performed the
6279
+ * acquire effects and then dropped every tree as "cap reached"
6280
+ * (v1.35.0 review P2-5).
6281
+ */
6145
6282
  maxPinnedWorktrees?: number;
6146
6283
  /** Warning sink (cap overflow); defaults to process.emitWarning. */
6147
6284
  onWarn?: (msg: string) => void;
@@ -6754,4 +6891,4 @@ interface SandboxBridge {
6754
6891
  declare const SANDBOX_AGENT_OPT_KEYS: readonly string[];
6755
6892
  declare function createSandboxBridge(ctx: Ctx<never>, options: SandboxBridgeOptions): SandboxBridge;
6756
6893
  //#endregion
6757
- export { AWAIT_SCHEMA, AbandonAttempt, AbandonFold, AbandonPayload, AbandonedSpendView, AbortClass, type AdaptiveEvents, AdmissionController, AdmissionDecision, AdmissionRejectedError, AdmissionStatsBefore, AdmitLineage, AdmitRejectReason, AdmitSpec, AdmitVerdict, AgentCallError, AgentError, type AgentEvents, AgentIdentityInput, AgentOpts, AgentProfile, AgentProfilePermissions, AgentResult, AgentResultMeta, AgentStatus, ApproachSignatureInputs, ApprovalDecision, ApprovalIdentityInput, Artifact, AttemptOutcomeClass, BUDGET_ABORT_REASON, BaseAppend, BriefOpts, BudgetAccountView, BudgetDefaults, BudgetExhaustedError, BudgetExhaustionDiagnostics, BudgetHooks, BudgetReserve, type Bytes, CANCEL_AGENT_SCHEMA, CHECKPOINT_FORMAT_V1, CLAIM_STATEMENT_MAX_CHARS, CLAIM_TTL_DAYS, COMPACTION_SUMMARY_PREFIX, CURRENT_HASH_VERSION, CacheHint, CacheTtl, CanUseTool, CanonicalId, CanonicalIdentity, CanonicalLadderSpec, CanonicalModelSpec, ChatEvent, ChatRequest, CheckpointState, ChildIdentityInput, type ClaimClass, type ClaimOp, type ClaimStatus, ClaimValidationOptions, CollectOpts, CollectedTurn, CompactionConfig, CompiledPermissionChain, CompiledWorkflow, ConfigError, type CoreEvents, CostAttribution, CostAttributionFacts, CostReport, CreateEngineOptions, Ctx, DEFAULT_CHILD_BUDGET_FRACTION, DEFAULT_COMPACTION_THRESHOLD, DEFAULT_ESCALATION_LIMITS, DEFAULT_FLAT_RESERVE_USD, DEFAULT_MAX_CHILDREN_PER_NODE, DEFAULT_MAX_DEPTH, DEFAULT_MAX_OSCILLATIONS_PER_KEY, DEFAULT_MAX_PINNED_WORKTREES, DEFAULT_MAX_REVISIONS_PER_RUN, DEFAULT_MAX_TOTAL_SPAWNS, DEFAULT_MAX_TURNS, DEFAULT_MODEL_RETRY_ATTEMPTS, DEFAULT_NO_PROGRESS_TURNS, DEFAULT_PER_RUN_CONCURRENCY, DEFAULT_RETRY_POLICY, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DebitResult, DeclaredLadder, DedupIndex, DedupNote, DerivedKey, DeriverRegistry, DispositionRule, DispositionTable, DonorCandidate, DonorRef, DroppedItem, EMIT_RESULT_TOOL, EMPTY_SCHEMA_HASH, EMPTY_TOOLSET_HASH, ESCALATE_TOOL_NAME, ESCALATION_REPORT_SCHEMA, ESCALATION_REQUEST_SCHEMA, EVENT_SEGMENT_STRIDE, EffectiveUsageLimits, Effort, Engine, EngineDefaults, EntryKind, EntryRef, EntryStatus, ErrorClass, ErrorCode, ErrorPolicy, EscalatedResult, EscalationDecision, EscalationDigest, EscalationKind, EscalationLimits, EscalationOptions, EscalationReport, EscalationRequest, EventBus, type EvidenceRef, ExtensionAppendInput, ExtensionDispatchSpec, ExternalIdentityInput, ExternalRegistry, ExtractNecessityInput, FINALIZE_SYNTHESIS_INSTRUCTION, FINISH_SCHEMA, FINISH_TOOL_NAME, FailoverTarget, FailoverTrigger, FallbackField, FallbackTrigger, FileModelKnowledgeStore, FileModelKnowledgeStoreOptions, FileTranscriptStore, FinishInfo, Gate, GateAudit, type GateRecord, GitWorktreeProvider, GitWorktreeProviderOptions, GraftBoot, HashVersion, HookVerdict, INBOX_PROPOSAL_TTL_DAYS, IdentityInput, InMemoryStore, InMemoryTranscriptStore, InProcessRunner, InvalidResolutionError, InvocationRole, type IsolationProvider, type IsolationSpec, Issue$1 as Issue, JournalCompatSubCode, JournalCompatibilityError, JournalEntry, JournalMatcher, JournalMissError, JournalOperation, JournalOrderViolation, JournalSerializationHook, type JournalStore, type Json, JsonSchema, JsonlFileStore, KB_ACTIVE_CLAIMS_CAP, KB_CARD_RENDER_BUDGET_CHARS, type KbProposal, type KbProposalTrigger, KeyDeriver, KeyRing, KeyedLimiter, KnowledgeCasError, type KnowledgeSnapshot, LARGE_VALUE_WARN_BYTES, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LINEAGE_SIG_VERSION, LadderSpec, type LeasableStore, type Lease, LeaseHeldError, Ledger, LineageCounters, LineageIndex, LineageRef, LineageRelation, LineageStats, LogicalTaskId, MASKED_SECRET, MAX_DEPTH_CEILING, MatchResult, McpConfig, McpToolSource, MechanicalGateProfile, MechanicalGateVerdict, type MetaLookupStore, type ModelCaps, ModelChoice, type ModelClaim, ModelEpochInputs, type ModelKnowledgeHandle, type ModelKnowledgeStore, ModelListConstraint, ModelRef, ModelRetry, ModelSpec, Msg, NoProgressDetector, NodeId, NodeLinkValue, NonSerializableValueError, ORCHESTRATE_WORKFLOW_NAME, OnEscalation, OperationDisposition, OrchestrateOptions, OrchestratorBudgetSpec, OrchestratorCapConfigError, OrchestratorExtension, OrchestratorExtensionIO, OrchestratorRuntime, Out, PARALLEL_AGENTS_SCHEMA, ParallelSiteCounter, Part, PendingExternal, PendingToolTurn, PermissionConfig, PermissionGate, PermissionHook, PermissionPreset, PermissionRule, PermissionVerdict, PhaseTarget, PipelineCollected, PipelineOpts, PlanInvariantError, PriceTable, PricedUsage, type Pricing, type PricingTier, type ProviderAdapter, QualityFloors, ROLE_EFFORT_DEFAULTS, ROOT_ACCOUNT, ROOT_SCOPE, RUN_PROFILES, RandIdentityInput, RandPayload, RefEntryAppender, RefEntryClassification, RefusalInfo, ReplayDisposition, ReplayMode, ReplayPlanHashMismatch, Replayer, ResolutionArbiter, ResolutionAttempt, ResolutionBy, ResolutionFold, ResolutionLayer, ResolutionOutcome, ResolutionPayload, ResolvedInvocation, ResolvedToolset, ResumeHandle, ResumeOptions, ResumePreview, ResumeReport, RetryClass, RetryPolicy, ReuseConfig, RiskRuleValue, Role, RulvarError, RulvarErrorCode, RunAgentOptions, RunBudget, RunEventSink, type RunFilter, RunHandle, RunInternals, type RunMeta, RunOptions, RunOutcome, RunProfile, RunStatus, RuntimeEventSink, SANDBOX_AGENT_OPT_KEYS, SPAWN_AGENT_SCHEMA, SandboxBridge, SandboxBridgeOptions, SandboxError, SandboxHostToWorker, SandboxMethod, SandboxWorkerToHost, SchemaPair, SchemaSpec, SchemaValidationResult, ScopeSegment, ScriptRejected, ScriptRunner, ScrubNote, Semaphore, SerializationHook, Settled, ShellPatternRules, ShellSegment, ShellVerdict, SinglePhaseAppend, SpanMinter, SpanRegistry, SpawnAdmissionValue, SpawnAgentParams, SpawnKey, SpawnLineage, SpawnLineageOpt, SpawnOrigin, SpawnRecord, Spend, Stage, type StandardJSONSchemaV1, type StandardSchemaV1, StepIdentityInput, StructuredOutputTier, SuspendedAppend, SuspensionState, TOOL_NAME_PATTERN, type TaskClass, TaskDigest, TaskSpec, TerminalPatch, TerminationAccount, TerminationAccountSnapshot, TerminationDeniedValue, TerminationDeniedWriter, TerminationInitValue, TerminationLimits, TerminationResource, ToolCallRequest, ToolChoice, type ToolContext, ToolContextSeed, ToolContract, type ToolDef, type ToolEvents, type ToolExecutor, ToolInit, type ToolRisk, ToolRuntime, type ToolSource, type ToolSourceSession, ToolsOption, TranscriptSerializationHook, type TranscriptStore, TriggerClass, TtlState, Usage, UsageLimits, UsageSlice, VerifiedRecommendation, WAIT_FOR_EVENTS_SCHEMA, WAIT_FOR_EVENTS_TOOL_NAME, WAKE_SUMMARY_RENDER_BUDGET_CHARS, WakeBudgetBlock, WakeDigest, WakeTrigger, WireError, Workflow, WorkflowCallOpts, type WorkflowEvent, type WorkflowEventBody, WorkflowRegistry, admissionReserveUsd, affordableOutputTokens, agentErrorFromWire, agentErrorToWire, agentResultWire, agentScope, applyClaimOps, applyStructuredOutputTier, approachSigCoarse, approachSigOf, archiveDeprecatedModelOps, atCompactionThreshold, buildAbandonFold, buildAdapterRegistry, buildCostReport, buildDeriverRegistry, buildOrchestratorTools, buildTerminationInitValue, buildToolContext, canRideLoopTurn, canonicalIsolationTag, canonicalizeLadder, canonicalizeSchema, capIssues, capsHashOf, checkFloors, checkpointRefFor, childCoveragePrefix, claimExpired, claimExpiry, claimIssues, claimOpIssues, classifyAgentError, classifyAttemptOutcome, collectDeclaredLadders, compactMessages, compilePermissionChain, compilePermissionPreset, compileVerifiedLayer, costReportFromJournal, countsAgainstLimit, createCanonicalIdMinter, createCtx, createEngine, createSandboxBridge, currentOnlyKeyRing, decodeCheckpoint, defineWorkflow, deriveContentKey, deriverV1, deriverV2, digestOf, dispositionHook, emptyDigestBlocks, emptyToolset, encodeCheckpoint, entryUsageSlices, escalateTool, evaluatePermission, evaluateReuse, executeWorkflow, exhaustionCodeOf, extractCandidate, failoverTriggerOf, fallbackTriggerOf, filterClaimsForRun, finalizeFires, foldTermination, formatRePrompt, formatScopePath, hasMetaLookup, hashRunArgs, hashWorkflowBody, hashWorkflowSource, identityJcs, isEscalated, isSchemaPairSpec, isStandardSchemaSpec, isStrictCompatibleSchema, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, lexShellCommand, liftRetainedParts, lineageWeightOf, makeOrchestratorWorkflow, maskSecrets, maskSecretsDeep, maskSecretsJson, matchArgvPattern, matchShellCommand, mcp, mergeUsageLimits, metaMatchesFilter, modelEpochOf, modelKnowledgeCard, modelSpecIdentity, needsSeparateExtract, nextFailover, nodeLinkKey, normalizeApproachTag, normalizeEntry, normalizeFallbacks, orchestrate, parallelScope, parseModelRef, parseScopePath, phiInitialOf, pipelineScope, planNodeScope, priceEntryUsage, priceUsdOf, profileCard, profileRegistrySnapshotHash, projectHistory, projectIdentity, projectToJsonSchema, proposalStatement, providerOf, readRunMeta, readTerminationInit, registryKeyRing, remeasureQueue, replayDisposition, resolveModelInvocation, resolvePricing, resolveToolset, retryClassOf, retryDelayMs, roleConfiguredInRouting, roundOneDisposition, runAgent, runProfile, sanitizeTerminalText, sanitizeTokenCount, sanitizeUsage, sanitizeUsageDelta, scanJournalCompatibility, schemaHash, schemaHashOfSpec, selectStructuredOutputTier, shouldCompact, snapshotUsage, spawnDepthOf, summarizeInstruction, summarizeOutput, terminationConfigDrift, tierWithinCaps, toApprovalDecision, toJournalValue, tool, toolContract, toolsetHash, ttlState, usageViolations, validateEditorialCommit, validateEntryShape, validateEscalationLimits, validateEscalationReport, validateRetryPolicy, validateSchemaSpec, validateTerminationLimits, workflowScope, workflowSourceRef, wrapJournalStore, wrapTranscriptStore };
6894
+ export { AWAIT_SCHEMA, AbandonAttempt, AbandonFold, AbandonPayload, AbandonedSpendView, AbortClass, type AdaptiveEvents, AdmissionController, AdmissionDecision, AdmissionRejectedError, AdmissionStatsBefore, AdmitLineage, AdmitRejectReason, AdmitSpec, AdmitVerdict, AgentCallError, AgentError, type AgentEvents, AgentIdentityInput, AgentOpts, AgentProfile, AgentProfilePermissions, AgentResult, AgentResultMeta, AgentStatus, ApproachSignatureInputs, ApprovalDecision, ApprovalIdentityInput, Artifact, AttemptOutcomeClass, BUDGET_ABORT_REASON, BaseAppend, BriefOpts, BudgetAccountView, BudgetDefaults, BudgetExhaustedError, BudgetExhaustionDiagnostics, BudgetHooks, BudgetReserve, type Bytes, CANCEL_AGENT_SCHEMA, CHECKPOINT_FORMAT_V1, CLAIM_STATEMENT_MAX_CHARS, CLAIM_TTL_DAYS, COMPACTION_SUMMARY_PREFIX, CURRENT_HASH_VERSION, CacheHint, CacheTtl, CanUseTool, CanonicalId, CanonicalIdentity, CanonicalLadderSpec, CanonicalModelSpec, ChatEvent, ChatRequest, CheckpointState, ChildIdentityInput, type ClaimClass, type ClaimOp, type ClaimStatus, ClaimValidationOptions, CollectOpts, CollectedTurn, CompactionConfig, CompiledPermissionChain, CompiledWorkflow, ConfigError, type CoreEvents, CostAttribution, CostAttributionFacts, CostReport, CreateEngineOptions, Ctx, DEFAULT_CHILD_BUDGET_FRACTION, DEFAULT_COMPACTION_THRESHOLD, DEFAULT_ESCALATION_LIMITS, DEFAULT_FLAT_RESERVE_USD, DEFAULT_MAX_CHILDREN_PER_NODE, DEFAULT_MAX_DEPTH, DEFAULT_MAX_OSCILLATIONS_PER_KEY, DEFAULT_MAX_PINNED_WORKTREES, DEFAULT_MAX_REVISIONS_PER_RUN, DEFAULT_MAX_TOTAL_SPAWNS, DEFAULT_MAX_TURNS, DEFAULT_MODEL_RETRY_ATTEMPTS, DEFAULT_NO_PROGRESS_TURNS, DEFAULT_PER_RUN_CONCURRENCY, DEFAULT_RETRY_POLICY, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DebitResult, DeclaredLadder, DedupIndex, DedupNote, DerivedKey, DeriverRegistry, DispositionRule, DispositionTable, DonorCandidate, DonorRef, DroppedItem, EMIT_RESULT_TOOL, EMPTY_SCHEMA_HASH, EMPTY_TOOLSET_HASH, ESCALATE_TOOL_NAME, ESCALATION_REPORT_SCHEMA, ESCALATION_REQUEST_SCHEMA, EVENT_SEGMENT_STRIDE, EffectiveUsageLimits, Effort, Engine, EngineDefaults, EntryKind, EntryRef, EntryStatus, ErrorClass, ErrorCode, ErrorPolicy, EscalatedResult, EscalationDecision, EscalationDecisionAbortedError, EscalationDigest, EscalationKind, EscalationLimits, EscalationOptions, EscalationReport, EscalationRequest, EventBus, type EvidenceRef, ExtensionAppendInput, ExtensionDispatchSpec, ExternalIdentityInput, ExternalRegistry, ExtractNecessityInput, FINALIZE_SYNTHESIS_INSTRUCTION, FINISH_SCHEMA, FINISH_TOOL_NAME, FailRunError, FailoverTarget, FailoverTrigger, FallbackField, FallbackTrigger, FileModelKnowledgeStore, FileModelKnowledgeStoreOptions, FileTranscriptStore, FinishInfo, Gate, GateAudit, type GateRecord, GitWorktreeProvider, GitWorktreeProviderOptions, GraftBoot, HashVersion, HookVerdict, INBOX_PROPOSAL_TTL_DAYS, IdentityInput, InMemoryStore, InMemoryTranscriptStore, InProcessRunner, InvalidResolutionError, InvocationRole, type IsolationProvider, type IsolationSpec, Issue$1 as Issue, JournalCompatSubCode, JournalCompatibilityError, JournalEntry, JournalMatcher, JournalMissError, JournalOperation, JournalOrderViolation, JournalSerializationHook, type JournalStore, type Json, JsonSchema, JsonlFileStore, KB_ACTIVE_CLAIMS_CAP, KB_CARD_RENDER_BUDGET_CHARS, type KbProposal, type KbProposalTrigger, KeyDeriver, KeyRing, KeyedLimiter, KnowledgeCasError, type KnowledgeSnapshot, LARGE_VALUE_WARN_BYTES, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LINEAGE_SIG_VERSION, LadderSpec, type LeasableStore, type Lease, LeaseHeldError, Ledger, LineageCounters, LineageIndex, LineageRef, LineageRelation, LineageStats, LogicalTaskId, MASKED_SECRET, MAX_DEPTH_CEILING, MatchResult, McpConfig, McpToolSource, MechanicalGateProfile, MechanicalGateVerdict, type MetaLookupStore, type ModelCaps, ModelChoice, type ModelClaim, ModelEpochInputs, type ModelKnowledgeHandle, type ModelKnowledgeStore, ModelListConstraint, ModelRef, ModelRetry, ModelSpec, Msg, NoProgressDetector, NodeId, NodeLinkValue, NonSerializableValueError, ORCHESTRATE_WORKFLOW_NAME, OnEscalation, OperationDisposition, OrchestrateOptions, OrchestratorBudgetSpec, OrchestratorCapConfigError, OrchestratorExtension, OrchestratorExtensionIO, OrchestratorRuntime, Out, PARALLEL_AGENTS_SCHEMA, ParallelSiteCounter, Part, PendingExternal, PendingToolTurn, PermissionConfig, PermissionGate, PermissionHook, PermissionPreset, PermissionRule, PermissionVerdict, PhaseTarget, PipelineCollected, PipelineOpts, PlanInvariantError, PriceTable, PricedUsage, type Pricing, type PricingTier, type ProviderAdapter, QualityFloors, ROLE_EFFORT_DEFAULTS, ROOT_ACCOUNT, ROOT_SCOPE, RUN_PROFILES, RandIdentityInput, RandPayload, RefEntryAppender, RefEntryClassification, RefusalInfo, ReplayDisposition, ReplayMode, ReplayPlanHashMismatch, Replayer, ResolutionArbiter, ResolutionAttempt, ResolutionBy, ResolutionFold, ResolutionLayer, ResolutionOutcome, ResolutionPayload, ResolvedInvocation, ResolvedToolset, ResumeHandle, ResumeOptions, ResumePreview, ResumeReport, RetryClass, RetryPolicy, ReuseConfig, RiskRuleValue, Role, RulvarError, RulvarErrorCode, RunAgentOptions, RunBudget, RunEventSink, type RunFilter, RunHandle, RunInternals, type RunMeta, RunOptions, RunOutcome, RunProfile, RunStatus, RuntimeEventSink, SANDBOX_AGENT_OPT_KEYS, SPAWN_AGENT_SCHEMA, SandboxBridge, SandboxBridgeOptions, SandboxError, SandboxHostToWorker, SandboxMethod, SandboxWorkerToHost, SchemaPair, SchemaSpec, SchemaValidationResult, ScopeSegment, ScriptRejected, ScriptRunner, ScrubNote, Semaphore, SerializationHook, Settled, ShellPatternRules, ShellSegment, ShellVerdict, SinglePhaseAppend, SpanMinter, SpanRegistry, SpawnAdmissionValue, SpawnAgentParams, SpawnKey, SpawnLineage, SpawnLineageOpt, SpawnOrigin, SpawnRecord, Spend, Stage, type StandardJSONSchemaV1, type StandardSchemaV1, StepIdentityInput, StructuredOutputTier, SuspendedAppend, SuspensionState, TOOL_NAME_PATTERN, type TaskClass, TaskDigest, TaskSpec, TerminalPatch, TerminationAccount, TerminationAccountSnapshot, TerminationDeniedValue, TerminationDeniedWriter, TerminationInitValue, TerminationLimits, TerminationResource, ToolCallRequest, ToolChoice, type ToolContext, ToolContextSeed, ToolContract, type ToolDef, type ToolEvents, type ToolExecutor, ToolInit, type ToolRisk, ToolRuntime, type ToolSource, type ToolSourceSession, ToolsOption, TranscriptSerializationHook, type TranscriptStore, TriggerClass, TtlState, Usage, UsageLimits, UsageSlice, VerifiedRecommendation, WAIT_FOR_EVENTS_SCHEMA, WAIT_FOR_EVENTS_TOOL_NAME, WAKE_SUMMARY_RENDER_BUDGET_CHARS, WakeBudgetBlock, WakeDigest, WakeTrigger, WireError, Workflow, WorkflowCallOpts, type WorkflowEvent, type WorkflowEventBody, WorkflowRegistry, admissionReserveUsd, affordableOutputTokens, agentErrorFromWire, agentErrorToWire, agentResultWire, agentScope, applyClaimOps, applyStructuredOutputTier, approachSigCoarse, approachSigOf, archiveDeprecatedModelOps, atCompactionThreshold, buildAbandonFold, buildAdapterRegistry, buildCostReport, buildDeriverRegistry, buildOrchestratorTools, buildTerminationInitValue, buildToolContext, canRideLoopTurn, canonicalIsolationTag, canonicalizeLadder, canonicalizeSchema, capIssues, capsHashOf, checkFloors, checkpointRefFor, childCoveragePrefix, claimExpired, claimExpiry, claimIssues, claimOpIssues, classifyAgentError, classifyAttemptOutcome, collectDeclaredLadders, compactMessages, compilePermissionChain, compilePermissionPreset, compileVerifiedLayer, costReportFromJournal, countsAgainstLimit, createCanonicalIdMinter, createCtx, createEngine, createSandboxBridge, currentOnlyKeyRing, decodeCheckpoint, defineWorkflow, deriveContentKey, deriverV1, deriverV2, digestOf, dispositionHook, emptyDigestBlocks, emptyToolset, encodeCheckpoint, entryUsageSlices, escalateTool, evaluatePermission, evaluateReuse, executeWorkflow, exhaustionCodeOf, extractCandidate, failoverTriggerOf, fallbackTriggerOf, filterClaimsForRun, finalizeFires, foldTermination, formatRePrompt, formatScopePath, hasMetaLookup, hashRunArgs, hashWorkflowBody, hashWorkflowSource, identityJcs, isEscalated, isSchemaPairSpec, isStandardSchemaSpec, isStrictCompatibleSchema, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, lexShellCommand, liftRetainedParts, lineageWeightOf, makeOrchestratorWorkflow, maskSecrets, maskSecretsDeep, maskSecretsJson, matchArgvPattern, matchShellCommand, mcp, mergeUsageLimits, metaMatchesFilter, modelEpochOf, modelKnowledgeCard, modelSpecIdentity, needsSeparateExtract, nextFailover, nodeLinkKey, normalizeApproachTag, normalizeEntry, normalizeFallbacks, orchestrate, parallelScope, parseModelRef, parseScopePath, phiInitialOf, pipelineScope, planNodeScope, priceEntryUsage, priceUsdOf, profileCard, profileRegistrySnapshotHash, projectHistory, projectIdentity, projectToJsonSchema, proposalStatement, providerOf, readRunMeta, readTerminationInit, registryKeyRing, remeasureQueue, replayDisposition, resolveModelInvocation, resolvePricing, resolveToolset, retryClassOf, retryDelayMs, roleConfiguredInRouting, roundOneDisposition, runAgent, runProfile, sanitizeTerminalText, sanitizeTokenCount, sanitizeUsage, sanitizeUsageDelta, scanJournalCompatibility, schemaHash, schemaHashOfSpec, selectStructuredOutputTier, shouldCompact, snapshotUsage, spawnDepthOf, summarizeInstruction, summarizeOutput, terminationConfigDrift, tierWithinCaps, toApprovalDecision, toJournalValue, tool, toolContract, toolsetHash, ttlState, usageViolations, validateEditorialCommit, validateEntryShape, validateEscalationLimits, validateEscalationReport, validateRetryPolicy, validateSchemaSpec, validateTerminationLimits, validateUsageLimits, workflowScope, workflowSourceRef, wrapJournalStore, wrapTranscriptStore };
package/dist/index.js CHANGED
@@ -206,6 +206,25 @@ var BudgetExhaustedError = class extends RulvarError {
206
206
  }
207
207
  };
208
208
  /**
209
+ * A declared fail-run policy engaged and closed the run as a failure
210
+ * (v1.35.0 review P2-1): `budget.atCap: 'fail-run'` after the journaled
211
+ * orchestrator cap decision, or `guards.fallback: 'fail-run'` after the
212
+ * journaled guard verdict. The run outcome is 'error' with this code;
213
+ * `data.source` names the policy ('orchestrator_budget_cap' or
214
+ * 'plan_guards') and `data` carries the decision entry reference, so the
215
+ * outcome is a pure roll forward of the journal on resume: no second
216
+ * decision, no model call, no spend.
217
+ */
218
+ var FailRunError = class extends RulvarError {
219
+ code = "fail_run";
220
+ constructor(message, opts) {
221
+ super(message, {
222
+ retryable: false,
223
+ ...opts
224
+ });
225
+ }
226
+ };
227
+ /**
209
228
  * A structural admission rejection (maxDepth, maxChildrenPerNode,
210
229
  * maxTotalSpawns) from the AdmissionController (M6-T06). The rejection verdict is embedded in
211
230
  * the carrying spawn-admission decision entry and replays identically;
@@ -2179,6 +2198,51 @@ function modelEpochOf(inputs) {
2179
2198
  return Object.keys(epoch).length === 0 ? void 0 : epoch;
2180
2199
  }
2181
2200
  //#endregion
2201
+ //#region src/l0/validate-numbers.ts
2202
+ /**
2203
+ * Shared numeric option validators (v1.34.0 review P2-3). Every public
2204
+ * numeric knob that shapes admission, limits, concurrency, or timers is
2205
+ * validated with these helpers at its intake boundary, so a malformed
2206
+ * value (NaN, Infinity, a negative, a fraction where an integer is
2207
+ * required) fails as a typed ConfigError before any journal entry,
2208
+ * worker, or provider dispatch. NaN needs dedicated handling because
2209
+ * every comparison with it is false: a hand-written range check in the
2210
+ * rejecting polarity (`value < min || value > max`) silently admits it.
2211
+ */
2212
+ /**
2213
+ * The Node timer ceiling: setTimeout clamps any longer delay to 1 ms, so
2214
+ * a naive far-future timer fires immediately (v1.34.0 review P2-2).
2215
+ * Relative timer options are validated against this bound; absolute
2216
+ * deadlines use the sliced timer in long-timer.ts instead.
2217
+ */
2218
+ const MAX_TIMER_DELAY_MS = 2147483647;
2219
+ function refuse(site, requirement, value) {
2220
+ throw new ConfigError(`${site} must be ${requirement}; got ${String(value)}`);
2221
+ }
2222
+ /** An integer >= 1 (counts, caps, and depths). */
2223
+ function requirePositiveInteger(value, site) {
2224
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) refuse(site, "a positive integer", value);
2225
+ }
2226
+ /** An integer >= 0 (caps where zero means "none allowed"). */
2227
+ function requireNonNegativeInteger(value, site) {
2228
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0) refuse(site, "a nonnegative integer", value);
2229
+ }
2230
+ /** A finite number >= 0 (USD amounts and reserves). */
2231
+ function requireNonNegativeNumber(value, site) {
2232
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) refuse(site, "a finite nonnegative number", value);
2233
+ }
2234
+ /** A finite fraction in (0, 1]. */
2235
+ function requireFraction(value, site) {
2236
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0 || value > 1) refuse(site, "a fraction in (0, 1]", value);
2237
+ }
2238
+ /**
2239
+ * A relative delay handed to setTimeout as-is: an integer within the
2240
+ * Node timer maximum, mirroring validateRetryPolicy's bound.
2241
+ */
2242
+ function requireTimerDelayMs(value, site) {
2243
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > 2147483647) refuse(site, "an integer between 1 and 2147483647 ms (the Node timer maximum)", value);
2244
+ }
2245
+ //#endregion
2182
2246
  //#region src/knowledge/file-store.ts
2183
2247
  /**
2184
2248
  * FileModelKnowledgeStore (M10-T01): the default ModelKnowledgeStore, a
@@ -2245,6 +2309,7 @@ var FileModelKnowledgeStore = class {
2245
2309
  queue = Promise.resolve();
2246
2310
  constructor(options) {
2247
2311
  this.path = resolve(options?.path ?? "./rulvar.models.json");
2312
+ if (options?.activeClaimsCap !== void 0) requireNonNegativeInteger(options.activeClaimsCap, "FileModelKnowledgeStore activeClaimsCap");
2248
2313
  this.activeClaimsCap = options?.activeClaimsCap;
2249
2314
  }
2250
2315
  read() {
@@ -2301,6 +2366,19 @@ var FileModelKnowledgeStore = class {
2301
2366
  return result;
2302
2367
  }
2303
2368
  };
2369
+ /**
2370
+ * Truncates `raw` to at most `budgetChars` characters. A string within
2371
+ * the budget returns unchanged; a longer one is cut to
2372
+ * `budgetChars - 3` characters plus the marker, and budgets below the
2373
+ * marker length fall back to a bare slice so the bound still holds.
2374
+ * The measure is deterministic characters (UTF-16 units): identical
2375
+ * live and on replay, no tokenizer dependence.
2376
+ */
2377
+ function truncateToBudget(raw, budgetChars) {
2378
+ if (raw.length <= budgetChars) return raw;
2379
+ if (budgetChars < 3) return raw.slice(0, Math.max(0, budgetChars));
2380
+ return `${raw.slice(0, budgetChars - 3)}...`;
2381
+ }
2304
2382
  //#endregion
2305
2383
  //#region src/model/floors.ts
2306
2384
  /**
@@ -2442,10 +2520,15 @@ function compileVerifiedLayer(claims, ladders) {
2442
2520
  /**
2443
2521
  * The deterministic card render. Pure: same filtered
2444
2522
  * claims and ladders give byte-identical text. The render budget is
2445
- * 4096 chars; over it, the OLDEST-observed notes
2446
- * withhold first behind an explicit marker.
2523
+ * 4096 chars by default; over it, the OLDEST-observed notes withhold
2524
+ * first behind an explicit marker, and the budget is a HARD upper bound
2525
+ * of the returned string: a card whose mandatory sections alone exceed
2526
+ * it is truncated with the shared marker (v1.35.0 review P2-5: a budget
2527
+ * of 32 used to return the full 136-char header form). budgetChars is a
2528
+ * nonnegative integer, validated as a ConfigError.
2447
2529
  */
2448
2530
  function modelKnowledgeCard(claims, ladders, options) {
2531
+ if (options?.budgetChars !== void 0) requireNonNegativeInteger(options.budgetChars, "modelKnowledgeCard budgetChars");
2449
2532
  const budget = options?.budgetChars ?? 4096;
2450
2533
  const lines = ["Model knowledge card (tier-relative; advisory within declared ladders and hard floors)."];
2451
2534
  const verified = compileVerifiedLayer(claims, ladders);
@@ -2494,7 +2577,7 @@ function modelKnowledgeCard(claims, ladders, options) {
2494
2577
  shown -= 1;
2495
2578
  text = render(shown);
2496
2579
  }
2497
- return text;
2580
+ return truncateToBudget(text, budget);
2498
2581
  }
2499
2582
  //#endregion
2500
2583
  //#region src/tools/presets.ts
@@ -3047,6 +3130,7 @@ var GitWorktreeProvider = class {
3047
3130
  constructor(options) {
3048
3131
  this.repoRoot = options?.repoRoot ?? process.cwd();
3049
3132
  this.keepOnError = options?.keepOnError ?? false;
3133
+ if (options?.maxPinnedWorktrees !== void 0) requireNonNegativeInteger(options.maxPinnedWorktrees, "GitWorktreeProvider maxPinnedWorktrees");
3050
3134
  this.maxPinned = options?.maxPinnedWorktrees ?? 4;
3051
3135
  this.onWarn = options?.onWarn ?? ((msg) => process.emitWarning(msg, {
3052
3136
  code: "RULVAR_WORKTREE",
@@ -5836,6 +5920,24 @@ var Replayer = class {
5836
5920
  * Full contract: https://docs.rulvar.com/guide/durability
5837
5921
  */
5838
5922
  /**
5923
+ * The rejection carrier of an aborted flavor B decision wait (v1.35.0
5924
+ * review P1): the parked `awaitDecision` observes the branch/run
5925
+ * AbortSignal, releases its held activity, removes its waiter, and
5926
+ * rejects with this class so cancel, host abort, the run deadline, and
5927
+ * failed sibling aborts all settle the run in bounded time.
5928
+ * Deliberately not a RulvarError: the abort is cancellation intent, not
5929
+ * a registry failure class; the suspension entry stays OPEN, so a later
5930
+ * resume parks the decision again and the durable deadline still applies.
5931
+ */
5932
+ var EscalationDecisionAbortedError = class extends Error {
5933
+ entryRef;
5934
+ constructor(message, entryRef) {
5935
+ super(message);
5936
+ this.name = "EscalationDecisionAbortedError";
5937
+ this.entryRef = entryRef;
5938
+ }
5939
+ };
5940
+ /**
5839
5941
  * Normalizes a resolution value into an ApprovalDecision. Anything that
5840
5942
  * is not an explicit allow is a deny: an approval never fails open.
5841
5943
  */
@@ -6112,8 +6214,33 @@ var ExternalRegistry = class ExternalRegistry {
6112
6214
  },
6113
6215
  deadlineAt: options.deadlineAt
6114
6216
  });
6115
- return new Promise((resolve) => {
6217
+ return new Promise((resolve, reject) => {
6218
+ const signal = options.signal;
6219
+ const abortError = () => {
6220
+ const reason = signal?.reason;
6221
+ const detail = reason instanceof Error ? reason.message : typeof reason === "string" ? reason : "aborted";
6222
+ return new EscalationDecisionAbortedError(`flavor B escalation decision wait aborted (entry ${String(entry.seq)}): ${detail}`, entry.seq);
6223
+ };
6224
+ if (signal?.aborted === true) {
6225
+ reject(abortError());
6226
+ return;
6227
+ }
6116
6228
  const exitActivity = this.enter();
6229
+ let settled = false;
6230
+ let detachAbort;
6231
+ /** Exactly one terminal: activity exits once, the listener detaches once. */
6232
+ const settle = () => {
6233
+ if (settled) return false;
6234
+ settled = true;
6235
+ exitActivity();
6236
+ detachAbort?.();
6237
+ return true;
6238
+ };
6239
+ const onAbort = () => {
6240
+ if (!settle()) return;
6241
+ this.waiters.delete(entry.seq);
6242
+ if (!this.closedFlag) reject(abortError());
6243
+ };
6117
6244
  const waiter = {
6118
6245
  kind: "decision",
6119
6246
  key: ExternalRegistry.approvalKey(entry.seq),
@@ -6121,7 +6248,7 @@ var ExternalRegistry = class ExternalRegistry {
6121
6248
  entryRef: entry.seq,
6122
6249
  prompt: `decide escalation of '${options.toolName}'`,
6123
6250
  resolve: (value) => {
6124
- exitActivity();
6251
+ if (!settle()) return;
6125
6252
  resolve({
6126
6253
  value,
6127
6254
  entryRef: entry.seq
@@ -6129,6 +6256,12 @@ var ExternalRegistry = class ExternalRegistry {
6129
6256
  }
6130
6257
  };
6131
6258
  this.waiters.set(entry.seq, waiter);
6259
+ if (signal !== void 0) {
6260
+ signal.addEventListener("abort", onAbort, { once: true });
6261
+ detachAbort = () => {
6262
+ signal.removeEventListener("abort", onAbort);
6263
+ };
6264
+ }
6132
6265
  options.onPending?.(entry, replayed);
6133
6266
  });
6134
6267
  }
@@ -6772,8 +6905,17 @@ var Semaphore = class {
6772
6905
  limit;
6773
6906
  active = 0;
6774
6907
  waiters = [];
6908
+ /**
6909
+ * `limit` must be a positive integer: anything else (NaN included) is
6910
+ * a typed ConfigError. Before this gate a NaN limit made
6911
+ * `active < limit` permanently false, so the first acquire queued
6912
+ * forever and the run could not settle, not even through cancel()
6913
+ * (v1.34.0 review P2-4). Unlimited is expressed by not constructing a
6914
+ * semaphore, never by a sentinel limit.
6915
+ */
6775
6916
  constructor(limit) {
6776
- this.limit = Math.max(1, limit);
6917
+ requirePositiveInteger(limit, "Semaphore limit");
6918
+ this.limit = limit;
6777
6919
  }
6778
6920
  get pending() {
6779
6921
  return this.waiters.length;
@@ -6781,21 +6923,50 @@ var Semaphore = class {
6781
6923
  /**
6782
6924
  * Acquires a slot, resolving in FIFO order. `onQueued` fires only when
6783
6925
  * the caller actually has to wait (feeds the agent:queued event).
6926
+ * An aborted `signal` releases the caller from the queue without a
6927
+ * slot: the returned release is a no-op, the remaining waiters keep
6928
+ * their FIFO positions, and the caller proceeds to observe its own
6929
+ * aborted signal (the model layers refuse dispatch under an aborted
6930
+ * signal, so no provider call follows). Cancellation can therefore
6931
+ * always drain a queued run (v1.34.0 review P2-4).
6784
6932
  */
6785
- async acquire(onQueued) {
6933
+ async acquire(onQueued, signal) {
6786
6934
  if (this.active < this.limit) {
6787
6935
  this.active += 1;
6788
6936
  return () => this.release();
6789
6937
  }
6938
+ if (signal?.aborted === true) return () => void 0;
6790
6939
  onQueued?.();
6791
- await new Promise((resolve) => {
6792
- this.waiters.push(resolve);
6940
+ const waiter = {
6941
+ resolve: () => void 0,
6942
+ aborted: false
6943
+ };
6944
+ const wait = new Promise((resolve) => {
6945
+ waiter.resolve = resolve;
6793
6946
  });
6947
+ this.waiters.push(waiter);
6948
+ let onAbort;
6949
+ if (signal !== void 0) {
6950
+ onAbort = () => {
6951
+ const index = this.waiters.indexOf(waiter);
6952
+ if (index === -1) return;
6953
+ this.waiters.splice(index, 1);
6954
+ waiter.aborted = true;
6955
+ waiter.resolve();
6956
+ };
6957
+ signal.addEventListener("abort", onAbort, { once: true });
6958
+ }
6959
+ try {
6960
+ await wait;
6961
+ } finally {
6962
+ if (signal !== void 0 && onAbort !== void 0) signal.removeEventListener("abort", onAbort);
6963
+ }
6964
+ if (waiter.aborted) return () => void 0;
6794
6965
  this.active += 1;
6795
6966
  return () => this.release();
6796
6967
  }
6797
- async withSlot(fn, onQueued) {
6798
- const release = await this.acquire(onQueued);
6968
+ async withSlot(fn, onQueued, signal) {
6969
+ const release = await this.acquire(onQueued, signal);
6799
6970
  try {
6800
6971
  return await fn();
6801
6972
  } finally {
@@ -6805,7 +6976,7 @@ var Semaphore = class {
6805
6976
  release() {
6806
6977
  this.active -= 1;
6807
6978
  const next = this.waiters.shift();
6808
- if (next !== void 0) next();
6979
+ if (next !== void 0) next.resolve();
6809
6980
  }
6810
6981
  };
6811
6982
  //#endregion
@@ -6834,12 +7005,14 @@ var KeyedLimiter = class {
6834
7005
  }
6835
7006
  /**
6836
7007
  * Runs `fn` under the key's semaphore; keys without a configured cap
6837
- * run unlimited (no queueing, no overhead).
7008
+ * run unlimited (no queueing, no overhead). An aborted `signal` frees
7009
+ * a queued caller without a slot (the Semaphore contract), so run
7010
+ * cancellation drains provider queues too (v1.34.0 review P2-4).
6838
7011
  */
6839
- async withSlot(key, fn, onQueued) {
7012
+ async withSlot(key, fn, onQueued, signal) {
6840
7013
  const semaphore = this.semaphores.get(key);
6841
7014
  if (semaphore === void 0) return fn();
6842
- return semaphore.withSlot(fn, onQueued);
7015
+ return semaphore.withSlot(fn, onQueued, signal);
6843
7016
  }
6844
7017
  };
6845
7018
  //#endregion
@@ -7066,13 +7239,6 @@ function retryClassOf(error) {
7066
7239
  if (kind === "overloaded") return "overloaded";
7067
7240
  return "transport";
7068
7241
  }
7069
- /**
7070
- * The largest delay a Node timer represents exactly (2^31 above that
7071
- * a timer overflows and fires almost immediately); every returned
7072
- * delay is clamped to it so a huge provider value can never turn
7073
- * into an instant retry storm.
7074
- */
7075
- const MAX_TIMER_DELAY_MS = 2147483647;
7076
7242
  /** Bounds a delay to a finite nonnegative integer a Node timer can honor. */
7077
7243
  function timerSafe(ms) {
7078
7244
  if (!Number.isFinite(ms) || ms <= 0) return 0;
@@ -7128,7 +7294,7 @@ function validateRetryPolicy(policy, source = "retry") {
7128
7294
  const backoff = candidate.backoff;
7129
7295
  if (typeof backoff !== "object" || backoff === null || Array.isArray(backoff)) throw new ConfigError(`${source}: backoff must be an object with initialMs, factor, and maxMs; got ${renderConfigValue(backoff)}`);
7130
7296
  const { initialMs, factor, maxMs, jitter } = backoff;
7131
- for (const [field, value] of [["backoff.initialMs", initialMs], ["backoff.maxMs", maxMs]]) if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0 || value > MAX_TIMER_DELAY_MS) fail(field, "must be an integer between 0 and 2147483647 ms (the Node timer maximum)", value);
7297
+ for (const [field, value] of [["backoff.initialMs", initialMs], ["backoff.maxMs", maxMs]]) if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0 || value > 2147483647) fail(field, "must be an integer between 0 and 2147483647 ms (the Node timer maximum)", value);
7132
7298
  if (typeof factor !== "number" || !Number.isFinite(factor) || factor <= 0) fail("backoff.factor", "must be a finite number above zero", factor);
7133
7299
  if (jitter !== void 0 && typeof jitter !== "boolean") fail("backoff.jitter", "must be a boolean when given", jitter);
7134
7300
  const retryOn = candidate.retryOn;
@@ -7517,6 +7683,15 @@ function ladderRungChoice(ladder, index) {
7517
7683
  }
7518
7684
  //#endregion
7519
7685
  //#region src/runtime/usage-limits.ts
7686
+ /**
7687
+ * UsageLimits (M1-T06): normative limit vocabulary and the per-spawn merge.
7688
+ *
7689
+ * Full contract: https://docs.rulvar.com/guide/agents. Expiry of maxTurns, maxToolCalls,
7690
+ * or timeoutMs produces the terminal status 'limit' (paid partial work);
7691
+ * streamIdleTimeoutMs expiry is a retryable transport-class AgentError,
7692
+ * never 'limit'. The run-level deadline is RunOptions.deadlineAt, not a
7693
+ * UsageLimits field.
7694
+ */
7520
7695
  const DEFAULT_MAX_TURNS = 32;
7521
7696
  const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 12e4;
7522
7697
  /**
@@ -7539,6 +7714,26 @@ function mergeUsageLimits(call, profile, engine) {
7539
7714
  if (noProgressTurns !== void 0) merged.noProgressTurns = noProgressTurns;
7540
7715
  return merged;
7541
7716
  }
7717
+ /**
7718
+ * Validates one UsageLimits layer at its intake boundary (v1.34.0
7719
+ * review P2-3): a malformed field (NaN, Infinity, a negative, a
7720
+ * fraction) is a typed ConfigError before the merge, before any journal
7721
+ * entry, and before any provider dispatch. `site` names the layer in the
7722
+ * error text (e.g. `RunOptions.limits`). Counts are positive integers
7723
+ * (maxToolCalls may be 0: a spawn that must not call tools).
7724
+ * streamIdleTimeoutMs is handed to setTimeout as-is, so it is bounded by
7725
+ * the Node timer maximum like RetryPolicy delays; timeoutMs is a
7726
+ * wall-clock comparison, so it has no upper bound. Every present field
7727
+ * is checked; absent fields keep their defaults.
7728
+ */
7729
+ function validateUsageLimits(limits, site) {
7730
+ if (limits.maxTurns !== void 0) requirePositiveInteger(limits.maxTurns, `${site}.maxTurns`);
7731
+ if (limits.maxToolCalls !== void 0) requireNonNegativeInteger(limits.maxToolCalls, `${site}.maxToolCalls`);
7732
+ if (limits.maxOutputTokensPerTurn !== void 0) requirePositiveInteger(limits.maxOutputTokensPerTurn, `${site}.maxOutputTokensPerTurn`);
7733
+ if (limits.timeoutMs !== void 0) requirePositiveInteger(limits.timeoutMs, `${site}.timeoutMs`);
7734
+ if (limits.streamIdleTimeoutMs !== void 0) requireTimerDelayMs(limits.streamIdleTimeoutMs, `${site}.streamIdleTimeoutMs`);
7735
+ if (limits.noProgressTurns !== void 0) requirePositiveInteger(limits.noProgressTurns, `${site}.noProgressTurns`);
7736
+ }
7542
7737
  //#endregion
7543
7738
  //#region src/runtime/model-retry.ts
7544
7739
  var ModelRetry = class extends Error {
@@ -8801,7 +8996,7 @@ async function runAgent(options) {
8801
8996
  const aborted = abortKind();
8802
8997
  return aborted === void 0 ? streamTurn(target.adapter, site.requestFor(target), site.streamOptionsFor(target)) : Promise.resolve(abortedOutcome(aborted));
8803
8998
  };
8804
- const outcome = await (options.providerSlot === void 0 ? dispatch() : options.providerSlot(target.adapter.id, dispatch));
8999
+ const outcome = await (options.providerSlot === void 0 ? dispatch() : options.providerSlot(target.adapter.id, dispatch, options.signal));
8805
9000
  recordUsage(outcome.usage, outcome.reported, target.adapter.id, target.resolved.ref, site.role, outcome.usageViolation);
8806
9001
  tries += 1;
8807
9002
  const retryClass = outcome.aborted === "idle" ? "transport" : outcome.wireError === void 0 ? void 0 : retryClassOf(outcome.wireError);
@@ -9772,6 +9967,7 @@ var RunBudget = class {
9772
9967
  * Also enforces the engine lifetime spawn cap.
9773
9968
  */
9774
9969
  admitSpawn(reserveUsd, accountScope = "run") {
9970
+ requireNonNegativeNumber(reserveUsd, "the admission reserve (estCost or its fallbacks)");
9775
9971
  if (this.agentsSpawnedInternal >= this.lifetimeSpawnCap) {
9776
9972
  this.exhaustedInternal = true;
9777
9973
  throw new BudgetExhaustedError(`engine lifetime spawn cap reached (${this.lifetimeSpawnCap} spawns per run; budgetDefaults.lifetimeSpawnCap)`, { data: { cap: this.lifetimeSpawnCap } });
@@ -9990,7 +10186,11 @@ var AdmissionController = class {
9990
10186
  admittedTotal = 0;
9991
10187
  constructor(options) {
9992
10188
  const maxDepth = options.maxDepth ?? 1;
9993
- if (maxDepth < 1 || maxDepth > 4) throw new ConfigError(`maxDepth ${String(maxDepth)} is outside [1, ${String(4)}] (default 1, hard ceiling 4)`);
10189
+ if (!Number.isInteger(maxDepth) || maxDepth < 1 || maxDepth > 4) throw new ConfigError(`maxDepth ${String(maxDepth)} is outside [1, ${String(4)}] (default 1, hard ceiling 4)`);
10190
+ if (options.maxChildrenPerNode !== void 0) requirePositiveInteger(options.maxChildrenPerNode, "maxChildrenPerNode");
10191
+ if (options.childBudgetFraction !== void 0) requireFraction(options.childBudgetFraction, "childBudgetFraction");
10192
+ if (options.flatReserveUsd !== void 0) requireNonNegativeNumber(options.flatReserveUsd, "flatReserveUsd");
10193
+ if (options.maxTotalSpawns !== void 0) requirePositiveInteger(options.maxTotalSpawns, "maxTotalSpawns");
9994
10194
  this.budget = options.budget;
9995
10195
  this.maxDepth = maxDepth;
9996
10196
  this.maxChildrenPerNode = options.maxChildrenPerNode ?? 16;
@@ -10301,8 +10501,7 @@ const WAKE_SUMMARY_RENDER_BUDGET_CHARS = 400;
10301
10501
  * spawn ordinal; the LLM distillation upgrade is M7 territory).
10302
10502
  */
10303
10503
  function summarizeOutput(result) {
10304
- const raw = result.status === "ok" ? typeof result.output === "string" ? result.output : JSON.stringify(result.output ?? null) : result.errorMessage ?? `terminal status ${result.status}`;
10305
- return raw.length <= 400 ? raw : `${raw.slice(0, 400)}...`;
10504
+ return truncateToBudget(result.status === "ok" ? typeof result.output === "string" ? result.output : JSON.stringify(result.output ?? null) : result.errorMessage ?? `terminal status ${result.status}`, 400);
10306
10505
  }
10307
10506
  /** Folds one settled child into its digest (spawn-ordinal ordering is the caller's). */
10308
10507
  function digestOf(record, result) {
@@ -10617,6 +10816,45 @@ function emitSpawnRejected(events, input) {
10617
10816
  }, input.spanId, input.replayed);
10618
10817
  }
10619
10818
  //#endregion
10819
+ //#region src/l0/long-timer.ts
10820
+ /**
10821
+ * Sliced timers for absolute wall-clock deadlines (v1.34.0 review P2-2).
10822
+ *
10823
+ * Node clamps a setTimeout delay above 2147483647 ms (about 24.8 days)
10824
+ * to 1 ms, so a naive timer for a far-future deadline fires immediately.
10825
+ * setLongTimeout never hands Node more than one MAX_TIMER_DELAY_MS
10826
+ * slice, re-checks the wall clock when a slice fires, and re-arms until
10827
+ * the clock actually reaches the deadline: firing a slice is never taken
10828
+ * as proof the deadline arrived. A deadline already in the past fires on
10829
+ * the next macrotask (delay 0), matching the plain setTimeout behavior
10830
+ * the callers had for near deadlines.
10831
+ */
10832
+ /**
10833
+ * Schedules `onDue` for the absolute wall-clock instant `dueAtMs` as
10834
+ * reported by `now` (default Date.now), slicing delays beyond the Node
10835
+ * timer maximum.
10836
+ */
10837
+ function setLongTimeout(onDue, dueAtMs, now = Date.now) {
10838
+ let handle;
10839
+ let cancelled = false;
10840
+ const arm = () => {
10841
+ const remaining = Math.max(0, dueAtMs - now());
10842
+ handle = setTimeout(() => {
10843
+ if (cancelled) return;
10844
+ if (now() >= dueAtMs) {
10845
+ onDue();
10846
+ return;
10847
+ }
10848
+ arm();
10849
+ }, Math.min(remaining, MAX_TIMER_DELAY_MS));
10850
+ };
10851
+ arm();
10852
+ return { cancel: () => {
10853
+ cancelled = true;
10854
+ if (handle !== void 0) clearTimeout(handle);
10855
+ } };
10856
+ }
10857
+ //#endregion
10620
10858
  //#region src/engine/ctx.ts
10621
10859
  /**
10622
10860
  * Ctx primitives (M1-T07) plus the parallel/pipeline composition semantics
@@ -10882,6 +11120,8 @@ function createCtx(internals, rootWorkflow) {
10882
11120
  const escalation = opts.escalation ?? profile?.escalation;
10883
11121
  if (escalation !== void 0) {
10884
11122
  if (escalation.flavor === "B" && escalation.deadlineMs === void 0) throw new ConfigError("flavor 'B' escalation requires an explicit deadlineMs: the suspension deadline has no engine default");
11123
+ if (escalation.deadlineMs !== void 0) requirePositiveInteger(escalation.deadlineMs, "escalation.deadlineMs");
11124
+ if (escalation.minSpendUsd !== void 0) requireNonNegativeNumber(escalation.minSpendUsd, "escalation.minSpendUsd");
10885
11125
  if (opts.result !== "full" && internals.onEscalation === void 0) throw new ConfigError("a spawn that opts into escalation from a plain value-form call needs an onEscalation hook (or use result: 'full')");
10886
11126
  if (escalation.flavor === "B" && escalation.deadlineMs === void 0) throw new ConfigError("escalation flavor 'B' requires an explicit deadlineMs");
10887
11127
  }
@@ -11010,6 +11250,8 @@ function createCtx(internals, rootWorkflow) {
11010
11250
  }
11011
11251
  const retryPolicy = opts.retry ?? profile?.retry ?? internals.defaults.retry;
11012
11252
  if (retryPolicy !== void 0) validateRetryPolicy(retryPolicy, opts.retry !== void 0 ? "the agent retry option" : profile?.retry !== void 0 ? `the retry of profile '${String(opts.agentType)}'` : "engine defaults.retry");
11253
+ if (opts.estCost !== void 0) requireNonNegativeNumber(opts.estCost, "the agent estCost option");
11254
+ if (opts.limits !== void 0) validateUsageLimits(opts.limits, "the agent limits option");
11013
11255
  const identityInput = {
11014
11256
  kind: "agent",
11015
11257
  agentType,
@@ -11413,12 +11655,12 @@ function createCtx(internals, rootWorkflow) {
11413
11655
  if (retryPolicy !== void 0) runAgentOptions.retry = { policy: retryPolicy };
11414
11656
  if (internals.providerLimiter !== void 0) {
11415
11657
  const limiter = internals.providerLimiter;
11416
- runAgentOptions.providerSlot = (key, fn) => limiter.withSlot(key, fn, () => internals.events.emit({
11658
+ runAgentOptions.providerSlot = (key, fn, signal) => limiter.withSlot(key, fn, () => internals.events.emit({
11417
11659
  type: "agent:queued",
11418
11660
  agentType,
11419
11661
  label: opts.label,
11420
11662
  providerKey: key
11421
- }, spanId));
11663
+ }, spanId), signal);
11422
11664
  }
11423
11665
  if (opts.stream !== void 0) runAgentOptions.stream = opts.stream;
11424
11666
  if (opts.label !== void 0) runAgentOptions.label = opts.label;
@@ -11430,56 +11672,13 @@ function createCtx(internals, rootWorkflow) {
11430
11672
  type: "agent:queued",
11431
11673
  agentType,
11432
11674
  label: opts.label
11433
- }, spanId));
11675
+ }, spanId), branchOrRunSignal);
11434
11676
  } finally {
11435
11677
  exitActivity?.();
11436
11678
  }
11437
11679
  internals.budget.releaseReserve(reserve, budgetAccount);
11438
- let flavorBDecision;
11439
- if (result.status === "escalated" && escalation?.flavor === "B" && result.escalationRequest !== void 0) {
11440
- if (internals.external === void 0) throw new ConfigError("flavor B escalation requires the engine run context");
11441
- const request = result.escalationRequest;
11442
- const deadlineMs = escalation.deadlineMs;
11443
- if (deadlineMs === void 0) throw new ConfigError("flavor 'B' escalation requires an explicit deadlineMs");
11444
- const defaultDecision = escalation.defaultDecision ?? { kind: "accept" };
11445
- let timer;
11446
- const decisionOutcome = await internals.external.awaitDecision({
11447
- scope: agentScope(state.scope, running.seq),
11448
- spanId: internals.spans.mint(spanId),
11449
- toolName: "escalate",
11450
- input: request,
11451
- deadlineAt: new Date(internals.now() + deadlineMs).toISOString(),
11452
- onPending: (entry, replayed) => {
11453
- internals.events.emit({
11454
- type: "approval:pending",
11455
- toolName: "escalate",
11456
- entryRef: entry.seq
11457
- }, spanId, replayed);
11458
- const registry = internals.external;
11459
- const dueAt = Date.parse(entry.deadlineAt ?? "") || internals.now();
11460
- timer = setTimeout(() => {
11461
- registry?.submitResolution(entry.seq, {
11462
- by: "timeout",
11463
- value: defaultDecision
11464
- }).catch(() => void 0);
11465
- }, Math.max(0, dueAt - internals.now()));
11466
- if (internals.onEscalation !== void 0) {
11467
- const preview = buildEscalationReport(request, result, void 0);
11468
- const previewResult = {
11469
- ...result,
11470
- escalation: preview
11471
- };
11472
- Promise.resolve(internals.onEscalation(previewResult)).then((decision) => registry?.submitResolution(entry.seq, {
11473
- by: "external",
11474
- value: decision
11475
- })).catch(() => void 0);
11476
- }
11477
- }
11478
- });
11479
- if (timer !== void 0) clearTimeout(timer);
11480
- flavorBDecision = decisionOutcome.value;
11481
- }
11482
- if (acquired !== void 0) {
11680
+ const collectAndDisposeWorktree = async () => {
11681
+ if (acquired === void 0) return;
11483
11682
  try {
11484
11683
  const { files, patch } = await acquired.collect();
11485
11684
  const patchRef = internals.mintTranscriptRef();
@@ -11499,7 +11698,62 @@ function createCtx(internals, rootWorkflow) {
11499
11698
  }, spanId);
11500
11699
  }
11501
11700
  await acquired.dispose(result.status !== "ok" && result.status !== "escalated");
11701
+ };
11702
+ let flavorBDecision;
11703
+ if (result.status === "escalated" && escalation?.flavor === "B" && result.escalationRequest !== void 0) {
11704
+ if (internals.external === void 0) throw new ConfigError("flavor B escalation requires the engine run context");
11705
+ const request = result.escalationRequest;
11706
+ const deadlineMs = escalation.deadlineMs;
11707
+ if (deadlineMs === void 0) throw new ConfigError("flavor 'B' escalation requires an explicit deadlineMs");
11708
+ const defaultDecision = escalation.defaultDecision ?? { kind: "accept" };
11709
+ let timer;
11710
+ let decisionOutcome;
11711
+ try {
11712
+ decisionOutcome = await internals.external.awaitDecision({
11713
+ scope: agentScope(state.scope, running.seq),
11714
+ spanId: internals.spans.mint(spanId),
11715
+ toolName: "escalate",
11716
+ input: request,
11717
+ deadlineAt: new Date(internals.now() + deadlineMs).toISOString(),
11718
+ signal: branchOrRunSignal,
11719
+ onPending: (entry, replayed) => {
11720
+ internals.events.emit({
11721
+ type: "approval:pending",
11722
+ toolName: "escalate",
11723
+ entryRef: entry.seq
11724
+ }, spanId, replayed);
11725
+ const registry = internals.external;
11726
+ timer = setLongTimeout(() => {
11727
+ registry?.submitResolution(entry.seq, {
11728
+ by: "timeout",
11729
+ value: defaultDecision
11730
+ }).catch(() => void 0);
11731
+ }, Date.parse(entry.deadlineAt ?? "") || internals.now(), () => internals.now());
11732
+ if (internals.onEscalation !== void 0) {
11733
+ const preview = buildEscalationReport(request, result, void 0);
11734
+ const previewResult = {
11735
+ ...result,
11736
+ escalation: preview
11737
+ };
11738
+ Promise.resolve(internals.onEscalation(previewResult)).then((decision) => registry?.submitResolution(entry.seq, {
11739
+ by: "external",
11740
+ value: decision
11741
+ })).catch(() => void 0);
11742
+ }
11743
+ }
11744
+ });
11745
+ } catch (thrown) {
11746
+ if (thrown instanceof EscalationDecisionAbortedError) {
11747
+ await collectAndDisposeWorktree();
11748
+ throw new AgentCallError(thrown.message, result, state.scope, running.seq);
11749
+ }
11750
+ throw thrown;
11751
+ } finally {
11752
+ if (timer !== void 0) timer.cancel();
11753
+ }
11754
+ flavorBDecision = decisionOutcome.value;
11502
11755
  }
11756
+ if (acquired !== void 0) await collectAndDisposeWorktree();
11503
11757
  if (result.status === "escalated" && result.escalationRequest !== void 0) {
11504
11758
  const patchRef = result.artifacts?.find((artifact) => artifact.kind === "patch")?.ref;
11505
11759
  const report = buildEscalationReport(result.escalationRequest, result, patchRef);
@@ -12137,6 +12391,28 @@ async function executeWorkflow(internals, wf, args) {
12137
12391
  * written; escalated children simply settle into their digests.
12138
12392
  */
12139
12393
  const ORCHESTRATE_WORKFLOW_NAME = "rulvar-orchestrate";
12394
+ /**
12395
+ * The orchestrate intake gate (v1.35.0 review P2-2): every numeric
12396
+ * option and the atCap literal validate SYNCHRONOUSLY at workflow
12397
+ * construction, shared by both surfaces (the top level orchestrate() throws
12398
+ * before a run exists; ctx.orchestrate throws before any journal entry,
12399
+ * provider call, or child dispatch). A NaN here previously disabled the
12400
+ * spawn cap (`spawnOrdinal >= NaN` is false forever) and the digest
12401
+ * render bound, and a negative finalize reserve WIDENED the soft cap
12402
+ * boundary instead of reserving from it.
12403
+ */
12404
+ function validateOrchestrateOptions(opts) {
12405
+ if (opts === void 0) return;
12406
+ if (opts.maxSpawns !== void 0) requireNonNegativeInteger(opts.maxSpawns, "orchestrate maxSpawns");
12407
+ if (opts.renderBudgetChars !== void 0) requireNonNegativeInteger(opts.renderBudgetChars, "orchestrate renderBudgetChars");
12408
+ const spec = opts.budget;
12409
+ if (spec === void 0) return;
12410
+ if (spec.capUsd !== void 0) requireNonNegativeNumber(spec.capUsd, "orchestrate budget.capUsd");
12411
+ if (spec.capFraction !== void 0) requireFraction(spec.capFraction, "orchestrate budget.capFraction");
12412
+ if (spec.finalizeReserveUsd !== void 0) requireNonNegativeNumber(spec.finalizeReserveUsd, "orchestrate budget.finalizeReserveUsd");
12413
+ if (spec.finalizeTurns !== void 0) requirePositiveInteger(spec.finalizeTurns, "orchestrate budget.finalizeTurns");
12414
+ if (spec.atCap !== void 0 && spec.atCap !== "finish-with-partial" && spec.atCap !== "fail-run") throw new ConfigError(`orchestrate budget.atCap must be 'finish-with-partial' or 'fail-run'; got ${String(spec.atCap)}`);
12415
+ }
12140
12416
  function orchestratorPrompt(goal, maxSpawns, extensionLines) {
12141
12417
  return [
12142
12418
  "You are the orchestrator of a multi-agent run.",
@@ -12191,6 +12467,7 @@ function filterProfiles(registered, names) {
12191
12467
  * orchestrator agent with the finish terminal tool.
12192
12468
  */
12193
12469
  function makeOrchestratorWorkflow(goal, opts) {
12470
+ validateOrchestrateOptions(opts);
12194
12471
  return defineWorkflow({ name: ORCHESTRATE_WORKFLOW_NAME }, async (ctx) => {
12195
12472
  const runtime = runtimeOf(ctx);
12196
12473
  const { internals } = runtime;
@@ -12216,7 +12493,6 @@ function makeOrchestratorWorkflow(goal, opts) {
12216
12493
  const runCeiling = internals.budget.accountView(callingState.budgetScope ?? "run")?.ceilingUsd;
12217
12494
  const spec = opts?.budget;
12218
12495
  const fraction = spec?.capFraction ?? .2;
12219
- if (fraction > 1) throw new OrchestratorCapConfigError(`capFraction ${String(fraction)} exceeds 1.0 (opting out of the cap is explicit only, up to 1.0 inclusive)`);
12220
12496
  const fromFraction = runCeiling === void 0 ? void 0 : fraction * runCeiling;
12221
12497
  const bounds = [spec?.capUsd, fromFraction].filter((bound) => bound !== void 0);
12222
12498
  const priorReserveDecision = internals.replayer.snapshot().find((entry) => {
@@ -12399,6 +12675,15 @@ function makeOrchestratorWorkflow(goal, opts) {
12399
12675
  byOrdinal.set(spawnOrdinal, record);
12400
12676
  return record;
12401
12677
  };
12678
+ /**
12679
+ * The declared fail-run terminal (v1.35.0 review P2-1): the first
12680
+ * extension terminate() call stores its failure and aborts the
12681
+ * orchestrator loop; the settle boundary rethrows it deterministically
12682
+ * (boot terminates again from the journaled verdict on resume, so the
12683
+ * same failure rolls forward without a model call).
12684
+ */
12685
+ let extensionTermination;
12686
+ const forcedFinishController = new AbortController();
12402
12687
  const io = {
12403
12688
  runId: internals.runId,
12404
12689
  baseScope: callingState.scope,
@@ -12444,7 +12729,12 @@ function makeOrchestratorWorkflow(goal, opts) {
12444
12729
  },
12445
12730
  registerAlias: (donorScope, targetScope) => internals.replayer.registerAlias(donorScope, targetScope),
12446
12731
  priceUsd: (servedBy, usage) => servedBy === void 0 ? void 0 : internals.priceUsd(servedBy, usage),
12447
- emit: (event, options) => internals.events.emit(event, callingState.spanId, options?.replayed)
12732
+ emit: (event, options) => internals.events.emit(event, callingState.spanId, options?.replayed),
12733
+ terminate: (error) => {
12734
+ if (extensionTermination !== void 0) return;
12735
+ extensionTermination = error;
12736
+ forcedFinishController.abort("rulvar:extension-terminate");
12737
+ }
12448
12738
  };
12449
12739
  const cancelByHandle = async (handle, _reason) => {
12450
12740
  const record = records.get(handle);
@@ -12537,7 +12827,6 @@ function makeOrchestratorWorkflow(goal, opts) {
12537
12827
  await runExtensionActivity();
12538
12828
  };
12539
12829
  let capDecisionRef = internals.replayer.snapshot().find((entry) => entry.kind === "decision" && entry.value?.decisionType === "orchestrator_budget_cap")?.seq;
12540
- const forcedFinishController = new AbortController();
12541
12830
  let capInFlight = false;
12542
12831
  /**
12543
12832
  * The at-cap freeze: EXACTLY one decision entry
@@ -12619,11 +12908,11 @@ function makeOrchestratorWorkflow(goal, opts) {
12619
12908
  completedDigests: undelivered.map((record) => {
12620
12909
  const row = digestOf(record, record.settled);
12621
12910
  const budgetChars = opts?.renderBudgetChars ?? 400;
12622
- if (row.outputSummary.length > budgetChars) return {
12911
+ const outputSummary = truncateToBudget(row.outputSummary, budgetChars);
12912
+ return outputSummary === row.outputSummary ? row : {
12623
12913
  ...row,
12624
- outputSummary: `${row.outputSummary.slice(0, budgetChars)}...`
12914
+ outputSummary
12625
12915
  };
12626
- return row;
12627
12916
  }),
12628
12917
  escalations
12629
12918
  };
@@ -13015,9 +13304,32 @@ function makeOrchestratorWorkflow(goal, opts) {
13015
13304
  completed: [...records.values()].filter((record) => record.settled !== void 0).sort((a, b) => a.spawnOrdinal - b.spawnOrdinal).map((record) => digestOf(record, record.settled))
13016
13305
  };
13017
13306
  };
13018
- if (capDecisionRef !== void 0) return await runForcedFinish();
13307
+ /**
13308
+ * The settle at the cap: the JOURNALED cap decision drives the policy
13309
+ * branch (its `fallback` field froze budget.atCap when the cap
13310
+ * tripped), so a crash between the decision and its effect rolls the
13311
+ * SAME outcome forward on resume, immune to drift of the live options.
13312
+ * 'finish-with-partial' runs the reserved finalizer;
13313
+ * 'fail-run' skips it and fails the run typed (v1.35.0 review P2-1:
13314
+ * the policy used to be journaled and then ignored).
13315
+ */
13316
+ const settleCapOutcome = async () => {
13317
+ const capValue = internals.replayer.snapshot().find((entry) => entry.seq === capDecisionRef)?.value;
13318
+ if (capValue?.fallback === "fail-run") throw new FailRunError(`the orchestrator budget cap was reached (decision entry ${String(capDecisionRef ?? -1)}) and budget.atCap is 'fail-run': the reserved finalizer is skipped and the run fails instead of returning a partial result`, { data: {
13319
+ source: "orchestrator_budget_cap",
13320
+ capDecisionRef: capDecisionRef ?? -1,
13321
+ spentUsd: capValue.spentUsd ?? 0,
13322
+ capUsd: capValue.capUsd ?? 0
13323
+ } });
13324
+ return await runForcedFinish();
13325
+ };
13326
+ const bootTermination = extensionTermination;
13327
+ if (bootTermination !== void 0) throw bootTermination;
13328
+ if (capDecisionRef !== void 0) return await settleCapOutcome();
13019
13329
  const result = await runtime.runInScope(orchestratorState, () => ctx.agent(orchestratorPrompt(goal, opts?.maxSpawns, extension?.promptLines?.()), agentOpts));
13020
- if (capDecisionRef !== void 0) return await runForcedFinish();
13330
+ const liveTermination = extensionTermination;
13331
+ if (liveTermination !== void 0) throw liveTermination;
13332
+ if (capDecisionRef !== void 0) return await settleCapOutcome();
13021
13333
  if (orchestratorAccount !== void 0) internals.cost.orchestrator.spentUsd = internals.budget.accountView(orchestratorAccount)?.spentUsd ?? 0;
13022
13334
  if (result.status !== "ok") throw new ConfigError(`the orchestrator agent terminated with status '${result.status}'` + (result.errorMessage === void 0 ? "" : `: ${result.errorMessage}`));
13023
13335
  return result.output;
@@ -13325,6 +13637,36 @@ var InProcessRunner = class {
13325
13637
  * ctx is created per run. engine.resume lands with the journal
13326
13638
  * kernel in M2.
13327
13639
  */
13640
+ /**
13641
+ * The accepted RunOptions.deadlineAt grammar: an ISO 8601 calendar
13642
+ * date-time with minute precision at least, optional seconds and
13643
+ * fractional seconds, and a MANDATORY UTC designator or numeric offset.
13644
+ * Date.parse would accept far more (and would read an offset-less
13645
+ * date-time in the host's local zone, so the same string would mean a
13646
+ * different instant on different hosts); the grammar pins one meaning.
13647
+ */
13648
+ const DEADLINE_AT_GRAMMAR = /^(\d{4})-(\d{2})-(\d{2})T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?(?:Z|[+-]\d{2}:\d{2})$/;
13649
+ /**
13650
+ * Typed refusal of a malformed deadlineAt (v1.34.0 review P2-1). The
13651
+ * calendar day is range-checked explicitly: V8's Date.parse silently
13652
+ * ROLLS an impossible ISO day into the next month (2026-02-30 parses as
13653
+ * 2026-03-02), so the finite check alone would accept a date the host
13654
+ * never wrote and cancel the run at a different instant.
13655
+ */
13656
+ function parseDeadlineAt(value) {
13657
+ const parsed = Date.parse(value);
13658
+ const match = DEADLINE_AT_GRAMMAR.exec(value);
13659
+ const refuse = () => {
13660
+ throw new ConfigError(`RunOptions.deadlineAt must be an ISO 8601 date-time with an explicit UTC designator or offset (e.g. 2026-07-21T10:00:00Z or 2026-07-21T12:00:00+02:00); got '${value}'`);
13661
+ };
13662
+ if (match === null || !Number.isFinite(parsed)) refuse();
13663
+ const year = Number(match?.[1]);
13664
+ const month = Number(match?.[2]);
13665
+ const day = Number(match?.[3]);
13666
+ const daysInMonth = new Date(Date.UTC(year, month, 0)).getUTCDate();
13667
+ if (month < 1 || month > 12 || day < 1 || day > daysInMonth) refuse();
13668
+ return parsed;
13669
+ }
13328
13670
  /** Content hash of an in-process workflow body (run-to-definition binding). */
13329
13671
  function hashWorkflowBody(wf) {
13330
13672
  return createHash("sha256").update(wf.body.toString(), "utf8").digest("hex");
@@ -13366,7 +13708,25 @@ function createEngine(options) {
13366
13708
  const maskEvents = options.redaction?.maskEvents ?? true;
13367
13709
  const defaults = options.defaults ?? {};
13368
13710
  if (defaults.retry !== void 0) validateRetryPolicy(defaults.retry, "createEngine defaults.retry");
13369
- for (const [name, profile] of Object.entries(defaults.profiles ?? {})) if (profile.retry !== void 0) validateRetryPolicy(profile.retry, `createEngine defaults.profiles['${name}'].retry`);
13711
+ if (options.concurrency?.perRun !== void 0) requirePositiveInteger(options.concurrency.perRun, "createEngine concurrency.perRun");
13712
+ for (const [adapterId, cap] of Object.entries(options.concurrency?.perProvider ?? {})) requirePositiveInteger(cap, `createEngine concurrency.perProvider['${adapterId}']`);
13713
+ const budgetDefaults = options.budgetDefaults;
13714
+ if (budgetDefaults?.flatReserveUsd !== void 0) requireNonNegativeNumber(budgetDefaults.flatReserveUsd, "createEngine budgetDefaults.flatReserveUsd");
13715
+ if (budgetDefaults?.lifetimeSpawnCap !== void 0) requireNonNegativeInteger(budgetDefaults.lifetimeSpawnCap, "createEngine budgetDefaults.lifetimeSpawnCap");
13716
+ if (budgetDefaults?.childBudgetFraction !== void 0) requireFraction(budgetDefaults.childBudgetFraction, "createEngine budgetDefaults.childBudgetFraction");
13717
+ if (budgetDefaults?.maxDepth !== void 0) {
13718
+ requirePositiveInteger(budgetDefaults.maxDepth, "createEngine budgetDefaults.maxDepth");
13719
+ if (budgetDefaults.maxDepth > 4) throw new ConfigError(`createEngine budgetDefaults.maxDepth ${String(budgetDefaults.maxDepth)} is outside [1, ${String(4)}] (default 1, hard ceiling ${String(4)})`);
13720
+ }
13721
+ if (defaults.limits !== void 0) validateUsageLimits(defaults.limits, "createEngine defaults.limits");
13722
+ for (const [name, profile] of Object.entries(defaults.profiles ?? {})) {
13723
+ if (profile.retry !== void 0) validateRetryPolicy(profile.retry, `createEngine defaults.profiles['${name}'].retry`);
13724
+ if (profile.limits !== void 0) validateUsageLimits(profile.limits, `createEngine defaults.profiles['${name}'].limits`);
13725
+ if (profile.estCost !== void 0) requireNonNegativeNumber(profile.estCost, `createEngine defaults.profiles['${name}'].estCost`);
13726
+ if (profile.escalation?.deadlineMs !== void 0) requirePositiveInteger(profile.escalation.deadlineMs, `createEngine defaults.profiles['${name}'].escalation.deadlineMs`);
13727
+ if (profile.escalation?.minSpendUsd !== void 0) requireNonNegativeNumber(profile.escalation.minSpendUsd, `createEngine defaults.profiles['${name}'].escalation.minSpendUsd`);
13728
+ if (profile.compaction?.threshold !== void 0) requireFraction(profile.compaction.threshold, `createEngine defaults.profiles['${name}'].compaction.threshold`);
13729
+ }
13370
13730
  const knowledgeStore = options.stores?.modelKnowledge;
13371
13731
  const knowledge = knowledgeStore === void 0 ? void 0 : { current: () => knowledgeStore.current() };
13372
13732
  const runner = new InProcessRunner(options.onEscalation === void 0 ? void 0 : { onEscalation: options.onEscalation });
@@ -13385,6 +13745,9 @@ function createEngine(options) {
13385
13745
  const activeSegments = /* @__PURE__ */ new Set();
13386
13746
  function run(wf, args, opts, resumeCtx) {
13387
13747
  if (wf.kind !== "workflow" && wf.kind !== "compiled-workflow") throw new ConfigError("engine.run accepts in-process Workflow values or compileScript CompiledWorkflow values");
13748
+ if (opts?.budgetUsd !== void 0) requireNonNegativeNumber(opts.budgetUsd, "RunOptions.budgetUsd");
13749
+ if (opts?.limits !== void 0) validateUsageLimits(opts.limits, "RunOptions.limits");
13750
+ const deadlineAtMs = opts?.deadlineAt === void 0 ? void 0 : parseDeadlineAt(opts.deadlineAt);
13388
13751
  const compiled = wf.kind === "compiled-workflow" ? wf : void 0;
13389
13752
  if (compiled !== void 0 && options.runners?.sandbox === void 0) throw new ConfigError("running a CompiledWorkflow requires a sandbox runner: pass createEngine({ runners: { sandbox: new WorkerSandboxRunner() } }) from @rulvar/planner ");
13390
13753
  const runId = resumeCtx?.runId ?? opts?.runId ?? mintRunId();
@@ -13448,10 +13811,7 @@ function createEngine(options) {
13448
13811
  if (opts?.signal !== void 0) if (opts.signal.aborted) requestCancel("host signal aborted");
13449
13812
  else opts.signal.addEventListener("abort", () => requestCancel("host signal aborted"), { once: true });
13450
13813
  let deadlineTimer;
13451
- if (opts?.deadlineAt !== void 0) {
13452
- const delay = Date.parse(opts.deadlineAt) - realNow();
13453
- deadlineTimer = setTimeout(() => requestCancel(`run deadline ${opts.deadlineAt} crossed`), Math.max(0, delay));
13454
- }
13814
+ if (deadlineAtMs !== void 0) deadlineTimer = setLongTimeout(() => requestCancel(`run deadline ${opts?.deadlineAt ?? ""} crossed`), deadlineAtMs, realNow);
13455
13815
  const budget = makeBudget();
13456
13816
  const admission = new AdmissionController({
13457
13817
  budget,
@@ -13629,7 +13989,7 @@ function createEngine(options) {
13629
13989
  };
13630
13990
  }
13631
13991
  } finally {
13632
- if (deadlineTimer !== void 0) clearTimeout(deadlineTimer);
13992
+ if (deadlineTimer !== void 0) deadlineTimer.cancel();
13633
13993
  external.close();
13634
13994
  await replayer.flush().catch(() => void 0);
13635
13995
  }
@@ -14119,4 +14479,4 @@ function createSandboxBridge(ctx, options) {
14119
14479
  };
14120
14480
  }
14121
14481
  //#endregion
14122
- export { AWAIT_SCHEMA, AdmissionController, AdmissionRejectedError, AgentCallError, BUDGET_ABORT_REASON, BudgetExhaustedError, CANCEL_AGENT_SCHEMA, CHECKPOINT_FORMAT_V1, CLAIM_STATEMENT_MAX_CHARS, CLAIM_TTL_DAYS, COMPACTION_SUMMARY_PREFIX, CURRENT_HASH_VERSION, ConfigError, DEFAULT_CHILD_BUDGET_FRACTION, DEFAULT_COMPACTION_THRESHOLD, DEFAULT_ESCALATION_LIMITS, DEFAULT_FLAT_RESERVE_USD, DEFAULT_MAX_CHILDREN_PER_NODE, DEFAULT_MAX_DEPTH, DEFAULT_MAX_OSCILLATIONS_PER_KEY, DEFAULT_MAX_PINNED_WORKTREES, DEFAULT_MAX_REVISIONS_PER_RUN, DEFAULT_MAX_TOTAL_SPAWNS, DEFAULT_MAX_TURNS, DEFAULT_MODEL_RETRY_ATTEMPTS, DEFAULT_NO_PROGRESS_TURNS, DEFAULT_PER_RUN_CONCURRENCY, DEFAULT_RETRY_POLICY, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DedupIndex, EMIT_RESULT_TOOL, EMPTY_SCHEMA_HASH, EMPTY_TOOLSET_HASH, ESCALATE_TOOL_NAME, ESCALATION_REPORT_SCHEMA, ESCALATION_REQUEST_SCHEMA, EVENT_SEGMENT_STRIDE, EventBus, ExternalRegistry, FINALIZE_SYNTHESIS_INSTRUCTION, FINISH_SCHEMA, FINISH_TOOL_NAME, FileModelKnowledgeStore, FileTranscriptStore, GitWorktreeProvider, INBOX_PROPOSAL_TTL_DAYS, InMemoryStore, InMemoryTranscriptStore, InProcessRunner, InvalidResolutionError, JournalCompatibilityError, JournalMatcher, JournalMissError, JournalOrderViolation, JsonlFileStore, KB_ACTIVE_CLAIMS_CAP, KB_CARD_RENDER_BUDGET_CHARS, KeyedLimiter, KnowledgeCasError, LARGE_VALUE_WARN_BYTES, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LINEAGE_SIG_VERSION, LeaseHeldError, LineageIndex, MASKED_SECRET, MAX_DEPTH_CEILING, ModelRetry, NoProgressDetector, NonSerializableValueError, ORCHESTRATE_WORKFLOW_NAME, OrchestratorCapConfigError, PARALLEL_AGENTS_SCHEMA, ParallelSiteCounter, PlanInvariantError, ROLE_EFFORT_DEFAULTS, ROOT_ACCOUNT, ROOT_SCOPE, RUN_PROFILES, ReplayPlanHashMismatch, Replayer, ResolutionArbiter, ResolutionFold, RulvarError, RunBudget, SANDBOX_AGENT_OPT_KEYS, SPAWN_AGENT_SCHEMA, SandboxError, ScriptRejected, Semaphore, SpanRegistry, TOOL_NAME_PATTERN, TerminationAccount, WAIT_FOR_EVENTS_SCHEMA, WAIT_FOR_EVENTS_TOOL_NAME, WAKE_SUMMARY_RENDER_BUDGET_CHARS, admissionReserveUsd, affordableOutputTokens, agentErrorFromWire, agentErrorToWire, agentResultWire, agentScope, applyClaimOps, applyStructuredOutputTier, approachSigCoarse, approachSigOf, archiveDeprecatedModelOps, atCompactionThreshold, buildAbandonFold, buildAdapterRegistry, buildCostReport, buildDeriverRegistry, buildOrchestratorTools, buildTerminationInitValue, buildToolContext, canRideLoopTurn, canonicalIsolationTag, canonicalizeLadder, canonicalizeSchema, capIssues, capsHashOf, checkFloors, checkpointRefFor, childCoveragePrefix, claimExpired, claimExpiry, claimIssues, claimOpIssues, classifyAgentError, classifyAttemptOutcome, collectDeclaredLadders, compactMessages, compilePermissionChain, compilePermissionPreset, compileVerifiedLayer, costReportFromJournal, countsAgainstLimit, createCanonicalIdMinter, createCtx, createEngine, createSandboxBridge, currentOnlyKeyRing, decodeCheckpoint, defineWorkflow, deriveContentKey, deriverV1, deriverV2, digestOf, dispositionHook, emptyDigestBlocks, emptyToolset, encodeCheckpoint, entryUsageSlices, escalateTool, evaluatePermission, evaluateReuse, executeWorkflow, exhaustionCodeOf, extractCandidate, failoverTriggerOf, fallbackTriggerOf, filterClaimsForRun, finalizeFires, foldTermination, formatRePrompt, formatScopePath, hasMetaLookup, hashRunArgs, hashWorkflowBody, hashWorkflowSource, identityJcs, isEscalated, isSchemaPairSpec, isStandardSchemaSpec, isStrictCompatibleSchema, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, lexShellCommand, liftRetainedParts, lineageWeightOf, makeOrchestratorWorkflow, maskSecrets, maskSecretsDeep, maskSecretsJson, matchArgvPattern, matchShellCommand, mcp, mergeUsageLimits, metaMatchesFilter, modelEpochOf, modelKnowledgeCard, modelSpecIdentity, needsSeparateExtract, nextFailover, nodeLinkKey, normalizeApproachTag, normalizeEntry, normalizeFallbacks, orchestrate, parallelScope, parseModelRef, parseScopePath, phiInitialOf, pipelineScope, planNodeScope, priceEntryUsage, priceUsdOf, profileCard, profileRegistrySnapshotHash, projectHistory, projectIdentity, projectToJsonSchema, proposalStatement, providerOf, readRunMeta, readTerminationInit, registryKeyRing, remeasureQueue, replayDisposition, resolveModelInvocation, resolvePricing, resolveToolset, retryClassOf, retryDelayMs, roleConfiguredInRouting, roundOneDisposition, runAgent, runProfile, sanitizeTerminalText, sanitizeTokenCount, sanitizeUsage, sanitizeUsageDelta, scanJournalCompatibility, schemaHash, schemaHashOfSpec, selectStructuredOutputTier, shouldCompact, snapshotUsage, spawnDepthOf, summarizeInstruction, summarizeOutput, terminationConfigDrift, tierWithinCaps, toApprovalDecision, toJournalValue, tool, toolContract, toolsetHash, ttlState, usageViolations, validateEditorialCommit, validateEntryShape, validateEscalationLimits, validateEscalationReport, validateRetryPolicy, validateSchemaSpec, validateTerminationLimits, workflowScope, workflowSourceRef, wrapJournalStore, wrapTranscriptStore };
14482
+ export { AWAIT_SCHEMA, AdmissionController, AdmissionRejectedError, AgentCallError, BUDGET_ABORT_REASON, BudgetExhaustedError, CANCEL_AGENT_SCHEMA, CHECKPOINT_FORMAT_V1, CLAIM_STATEMENT_MAX_CHARS, CLAIM_TTL_DAYS, COMPACTION_SUMMARY_PREFIX, CURRENT_HASH_VERSION, ConfigError, DEFAULT_CHILD_BUDGET_FRACTION, DEFAULT_COMPACTION_THRESHOLD, DEFAULT_ESCALATION_LIMITS, DEFAULT_FLAT_RESERVE_USD, DEFAULT_MAX_CHILDREN_PER_NODE, DEFAULT_MAX_DEPTH, DEFAULT_MAX_OSCILLATIONS_PER_KEY, DEFAULT_MAX_PINNED_WORKTREES, DEFAULT_MAX_REVISIONS_PER_RUN, DEFAULT_MAX_TOTAL_SPAWNS, DEFAULT_MAX_TURNS, DEFAULT_MODEL_RETRY_ATTEMPTS, DEFAULT_NO_PROGRESS_TURNS, DEFAULT_PER_RUN_CONCURRENCY, DEFAULT_RETRY_POLICY, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DedupIndex, EMIT_RESULT_TOOL, EMPTY_SCHEMA_HASH, EMPTY_TOOLSET_HASH, ESCALATE_TOOL_NAME, ESCALATION_REPORT_SCHEMA, ESCALATION_REQUEST_SCHEMA, EVENT_SEGMENT_STRIDE, EscalationDecisionAbortedError, EventBus, ExternalRegistry, FINALIZE_SYNTHESIS_INSTRUCTION, FINISH_SCHEMA, FINISH_TOOL_NAME, FailRunError, FileModelKnowledgeStore, FileTranscriptStore, GitWorktreeProvider, INBOX_PROPOSAL_TTL_DAYS, InMemoryStore, InMemoryTranscriptStore, InProcessRunner, InvalidResolutionError, JournalCompatibilityError, JournalMatcher, JournalMissError, JournalOrderViolation, JsonlFileStore, KB_ACTIVE_CLAIMS_CAP, KB_CARD_RENDER_BUDGET_CHARS, KeyedLimiter, KnowledgeCasError, LARGE_VALUE_WARN_BYTES, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LINEAGE_SIG_VERSION, LeaseHeldError, LineageIndex, MASKED_SECRET, MAX_DEPTH_CEILING, ModelRetry, NoProgressDetector, NonSerializableValueError, ORCHESTRATE_WORKFLOW_NAME, OrchestratorCapConfigError, PARALLEL_AGENTS_SCHEMA, ParallelSiteCounter, PlanInvariantError, ROLE_EFFORT_DEFAULTS, ROOT_ACCOUNT, ROOT_SCOPE, RUN_PROFILES, ReplayPlanHashMismatch, Replayer, ResolutionArbiter, ResolutionFold, RulvarError, RunBudget, SANDBOX_AGENT_OPT_KEYS, SPAWN_AGENT_SCHEMA, SandboxError, ScriptRejected, Semaphore, SpanRegistry, TOOL_NAME_PATTERN, TerminationAccount, WAIT_FOR_EVENTS_SCHEMA, WAIT_FOR_EVENTS_TOOL_NAME, WAKE_SUMMARY_RENDER_BUDGET_CHARS, admissionReserveUsd, affordableOutputTokens, agentErrorFromWire, agentErrorToWire, agentResultWire, agentScope, applyClaimOps, applyStructuredOutputTier, approachSigCoarse, approachSigOf, archiveDeprecatedModelOps, atCompactionThreshold, buildAbandonFold, buildAdapterRegistry, buildCostReport, buildDeriverRegistry, buildOrchestratorTools, buildTerminationInitValue, buildToolContext, canRideLoopTurn, canonicalIsolationTag, canonicalizeLadder, canonicalizeSchema, capIssues, capsHashOf, checkFloors, checkpointRefFor, childCoveragePrefix, claimExpired, claimExpiry, claimIssues, claimOpIssues, classifyAgentError, classifyAttemptOutcome, collectDeclaredLadders, compactMessages, compilePermissionChain, compilePermissionPreset, compileVerifiedLayer, costReportFromJournal, countsAgainstLimit, createCanonicalIdMinter, createCtx, createEngine, createSandboxBridge, currentOnlyKeyRing, decodeCheckpoint, defineWorkflow, deriveContentKey, deriverV1, deriverV2, digestOf, dispositionHook, emptyDigestBlocks, emptyToolset, encodeCheckpoint, entryUsageSlices, escalateTool, evaluatePermission, evaluateReuse, executeWorkflow, exhaustionCodeOf, extractCandidate, failoverTriggerOf, fallbackTriggerOf, filterClaimsForRun, finalizeFires, foldTermination, formatRePrompt, formatScopePath, hasMetaLookup, hashRunArgs, hashWorkflowBody, hashWorkflowSource, identityJcs, isEscalated, isSchemaPairSpec, isStandardSchemaSpec, isStrictCompatibleSchema, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, lexShellCommand, liftRetainedParts, lineageWeightOf, makeOrchestratorWorkflow, maskSecrets, maskSecretsDeep, maskSecretsJson, matchArgvPattern, matchShellCommand, mcp, mergeUsageLimits, metaMatchesFilter, modelEpochOf, modelKnowledgeCard, modelSpecIdentity, needsSeparateExtract, nextFailover, nodeLinkKey, normalizeApproachTag, normalizeEntry, normalizeFallbacks, orchestrate, parallelScope, parseModelRef, parseScopePath, phiInitialOf, pipelineScope, planNodeScope, priceEntryUsage, priceUsdOf, profileCard, profileRegistrySnapshotHash, projectHistory, projectIdentity, projectToJsonSchema, proposalStatement, providerOf, readRunMeta, readTerminationInit, registryKeyRing, remeasureQueue, replayDisposition, resolveModelInvocation, resolvePricing, resolveToolset, retryClassOf, retryDelayMs, roleConfiguredInRouting, roundOneDisposition, runAgent, runProfile, sanitizeTerminalText, sanitizeTokenCount, sanitizeUsage, sanitizeUsageDelta, scanJournalCompatibility, schemaHash, schemaHashOfSpec, selectStructuredOutputTier, shouldCompact, snapshotUsage, spawnDepthOf, summarizeInstruction, summarizeOutput, terminationConfigDrift, tierWithinCaps, toApprovalDecision, toJournalValue, tool, toolContract, toolsetHash, ttlState, usageViolations, validateEditorialCommit, validateEntryShape, validateEscalationLimits, validateEscalationReport, validateRetryPolicy, validateSchemaSpec, validateTerminationLimits, validateUsageLimits, workflowScope, workflowSourceRef, wrapJournalStore, wrapTranscriptStore };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/core",
3
- "version": "1.34.0",
3
+ "version": "1.36.0",
4
4
  "description": "Rulvar core: L0 contracts, journal kernel, ctx primitives, agent runtime, model router, tool system, dynamic orchestrator, InMemory and JSONL stores, event stream.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",