@rulvar/core 1.71.0 → 1.73.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +256 -1
  2. package/dist/index.js +657 -166
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -11087,7 +11087,9 @@ async function runAgent(options) {
11087
11087
  status = "limit";
11088
11088
  break;
11089
11089
  }
11090
- if (turns >= limits.maxTurns) {
11090
+ const repairReserve = options.terminalTool?.repairTurnReserve ?? 0;
11091
+ const grantedRepairTurns = repairReserve === 0 ? 0 : Math.min(repairReserve, messages.reduce((count, message) => count + message.parts.filter((part) => part.type === "tool-result" && part.name === options.terminalTool?.name && part.isError === true).length, 0));
11092
+ if (turns >= limits.maxTurns + grantedRepairTurns) {
11091
11093
  status = "limit";
11092
11094
  break;
11093
11095
  }
@@ -15091,6 +15093,465 @@ function buildOrchestratorTools(runtime, profileCardText, options) {
15091
15093
  return tools;
15092
15094
  }
15093
15095
  //#endregion
15096
+ //#region src/orchestrator/finish-validators.ts
15097
+ /**
15098
+ * Deterministic host validation of the orchestrator finish result (the
15099
+ * v1.40.0 improvement plan's RV-204 slice). A validator is plain
15100
+ * synchronous host code judging the finish({ result }) argument; the
15101
+ * orchestrator runtime runs the configured set on every schema valid
15102
+ * finish call, returns the failure reasons to the model as the call's
15103
+ * error tool result (a bounded repair turn), and fails the run with a
15104
+ * typed error when the repair bound is exhausted. Verdicts journal as
15105
+ * decision entries, so a resume rolls the SAME verdicts forward without
15106
+ * re-running validator code.
15107
+ */
15108
+ const ok = { ok: true };
15109
+ function requireNonEmptyStrings(values, what) {
15110
+ if (!Array.isArray(values) || values.length === 0) throw new ConfigError(`${what} must be a non empty array of strings`);
15111
+ for (const value of values) if (typeof value !== "string" || value.length === 0) throw new ConfigError(`${what} must contain only non empty strings`);
15112
+ return values;
15113
+ }
15114
+ /**
15115
+ * Requires every named section to appear LITERALLY in the result text
15116
+ * (a heading like 'FINDINGS' or any marker the goal demands). Default
15117
+ * name 'required-sections'; pass `name` to run several instances.
15118
+ */
15119
+ function requiredSectionsValidator(options) {
15120
+ const sections = requireNonEmptyStrings(options.sections, "requiredSectionsValidator sections");
15121
+ return {
15122
+ name: options.name ?? "required-sections",
15123
+ validate: (input) => {
15124
+ const missing = sections.filter((section) => !input.text.includes(section));
15125
+ return missing.length === 0 ? ok : {
15126
+ ok: false,
15127
+ reasons: missing.map((section) => `required section '${section}' is missing`)
15128
+ };
15129
+ }
15130
+ };
15131
+ }
15132
+ /**
15133
+ * Requires the result to be a JSON object carrying every named field
15134
+ * with a substantial value: present, not null, and not an empty or
15135
+ * whitespace only string (empty arrays, zero, and false COUNT as
15136
+ * present; emptiness rules beyond strings belong to a custom
15137
+ * validator). Default name 'required-fields'.
15138
+ */
15139
+ function requiredFieldsValidator(options) {
15140
+ const fields = requireNonEmptyStrings(options.fields, "requiredFieldsValidator fields");
15141
+ return {
15142
+ name: options.name ?? "required-fields",
15143
+ validate: (input) => {
15144
+ const result = input.result;
15145
+ if (typeof result !== "object" || result === null || Array.isArray(result)) return {
15146
+ ok: false,
15147
+ reasons: ["the finish result is not a JSON object"]
15148
+ };
15149
+ const record = result;
15150
+ const reasons = [];
15151
+ for (const field of fields) {
15152
+ const value = record[field];
15153
+ if (value === void 0 || value === null) reasons.push(`required field '${field}' is missing`);
15154
+ else if (typeof value === "string" && value.trim().length === 0) reasons.push(`required field '${field}' is empty`);
15155
+ }
15156
+ return reasons.length === 0 ? ok : {
15157
+ ok: false,
15158
+ reasons
15159
+ };
15160
+ }
15161
+ };
15162
+ }
15163
+ /**
15164
+ * Requires the result text's word count (whitespace separated tokens;
15165
+ * an empty text counts zero) to sit inside the configured bounds (the
15166
+ * v1.71 experiment review, P0.7: a formal length requirement must be
15167
+ * code, never a natural-language plea the model may round away). At
15168
+ * least one bound is required; both are positive integers with
15169
+ * min <= max. Default name 'word-count'.
15170
+ */
15171
+ function wordCountValidator(options) {
15172
+ const { min, max } = options;
15173
+ if (min === void 0 && max === void 0) throw new ConfigError("wordCountValidator requires min, max, or both");
15174
+ for (const [label, value] of [["min", min], ["max", max]]) if (value !== void 0 && (!Number.isInteger(value) || value < 1)) throw new ConfigError(`wordCountValidator ${label} must be a positive integer; got ${String(value)}`);
15175
+ if (min !== void 0 && max !== void 0 && min > max) throw new ConfigError(`wordCountValidator min ${String(min)} exceeds max ${String(max)}`);
15176
+ return {
15177
+ name: options.name ?? "word-count",
15178
+ validate: (input) => {
15179
+ const trimmed = input.text.trim();
15180
+ const count = trimmed.length === 0 ? 0 : trimmed.split(/\s+/).length;
15181
+ const reasons = [];
15182
+ if (min !== void 0 && count < min) reasons.push(`result word count ${String(count)} is below the required minimum ${String(min)}`);
15183
+ if (max !== void 0 && count > max) reasons.push(`result word count ${String(count)} exceeds the maximum ${String(max)}`);
15184
+ return reasons.length === 0 ? ok : {
15185
+ ok: false,
15186
+ reasons
15187
+ };
15188
+ }
15189
+ };
15190
+ }
15191
+ /** The default citation shape: a path with an extension, a colon, a line number. */
15192
+ const DEFAULT_CITATION_PATTERN = "[\\w./-]+\\.\\w+:\\d+";
15193
+ /** The default preserved share, the improvement plan's RV-202 gate. */
15194
+ const DEFAULT_EVIDENCE_MIN_SHARE = .95;
15195
+ const MAX_LISTED_CITATIONS = 20;
15196
+ function listCitations(values) {
15197
+ return values.length <= MAX_LISTED_CITATIONS ? values.join(", ") : `${values.slice(0, MAX_LISTED_CITATIONS).join(", ")} and ${String(values.length - MAX_LISTED_CITATIONS)} more`;
15198
+ }
15199
+ /**
15200
+ * The RV-202 evidence preservation contract: the finish result must
15201
+ * PRESERVE the citations the children actually produced. Distinct
15202
+ * matches of `pattern` are collected across the outputs of children
15203
+ * settled 'ok' (spawn order); at least `minShare` of them (default
15204
+ * {@link DEFAULT_EVIDENCE_MIN_SHARE}, the plan's 95 percent gate,
15205
+ * compared as a ceiling on the required count so an exact boundary like
15206
+ * 19 of 20 passes) must appear literally in the result text. Zero child
15207
+ * citations pass vacuously. With `requireKnown: true` the contract also
15208
+ * runs in reverse: every citation in the RESULT must appear in some
15209
+ * child's output, so a fabricated but pattern valid citation is
15210
+ * rejected instead of silently counting as evidence. Rejection reasons
15211
+ * list the missing (and unknown) citations, capped at 20, so the repair
15212
+ * turn can restore them. Purely textual and deterministic; checking
15213
+ * that cited targets EXIST on disk is host territory (a custom
15214
+ * validator), not this contract. Default name 'evidence-preserved'.
15215
+ */
15216
+ function evidencePreservedValidator(options) {
15217
+ const pattern = options?.pattern ?? "[\\w./-]+\\.\\w+:\\d+";
15218
+ const flags = options?.flags ?? "";
15219
+ const globalFlags = flags.includes("g") ? flags : `${flags}g`;
15220
+ try {
15221
+ new RegExp(pattern, globalFlags);
15222
+ } catch (thrown) {
15223
+ throw new ConfigError(`evidencePreservedValidator pattern does not compile: ${thrown instanceof Error ? thrown.message : String(thrown)}`);
15224
+ }
15225
+ const minShare = options?.minShare ?? .95;
15226
+ if (typeof minShare !== "number" || !Number.isFinite(minShare) || minShare <= 0 || minShare > 1) throw new ConfigError(`evidencePreservedValidator minShare must be a number in (0, 1]; got ${String(minShare)}`);
15227
+ return {
15228
+ name: options?.name ?? "evidence-preserved",
15229
+ validate: (input) => {
15230
+ const cited = /* @__PURE__ */ new Set();
15231
+ for (const child of input.children ?? []) {
15232
+ if (child.status !== "ok" && child.salvageableOutput !== true) continue;
15233
+ for (const match of child.text.match(new RegExp(pattern, globalFlags)) ?? []) cited.add(match);
15234
+ }
15235
+ const reasons = [];
15236
+ if (cited.size > 0) {
15237
+ const missing = [...cited].filter((citation) => !input.text.includes(citation));
15238
+ const preserved = cited.size - missing.length;
15239
+ if (preserved < Math.ceil(minShare * cited.size - 1e-9)) reasons.push(`evidence preservation ${String(preserved)} of ${String(cited.size)} child citations is below the required share ${String(minShare)}; missing: ${listCitations(missing)}`);
15240
+ }
15241
+ if (options?.requireKnown === true) {
15242
+ const fabricated = [...new Set(input.text.match(new RegExp(pattern, globalFlags)) ?? [])].filter((citation) => !cited.has(citation));
15243
+ if (fabricated.length > 0) reasons.push(`unknown citations not present in any child report: ${listCitations(fabricated)}`);
15244
+ }
15245
+ return reasons.length === 0 ? ok : {
15246
+ ok: false,
15247
+ reasons
15248
+ };
15249
+ }
15250
+ };
15251
+ }
15252
+ /**
15253
+ * Requires at least `min` matches of `pattern` INSIDE every named
15254
+ * section (the v1.71 experiment review, P1.2: a total citation count
15255
+ * hides sections carrying zero provenance). A section's slice runs
15256
+ * from its FIRST occurrence to the next found section marker in text
15257
+ * position order, or to the end of the text; a marker absent from the
15258
+ * text is its own failure reason, because coverage of a missing
15259
+ * section cannot silently count as satisfied.
15260
+ * requiredSectionsValidator still owns plain presence. Default name
15261
+ * 'section-citations'.
15262
+ */
15263
+ function sectionCitationsValidator(options) {
15264
+ const sections = requireNonEmptyStrings(options.sections, "sectionCitationsValidator sections");
15265
+ const pattern = options.pattern ?? "[\\w./-]+\\.\\w+:\\d+";
15266
+ const flags = options.flags ?? "";
15267
+ const globalFlags = flags.includes("g") ? flags : `${flags}g`;
15268
+ try {
15269
+ new RegExp(pattern, globalFlags);
15270
+ } catch (thrown) {
15271
+ throw new ConfigError(`sectionCitationsValidator pattern does not compile: ${thrown instanceof Error ? thrown.message : String(thrown)}`);
15272
+ }
15273
+ if (!Number.isInteger(options.min) || options.min < 1) throw new ConfigError(`sectionCitationsValidator min must be a positive integer; got ${String(options.min)}`);
15274
+ return {
15275
+ name: options.name ?? "section-citations",
15276
+ validate: (input) => {
15277
+ const positions = /* @__PURE__ */ new Map();
15278
+ for (const section of sections) {
15279
+ const at = input.text.indexOf(section);
15280
+ if (at >= 0) positions.set(section, at);
15281
+ }
15282
+ const ordered = [...positions.entries()].sort((a, b) => a[1] - b[1]);
15283
+ const reasons = [];
15284
+ for (const section of sections) {
15285
+ const at = positions.get(section);
15286
+ if (at === void 0) {
15287
+ reasons.push(`required section '${section}' is missing, so its citation coverage cannot be judged`);
15288
+ continue;
15289
+ }
15290
+ const next = ordered.find(([, position]) => position > at);
15291
+ const matches = input.text.slice(at, next === void 0 ? input.text.length : next[1]).match(new RegExp(pattern, globalFlags))?.length ?? 0;
15292
+ if (matches < options.min) reasons.push(`section '${section}' carries ${String(matches)} citations matching /${pattern}/; at least ${String(options.min)} required`);
15293
+ }
15294
+ return reasons.length === 0 ? ok : {
15295
+ ok: false,
15296
+ reasons
15297
+ };
15298
+ }
15299
+ };
15300
+ }
15301
+ /**
15302
+ * Requires at least `min` matches of `pattern` in the result text (the
15303
+ * plan's citation and source count checks: a file:line pattern, a URL
15304
+ * pattern). The pattern compiles at construction (invalid patterns are a
15305
+ * ConfigError before any run exists) and matches globally; `min` is a
15306
+ * positive integer. Default name 'min-matches'; pass `name` to run
15307
+ * several instances, because names must be unique per orchestrate call.
15308
+ */
15309
+ function minMatchesValidator(options) {
15310
+ const flags = options.flags ?? "";
15311
+ const globalFlags = flags.includes("g") ? flags : `${flags}g`;
15312
+ try {
15313
+ new RegExp(options.pattern, globalFlags);
15314
+ } catch (thrown) {
15315
+ throw new ConfigError(`minMatchesValidator pattern does not compile: ${thrown instanceof Error ? thrown.message : String(thrown)}`);
15316
+ }
15317
+ if (!Number.isInteger(options.min) || options.min < 1) throw new ConfigError(`minMatchesValidator min must be a positive integer; got ${String(options.min)}`);
15318
+ return {
15319
+ name: options.name ?? "min-matches",
15320
+ validate: (input) => {
15321
+ const found = input.text.match(new RegExp(options.pattern, globalFlags))?.length ?? 0;
15322
+ return found >= options.min ? ok : {
15323
+ ok: false,
15324
+ reasons: [`expected at least ${String(options.min)} matches of /${options.pattern}/${flags}; found ${String(found)}`]
15325
+ };
15326
+ }
15327
+ };
15328
+ }
15329
+ //#endregion
15330
+ //#region src/orchestrator/output-contract.ts
15331
+ /**
15332
+ * The unified output contract (the v1.71 experiment review: P0.1 the
15333
+ * manifest, P0.2 the frozen bundle, P0.3 the golden self test). ONE
15334
+ * immutable manifest generates the prompt statement the model reads,
15335
+ * the stock validator set the host enforces, a stable content hash,
15336
+ * and golden self-test fixtures, so the prompt and the validators
15337
+ * cannot drift apart by construction. The experiment's terminal
15338
+ * failure was exactly that drift: the question renamed three sections,
15339
+ * the harness validator kept the old names, and the mismatch survived
15340
+ * until paid provider turns had burned. With a contract, the same
15341
+ * mistake is a ConfigError before the first provider call.
15342
+ */
15343
+ /** The golden citation sample used with {@link DEFAULT_CITATION_PATTERN}. */
15344
+ const DEFAULT_CITATION_SAMPLE = "docs/output-contract.md:1";
15345
+ /** The filler token golden skeletons pad word counts with. */
15346
+ const FILLER_WORD = "placeholder";
15347
+ function requirePositiveInt(value, what) {
15348
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) throw new ConfigError(`${what} must be a positive integer; got ${String(value)}`);
15349
+ return value;
15350
+ }
15351
+ function countWords(text) {
15352
+ const trimmed = text.trim();
15353
+ return trimmed.length === 0 ? 0 : trimmed.split(/\s+/).length;
15354
+ }
15355
+ /**
15356
+ * Builds a {@link FinishContract} from one manifest: validation and the
15357
+ * golden fixtures happen HERE, at configuration time, so a
15358
+ * self-contradictory contract (mandatory content alone above words.max,
15359
+ * an unsampled custom pattern) fails before any run exists. Spread
15360
+ * `contract.validators` into finishValidation.validators and pass the
15361
+ * contract itself as finishValidation.contract; the orchestrator then
15362
+ * injects `promptLines` into the coordination and synthesis prompts,
15363
+ * runs the golden self test at construction, and journals the frozen
15364
+ * bundle descriptor.
15365
+ */
15366
+ function finishContract(manifest) {
15367
+ if (typeof manifest !== "object" || manifest === null) throw new ConfigError("finishContract manifest must be an object");
15368
+ const { sections, words, citations } = manifest;
15369
+ if (sections === void 0 && words === void 0 && citations === void 0) throw new ConfigError("finishContract manifest must declare sections, words, or citations");
15370
+ let normalizedSections;
15371
+ if (sections !== void 0) {
15372
+ if (!Array.isArray(sections) || sections.length === 0) throw new ConfigError("finishContract sections must be a non empty array of strings");
15373
+ const seen = /* @__PURE__ */ new Set();
15374
+ for (const section of sections) {
15375
+ if (typeof section !== "string" || section.trim().length === 0) throw new ConfigError("finishContract sections must contain only non empty strings");
15376
+ if (section.includes("\n")) throw new ConfigError(`finishContract section '${section}' must be a single line`);
15377
+ if (seen.has(section)) throw new ConfigError(`finishContract sections must be unique; '${section}' repeats`);
15378
+ seen.add(section);
15379
+ }
15380
+ normalizedSections = [...sections];
15381
+ }
15382
+ let normalizedWords;
15383
+ if (words !== void 0) {
15384
+ if (typeof words !== "object" || words === null) throw new ConfigError("finishContract words must be an object");
15385
+ if (words.min === void 0 && words.max === void 0) throw new ConfigError("finishContract words requires min, max, or both");
15386
+ if (words.min !== void 0) requirePositiveInt(words.min, "finishContract words.min");
15387
+ if (words.max !== void 0) requirePositiveInt(words.max, "finishContract words.max");
15388
+ if (words.min !== void 0 && words.max !== void 0 && words.min > words.max) throw new ConfigError(`finishContract words.min ${String(words.min)} exceeds words.max ${String(words.max)}`);
15389
+ normalizedWords = {
15390
+ ...words.min === void 0 ? {} : { min: words.min },
15391
+ ...words.max === void 0 ? {} : { max: words.max }
15392
+ };
15393
+ }
15394
+ let normalizedCitations;
15395
+ if (citations !== void 0) {
15396
+ if (typeof citations !== "object" || citations === null) throw new ConfigError("finishContract citations must be an object");
15397
+ if (citations.min === void 0 && citations.perSection === void 0) throw new ConfigError("finishContract citations requires min, perSection, or both");
15398
+ if (citations.min !== void 0) requirePositiveInt(citations.min, "finishContract citations.min");
15399
+ if (citations.perSection !== void 0) {
15400
+ requirePositiveInt(citations.perSection, "finishContract citations.perSection");
15401
+ if (normalizedSections === void 0) throw new ConfigError("finishContract citations.perSection requires sections");
15402
+ }
15403
+ const pattern = citations.pattern ?? "[\\w./-]+\\.\\w+:\\d+";
15404
+ const flags = citations.flags ?? "";
15405
+ const globalFlags = flags.includes("g") ? flags : `${flags}g`;
15406
+ try {
15407
+ new RegExp(pattern, globalFlags);
15408
+ } catch (thrown) {
15409
+ throw new ConfigError(`finishContract citations.pattern does not compile: ${thrown instanceof Error ? thrown.message : String(thrown)}`);
15410
+ }
15411
+ const sample = citations.sample ?? (citations.pattern === void 0 ? "docs/output-contract.md:1" : void 0);
15412
+ if (sample === void 0) throw new ConfigError("finishContract citations.sample is required with a custom pattern: the golden fixtures embed a literal match");
15413
+ if (typeof sample !== "string" || sample.length === 0 || /\s/u.test(sample)) throw new ConfigError("finishContract citations.sample must be a non empty string without whitespace");
15414
+ if (!new RegExp(pattern, globalFlags).test(sample)) throw new ConfigError(`finishContract citations.sample '${sample}' does not match the citation pattern`);
15415
+ if (normalizedSections?.some((section) => sample.includes(section)) === true) throw new ConfigError("finishContract citations.sample must not contain a declared section marker");
15416
+ normalizedCitations = {
15417
+ pattern,
15418
+ flags,
15419
+ sample,
15420
+ ...citations.min === void 0 ? {} : { min: citations.min },
15421
+ ...citations.perSection === void 0 ? {} : { perSection: citations.perSection }
15422
+ };
15423
+ }
15424
+ const normalized = {
15425
+ ...normalizedSections === void 0 ? {} : { sections: normalizedSections },
15426
+ ...normalizedWords === void 0 ? {} : { words: normalizedWords },
15427
+ ...normalizedCitations === void 0 ? {} : { citations: normalizedCitations }
15428
+ };
15429
+ const hash = createHash("sha256").update(jcsSerialize(normalized), "utf8").digest("hex");
15430
+ const validators = [];
15431
+ if (normalizedSections !== void 0) validators.push(requiredSectionsValidator({
15432
+ sections: normalizedSections,
15433
+ name: "contract-sections"
15434
+ }));
15435
+ if (normalizedWords !== void 0) validators.push(wordCountValidator({
15436
+ ...normalizedWords,
15437
+ name: "contract-words"
15438
+ }));
15439
+ if (normalizedCitations?.min !== void 0) validators.push(minMatchesValidator({
15440
+ pattern: normalizedCitations.pattern,
15441
+ flags: normalizedCitations.flags,
15442
+ min: normalizedCitations.min,
15443
+ name: "contract-citations"
15444
+ }));
15445
+ if (normalizedCitations?.perSection !== void 0 && normalizedSections !== void 0) validators.push(sectionCitationsValidator({
15446
+ sections: normalizedSections,
15447
+ pattern: normalizedCitations.pattern,
15448
+ flags: normalizedCitations.flags,
15449
+ min: normalizedCitations.perSection,
15450
+ name: "contract-section-citations"
15451
+ }));
15452
+ const promptLines = [];
15453
+ if (normalizedSections !== void 0) promptLines.push("The final result must contain each of these section markers verbatim: " + normalizedSections.map((section) => `'${section}'`).join(", ") + ".");
15454
+ if (normalizedWords !== void 0) {
15455
+ const { min, max } = normalizedWords;
15456
+ if (min !== void 0 && max !== void 0) promptLines.push(`The final result must be between ${String(min)} and ${String(max)} words (whitespace separated).`);
15457
+ else if (min !== void 0) promptLines.push(`The final result must be at least ${String(min)} words (whitespace separated).`);
15458
+ else if (max !== void 0) promptLines.push(`The final result must be at most ${String(max)} words (whitespace separated).`);
15459
+ }
15460
+ if (normalizedCitations !== void 0) {
15461
+ if (normalizedCitations.min !== void 0) promptLines.push(`Include at least ${String(normalizedCitations.min)} citations matching /${normalizedCitations.pattern}/ overall (for example '${normalizedCitations.sample}').`);
15462
+ if (normalizedCitations.perSection !== void 0) promptLines.push(`Every required section must itself contain at least ${String(normalizedCitations.perSection)} such citations.`);
15463
+ }
15464
+ const lines = [];
15465
+ const perSection = normalizedCitations?.perSection ?? 0;
15466
+ for (const section of normalizedSections ?? []) {
15467
+ lines.push(section);
15468
+ if (normalizedCitations !== void 0 && perSection > 0) {
15469
+ const sample = normalizedCitations.sample;
15470
+ lines.push(Array.from({ length: perSection }, () => sample).join(" "));
15471
+ }
15472
+ }
15473
+ if (normalizedCitations !== void 0) {
15474
+ const placed = (normalizedSections?.length ?? 0) * perSection;
15475
+ const deficit = Math.max(0, (normalizedCitations.min ?? 0) - placed);
15476
+ if (deficit > 0) {
15477
+ const sample = normalizedCitations.sample;
15478
+ lines.push(Array.from({ length: deficit }, () => sample).join(" "));
15479
+ }
15480
+ }
15481
+ let goldenText = lines.join("\n");
15482
+ const mandatoryWords = countWords(goldenText);
15483
+ if (normalizedWords?.max !== void 0 && mandatoryWords > normalizedWords.max) throw new ConfigError(`finishContract is self contradictory: its mandatory content alone is ${String(mandatoryWords)} words, above words.max ${String(normalizedWords.max)}`);
15484
+ if (normalizedWords?.min !== void 0 && mandatoryWords < normalizedWords.min) {
15485
+ const padding = Array.from({ length: normalizedWords.min - mandatoryWords }, () => FILLER_WORD).join(" ");
15486
+ goldenText = goldenText.length === 0 ? padding : `${goldenText}\n${padding}`;
15487
+ }
15488
+ const goldenAccept = Object.freeze({
15489
+ result: goldenText,
15490
+ text: goldenText,
15491
+ children: Object.freeze([Object.freeze({
15492
+ handle: 0,
15493
+ nodeId: "contract-golden",
15494
+ status: "ok",
15495
+ text: goldenText
15496
+ })])
15497
+ });
15498
+ const goldenReject = normalizedSections !== void 0 || normalizedWords?.min !== void 0 || normalizedCitations !== void 0 ? Object.freeze({
15499
+ result: "",
15500
+ text: "",
15501
+ children: Object.freeze([])
15502
+ }) : void 0;
15503
+ return Object.freeze({
15504
+ manifest: Object.freeze(normalized),
15505
+ hash,
15506
+ validators,
15507
+ promptLines: Object.freeze(promptLines),
15508
+ goldenAccept,
15509
+ ...goldenReject === void 0 ? {} : { goldenReject }
15510
+ });
15511
+ }
15512
+ /**
15513
+ * Runs a configured validator set against golden fixtures BEFORE any
15514
+ * provider call exists (the v1.71 experiment review, P0.3): the accept
15515
+ * fixture must pass every validator (a stale validator rejecting a
15516
+ * correct skeleton is exactly the drift the experiment died of, three
15517
+ * renamed sections deep into a paid run), and the reject fixture must
15518
+ * fail at least one (a set that accepts the known-bad input validates
15519
+ * nothing). A validator that THROWS here is a host defect and the
15520
+ * ConfigError propagates, the same posture the live loop takes.
15521
+ * Deterministic and free: validators are pure synchronous host code by
15522
+ * contract, so this costs zero provider calls.
15523
+ */
15524
+ function selfTestFinishValidation(options) {
15525
+ const run = (validator, input) => {
15526
+ try {
15527
+ return validator.validate(input);
15528
+ } catch (thrown) {
15529
+ throw new ConfigError(`finish validator '${validator.name}' threw during the self test instead of returning a verdict: ${thrown instanceof Error ? thrown.message : String(thrown)}`);
15530
+ }
15531
+ };
15532
+ const failures = [];
15533
+ const accept = options.accept;
15534
+ if (accept !== void 0) for (const validator of options.validators) {
15535
+ const verdict = run(validator, accept);
15536
+ if (!verdict.ok) failures.push({
15537
+ fixture: "accept",
15538
+ validator: validator.name,
15539
+ reasons: verdict.reasons
15540
+ });
15541
+ }
15542
+ const reject = options.reject;
15543
+ if (reject !== void 0) {
15544
+ if (!options.validators.some((validator) => !run(validator, reject).ok)) failures.push({
15545
+ fixture: "reject",
15546
+ reasons: ["every configured validator accepts the known-bad fixture; the validation is vacuous"]
15547
+ });
15548
+ }
15549
+ return {
15550
+ ok: failures.length === 0,
15551
+ failures
15552
+ };
15553
+ }
15554
+ //#endregion
15094
15555
  //#region src/orchestrator/orchestrate.ts
