@rulvar/core 1.219.0 → 1.221.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.
package/dist/index.d.ts CHANGED
@@ -2162,7 +2162,15 @@ type CoreEvents = {
2162
2162
  ran: boolean;
2163
2163
  reason?: string;
2164
2164
  };
2165
- }; /** Children accepted through validated terminal output salvage on 'limit'; same lift. */
2165
+ };
2166
+ /**
2167
+ * The claim-consistency pass meta, lifted from the same envelope
2168
+ * (or typed error data) when it carries a valid object (RV2203);
2169
+ * `judgeDeclined` rides here on the failed terminals that used to
2170
+ * read null while the journal held the verdict.
2171
+ */
2172
+ claimConsistencyMeta?: Record<string, unknown>; /** The synthesis-skip marker from the same envelope; same lift (RV2203). */
2173
+ synthesisSkipped?: boolean | string; /** Children accepted through validated terminal output salvage on 'limit'; same lift. */
2166
2174
  salvagedTerminalOutputChildren?: string[];
2167
2175
  /**
2168
2176
  * Children that settled 'ok' below their declared evidence floor
@@ -11305,6 +11313,15 @@ type RunOutcome<R> = {
11305
11313
  salvagedPartialChildren?: string[]; /** The explicit semantic pass summaries (RV1906); same lift and posture. */
11306
11314
  semanticPasses?: SemanticPassesSummary;
11307
11315
  /**
11316
+ * The claim-consistency pass meta (`judgeInvoked`, `judgeDeclined`,
11317
+ * the pair counts), lifted from the same envelope or typed error
11318
+ * data (RV2203). The RV2106 mirror run journaled its declined judge
11319
+ * and the error terminal carried null: the truth now rides every
11320
+ * terminal that has it, ok and failed alike.
11321
+ */
11322
+ claimConsistencyMeta?: Record<string, unknown>; /** The synthesis-skip marker from the same envelope; same lift and posture (RV2203). */
11323
+ synthesisSkipped?: boolean | string;
11324
+ /**
11308
11325
  * Children accepted through validated terminal output salvage on
11309
11326
  * 'limit'; same lift and posture.
11310
11327
  */
package/dist/index.js CHANGED
@@ -12604,6 +12604,102 @@ async function runAgent(options) {
12604
12604
  resolved: options.resolved
12605
12605
  }, ...options.fallbacks ?? []];
12606
12606
  const loopCursor = { index: 0 };
