@rulvar/core 1.3.0 → 1.3.1
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.js +56 -56
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -348,7 +348,7 @@ const PINNED_FIELDS = [
|
|
|
348
348
|
"status"
|
|
349
349
|
];
|
|
350
350
|
function assertPinnedFields(before, after, site) {
|
|
351
|
-
for (const field of PINNED_FIELDS) if (before[field] !== after[field]) throw new ConfigError(`serialization hook ${site} modified the kernel field '${field}' (
|
|
351
|
+
for (const field of PINNED_FIELDS) if (before[field] !== after[field]) throw new ConfigError(`serialization hook ${site} modified the kernel field '${field}' (ordering and identity fields MUST pass through unmodified)`);
|
|
352
352
|
}
|
|
353
353
|
/** Wraps a journal store with the hook; lease capability is preserved. */
|
|
354
354
|
function wrapJournalStore(inner, hook) {
|
|
@@ -1602,11 +1602,11 @@ function findAnchor(node, anchor) {
|
|
|
1602
1602
|
}
|
|
1603
1603
|
function canonicalizeNode(node, root, refStack) {
|
|
1604
1604
|
if (typeof node === "boolean") return node;
|
|
1605
|
-
if ("$dynamicRef" in node || "$dynamicAnchor" in node) throw new ConfigError("Dynamic references ($dynamicRef/$dynamicAnchor) are forbidden in rulvar schemas
|
|
1605
|
+
if ("$dynamicRef" in node || "$dynamicAnchor" in node) throw new ConfigError("Dynamic references ($dynamicRef/$dynamicAnchor) are forbidden in rulvar schemas ");
|
|
1606
1606
|
const ref = node.$ref;
|
|
1607
1607
|
if (typeof ref === "string") {
|
|
1608
|
-
if (!ref.startsWith("#")) throw new ConfigError(`Remote $ref '${ref}' is forbidden in rulvar schemas; only fragment-only local references resolve
|
|
1609
|
-
if (refStack.includes(ref)) throw new ConfigError(`Recursive local $ref '${ref}' cannot be inlined; recursive schemas are not canonicalizable
|
|
1608
|
+
if (!ref.startsWith("#")) throw new ConfigError(`Remote $ref '${ref}' is forbidden in rulvar schemas; only fragment-only local references resolve`);
|
|
1609
|
+
if (refStack.includes(ref)) throw new ConfigError(`Recursive local $ref '${ref}' cannot be inlined; recursive schemas are not canonicalizable`);
|
|
1610
1610
|
const target = ref.startsWith("#/") ? resolvePointer(root, ref.slice(1)) : ref === "#" ? root : findAnchor(root, ref.slice(1));
|
|
1611
1611
|
if (target === void 0 || typeof target !== "object" && typeof target !== "boolean") throw new ConfigError(`Local $ref '${ref}' does not resolve to a schema`);
|
|
1612
1612
|
refStack.push(ref);
|
|
@@ -1879,7 +1879,7 @@ function gateIssues(gate, path) {
|
|
|
1879
1879
|
return issues;
|
|
1880
1880
|
}
|
|
1881
1881
|
const ruledOut = gate.attribution?.ruledOut;
|
|
1882
|
-
if (!Array.isArray(ruledOut) || ruledOut.length === 0) issues.push(`${path}: the human gate requires the attribution attestation (a non-empty ruledOut checklist
|
|
1882
|
+
if (!Array.isArray(ruledOut) || ruledOut.length === 0) issues.push(`${path}: the human gate requires the attribution attestation (a non-empty ruledOut checklist)`);
|
|
1883
1883
|
else for (const entry of ruledOut) if (typeof entry !== "string" || !RULED_OUT_VOCABULARY.has(entry)) issues.push(`${path}: ruledOut entry '${String(entry)}' is outside the checklist`);
|
|
1884
1884
|
if (typeof gate.approver !== "string" || gate.approver.length === 0) issues.push(`${path}: the human gate requires an approver`);
|
|
1885
1885
|
return issues;
|
|
@@ -1897,8 +1897,8 @@ function claimIssues(claim, path, options) {
|
|
|
1897
1897
|
if (Number.isNaN(Date.parse(claim.expiresAt))) issues.push(`${path}: expiresAt is not a date`);
|
|
1898
1898
|
else if (!Number.isNaN(Date.parse(claim.observedAt)) && Date.parse(claim.expiresAt) <= Date.parse(claim.observedAt)) issues.push(`${path}: expiresAt must follow observedAt`);
|
|
1899
1899
|
if (options?.evalCommitter !== true) {
|
|
1900
|
-
if (claim.class === "eval-measured") issues.push(`${path}: eval-measured claims are committable only under the eval-committer gate (the eval-committer identity
|
|
1901
|
-
if (claim.metrics !== void 0) issues.push(`${path}: the metrics block is writable only by the eval-committer identity
|
|
1900
|
+
if (claim.class === "eval-measured") issues.push(`${path}: eval-measured claims are committable only under the eval-committer gate (the eval-committer identity); the editorial path carries human-editorial only`);
|
|
1901
|
+
if (claim.metrics !== void 0) issues.push(`${path}: the metrics block is writable only by the eval-committer identity `);
|
|
1902
1902
|
}
|
|
1903
1903
|
if (options?.evalCommitter === true && claim.class === "eval-measured") {
|
|
1904
1904
|
if (claim.metrics === void 0) issues.push(`${path}: an eval-measured claim carries its metrics block`);
|
|
@@ -1947,7 +1947,7 @@ function capIssues(claims, cap = 8) {
|
|
|
1947
1947
|
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
1948
1948
|
}
|
|
1949
1949
|
const issues = [];
|
|
1950
|
-
for (const [key, count] of counts) if (count > cap) issues.push(`active claims for (${key}) would reach ${String(count)}, over the cap ${String(cap)}
|
|
1950
|
+
for (const [key, count] of counts) if (count > cap) issues.push(`active claims for (${key}) would reach ${String(count)}, over the cap ${String(cap)}; supersede or archive first`);
|
|
1951
1951
|
return issues.sort();
|
|
1952
1952
|
}
|
|
1953
1953
|
/**
|
|
@@ -2137,10 +2137,10 @@ function checkFloors(options) {
|
|
|
2137
2137
|
const { ref, role, floors, taskClass } = options;
|
|
2138
2138
|
if (floors === void 0) return;
|
|
2139
2139
|
const roleViolation = violates(ref, floors.byRole?.[role]);
|
|
2140
|
-
if (roleViolation !== void 0) throw new ConfigError(`quality floor violation: '${ref}' is floored out for role '${role}' (${roleViolation}); floors are hard router constraints
|
|
2140
|
+
if (roleViolation !== void 0) throw new ConfigError(`quality floor violation: '${ref}' is floored out for role '${role}' (${roleViolation}); floors are hard router constraints`);
|
|
2141
2141
|
if (taskClass !== void 0) {
|
|
2142
2142
|
const classViolation = violates(ref, floors.byTaskClass?.[taskClass]);
|
|
2143
|
-
if (classViolation !== void 0) throw new ConfigError(`quality floor violation: '${ref}' is floored out for taskClass '${taskClass}' (${classViolation}); floors are hard router constraints
|
|
2143
|
+
if (classViolation !== void 0) throw new ConfigError(`quality floor violation: '${ref}' is floored out for taskClass '${taskClass}' (${classViolation}); floors are hard router constraints`);
|
|
2144
2144
|
}
|
|
2145
2145
|
}
|
|
2146
2146
|
//#endregion
|
|
@@ -2523,7 +2523,7 @@ const TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
|
2523
2523
|
* reference all fail here.
|
|
2524
2524
|
*/
|
|
2525
2525
|
function tool(init) {
|
|
2526
|
-
if (!TOOL_NAME_PATTERN.test(init.name)) throw new ConfigError(`tool name '${init.name}' must match ^[a-zA-Z0-9_-]{1,64}$ (docs/
|
|
2526
|
+
if (!TOOL_NAME_PATTERN.test(init.name)) throw new ConfigError(`tool name '${init.name}' must match ^[a-zA-Z0-9_-]{1,64}$ (https://docs.rulvar.com/guide/tools)`);
|
|
2527
2527
|
canonicalizeSchema(projectToJsonSchema(init.parameters));
|
|
2528
2528
|
return {
|
|
2529
2529
|
kind: "tool",
|
|
@@ -2583,7 +2583,7 @@ async function resolveToolset(specs, session) {
|
|
|
2583
2583
|
if (specs === void 0 || specs.length === 0) return emptyToolset();
|
|
2584
2584
|
const tools = [];
|
|
2585
2585
|
for (const spec of specs) {
|
|
2586
|
-
if (typeof spec === "string") throw new ConfigError(`tools by registered name ('${spec}') resolve only inside the worker sandbox; name-based tool registries
|
|
2586
|
+
if (typeof spec === "string") throw new ConfigError(`tools by registered name ('${spec}') resolve only inside the worker sandbox; name-based tool registries exist for compiled scripts only (https://docs.rulvar.com/guide/planner)`);
|
|
2587
2587
|
if (isToolDef(spec)) {
|
|
2588
2588
|
tools.push(spec);
|
|
2589
2589
|
continue;
|
|
@@ -2593,9 +2593,9 @@ async function resolveToolset(specs, session) {
|
|
|
2593
2593
|
}
|
|
2594
2594
|
const seen = /* @__PURE__ */ new Map();
|
|
2595
2595
|
for (const def of tools) {
|
|
2596
|
-
if (!TOOL_NAME_PATTERN.test(def.name)) throw new ConfigError(`imported tool name '${def.name}' must match ^[a-zA-Z0-9_-]{1,64}$; namespace it with the source prefix option
|
|
2597
|
-
if (seen.has(def.name)) throw new ConfigError(`duplicate tool name '${def.name}' in one toolset; disambiguate with the MCP prefix option
|
|
2598
|
-
if (def.executor !== "inprocess") throw new ConfigError(`tool '${def.name}' declares executor '${def.executor}', but this engine implements only 'inprocess' in v1
|
|
2596
|
+
if (!TOOL_NAME_PATTERN.test(def.name)) throw new ConfigError(`imported tool name '${def.name}' must match ^[a-zA-Z0-9_-]{1,64}$; namespace it with the source prefix option`);
|
|
2597
|
+
if (seen.has(def.name)) throw new ConfigError(`duplicate tool name '${def.name}' in one toolset; disambiguate with the MCP prefix option`);
|
|
2598
|
+
if (def.executor !== "inprocess") throw new ConfigError(`tool '${def.name}' declares executor '${def.executor}', but this engine implements only 'inprocess' in v1`);
|
|
2599
2599
|
seen.set(def.name, def);
|
|
2600
2600
|
}
|
|
2601
2601
|
const contracts = tools.map((def) => toolContract(def));
|
|
@@ -2637,7 +2637,7 @@ function buildToolContext(seed) {
|
|
|
2637
2637
|
*/
|
|
2638
2638
|
function validateConfig(cfg) {
|
|
2639
2639
|
const forbid = (key) => {
|
|
2640
|
-
if (cfg[key] !== void 0) throw new ConfigError(`mcp: '${key}' is not a config key of the '${cfg.transport}' transport (
|
|
2640
|
+
if (cfg[key] !== void 0) throw new ConfigError(`mcp: '${key}' is not a config key of the '${cfg.transport}' transport (exactly the keys matching the chosen transport)`);
|
|
2641
2641
|
};
|
|
2642
2642
|
switch (cfg.transport) {
|
|
2643
2643
|
case "stdio":
|
|
@@ -2732,7 +2732,7 @@ function mcp(cfg) {
|
|
|
2732
2732
|
};
|
|
2733
2733
|
const toDef = (client, wire) => {
|
|
2734
2734
|
const name = cfg.prefix === void 0 ? wire.name : `${cfg.prefix}_${wire.name}`;
|
|
2735
|
-
if (!TOOL_NAME_PATTERN.test(name)) throw new ConfigError(`mcp: imported tool name '${name}' must match ^[a-zA-Z0-9_-]{1,64}$
|
|
2735
|
+
if (!TOOL_NAME_PATTERN.test(name)) throw new ConfigError(`mcp: imported tool name '${name}' must match ^[a-zA-Z0-9_-]{1,64}$ `);
|
|
2736
2736
|
const risk = cfg.risk?.[wire.name];
|
|
2737
2737
|
return tool({
|
|
2738
2738
|
name,
|
|
@@ -2826,7 +2826,7 @@ var GitWorktreeProvider = class {
|
|
|
2826
2826
|
try {
|
|
2827
2827
|
await git(this.repoRoot, ["rev-parse", "--git-common-dir"]);
|
|
2828
2828
|
} catch {
|
|
2829
|
-
throw new ConfigError(`worktree isolation requires a git repository at '${this.repoRoot}'
|
|
2829
|
+
throw new ConfigError(`worktree isolation requires a git repository at '${this.repoRoot}' `);
|
|
2830
2830
|
}
|
|
2831
2831
|
const dir = await mkdtemp(join(tmpdir(), `rulvar-wt-${spawn.runId.slice(0, 8)}-`));
|
|
2832
2832
|
await git(this.repoRoot, [
|
|
@@ -2860,7 +2860,7 @@ var GitWorktreeProvider = class {
|
|
|
2860
2860
|
this.pinned.add(dir);
|
|
2861
2861
|
return;
|
|
2862
2862
|
}
|
|
2863
|
-
this.onWarn(`worktree pin cap (${this.maxPinned}) reached; dropping the tree of a failed agent instead of retaining it
|
|
2863
|
+
this.onWarn(`worktree pin cap (${this.maxPinned}) reached; dropping the tree of a failed agent instead of retaining it`);
|
|
2864
2864
|
}
|
|
2865
2865
|
try {
|
|
2866
2866
|
await git(this.repoRoot, [
|
|
@@ -3175,7 +3175,7 @@ function isKeyDeriver(value) {
|
|
|
3175
3175
|
function buildDeriverRegistry(extraDerivers) {
|
|
3176
3176
|
const registry = /* @__PURE__ */ new Map([[deriverV1.hashVersion, deriverV1], [deriverV2.hashVersion, deriverV2]]);
|
|
3177
3177
|
for (const extra of extraDerivers ?? []) {
|
|
3178
|
-
if (!isKeyDeriver(extra)) throw new ConfigError("extraDerivers entries must implement the KeyDeriver SPI
|
|
3178
|
+
if (!isKeyDeriver(extra)) throw new ConfigError("extraDerivers entries must implement the KeyDeriver SPI");
|
|
3179
3179
|
registry.set(extra.hashVersion, extra);
|
|
3180
3180
|
}
|
|
3181
3181
|
return registry;
|
|
@@ -3372,7 +3372,7 @@ const DEFAULT_ESCALATION_LIMITS = {
|
|
|
3372
3372
|
* change semantics (per logical task, not per node).
|
|
3373
3373
|
*/
|
|
3374
3374
|
function validateEscalationLimits(raw) {
|
|
3375
|
-
if (raw !== void 0 && "maxEscalationsPerNode" in raw) throw new ConfigError("config knob 'maxEscalationsPerNode' was renamed: escalations are counted per logical task across respawns via the lineage chain; use 'maxEscalationsPerLogicalTask' (XF-10
|
|
3375
|
+
if (raw !== void 0 && "maxEscalationsPerNode" in raw) throw new ConfigError("config knob 'maxEscalationsPerNode' was renamed: escalations are counted per logical task across respawns via the lineage chain; use 'maxEscalationsPerLogicalTask' (XF-10)");
|
|
3376
3376
|
const limits = { ...DEFAULT_ESCALATION_LIMITS };
|
|
3377
3377
|
if (raw?.maxEscalationsPerLogicalTask !== void 0) limits.maxEscalationsPerLogicalTask = requireCount(raw.maxEscalationsPerLogicalTask, "maxEscalationsPerLogicalTask");
|
|
3378
3378
|
if (raw?.maxAttemptsPerLogicalTask !== void 0) limits.maxAttemptsPerLogicalTask = requireCount(raw.maxAttemptsPerLogicalTask, "maxAttemptsPerLogicalTask");
|
|
@@ -3850,7 +3850,7 @@ function profileRegistrySnapshotHash(profiles) {
|
|
|
3850
3850
|
* must be non-negative integers; kMax at least 1.
|
|
3851
3851
|
*/
|
|
3852
3852
|
function validateTerminationLimits(raw) {
|
|
3853
|
-
if ("maxEscalationsPerNode" in raw) throw new ConfigError("config knob 'maxEscalationsPerNode' was renamed: escalations are counted per logical task across respawns via the lineage chain; use 'maxEscalationsPerLogicalTask' (XF-10
|
|
3853
|
+
if ("maxEscalationsPerNode" in raw) throw new ConfigError("config knob 'maxEscalationsPerNode' was renamed: escalations are counted per logical task across respawns via the lineage chain; use 'maxEscalationsPerLogicalTask' (XF-10)");
|
|
3854
3854
|
const record = raw;
|
|
3855
3855
|
const count = (name, fallback) => {
|
|
3856
3856
|
const value = record[name] ?? fallback;
|
|
@@ -3990,7 +3990,7 @@ var TerminationAccount = class {
|
|
|
3990
3990
|
};
|
|
3991
3991
|
if (lineage !== void 0) {
|
|
3992
3992
|
const ladderLength = lineage.ladderLength ?? 1;
|
|
3993
|
-
if (ladderLength > this.limits.kMax) throw new ConfigError(`ladder length ${String(ladderLength)} exceeds the frozen kMax ${String(this.limits.kMax)}; admit() must reject with ladder_exceeds_frozen before debiting
|
|
3993
|
+
if (ladderLength > this.limits.kMax) throw new ConfigError(`ladder length ${String(ladderLength)} exceeds the frozen kMax ${String(this.limits.kMax)}; admit() must reject with ladder_exceeds_frozen before debiting`);
|
|
3994
3994
|
if (lineage.isNew && !this.lineages.has(lineage.logicalTaskId)) this.lineages.set(lineage.logicalTaskId, {
|
|
3995
3995
|
escalationUnitsRemaining: this.limits.maxEscalationsPerLogicalTask,
|
|
3996
3996
|
rungsRemaining: ladderLength - 1,
|
|
@@ -4067,7 +4067,7 @@ var TerminationAccount = class {
|
|
|
4067
4067
|
async debit(resource, lineage, context) {
|
|
4068
4068
|
const attempt = this.tryDebit(resource, lineage);
|
|
4069
4069
|
if (attempt.ok) return attempt;
|
|
4070
|
-
if (this.deniedWriter === void 0) throw new ConfigError(`termination debit of ${resource} underflowed and no deniedWriter is bound; the denied entry MUST precede the surfaced error
|
|
4070
|
+
if (this.deniedWriter === void 0) throw new ConfigError(`termination debit of ${resource} underflowed and no deniedWriter is bound; the denied entry MUST precede the surfaced error`);
|
|
4071
4071
|
return {
|
|
4072
4072
|
ok: false,
|
|
4073
4073
|
deniedEntryRef: await this.deniedWriter({
|
|
@@ -4174,7 +4174,7 @@ function foldTermination(entries) {
|
|
|
4174
4174
|
/** First-closing-wins over resolution targets (DEF-4): losers never debit. */
|
|
4175
4175
|
const closedTargets = /* @__PURE__ */ new Set();
|
|
4176
4176
|
const assertBalance = (entry, what, embedded, recomputed) => {
|
|
4177
|
-
if (typeof embedded === "number" && embedded !== recomputed) throw new PlanInvariantError(`termination fold divergence at seq ${String(entry.seq)}: ${what} recomputes to ${String(recomputed)} but the entry embeds ${String(embedded)} (
|
|
4177
|
+
if (typeof embedded === "number" && embedded !== recomputed) throw new PlanInvariantError(`termination fold divergence at seq ${String(entry.seq)}: ${what} recomputes to ${String(recomputed)} but the entry embeds ${String(embedded)} (the debit fold is authoritative)`, { data: {
|
|
4178
4178
|
entryRef: entry.seq,
|
|
4179
4179
|
what,
|
|
4180
4180
|
embedded,
|
|
@@ -4712,7 +4712,7 @@ function validateEntryShape(entry) {
|
|
|
4712
4712
|
return issues;
|
|
4713
4713
|
}
|
|
4714
4714
|
const legal = LEGAL_STATUSES[entry.kind];
|
|
4715
|
-
if (legal !== void 0 && !legal.includes(entry.status)) issues.push(issue(`status '${entry.status}' is not legal for kind '${entry.kind}'
|
|
4715
|
+
if (legal !== void 0 && !legal.includes(entry.status)) issues.push(issue(`status '${entry.status}' is not legal for kind '${entry.kind}'`));
|
|
4716
4716
|
if (entry.status === "skipped") issues.push(issue("'skipped' is a derived fold status and is never persisted"));
|
|
4717
4717
|
if (REF_ENTRY_KINDS$1.has(entry.kind)) {
|
|
4718
4718
|
if (entry.ref === void 0) issues.push(issue(`ref-entry kind '${entry.kind}' requires ref (the target seq)`));
|
|
@@ -4734,10 +4734,10 @@ function validateEntryShape(entry) {
|
|
|
4734
4734
|
if (payload === void 0 || typeof payload.decisionType !== "string") issues.push(issue("decision entries carry a decisionType discriminator", ["value"]));
|
|
4735
4735
|
}
|
|
4736
4736
|
if (entry.kind === "external" || entry.kind === "approval") {
|
|
4737
|
-
if (entry.kind === "external" && entry.deadlineAt !== void 0) issues.push(issue("awaitExternal has NO deadline in v1
|
|
4737
|
+
if (entry.kind === "external" && entry.deadlineAt !== void 0) issues.push(issue("awaitExternal has NO deadline in v1"));
|
|
4738
4738
|
}
|
|
4739
4739
|
if (entry.deadlineAt !== void 0 && entry.status !== "suspended") issues.push(issue("deadlineAt is legal only on suspended entries"));
|
|
4740
|
-
if (entry.status === "escalated" && entry.escalation === void 0) issues.push(issue("terminal escalated entries carry the validated EscalationReport
|
|
4740
|
+
if (entry.status === "escalated" && entry.escalation === void 0) issues.push(issue("terminal escalated entries carry the validated EscalationReport"));
|
|
4741
4741
|
if (entry.escalation !== void 0 && entry.status !== "escalated") issues.push(issue("the escalation payload is legal only on status 'escalated'"));
|
|
4742
4742
|
return issues;
|
|
4743
4743
|
}
|
|
@@ -5638,7 +5638,7 @@ var ExternalRegistry = class ExternalRegistry {
|
|
|
5638
5638
|
*/
|
|
5639
5639
|
async awaitExternal(scope, spanId, key, options) {
|
|
5640
5640
|
const scopeKey = `${scope}${key}`;
|
|
5641
|
-
if (this.keysByScope.has(scopeKey)) throw new ConfigError(`duplicate awaitExternal key '${key}' in scope '${scope}'
|
|
5641
|
+
if (this.keysByScope.has(scopeKey)) throw new ConfigError(`duplicate awaitExternal key '${key}' in scope '${scope}'`);
|
|
5642
5642
|
this.keysByScope.add(scopeKey);
|
|
5643
5643
|
const identity = {
|
|
5644
5644
|
kind: "external",
|
|
@@ -6778,7 +6778,7 @@ function resolveModelInvocation(options) {
|
|
|
6778
6778
|
if (fields.ladder !== void 0) delete merged.model;
|
|
6779
6779
|
else if (fields.model !== void 0) delete merged.ladder;
|
|
6780
6780
|
}
|
|
6781
|
-
if (merged.ladder !== void 0) throw new ConfigError(`a ladder ModelSpec wins wire resolution for role '${role}': ladder execution is owned by the PlanRunner ladder driver, which resolves each rung attempt to a concrete model override
|
|
6781
|
+
if (merged.ladder !== void 0) throw new ConfigError(`a ladder ModelSpec wins wire resolution for role '${role}': ladder execution is owned by the PlanRunner ladder driver, which resolves each rung attempt to a concrete model override; dispatch laddered profiles through orchestratePlanned or pass a plain ModelRef or ModelChoice`);
|
|
6782
6782
|
if (merged.model === void 0) throw new ConfigError(`no model resolves for role '${role}': set AgentOpts.model, a profile model, or engine defaults.routing`);
|
|
6783
6783
|
checkFloors({
|
|
6784
6784
|
ref: merged.model,
|
|
@@ -6847,7 +6847,7 @@ const TRIGGER_CLASSES = [
|
|
|
6847
6847
|
];
|
|
6848
6848
|
function validateGate(gate, rungCount, index) {
|
|
6849
6849
|
if (gate.kind === "mechanical") {
|
|
6850
|
-
if (typeof gate.profile !== "string" || gate.profile === "") throw new ConfigError(`ladder acceptance gate ${String(index)}: a mechanical gate names a registered gate profile
|
|
6850
|
+
if (typeof gate.profile !== "string" || gate.profile === "") throw new ConfigError(`ladder acceptance gate ${String(index)}: a mechanical gate names a registered gate profile`);
|
|
6851
6851
|
return;
|
|
6852
6852
|
}
|
|
6853
6853
|
if (gate.kind === "judge") {
|
|
@@ -6869,16 +6869,16 @@ function validateGate(gate, rungCount, index) {
|
|
|
6869
6869
|
* member by declaration).
|
|
6870
6870
|
*/
|
|
6871
6871
|
function canonicalizeLadder(spec, options) {
|
|
6872
|
-
if (!Array.isArray(spec.rungs) || spec.rungs.length === 0) throw new ConfigError("a ladder declares at least one rung
|
|
6872
|
+
if (!Array.isArray(spec.rungs) || spec.rungs.length === 0) throw new ConfigError("a ladder declares at least one rung");
|
|
6873
6873
|
if (!Number.isInteger(spec.startTier) || spec.startTier < 0 || spec.startTier >= spec.rungs.length) throw new ConfigError(`ladder startTier ${String(spec.startTier)} is not a declared rung index of a ${String(spec.rungs.length)}-rung ladder`);
|
|
6874
|
-
for (const trigger of spec.escalateOn) if (!TRIGGER_CLASSES.includes(trigger)) throw new ConfigError(`unknown ladder trigger '${String(trigger)}': the vocabulary is closed to ${TRIGGER_CLASSES.join(", ")}
|
|
6874
|
+
for (const trigger of spec.escalateOn) if (!TRIGGER_CLASSES.includes(trigger)) throw new ConfigError(`unknown ladder trigger '${String(trigger)}': the vocabulary is closed to ${TRIGGER_CLASSES.join(", ")}`);
|
|
6875
6875
|
const rungs = spec.rungs.map((rung, index) => {
|
|
6876
6876
|
parseModelRef(rung.model);
|
|
6877
6877
|
if (!Number.isInteger(rung.maxTurns) || rung.maxTurns <= 0) throw new ConfigError(`ladder rung ${String(index)}: maxTurns is a positive integer`);
|
|
6878
6878
|
if (!Number.isInteger(rung.maxTokens) || rung.maxTokens <= 0) throw new ConfigError(`ladder rung ${String(index)}: maxTokens is a positive integer`);
|
|
6879
6879
|
if (rung.maxCostUsd !== void 0 && !(rung.maxCostUsd > 0)) throw new ConfigError(`ladder rung ${String(index)}: maxCostUsd is positive when present`);
|
|
6880
6880
|
const effort = rung.effort ?? options?.chainEffort;
|
|
6881
|
-
if (effort === void 0) throw new ConfigError(`ladder rung ${String(index)} resolves no effort: the canonical ladder embeds explicit efforts
|
|
6881
|
+
if (effort === void 0) throw new ConfigError(`ladder rung ${String(index)} resolves no effort: the canonical ladder embeds explicit efforts; declare rung.effort or a chain effort`);
|
|
6882
6882
|
return {
|
|
6883
6883
|
model: rung.model,
|
|
6884
6884
|
effort,
|
|
@@ -8658,7 +8658,7 @@ var RunBudget = class {
|
|
|
8658
8658
|
let cursor = scope;
|
|
8659
8659
|
while (cursor !== void 0) {
|
|
8660
8660
|
const account = this.accounts.get(cursor);
|
|
8661
|
-
if (account === void 0) throw new ConfigError(`unknown budget account '${cursor}': openAccount precedes any charge
|
|
8661
|
+
if (account === void 0) throw new ConfigError(`unknown budget account '${cursor}': openAccount precedes any charge`);
|
|
8662
8662
|
chain.push(account);
|
|
8663
8663
|
cursor = account.parentScope;
|
|
8664
8664
|
}
|
|
@@ -8912,7 +8912,7 @@ var AdmissionController = class {
|
|
|
8912
8912
|
admittedTotal = 0;
|
|
8913
8913
|
constructor(options) {
|
|
8914
8914
|
const maxDepth = options.maxDepth ?? 1;
|
|
8915
|
-
if (maxDepth < 1 || maxDepth > 4) throw new ConfigError(`maxDepth ${String(maxDepth)} is outside [1, ${String(4)}] (
|
|
8915
|
+
if (maxDepth < 1 || maxDepth > 4) throw new ConfigError(`maxDepth ${String(maxDepth)} is outside [1, ${String(4)}] (default 1, hard ceiling 4)`);
|
|
8916
8916
|
this.budget = options.budget;
|
|
8917
8917
|
this.maxDepth = maxDepth;
|
|
8918
8918
|
this.maxChildrenPerNode = options.maxChildrenPerNode ?? 16;
|
|
@@ -8944,7 +8944,7 @@ var AdmissionController = class {
|
|
|
8944
8944
|
* keep the engine lifetime cap semantics unchanged.
|
|
8945
8945
|
*/
|
|
8946
8946
|
bindTermination(account) {
|
|
8947
|
-
if (this.terminationAccount !== void 0 && this.terminationAccount !== account) throw new ConfigError("one run carries exactly one TerminationAccount
|
|
8947
|
+
if (this.terminationAccount !== void 0 && this.terminationAccount !== account) throw new ConfigError("one run carries exactly one TerminationAccount");
|
|
8948
8948
|
this.terminationAccount = account;
|
|
8949
8949
|
}
|
|
8950
8950
|
/** The bound account, when this is a PlanRunner run (DEF-2). */
|
|
@@ -8960,7 +8960,7 @@ var AdmissionController = class {
|
|
|
8960
8960
|
* (`lineage_exhausted`); never touches budget or structural limits.
|
|
8961
8961
|
*/
|
|
8962
8962
|
evaluateLineage(spec) {
|
|
8963
|
-
if (spec.lineage !== void 0 && typeof spec.lineage.causeRef !== "number") throw new ConfigError("a lineage continuation demands a causeRef: the seq of the entry that caused the rebirth
|
|
8963
|
+
if (spec.lineage !== void 0 && typeof spec.lineage.causeRef !== "number") throw new ConfigError("a lineage continuation demands a causeRef: the seq of the entry that caused the rebirth");
|
|
8964
8964
|
const index = this.lineage();
|
|
8965
8965
|
const continued = spec.lineage?.continues;
|
|
8966
8966
|
const statsBefore = index !== void 0 && continued !== void 0 ? index.statsOf(continued) : void 0;
|
|
@@ -9671,7 +9671,7 @@ function createCtx(internals) {
|
|
|
9671
9671
|
});
|
|
9672
9672
|
};
|
|
9673
9673
|
const isolation = opts.isolation ?? profile?.isolation ?? "none";
|
|
9674
|
-
if (typeof isolation === "object" && isolation.kind === "worktree" && internals.isolation === void 0) throw new ConfigError("worktree isolation requires an IsolationProvider: pass defaults.isolation to createEngine
|
|
9674
|
+
if (typeof isolation === "object" && isolation.kind === "worktree" && internals.isolation === void 0) throw new ConfigError("worktree isolation requires an IsolationProvider: pass defaults.isolation to createEngine");
|
|
9675
9675
|
const floorContext = {
|
|
9676
9676
|
...internals.floors === void 0 ? {} : { floors: internals.floors },
|
|
9677
9677
|
...profile?.taskClass === void 0 ? {} : { taskClass: profile.taskClass }
|
|
@@ -9717,9 +9717,9 @@ function createCtx(internals) {
|
|
|
9717
9717
|
}
|
|
9718
9718
|
const escalation = opts.escalation ?? profile?.escalation;
|
|
9719
9719
|
if (escalation !== void 0) {
|
|
9720
|
-
if (escalation.flavor === "B" && escalation.deadlineMs === void 0) throw new ConfigError("flavor 'B' escalation requires an explicit deadlineMs: the suspension deadline has no engine default
|
|
9720
|
+
if (escalation.flavor === "B" && escalation.deadlineMs === void 0) throw new ConfigError("flavor 'B' escalation requires an explicit deadlineMs: the suspension deadline has no engine default");
|
|
9721
9721
|
if (opts.result !== "full" && internals.onEscalation === void 0) throw new ConfigError("a spawn that opts into escalation from a plain value-form call needs an onEscalation hook (or use result: 'full')");
|
|
9722
|
-
if (escalation.flavor === "B" && escalation.deadlineMs === void 0) throw new ConfigError("escalation flavor 'B' requires an explicit deadlineMs
|
|
9722
|
+
if (escalation.flavor === "B" && escalation.deadlineMs === void 0) throw new ConfigError("escalation flavor 'B' requires an explicit deadlineMs");
|
|
9723
9723
|
}
|
|
9724
9724
|
const declaredTools = opts.tools ?? profile?.tools ?? [];
|
|
9725
9725
|
const toolset = await resolveToolset(escalation === void 0 ? declaredTools : [...declaredTools, escalateTool()], { runId: internals.runId });
|
|
@@ -10225,7 +10225,7 @@ function createCtx(internals) {
|
|
|
10225
10225
|
if (internals.external === void 0) throw new ConfigError("flavor B escalation requires the engine run context");
|
|
10226
10226
|
const request = result.escalationRequest;
|
|
10227
10227
|
const deadlineMs = escalation.deadlineMs;
|
|
10228
|
-
if (deadlineMs === void 0) throw new ConfigError("flavor 'B' escalation requires an explicit deadlineMs
|
|
10228
|
+
if (deadlineMs === void 0) throw new ConfigError("flavor 'B' escalation requires an explicit deadlineMs");
|
|
10229
10229
|
const defaultDecision = escalation.defaultDecision ?? { kind: "accept" };
|
|
10230
10230
|
let timer;
|
|
10231
10231
|
const decisionOutcome = await internals.external.awaitDecision({
|
|
@@ -10550,9 +10550,9 @@ function createCtx(internals) {
|
|
|
10550
10550
|
let name;
|
|
10551
10551
|
if (typeof wfOrName === "string") {
|
|
10552
10552
|
const registered = internals.defaults.workflows?.[wfOrName];
|
|
10553
|
-
if (registered === void 0) throw new ConfigError(`unknown workflow '${wfOrName}': register it under defaults.workflows
|
|
10553
|
+
if (registered === void 0) throw new ConfigError(`unknown workflow '${wfOrName}': register it under defaults.workflows`);
|
|
10554
10554
|
const candidate = registered;
|
|
10555
|
-
if (candidate.kind !== "workflow") throw new ConfigError(`registry entry '${wfOrName}' is not a defineWorkflow value
|
|
10555
|
+
if (candidate.kind !== "workflow") throw new ConfigError(`registry entry '${wfOrName}' is not a defineWorkflow value`);
|
|
10556
10556
|
wf = candidate;
|
|
10557
10557
|
name = wfOrName;
|
|
10558
10558
|
} else {
|
|
@@ -10881,12 +10881,12 @@ function resolveDispatchOpts(spec, defaults) {
|
|
|
10881
10881
|
const opts = {};
|
|
10882
10882
|
if (spec.outputSchemaRef !== void 0) {
|
|
10883
10883
|
const schema = defaults.schemas?.[spec.outputSchemaRef];
|
|
10884
|
-
if (schema === void 0) throw new ConfigError(`unknown outputSchemaRef '${spec.outputSchemaRef}': register it under defaults.schemas
|
|
10884
|
+
if (schema === void 0) throw new ConfigError(`unknown outputSchemaRef '${spec.outputSchemaRef}': register it under defaults.schemas`);
|
|
10885
10885
|
opts.schema = schema;
|
|
10886
10886
|
}
|
|
10887
10887
|
if (spec.toolsetRef !== void 0) {
|
|
10888
10888
|
const tools = defaults.toolsets?.[spec.toolsetRef];
|
|
10889
|
-
if (tools === void 0) throw new ConfigError(`unknown toolsetRef '${spec.toolsetRef}': register it under defaults.toolsets (docs/
|
|
10889
|
+
if (tools === void 0) throw new ConfigError(`unknown toolsetRef '${spec.toolsetRef}': register it under defaults.toolsets (https://docs.rulvar.com/guide/tools)`);
|
|
10890
10890
|
opts.tools = tools;
|
|
10891
10891
|
}
|
|
10892
10892
|
const extended = spec;
|
|
@@ -10936,16 +10936,16 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
10936
10936
|
const runCeiling = internals.budget.accountView(callingState.budgetScope ?? "run")?.ceilingUsd;
|
|
10937
10937
|
const spec = opts?.budget;
|
|
10938
10938
|
const fraction = spec?.capFraction ?? .2;
|
|
10939
|
-
if (fraction > 1) throw new OrchestratorCapConfigError(`capFraction ${String(fraction)} exceeds 1.0 (
|
|
10939
|
+
if (fraction > 1) throw new OrchestratorCapConfigError(`capFraction ${String(fraction)} exceeds 1.0 (opting out of the cap is explicit only, up to 1.0 inclusive)`);
|
|
10940
10940
|
const fromFraction = runCeiling === void 0 ? void 0 : fraction * runCeiling;
|
|
10941
10941
|
const bounds = [spec?.capUsd, fromFraction].filter((bound) => bound !== void 0);
|
|
10942
|
-
if (extension !== void 0 && bounds.length === 0) throw new OrchestratorCapConfigError("the orchestrator cap is unresolvable: the run has no USD ceiling and no explicit budget.capUsd; PlanRunner requires a resolved effectiveCap
|
|
10942
|
+
if (extension !== void 0 && bounds.length === 0) throw new OrchestratorCapConfigError("the orchestrator cap is unresolvable: the run has no USD ceiling and no explicit budget.capUsd; PlanRunner requires a resolved effectiveCap");
|
|
10943
10943
|
if (bounds.length > 0) {
|
|
10944
10944
|
const effectiveCapUsd = Math.min(...bounds);
|
|
10945
10945
|
const turnEstimateUsd = internals.flatReserveUsd ?? .5;
|
|
10946
10946
|
const finalizeTurns = spec?.finalizeTurns ?? 2;
|
|
10947
10947
|
const finalizeReserveUsd = spec?.finalizeReserveUsd ?? finalizeTurns * turnEstimateUsd;
|
|
10948
|
-
if (extension !== void 0 && effectiveCapUsd < finalizeReserveUsd) throw new OrchestratorCapConfigError(`effectiveCap ${effectiveCapUsd.toFixed(4)} USD is below the finalize reserve ${finalizeReserveUsd.toFixed(4)} USD
|
|
10948
|
+
if (extension !== void 0 && effectiveCapUsd < finalizeReserveUsd) throw new OrchestratorCapConfigError(`effectiveCap ${effectiveCapUsd.toFixed(4)} USD is below the finalize reserve ${finalizeReserveUsd.toFixed(4)} USD`);
|
|
10949
10949
|
orchestratorAccount = callingState.scope === "" ? "orchestrator" : `${callingState.scope}/orchestrator`;
|
|
10950
10950
|
internals.budget.openAccount(orchestratorAccount, {
|
|
10951
10951
|
parentScope: callingState.budgetScope ?? "run",
|
|
@@ -11282,7 +11282,7 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
11282
11282
|
const scope = childScopeOf();
|
|
11283
11283
|
const profile = internals.defaults.profiles?.[params.agentType];
|
|
11284
11284
|
const profileModel = profile?.model;
|
|
11285
|
-
if (profileModel !== void 0 && typeof profileModel !== "string" && "ladder" in profileModel) throw new ConfigError(`agentType '${params.agentType}' declares a ladder; ladder execution is owned by the plan extension, which resolves each rung attempt to a concrete model override
|
|
11285
|
+
if (profileModel !== void 0 && typeof profileModel !== "string" && "ladder" in profileModel) throw new ConfigError(`agentType '${params.agentType}' declares a ladder; ladder execution is owned by the plan extension, which resolves each rung attempt to a concrete model override; spawn a concrete profile instead`);
|
|
11286
11286
|
const decision = admission.admit({
|
|
11287
11287
|
origin: "spawn_agent",
|
|
11288
11288
|
name: params.agentType,
|
|
@@ -11817,7 +11817,7 @@ function createEngine(options) {
|
|
|
11817
11817
|
function run(wf, args, opts, resumeCtx) {
|
|
11818
11818
|
if (wf.kind !== "workflow" && wf.kind !== "compiled-workflow") throw new ConfigError("engine.run accepts in-process Workflow values or compileScript CompiledWorkflow values");
|
|
11819
11819
|
const compiled = wf.kind === "compiled-workflow" ? wf : void 0;
|
|
11820
|
-
if (compiled !== void 0 && options.runners?.sandbox === void 0) throw new ConfigError("running a CompiledWorkflow requires a sandbox runner: pass createEngine({ runners: { sandbox: new WorkerSandboxRunner() } }) from @rulvar/planner
|
|
11820
|
+
if (compiled !== void 0 && options.runners?.sandbox === void 0) throw new ConfigError("running a CompiledWorkflow requires a sandbox runner: pass createEngine({ runners: { sandbox: new WorkerSandboxRunner() } }) from @rulvar/planner ");
|
|
11821
11821
|
const runId = resumeCtx?.runId ?? opts?.runId ?? mintRunId();
|
|
11822
11822
|
const registry = buildDeriverRegistry(options.extraDerivers);
|
|
11823
11823
|
const spans = new SpanRegistry();
|
|
@@ -12087,12 +12087,12 @@ function createEngine(options) {
|
|
|
12087
12087
|
if (supplied === void 0 && meta?.workflowSourceRef === void 0) {
|
|
12088
12088
|
const name = meta?.workflowName;
|
|
12089
12089
|
const registered = name === void 0 ? void 0 : defaults.workflows?.[name];
|
|
12090
|
-
if (registered === void 0) throw new ConfigError(`engine.resume(runId) with no workflow resolves by the RunMeta-recorded name from defaults.workflows
|
|
12090
|
+
if (registered === void 0) throw new ConfigError(`engine.resume(runId) with no workflow resolves by the RunMeta-recorded name from defaults.workflows; run '${runId}' records ` + (name === void 0 ? "no workflowName" : `workflow '${name}', which is not registered`) + "; register it under defaults.workflows or pass the workflow value");
|
|
12091
12091
|
supplied = registered;
|
|
12092
12092
|
}
|
|
12093
12093
|
let bound;
|
|
12094
12094
|
if (supplied === void 0) {
|
|
12095
|
-
if (meta?.workflowSourceRef === void 0) throw new ConfigError("engine.resume requires the workflow for in-process runs (docs/
|
|
12095
|
+
if (meta?.workflowSourceRef === void 0) throw new ConfigError("engine.resume requires the workflow for in-process runs (https://docs.rulvar.com/guide/durability); only compiled runs with a persisted source resume bare");
|
|
12096
12096
|
const blob = await transcripts.get(meta.workflowSourceRef);
|
|
12097
12097
|
if (blob === null) throw new ConfigError(`resume: run '${runId}' records workflowSourceRef '${meta.workflowSourceRef}' but the transcript store has no such blob`);
|
|
12098
12098
|
const source = new TextDecoder().decode(blob);
|
|
@@ -12107,7 +12107,7 @@ function createEngine(options) {
|
|
|
12107
12107
|
if (meta?.workflowName !== void 0 && meta.workflowName !== supplied.name) throw new ConfigError(`resume binding mismatch: run '${runId}' was started by workflow '${meta.workflowName}', not '${supplied.name}'`);
|
|
12108
12108
|
if (supplied.kind === "compiled-workflow") {
|
|
12109
12109
|
const expectedHash = hashWorkflowSource(supplied.source);
|
|
12110
|
-
if (meta?.workflowHash !== void 0 && meta.workflowHash !== expectedHash) throw new ConfigError(`resume binding mismatch: the supplied CompiledWorkflow source hash differs from the one recorded for run '${runId}'
|
|
12110
|
+
if (meta?.workflowHash !== void 0 && meta.workflowHash !== expectedHash) throw new ConfigError(`resume binding mismatch: the supplied CompiledWorkflow source hash differs from the one recorded for run '${runId}'`);
|
|
12111
12111
|
} else {
|
|
12112
12112
|
const expectedHash = hashWorkflowBody(supplied);
|
|
12113
12113
|
if (meta?.workflowHash !== void 0 && meta.workflowHash !== expectedHash) process.emitWarning(`resume: the body of workflow '${supplied.name}' changed since run '${runId}' started; orphans and misses will be reported honestly`, {
|
|
@@ -12318,7 +12318,7 @@ function createSandboxBridge(ctx, options) {
|
|
|
12318
12318
|
for (const key of Object.keys(rawOpts)) if (!SANDBOX_AGENT_OPT_KEYS.has(key)) throw new ConfigError(`sandbox agent option '${key}' is outside the sanctioned dialect; allowed: ` + [...SANDBOX_AGENT_OPT_KEYS].sort().join(", "));
|
|
12319
12319
|
if (rawOpts.tools !== void 0) {
|
|
12320
12320
|
const tools = rawOpts.tools;
|
|
12321
|
-
if (!(Array.isArray(tools) && tools.every((v) => typeof v === "string"))) throw new ConfigError("sandbox agent tools must be registered profile NAMES
|
|
12321
|
+
if (!(Array.isArray(tools) && tools.every((v) => typeof v === "string"))) throw new ConfigError("sandbox agent tools must be registered profile NAMES");
|
|
12322
12322
|
}
|
|
12323
12323
|
const opts = rawOpts;
|
|
12324
12324
|
return toJournalValue(await ctx.agent(record.prompt, opts) ?? null, "sandbox agent result");
|
|
@@ -12334,7 +12334,7 @@ function createSandboxBridge(ctx, options) {
|
|
|
12334
12334
|
}
|
|
12335
12335
|
case "workflow": {
|
|
12336
12336
|
const record = asRecord(params, "workflow params");
|
|
12337
|
-
if (typeof record.name !== "string") throw new ConfigError("sandbox workflow calls take a registered workflow NAME
|
|
12337
|
+
if (typeof record.name !== "string") throw new ConfigError("sandbox workflow calls take a registered workflow NAME");
|
|
12338
12338
|
const callOpts = {};
|
|
12339
12339
|
if (typeof record.key === "string") callOpts.key = record.key;
|
|
12340
12340
|
return toJournalValue(await ctx.workflow(record.name, record.args ?? null, callOpts) ?? null, "sandbox workflow result");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/core",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
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",
|