15095
15556
  /**
15096
15557
  * The mode (c) dynamic orchestrator (M6-T07/T08).
@@ -15192,6 +15653,26 @@ function validateOrchestrateOptions(opts) {
15192
15653
  seen.add(validator.name);
15193
15654
  }
15194
15655
  if (fv.maxRepairs !== void 0) requireNonNegativeInteger(fv.maxRepairs, "orchestrate finishValidation.maxRepairs");
15656
+ if (fv.repairTurnReserve !== void 0) requireNonNegativeInteger(fv.repairTurnReserve, "orchestrate finishValidation.repairTurnReserve");
15657
+ const contract = fv.contract;
15658
+ if (contract !== void 0) {
15659
+ const shape = contract;
15660
+ if (typeof shape.hash !== "string" || !Array.isArray(shape.validators) || !Array.isArray(shape.promptLines) || shape.goldenAccept === void 0) throw new ConfigError("orchestrate finishValidation.contract must be a finishContract(...) product");
15661
+ for (const contractValidator of contract.validators) if (!seen.has(contractValidator.name)) throw new ConfigError(`orchestrate finishValidation.contract validator '${contractValidator.name}' is not in finishValidation.validators; spread contract.validators into the set so the promised contract is actually enforced`);
15662
+ }
15663
+ const selfTest = fv.selfTest;
15664
+ if (selfTest !== void 0 && (typeof selfTest !== "object" || selfTest === null)) throw new ConfigError("orchestrate finishValidation.selfTest must be an object");
15665
+ const acceptFixture = selfTest?.accept ?? contract?.goldenAccept;
15666
+ const rejectFixture = selfTest?.reject ?? contract?.goldenReject;
15667
+ if (selfTest !== void 0 && acceptFixture === void 0 && rejectFixture === void 0) throw new ConfigError("orchestrate finishValidation.selfTest requires an accept or reject fixture");
15668
+ if (acceptFixture !== void 0 || rejectFixture !== void 0) {
15669
+ const report = selfTestFinishValidation({
15670
+ validators: fv.validators,
15671
+ ...acceptFixture === void 0 ? {} : { accept: acceptFixture },
15672
+ ...rejectFixture === void 0 ? {} : { reject: rejectFixture }
15673
+ });
15674
+ if (!report.ok) throw new ConfigError("the finish validation self test failed BEFORE any provider call: " + report.failures.map((failure) => failure.validator === void 0 ? failure.reasons.join("; ") : `validator '${failure.validator}' rejected the accept fixture: ` + failure.reasons.join("; ")).join("; "));
15675
+ }
15195
15676
  }
15196
15677
  if (opts.synthesis !== void 0) {
15197
15678
  const synthesis = opts.synthesis;
@@ -15240,7 +15721,11 @@ function finishValidationPromptLines(spec) {
15240
15721
  if (spec === void 0) return [];
15241
15722
  const names = spec.validators.map((validator) => validator.name).join(", ");
15242
15723
  const repairs = spec.maxRepairs ?? 1;
15243
- return [`The host validates every finish({ result }) with deterministic validators: ${names}.`, "A rejected finish returns the failure reasons as the tool error result; repair the result and call finish again. " + (repairs === 0 ? "No repair attempt is granted: the first rejected finish fails the run." : repairs === 1 ? "At most one repair attempt is granted before the run fails." : `At most ${String(repairs)} repair attempts are granted before the run fails.`)];
15724
+ return [
15725
+ `The host validates every finish({ result }) with deterministic validators: ${names}.`,
15726
+ ...spec.contract === void 0 ? [] : spec.contract.promptLines,
15727
+ "A rejected finish returns the failure reasons as the tool error result; repair the result and call finish again. " + (repairs === 0 ? "No repair attempt is granted: the first rejected finish fails the run." : repairs === 1 ? "At most one repair attempt is granted before the run fails." : `At most ${String(repairs)} repair attempts are granted before the run fails.`)
15728
+ ];
15244
15729
  }
15245
15730
  /**
15246
15731
  * The partial-salvage contract rides the PROMPT exactly like finish
@@ -16229,6 +16714,37 @@ function makeOrchestratorWorkflow(goal, opts) {
16229
16714
  }
16230
16715
  };
16231
16716
  };
16717
+ /**
16718
+ * The frozen bundle descriptor (the v1.71 experiment review,
16719
+ * P0.2): with a contract configured, the run durably records WHAT
16720
+ * validates it. One decision entry per distinct contract hash, in
16721
+ * supersession order: a resume under the SAME contract appends
16722
+ * nothing (the descriptor already exists), and a resume under a
16723
+ * FIXED contract appends a superseding descriptor instead of
16724
+ * failing, because repairing a stale validator and resuming is the
16725
+ * intended remedy. No contract (or no validators at all) keeps the
16726
+ * journal byte identical, awaits included.
16727
+ */
16728
+ if (validationSpec?.contract !== void 0) {
16729
+ const bundles = internals.replayer.snapshot().filter((entry) => entry.kind === "decision" && entry.scope === callingState.scope && entry.value?.decisionType === "orchestrator_finish_validation_bundle").map((entry) => entry.value);
16730
+ const last = bundles.at(-1);
16731
+ if (last?.contractHash !== validationSpec.contract.hash) await internals.replayer.appendSinglePhase({
16732
+ scope: callingState.scope,
16733
+ key: `finish-validation-bundle:${String(bundles.length)}`,
16734
+ kind: "decision",
16735
+ status: "ok",
16736
+ spanId: internals.spans.mint(callingState.spanId),
16737
+ site: "orchestrator-finish-validation-bundle",
16738
+ value: {
16739
+ decisionType: "orchestrator_finish_validation_bundle",
16740
+ ordinal: bundles.length,
16741
+ contractHash: validationSpec.contract.hash,
16742
+ validators: validationSpec.validators.map((validator) => validator.name),
16743
+ maxRepairs: validationSpec.maxRepairs ?? 1,
16744
+ ...last?.contractHash === void 0 ? {} : { supersedes: last.contractHash }
16745
+ }
16746
+ });
16747
+ }
16232
16748
  const agentOpts = {
16233
16749
  role: "orchestrate",
16234
16750
  result: "full",
@@ -16243,7 +16759,10 @@ function makeOrchestratorWorkflow(goal, opts) {
16243
16759
  },
16244
16760
  [kTerminalTool]: {
16245
16761
  name: FINISH_TOOL_NAME,
16246
- ...validationSpec === void 0 || opts?.synthesis !== void 0 ? {} : { validate: validateFinish }
16762
+ ...validationSpec === void 0 || opts?.synthesis !== void 0 ? {} : {
16763
+ validate: validateFinish,
16764
+ ...validationSpec.repairTurnReserve === void 0 ? {} : { repairTurnReserve: validationSpec.repairTurnReserve }
16765
+ }
16247
16766
  },
16248
16767
  ...(() => {
16249
16768
  const priorCancelledRoot = internals.replayer.snapshot().filter((entry) => entry.kind === "agent" && entry.scope === callingState.scope && entry.status === "cancelled" && entry.checkpointRef !== void 0).at(-1);
@@ -16543,7 +17062,10 @@ function makeOrchestratorWorkflow(goal, opts) {
16543
17062
  ...spec.estCost === void 0 ? {} : { estCost: spec.estCost },
16544
17063
  [kTerminalTool]: {
16545
17064
  name: FINISH_TOOL_NAME,
16546
- ...validationSpec === void 0 ? {} : { validate: validateFinish }
17065
+ ...validationSpec === void 0 ? {} : {
17066
+ validate: validateFinish,
17067
+ ...validationSpec.repairTurnReserve === void 0 ? {} : { repairTurnReserve: validationSpec.repairTurnReserve }
17068
+ }
16547
17069
  }
16548
17070
  };
16549
17071
  const synthesized = await runtime.runInScope(synthesisState, () => ctx.agent(prompt, synthesisOpts));
@@ -16623,7 +17145,40 @@ function makeOrchestratorWorkflow(goal, opts) {
16623
17145
  if (validationTermination !== void 0) throw validationTermination;
16624
17146
  if (orchestratorAccount !== void 0) internals.cost.orchestrator.spentUsd = internals.budget.accountView(orchestratorAccount)?.spentUsd ?? 0;
16625
17147
  if (result.status !== "ok") throw new ConfigError(`the orchestrator agent terminated with status '${result.status}'` + (result.errorMessage === void 0 ? "" : `: ${result.errorMessage}`));
16626
- if (opts?.acceptance === void 0) return await runSynthesis(result.output);
17148
+ /**
17149
+ * The synthesis failure enrichment (the v1.71 experiment review,
17150
+ * P0.8 remainder + P1.7): every typed failure a synthesis
17151
+ * invocation throws gains the verdict-derived repair taxonomy read
17152
+ * from the JOURNALED validation decisions (identical live and on
17153
+ * replay), and, when an acceptance verdict exists, the acceptance
17154
+ * snapshot the children already earned; the completion mirror then
17155
+ * lifts completion and childStatusCounts onto the error outcome,
17156
+ * exactly like the acceptance rejection path. The experiment's
17157
+ * outcome showed completion null beside four accepted children;
17158
+ * these are the fields that say "the fan-out work is complete, the
17159
+ * failure is downstream". The rejection-past-the-bound error keeps
17160
+ * its own repairsUsed/maxRepairs/failed untouched.
17161
+ */
17162
+ const enrichSynthesisFailure = (thrown, snapshot) => {
17163
+ if (!(thrown instanceof FailRunError)) throw thrown;
17164
+ const base = thrown.data ?? {};
17165
+ const spent = validationDecisions().filter((candidate) => candidate.verdict !== "accepted");
17166
+ const rejectedValidators = [...new Set(spent.flatMap((candidate) => candidate.failed.map((f) => f.name)))];
17167
+ throw new FailRunError(thrown.message, { data: {
17168
+ ...base,
17169
+ ...snapshot ?? {},
17170
+ ...spent.length === 0 || base.repairsUsed !== void 0 ? {} : {
17171
+ repairsUsed: spent.length,
17172
+ maxRepairs: validationSpec?.maxRepairs ?? 1,
17173
+ rejectedValidators
17174
+ }
17175
+ } });
17176
+ };
17177
+ if (opts?.acceptance === void 0) try {
17178
+ return await runSynthesis(result.output);
17179
+ } catch (thrown) {
17180
+ return enrichSynthesisFailure(thrown);
17181
+ }
16627
17182
  const acceptanceKey = "acceptance";
16628
17183
  const priorAcceptance = internals.replayer.snapshot().find((entry) => entry.kind === "decision" && entry.scope === callingState.scope && entry.key === acceptanceKey);
16629
17184
  let decision;
@@ -16696,8 +17251,17 @@ function makeOrchestratorWorkflow(goal, opts) {
16696
17251
  ...decision.synthesisSkipped === void 0 ? {} : { synthesisSkipped: decision.synthesisSkipped }
16697
17252
  } });
16698
17253
  }
17254
+ let synthesizedFinal;
17255
+ try {
17256
+ synthesizedFinal = await runSynthesis(result.output);
17257
+ } catch (thrown) {
17258
+ enrichSynthesisFailure(thrown, {
17259
+ completion: decision.completion,
17260
+ childStatusCounts: decision.childStatusCounts
17261
+ });
17262
+ }
16699
17263
  return {
16700
- result: await runSynthesis(result.output),
17264
+ result: synthesizedFinal,
16701
17265
  completion: decision.completion,
16702
17266
  childStatusCounts: decision.childStatusCounts,
16703
17267
  degradedReasons: decision.degradedReasons,
@@ -16838,6 +17402,8 @@ function preflightEstimate(input) {
16838
17402
  const maxDepth = engine.budgetDefaults?.maxDepth ?? 1;
16839
17403
  const perRun = engine.concurrency?.perRun ?? 12;
16840
17404
  const runLimits = mergeUsageLimits(void 0, input.run?.limits, defaults.limits);
17405
+ const finishRepairReserve = input.finishValidation?.repairTurnReserve ?? 0;
17406
+ const coordinationRepairReserve = input.orchestrator?.synthesis === void 0 ? finishRepairReserve : 0;
16841
17407
  let orchestratorEcho;
16842
17408
  let reservedForFinalizationUsd = 0;
16843
17409
  let effectiveCapUsd;
@@ -16858,7 +17424,7 @@ function preflightEstimate(input) {
16858
17424
  finalizeReserveUsd,
16859
17425
  finalizeTurns,
16860
17426
  reserveCommitted,
16861
- projectedProviderTurns: projectedProviderTurnsOf(echoLimits, overallExecutedCeiling(echoLimits, toolCeilingsOf(echoLimits)))
17427
+ projectedProviderTurns: projectedProviderTurnsOf(echoLimits, overallExecutedCeiling(echoLimits, toolCeilingsOf(echoLimits))) + coordinationRepairReserve
16862
17428
  };
16863
17429
  if (spec?.capUsd !== void 0 && spec.capFraction === void 0 && effectiveCapUsd !== void 0 && effectiveCapUsd < spec.capUsd) say({
16864
17430
  severity: "warning",
@@ -17076,7 +17642,7 @@ function preflightEstimate(input) {
17076
17642
  model,
17077
17643
  inputFloor: input.orchestrator.estInputTokens ?? 0,
17078
17644
  outputBound: outputBound ?? 0,
17079
- turns: projectedProviderTurnsOf(orchLimits, overallExecutedCeiling(orchLimits, toolCeilingsOf(orchLimits))),
17645
+ turns: projectedProviderTurnsOf(orchLimits, overallExecutedCeiling(orchLimits, toolCeilingsOf(orchLimits))) + coordinationRepairReserve,
17080
17646
  count: 1
17081
17647
  });
17082
17648
  if (effectiveCapUsd !== void 0) orchestratorReserveUsd = Math.max(0, orchestratorAdmissionEstCostUsd(effectiveCapUsd, reservedForFinalizationUsd));
@@ -17088,6 +17654,38 @@ function preflightEstimate(input) {
17088
17654
  flatReserveUsd
17089
17655
  });
17090
17656
  }
17657
+ if (input.orchestrator.synthesis !== void 0) {
17658
+ const synthesis = input.orchestrator.synthesis;
17659
+ if (synthesis.limits !== void 0) validateUsageLimits(synthesis.limits, "preflight orchestrator.synthesis.limits");
17660
+ if (synthesis.estInputTokens !== void 0) requireNonNegativeInteger(synthesis.estInputTokens, "preflight orchestrator.synthesis.estInputTokens");
17661
+ const servedBy = resolveServing(synthesis.model) ?? resolveServing(defaults.routing?.synthesize);
17662
+ if (servedBy === void 0) say({
17663
+ severity: "error",
17664
+ code: "unrouted-role",
17665
+ message: "the synthesis invocation resolves no model for role 'synthesize': set defaults.routing.synthesize or a synthesis model on the call",
17666
+ spawn: "synthesis"
17667
+ });
17668
+ else {
17669
+ const caps = capsOf(servedBy);
17670
+ const merged = mergeUsageLimits(synthesis.limits ?? { maxTurns: 4 }, void 0, defaults.limits);
17671
+ const outputBound = caps === void 0 ? merged.maxOutputTokensPerTurn : merged.maxOutputTokensPerTurn === void 0 ? caps.maxOutputTokens : Math.min(caps.maxOutputTokens, merged.maxOutputTokensPerTurn);
17672
+ const projected = projectedProviderTurnsOf(merged, overallExecutedCeiling(merged, toolCeilingsOf(merged))) + finishRepairReserve;
17673
+ if (orchestratorEcho !== void 0) orchestratorEcho.synthesis = {
17674
+ projectedProviderTurns: projected,
17675
+ servedBy
17676
+ };
17677
+ const { adapterId, model } = parseModelRef(servedBy);
17678
+ shapes.push({
17679
+ label: "synthesis",
17680
+ provider: adapterId,
17681
+ model,
17682
+ inputFloor: synthesis.estInputTokens ?? 0,
17683
+ outputBound: outputBound ?? 0,
17684
+ turns: projected,
17685
+ count: 1
17686
+ });
17687
+ }
17688
+ }
17091
17689
  }
17092
17690
  const wave = [];
17093
17691
  let committed = 0;
@@ -17275,6 +17873,55 @@ function preflightEstimate(input) {
17275
17873
  requests: 0,
17276
17874
  tokens: 0
17277
17875
  });
