@rulvar/core 1.78.0 → 1.80.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
@@ -3476,6 +3476,16 @@ type CoreEvents = {
3476
3476
  * nonnegative integers. Absent otherwise.
3477
3477
  */
3478
3478
  childStatusCounts?: Record<string, number>;
3479
+ /**
3480
+ * Per-child degradation notes, lifted from the same envelope (or
3481
+ * typed error data) when it carries a valid string array (the
3482
+ * fifth experiment, cycle 75). An empty array is the workflow's
3483
+ * claim of zero degradation; absence means no claim. The outcome
3484
+ * mirror spreads the SAME lift, so the surfaces cannot disagree.
3485
+ */
3486
+ degradedReasons?: string[]; /** Children accepted by acceptPartialChildren; same lift. */
3487
+ salvagedPartialChildren?: string[]; /** Children accepted through validated terminal output salvage on 'limit'; same lift. */
3488
+ salvagedTerminalOutputChildren?: string[];
3479
3489
  } | {
3480
3490
  type: "phase:start";
3481
3491
  phase: string;
@@ -4915,6 +4925,8 @@ interface BudgetAccountView {
4915
4925
  spentUsd: number;
4916
4926
  committedReserveUsd: number;
4917
4927
  finalizeReserveUsd: number;
4928
+ /** The synthesis payload hold (cycle 76); zero when none is committed. */
4929
+ synthesisReserveUsd: number;
4918
4930
  parentScope?: string;
4919
4931
  }
4920
4932
  /**
@@ -5071,6 +5083,21 @@ declare class RunBudget {
5071
5083
  * frozen past the cap, so nothing else can take it.
5072
5084
  */
5073
5085
  releaseFinalizeReserve(scope: string): void;
5086
+ /**
5087
+ * Registers the synthesis payload reserve (the sixth comparison
5088
+ * experiment, cycle 76): absolute dollars held on the orchestrator
5089
+ * account AND the run root, so neither spawn admission nor the
5090
+ * per-turn output clamp lets the coordination prefix eat the money
5091
+ * the synthesis finish needs. Unlike the finalize reserve it is
5092
+ * released BEFORE the synthesis invocation dispatches (the held
5093
+ * money is exactly what that invocation is meant to spend), and it
5094
+ * never joins the severing check: a coordination running against the
5095
+ * hold is clamped smaller, never aborted. Idempotent per account:
5096
+ * re-registering adjusts the root by the delta.
5097
+ */
5098
+ commitSynthesisReserve(scope: string, reserveUsd: number): void;
5099
+ /** The synthesis dispatch consumes its reserve; see commitSynthesisReserve. */
5100
+ releaseSynthesisReserve(scope: string): void;
5074
5101
  /** The reserve is replaced by real spend when the spawn settles. */
5075
5102
  releaseReserve(reserveUsd: number, accountScope?: string): void;
5076
5103
  /** Layer 2: the per-turn guard. A turn that would cross any ceiling in the chain is not dispatched. */
@@ -5682,7 +5709,23 @@ type RunOutcome<R> = {
5682
5709
  * nonnegative integers; the mirror of the `run:end` field. Absent
5683
5710
  * otherwise.
5684
5711
  */
5685
- childStatusCounts?: Record<string, number>; /** Pipeline drops and onError:'null' losses; silent losses are forbidden. */
5712
+ childStatusCounts?: Record<string, number>;
5713
+ /**
5714
+ * Per-child degradation notes, lifted from the same envelope (or
5715
+ * typed error data) when it carries a valid string array (the fifth
5716
+ * experiment, cycle 75): the facts the orchestrator acceptance path
5717
+ * has always emitted beside completion, now on the outcome itself so
5718
+ * a host stops digging error.data on the rejected path. An empty
5719
+ * array is the workflow's claim of zero degradation; absence means no
5720
+ * claim was made.
5721
+ */
5722
+ degradedReasons?: string[]; /** Children accepted by acceptPartialChildren; same lift and posture. */
5723
+ salvagedPartialChildren?: string[];
5724
+ /**
5725
+ * Children accepted through validated terminal output salvage on
5726
+ * 'limit'; same lift and posture.
5727
+ */
5728
+ salvagedTerminalOutputChildren?: string[]; /** Pipeline drops and onError:'null' losses; silent losses are forbidden. */
5686
5729
  dropped: DroppedItem[]; /** Suspensions open at settle time (M2). */
5687
5730
  pending: PendingExternal[];
5688
5731
  usage: Usage;
@@ -7098,6 +7141,21 @@ interface OrchestratorBudgetSpec {
7098
7141
  */
7099
7142
  finalizeReserveUsd?: number;
7100
7143
  /**
7144
+ * The synthesis payload reserve (the sixth comparison experiment,
7145
+ * cycle 76): absolute USD held out of the orchestrator sub account
7146
+ * while the coordination loop runs, released to the synthesis
7147
+ * invocation just before it dispatches. Without it a pricey
7148
+ * coordination can leave the synthesis turns a remainder the budget
7149
+ * clamp shrinks below the contract's minimal accepting payload: the
7150
+ * finish is then cut at the output allowance before any tool call,
7151
+ * the invocation dies at maxTurns, and a validator-bound run fails
7152
+ * closed (the rematch run 1 lost an entire paid run exactly there).
7153
+ * Requires the `synthesis` option (single mode); must stay below the
7154
+ * effective cap. Declaring it changes budget arithmetic only; absent
7155
+ * keeps every account byte identical.
7156
+ */
7157
+ synthesisReserveUsd?: number;
7158
+ /**
7101
7159
  * A positive integer, validated before any journal entry or dispatch:
7102
7160
  * the turn limit of the reserved final wake.
7103
7161
  */
@@ -9112,6 +9170,19 @@ interface PreflightInput {
9112
9170
  * the repair-reserve-unfunded warning stays silent.
9113
9171
  */
9114
9172
  maxRepairs?: number;
9173
+ /**
9174
+ * Mirrors FinishValidationSpec.draftPolicy (the fifth experiment,
9175
+ * cycle 75): declaring it lets the estimator compare the draft
9176
+ * gate's word floor against the contract's own word minimum. The
9177
+ * experiment gated drafts at 3200 words under a 4500 word contract,
9178
+ * so the gate admitted a draft the final validators had to reject
9179
+ * and the synthesis started from an underlength base; the
9180
+ * draft-gate-below-contract warning names exactly that shape.
9181
+ */
9182
+ draftPolicy?: {
9183
+ minWords?: number;
9184
+ requireSections?: string[];
9185
+ };
9115
9186
  };
9116
9187
  }
9117
9188
  /** One linter verdict; `spawn` names the wave entry it is about. */
package/dist/index.js CHANGED
@@ -10740,24 +10740,35 @@ async function runAgent(options) {
10740
10740
  skipped: true
10741
10741
  }));
