@rulvar/core 1.235.0 → 1.237.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1217,6 +1217,15 @@ type RunMeta = {
1217
1217
  allowUnpriced?: string[];
1218
1218
  };
1219
1219
  /**
1220
+ * The host-declared config identity (RunOptions.configFingerprint,
1221
+ * RV3210): an opaque pin over what the workflow body closes over,
1222
+ * recorded at genesis and compared on every resume that asserts one.
1223
+ * Absent when the run declared none. A store that drops the field
1224
+ * degrades the check to the UNRECORDED warning, never a false pass
1225
+ * or a false refusal (absence means NOT RECORDED).
1226
+ */
1227
+ configFingerprint?: string;
1228
+ /**
1220
1229
  * Count of execution segments this run has STARTED (a fresh start
1221
1230
  * writes 1; every resume writes prior + 1, durably, BEFORE the
1222
1231
  * segment emits its first event). The engine derives each segment's
@@ -1670,6 +1679,46 @@ interface TerminalEnvelope {
1670
1679
  /** Agents admitted over the run's lifetime, resume seed included. */
1671
1680
  agentsSpawned: number;
1672
1681
  /**
1682
+ * Whether the artifact this terminal carries passed the declared
1683
+ * finish contract (RV2506), mirrored onto the envelope since RV3304:
1684
+ * the 2026-08-12 comparison run settled ok/complete over a retained
1685
+ * contradiction, and neither the HTTP response nor the persisted
1686
+ * rebuild could say whether anything ever judged the deliverable.
1687
+ * Absent when no contract judged anything; absence means NOT
1688
+ * RECORDED, never "accepted".
1689
+ */
1690
+ deliverableAccepted?: boolean;
1691
+ /**
1692
+ * Whether this terminal carries a deliverable to read at all
1693
+ * (RV2506); same mirror and posture. Distinct from
1694
+ * `deliverableAccepted`: an unjudged artifact still EXISTS, and a
1695
+ * run with no artifact still has a completion claim.
1696
+ */
1697
+ resultAvailable?: boolean;
1698
+ /**
1699
+ * The journal seq of the decision entry recording the acceptance of
1700
+ * the artifact this terminal carries (RV2506); same mirror, absent
1701
+ * unless the acceptance actually rendered. Read it with
1702
+ * `rulvar inspect` to see WHICH validators accepted WHICH hash.
1703
+ */
1704
+ acceptedArtifactRef?: number;
1705
+ /**
1706
+ * The claim consistency pass meta, detached (RV3304): `judgedStage`,
1707
+ * `judgedHash`, the coverage grade and the `findings` count, so the
1708
+ * surface a consumer gates on says WHAT was semantically verified,
1709
+ * over WHICH document, and what the judge found, without reaching
1710
+ * into the workflow value. Mutating this copy never touches the
1711
+ * outcome the engine owns.
1712
+ */
1713
+ claimConsistencyMeta?: Record<string, unknown>;
1714
+ /**
1715
+ * The host declared config identity the run was started under
1716
+ * (RV3210), echoed here since RV3304 so a decision consumer binds
1717
+ * the verdict above to the configuration that produced it without a
1718
+ * second read of the run record. Absent when the run declared none.
1719
+ */
1720
+ configFingerprint?: string;
1721
+ /**
1673
1722
  * Where THIS copy of the envelope was assembled (RV1209). Absent, the
1674
1723
  * historical byte contract, means the settlement chokepoint built it
1675
1724
  * from the live outcome, so every field above is the run's own
@@ -7784,6 +7833,24 @@ interface RunOptions {
7784
7833
  /** Explicit id; otherwise the engine mints a ULID. */
7785
7834
  runId?: string;
7786
7835
  /**
7836
+ * An opaque host-declared identity over the config the workflow body
7837
+ * CLOSES OVER (RV3210, the honest answer to `hashWorkflowBody`'s
7838
+ * closure blindness: the body-text hash cannot see captured values,
7839
+ * so two byte-identical bodies over different closures pin
7840
+ * identically). Recorded in RunMeta at genesis and compared on every
7841
+ * resume that supplies one: a mismatch refuses the resume typed
7842
+ * BEFORE ownership, meta writes, and appends, because the host
7843
+ * itself asserted the identity; a recorded fingerprint the resume
7844
+ * does not supply warns (`RULVAR_RESUME_FINGERPRINT_UNCHECKED`), and
7845
+ * a supplied fingerprint the run never recorded warns
7846
+ * (`RULVAR_RESUME_FINGERPRINT_UNRECORDED`) instead of failing,
7847
+ * because absence means NOT RECORDED. The preferred pattern is still
7848
+ * to close over nothing and pass config through args; the
7849
+ * fingerprint is the pin for what must stay closed over. A non-empty
7850
+ * string of at most 512 characters.
7851
+ */
7852
+ configFingerprint?: string;
7853
+ /**
7787
7854
  * Run ceiling B0; immutable after start. Enforced by projected
7788
7855
  * admission (a spawn whose reserve does not fit is denied before any
7789
7856
  * dispatch), the per-turn guard with a budget-derived maxOutputTokens
@@ -7931,6 +7998,18 @@ interface ResumeOptions {
7931
7998
  */
