@rulvar/core 1.3.0 → 1.3.2

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/README.md +40 -0
  2. package/dist/index.js +56 -56
  3. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # @rulvar/core
2
+
3
+ The rulvar engine in one dependency-light package: the L0 contracts and
4
+ SPI interfaces, the journal kernel behind the never-pay-twice invariant,
5
+ the model router with the capability and price registry, the agent
6
+ runtime, the tool system and MCP bus, the `ctx` primitives and run
7
+ engine, the dynamic orchestrator, the in-memory and JSONL reference
8
+ stores, and the typed event stream. Zero provider SDK dependencies:
9
+ adapters plug in from their own packages. Key exports: `createEngine`,
10
+ `defineWorkflow`, `tool`, `mcp`, `orchestrate`, `InMemoryStore`,
11
+ `JsonlFileStore`.
12
+
13
+ Part of [rulvar](https://rulvar.com), an embeddable TypeScript engine
14
+ for durable, budget-bounded multi-agent LLM workflows, where a completed
15
+ LLM call is never paid for twice. Full documentation:
16
+ [docs.rulvar.com](https://docs.rulvar.com).
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ pnpm add @rulvar/core
22
+ ```
23
+
24
+ Most applications start with the umbrella instead: `pnpm add
25
+ @rulvar/rulvar` bundles this engine with both first-class adapters and
26
+ the recommended model defaults. The a la carte path pairs the core with
27
+ exactly the pieces you need, for example
28
+ `pnpm add @rulvar/core @rulvar/anthropic @rulvar/store-sqlite`.
29
+
30
+ ## Documentation
31
+
32
+ - [Quickstart](https://docs.rulvar.com/guide/quickstart)
33
+ - [Architecture](https://docs.rulvar.com/guide/architecture)
34
+ - [Workflows](https://docs.rulvar.com/guide/workflows) and
35
+ [The journal](https://docs.rulvar.com/guide/journal)
36
+ - [API reference](https://docs.rulvar.com/api/%40rulvar/core/)
37
+
38
+ ## License
39
+
40
+ [Apache-2.0](https://github.com/o-stepper/rulvar/blob/main/LICENSE)
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}' (docs/03, 12.8: ordering and identity fields MUST pass through unmodified)`);
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 (docs/03, section \"schemaHash and toolsetHash derivation\")");
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 (docs/03, section "schemaHash and toolsetHash derivation")`);
1609
- if (refStack.includes(ref)) throw new ConfigError(`Recursive local $ref '${ref}' cannot be inlined; recursive schemas are not canonicalizable (docs/03, section "schemaHash and toolsetHash derivation")`);
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; docs/05, section "The human gate")`);
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; docs/05, 5.4); 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 (docs/05, security channel 4)`);
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)} (docs/06, Appendix A); supersede or archive first`);
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 (docs/04, section 9)`);
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 (docs/04, section 9)`);
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/08, section "tool() definition and ToolDef")`);
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 land with compileScript in M6 (docs/06, section "Script runners")`);
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 (docs/08, section 6.4)`);
2597
- if (seen.has(def.name)) throw new ConfigError(`duplicate tool name '${def.name}' in one toolset; disambiguate with the MCP prefix option (docs/08, sections 1.1 and 6.4)`);
2598
- if (def.executor !== "inprocess") throw new ConfigError(`tool '${def.name}' declares executor '${def.executor}', but this engine implements only 'inprocess' in v1 (docs/08, section 7.1)`);
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 (docs/08, section 6.2: exactly the keys matching the chosen 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}$ (docs/08, section 6.4)`);
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}' (docs/08, section 8.3)`);
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 (docs/08, section 8.4)`);
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 (docs/03, section 4.2)");
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; docs/07, section 6.5)");
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; docs/07, section 6.5)");
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 (docs/07, 11.8)`);
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 (docs/07, 11.3)`);
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)} (docs/07, 11.6: the debit fold is authoritative)`, { data: {
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}' (docs/03 section 5.3)`));
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 (docs/03 section 8.1)"));
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 (docs/03 5.4)"));
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}' (docs/03, section 8.1)`);
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 (docs/07, section 10); dispatch laddered profiles through orchestratePlanned or pass a plain ModelRef or ModelChoice`);
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 (docs/04, section 12)`);
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 (docs/04, section 12)");
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(", ")} (docs/04, section 12)`);
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 (docs/04, section 8.2); declare rung.effort or a chain effort`);
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 (docs/06, 5.4)`);
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)}] (docs/06, Appendix A: default 1, hard ceiling 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 (docs/07, 11.2)");
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 (docs/03, 10.1, rule 2)");
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 (docs/08, section 8.2)");
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 (docs/06, Appendix A; docs/07, section 6.2)");
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 (docs/06, Appendix A)");
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 (docs/06, Appendix A)");
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 (docs/06, 10.4)`);
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 (docs/06, 10.4)`);
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 (docs/08, section "SchemaSpec"; docs/07, 4.2)`);
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/08, section "tool() definition"; docs/07, 4.2)`);
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 (docs/07, 12.2: opting out of the cap is explicit only, up to 1.0 inclusive)`);
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 (docs/07, 12.2)");
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 (docs/07, 12.2)`);
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 (docs/07, section 10); spawn a concrete profile instead`);
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 (docs/06, sections 8.2 and 10.1)");
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 (docs/06, 10.2); run '${runId}' records ` + (name === void 0 ? "no workflowName" : `workflow '${name}', which is not registered`) + "; register it under defaults.workflows or pass the workflow value");
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/06, section \"Engine and ops API\"); only compiled runs with a persisted source resume bare");
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}' (docs/06, 10.2)`);
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 (docs/06, 8.3)");
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 (docs/06, 2.5)");
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.0",
3
+ "version": "1.3.2",
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",