10742
10742
  };
10743
+ let terminalAdmitted = false;
10743
10744
  for (const [index, call] of calls.entries()) {
10744
- if (limits.maxToolCalls !== void 0 && toolCallsUsed >= limits.maxToolCalls) {
10745
- closeSkippedTail(calls.slice(index), "maxToolCalls");
10746
- return {
10747
- parts,
10748
- limitHit: true,
10749
- limiter: "maxToolCalls",
10750
- skipped: calls.length - index
10751
- };
10752
- }
10753
- if (guard !== void 0 && guard.unitsExhausted()) {
10754
- closeSkippedTail(calls.slice(index), "toolUnits");
10755
- return {
10756
- parts,
10757
- limitHit: true,
10758
- limiter: "toolUnits",
10759
- skipped: calls.length - index
10760
- };
10745
+ const expiredLimiter = limits.maxToolCalls !== void 0 && toolCallsUsed >= limits.maxToolCalls ? "maxToolCalls" : guard !== void 0 && guard.unitsExhausted() ? "toolUnits" : void 0;
10746
+ if (expiredLimiter !== void 0) {
10747
+ const tail = calls.slice(index);
10748
+ const terminalName = options.terminalTool?.name;
10749
+ if (!(terminalName !== void 0 && (terminalAdmitted || tail.some((candidate) => candidate.name === terminalName)))) {
10750
+ closeSkippedTail(tail, expiredLimiter);
10751
+ return {
10752
+ parts,
10753
+ limitHit: true,
10754
+ limiter: expiredLimiter,
10755
+ skipped: calls.length - index
10756
+ };
10757
+ }
10758
+ if (call.name !== terminalName) {
10759
+ parts.push(errorPart(call, {
10760
+ error: "skipped: the tool budget is exhausted; the call was not executed",
10761
+ limiter: expiredLimiter,
10762
+ skipped: true
10763
+ }));
10764
+ continue;
10765
+ }
10766
+ terminalAdmitted = true;
10767
+ events?.emit({
10768
+ type: "log",
10769
+ level: "warn",
10770
+ msg: `terminal tool '${call.name}' admitted at the exhausted tool budget (${expiredLimiter}): terminal calls do not consume the budget`
10771
+ });
10761
10772
  }
10762
10773
  const def = runtime.defs.find((candidate) => candidate.name === call.name);
10763
10774
  events?.emit({
@@ -12130,6 +12141,7 @@ var RunBudget = class {
12130
12141
  spentUsd: 0,
12131
12142
  committedReserveUsd: 0,
12132
12143
  finalizeReserveUsd: 0,
12144
+ synthesisReserveUsd: 0,
12133
12145
  controller: new AbortController()
12134
12146
  };
12135
12147
  if (options.ceilingUsd !== void 0) root.ceilingUsd = options.ceilingUsd;
@@ -12174,6 +12186,7 @@ var RunBudget = class {
12174
12186
  spentUsd: 0,
12175
12187
  committedReserveUsd: 0,
12176
12188
  finalizeReserveUsd: options.finalizeReserveUsd ?? 0,
12189
+ synthesisReserveUsd: 0,
12177
12190
  parentScope,
12178
12191
  controller: new AbortController()
12179
12192
  };
@@ -12225,7 +12238,8 @@ var RunBudget = class {
12225
12238
  scope: account.scope,
12226
12239
  spentUsd: account.spentUsd,
12227
12240
  committedReserveUsd: account.committedReserveUsd,
12228
- finalizeReserveUsd: account.finalizeReserveUsd
12241
+ finalizeReserveUsd: account.finalizeReserveUsd,
12242
+ synthesisReserveUsd: account.synthesisReserveUsd
12229
12243
  };
12230
12244
  if (account.ceilingUsd !== void 0) view.ceilingUsd = account.ceilingUsd;
12231
12245
  if (account.parentScope !== void 0) view.parentScope = account.parentScope;
@@ -12239,7 +12253,7 @@ var RunBudget = class {
12239
12253
  remainderOf(scope) {
12240
12254
  const account = this.accounts.get(scope);
12241
12255
  if (account?.ceilingUsd === void 0) return;
12242
- return Math.max(0, account.ceilingUsd - account.spentUsd - account.committedReserveUsd - account.finalizeReserveUsd);
12256
+ return Math.max(0, account.ceilingUsd - account.spentUsd - account.committedReserveUsd - account.finalizeReserveUsd - account.synthesisReserveUsd);
12243
12257
  }
12244
12258
  /**
12245
12259
  * The tightest allowance headroom on the chain of `scope`: the minimum
@@ -12309,7 +12323,7 @@ var RunBudget = class {
12309
12323
  const chain = this.chainOf(accountScope);
12310
12324
  for (const account of chain) {
12311
12325
  if (account.ceilingUsd === void 0) continue;
12312
- const committed = account.spentUsd + account.committedReserveUsd + account.finalizeReserveUsd;
12326
+ const committed = account.spentUsd + account.committedReserveUsd + account.finalizeReserveUsd + account.synthesisReserveUsd;
12313
12327
  if (committed >= account.ceilingUsd || committed + reserveUsd > account.ceilingUsd) {
12314
12328
  if (account.scope === "run") this.exhaustedInternal = true;
12315
12329
  throw new BudgetExhaustedError(`budget ceiling reached on account '${account.scope}': spent ${account.spentUsd.toFixed(4)} USD plus committed reserves ${(account.committedReserveUsd + account.finalizeReserveUsd).toFixed(4)} USD plus the proposed reserve ${reserveUsd.toFixed(4)} USD does not fit the ceiling ${account.ceilingUsd.toFixed(4)} USD`, { data: {
@@ -12364,6 +12378,34 @@ var RunBudget = class {
12364
12378
  this.root.finalizeReserveUsd = 0;
12365
12379
  this.emitUpdate();
12366
12380
  }
12381
+ /**
12382
+ * Registers the synthesis payload reserve (the sixth comparison
12383
+ * experiment, cycle 76): absolute dollars held on the orchestrator
12384
+ * account AND the run root, so neither spawn admission nor the
12385
+ * per-turn output clamp lets the coordination prefix eat the money
12386
+ * the synthesis finish needs. Unlike the finalize reserve it is
12387
+ * released BEFORE the synthesis invocation dispatches (the held
12388
+ * money is exactly what that invocation is meant to spend), and it
12389
+ * never joins the severing check: a coordination running against the
12390
+ * hold is clamped smaller, never aborted. Idempotent per account:
12391
+ * re-registering adjusts the root by the delta.
12392
+ */
12393
+ commitSynthesisReserve(scope, reserveUsd) {
12394
+ const account = this.accounts.get(scope);
12395
+ if (account === void 0) throw new ConfigError(`unknown budget account '${scope}' for the synthesis reserve`);
12396
+ const previous = account.synthesisReserveUsd;
12397
+ account.synthesisReserveUsd = reserveUsd;
12398
+ if (account.scope !== "run") this.root.synthesisReserveUsd = Math.max(0, this.root.synthesisReserveUsd + reserveUsd - previous);
12399
+ this.emitUpdate();
12400
+ }
12401
+ /** The synthesis dispatch consumes its reserve; see commitSynthesisReserve. */
12402
+ releaseSynthesisReserve(scope) {
12403
+ const account = this.accounts.get(scope);
12404
+ if (account === void 0 || account.synthesisReserveUsd === 0) return;
12405
+ if (account.scope !== "run") this.root.synthesisReserveUsd = Math.max(0, this.root.synthesisReserveUsd - account.synthesisReserveUsd);
12406
+ account.synthesisReserveUsd = 0;
12407
+ this.emitUpdate();
12408
+ }
12367
12409
  /** The reserve is replaced by real spend when the spawn settles. */
12368
12410
  releaseReserve(reserveUsd, accountScope = "run") {
12369
12411
  for (const account of this.chainOf(accountScope)) account.committedReserveUsd = Math.max(0, account.committedReserveUsd - reserveUsd);
@@ -12396,7 +12438,7 @@ var RunBudget = class {
12396
12438
  let remainingUsd;
12397
12439
  for (const account of this.chainOf(accountScope)) {
12398
12440
  if (account.ceilingUsd === void 0) continue;
12399
- const headroom = account.ceilingUsd - account.spentUsd;
12441
+ const headroom = account.ceilingUsd - account.spentUsd - account.synthesisReserveUsd;
12400
12442
  remainingUsd = remainingUsd === void 0 ? headroom : Math.min(remainingUsd, headroom);
12401
12443
  }
12402
12444
  if (remainingUsd === void 0) return;
@@ -16148,6 +16190,11 @@ function validateOrchestrateOptions(opts) {
16148
16190
  if (spec.capUsd !== void 0) requireNonNegativeNumber(spec.capUsd, "orchestrate budget.capUsd");
16149
16191
  if (spec.capFraction !== void 0) requireFraction(spec.capFraction, "orchestrate budget.capFraction");
16150
16192
  if (spec.finalizeReserveUsd !== void 0) requireNonNegativeNumber(spec.finalizeReserveUsd, "orchestrate budget.finalizeReserveUsd");
16193
+ if (spec.synthesisReserveUsd !== void 0) {
16194
+ requireNonNegativeNumber(spec.synthesisReserveUsd, "orchestrate budget.synthesisReserveUsd");
16195
+ if (opts.synthesis === void 0) throw new ConfigError("orchestrate budget.synthesisReserveUsd requires the synthesis option: the reserve holds sub account money for the separate synthesis invocation");
16196
+ if (opts.synthesis.mode === "incremental") throw new ConfigError("orchestrate budget.synthesisReserveUsd is incompatible with synthesis.mode 'incremental': the reserve protects the single post-fan-in invocation");
16197
+ }
16151
16198
  if (spec.finalizeTurns !== void 0) requirePositiveInteger(spec.finalizeTurns, "orchestrate budget.finalizeTurns");
16152
16199
  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)}`);
16153
16200
  }
@@ -16297,12 +16344,14 @@ function makeOrchestratorWorkflow(goal, opts) {
16297
16344
  liveValue: internals.pricingVersion ?? "unpriced"
16298
16345
  }] : []);
16299
16346
  orchestratorAccount = callingState.scope === "" ? "orchestrator" : `${callingState.scope}/orchestrator`;
16347
+ if (spec?.synthesisReserveUsd !== void 0 && frozen.capUsd <= spec.synthesisReserveUsd) throw new OrchestratorCapConfigError(`effectiveCap ${frozen.capUsd.toFixed(4)} USD is not above the synthesis reserve ${spec.synthesisReserveUsd.toFixed(4)} USD`);
16300
16348
  internals.budget.openAccount(orchestratorAccount, {
16301
16349
  parentScope: callingState.budgetScope ?? "run",
16302
16350
  ceilingUsd: frozen.capUsd,
16303
16351
  kind: "orchestrator-cap"
16304
16352
  });
16305
16353
  if (extension !== void 0) internals.budget.commitFinalizeReserve(orchestratorAccount, frozen.finalizeReserveUsd);
16354
+ if (spec?.synthesisReserveUsd !== void 0 && spec.synthesisReserveUsd > 0) internals.budget.commitSynthesisReserve(orchestratorAccount, spec.synthesisReserveUsd);
16306
16355
  capState = {
16307
16356
  effectiveCapUsd: frozen.capUsd,
16308
16357
  finalizeReserveUsd: frozen.finalizeReserveUsd,
@@ -16324,6 +16373,7 @@ function makeOrchestratorWorkflow(goal, opts) {
16324
16373
  level: "warn",
16325
16374
  msg: `orchestrator budget.capUsd ${spec.capUsd.toFixed(4)} USD is bounded to ${effectiveCapUsd.toFixed(4)} USD by the default capFraction 0.2 of the run ceiling (effectiveCap = min(capUsd, capFraction * ceiling)); pass capFraction: 1.0 to make capUsd the sole bound`
16326
16375
  }, callingState.spanId);
16376
+ if (spec?.synthesisReserveUsd !== void 0 && effectiveCapUsd <= spec.synthesisReserveUsd) throw new OrchestratorCapConfigError(`effectiveCap ${effectiveCapUsd.toFixed(4)} USD is not above the synthesis reserve ${spec.synthesisReserveUsd.toFixed(4)} USD: the coordination loop would have no money at all`);
16327
16377
  orchestratorAccount = callingState.scope === "" ? "orchestrator" : `${callingState.scope}/orchestrator`;
16328
16378
  internals.budget.openAccount(orchestratorAccount, {
16329
16379
  parentScope: callingState.budgetScope ?? "run",
@@ -16331,6 +16381,7 @@ function makeOrchestratorWorkflow(goal, opts) {
16331
16381
  kind: "orchestrator-cap"
16332
16382
  });
16333
16383
  if (extension !== void 0) internals.budget.commitFinalizeReserve(orchestratorAccount, finalizeReserveUsd);
16384
+ if (spec?.synthesisReserveUsd !== void 0 && spec.synthesisReserveUsd > 0) internals.budget.commitSynthesisReserve(orchestratorAccount, spec.synthesisReserveUsd);
16334
16385
  capState = {
16335
16386
  effectiveCapUsd,
16336
16387
  finalizeReserveUsd,
@@ -17257,7 +17308,7 @@ function makeOrchestratorWorkflow(goal, opts) {
17257
17308
  role: "orchestrate",
17258
17309
  result: "full",
17259
17310
  tools,
17260
- ...capState === void 0 ? {} : { estCost: orchestratorAdmissionEstCostUsd(capState.effectiveCapUsd, orchestratorAccount === void 0 ? 0 : internals.budget.accountView(orchestratorAccount)?.finalizeReserveUsd ?? 0) },
17311
+ ...capState === void 0 ? {} : { estCost: orchestratorAdmissionEstCostUsd(capState.effectiveCapUsd, orchestratorAccount === void 0 ? 0 : (internals.budget.accountView(orchestratorAccount)?.finalizeReserveUsd ?? 0) + (internals.budget.accountView(orchestratorAccount)?.synthesisReserveUsd ?? 0)) },
17261
17312
  ...opts?.model === void 0 ? {} : { model: opts.model },
17262
17313
  ...opts?.limits === void 0 ? {} : { limits: opts.limits },
17263
17314
  [kOnRunning]: (seq) => {
@@ -17573,7 +17624,10 @@ function makeOrchestratorWorkflow(goal, opts) {
17573
17624
  }
17574
17625
  }, callingState.spanId);
17575
17626
  const synthesisState = { ...callingState };
17576
- if (orchestratorAccount !== void 0) synthesisState.budgetScope = orchestratorAccount;
17627
+ if (orchestratorAccount !== void 0) {
17628
+ synthesisState.budgetScope = orchestratorAccount;
17629
+ internals.budget.releaseSynthesisReserve(orchestratorAccount);
17630
+ }
17577
17631
  const synthesisBreak = validationSpec === void 0 ? void 0 : validationAbort.signal;
17578
17632
  if (synthesisBreak !== void 0) synthesisState.signal = callingState.signal === void 0 ? synthesisBreak : AbortSignal.any([callingState.signal, synthesisBreak]);
17579
17633
  const synthesisOpts = {
@@ -18226,7 +18280,7 @@ function preflightEstimate(input) {
18226
18280
  turns: projectedProviderTurnsOf(orchLimits, overallExecutedCeiling(orchLimits, toolCeilingsOf(orchLimits))) + coordinationRepairReserve,
18227
18281
  count: 1
18228
18282
  });
18229
- if (effectiveCapUsd !== void 0) orchestratorReserveUsd = Math.max(0, orchestratorAdmissionEstCostUsd(effectiveCapUsd, reservedForFinalizationUsd));
18283
+ if (effectiveCapUsd !== void 0) orchestratorReserveUsd = Math.max(0, orchestratorAdmissionEstCostUsd(effectiveCapUsd, reservedForFinalizationUsd + (input.orchestrator.budget?.synthesisReserveUsd ?? 0)));
18230
18284
  else if (pricing === void 0) orchestratorReserveUsd = 0;
18231
18285
  else orchestratorReserveUsd = admissionReserveUsd({
18232
18286
  ...input.orchestrator.estInputTokens === void 0 ? {} : { inputTokens: input.orchestrator.estInputTokens },
@@ -18254,6 +18308,16 @@ function preflightEstimate(input) {
18254
18308
  spawn: "synthesis"
18255
18309
  });
18256
18310
  }
18311
+ {
18312
+ const synthesisToolCap = synthesis.limits?.maxToolCalls;
18313
+ const expectedReads = input.orchestrator?.maxSpawns ?? 1;
18314
+ if (synthesis.exposeChildResultTools === true && synthesisToolCap !== void 0 && synthesisToolCap < expectedReads) say({
18315
+ severity: "warning",
18316
+ code: "synthesis-terminal-tool-headroom",
18317
+ message: `synthesis.limits.maxToolCalls ${String(synthesisToolCap)} cannot cover one get_child_result read per child (${input.orchestrator?.maxSpawns === void 0 ? "at least 1 read" : `maxSpawns ${String(expectedReads)}`}): the reads exhaust the tool budget and the synthesis loses evidence access (the terminal finish is admitted budget free and needs no slot); raise the cap to at least the child count plus paging margin, or use context 'full' without the read tools`,
18318
+ spawn: "synthesis"
18319
+ });
18320
+ }
18257
18321
  const servedBy = resolveServing(synthesis.model) ?? resolveServing(defaults.routing?.synthesize);
18258
18322
  if (servedBy === void 0) say({
18259
18323
  severity: "error",
@@ -18272,6 +18336,21 @@ function preflightEstimate(input) {
18272
18336
  spawn: "synthesis"
18273
18337
  });
18274
18338
  contractFeasibilityFindings(outputBound, "synthesis", servedBy);
18339
+ {
18340
+ const contract = input.finishValidation?.contract;
18341
+ const pricing = pricingOf(servedBy);
18342
+ if (contract !== void 0 && pricing !== void 0 && pricing.outputUsdPerMTok > 0) {
18343
+ const minTokens = Math.ceil(JSON.stringify({ result: contract.goldenAccept.text }).length / 4);
18344
+ const payloadUsd = minTokens / 1e6 * pricing.outputUsdPerMTok;
18345
+ const declared = input.orchestrator?.budget?.synthesisReserveUsd;
18346
+ if (declared === void 0 || declared < payloadUsd) say({
18347
+ severity: "warning",
18348
+ code: "synthesis-reserve-unfunded",
18349
+ message: `the contract's minimal accepting payload is about ${String(minTokens)} output tokens, about ${payloadUsd.toFixed(4)} USD at the output rate of '${servedBy}', and the orchestrator sub account holds ${declared === void 0 ? "no synthesis reserve" : `only ${declared.toFixed(4)} USD`} for it: a pricey coordination prefix can leave the synthesis turns a remainder the budget clamp shrinks below the payload, cutting the finish before any tool call; declare budget.synthesisReserveUsd at or above the payload price`,
18350
+ spawn: "synthesis"
18351
+ });
18352
+ }
18353
+ }
18275
18354
  const projected = projectedProviderTurnsOf(merged, overallExecutedCeiling(merged, toolCeilingsOf(merged))) + finishRepairReserve;
18276
18355
  if (orchestratorEcho !== void 0) orchestratorEcho.synthesis = {
18277
18356
  projectedProviderTurns: projected,
@@ -18294,10 +18373,12 @@ function preflightEstimate(input) {
18294
18373
  let committed = 0;
18295
18374
  let spawned = 0;
18296
18375
  let children = 0;
18297
- const admitAgainstRoot = (reserveUsd) => {
18376
+ const admitAgainstRoot = (reserveUsd, strictAtFill = false) => {
18298
18377
  if (ceilingUsd === void 0) return true;
18299
18378
  const held = committed + reservedForFinalizationUsd;
18300
- return !(held >= ceilingUsd || held + reserveUsd > ceilingUsd);
18379
+ if (held >= ceilingUsd) return false;
18380
+ const fill = held + reserveUsd;
18381
+ return strictAtFill ? fill < ceilingUsd : fill <= ceilingUsd;
18301
18382
  };
18302
18383
  if (input.orchestrator !== void 0) {
18303
18384
  const reserveUsd = orchestratorReserveUsd ?? flatReserveUsd;
@@ -18329,9 +18410,9 @@ function preflightEstimate(input) {
18329
18410
  if (orchestrateWave && ceilingUsd !== void 0) {
18330
18411
  const remainder = ceilingUsd - committed - reservedForFinalizationUsd;
18331
18412
  const projection = dispatchProjectionReserveUsd(gate, flatReserveUsd);
18332
- if (remainder <= 0 || remainder < projection) deniedBy = "budget";
18413
+ if (remainder <= 0 || remainder <= projection) deniedBy = "budget";
18333
18414
  }
18334
- if (deniedBy === void 0 && !admitAgainstRoot(reserveUsd)) deniedBy = "budget";
18415
+ if (deniedBy === void 0 && !admitAgainstRoot(reserveUsd, orchestrateWave)) deniedBy = "budget";
18335
18416
  }
18336
18417
  wave.push({
18337
18418
  label,
@@ -18505,6 +18586,16 @@ function preflightEstimate(input) {
18505
18586
  message: `finishValidation grants up to ${String(grantedRepairs)} repair exchange(s) but declares no repairTurnReserve: a rejected finish burns an ordinary turn and a window at maxTurns settles 'limit' with the repair unspent; set finishValidation.repairTurnReserve to fund the repair exchanges`
18506
18587
  });
18507
18588
  }
18589
+ {
18590
+ const draftMinWords = fv.draftPolicy?.minWords;
18591
+ if (draftMinWords !== void 0) requireNonNegativeInteger(draftMinWords, "preflight finishValidation.draftPolicy.minWords");
18592
+ const contractMinWords = fv.contract?.manifest.words?.min;
18593
+ if (draftMinWords !== void 0 && contractMinWords !== void 0 && draftMinWords < contractMinWords) findings.push({
18594
+ severity: "warning",
18595
+ code: "draft-gate-below-contract",
18596
+ message: `draftPolicy.minWords ${String(draftMinWords)} sits below the contract word minimum ${String(contractMinWords)}: the draft gate admits drafts the final validators must reject, so the synthesis starts from an underlength base; raise draftPolicy.minWords to the contract minimum or above`
18597
+ });
18598
+ }
18508
18599
  if (fv.contract !== void 0) {
18509
18600
  for (const contractValidator of fv.contract.validators) if (!names.has(contractValidator.name)) findings.push({
18510
18601
  severity: "error",
@@ -19325,15 +19416,23 @@ function liftRunCompletion(candidate) {
19325
19416
  if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) return;
19326
19417
  const completion = candidate.completion;
19327
19418
  if (completion !== "complete" && completion !== "partial" && completion !== "rejected") return;
19419
+ const lifted = { completion };
19328
19420
  const counts = candidate.childStatusCounts;
19329
19421
  if (typeof counts === "object" && counts !== null && !Array.isArray(counts)) {
19330
19422
  const entries = Object.entries(counts);
19331
- if (entries.every(([, value]) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0)) return {
19332
- completion,
19333
- childStatusCounts: Object.fromEntries(entries)
19334
- };
19423
+ if (entries.every(([, value]) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0)) lifted.childStatusCounts = Object.fromEntries(entries);
19335
19424
  }
19336
- return { completion };
19425
+ const liftStringList = (key) => {
19426
+ const value = candidate[key];
19427
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string") ? [...value] : void 0;
19428
+ };
19429
+ const degradedReasons = liftStringList("degradedReasons");
19430
+ if (degradedReasons !== void 0) lifted.degradedReasons = degradedReasons;
19431
+ const salvagedPartialChildren = liftStringList("salvagedPartialChildren");
19432
+ if (salvagedPartialChildren !== void 0) lifted.salvagedPartialChildren = salvagedPartialChildren;
19433
+ const salvagedTerminalOutputChildren = liftStringList("salvagedTerminalOutputChildren");
19434
+ if (salvagedTerminalOutputChildren !== void 0) lifted.salvagedTerminalOutputChildren = salvagedTerminalOutputChildren;
19435
+ return lifted;
19337
19436
  }
19338
19437
  /**
19339
19438
  * sha256 hex over the JCS canonical serialization of a run's args: the
@@ -19788,6 +19887,9 @@ function createEngine(options) {
19788
19887
  if (lifted !== void 0) {
19789
19888
  outcome.completion = lifted.completion;
19790
19889
  if (lifted.childStatusCounts !== void 0) outcome.childStatusCounts = lifted.childStatusCounts;
19890
+ if (lifted.degradedReasons !== void 0) outcome.degradedReasons = lifted.degradedReasons;
19891
+ if (lifted.salvagedPartialChildren !== void 0) outcome.salvagedPartialChildren = lifted.salvagedPartialChildren;
19892
+ if (lifted.salvagedTerminalOutputChildren !== void 0) outcome.salvagedTerminalOutputChildren = lifted.salvagedTerminalOutputChildren;
19791
19893
  }
19792
19894
  let settlementFailure;
19793
19895
  if (resumeCtx?.strict !== true) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/core",
3
- "version": "1.78.0",
3
+ "version": "1.80.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",