7932
7999
  bodyHash?: "warn" | "refuse";
7933
8000
  /**
8001
+ * The host's asserted config identity for this resume (RV3210),
8002
+ * compared against the RunMeta-recorded
8003
+ * {@link RunOptions.configFingerprint} BEFORE ownership, meta
8004
+ * writes, or any append. Both present and unequal is a typed
8005
+ * ConfigError always, no posture knob: supplying the fingerprint IS
8006
+ * the assertion. A recorded fingerprint the resume does not supply
8007
+ * warns (`RULVAR_RESUME_FINGERPRINT_UNCHECKED`); a supplied one the
8008
+ * run never recorded warns (`RULVAR_RESUME_FINGERPRINT_UNRECORDED`),
8009
+ * because absence means NOT RECORDED, never a verdict.
8010
+ */
8011
+ configFingerprint?: string;
8012
+ /**
7934
8013
  * Dry-run: replay-strict matching; the first would-be-live call throws
7935
8014
  * JournalMissError and the run settles with that typed error, zero live
7936
8015
  * calls performed.
@@ -10322,7 +10401,12 @@ interface OrchestrateClaimConsistency {
10322
10401
  * explicitly (a ConfigError without that synthesis, the
10323
10402
  * contradictions precedent), and non-empty findings block the
10324
10403
  * `skipWhenDraftValid` gate: a draft contradicting its own pool
10325
- * never earns the skip. 'fail' fails the run typed with
10404
+ * never earns the skip. The carry can only ride a prompt that still
10405
+ * lies ahead, so it binds the pass that runs BEFORE the synthesis:
10406
+ * under `stage: 'both'` the draft pass carries and the final pass
10407
+ * reports, and `stage: 'final'` with 'carry' is a ConfigError at
10408
+ * intake, because a posture that reads as a gate must not quietly
10409
+ * behave as 'report'. 'fail' fails the run typed with
10326
10410
  * `data.source` 'orchestrator_claim_consistency' BEFORE any
10327
10411
  * synthesis dispatch; the judge itself has already been paid, which
10328
10412
  * is the honest minimum for a semantic verdict. A judge that does
@@ -10546,6 +10630,18 @@ interface OrchestrateClaimConsistencyMeta {
10546
10630
  */
10547
10631
  judgeDeclined?: true;
10548
10632
  /**
10633
+ * How many judged contradictions the pass FOUND on the judged
10634
+ * document, present exactly when the judge settled ok (RV3304): `0`
10635
+ * is a clean verdict, a positive count is a disagreement that stayed
10636
+ * wherever the posture did not stop the run. The findings themselves
10637
+ * ride `claimContradictions` beside this meta on the acceptance
10638
+ * envelope, but the meta travels ALONE onto RunOutcome, the
10639
+ * journaled run settle, and the terminal envelope, and the
10640
+ * 2026-08-12 comparison run settled ok/complete over a retained
10641
+ * finding no terminal surface could count.
10642
+ */
10643
+ findings?: number;
10644
+ /**
10549
10645
  * The one field a consumer reads INSTEAD of inferring semantic
10550
10646
  * health from an empty findings array (RV1702):
10551
10647
  * {@link claimCoverageOf} over this meta, so `completion:
@@ -12116,7 +12212,7 @@ interface RunHandle<R> {
12116
12212
  //#endregion
12117
12213
  //#region src/engine/terminal-envelope.d.ts
12118
12214
  /** The outcome facts the assembler reads; a structural subset of RunOutcome. */
