@rulvar/core 1.64.0 → 1.66.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
@@ -5577,7 +5577,31 @@ interface CostReport {
5577
5577
  type RunOutcome<R> = {
5578
5578
  status: "ok" | "error" | "cancelled" | "exhausted" | "suspended";
5579
5579
  value?: R;
5580
- error?: WireError; /** Pipeline drops and onError:'null' losses; silent losses are forbidden. */
5580
+ error?: WireError;
5581
+ /**
5582
+ * The semantic completion lift, mirrored from `run:end` (RV-207 tail;
5583
+ * the 1.65.0 experiment review, P0.5): present when the workflow
5584
+ * reported semantic completion through the completion envelope
5585
+ * contract, an `ok`/`exhausted` run whose result value is an object
5586
+ * carrying a valid `completion` literal, or an `error` run whose typed
5587
+ * error data carries one (the orchestrator acceptance path emits
5588
+ * both). Transport status says whether the run ran; completion says
5589
+ * whether the work is COMPLETE: an accepted degraded run is `status:
5590
+ * 'ok'` with `completion: 'partial'`. The engine computes the lift
5591
+ * ONCE and both surfaces spread the same object, so the outcome and
5592
+ * the event can never disagree; a host reads completeness here
5593
+ * without parsing workflow-specific value shapes on the accepted path
5594
+ * or digging typed error data on the rejected one. Absent when the
5595
+ * workflow makes no completion claim.
5596
+ */
5597
+ completion?: "complete" | "partial" | "rejected";
5598
+ /**
5599
+ * Settled child statuses by status name, lifted from the same
5600
+ * envelope (or typed error data) when it carries a valid record of
5601
+ * nonnegative integers; the mirror of the `run:end` field. Absent
5602
+ * otherwise.
5603
+ */
5604
+ childStatusCounts?: Record<string, number>; /** Pipeline drops and onError:'null' losses; silent losses are forbidden. */
5581
5605
  dropped: DroppedItem[]; /** Suspensions open at settle time (M2). */
5582
5606
  pending: PendingExternal[];
5583
5607
  usage: Usage;
@@ -6169,6 +6193,16 @@ interface FinishValidationChild {
6169
6193
  * same serialization the child result evidence tools page.
6170
6194
  */
6171
6195
  readonly text: string;
6196
+ /**
6197
+ * Present and true ONLY when acceptance.acceptValidatedTerminalOutputOnLimit
6198
+ * is configured and this child settled 'limit' CARRYING a terminal
6199
+ * output (the finalization reserve summary that, for a schema child,
6200
+ * already validated against the declared output schema). Acceptance
6201
+ * will count such a child as a success, so evidencePreservedValidator
6202
+ * treats its text as part of the cited evidence pool. Absent in every
6203
+ * other configuration, keeping the old pool exactly.
6204
+ */
6205
+ readonly salvageableOutput?: boolean;
6172
6206
  }
6173
6207
  /** What a {@link FinishValidator} judges. */
6174
6208
  interface FinishValidationInput {
@@ -6790,6 +6824,29 @@ interface OrchestrateAcceptance {
6790
6824
  * decision, so a resume rolls the same verdict forward.
6791
6825
  */
6792
6826
  acceptPartialChildren?: boolean;
6827
+ /**
6828
+ * The terminal-output salvage switch (the 1.64.0 experiment review,
6829
+ * P0.4 + P1.1; default false). When true, a child that settled
6830
+ * 'limit' CARRYING a terminal output counts as a successful child for
6831
+ * the policy, exactly like acceptPartialChildren counts a
6832
+ * partial-bearing one. A limit terminal carries an output ONLY when
6833
+ * the child's limits.finalizationReserve summary turn produced one
6834
+ * AND, for a schema child, that summary already validated against the
6835
+ * declared output schema (an invalid summary keeps output null and is
6836
+ * never salvaged), so validation runs BEFORE acceptance by
6837
+ * construction. The verdict then reports completion 'partial' (never
6838
+ * 'complete'), lists the children in `salvagedTerminalOutputChildren`
6839
+ * on the result envelope, and keeps a per-child note in
6840
+ * degradedReasons. A child carrying BOTH an output and a progress
6841
+ * partial salvages by its output. The child's digest and
6842
+ * get_child_result surface the output unconditionally (paid, journaled
6843
+ * evidence is never withheld); this option gates only the acceptance
6844
+ * fold, the evidencePreservedValidator cited pool (via
6845
+ * FinishValidationChild.salvageableOutput), and the coordination
6846
+ * prompt line. The whole fold is journaled in the single acceptance
6847
+ * decision, so a resume rolls the same verdict forward.
6848
+ */
6849
+ acceptValidatedTerminalOutputOnLimit?: boolean;
6793
6850
  }
6794
6851
  /** How many rejected finishes are repaired by default: the plan's repair once. */
6795
6852
  declare const DEFAULT_FINISH_MAX_REPAIRS = 1;
package/dist/index.js CHANGED
@@ -14689,6 +14689,10 @@ function summarizeOutput(result) {
14689
14689
  if (result.status === "ok") raw = typeof result.output === "string" ? result.output : JSON.stringify(result.output ?? null);
14690
14690
  else {
14691
14691
  raw = result.errorMessage ?? `terminal status ${result.status}`;
14692
+ if (result.status === "limit" && result.output !== null && result.output !== void 0) {
14693
+ const final = typeof result.output === "string" ? result.output : JSON.stringify(result.output);
14694
+ raw = `${raw}; final: ${final}`;
14695
+ }
14692
14696
  if (result.partial !== void 0) raw = `${raw}; partial: ${JSON.stringify(result.partial)}`;
14693
14697
  }
14694
14698
  return truncateToBudget(raw, 400);
@@ -15075,9 +15079,11 @@ function pageOf(content, rawOffset, rawMaxChars) {
15075
15079
  function serializeChildOutput(result) {
15076
15080
  if (result.status !== "ok") {
15077
15081
  const base = result.errorMessage ?? `terminal status ${result.status}`;
15078
- if (result.partial !== void 0) return JSON.stringify({
15082
+ const limitOutput = result.status === "limit" && result.output !== null && result.output !== void 0;
15083
+ if (limitOutput || result.partial !== void 0) return JSON.stringify({
15079
15084
  error: base,
15080
- partial: result.partial
15085
+ ...limitOutput ? { output: result.output } : {},
15086
+ ...result.partial === void 0 ? {} : { partial: result.partial }
15081
15087
  });
15082
15088
  return base;
15083
15089
  }
@@ -15104,6 +15110,8 @@ function validateOrchestrateOptions(opts) {
15104
15110
  if (policy !== "all-ok") requirePositiveInteger(minSuccessful, "orchestrate acceptance.childPolicy.minSuccessful");
15105
15111
  const acceptPartial = opts.acceptance.acceptPartialChildren;
15106
15112
  if (acceptPartial !== void 0 && typeof acceptPartial !== "boolean") throw new ConfigError(`orchestrate acceptance.acceptPartialChildren must be a boolean; got ${typeof acceptPartial}`);
15113
+ const acceptOutput = opts.acceptance.acceptValidatedTerminalOutputOnLimit;
15114
+ if (acceptOutput !== void 0 && typeof acceptOutput !== "boolean") throw new ConfigError("orchestrate acceptance.acceptValidatedTerminalOutputOnLimit must be a boolean; got " + typeof acceptOutput);
15107
15115
  }
15108
15116
  if (opts.finishValidation !== void 0) {
15109
15117
  const fv = opts.finishValidation;
@@ -15174,8 +15182,10 @@ function finishValidationPromptLines(spec) {
15174
15182
  * keeps byte-identical coordination prompts.
15175
15183
  */
15176
15184
  function acceptancePromptLines(acceptance) {
15177
- if (acceptance?.acceptPartialChildren !== true) return [];
15178
- return ["Partial salvage is on: a child that ends at its limit AFTER recording progress with report_progress counts as a partial success for acceptance; its digest carries the partial and get_child_result (when enabled) pages the full report. When the gap matters, respawn a NARROWED child carrying the partial instead of repeating the task."];
15185
+ const lines = [];
15186
+ if (acceptance?.acceptPartialChildren === true) lines.push("Partial salvage is on: a child that ends at its limit AFTER recording progress with report_progress counts as a partial success for acceptance; its digest carries the partial and get_child_result (when enabled) pages the full report. When the gap matters, respawn a NARROWED child carrying the partial instead of repeating the task.");
15187
+ if (acceptance?.acceptValidatedTerminalOutputOnLimit === true) lines.push("Terminal-output salvage is on: a child that ends at its limit WITH a final answer (its finalization reserve summary, already validated against the declared output schema) counts as a successful child for acceptance; its digest carries it after the 'final:' marker and get_child_result (when enabled) pages it in full.");
15188
+ return lines;
15179
15189
  }
15180
15190
  /**
15181
15191
  * Resolves per-spawn dispatch options against the engine registries
@@ -16080,11 +16090,13 @@ function makeOrchestratorWorkflow(goal, opts) {
16080
16090
  let decision = known.find((candidate) => candidate.callId === call.id);
16081
16091
  if (decision === void 0) {
16082
16092
  const result = call.result ?? null;
16093
+ const salvageOutputOn = opts?.acceptance?.acceptValidatedTerminalOutputOnLimit === true;
16083
16094
  const children = [...records.values()].sort((a, b) => a.spawnOrdinal - b.spawnOrdinal).map((record) => ({
16084
16095
  handle: record.handle,
16085
16096
  nodeId: record.nodeId,
16086
16097
  status: record.settled?.status ?? "running",
16087
- text: record.settled === void 0 ? "" : serializeChildOutput(record.settled)
16098
+ text: record.settled === void 0 ? "" : serializeChildOutput(record.settled),
16099
+ ...salvageOutputOn && record.settled?.status === "limit" && record.settled.output !== null && record.settled.output !== void 0 ? { salvageableOutput: true } : {}
16088
16100
  }));
16089
16101
  const input = {
16090
16102
  result,
@@ -16542,13 +16554,20 @@ function makeOrchestratorWorkflow(goal, opts) {
16542
16554
  const childStatusCounts = {};
16543
16555
  const degradedReasons = [];
16544
16556
  const salvaged = [];
16557
+ const salvagedOutput = [];
16545
16558
  let hardDegraded = 0;
16546
16559
  const acceptPartial = opts.acceptance.acceptPartialChildren === true;
16560
+ const acceptOutput = opts.acceptance.acceptValidatedTerminalOutputOnLimit === true;
16547
16561
  const sortedRecords = [...records.values()].sort((a, b) => a.spawnOrdinal - b.spawnOrdinal);
16548
16562
  for (const record of sortedRecords) {
16549
16563
  const status = record.settled?.status ?? "running";
16550
16564
  childStatusCounts[status] = (childStatusCounts[status] ?? 0) + 1;
16551
16565
  if (status === "ok") continue;
16566
+ if (acceptOutput && status === "limit" && record.settled?.output !== null && record.settled?.output !== void 0) {
16567
+ salvagedOutput.push(record.nodeId);
16568
+ degradedReasons.push(`child ${record.nodeId} accepted with its validated terminal output (settled 'limit' after the finalization reserve summary)`);
16569
+ continue;
16570
+ }
16552
16571
  if (acceptPartial && status === "limit" && record.settled?.partial !== void 0) {
16553
16572
  salvaged.push(record.nodeId);
16554
16573
  degradedReasons.push(`child ${record.nodeId} accepted as partial (settled 'limit' with a structured partial)`);
@@ -16558,7 +16577,7 @@ function makeOrchestratorWorkflow(goal, opts) {
16558
16577
  degradedReasons.push(status === "running" ? `child ${record.nodeId} was still running when finish validated` : `child ${record.nodeId} settled '${status}'`);
16559
16578
  }
16560
16579
  const childPolicy = opts.acceptance.childPolicy;
16561
- const accepted = childPolicy === "all-ok" ? hardDegraded === 0 : (childStatusCounts.ok ?? 0) + salvaged.length >= childPolicy.minSuccessful;
16580
+ const accepted = childPolicy === "all-ok" ? hardDegraded === 0 : (childStatusCounts.ok ?? 0) + salvaged.length + salvagedOutput.length >= childPolicy.minSuccessful;
16562
16581
  decision = {
16563
16582
  decisionType: "orchestrator_acceptance",
16564
16583
  verdict: accepted ? "accepted" : "rejected",
@@ -16566,7 +16585,8 @@ function makeOrchestratorWorkflow(goal, opts) {
16566
16585
  childPolicy,
16567
16586
  childStatusCounts,
16568
16587
  degradedReasons,
16569
- ...salvaged.length === 0 ? {} : { salvagedPartialChildren: salvaged }
16588
+ ...salvaged.length === 0 ? {} : { salvagedPartialChildren: salvaged },
16589
+ ...salvagedOutput.length === 0 ? {} : { salvagedTerminalOutputChildren: salvagedOutput }
16570
16590
  };
16571
16591
  await internals.replayer.appendSinglePhase({
16572
16592
  scope: callingState.scope,
@@ -16586,7 +16606,8 @@ function makeOrchestratorWorkflow(goal, opts) {
16586
16606
  childPolicy: decision.childPolicy,
16587
16607
  childStatusCounts: decision.childStatusCounts,
16588
16608
  degradedReasons: decision.degradedReasons,
16589
- ...decision.salvagedPartialChildren === void 0 ? {} : { salvagedPartialChildren: decision.salvagedPartialChildren }
16609
+ ...decision.salvagedPartialChildren === void 0 ? {} : { salvagedPartialChildren: decision.salvagedPartialChildren },
16610
+ ...decision.salvagedTerminalOutputChildren === void 0 ? {} : { salvagedTerminalOutputChildren: decision.salvagedTerminalOutputChildren }
16590
16611
  } });
16591
16612
  }
16592
16613
  return {
@@ -16594,7 +16615,8 @@ function makeOrchestratorWorkflow(goal, opts) {
16594
16615
  completion: decision.completion,
16595
16616
  childStatusCounts: decision.childStatusCounts,
16596
16617
  degradedReasons: decision.degradedReasons,
16597
- ...decision.salvagedPartialChildren === void 0 ? {} : { salvagedPartialChildren: decision.salvagedPartialChildren }
16618
+ ...decision.salvagedPartialChildren === void 0 ? {} : { salvagedPartialChildren: decision.salvagedPartialChildren },
16619
+ ...decision.salvagedTerminalOutputChildren === void 0 ? {} : { salvagedTerminalOutputChildren: decision.salvagedTerminalOutputChildren }
16598
16620
  };
16599
16621
  });
16600
16622
  }
@@ -17315,7 +17337,7 @@ function evidencePreservedValidator(options) {
17315
17337
  validate: (input) => {
17316
17338
  const cited = /* @__PURE__ */ new Set();
17317
17339
  for (const child of input.children ?? []) {
17318
- if (child.status !== "ok") continue;
17340
+ if (child.status !== "ok" && child.salvageableOutput !== true) continue;
17319
17341
  for (const match of child.text.match(new RegExp(pattern, globalFlags)) ?? []) cited.add(match);
17320
17342
  }
17321
17343
  const reasons = [];
@@ -18466,6 +18488,11 @@ function createEngine(options) {
18466
18488
  };
18467
18489
  if (value !== void 0 && (status === "ok" || status === "exhausted")) outcome.value = value;
18468
18490
  if (wireError !== void 0) outcome.error = wireError;
18491
+ const lifted = liftRunCompletion(status === "ok" || status === "exhausted" ? outcome.value : status === "error" ? wireError?.data : void 0);
18492
+ if (lifted !== void 0) {
18493
+ outcome.completion = lifted.completion;
18494
+ if (lifted.childStatusCounts !== void 0) outcome.childStatusCounts = lifted.childStatusCounts;
18495
+ }
18469
18496
  let settlementFailure;
18470
18497
  if (resumeCtx?.strict !== true) {
18471
18498
  const priorCount = resumeCtx?.priorEntries.length ?? 0;
@@ -18510,7 +18537,6 @@ function createEngine(options) {
18510
18537
  level: "warn",
18511
18538
  msg: `settlement write failed (${settlementFailure.stage}); handle.result rejects with SettlementError; resume re-settles by replay without a provider call`
18512
18539
  }, rootSpanId);
18513
- const lifted = liftRunCompletion(status === "ok" || status === "exhausted" ? outcome.value : status === "error" ? wireError?.data : void 0);
18514
18540
  bus.emit({
18515
18541
  type: "run:end",
18516
18542
  status,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/core",
3
- "version": "1.64.0",
3
+ "version": "1.66.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",