@rulvar/core 1.78.0 → 1.79.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;
@@ -5682,7 +5692,23 @@ type RunOutcome<R> = {
5682
5692
  * nonnegative integers; the mirror of the `run:end` field. Absent
5683
5693
  * otherwise.
5684
5694
  */
5685
- childStatusCounts?: Record<string, number>; /** Pipeline drops and onError:'null' losses; silent losses are forbidden. */
5695
+ childStatusCounts?: Record<string, number>;
5696
+ /**
5697
+ * Per-child degradation notes, lifted from the same envelope (or
5698
+ * typed error data) when it carries a valid string array (the fifth
5699
+ * experiment, cycle 75): the facts the orchestrator acceptance path
5700
+ * has always emitted beside completion, now on the outcome itself so
5701
+ * a host stops digging error.data on the rejected path. An empty
5702
+ * array is the workflow's claim of zero degradation; absence means no
5703
+ * claim was made.
5704
+ */
5705
+ degradedReasons?: string[]; /** Children accepted by acceptPartialChildren; same lift and posture. */
5706
+ salvagedPartialChildren?: string[];
5707
+ /**
5708
+ * Children accepted through validated terminal output salvage on
5709
+ * 'limit'; same lift and posture.
5710
+ */
5711
+ salvagedTerminalOutputChildren?: string[]; /** Pipeline drops and onError:'null' losses; silent losses are forbidden. */
5686
5712
  dropped: DroppedItem[]; /** Suspensions open at settle time (M2). */
5687
5713
  pending: PendingExternal[];
5688
5714
  usage: Usage;
@@ -9112,6 +9138,19 @@ interface PreflightInput {
9112
9138
  * the repair-reserve-unfunded warning stays silent.
9113
9139
  */
9114
9140
  maxRepairs?: number;
9141
+ /**
9142
+ * Mirrors FinishValidationSpec.draftPolicy (the fifth experiment,
9143
+ * cycle 75): declaring it lets the estimator compare the draft
9144
+ * gate's word floor against the contract's own word minimum. The
9145
+ * experiment gated drafts at 3200 words under a 4500 word contract,
9146
+ * so the gate admitted a draft the final validators had to reject
9147
+ * and the synthesis started from an underlength base; the
9148
+ * draft-gate-below-contract warning names exactly that shape.
9149
+ */
9150
+ draftPolicy?: {
9151
+ minWords?: number;
9152
+ requireSections?: string[];
9153
+ };
9115
9154
  };
9116
9155
  }
9117
9156
  /** 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({
@@ -18254,6 +18265,16 @@ function preflightEstimate(input) {
18254
18265
  spawn: "synthesis"
18255
18266
  });
18256
18267
  }
18268
+ {
18269
+ const synthesisToolCap = synthesis.limits?.maxToolCalls;
18270
+ const expectedReads = input.orchestrator?.maxSpawns ?? 1;
18271
+ if (synthesis.exposeChildResultTools === true && synthesisToolCap !== void 0 && synthesisToolCap < expectedReads) say({
18272
+ severity: "warning",
18273
+ code: "synthesis-terminal-tool-headroom",
18274
+ 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`,
18275
+ spawn: "synthesis"
18276
+ });
18277
+ }
18257
18278
  const servedBy = resolveServing(synthesis.model) ?? resolveServing(defaults.routing?.synthesize);
18258
18279
  if (servedBy === void 0) say({
18259
18280
  severity: "error",
@@ -18505,6 +18526,16 @@ function preflightEstimate(input) {
18505
18526
  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
18527
  });
18507
18528
  }
18529
+ {
18530
+ const draftMinWords = fv.draftPolicy?.minWords;
18531
+ if (draftMinWords !== void 0) requireNonNegativeInteger(draftMinWords, "preflight finishValidation.draftPolicy.minWords");
18532
+ const contractMinWords = fv.contract?.manifest.words?.min;
18533
+ if (draftMinWords !== void 0 && contractMinWords !== void 0 && draftMinWords < contractMinWords) findings.push({
18534
+ severity: "warning",
18535
+ code: "draft-gate-below-contract",
18536
+ 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`
18537
+ });
18538
+ }
18508
18539
  if (fv.contract !== void 0) {
18509
18540
  for (const contractValidator of fv.contract.validators) if (!names.has(contractValidator.name)) findings.push({
18510
18541
  severity: "error",
@@ -19325,15 +19356,23 @@ function liftRunCompletion(candidate) {
19325
19356
  if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) return;
19326
19357
  const completion = candidate.completion;
19327
19358
  if (completion !== "complete" && completion !== "partial" && completion !== "rejected") return;
19359
+ const lifted = { completion };
19328
19360
  const counts = candidate.childStatusCounts;
19329
19361
  if (typeof counts === "object" && counts !== null && !Array.isArray(counts)) {
19330
19362
  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
- };
19363
+ if (entries.every(([, value]) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0)) lifted.childStatusCounts = Object.fromEntries(entries);
19335
19364
  }
19336
- return { completion };
19365
+ const liftStringList = (key) => {
19366
+ const value = candidate[key];
19367
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string") ? [...value] : void 0;
19368
+ };
19369
+ const degradedReasons = liftStringList("degradedReasons");
19370
+ if (degradedReasons !== void 0) lifted.degradedReasons = degradedReasons;
19371
+ const salvagedPartialChildren = liftStringList("salvagedPartialChildren");
19372
+ if (salvagedPartialChildren !== void 0) lifted.salvagedPartialChildren = salvagedPartialChildren;
19373
+ const salvagedTerminalOutputChildren = liftStringList("salvagedTerminalOutputChildren");
19374
+ if (salvagedTerminalOutputChildren !== void 0) lifted.salvagedTerminalOutputChildren = salvagedTerminalOutputChildren;
19375
+ return lifted;
19337
19376
  }
19338
19377
  /**
19339
19378
  * sha256 hex over the JCS canonical serialization of a run's args: the
@@ -19788,6 +19827,9 @@ function createEngine(options) {
19788
19827
  if (lifted !== void 0) {
19789
19828
  outcome.completion = lifted.completion;
19790
19829
  if (lifted.childStatusCounts !== void 0) outcome.childStatusCounts = lifted.childStatusCounts;
19830
+ if (lifted.degradedReasons !== void 0) outcome.degradedReasons = lifted.degradedReasons;
19831
+ if (lifted.salvagedPartialChildren !== void 0) outcome.salvagedPartialChildren = lifted.salvagedPartialChildren;
19832
+ if (lifted.salvagedTerminalOutputChildren !== void 0) outcome.salvagedTerminalOutputChildren = lifted.salvagedTerminalOutputChildren;
19791
19833
  }
19792
19834
  let settlementFailure;
19793
19835
  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.79.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",