12607
+ /**
12608
+ * The drained-finalization grant (RV2204, the third parity rerun).
12609
+ * The exposure drain is terminal mid-work: spend only grows, so a
12610
+ * seat refused with no live holder left can never dispatch an
12611
+ * ordinary turn again. The third parity rerun killed three workers
12612
+ * ~30 turns into research with evidence pools of 17 and 22 under a
12613
+ * floor of 24 and a CONFIGURED finalization window: the drain came
12614
+ * before the window, and the window's play needs the very wire the
12615
+ * drain refuses. With `limits.finalizationReserve.maxOutputTokens`
12616
+ * declared, a drained seat that already did work now spends ONE
12617
+ * clamped finalization turn before its typed terminal: the output
12618
+ * clamp shrinks the turn's exposure estimate (the full estimate was
12619
+ * refused; the clamped one prices the summary allowance instead of
12620
+ * the whole per-turn cap), the finalization-window allowlist rides
12621
+ * as the turn's only tools so outstanding record_evidence calls can
12622
+ * land in parallel, and the executed calls persist through the
12623
+ * ordinary tool machinery. Best effort, exactly like the tool-budget
12624
+ * reserve turn: a refusal of even the clamped estimate warns and
12625
+ * keeps the typed drained terminal; a seat with NO completed turns
12626
+ * keeps dying free (the RV2002 zero-cost doctrine: nothing to
12627
+ * summarize, nothing paid).
12628
+ */
12629
+ let drainFinalizationRan = false;
12630
+ const runDrainFinalization = async (refusal) => {
12631
+ const allow = limits.finalizationWindow?.allow;
12632
+ const allowedTools = allow === void 0 ? void 0 : options.tools?.contracts.filter((contract) => allow.includes(contract.name));
12633
+ const toolsRide = allowedTools !== void 0 && allowedTools.length > 0;
12634
+ const drainMessages = [...messages, {
12635
+ role: "user",
12636
+ parts: [{
12637
+ type: "text",
12638
+ text: `The run's in-flight exposure pool is drained; no further ordinary turns can dispatch (${refusal}). This is your single finalization turn` + (toolsRide ? ": record any outstanding evidence with the allowed tools IN THIS turn (parallel calls), then close" : `: close`) + ` with your best final summary of the work already done.`
12639
+ }]
12640
+ }];
12641
+ let grant;
12642
+ try {
12643
+ grant = await dispatchPhase({
12644
+ role: primaryRole,
12645
+ chain: loopChain,
12646
+ cursor: loopCursor,
12647
+ requestFor: (target) => {
12648
+ let req = buildRequest(target.resolved, projectHistory(drainMessages, providerOf(target.adapter)), limits, toolsRide ? allowedTools : void 0);
12649
+ const reserveMax = limits.finalizationReserve?.maxOutputTokens;
12650
+ if (reserveMax !== void 0) req = {
12651
+ ...req,
12652
+ maxOutputTokens: Math.min(req.maxOutputTokens ?? reserveMax, reserveMax)
12653
+ };
12654
+ req = applyCachePolicy(req, target, options.cache);
12655
+ return applyOutputBudget(req, target, options.budget);
12656
+ },
12657
+ streamOptionsFor: (target) => {
12658
+ const drainStreamOptions = {
12659
+ idleTimeoutMs: limits.streamIdleTimeoutMs,
12660
+ signals: options.signal === void 0 ? [] : [options.signal],
12661
+ onUsage: (delta) => options.budget?.onUsage(delta, target.resolved.ref)
12662
+ };
12663
+ if (options.budget?.signal !== void 0) drainStreamOptions.budgetSignal = options.budget.signal;
12664
+ if (options.stream === true) drainStreamOptions.onDelta = (delta) => events?.emit({
12665
+ type: "agent:stream",
12666
+ delta
12667
+ });
12668
+ return drainStreamOptions;
12669
+ }
12670
+ });
12671
+ } catch (grantThrown) {
12672
+ if (!(grantThrown instanceof BudgetExhaustedError)) throw grantThrown;
12673
+ events?.emit({
12674
+ type: "log",
12675
+ level: "warn",
12676
+ msg: `the drained-finalization turn was skipped: ${grantThrown.message}`
12677
+ });
12678
+ return;
12679
+ }
12680
+ const { outcome: grantOutcome, target: grantTarget } = grant;
12681
+ servedBy = grantTarget.resolved.ref;
12682
+ usageApprox = usageApprox || grantOutcome.usageApprox;
12683
+ messages.push(assistantMsg(grantOutcome.turn, liftRetainedParts(grantOutcome.providerMetadata, grantTarget.adapter)));
12684
+ if (toolsRide && grantOutcome.turn.toolCalls.length > 0) try {
12685
+ await runToolCalls(grantOutcome.turn.toolCalls, []);
12686
+ } catch (toolThrown) {
12687
+ events?.emit({
12688
+ type: "log",
12689
+ level: "warn",
12690
+ msg: `a drained-finalization tool call failed: ${toolThrown instanceof Error ? toolThrown.message : String(toolThrown)}`
12691
+ });
12692
+ }
12693
+ events?.emit({
12694
+ type: "log",
12695
+ level: "info",
12696
+ msg: "the drained seat spent its finalization turn",
12697
+ data: {
12698
+ toolCalls: grantOutcome.turn.toolCalls.length,
12699
+ outputTokens: grantOutcome.usage.outputTokens
12700
+ }
12701
+ });
12702
+ };
12607
12703
  loop: while (status === "ok" && !finishedViaTool) {
12608
12704
  if (limits.timeoutMs !== void 0 && now() - startedAt >= limits.timeoutMs) {
12609
12705
  status = "limit";
@@ -12657,8 +12753,12 @@ async function runAgent(options) {
12657
12753
  });
12658
12754
  } catch (thrown) {
12659
12755
  if (!(thrown instanceof BudgetExhaustedError)) throw thrown;
12660
- status = "error";
12661
12756
  const typedReason = thrown.data?.reason;
12757
+ if (typedReason === "exposure-drained" && limits.finalizationReserve !== void 0 && turns > 1 && !drainFinalizationRan) {
12758
+ drainFinalizationRan = true;
12759
+ await runDrainFinalization(thrown.message);
12760
+ }
12761
+ status = "error";
12662
12762
  agentError = {
12663
12763
  kind: "budget",
12664
12764
  retryable: false,
@@ -24100,7 +24200,41 @@ function makeOrchestratorWorkflow(goal, opts) {
24100
24200
  }
24101
24201
  });
