@rulvar/core 1.75.1 → 1.76.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
@@ -7126,6 +7126,30 @@ interface FinishValidationSpec {
7126
7126
  */
7127
7127
  repairTurnReserve?: number;
7128
7128
  /**
7129
+ * The coordination draft gate (the v1.74 experiment review, P0.3),
7130
+ * meaningful ONLY with `synthesis` configured: with validators bound
7131
+ * to the synthesis finish, the coordination finish is an unvalidated
7132
+ * draft, and the experiment's model escaped six failed finish
7133
+ * exchanges with the schema-valid draft 'test', which then starved
7134
+ * synthesis of every citation the validators demanded. The policy
7135
+ * runs deterministic library checks on each coordination finish
7136
+ * (whitespace-token `minWords`, literal `requireSections` markers,
7137
+ * the wordCountValidator and requiredSectionsValidator semantics);
7138
+ * a failing draft returns to the model as the finish call's error
7139
+ * result and the turn continues, exactly like a host validation
7140
+ * rejection, and `repairTurnReserve` grants coordination the same
7141
+ * per-rejected-exchange headroom it grants the synthesis finish.
7142
+ * Pure text checks over the durable exchange: nothing journals, a
7143
+ * resumed segment recounts identically, and `maxRepairs` is not
7144
+ * consumed (it belongs to the synthesis-bound validators). Absent =
7145
+ * byte identical pre 1.76 behavior; configured without `synthesis` =
7146
+ * ConfigError.
7147
+ */
7148
+ draftPolicy?: {
7149
+ /** Minimum whitespace-separated words the draft must carry. */minWords?: number; /** Literal markers the draft text must contain. */
7150
+ requireSections?: string[];
7151
+ };
7152
+ /**
7129
7153
  * The unified output contract this validator set enforces (the v1.71
7130
7154
  * experiment review, P0.1/P0.2). Construction then runs the golden
7131
7155
  * self test with the contract's fixtures as defaults, the contract's
@@ -7286,6 +7310,31 @@ interface OrchestrateSynthesis {
7286
7310
  * { maxTurns: 2 }. Ignored in 'single' mode.
7287
7311
  */
7288
7312
  noteLimits?: UsageLimits;
7313
+ /**
7314
+ * Give the 'single' synthesis invocation the RV-201 evidence tools
7315
+ * `get_child_result` and `read_child_artifact` beside `finish` (the
7316
+ * v1.74 experiment review, P0.2): the finish validators hold the
7317
+ * result against the FULL child outputs while the synthesis model
7318
+ * sees 400 char digests, so when the coordination draft collapses the
7319
+ * evidence the validators demand is model-invisible. With the tools
7320
+ * exposed the digest rows in the synthesis prompt carry each child's
7321
+ * `handle`, and the model pages any settled child's full output or
7322
+ * artifacts before finishing. Off by default: the synthesis toolset
7323
+ * and prompt stay byte identical, exactly like the coordination
7324
+ * `exposeChildResultTools`.
7325
+ */
7326
+ exposeChildResultTools?: boolean;
7327
+ /**
7328
+ * What the 'single' synthesis prompt embeds beside the draft (the
7329
+ * v1.74 experiment review, P0.2). Default 'digests': the 400 char
7330
+ * settled digest rows, byte identical to pre 1.76. 'full' appends a
7331
+ * CHILD OUTPUTS section carrying every settled child's FULL
7332
+ * serialized output after the digest rows: the whole evidence pool
7333
+ * the validators judge against rides the prompt, paid as input
7334
+ * tokens (declare `estCost` or the preflight `estInputTokens`
7335
+ * accordingly).
7336
+ */
7337
+ context?: "digests" | "full";
7289
7338
  }
7290
7339
  /**
7291
7340
  * The deterministic reconciliation envelope an 'incremental' synthesis
@@ -8866,6 +8915,14 @@ interface PreflightOrchestratorSpec {
8866
8915
  model?: ModelSpec;
8867
8916
  limits?: UsageLimits;
8868
8917
  estInputTokens?: number;
8918
+ /**
8919
+ * Mirrors OrchestrateSynthesis.exposeChildResultTools (the v1.74
8920
+ * experiment review, P0.2): declaring it lets the evidence
8921
+ * asymmetry check see that the synthesis model can page the full
8922
+ * child outputs the validators judge against.
8923
+ */
8924
+ exposeChildResultTools?: boolean; /** Mirrors OrchestrateSynthesis.context; default 'digests'. */
8925
+ context?: "digests" | "full";
8869
8926
  };
8870
8927
  }