12119
- type TerminalOutcomeFacts = Pick<RunOutcome<unknown>, "status" | "error" | "completion"> & {
12215
+ type TerminalOutcomeFacts = Pick<RunOutcome<unknown>, "status" | "error" | "completion" | "deliverableAccepted" | "resultAvailable" | "acceptedArtifactRef" | "claimConsistencyMeta"> & {
12120
12216
  usage: RunOutcome<unknown>["usage"];
12121
12217
  cost: Pick<RunOutcome<unknown>["cost"], "totalUsd" | "grossUsd" | "byModel"> & {
12122
12218
  usageApprox?: boolean;
@@ -12144,7 +12240,8 @@ declare function terminalEnvelopeOf(input: {
12144
12240
  settlement?: {
12145
12241
  settledReason?: "superseded";
12146
12242
  };
12147
- provenance?: "journal";
12243
+ provenance?: "journal"; /** The run's declared config identity (RV3210), echoed onto the envelope (RV3304). */
12244
+ configFingerprint?: string;
12148
12245
  }): TerminalEnvelope;
12149
12246
  //#endregion
12150
12247
  //#region src/l0/decision-chain.d.ts
@@ -13036,6 +13133,18 @@ declare function lastRunSettle(entries: readonly JournalEntry[]): {
13036
13133
  * recorded" rather than as a claim.
13037
13134
  */
13038
13135
  rejectedFinishCandidates?: RejectedFinishCandidate[];
13136
+ /**
13137
+ * The semantic outcome the settle recorded (RV3304), read back
13138
+ * the same defensive way: the acceptance verdict, the
13139
+ * deliverable presence, the acceptance ref and the judge meta,
13140
+ * so a restarted reader recovers the facts a live consumer
13141
+ * gated on. Absent on journals written before the lift carried
13142
+ * them; absence means NOT RECORDED, never a verdict.
13143
+ */
13144
+ deliverableAccepted?: boolean;
13145
+ resultAvailable?: boolean;
13146
+ acceptedArtifactRef?: number;
13147
+ claimConsistencyMeta?: Record<string, unknown>;
13039
13148
  } | undefined;
13040
13149
  /**
13041
13150
  * Whether a terminal figure counts THIS segment's work or the whole
@@ -14016,6 +14125,17 @@ interface StatementReconciliation {
14016
14125
  componentToleranceUsd: number;
14017
14126
  verdict: "match" | "divergence" | "partial-coverage" | "no-overlap";
14018
14127
  /**
14128
+ * How much of the MATCHED statement claims money (RV3306):
14129
+ * 'complete' when every matched export row (requests mode) or every
14130
+ * component line (categories mode) carries a dollar claim, a row
14131
+ * total or a component split; 'partial' when some do; 'none' when
14132
+ * the statement matched on identity and usage alone, or matched
14133
+ * nothing. Kept apart from row coverage on purpose: coverage says
14134
+ * the records line up, this says whether the provider actually
14135
+ * stated dollars over them.
14136
+ */
14137
+ dollarCoverage: "complete" | "partial" | "none";
14138
+ /**
14019
14139
  * The settlement-grade composite, first class (RV1006): true exactly
14020
14140
  * when the verdict is 'match' AND coverage is complete AND no row's
14021
14141
  * usage is unknown AND no model went unpriced. A 'match' alone is
@@ -14024,9 +14144,22 @@ interface StatementReconciliation {
14024
14144
  * consumer must not assemble this predicate by hand. The last two
14025
14145
  * conditions overlap today's verdict semantics deliberately: the
14026
14146
  * predicate states the full contract so it cannot drift apart from
14027
- * a future verdict refinement.
14147
+ * a future verdict refinement. Note what it does NOT require: a
14148
+ * dollar claim. A usage-only export that matches on identity and
14149
+ * tokens reads `settleable: true`; gate MONETARY closure on
14150
+ * `monetarySettleable` below.
14028
14151
  */
14029
14152
  settleable: boolean;
14153
+ /**
14154
+ * The MONETARY settlement predicate (RV3306): `settleable` AND
14155
+ * complete dollar coverage. `settleable` answers "do the records
14156
+ * agree"; this answers "may money close against this statement".
14157
+ * The 2026-08-12 audit named the difference on this exact module: a
14158
+ * usage-only request export settled 'match' without one dollar of
14159
+ * provider evidence, and a finance pipeline gating on `settleable`
14160
+ * alone would have closed money against it.
14161
+ */
14162
+ monetarySettleable: boolean;
14030
14163
  }
14031
14164
  /**
14032
14165
  * Reconciles the invoice against a normalized provider export. Pure and
@@ -14304,6 +14437,17 @@ interface PreflightOrchestratorSpec {
14304
14437
  * the finding entirely. Default 2.
14305
14438
  */
14306
14439
  headroomTurns?: number;
14440
+ /**
14441
+ * The `ceiling-headroom-thin` threshold as a fraction of the ceiling
14442
+ * (RV3208, the 2026-08-11 experiment's admission cliff: a $7.00
14443
+ * ceiling over a $6.80 required minimum left 2.86 percent headroom,
14444
+ * and a small pricing or context drift would have refused the whole
14445
+ * workflow at admission). The finding warns when
14446
+ * `ceilingHeadroomShare` sits below this fraction. A number in
14447
+ * [0, 1]; 0 (the default) keeps the finding silent, so declared
14448
+ * configs are byte identical until a host opts in.
14449
+ */
14450
+ minCeilingHeadroomShare?: number;
14307
14451
  }
14308
14452
  /** The full input: engine surface, run surface, and the declared wave. */
14309
14453
  interface PreflightInput {
@@ -14531,6 +14675,20 @@ interface PreflightReport {
14531
14675
  */
14532
14676
  requiredMinimumCeilingUsd?: number;
14533
14677
  /**
14678
+ * The ceiling minus the required minimum (RV3208): the absolute
14679
+ * dollars of drift the admission survives before the wave stops
14680
+ * seating. Present beside requiredMinimumCeilingUsd whenever a
14681
+ * ceiling is declared.
14682
+ */
14683
+ ceilingHeadroomUsd?: number;
14684
+ /**
14685
+ * The same headroom as a fraction of the ceiling (RV3208): the
14686
+ * one-field read of the admission cliff (the 2026-08-11 experiment
14687
+ * ran at 0.0286). Present beside ceilingHeadroomUsd on positive
14688
+ * ceilings.
14689
+ */
14690
+ ceilingHeadroomShare?: number;
14691
+ /**
14534
14692
  * The live-root-exposure term of the wave projection (RV2004): the
14535
14693
  * orchestrator's own worst-case turn floor, the money coordination
14536
14694
  * has ALWAYS already spent (and holds in flight) by the time any
package/dist/index.js CHANGED
@@ -1327,6 +1327,20 @@ function detachedError(error) {
1327
1327
  }
1328
1328
  }
1329
1329
  /**
1330
+ * A detached copy of the judge meta (RV3304), the `detachedError`
1331
+ * posture: Json shaped by construction, so a structured clone
1332
+ * reproduces it exactly, and a host that smuggled something exotic
1333
+ * past the type falls back to a shallow copy rather than throwing at
1334
+ * the settlement chokepoint.
1335
+ */
1336
+ function detachedMeta(meta) {
1337
+ try {
1338
+ return structuredClone(meta);
1339
+ } catch {
1340
+ return { ...meta };
1341
+ }
1342
+ }
1343
+ /**
1330
1344
  * Assembles one terminal envelope (RV1105). `settlement` present means
1331
1345
  * nothing durable records the terminal: `settled` reads false, and the
1332
1346
  * optional `settledReason: 'superseded'` names the fenced-out segment
@@ -1356,6 +1370,11 @@ function terminalEnvelopeOf(input) {
1356
1370
  };
1357
1371
  if (outcome.error !== void 0) envelope.error = detachedError(outcome.error);
1358
1372
  if (outcome.completion !== void 0) envelope.completion = outcome.completion;
1373
+ if (outcome.deliverableAccepted !== void 0) envelope.deliverableAccepted = outcome.deliverableAccepted;
1374
+ if (outcome.resultAvailable !== void 0) envelope.resultAvailable = outcome.resultAvailable;
1375
+ if (outcome.acceptedArtifactRef !== void 0) envelope.acceptedArtifactRef = outcome.acceptedArtifactRef;
1376
+ if (outcome.claimConsistencyMeta !== void 0) envelope.claimConsistencyMeta = detachedMeta(outcome.claimConsistencyMeta);
1377
+ if (input.configFingerprint !== void 0) envelope.configFingerprint = input.configFingerprint;
1359
1378
  if (outcome.cost.wireRequests !== void 0) envelope.wireRequests = outcome.cost.wireRequests;
1360
1379
  if (input.settlement?.settledReason !== void 0) envelope.settledReason = input.settlement.settledReason;
1361
1380
  if (input.provenance !== void 0) envelope.provenance = input.provenance;
@@ -8806,17 +8825,36 @@ function lastRunSettle(entries) {
8806
8825
  if (value?.decisionType === "run_settle" && typeof value.runStatus === "string" && RUN_STATUSES.has(value.runStatus)) {
8807
8826
  const completion = value.completion;
8808
8827
  const rejected = readRejectedFinishCandidates(value.rejectedFinishCandidates);
8828
+ const judgeMeta = readClaimConsistencyMeta(value.claimConsistencyMeta);
8809
8829
  return {
8810
8830
  runStatus: value.runStatus,
8811
8831
  seq: entry.seq,
8812
8832
  ...typeof value.outputHash === "string" ? { outputHash: value.outputHash } : {},
8813
8833
  ...completion === "complete" || completion === "partial" || completion === "rejected" ? { completion } : {},
8814
- ...rejected === void 0 ? {} : { rejectedFinishCandidates: rejected }
8834
+ ...rejected === void 0 ? {} : { rejectedFinishCandidates: rejected },
8835
+ ...typeof value.deliverableAccepted === "boolean" ? { deliverableAccepted: value.deliverableAccepted } : {},
8836
+ ...typeof value.resultAvailable === "boolean" ? { resultAvailable: value.resultAvailable } : {},
8837
+ ...typeof value.acceptedArtifactRef === "number" && Number.isSafeInteger(value.acceptedArtifactRef) && value.acceptedArtifactRef >= 0 ? { acceptedArtifactRef: value.acceptedArtifactRef } : {},
8838
+ ...judgeMeta === void 0 ? {} : { claimConsistencyMeta: judgeMeta }
8815
8839
  };
8816
8840
  }
8817
8841
  }
8818
8842
  }
8819
8843
  /**
8844
+ * The judge meta of a persisted settle, or `undefined` (RV3304). The
8845
+ * whole object drops unless its load bearing fields are shaped as the
8846
+ * live producer writes them (`judgeInvoked`, the coverage grade, the
8847
+ * judged stage and hash): a partially shaped meta read as a verdict
8848
+ * would claim semantic ground the journal does not hold, the same
8849
+ * posture as `readRejectedFinishCandidates`.
8850
+ */
8851
+ function readClaimConsistencyMeta(raw) {
8852
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return;
8853
+ const meta = raw;
8854
+ if (typeof meta.judgeInvoked !== "boolean" || typeof meta.coverage !== "string" || meta.judgedStage !== "draft" && meta.judgedStage !== "final" || typeof meta.judgedHash !== "string") return;
8855
+ return { ...raw };
8856
+ }
8857
+ /**
8820
8858
  * The rejected finish candidates of a persisted settle, or `undefined`
8821
8859
  * (RV2605). The WHOLE list drops on any malformed row, the same posture
8822
8860
  * the live lift takes (RV2507): a partial history read as complete
@@ -9286,6 +9324,21 @@ function reduceInvocationTable(events) {
9286
9324
  */
9287
9325
  const CLAIM_JUDGE_LABEL = "claim-consistency-judge";
9288
9326
  /**
9327
+ * Whether a synthesize span's label names a claim-consistency judge
9328
+ * invocation: the exact {@link CLAIM_JUDGE_LABEL}, or a suffixed
9329
+ * variant of it (the final pass dispatches under
9330
+ * `claim-consistency-judge-final` since RV2509 so the two passes of
9331
+ * `stage: 'both'` stay separable). BOTH reducers must classify through
9332
+ * this one predicate (RV3302): the live fold compared the label for
9333
+ * exact equality while the journal fold accepted the suffix, and the
9334
+ * 2026-08-12 comparison run reported semanticJudgeMs 0 with the whole
9335
+ * 272923 ms window read as final composition on the live surface
9336
+ * while the journal fold correctly split 224864 against 48059.
9337
+ */
9338
+ function isClaimJudgeLabel(label) {
9339
+ return label === "claim-consistency-judge" || (label?.startsWith(`claim-consistency-judge-`) ?? false);
9340
+ }
9341
+ /**
9289
9342
  * The label the final synthesis (composition) invocation dispatches
9290
9343
  * under (RV2901). The engine labelling its OWN dispatches is what lets
9291
9344
  * `criticalPathFromJournal` split the synthesize bucket offline: the
@@ -9366,7 +9419,7 @@ function reduceCriticalPath(events) {
9366
9419
  if (started === void 0) break;
9367
9420
  if (started.role === "synthesize") {
9368
9421
  const wall = Math.max(0, at - started.at);
9369
- const judge = started.label === CLAIM_JUDGE_LABEL;
9422
+ const judge = isClaimJudgeLabel(started.label);
9370
9423
  synthesisMs += wall;
9371
9424
  if (judge) semanticJudgeMs += wall;
9372
9425
  else finalCompositionMs += wall;
@@ -9510,7 +9563,7 @@ function criticalPathFromJournal(entries) {
9510
9563
  continue;
9511
9564
  }
9512
9565
  labelledSynthesis = true;
9513
- if (label === "claim-consistency-judge" || label.startsWith(`claim-consistency-judge-`)) semanticJudgeMs += wall;
9566
+ if (isClaimJudgeLabel(label)) semanticJudgeMs += wall;
9514
9567
  else finalCompositionMs += wall;
9515
9568
  }
9516
9569
  const segments = logicalRunTelemetry(ordered).segments;
@@ -16111,6 +16164,7 @@ function reconcileStatement(invoice, statement, options) {
16111
16164
  let statementComponents;
16112
16165
  let matchedStatementRows = 0;
16113
16166
  let matchedUsdRows = 0;
16167
+ let matchedDollarRows = 0;
16114
16168
  let tokenMismatches = 0;
16115
16169
  let partialOverlap = false;
16116
16170
  const tokenMismatchSample = [];
@@ -16215,6 +16269,7 @@ function reconcileStatement(invoice, statement, options) {
16215
16269
  for (const row of statement.rows) {
16216
16270
  if (!matchedStatement.has(row.responseId)) continue;
16217
16271
  matchedStatementRows += 1;
16272
+ if (row.usd !== void 0 || row.componentsUsd !== void 0) matchedDollarRows += 1;
16218
16273
  if (row.usd !== void 0) {
16219
16274
  matchedUsdRows += 1;
16220
16275
  totalSeen = true;
@@ -16320,6 +16375,10 @@ function reconcileStatement(invoice, statement, options) {
16320
16375
  else if (matchedRows === 0 && !partialOverlap) verdict = "no-overlap";
16321
16376
  else if (!coverageComplete) verdict = "partial-coverage";
16322
16377
  else verdict = "match";
16378
+ const dollarClaims = statement.kind === "requests" ? matchedDollarRows : components.filter((line) => line.statementUsd !== void 0).length;
16379
+ const dollarSlots = statement.kind === "requests" ? matchedStatementRows : components.length;
16380
+ const dollarCoverage = dollarSlots > 0 && dollarClaims === dollarSlots ? "complete" : dollarClaims > 0 ? "partial" : "none";
16381
+ const settleable = verdict === "match" && coverageComplete && usageUnknownRows === 0 && unpricedModels.size === 0;
16323
16382
  return {
16324
16383
  mode: statement.kind,
16325
16384
  coverage: {
@@ -16345,7 +16404,9 @@ function reconcileStatement(invoice, statement, options) {
16345
16404
  usageUnknownRows,
16346
16405
  componentToleranceUsd,
16347
16406
  verdict,
16348
- settleable: verdict === "match" && coverageComplete && usageUnknownRows === 0 && unpricedModels.size === 0
16407
+ dollarCoverage,
16408
+ settleable,
16409
+ monetarySettleable: settleable && dollarCoverage === "complete"
16349
16410
  };
16350
16411
  }
16351
16412
  /** A cell that is absent by export convention: missing, null, or ''. */
@@ -16587,10 +16648,15 @@ function persistedTerminalEnvelope(input) {
16587
16648
  outcome: {
16588
16649
  status: settle.runStatus,
16589
16650
  ...settle.completion === void 0 ? {} : { completion: settle.completion },
16651
+ ...settle.deliverableAccepted === void 0 ? {} : { deliverableAccepted: settle.deliverableAccepted },
16652
+ ...settle.resultAvailable === void 0 ? {} : { resultAvailable: settle.resultAvailable },
16653
+ ...settle.acceptedArtifactRef === void 0 ? {} : { acceptedArtifactRef: settle.acceptedArtifactRef },
16654
+ ...settle.claimConsistencyMeta === void 0 ? {} : { claimConsistencyMeta: settle.claimConsistencyMeta },
16590
16655
  usage: ledger.usage,
16591
16656
  cost: costReportFromJournal(input.entries, input.priceUsd)
16592
16657
  },
16593
16658
  agentsSpawned: ledger.agentsSpawned,
16659
+ ...input.meta?.configFingerprint === void 0 ? {} : { configFingerprint: input.meta.configFingerprint },
16594
16660
  provenance: "journal"
16595
16661
  })
16596
16662
  };
@@ -22549,6 +22615,7 @@ function validateOrchestrateOptions(opts) {
22549
22615
  const stage = consistency.stage ?? "draft";
22550
22616
  if (stage !== "draft" && stage !== "final" && stage !== "both") throw new ConfigError("orchestrate claimConsistency.stage must be 'draft', 'final' or 'both'; got " + JSON.stringify(consistency.stage));
22551
22617
  if (stage !== "draft" && opts.synthesis === void 0) throw new ConfigError(`orchestrate claimConsistency.stage '${stage}' requires synthesis: without the post-fan-in invocation the coordination draft IS the final artifact, and the default 'draft' already judges it`);
22618
+ if (stage === "final" && onFound === "carry") throw new ConfigError("orchestrate claimConsistency.onFound 'carry' cannot pair with stage 'final': the final pass runs after the synthesis, so there is no prompt left to carry the findings into; use 'report' or 'fail', or keep a carried draft pass with stage 'both'");
22552
22619
  if (consistency.pattern !== void 0) {
22553
22620
  if (typeof consistency.pattern !== "string") throw new ConfigError(`orchestrate claimConsistency.pattern must be a string; got ${typeof consistency.pattern}`);
22554
22621
  let probe;
@@ -24848,7 +24915,10 @@ function makeOrchestratorWorkflow(goal, opts) {
24848
24915
  reason
24849
24916
  }));
24850
24917
  claimFindingsFound = findings;
24851
- claimConsistencyMeta = finishMeta({ judgeInvoked: true });
24918
+ claimConsistencyMeta = finishMeta({
24919
+ judgeInvoked: true,
24920
+ findings: findings.length
24921
+ });
24852
24922
  announce();
24853
24923
  if (onFound !== "fail" || findings.length === 0) return;
24854
24924
  throw new FailRunError(`the claim-consistency judge found ${String(findings.length)} contradiction${findings.length === 1 ? "" : "s"} between the draft and the settled child pool: ` + findings.map((finding) => `${finding.anchor} (${finding.reason})`).join("; "), { data: {
@@ -26769,6 +26839,14 @@ function preflightEstimate(input) {
26769
26839
  code: "reserve-line-headroom",
26770
26840
  message: `the admitted wave's steady state sits ${reserveLineHeadroomUsd.toFixed(4)} USD under the reserve line ${reserveLineUsd.toFixed(4)} USD (the ceiling minus the synthesis reserve), less than ${String(headroomTurns)} coordination turn floors of headroom (${liveRootExposureTermUsd.toFixed(4)} USD each): child spend past the declared estimates eats that headroom, the coordination loop is then refused at the line, and the run settles partial with the synthesis redeemed from its reserve (RV2101); size the wave below the line or raise the ceiling to keep coordinating past it`
26771
26841
  });
26842
+ const ceilingHeadroomUsd = ceilingUsd === void 0 || requiredMinimumCeilingUsd === void 0 ? void 0 : ceilingUsd - requiredMinimumCeilingUsd;
26843
+ const ceilingHeadroomShare = ceilingHeadroomUsd === void 0 || ceilingUsd === void 0 || ceilingUsd <= 0 ? void 0 : ceilingHeadroomUsd / ceilingUsd;
26844
+ const minCeilingHeadroomShare = input.orchestrator?.minCeilingHeadroomShare ?? 0;
26845
+ if (ceilingHeadroomShare !== void 0 && minCeilingHeadroomShare > 0 && ceilingHeadroomShare < minCeilingHeadroomShare) say({
26846
+ severity: "warning",
26847
+ code: "ceiling-headroom-thin",
26848
+ message: `the ceiling headroom is ${(ceilingHeadroomShare * 100).toFixed(2)} percent of the ceiling (${(ceilingHeadroomUsd ?? 0).toFixed(4)} USD over the required minimum ${(requiredMinimumCeilingUsd ?? 0).toFixed(4)} USD), below the declared ${(minCeilingHeadroomShare * 100).toFixed(2)} percent floor: a small pricing or context drift refuses the whole wave at admission; raise the ceiling or slim the wave`
26849
+ });
26772
26850
  {
26773
26851
  const judgeEstUsd = input.orchestrator?.claimConsistency?.judge?.estCost;
26774
26852
  if (judgeEstUsd !== void 0 && effectiveCapUsd !== void 0 && synthesisHoldUsd > 0) {
@@ -27052,6 +27130,8 @@ function preflightEstimate(input) {
27052
27130
  reservedForFinalizationUsd,
27053
27131
  synthesisReserveUsd: synthesisHoldUsd,
27054
27132
  ...requiredMinimumCeilingUsd === void 0 ? {} : { requiredMinimumCeilingUsd },
27133
+ ...ceilingHeadroomUsd === void 0 ? {} : { ceilingHeadroomUsd },
27134
+ ...ceilingHeadroomShare === void 0 ? {} : { ceilingHeadroomShare },
27055
27135
  ...liveRootExposureTermUsd > 0 ? { liveRootExposureTermUsd } : {},
27056
27136
  ...reserveLineUsd === void 0 ? {} : { reserveLineUsd },
27057
27137
  ...reserveLineHeadroomUsd === void 0 ? {} : { reserveLineHeadroomUsd },
@@ -27588,6 +27668,10 @@ function parseDeadlineAt(value) {
27588
27668
  if (month < 1 || month > 12 || day < 1 || day > daysInMonth) refuse();
27589
27669
  return parsed;
27590
27670
  }
27671
+ /** Validates a declared config fingerprint (RV3210): a non-empty string of at most 512 chars. */
27672
+ function requireConfigFingerprint(value, site) {
27673
+ if (typeof value !== "string" || value.length === 0 || value.length > 512) throw new ConfigError(`${site} must be a non-empty string of at most 512 characters; got ` + (typeof value === "string" ? `${String(value.length)} characters` : JSON.stringify(value)));
27674
+ }
27591
27675
  /** Content hash of an in-process workflow body (run-to-definition binding). */
27592
27676
  function hashWorkflowBody(wf) {
27593
27677
  return createHash("sha256").update(wf.body.toString(), "utf8").digest("hex");
@@ -27894,6 +27978,7 @@ function createEngine(options) {
27894
27978
  if (wf.kind !== "workflow" && wf.kind !== "compiled-workflow") throw new ConfigError("engine.run accepts in-process Workflow values or compileScript CompiledWorkflow values");
27895
27979
  if (opts?.budgetUsd !== void 0) requireNonNegativeNumber(opts.budgetUsd, "RunOptions.budgetUsd");
27896
27980
  if (opts?.maxInFlightExposureUsd !== void 0) requireNonNegativeNumber(opts.maxInFlightExposureUsd, "RunOptions.maxInFlightExposureUsd");
27981
+ if (opts?.configFingerprint !== void 0) requireConfigFingerprint(opts.configFingerprint, "RunOptions.configFingerprint");
27897
27982
  if (opts?.clampTurnToExposure !== void 0 && typeof opts.clampTurnToExposure !== "boolean") throw new ConfigError("RunOptions.clampTurnToExposure must be a boolean; got " + JSON.stringify(opts.clampTurnToExposure));
27898
27983
  if (opts?.strictPricing !== void 0 && typeof opts.strictPricing !== "boolean" && (typeof opts.strictPricing !== "object" || opts.strictPricing === null || Array.isArray(opts.strictPricing))) throw new ConfigError("RunOptions.strictPricing must be a boolean or an options object; got " + JSON.stringify(opts.strictPricing));
27899
27984
  if (opts?.limits !== void 0) validateUsageLimits(opts.limits, "RunOptions.limits");
@@ -27929,6 +28014,7 @@ function createEngine(options) {
27929
28014
  ...opts.strictPricing.maxRatesAgeDays === void 0 ? {} : { maxRatesAgeDays: opts.strictPricing.maxRatesAgeDays },
27930
28015
  ...opts.strictPricing.allowUnpriced === void 0 ? {} : { allowUnpriced: [...opts.strictPricing.allowUnpriced] }
27931
28016
  };
28017
+ const configFingerprint = opts?.configFingerprint ?? resumeCtx?.configFingerprint;
27932
28018
  const makeBudget = () => new RunBudget({
27933
28019
  ...ceilingUsd === void 0 ? {} : { ceilingUsd },
27934
28020
  ...exposureCapUsd === void 0 ? {} : { maxInFlightExposureUsd: exposureCapUsd },
@@ -28109,6 +28195,7 @@ function createEngine(options) {
28109
28195
  ...ceilingUsd === void 0 ? {} : { budgetUsd: ceilingUsd },
28110
28196
  ...exposureCapUsd === void 0 ? {} : { maxInFlightExposureUsd: exposureCapUsd },
28111
28197
  ...strictPricing === void 0 ? {} : { strictPricing },
28198
+ ...configFingerprint === void 0 ? {} : { configFingerprint },
28112
28199
  ...argsBinding.argsProvided === void 0 ? {} : { argsProvided: argsBinding.argsProvided },
28113
28200
  ...argsBinding.argsHash === void 0 ? {} : { argsHash: argsBinding.argsHash },
28114
28201
  ...genesis === void 0 ? {} : { genesis },
@@ -28402,6 +28489,7 @@ function createEngine(options) {
28402
28489
  workflow: wf.name,
28403
28490
  outcome: outcomeFacts,
28404
28491
  agentsSpawned: budget.spent().agentsSpawned,
28492
+ ...configFingerprint === void 0 ? {} : { configFingerprint },
28405
28493
  ...settlementFailure !== void 0 ? { settlement: {} } : supersededBy !== void 0 ? { settlement: { settledReason: "superseded" } } : {}
28406
28494
  });
28407
28495
  const outcome = {
@@ -28508,6 +28596,20 @@ function createEngine(options) {
28508
28596
  }
28509
28597
  bound = supplied;
28510
28598
  }
28599
+ {
28600
+ const supplied = resumeOptions?.configFingerprint;
28601
+ if (supplied !== void 0) requireConfigFingerprint(supplied, "ResumeOptions.configFingerprint");
28602
+ const recorded = typeof meta?.configFingerprint === "string" ? meta.configFingerprint : void 0;
28603
+ if (supplied !== void 0 && recorded !== void 0 && supplied !== recorded) throw new ConfigError(`resume: the supplied configFingerprint does not match the one run '${runId}' recorded at genesis; the config the workflow closes over changed, and the host declared exactly this check. Resume under the original config, or drop the option to proceed under the loud warning`);
28604
+ if (supplied !== void 0 && recorded === void 0) process.emitWarning(`resume: a configFingerprint was supplied but run '${runId}' never recorded one; the assertion cannot be verified (absence means NOT RECORDED)`, {
28605
+ code: "RULVAR_RESUME_FINGERPRINT_UNRECORDED",
28606
+ type: "RulvarWarning"
28607
+ });
28608
+ if (supplied === void 0 && recorded !== void 0) process.emitWarning(`resume: run '${runId}' recorded a configFingerprint at genesis and this resume did not supply one; the declared config identity goes unchecked`, {
28609
+ code: "RULVAR_RESUME_FINGERPRINT_UNCHECKED",
28610
+ type: "RulvarWarning"
28611
+ });
28612
+ }
28511
28613
  const priorEntries = (await journal.load(runId)).map((entry) => normalizeEntry(entry));
28512
28614
  scanJournalCompatibility(runId, priorEntries, buildDeriverRegistry(options.extraDerivers));
28513
28615
  if (priorEntries.some((entry) => entry.usageSemantics === void 0 && (entry.servedBy?.startsWith("openai:") === true && (entry.usage?.cacheWriteTokens ?? 0) > 0 || (entry.usageByModel?.some((slice) => slice.servedBy.startsWith("openai:") && slice.usage.cacheWriteTokens > 0) ?? false)))) process.emitWarning(`resume: run '${runId}' contains OpenAI cache-write usage recorded without a usage-semantics stamp. Entries written by rulvar v1.19.0 double-counted cache writes into inputTokens, so their recorded cost and budget debits are OVERSTATED; unstamped entries from v1.20.0 are correct. Resuming keeps the recorded debits. Audit procedure: https://docs.rulvar.com/guide/providers#openai-legacy-cache-journals`, {
@@ -28534,6 +28636,7 @@ function createEngine(options) {
28534
28636
  ...typeof meta?.argsHash === "string" ? { argsHash: meta.argsHash } : {},
28535
28637
  ...typeof meta?.genesis === "string" ? { genesis: meta.genesis } : {},
28536
28638
  ...typeof meta?.execKeyDerivation === "number" ? { execKeyDerivation: meta.execKeyDerivation } : {},
28639
+ ...typeof meta?.configFingerprint === "string" ? { configFingerprint: meta.configFingerprint } : {},
28537
28640
  previewResolve
28538
28641
  });
28539
28642
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/core",
3
- "version": "1.235.0",
3
+ "version": "1.237.0",
4
4
  "description": "Rulvar core: L0 contracts, journal kernel, ctx primitives, agent runtime, model router, tool system, dynamic orchestrator, InMemory and JSONL stores, event stream.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",