17876
+ /**
17877
+ * The finish validation self test (the v1.71 experiment review,
17878
+ * P1.1): the SAME golden checks orchestrate runs at construction,
17879
+ * reported as error findings instead of throws. The experiment's
17880
+ * initial preflight was silent while a stale validator waited to
17881
+ * reject every correct answer; with the contract declared here, that
17882
+ * drift is a red finding before the first paid call.
17883
+ */
17884
+ let finishValidationEcho;
17885
+ if (input.finishValidation !== void 0) {
17886
+ const fv = input.finishValidation;
17887
+ if (!Array.isArray(fv.validators) || fv.validators.length === 0) throw new ConfigError("preflight finishValidation.validators must be a non empty array of validators");
17888
+ const names = /* @__PURE__ */ new Set();
17889
+ for (const candidate of fv.validators) {
17890
+ const validator = candidate;
17891
+ if (typeof validator.name !== "string" || validator.name.length === 0) throw new ConfigError("every preflight finish validator must carry a non empty name");
17892
+ if (typeof validator.validate !== "function") throw new ConfigError(`preflight finish validator '${validator.name}' has no validate function`);
17893
+ names.add(validator.name);
17894
+ }
17895
+ if (fv.repairTurnReserve !== void 0) requireNonNegativeInteger(fv.repairTurnReserve, "preflight finishValidation.repairTurnReserve");
17896
+ if (fv.contract !== void 0) {
17897
+ for (const contractValidator of fv.contract.validators) if (!names.has(contractValidator.name)) findings.push({
17898
+ severity: "error",
17899
+ code: "output-contract-validator-mismatch",
17900
+ message: `contract validator '${contractValidator.name}' is not in the configured validator set; the promised contract is not enforced`
17901
+ });
17902
+ }
17903
+ const acceptFixture = fv.selfTest?.accept ?? fv.contract?.goldenAccept;
17904
+ const rejectFixture = fv.selfTest?.reject ?? fv.contract?.goldenReject;
17905
+ let selfTestOutcome = "skipped";
17906
+ if (acceptFixture !== void 0 || rejectFixture !== void 0) {
17907
+ const report = selfTestFinishValidation({
17908
+ validators: fv.validators,
17909
+ ...acceptFixture === void 0 ? {} : { accept: acceptFixture },
17910
+ ...rejectFixture === void 0 ? {} : { reject: rejectFixture }
17911
+ });
17912
+ selfTestOutcome = report.ok ? "passed" : "failed";
17913
+ for (const failure of report.failures) findings.push({
17914
+ severity: "error",
17915
+ code: "output-contract-validator-mismatch",
17916
+ message: failure.validator === void 0 ? failure.reasons.join("; ") : `validator '${failure.validator}' rejected the golden accept fixture: ` + failure.reasons.join("; ")
17917
+ });
17918
+ }
17919
+ finishValidationEcho = {
17920
+ ...fv.contract === void 0 ? {} : { contractHash: fv.contract.hash },
17921
+ validators: fv.validators.map((validator) => validator.name),
17922
+ selfTest: selfTestOutcome
17923
+ };
17924
+ }
17278
17925
  const severityRank = {
17279
17926
  error: 0,
17280
17927
  warning: 1,
@@ -17314,6 +17961,7 @@ function preflightEstimate(input) {
17314
17961
  perProvider,
17315
17962
  ...runCeiling === void 0 ? {} : { runCeiling }
17316
17963
  },
17964
+ ...finishValidationEcho === void 0 ? {} : { finishValidation: finishValidationEcho },
17317
17965
  findings
17318
17966
  };