8871
8928
  /** The full input: engine surface, run surface, and the declared wave. */
package/dist/index.js CHANGED
@@ -15879,6 +15879,19 @@ function validateOrchestrateOptions(opts) {
15879
15879
  }
15880
15880
  if (fv.maxRepairs !== void 0) requireNonNegativeInteger(fv.maxRepairs, "orchestrate finishValidation.maxRepairs");
15881
15881
  if (fv.repairTurnReserve !== void 0) requireNonNegativeInteger(fv.repairTurnReserve, "orchestrate finishValidation.repairTurnReserve");
15882
+ const draftPolicy = fv.draftPolicy;
15883
+ if (draftPolicy !== void 0) {
15884
+ if (typeof draftPolicy !== "object" || draftPolicy === null) throw new ConfigError("orchestrate finishValidation.draftPolicy must be an object");
15885
+ if (opts.synthesis === void 0) throw new ConfigError("orchestrate finishValidation.draftPolicy requires synthesis: without a synthesis invocation the validators bind the coordination finish itself and there is no unvalidated draft to gate");
15886
+ const policy = draftPolicy;
15887
+ if (policy.minWords === void 0 && policy.requireSections === void 0) throw new ConfigError("orchestrate finishValidation.draftPolicy must declare minWords, requireSections, or both");
15888
+ if (policy.minWords !== void 0) {
15889
+ if (typeof policy.minWords !== "number" || !Number.isInteger(policy.minWords) || policy.minWords < 1) throw new ConfigError(`orchestrate finishValidation.draftPolicy.minWords must be a positive integer; got ${JSON.stringify(policy.minWords)}`);
15890
+ }
15891
+ if (policy.requireSections !== void 0) {
15892
+ if (!Array.isArray(policy.requireSections) || policy.requireSections.length === 0 || policy.requireSections.some((section) => typeof section !== "string" || section.length === 0)) throw new ConfigError("orchestrate finishValidation.draftPolicy.requireSections must be a non empty array of non empty strings");
15893
+ }
15894
+ }
15882
15895
  const contract = fv.contract;