24102
24202
  };
24203
+ /**
24204
+ * The pass summaries as one builder (RV2203): the ok envelope and
24205
+ * the failure enrichment must tell the same {ran, reason} story,
24206
+ * with only the synthesis arm differing by path.
24207
+ */
24208
+ const semanticPassesSummary = (synthesis) => ({
24209
+ contradictions: opts?.contradictions === void 0 ? {
24210
+ ran: false,
24211
+ reason: "not-configured"
24212
+ } : contradictionsFound === void 0 ? {
24213
+ ran: false,
24214
+ reason: "not-run"
24215
+ } : { ran: true },
24216
+ claimConsistency: opts?.claimConsistency === void 0 ? {
24217
+ ran: false,
24218
+ reason: "not-configured"
24219
+ } : claimConsistencyMeta === void 0 ? {
24220
+ ran: false,
24221
+ reason: "not-run"
24222
+ } : { ran: true },
24223
+ synthesis
24224
+ });
24103
24225
  const enrichSynthesisFailure = (thrown, snapshot) => {
24226
+ const passTruth = {
24227
+ ...claimConsistencyMeta === void 0 ? {} : { claimConsistencyMeta },
24228
+ semanticPasses: semanticPassesSummary({
24229
+ ran: false,
24230
+ reason: "synthesis-failed"
24231
+ })
24232
+ };
24233
+ if (thrown instanceof BudgetExhaustedError) throw new BudgetExhaustedError(thrown.message, { data: {
24234
+ ...thrown.data ?? {},
24235
+ ...snapshot ?? {},
24236
+ ...passTruth
24237
+ } });
24104
24238
  if (!(thrown instanceof FailRunError)) throw thrown;
24105
24239
  const base = thrown.data ?? {};
24106
24240
  const spent = validationDecisions().filter((candidate) => candidate.verdict !== "accepted" && contractGenerationCurrent(candidate));
@@ -24110,6 +24244,7 @@ function makeOrchestratorWorkflow(goal, opts) {
24110
24244
  throw new FailRunError(thrown.message, { data: {
24111
24245
  ...base,
24112
24246
  ...snapshot ?? {},
24247
+ ...passTruth,
24113
24248
  ...spent.length === 0 || base.repairsUsed !== void 0 ? {} : {
24114
24249
  repairsUsed: spent.length,
24115
24250
  maxRepairs: validationSpec?.maxRepairs ?? 1,
@@ -24368,29 +24503,13 @@ function makeOrchestratorWorkflow(goal, opts) {
24368
24503
  ...claimFindingsFound === void 0 ? {} : { claimContradictions: claimFindingsFound },
24369
24504
  claimConsistencyMeta
24370
24505
  },
24371
- semanticPasses: {
24372
- contradictions: opts?.contradictions === void 0 ? {
24373
- ran: false,
24374
- reason: "not-configured"
24375
- } : contradictionsFound === void 0 ? {
24376
- ran: false,
24377
- reason: "not-run"
24378
- } : { ran: true },
24379
- claimConsistency: opts?.claimConsistency === void 0 ? {
24380
- ran: false,
24381
- reason: "not-configured"
24382
- } : claimConsistencyMeta === void 0 ? {
24383
- ran: false,
24384
- reason: "not-run"
24385
- } : { ran: true },
24386
- synthesis: opts?.synthesis === void 0 ? {
24387
- ran: false,
24388
- reason: "not-configured"
24389
- } : synthesisSkippedByValidDraft ? {
24390
- ran: false,
24391
- reason: "valid-draft"
24392
- } : { ran: true }
24393
- }
24506
+ semanticPasses: semanticPassesSummary(opts?.synthesis === void 0 ? {
24507
+ ran: false,
24508
+ reason: "not-configured"
24509
+ } : synthesisSkippedByValidDraft ? {
24510
+ ran: false,
24511
+ reason: "valid-draft"
24512
+ } : { ran: true })
24394
24513
  };
24395
24514
  };
24396
24515
  return defineWorkflow({ name: ORCHESTRATE_WORKFLOW_NAME }, async (ctx) => {
@@ -24773,10 +24892,16 @@ function preflightEstimate(input) {
24773
24892
  spawn: label
24774
24893
  });
24775
24894
  }
24776
- if (limits.finalizationReserve !== void 0 && limits.maxToolCalls === void 0 && limits.toolUnits === void 0) say({
24895
+ if (limits.finalizationReserve !== void 0 && limits.maxToolCalls === void 0 && limits.toolUnits === void 0 && input.run?.maxInFlightExposureUsd === void 0) say({
24777
24896
  severity: "warning",
24778
24897
  code: "inert-finalization-reserve",
24779
- message: `spawn '${label}' sets finalizationReserve without maxToolCalls or toolUnits: no tool budget limiter exists for it to fire on`,
24898
+ message: `spawn '${label}' sets finalizationReserve without maxToolCalls or toolUnits: no tool budget limiter exists for it to fire on, and no in-flight exposure cap is declared for the drained-finalization grant (RV2204) to spend it at`,
24899
+ spawn: label
24900
+ });
24901
+ if (limits.finalizationWindow !== void 0 && limits.finalizationReserve?.maxOutputTokens === void 0 && input.run?.maxInFlightExposureUsd !== void 0) say({
24902
+ severity: "info",
24903
+ code: "drained-finalization-unfunded",
24904
+ message: `spawn '${label}' declares finalizationWindow under an in-flight exposure cap but no finalizationReserve.maxOutputTokens: a mid-work exposure drain refuses the wire the window needs, and without the reserve's clamped turn the seat dies with its window unplayed (RV2204); declare the reserve to fund one drained-finalization turn`,
24780
24905
  spawn: label
24781
24906
  });
24782
24907
  if (limits.toolBudgetNotices === true && limits.maxToolCalls === void 0) say({
@@ -26026,6 +26151,10 @@ function liftRunCompletion(candidate) {
26026
26151
  synthesis: { ...synthesis }
26027
26152
  };
26028
26153
  }
26154
+ const metaCandidate = candidate.claimConsistencyMeta;
26155
+ if (typeof metaCandidate === "object" && metaCandidate !== null && !Array.isArray(metaCandidate)) lifted.claimConsistencyMeta = { ...metaCandidate };
26156
+ const skippedCandidate = candidate.synthesisSkipped;
26157
+ if (typeof skippedCandidate === "boolean" || typeof skippedCandidate === "string") lifted.synthesisSkipped = skippedCandidate;
26029
26158
  return lifted;
26030
26159
  }
26031
26160
  /**
@@ -26576,7 +26705,8 @@ function createEngine(options) {
26576
26705
  };
26577
26706
  if (value !== void 0 && (status === "ok" || status === "exhausted")) outcomeFacts.value = value;
26578
26707
  if (wireError !== void 0) outcomeFacts.error = wireError;
26579
- const lifted = liftRunCompletion(status === "ok" || status === "exhausted" ? outcomeFacts.value : status === "error" ? wireError?.data : void 0);
26708
+ let lifted = liftRunCompletion(status === "ok" || status === "exhausted" ? outcomeFacts.value : status === "error" ? wireError?.data : void 0);
26709
+ if (lifted === void 0 && status === "exhausted") lifted = liftRunCompletion(wireError?.data);
26580
26710
  if (lifted !== void 0) {
26581
26711
  outcomeFacts.completion = lifted.completion;
26582
26712
  if (lifted.childStatusCounts !== void 0) outcomeFacts.childStatusCounts = lifted.childStatusCounts;
@@ -26586,6 +26716,8 @@ function createEngine(options) {
26586
26716
  if (lifted.belowFloorOkChildren !== void 0) outcomeFacts.belowFloorOkChildren = lifted.belowFloorOkChildren;
26587
26717
  if (lifted.acceptanceChildren !== void 0) outcomeFacts.acceptanceChildren = lifted.acceptanceChildren;
26588
26718
  if (lifted.semanticPasses !== void 0) outcomeFacts.semanticPasses = lifted.semanticPasses;
26719
+ if (lifted.claimConsistencyMeta !== void 0) outcomeFacts.claimConsistencyMeta = lifted.claimConsistencyMeta;
26720
+ if (lifted.synthesisSkipped !== void 0) outcomeFacts.synthesisSkipped = lifted.synthesisSkipped;
26589
26721
  }
26590
26722
  let settlementFailure;
26591
26723
  let supersededBy;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/core",
3
- "version": "1.219.0",
3
+ "version": "1.221.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",