17319
17967
  }
@@ -17416,163 +18064,6 @@ var KeyedLimiter = class {
17416
18064
  }
17417
18065
  };
17418
18066
  //#endregion
17419
- //#region src/orchestrator/finish-validators.ts
17420
- /**
17421
- * Deterministic host validation of the orchestrator finish result (the
17422
- * v1.40.0 improvement plan's RV-204 slice). A validator is plain
17423
- * synchronous host code judging the finish({ result }) argument; the
17424
- * orchestrator runtime runs the configured set on every schema valid
17425
- * finish call, returns the failure reasons to the model as the call's
17426
- * error tool result (a bounded repair turn), and fails the run with a
17427
- * typed error when the repair bound is exhausted. Verdicts journal as
17428
- * decision entries, so a resume rolls the SAME verdicts forward without
17429
- * re-running validator code.
17430
- */
17431
- const ok = { ok: true };
17432
- function requireNonEmptyStrings(values, what) {
17433
- if (!Array.isArray(values) || values.length === 0) throw new ConfigError(`${what} must be a non empty array of strings`);
17434
- for (const value of values) if (typeof value !== "string" || value.length === 0) throw new ConfigError(`${what} must contain only non empty strings`);
17435
- return values;
17436
- }
17437
- /**
17438
- * Requires every named section to appear LITERALLY in the result text
17439
- * (a heading like 'FINDINGS' or any marker the goal demands). Default
17440
- * name 'required-sections'; pass `name` to run several instances.
17441
- */
17442
- function requiredSectionsValidator(options) {
17443
- const sections = requireNonEmptyStrings(options.sections, "requiredSectionsValidator sections");
17444
- return {
17445
- name: options.name ?? "required-sections",
17446
- validate: (input) => {
17447
- const missing = sections.filter((section) => !input.text.includes(section));
17448
- return missing.length === 0 ? ok : {
17449
- ok: false,
17450
- reasons: missing.map((section) => `required section '${section}' is missing`)
17451
- };
17452
- }
17453
- };
17454
- }
17455
- /**
17456
- * Requires the result to be a JSON object carrying every named field
17457
- * with a substantial value: present, not null, and not an empty or
17458
- * whitespace only string (empty arrays, zero, and false COUNT as
17459
- * present; emptiness rules beyond strings belong to a custom
17460
- * validator). Default name 'required-fields'.
17461
- */
17462
- function requiredFieldsValidator(options) {
17463
- const fields = requireNonEmptyStrings(options.fields, "requiredFieldsValidator fields");
17464
- return {
17465
- name: options.name ?? "required-fields",
17466
- validate: (input) => {
17467
- const result = input.result;
17468
- if (typeof result !== "object" || result === null || Array.isArray(result)) return {
17469
- ok: false,
17470
- reasons: ["the finish result is not a JSON object"]
17471
- };
17472
- const record = result;
17473
- const reasons = [];
17474
- for (const field of fields) {
17475
- const value = record[field];
17476
- if (value === void 0 || value === null) reasons.push(`required field '${field}' is missing`);
17477
- else if (typeof value === "string" && value.trim().length === 0) reasons.push(`required field '${field}' is empty`);
17478
- }
17479
- return reasons.length === 0 ? ok : {
17480
- ok: false,
17481
- reasons
17482
- };
17483
- }
17484
- };
17485
- }
17486
- /** The default citation shape: a path with an extension, a colon, a line number. */
17487
- const DEFAULT_CITATION_PATTERN = "[\\w./-]+\\.\\w+:\\d+";
17488
- /** The default preserved share, the improvement plan's RV-202 gate. */
17489
- const DEFAULT_EVIDENCE_MIN_SHARE = .95;
17490
- const MAX_LISTED_CITATIONS = 20;
17491
- function listCitations(values) {
17492
- return values.length <= MAX_LISTED_CITATIONS ? values.join(", ") : `${values.slice(0, MAX_LISTED_CITATIONS).join(", ")} and ${String(values.length - MAX_LISTED_CITATIONS)} more`;
17493
- }
17494
- /**
17495
- * The RV-202 evidence preservation contract: the finish result must
17496
- * PRESERVE the citations the children actually produced. Distinct
17497
- * matches of `pattern` are collected across the outputs of children
17498
- * settled 'ok' (spawn order); at least `minShare` of them (default
17499
- * {@link DEFAULT_EVIDENCE_MIN_SHARE}, the plan's 95 percent gate,
17500
- * compared as a ceiling on the required count so an exact boundary like
17501
- * 19 of 20 passes) must appear literally in the result text. Zero child
17502
- * citations pass vacuously. With `requireKnown: true` the contract also
17503
- * runs in reverse: every citation in the RESULT must appear in some
17504
- * child's output, so a fabricated but pattern valid citation is
17505
- * rejected instead of silently counting as evidence. Rejection reasons
17506
- * list the missing (and unknown) citations, capped at 20, so the repair
17507
- * turn can restore them. Purely textual and deterministic; checking
17508
- * that cited targets EXIST on disk is host territory (a custom
17509
- * validator), not this contract. Default name 'evidence-preserved'.
17510
- */
17511
- function evidencePreservedValidator(options) {
17512
- const pattern = options?.pattern ?? "[\\w./-]+\\.\\w+:\\d+";
17513
- const flags = options?.flags ?? "";
17514
- const globalFlags = flags.includes("g") ? flags : `${flags}g`;
17515
- try {
17516
- new RegExp(pattern, globalFlags);
17517
- } catch (thrown) {
17518
- throw new ConfigError(`evidencePreservedValidator pattern does not compile: ${thrown instanceof Error ? thrown.message : String(thrown)}`);
17519
- }
17520
- const minShare = options?.minShare ?? .95;
17521
- if (typeof minShare !== "number" || !Number.isFinite(minShare) || minShare <= 0 || minShare > 1) throw new ConfigError(`evidencePreservedValidator minShare must be a number in (0, 1]; got ${String(minShare)}`);
17522
- return {
17523
- name: options?.name ?? "evidence-preserved",
17524
- validate: (input) => {
17525
- const cited = /* @__PURE__ */ new Set();
17526
- for (const child of input.children ?? []) {
17527
- if (child.status !== "ok" && child.salvageableOutput !== true) continue;
17528
- for (const match of child.text.match(new RegExp(pattern, globalFlags)) ?? []) cited.add(match);
17529
- }
17530
- const reasons = [];
17531
- if (cited.size > 0) {
17532
- const missing = [...cited].filter((citation) => !input.text.includes(citation));
17533
- const preserved = cited.size - missing.length;
17534
- if (preserved < Math.ceil(minShare * cited.size - 1e-9)) reasons.push(`evidence preservation ${String(preserved)} of ${String(cited.size)} child citations is below the required share ${String(minShare)}; missing: ${listCitations(missing)}`);
17535
- }
17536
- if (options?.requireKnown === true) {
17537
- const fabricated = [...new Set(input.text.match(new RegExp(pattern, globalFlags)) ?? [])].filter((citation) => !cited.has(citation));
17538
- if (fabricated.length > 0) reasons.push(`unknown citations not present in any child report: ${listCitations(fabricated)}`);
17539
- }
17540
- return reasons.length === 0 ? ok : {
17541
- ok: false,
17542
- reasons
17543
- };
17544
- }
17545
- };
17546
- }
17547
- /**
17548
- * Requires at least `min` matches of `pattern` in the result text (the
17549
- * plan's citation and source count checks: a file:line pattern, a URL
17550
- * pattern). The pattern compiles at construction (invalid patterns are a
17551
- * ConfigError before any run exists) and matches globally; `min` is a
17552
- * positive integer. Default name 'min-matches'; pass `name` to run
17553
- * several instances, because names must be unique per orchestrate call.
17554
- */
17555
- function minMatchesValidator(options) {
17556
- const flags = options.flags ?? "";
17557
- const globalFlags = flags.includes("g") ? flags : `${flags}g`;
17558
- try {
17559
- new RegExp(options.pattern, globalFlags);
17560
- } catch (thrown) {
17561
- throw new ConfigError(`minMatchesValidator pattern does not compile: ${thrown instanceof Error ? thrown.message : String(thrown)}`);
17562
- }
17563
- if (!Number.isInteger(options.min) || options.min < 1) throw new ConfigError(`minMatchesValidator min must be a positive integer; got ${String(options.min)}`);
17564
- return {
17565
- name: options.name ?? "min-matches",
17566
- validate: (input) => {
17567
- const found = input.text.match(new RegExp(options.pattern, globalFlags))?.length ?? 0;
17568
- return found >= options.min ? ok : {
17569
- ok: false,
17570
- reasons: [`expected at least ${String(options.min)} matches of /${options.pattern}/${flags}; found ${String(found)}`]
17571
- };
17572
- }
17573
- };
17574
- }
17575
- //#endregion
17576
18067
  //#region src/engine/events.ts