15883
15896
  if (contract !== void 0) {
15884
15897
  const shape = contract;
@@ -15904,6 +15917,9 @@ function validateOrchestrateOptions(opts) {
15904
15917
  if (synthesis.mode !== void 0 && synthesis.mode !== "single" && synthesis.mode !== "incremental") throw new ConfigError("orchestrate synthesis.mode must be 'single' or 'incremental'; got " + JSON.stringify(synthesis.mode));
15905
15918
  if (synthesis.mode === "incremental" && opts.finishValidation !== void 0) throw new ConfigError("orchestrate synthesis.mode 'incremental' reconciles deterministically and has no model-composed final finish for finishValidation to bind; configure validators with mode 'single', or drop them");
15906
15919
  if (synthesis.dedupeClaims !== void 0 && typeof synthesis.dedupeClaims !== "boolean") throw new ConfigError("orchestrate synthesis.dedupeClaims must be a boolean; got " + typeof synthesis.dedupeClaims);
15920
+ const symmetry = synthesis;
15921
+ if (symmetry.exposeChildResultTools !== void 0 && typeof symmetry.exposeChildResultTools !== "boolean") throw new ConfigError("orchestrate synthesis.exposeChildResultTools must be a boolean; got " + typeof symmetry.exposeChildResultTools);
15922
+ if (symmetry.context !== void 0 && symmetry.context !== "digests" && symmetry.context !== "full") throw new ConfigError("orchestrate synthesis.context must be 'digests' or 'full'; got " + JSON.stringify(symmetry.context));
15907
15923
  if (synthesis.noteLimits !== void 0) validateUsageLimits(synthesis.noteLimits, "orchestrate synthesis.noteLimits");
15908
15924
  if (synthesis.effort !== void 0 && ![
15909
15925
  "low",
@@ -16940,6 +16956,37 @@ function makeOrchestratorWorkflow(goal, opts) {
16940
16956
  };
16941
16957
  };
16942
16958
  /**
16959
+ * The coordination draft gate (the v1.74 experiment review, P0.3):
16960
+ * deterministic library text checks over the draft finish, run only
16961
+ * when the validators bind synthesis. Unlike validateFinish nothing
16962
+ * journals: the checks are pure functions of the exchange, the
16963
+ * rejected exchange is durable in the transcript itself, and a
16964
+ * resumed segment recounts identically. The rejection is the finish
16965
+ * call's error result, so the loop's repair-reserve grants count it
16966
+ * exactly like a host validation rejection.
16967
+ */
16968
+ const validateDraft = (call) => {
16969
+ const policy = validationSpec?.draftPolicy;
16970
+ if (policy === void 0) return Promise.resolve({ ok: true });
16971
+ const result = call.result ?? null;
16972
+ const text = typeof result === "string" ? result : JSON.stringify(result);
16973
+ const reasons = [];
16974
+ if (policy.minWords !== void 0) {
16975
+ const trimmed = text.trim();
16976
+ const words = trimmed === "" ? 0 : trimmed.split(/\s+/).length;
16977
+ if (words < policy.minWords) reasons.push(`the draft carries ${String(words)} words, below the required ${String(policy.minWords)}`);
16978
+ }
16979
+ for (const section of policy.requireSections ?? []) if (!text.includes(section)) reasons.push(`required draft section '${section}' is missing`);
16980
+ if (reasons.length === 0) return Promise.resolve({ ok: true });
16981
+ return Promise.resolve({
16982
+ ok: false,
16983
+ feedback: {
16984
+ error: "the coordination draft failed the draft policy; repair the draft and call finish again: the synthesis invocation composes the FINAL result from this draft, and a collapsed draft starves it of the evidence the validators demand",
16985
+ reasons
16986
+ }
16987
+ });
16988
+ };
16989
+ /**
16943
16990
  * The frozen bundle descriptor (the v1.71 experiment review,
16944
16991
  * P0.2): with a contract configured, the run durably records WHAT
16945
16992
  * validates it. One decision entry per distinct contract hash, in
@@ -16984,7 +17031,10 @@ function makeOrchestratorWorkflow(goal, opts) {
16984
17031
  },
16985
17032
  [kTerminalTool]: {
16986
17033
  name: FINISH_TOOL_NAME,
16987
- ...validationSpec === void 0 || opts?.synthesis !== void 0 ? {} : {
17034
+ ...validationSpec === void 0 || opts?.synthesis !== void 0 ? validationSpec?.draftPolicy !== void 0 && opts?.synthesis !== void 0 ? {
17035
+ validate: validateDraft,
17036
+ ...validationSpec.repairTurnReserve === void 0 ? {} : { repairTurnReserve: validationSpec.repairTurnReserve }
17037
+ } : {} : {
16988
17038
  validate: validateFinish,
16989
17039
  ...validationSpec.repairTurnReserve === void 0 ? {} : { repairTurnReserve: validationSpec.repairTurnReserve }
16990
17040
  }
@@ -17229,8 +17279,15 @@ function makeOrchestratorWorkflow(goal, opts) {
17229
17279
  if (spec === void 0) return draft;
17230
17280
  await recoveryDone;
17231
17281
  if (spec.mode === "incremental") return await reconcileIncremental(draft, spec);
17232
- const finishOnly = buildOrchestratorTools(orchestratorRuntime, fullCardText).filter((tool) => tool.name === FINISH_TOOL_NAME);
17233
- const settledDigests = [...records.values()].filter((record) => record.settled !== void 0).sort((a, b) => a.spawnOrdinal - b.spawnOrdinal).map((record) => digestOf(record, record.settled));
17282
+ const exposeTools = spec.exposeChildResultTools === true;
17283
+ const fullContext = spec.context === "full";
17284
+ const synthesisToolNames = /* @__PURE__ */ new Set([FINISH_TOOL_NAME, ...exposeTools ? [GET_CHILD_RESULT_TOOL_NAME, READ_CHILD_ARTIFACT_TOOL_NAME] : []]);
17285
+ const synthesisTools = buildOrchestratorTools(orchestratorRuntime, fullCardText, { childResultTools: exposeTools }).filter((tool) => synthesisToolNames.has(tool.name));
17286
+ const settledEntries = [...records.entries()].filter((entry) => entry[1].settled !== void 0).sort((a, b) => a[1].spawnOrdinal - b[1].spawnOrdinal);
17287
+ const settledDigests = settledEntries.map(([handle, record]) => ({
17288
+ ...exposeTools ? { handle } : {},
17289
+ ...digestOf(record, record.settled)
17290
+ }));
17234
17291
  let digestRows = settledDigests;
17235
17292
  let repeatedClaims;
17236
17293
  if (spec.dedupeClaims === true) {
@@ -17248,13 +17305,19 @@ function makeOrchestratorWorkflow(goal, opts) {
17248
17305
  const draftJson = JSON.stringify(draft ?? null);
17249
17306
  const digestJson = JSON.stringify(digestRows);
17250
17307
  const prompt = [
17251
- "You are the synthesis invocation of an orchestrated run. Compose the FINAL result of the run from the goal, the coordination draft, and the settled child evidence below by calling finish({ result }) EXACTLY once. Preserve the evidence and citations the draft relies on; do not invent findings. No other tool exists.",
17308
+ "You are the synthesis invocation of an orchestrated run. Compose the FINAL result of the run from the goal, the coordination draft, and the settled child evidence below by calling finish({ result }) EXACTLY once. Preserve the evidence and citations the draft relies on; do not invent findings. " + (exposeTools ? "Beside finish, get_child_result and read_child_artifact page any SETTLED child's FULL output and artifacts by handle (each DIGEST row carries its handle); read what the validators will hold you to before finishing." : "No other tool exists."),
17252
17309
  ...repeatedClaims === void 0 ? [] : ["Repeated claims across children were deduplicated before this prompt: only the first occurrence of each repeated line remains in the digest, and the REPEATED CLAIMS index below lists each one with its reporters."],
17253
17310
  ...spec.instructions === void 0 ? [] : [spec.instructions],
17254
17311
  ...finishValidationPromptLines(validationSpec),
17255
17312
  `GOAL: ${goal}`,
17256
17313
  `DRAFT: ${draftJson}`,
17257
17314
  `DIGEST: ${digestJson}`,
17315
+ ...fullContext ? [`CHILD OUTPUTS: ${JSON.stringify(settledEntries.map(([handle, record]) => ({
17316
+ ...exposeTools ? { handle } : {},
17317
+ nodeId: record.nodeId,
17318
+ status: record.settled.status,
17319
+ text: serializeChildOutput(record.settled)
17320
+ })))}`] : [],
17258
17321
  ...repeatedClaims === void 0 ? [] : [`REPEATED CLAIMS: ${JSON.stringify(repeatedClaims)}`]
17259
17322
  ].join("\n");
17260
17323
  internals.events.emit({
@@ -17280,7 +17343,7 @@ function makeOrchestratorWorkflow(goal, opts) {
17280
17343
  const synthesisOpts = {
17281
17344
  role: "synthesize",
17282
17345
  result: "full",
17283
- tools: finishOnly,
17346
+ tools: synthesisTools,
17284
17347
  limits: spec.limits ?? { maxTurns: 4 },
17285
17348
  ...spec.model === void 0 ? {} : { model: spec.model },
17286
17349
  ...spec.effort === void 0 ? {} : { effort: spec.effort },
@@ -17895,6 +17958,21 @@ function preflightEstimate(input) {
17895
17958
  const synthesis = input.orchestrator.synthesis;
17896
17959
  if (synthesis.limits !== void 0) validateUsageLimits(synthesis.limits, "preflight orchestrator.synthesis.limits");
17897
17960
  if (synthesis.estInputTokens !== void 0) requireNonNegativeInteger(synthesis.estInputTokens, "preflight orchestrator.synthesis.estInputTokens");
17961
+ if (synthesis.context !== void 0 && synthesis.context !== "digests" && synthesis.context !== "full") throw new ConfigError("preflight orchestrator.synthesis.context must be 'digests' or 'full'; got " + JSON.stringify(synthesis.context));
17962
+ {
17963
+ const evidenceNames = /* @__PURE__ */ new Set([
17964
+ "evidence-preserved",
17965
+ "contract-citations",
17966
+ "contract-section-citations"
17967
+ ]);
17968
+ const evidenceValidators = (input.finishValidation?.validators ?? []).map((validator) => validator.name).filter((name) => evidenceNames.has(name));
17969
+ if ((evidenceValidators.length > 0 || input.finishValidation?.contract?.manifest.citations !== void 0) && synthesis.exposeChildResultTools !== true && (synthesis.context ?? "digests") === "digests") say({
17970
+ severity: "warning",
17971
+ code: "synthesis-evidence-asymmetry",
17972
+ message: `the finish validators demand child evidence (${evidenceValidators.length > 0 ? evidenceValidators.join(", ") : "contract citations"}) but the synthesis invocation sees only truncated digest rows and has no child read tools: a collapsed coordination draft starves it of every citation (the v1.74 experiment shape); set synthesis.exposeChildResultTools, synthesis.context 'full', or bind the validators to coordination by dropping synthesis`,
17973
+ spawn: "synthesis"
17974
+ });
17975
+ }
17898
17976
  const servedBy = resolveServing(synthesis.model) ?? resolveServing(defaults.routing?.synthesize);
17899
17977
  if (servedBy === void 0) say({
17900
17978
  severity: "error",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/core",
3
- "version": "1.75.1",
3
+ "version": "1.76.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",