17577
18068
  /**
17578
18069
  * Per-run event machinery (M1-T10): the span registry (run > phase >
@@ -19250,4 +19741,4 @@ function createSandboxBridge(ctx, options) {
19250
19741
  };
19251
19742
  }
19252
19743
  //#endregion
19253
- export { AWAIT_SCHEMA, AdmissionController, AdmissionRejectedError, AgentCallError, BUDGET_ABORT_REASON, BudgetExhaustedError, CANCEL_AGENT_SCHEMA, CHECKPOINT_FORMAT_V1, CLAIM_STATEMENT_MAX_CHARS, CLAIM_TTL_DAYS, COMPACTION_SUMMARY_PREFIX, CURRENT_HASH_VERSION, ConfigError, DEFAULT_CHILD_BUDGET_FRACTION, DEFAULT_CHILD_RESULT_PAGE_CHARS, DEFAULT_CITATION_PATTERN, DEFAULT_COMPACTION_THRESHOLD, DEFAULT_ESCALATION_LIMITS, DEFAULT_EVIDENCE_MIN_SHARE, DEFAULT_FINISH_MAX_REPAIRS, DEFAULT_FLAT_RESERVE_USD, DEFAULT_MAX_CHILDREN_PER_NODE, DEFAULT_MAX_DEPTH, DEFAULT_MAX_OSCILLATIONS_PER_KEY, DEFAULT_MAX_PINNED_WORKTREES, DEFAULT_MAX_REVISIONS_PER_RUN, DEFAULT_MAX_TOTAL_SPAWNS, DEFAULT_MAX_TURNS, DEFAULT_MODEL_RETRY_ATTEMPTS, DEFAULT_NO_PROGRESS_TURNS, DEFAULT_PER_RUN_CONCURRENCY, DEFAULT_RETRY_POLICY, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DEFAULT_SYNTHESIS_MAX_TURNS, DEFAULT_SYNTHESIS_NOTE_MAX_TURNS, DedupIndex, DeterminismError, EMIT_RESULT_TOOL, EMPTY_SCHEMA_HASH, EMPTY_TOOLSET_HASH, ESCALATE_TOOL_NAME, ESCALATION_REPORT_SCHEMA, ESCALATION_REQUEST_SCHEMA, EVENT_SEGMENT_STRIDE, EscalationDecisionAbortedError, EventBus, ExternalRegistry, FINALIZE_SYNTHESIS_INSTRUCTION, FINISH_SCHEMA, FINISH_TOOL_NAME, FailRunError, FileModelKnowledgeStore, FileTranscriptStore, GET_CHILD_RESULT_SCHEMA, GET_CHILD_RESULT_TOOL_NAME, GitWorktreeProvider, IMPLEMENTATION_PROFILE_LIMITS, INBOX_PROPOSAL_TTL_DAYS, InMemoryStore, InMemoryTranscriptStore, InProcessRunner, InvalidResolutionError, JOURNAL_ENVELOPE_MARKER, JournalCompatibilityError, JournalMatcher, JournalMissError, JournalOrderViolation, JsonlFileStore, KB_ACTIVE_CLAIMS_CAP, KB_CARD_RENDER_BUDGET_CHARS, KeyedLimiter, KnowledgeCasError, LARGE_VALUE_WARN_BYTES, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LINEAGE_SIG_VERSION, LeaseHeldError, LineageIndex, MASKED_SECRET, MAX_CHILD_RESULT_PAGE_CHARS, MAX_DEPTH_CEILING, ModelRetry, NoProgressDetector, NonSerializableValueError, ORCHESTRATE_WORKFLOW_NAME, OrchestratorCapConfigError, PARALLEL_AGENTS_SCHEMA, PROGRESS_REPORT_TOOL_NAME, ParallelSiteCounter, PlanInvariantError, QUOTA_WINDOW_MS, READ_CHILD_ARTIFACT_SCHEMA, READ_CHILD_ARTIFACT_TOOL_NAME, RESEARCH_PROFILE_LIMITS, REVIEW_PROFILE_LIMITS, ROLE_EFFORT_DEFAULTS, ROOT_ACCOUNT, ROOT_SCOPE, RUN_PROFILES, RUN_SETTLE_DECISION_TYPE, ReplayPlanHashMismatch, Replayer, ResolutionArbiter, ResolutionFold, RulvarError, RunBudget, SANDBOX_AGENT_OPT_KEYS, SPAWN_AGENT_SCHEMA, SandboxError, ScriptRejected, Semaphore, SettlementError, SpanRegistry, TOOL_NAME_PATTERN, TerminationAccount, WAIT_FOR_EVENTS_SCHEMA, WAIT_FOR_EVENTS_TOOL_NAME, WAKE_SUMMARY_RENDER_BUDGET_CHARS, admissionReserveUsd, affordableOutputTokens, agentErrorFromWire, agentErrorToWire, agentResultWire, agentScope, applyClaimOps, applyStructuredOutputTier, approachSigCoarse, approachSigOf, archiveDeprecatedModelOps, assertFencedWrites, atCompactionThreshold, auditRun, auditRuns, buildAbandonFold, buildAdapterRegistry, buildCostReport, buildDeriverRegistry, buildOrchestratorTools, buildTerminationInitValue, buildToolContext, canRideLoopTurn, canonicalIsolationTag, canonicalizeLadder, canonicalizeSchema, capIssues, capsHashOf, checkFloors, checkpointRefFor, childCoveragePrefix, claimExpired, claimExpiry, claimIssues, claimOpIssues, classifyAgentError, classifyAttemptOutcome, collectDeclaredLadders, compactMessages, compilePermissionChain, compilePermissionPreset, compileSecretMasker, compileVerifiedLayer, constantTimeEqual, costReportFromJournal, countsAgainstLimit, createCanonicalIdMinter, createCtx, createEngine, createEnvelopeEncryption, createSandboxBridge, currentOnlyKeyRing, decodeCheckpoint, dedupeRepeatedClaims, defineWorkflow, deriveContentKey, deriverV1, deriverV2, digestOf, dispatchProjectionReserveUsd, dispositionHook, emptyDigestBlocks, emptyToolset, encodeCheckpoint, entryUsageSlices, escalateTool, evaluatePermission, evaluateReuse, evidencePreservedValidator, executeWorkflow, exhaustionCodeOf, extractCandidate, failoverTriggerOf, fallbackTriggerOf, filterClaimsForRun, finalizeFires, foldTermination, formatRePrompt, formatScopePath, hasFencedWrites, hasMetaLookup, hashRunArgs, hashRunOutput, hashWorkflowBody, hashWorkflowSource, identityJcs, implementationAgentProfile, invoiceFromJournal, isEscalated, isSchemaPairSpec, isStandardSchemaSpec, isStrictCompatibleSchema, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, lastRunSettle, latestProgressReport, lexShellCommand, liftRetainedParts, lineageWeightOf, localKeyProvider, makeOrchestratorWorkflow, maskSecrets, maskSecretsDeep, maskSecretsJson, matchArgvPattern, matchShellCommand, mcp, memoryQuotaLimiter, mergeQuotaDenial, mergeUsageLimits, metaMatchesFilter, minMatchesValidator, modelEpochOf, modelKnowledgeCard, modelSpecIdentity, needsSeparateExtract, nextFailover, nodeLinkKey, normalizeApproachTag, normalizeEntry, normalizeFallbacks, orchestrate, orchestratorAdmissionEstCostUsd, parallelScope, parseModelRef, parseScopePath, phiInitialOf, pipelineScope, planNodeScope, preflightEstimate, priceEntryUsage, priceUsdOf, profileCard, profileRegistrySnapshotHash, progressReportTool, projectHistory, projectIdentity, projectToJsonSchema, proposalStatement, providerOf, quotaActualTokens, quotaEstimateTokens, quotaRuleAdmission, quotaRuleMatches, readRunMeta, readTerminationInit, reconcileRunMeta, reduceAuditTrail, reduceCriticalPath, reduceInvocationTable, registryKeyRing, remeasureQueue, replayDisposition, repositoryResearchToolset, requiredFieldsValidator, requiredSectionsValidator, researchAgentProfile, resolveModelInvocation, resolvePricing, resolveToolset, retryClassOf, retryDelayMs, reviewAgentProfile, roleConfiguredInRouting, roundOneDisposition, runAgent, runProfile, sanitizeTerminalText, sanitizeTokenCount, sanitizeUsage, sanitizeUsageDelta, scanJournalCompatibility, schemaHash, schemaHashOfSpec, selectStructuredOutputTier, shouldCompact, snapshotUsage, spawnDepthOf, summarizeInstruction, summarizeOutput, terminationConfigDrift, tierWithinCaps, toApprovalDecision, toJournalValue, tool, toolContract, toolsetHash, ttlState, usageViolations, validateEditorialCommit, validateEngineQuotaConfig, validateEntryShape, validateEscalationLimits, validateEscalationReport, validateQuotaRules, validateRetryPolicy, validateSchemaSpec, validateTerminationLimits, validateUsageLimits, workflowScope, workflowSourceRef, wrapJournalStore, wrapTranscriptStore };
19744
+ export { AWAIT_SCHEMA, AdmissionController, AdmissionRejectedError, AgentCallError, BUDGET_ABORT_REASON, BudgetExhaustedError, CANCEL_AGENT_SCHEMA, CHECKPOINT_FORMAT_V1, CLAIM_STATEMENT_MAX_CHARS, CLAIM_TTL_DAYS, COMPACTION_SUMMARY_PREFIX, CURRENT_HASH_VERSION, ConfigError, DEFAULT_CHILD_BUDGET_FRACTION, DEFAULT_CHILD_RESULT_PAGE_CHARS, DEFAULT_CITATION_PATTERN, DEFAULT_CITATION_SAMPLE, DEFAULT_COMPACTION_THRESHOLD, DEFAULT_ESCALATION_LIMITS, DEFAULT_EVIDENCE_MIN_SHARE, DEFAULT_FINISH_MAX_REPAIRS, DEFAULT_FLAT_RESERVE_USD, DEFAULT_MAX_CHILDREN_PER_NODE, DEFAULT_MAX_DEPTH, DEFAULT_MAX_OSCILLATIONS_PER_KEY, DEFAULT_MAX_PINNED_WORKTREES, DEFAULT_MAX_REVISIONS_PER_RUN, DEFAULT_MAX_TOTAL_SPAWNS, DEFAULT_MAX_TURNS, DEFAULT_MODEL_RETRY_ATTEMPTS, DEFAULT_NO_PROGRESS_TURNS, DEFAULT_PER_RUN_CONCURRENCY, DEFAULT_RETRY_POLICY, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DEFAULT_SYNTHESIS_MAX_TURNS, DEFAULT_SYNTHESIS_NOTE_MAX_TURNS, DedupIndex, DeterminismError, EMIT_RESULT_TOOL, EMPTY_SCHEMA_HASH, EMPTY_TOOLSET_HASH, ESCALATE_TOOL_NAME, ESCALATION_REPORT_SCHEMA, ESCALATION_REQUEST_SCHEMA, EVENT_SEGMENT_STRIDE, EscalationDecisionAbortedError, EventBus, ExternalRegistry, FINALIZE_SYNTHESIS_INSTRUCTION, FINISH_SCHEMA, FINISH_TOOL_NAME, FailRunError, FileModelKnowledgeStore, FileTranscriptStore, GET_CHILD_RESULT_SCHEMA, GET_CHILD_RESULT_TOOL_NAME, GitWorktreeProvider, IMPLEMENTATION_PROFILE_LIMITS, INBOX_PROPOSAL_TTL_DAYS, InMemoryStore, InMemoryTranscriptStore, InProcessRunner, InvalidResolutionError, JOURNAL_ENVELOPE_MARKER, JournalCompatibilityError, JournalMatcher, JournalMissError, JournalOrderViolation, JsonlFileStore, KB_ACTIVE_CLAIMS_CAP, KB_CARD_RENDER_BUDGET_CHARS, KeyedLimiter, KnowledgeCasError, LARGE_VALUE_WARN_BYTES, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LINEAGE_SIG_VERSION, LeaseHeldError, LineageIndex, MASKED_SECRET, MAX_CHILD_RESULT_PAGE_CHARS, MAX_DEPTH_CEILING, ModelRetry, NoProgressDetector, NonSerializableValueError, ORCHESTRATE_WORKFLOW_NAME, OrchestratorCapConfigError, PARALLEL_AGENTS_SCHEMA, PROGRESS_REPORT_TOOL_NAME, ParallelSiteCounter, PlanInvariantError, QUOTA_WINDOW_MS, READ_CHILD_ARTIFACT_SCHEMA, READ_CHILD_ARTIFACT_TOOL_NAME, RESEARCH_PROFILE_LIMITS, REVIEW_PROFILE_LIMITS, ROLE_EFFORT_DEFAULTS, ROOT_ACCOUNT, ROOT_SCOPE, RUN_PROFILES, RUN_SETTLE_DECISION_TYPE, ReplayPlanHashMismatch, Replayer, ResolutionArbiter, ResolutionFold, RulvarError, RunBudget, SANDBOX_AGENT_OPT_KEYS, SPAWN_AGENT_SCHEMA, SandboxError, ScriptRejected, Semaphore, SettlementError, SpanRegistry, TOOL_NAME_PATTERN, TerminationAccount, WAIT_FOR_EVENTS_SCHEMA, WAIT_FOR_EVENTS_TOOL_NAME, WAKE_SUMMARY_RENDER_BUDGET_CHARS, admissionReserveUsd, affordableOutputTokens, agentErrorFromWire, agentErrorToWire, agentResultWire, agentScope, applyClaimOps, applyStructuredOutputTier, approachSigCoarse, approachSigOf, archiveDeprecatedModelOps, assertFencedWrites, atCompactionThreshold, auditRun, auditRuns, buildAbandonFold, buildAdapterRegistry, buildCostReport, buildDeriverRegistry, buildOrchestratorTools, buildTerminationInitValue, buildToolContext, canRideLoopTurn, canonicalIsolationTag, canonicalizeLadder, canonicalizeSchema, capIssues, capsHashOf, checkFloors, checkpointRefFor, childCoveragePrefix, claimExpired, claimExpiry, claimIssues, claimOpIssues, classifyAgentError, classifyAttemptOutcome, collectDeclaredLadders, compactMessages, compilePermissionChain, compilePermissionPreset, compileSecretMasker, compileVerifiedLayer, constantTimeEqual, costReportFromJournal, countsAgainstLimit, createCanonicalIdMinter, createCtx, createEngine, createEnvelopeEncryption, createSandboxBridge, currentOnlyKeyRing, decodeCheckpoint, dedupeRepeatedClaims, defineWorkflow, deriveContentKey, deriverV1, deriverV2, digestOf, dispatchProjectionReserveUsd, dispositionHook, emptyDigestBlocks, emptyToolset, encodeCheckpoint, entryUsageSlices, escalateTool, evaluatePermission, evaluateReuse, evidencePreservedValidator, executeWorkflow, exhaustionCodeOf, extractCandidate, failoverTriggerOf, fallbackTriggerOf, filterClaimsForRun, finalizeFires, finishContract, foldTermination, formatRePrompt, formatScopePath, hasFencedWrites, hasMetaLookup, hashRunArgs, hashRunOutput, hashWorkflowBody, hashWorkflowSource, identityJcs, implementationAgentProfile, invoiceFromJournal, isEscalated, isSchemaPairSpec, isStandardSchemaSpec, isStrictCompatibleSchema, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, lastRunSettle, latestProgressReport, lexShellCommand, liftRetainedParts, lineageWeightOf, localKeyProvider, makeOrchestratorWorkflow, maskSecrets, maskSecretsDeep, maskSecretsJson, matchArgvPattern, matchShellCommand, mcp, memoryQuotaLimiter, mergeQuotaDenial, mergeUsageLimits, metaMatchesFilter, minMatchesValidator, modelEpochOf, modelKnowledgeCard, modelSpecIdentity, needsSeparateExtract, nextFailover, nodeLinkKey, normalizeApproachTag, normalizeEntry, normalizeFallbacks, orchestrate, orchestratorAdmissionEstCostUsd, parallelScope, parseModelRef, parseScopePath, phiInitialOf, pipelineScope, planNodeScope, preflightEstimate, priceEntryUsage, priceUsdOf, profileCard, profileRegistrySnapshotHash, progressReportTool, projectHistory, projectIdentity, projectToJsonSchema, proposalStatement, providerOf, quotaActualTokens, quotaEstimateTokens, quotaRuleAdmission, quotaRuleMatches, readRunMeta, readTerminationInit, reconcileRunMeta, reduceAuditTrail, reduceCriticalPath, reduceInvocationTable, registryKeyRing, remeasureQueue, replayDisposition, repositoryResearchToolset, requiredFieldsValidator, requiredSectionsValidator, researchAgentProfile, resolveModelInvocation, resolvePricing, resolveToolset, retryClassOf, retryDelayMs, reviewAgentProfile, roleConfiguredInRouting, roundOneDisposition, runAgent, runProfile, sanitizeTerminalText, sanitizeTokenCount, sanitizeUsage, sanitizeUsageDelta, scanJournalCompatibility, schemaHash, schemaHashOfSpec, sectionCitationsValidator, selectStructuredOutputTier, selfTestFinishValidation, shouldCompact, snapshotUsage, spawnDepthOf, summarizeInstruction, summarizeOutput, terminationConfigDrift, tierWithinCaps, toApprovalDecision, toJournalValue, tool, toolContract, toolsetHash, ttlState, usageViolations, validateEditorialCommit, validateEngineQuotaConfig, validateEntryShape, validateEscalationLimits, validateEscalationReport, validateQuotaRules, validateRetryPolicy, validateSchemaSpec, validateTerminationLimits, validateUsageLimits, wordCountValidator, workflowScope, workflowSourceRef, wrapJournalStore, wrapTranscriptStore };