Skip to content

Changelog

All packages in the fixed group release in lockstep with identical versions; @rulvar/compat is versioned independently. The sections below mirror each package's CHANGELOG.md as written by Changesets.

@rulvar/anthropic

1.252.0

Patch Changes

  • Updated dependencies [3ccb6cf]
  • Updated dependencies [52d807f]
  • Updated dependencies [517ed00]
  • Updated dependencies [a7e589d]
  • Updated dependencies [76e95eb]

1.251.0

Patch Changes

  • Updated dependencies [e7e829c]
  • Updated dependencies [5982be8]
  • Updated dependencies [7c58fb2]
  • Updated dependencies [b3e465a]
  • Updated dependencies [c4e5d6a]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [0ae8b85]
  • Updated dependencies [7932936]
  • Updated dependencies [7932936]
  • Updated dependencies [88da0ed]
  • Updated dependencies [06c0e85]

1.250.0

Patch Changes

  • Updated dependencies [0e240b9]
  • Updated dependencies [6fe585e]
  • Updated dependencies [c5eb19c]
  • Updated dependencies [565c13b]
  • Updated dependencies [c6d197b]
  • Updated dependencies [c9d9729]
  • Updated dependencies [fed9db6]
  • Updated dependencies [df9ed76]
  • Updated dependencies [3020912]
  • Updated dependencies [d8d598d]

1.249.0

Patch Changes

  • Updated dependencies [8862133]
  • Updated dependencies [0d7a717]
  • Updated dependencies [e4428bd]
  • Updated dependencies [d6873c1]
  • Updated dependencies [4092e8d]
  • Updated dependencies [e086590]
  • Updated dependencies [1411938]
  • Updated dependencies [737d1ee]
  • Updated dependencies [634f966]
  • Updated dependencies [052cc26]
  • Updated dependencies [bbae134]

1.248.0

Patch Changes

  • Updated dependencies [8d0cd69]
  • Updated dependencies [81065e4]
  • Updated dependencies [8573f20]
  • Updated dependencies [95f6a5e]

1.247.0

Minor Changes

  • b698726: The first-party surface attests, and the floor loses its holes (RV4204, the sixth comparison experiment). Before this, only mcp() and the AI SDK bridge exposed describeRegulatedPosture(), so unrecognized >= 1 on nearly every real regulated compile and a zero-blind-spot floor was unsatisfiable by construction; and the floor checked toolset attestation only on defaults.profiles, accepted legacy contract-only pins that pass authority drift silently, and never walked the executors at all. Now: anthropic() and openai() attest their egress (official, a custom-base-url whose ORIGIN enters the hashed posture map, or a preconstructed-client named honestly) plus the caps pagination bound; subprocessExecutor() and containerExecutor() attest their ledger, env allowlist, resolved ceilings, and isolation seam; compileRegulatedProfile walks engine.executors and the sandbox runner beside adapters and toolsets, wraps attested executors so run() re-judges the posture at use (the RV4102 seam), refuses a regulated executor without a ToolEffectLedger by field name, refuses legacy contract-only pins (re-record with attestToolset()), and arms the new engine-wide defaults.requireToolsetAttestation, under which a spawn resolving a non-empty toolset with no pin binding it refuses typed at spawn time (the per-call-tools hole the profile pins could not see). The opt-in construction: 'require-recognized' compile floor turns the unrecognized count into a typed refusal naming the blind constructions, satisfiable now that the first-party surface attests.

Patch Changes

  • Updated dependencies [1933ecc]
  • Updated dependencies [db0a5f0]
  • Updated dependencies [b698726]
  • Updated dependencies [48348d2]
  • Updated dependencies [4cfa1cc]
  • Updated dependencies [4b7197a]
  • Updated dependencies [5ebc842]
  • Updated dependencies [16ff6b9]
  • Updated dependencies [0c9941d]

1.246.0

Patch Changes

  • Updated dependencies [d165b0c]
  • Updated dependencies [d59f4a0]
  • Updated dependencies [46907ac]
  • Updated dependencies [9929ad3]
  • Updated dependencies [1790a6a]

1.245.0

Patch Changes

  • Updated dependencies [b4d47a8]
  • Updated dependencies [dee6db4]
  • Updated dependencies [b85c113]
  • Updated dependencies [bc556e7]
  • Updated dependencies [9f11d29]
  • Updated dependencies [19bcea0]
  • Updated dependencies [60b461c]
  • Updated dependencies [61e3a1a]
  • Updated dependencies [a156b81]
  • Updated dependencies [0bd7045]

1.244.0

Patch Changes

  • Updated dependencies [38d839a]
  • Updated dependencies [ce13b0f]
  • Updated dependencies [4fa23e3]
  • Updated dependencies [6841c69]
  • Updated dependencies [f56721d]
  • Updated dependencies [c894a43]
  • Updated dependencies [f6944a3]
  • Updated dependencies [23fd0e0]

1.243.0

Patch Changes

  • Updated dependencies [746d1f4]
  • Updated dependencies [009b29c]
  • Updated dependencies [1674cbe]
  • Updated dependencies [bd096bc]

1.242.0

Patch Changes

  • Updated dependencies [6e3438e]
  • Updated dependencies [ba5cf67]
  • Updated dependencies [c2d1531]

1.241.0

Patch Changes

  • Updated dependencies [dbcdd24]
  • Updated dependencies [7ae7243]
  • Updated dependencies [4f832c4]
  • Updated dependencies [7452d3d]
  • Updated dependencies [a4e22bf]
  • Updated dependencies [82df4af]

1.240.0

Patch Changes

1.239.0

Patch Changes

  • Updated dependencies [74ce99a]
  • Updated dependencies [ccd0665]
  • Updated dependencies [0c5ce21]
  • Updated dependencies [0616934]

1.238.0

Patch Changes

  • Updated dependencies [cf00947]
  • Updated dependencies [c7b9382]
  • Updated dependencies [88aea96]
  • Updated dependencies [6da8d05]
  • Updated dependencies [eae5c4c]

1.237.0

Minor Changes

  • 3b987a1: Anthropic model resolution adopts the dated snapshot grammar (RV3303), the posture openai took in the v1.17.0 review P1-1. The old matcher let ANY suffix of a known name inherit the full table row, so an unseen variant like claude-sonnet-5-preview silently took the known model's caps and its promotional pricing, exactly the fabricated row the table's unknown model contract forbids; the 2026-08-12 comparison run named this counterexample. Now only the exact name or <exact model>-YYYYMMDD resolves a row; every other suffix falls through to the conservative unpriced caps, surfaces in CostReport.unpriced, and trips the ceiling warning instead of pricing as its neighbor. Dated snapshots of known names (claude-haiku-4-5-20251001) resolve byte identically to before.

Patch Changes

  • Updated dependencies [9d6a279]
  • Updated dependencies [49a98f6]
  • Updated dependencies [a734ca0]
  • Updated dependencies [deb406f]

1.236.0

Patch Changes

1.235.0

Patch Changes

  • Updated dependencies [ba4e10d]
  • Updated dependencies [172402b]
  • Updated dependencies [2ecd787]
  • Updated dependencies [e20a5e9]
  • Updated dependencies [98c8691]
  • Updated dependencies [c70def0]

1.234.0

Patch Changes

1.233.0

Patch Changes

  • Updated dependencies [48b5200]
  • Updated dependencies [73bc32b]
  • Updated dependencies [e63b743]
  • Updated dependencies [ef45da7]

1.232.0

Minor Changes

  • 6a58120: Bounded refreshCaps() pagination (RV2904). The ninth comparison run's adversarial audit found models.list the one pagination in the tree the MCP cycle doctrine (RV1602/RV1808) had not reached: a server echoing or recycling last_id spun the sweep forever, comfortably inside every timeout. A cursor echoed back or re-used by the sweep is now refused unconditionally as a typed cycle, and the new opt-in capsMaxPages fails the refresh typed when more pages are still reported past the bound, in the fail-closed maxTools direction: truncating would clamp output bounds against a silently partial caps table.

Patch Changes

  • Updated dependencies [1440410]
  • Updated dependencies [6e467f4]
  • Updated dependencies [0b14293]
  • Updated dependencies [e3bcab2]
  • Updated dependencies [b55a0f7]

1.231.0

Patch Changes

  • Updated dependencies [4eb4b56]
  • Updated dependencies [bc8f09e]
  • Updated dependencies [ff9b8c2]

1.230.0

Patch Changes

1.229.0

Patch Changes

  • Updated dependencies [3370342]
  • Updated dependencies [2fb6656]
  • Updated dependencies [edce170]

1.228.0

Patch Changes

  • Updated dependencies [4034fac]
  • Updated dependencies [a54b085]
  • Updated dependencies [9d0a9be]
  • Updated dependencies [be9ef28]

1.227.0

Patch Changes

  • Updated dependencies [f262e9f]
  • Updated dependencies [f191ff7]
  • Updated dependencies [fbbfbe8]
  • Updated dependencies [263b5e8]
  • Updated dependencies [db4d56d]
  • Updated dependencies [41f93a9]
  • Updated dependencies [98c8ca9]

1.226.0

Patch Changes

1.225.0

Patch Changes

1.224.0

Patch Changes

1.223.0

Patch Changes

1.222.0

Patch Changes

1.221.0

Patch Changes

1.220.0

Patch Changes

1.219.0

Patch Changes

1.218.0

Patch Changes

1.217.0

Patch Changes

1.216.0

Patch Changes

1.215.0

Patch Changes

1.214.0

Patch Changes

1.213.0

Patch Changes

1.212.0

Patch Changes

1.211.0

Patch Changes

1.210.0

Patch Changes

1.209.0

Patch Changes

1.208.0

Minor Changes

  • e7d426f: First-class prompt-cache policy (RV2006). ChatRequest.cacheHint existed and the Anthropic adapter compiled it into cache_control, but nothing in the core ever populated it: the third parity rerun's workers re-paid the full input rate on every turn of their ~550k-token contexts (cacheReadTokens 0 across the run), and the $6 envelope sized on OpenAI's implicit server cache was incomparable on Anthropic. The agent loop now compiles the hint on every tool-cycle turn: breakpoints after tools, after system, and after the deepest message, sliding with the history. Default ON exactly where the adapter declares the new ModelCaps.promptCaching: 'explicit' (the Anthropic adapter does); OpenAI declares 'implicit' and undeclared adapters get byte-identical requests. Configure with defaults.cache, AgentProfile.cache, or per-call opts.cache (CachePolicy { mode?: 'auto' | 'off'; ttl?: '5m' | '1h' }), call over profile over engine. Billing note: on cache-capable Anthropic models this changes the wire requests of every loop turn to carry cache breakpoints, typically cutting long-cycle input cost several-fold (cached reads bill at a tenth of the input rate); CostReport cache accounting is unchanged, the hint never enters identity or journals, and @rulvar/testing's requestHash strips it so existing cassettes replay byte for byte.

Patch Changes

1.207.0

Patch Changes

1.206.0

Patch Changes

1.205.0

Patch Changes

1.204.0

Patch Changes

1.203.0

Patch Changes

1.202.0

Patch Changes

1.201.0

Patch Changes

1.200.0

Patch Changes

1.199.0

Patch Changes

1.198.0

Patch Changes

1.197.0

Patch Changes

1.196.0

Patch Changes

1.195.0

Patch Changes

1.194.0

Patch Changes

1.193.0

Patch Changes

1.192.0

Patch Changes

1.191.0

Patch Changes

1.190.0

Patch Changes

1.189.0

Patch Changes

1.188.0

Patch Changes

1.187.0

Minor Changes

  • c9798ef: The absorbed pause_turn wire set survives the error arms (RV1805). The Anthropic adapter published the whole segment set (wireRequests = { count, responseIds }) only on the successful terminal finish, so an error after absorbed continuations, a create() failure, a truncated read, the continuation cap, or a pre-wire segment denial, yielded bare and orphaned exactly the paid wires a per-request statement join needs most (the segments' usage already survives through mid-stream reports; the ids and the count did not). Every error arm now rides the COMPLETED absorbed segments' wire set on its error data, the agent loop's provider call record reads it when the finish that would have named the set never came (a single absorbed segment included, since an errored dispatch has no plain responseId to join by), the invoice row keeps the ids and the count, and a first-segment failure stays a bare error with nothing invented.

Patch Changes

1.186.0

Patch Changes

1.185.0

Patch Changes

1.184.0

Patch Changes

1.183.0

Patch Changes

1.182.0

Patch Changes

1.181.0

Patch Changes

1.180.0

Patch Changes

1.179.0

Patch Changes

1.178.0

Patch Changes

1.177.0

Patch Changes

1.176.0

Patch Changes

1.175.0

Patch Changes

1.174.0

Patch Changes

1.173.0

Patch Changes

1.172.0

Patch Changes

1.171.0

Patch Changes

1.170.0

Patch Changes

1.169.0

Patch Changes

1.168.0

Patch Changes

1.167.0

Patch Changes

1.166.0

Patch Changes

1.165.0

Patch Changes

1.164.0

Patch Changes

1.163.0

Patch Changes

1.162.0

Patch Changes

1.161.0

Patch Changes

1.160.0

Patch Changes

1.159.0

Patch Changes

1.158.0

Patch Changes

1.157.0

Patch Changes

1.156.0

Patch Changes

1.155.0

Patch Changes

1.154.0

Patch Changes

1.153.0

Patch Changes

1.152.0

Patch Changes

1.151.0

Patch Changes

1.150.0

Patch Changes

1.149.0

Patch Changes

1.148.0

Patch Changes

1.147.0

Patch Changes

1.146.0

Patch Changes

1.145.0

Patch Changes

1.144.0

Patch Changes

1.143.0

Patch Changes

1.142.0

Patch Changes

1.141.0

Patch Changes

1.140.0

Patch Changes

1.139.0

Patch Changes

1.138.0

Minor Changes

  • ed0c4fb: Pre-wire continuation reservation, the self-describing fault kit, and the run-id surface (RV1013 + RV1014, PR VII closing the fourteenth plan)

    • Pre-wire continuation admission (RV1013, opt-in). Post-hoc settlement is accounting, not admission: a hard provider RPM cap needs each pause_turn continuation reserved BEFORE its egress. With quota: { reserveContinuations: true } the engine admits every provider-side continuation through the new adapter-side StreamHooks seam (ProviderAdapter.stream gains an optional third parameter; the Anthropic adapter honors it): under a 2-request window the third wire of one absorbed dispatch never leaves and the denial rides the provider-429 machinery verbatim, the main settlement stops re-adding individually admitted segments (the window is never double-counted), and a granted admission whose wire never left is RELEASED back to the window through the new optional QuotaLimiter.release(reservationId) (implemented by memoryQuotaLimiter; a release returns exactly what admission consumed, and unknown or expired ids are no-ops). Adapters unaware of the hook keep the documented post-hoc semantics byte for byte, and the default stays post-hoc. The midstream-versus-finish usage confirmation now fires only when a finish CLAIM exists: an error-terminal absorption (a segment denial, a transport cut) no longer manufactures an invariant violation that shadows the real wire error.
    • The self-describing kit (RV1014). runFaultInjection refuses an empty only selection typed (a gate that runs zero scenarios used to report allMatched: true), and the report carries requested and selected counts so the gate can never quietly shrink. The audit scenario grows the RV1007 arcs (a page-only long-context tier and a NaN scalar are findings, never silent passes), completing kit coverage of every real defect of the fourteenth plan on its real path.
    • The run-id boundary surface (assertSafeRunId, MAX_RUN_ID_LENGTH) is now exported from @rulvar/core, so hosts can pre-validate ids before engine.run.

Patch Changes

1.137.0

Patch Changes

1.136.0

Patch Changes

1.135.0

Patch Changes

1.134.0

Patch Changes

1.133.0

Minor Changes

  • 2659f54: A legitimate pause_turn survives the engine end to end, and an invalid continuation cap refuses typed before the first wire (RV1003 + RV1004, PR II of the fourteenth plan)

    The fourteenth comparison experiment drove the real Anthropic adapter through the real engine and a legitimate two-segment pause_turn killed the run: every segment's message_start emitted its own usage mid-stream (5 then 6), the terminal finish carried only the LAST segment's counts, and the engine's midstream-versus-finish invariant read 11 > 6, losing the paid segments from the money. The same experiment fed pauseTurnMaxContinuations: NaN and the cap silently disarmed (continuations > NaN is always false), turning every further continuation into unplanned paid traffic.

    • The terminal finish now speaks for the WHOLE logical turn (RV1003): the adapter accumulates each absorbed segment's normalized usage (sumUsage, cache counts and the TTL split included) and the finish carries the sum, so the invariant confirms the per-segment mid-stream reports, the per-call record and the invoice price every paid segment, and the quota window still settles at true wire units. Mid-stream events stay per-segment deltas; a single-segment turn stays byte-identical. TurnMapping gains the segment's own usage.
    • pauseTurnMaxContinuations must be a nonnegative safe integer (RV1004): any other present value (NaN, Infinity, negatives, fractions, strings) refuses with a typed ConfigError before the first wire, instead of silently disarming the continuation bound.
    • runFaultInjection (@rulvar/evals) grows the seventeenth scenario, pause-turn-real-adapter: the two-segment absorption through the REAL adapter and engine must settle ok at usage 11/2 with both wire ids on the invoice row and the quota window at 2, and the NaN cap must refuse before any wire. Reverting either fix reports matched: false in the kit.

Patch Changes

1.132.0

Patch Changes

1.131.0

Patch Changes

1.130.0

Patch Changes

1.129.0

Patch Changes

1.128.0

Minor Changes

  • 27c4e38: pause_turn continuations become accounted wire units (RV905, the thirteenth experiment's fifth release risk). The Anthropic adapter absorbs server-side turn pauses by re-sending, making up to six wire requests inside ONE core dispatch; until now the request quota window, the provider call record, and the invoice row all saw one, and a per-request provider statement matched one segment while the rest read statement-only.

    The adapter's finish metadata now names the whole segment set (providerMetadata.anthropic.wireRequests = { count, responseIds }); the provider call record and the invoice row carry wireResponseIds; and the quota reconciliation settles the reservation against the TRUE wire request count. The QuotaLimiter.reconcile SPI gains an optional actual.requests argument, honored by all three reference limiters through one shared arithmetic (quotaActualRequestsDelta), so a window that admitted one request per reservation now reflects what the provider's own RPM meter saw; a settlement only ever adds, never denies retroactively, and implementations written against the two-argument form remain valid. reconcileStatement joins a multi-wire invoice row by ANY id of its segment set, all-or-nothing: a partially delivered segment set reads partial-coverage with its delivered segments never counted as statement-only (and never no-overlap when segments touched our data), and provider-reported token counts compare as the SUM over the segments against the dispatch's recorded usage. Single-wire dispatches carry none of the new fields and stay byte-identical, journals and events included.

Patch Changes

1.127.0

Minor Changes

  • b3b1805: Admission before egress for the pre-dispatch token count (RV904, the thirteenth experiment's pre-admission egress probe). ctx.agent calls the adapter's optional countTokens with the FULL child prompt to tighten the admission reserve; before this release that network call ran before the budget decided anything, so a spawn the budget could never admit still sent the prompt to the provider, the call honored no abort signal, and nothing observable recorded the egress.

    The reserve is monotone in the count, so the smallest reserve any count outcome could produce is computable without it: the priced floor at zero input tokens, or the flat fallback the count-failed path admits under. The engine now checks that floor against the budget first, through the exact refusal arithmetic admitSpawn itself uses (RunBudget.refuseSpawnIfInfeasible, the refusal arm factored out so the two layers can never disagree), and a spawn that could never be admitted (the lifetime spawn cap, a full account, an exhausted ceiling) refuses with zero network calls. The provider SPI's countTokens gains an options argument with an AbortSignal; the Anthropic adapter threads it into the SDK request, and an abort mid-count cancels the spawn instead of silently falling back to the flat reserve and dispatching behind a cancelled spawn. Every count is now observable: an admission.countTokens info log names the model and the counted tokens, and a failed count warns with the failure the flat reserve then covers. An explicit estCost (per call or per profile) remains the zero-egress path that skips the count entirely, now documented as the posture for hosts whose privacy gates must run before any prompt byte reaches a provider. Spawns on adapters without countTokens, and spawns carrying estCost, behave byte-identically to v1.126.0.

Patch Changes

1.126.0

Patch Changes

1.125.0

Minor Changes

  • 109e9fa: Pricing-table truth: the Anthropic 1h cache-write premium is seeded, the rates audit fails closed on documented rates the seed never declared, and the OpenAI Terra/Luna price cut ships as a versioned revision (RV901, RV902, RV911; the thirteenth experiment's underpricing probes).

    @rulvar/anthropic seeds now carry all five published pricing columns: cacheWrite1hUsdPerMTok lands on every priced row at the documented 2x base input (Fable 5 $20, Opus 4.8/4.7/4.6 $10, Sonnet 5 $4 under the introductory price, Sonnet 4.6 $6, Haiku 4.5 $2), under the new pricingVersion anthropic-2026-07-31. v1.124.0 taught the wire to fill the canonical 5m/1h split and priceUsdOf to bill the 1h share at the premium, but the seed never declared the rate, so a million Sonnet 5 1h write tokens priced at the 5m $2.50 instead of the documented $4.00: an underpricing a budget ceiling then failed to bound. A usage with no split still folds the whole write count at the 5m rate, byte for byte as before; the stale caps comment claiming the canonical Usage cannot distinguish 1h writes is retired.

    scripts/rates-audit.mjs (the weekly documented-rates drift audit) now compares seed and page in BOTH directions: a billable page rate the seed never declared is a finding, not a silent skip. The old one-directional rule rested on the 1h premium being unbillable; that rationale died with the Usage split, and the audit printing match for Sonnet 5 while the page showed a 1h column the seed lacked is exactly how the underpricing hid. The pinning test is flipped to the fail-closed behavior.

    @rulvar/openai picks up the provider's 2026-07-30 price cut, docs-verified per model page on 2026-07-31 after the live audit caught the drift: Terra to $2 input / $12 output / $0.20 cached input / $2.50 cache write (0.8x across the board) and Luna to $0.20 / $1.20 / $0.02 / $0.25 (0.2x), both keeping the family's long-context tier, under the new pricingVersion openai-2026-07-31. Sol is unchanged and additionally remains billing-confirmed by the 2026-07-30 statement reconciliation; the new Terra and Luna rates are docs-verified only until the next reconciliation over a saved export. Runs recorded under openai-2026-07-18-r2 overstated Terra/Luna spend relative to the cut, never under, and a resumed run surfaces the rotation as explicit pricing drift instead of silently reinterpreting recorded spend. Every re-verified row now stamps ratesVerifiedAt: '2026-07-31'.

Patch Changes

1.124.0

Minor Changes

  • 37fd1f2: The twelfth plan's closing trio (RV809, RV810, RV811). The tool budget extension gains coverEvidenceDeficit: with an evidence contract declared, the extension grants at a tool-turn boundary whenever the remaining call budget cannot cover the declared floor's outstanding deficit, under the same money, progress, and maxExtensions gates, so a limited child at 7 of 11 entries converts headroom into the missing evidence BEFORE the cap instead of dumping through the reserved tail; the journaled grant decision carries trigger: 'evidence-deficit' and the announcement names the exact deficit. Canonical Usage gains the optional cache-write TTL split (cacheWrite5mTokens and cacheWrite1hTokens, invariant: the split sums to cacheWriteTokens); priceUsdOf bills the 1h share at cacheWrite1hUsdPerMTok with everything unclaimed at the plain write rate (byte-identical arithmetic without a split), sanitize repairs broken splits with 1h priority (never an undercharge), and the Anthropic adapter fills the split from the cache_creation breakdown when it agrees with the flat total. @rulvar/evals gains the fault-injection kit: runFaultInjection drives the never-observed-live fail-closed branches (in-flight-exposure refusal, duplicate quota rule, torn and glued JSONL tails, the settle-boundary crash resume, pricing rotation with an uncovered tail, unknown provider id) on the real engine offline, verifies each documented typed observable fail closed, and leaves experiment-grade artifacts.

Patch Changes

1.123.0

Patch Changes

1.122.0

Patch Changes

1.121.0

Minor Changes

  • 3d67d41: Rate provenance made checkable (RV807, RV813, RV814). The pricing row grows ratesVerifiedAt (SPI), the ISO date it was last verified against the provider's documented rates or, stronger, its billing categories: the shipped seeds stamp it (the GPT-5.6 family reads 2026-07-30, the day the statement reconciliation confirmed those rates against the provider's own per-component billing categories to the cent; the pre-5.6 OpenAI rows keep their 2026-07-18 docs verification; every Anthropic row was re-verified against the documented table on 2026-07-30). The date is surfaced wherever a dollar is consumed: preflightEstimate copies it onto each spawn report and rulvar preflight renders ratesVerified=<date> with its age on the spawn line; the settle pin journals it with the rest of the applied row so it survives any later table rewrite; and rulvar invoice prints a rates verified: line naming each priced model's date and age, pinned rows first, current table past them; the twelfth run's founder read the invoice doubting the rates and nothing said the seed was 12 days stale. The doctrine ships with the mechanism: seeds bound ceilings conservatively, billing truth is established only by reconcileStatement over saved exports, and a confirmed divergence corrects the seed in its own release with a changeset, never a silent rewrite. Enforcement rides two new gates: a weekly documented-rates audit (scripts/rates-audit.mjs in the live contract workflow) re-fetches exactly the pages the seed comments cite, compares every rate, write premium, and long-context tier, and opens an issue on drift or on a page that stops extracting, and a README release-table gate (scripts/readme-release-shas.mjs, in CI) requires every cited squash SHA to be an ancestor of HEAD, catching the v1.109.0 row that pointed at an object no branch contained for eleven releases (now corrected to the real squash 58afdb5).

Patch Changes

1.120.0

Patch Changes

1.119.0

Patch Changes

1.118.0

Patch Changes

1.117.0

Patch Changes

1.116.0

Patch Changes

1.115.0

Patch Changes

1.114.0

Patch Changes

1.113.0

Patch Changes

1.112.0

Patch Changes

1.111.0

Patch Changes

1.110.0

Patch Changes

1.109.0

Patch Changes

1.108.0

Patch Changes

1.107.0

Patch Changes

1.106.0

Patch Changes

1.105.0

Patch Changes

1.104.0

Patch Changes

1.103.0

Patch Changes

1.102.0

Patch Changes

1.101.0

Patch Changes

1.100.0

Patch Changes

1.99.1

Patch Changes

1.99.0

Patch Changes

1.98.0

Patch Changes

1.97.0

Patch Changes

1.96.0

Patch Changes

1.95.0

Patch Changes

1.94.0

Patch Changes

1.93.0

Patch Changes

1.92.0

Patch Changes

1.91.0

Patch Changes

1.90.0

Patch Changes

1.89.0

Patch Changes

1.88.0

Patch Changes

1.87.0

Patch Changes

1.86.0

Patch Changes

1.85.0

Patch Changes

  • 6932a9f: Three fail-closed fixes from the cycle 83 sweep, plus the dependency refresh.

    Engine. A typed error thrown out of ProviderAdapter.stream() now keeps its own class instead of being laundered into a retryable transport fault. A ConfigError (a bridged model id that does not match the wrapped model, an unsupported role, a namespaced option contradicting a canonical field) used to be retried through the whole backoff ladder and then trigger transport failover, so a misconfigured primary silently served the run from a fallback model the caller never asked for while the real fault vanished behind a generic message. Typed errors that ARE retryable by class (a lost lease) keep retrying exactly as before, and an untyped throw is still a retryable transport fault.

    Planner sandbox. The realm scrub replaced Date.now and Math.random, which left three ambient sources open: a bare new Date() never consults Date.now (V8 reads the system clock directly), performance.now() is a second live clock, and WebCrypto (crypto.randomUUID(), crypto.getRandomValues()) is raw entropy. Those are the first idioms a machine-written script reaches for, and each silently produced a run that could not reproduce on replay. All of them now draw from the same seeded stream: zero-argument new Date() and Date() take the logical clock, performance.now() is that clock minus the segment base, crypto.randomUUID() is the journaled uuid shim, and crypto.getRandomValues() fills from the seed. Passing a timestamp or a date string to Date stays a pure conversion.

    Server. A tracked run whose segment REJECTS instead of settling (the genesis ownership boot refusing a run another process owns, a withheld settlement whose durable write failed) was reported as running for the life of the process, its SSE connections never closed, and neither retention nor the settled cap could release it. GET /runs/:id now answers status: "error" with the typed wire error, connected streams close with a comment naming the failure, a late subscriber gets that comment instead of an empty stream, and the tracked run becomes eligible for retention like any other terminal run.

    Dependencies. @anthropic-ai/sdk moves to ^0.115.0 (the only shipped floor its caret was blocking); in-range minors refresh across the workspace. The four majors stay held: eslint 10 and @eslint/js 10, @types/node 26 against the Node 22.12 floor, and TypeScript 7. The tsdown resolution is pinned at 0.22.3 because it generates the frozen .d.ts artifacts, including the published @rulvar/compat tarball that must repack byte identical.

  • Updated dependencies [6932a9f]

1.84.0

Patch Changes

1.83.0

Patch Changes

1.82.0

Patch Changes

1.81.2

Patch Changes

1.81.1

Patch Changes

1.81.0

Patch Changes

1.80.0

Patch Changes

1.79.0

Patch Changes

1.78.0

Patch Changes

1.77.0

Patch Changes

1.76.0

Patch Changes

1.75.1

Patch Changes

1.75.0

Minor Changes

  • c486de8: The provider output floor and the finish arguments second chance (the v1.74 comparison review, P0.1 + P1.5). ModelCaps.minOutputTokensPerTurn declares the smallest request output cap the provider accepts (OpenAI Responses: 16; absent means one), and the layer-2b budget clamp never dispatches below it: the last-gasp turn goes out AT the floor instead of one token, a remainder that cannot buy the floor is refused as a typed BudgetExhaustedError with zero wire calls, and a configured per-turn cap below the floor is a ConfigError; preflightEstimate reports that configuration as the error finding output-cap-below-provider-minimum. Tool arguments an adapter delivered as the parse-failure wrapper {__unparsed: raw} now get one deterministic second chance before the schema rejection: a strict re-parse, then one bounded normalization (markdown fence, first balanced object, raw control characters escaped inside string literals); a recovered object that passes the tool schema executes as if it had parsed on the wire, with a warn log naming the pass, and replay or resume recovers identically with nothing journaled. The OpenAI wire re-projects an unparseable call as the ORIGINAL raw arguments string instead of the wrapper JSON, so a model no longer learns to imitate {"__unparsed": ...} from its own rewritten history. Both wires drop unsafe-integer x-ratelimit values instead of normalizing 400 digits into Infinity. FakeAdapter gains capsOverrides so offline tests can drive caps-declared behavior like the floor.

Patch Changes

1.74.0

Minor Changes

  • d94beab: Quota drift telemetry and the honest zero (the v1.71 experiment review, P0.5 resized + P1.4). The experiment declared 12M TPM over a provider-real 1M, the local limiter went quiet, and seven live 429s followed with nothing recording the mismatch. Now: both wire adapters parse the provider's x-ratelimit headers on every real 429 into normalized per-minute limits (WireError.data.reportedLimits; the openai wire also gains the raw bucket capture the anthropic wire already had), the loop remembers them per (provider, model) as live telemetry, and the opt-in quota.declaredRules (the SAME rule array preflight takes) makes the engine journal a quota_drift decision plus a warn log whenever a binding declared cap EXCEEDS the provider-reported one, per invocation and dimension, with anthropic's split input and output windows summed against a combined declared tokensPerMinute. Purely observational, synthetic limiter denials never count, and without declaredRules journals and events stay byte identical. On the invoice, an unconfirmed row that recorded zero usage on every counter now carries usageUnknown: true (export-level usageUnknownRows count, CLI usage-unknown marker): the zeros mean "nothing recorded", never "the provider metered nothing"; derived at export time, no journal shape change.

Patch Changes

1.73.0

Patch Changes

1.72.0

Patch Changes

1.71.0

Patch Changes

1.70.1

Patch Changes

1.70.0

Patch Changes

1.69.0

Patch Changes

1.68.0

Patch Changes

1.67.0

Patch Changes

1.66.0

Patch Changes

1.65.0

Patch Changes

1.64.0

Patch Changes

1.63.0

Patch Changes

1.62.0

Patch Changes

1.61.0

Patch Changes

1.60.0

Patch Changes

1.59.4

Patch Changes

1.59.3

Patch Changes

1.59.2

Patch Changes

1.59.1

Patch Changes

1.59.0

Patch Changes

1.58.0

Patch Changes

1.57.0

Patch Changes

1.56.0

Patch Changes

1.55.0

Patch Changes

1.54.0

Patch Changes

1.53.0

Patch Changes

1.52.0

Patch Changes

1.51.0

Patch Changes

1.50.0

Patch Changes

1.49.0

Patch Changes

1.48.0

Patch Changes

1.47.0

Patch Changes

1.46.0

Patch Changes

1.45.0

Patch Changes

1.44.1

Patch Changes

1.44.0

Patch Changes

1.43.0

Patch Changes

1.42.0

Patch Changes

1.41.0

Patch Changes

1.40.0

Patch Changes

1.39.0

Patch Changes

1.38.0

Patch Changes

1.37.0

Patch Changes

1.36.0

Patch Changes

1.35.0

Patch Changes

1.34.0

Patch Changes

1.33.0

Patch Changes

1.32.0

Patch Changes

1.31.0

Patch Changes

  • df6b8f8: Retry-After accepts HTTP optional whitespace padding only. ECMAScript trim() removed far more than the OWS production (space and horizontal tab), so values padded with newline, carriage return, vertical tab, form feed, or NBSP were honored as delays despite the documented exact delta seconds grammar; a real HTTP transport rejects most of those octets, but an injected SDK client or a mock does not. Both first party adapters now match /^[\t ]*([0-9]+)[\t ]*$/ and fall back to the computed policy backoff for every other form.

1.30.0

Patch Changes

  • 87ce985: Parse Retry-After under the exact RFC delta seconds grammar (v1.29.0 review P3). Published 1.29.0 used Number(header), which accepted far more than the documented delta seconds form: an empty or whitespace header became a 0 ms delay (an instant retry instead of the policy backoff), and hex (0x10), exponent (1e3), decimal (1.5), and signed (+3) forms were honored as delays. The value must now be a nonempty run of decimal digits after optional whitespace; every other form (the HTTP date included) omits retryAfterMs so the engine's computed backoff applies, and a huge digit run still clamps to the Node timer maximum.
  • Updated dependencies [87ce985]

1.29.0

Minor Changes

  • 621d566: Make the retry and failover backoff interruptible and validate every provider supplied retry delay (v1.28.0 review P1 and P2).

    The retry engine now races its backoff wait against the host cancel signal (which the run deadline also drives) and the budget ceiling signal: an abort wakes the wait immediately, settles through the canonical aborted outcome (cancelled or exhausted, with every already recorded usage kept), and forbids every further dispatch, including the one behind a keyed limiter queue, so an adapter that ignores its signal can no longer be re entered after an abort. Previously a provider supplied retryAfterMs armed an uninterruptible sleep: a cancel, a crossed deadline, and a crossed budget ceiling all waited out the full backoff and the adapter was dispatched again. The injected retry.sleep(ms) test hook keeps its signature; a hook that loses the race is abandoned without an unhandled rejection, and the native timer path clears its timer so an abandoned long backoff never pins the event loop.

    retryDelayMs is now the defensive boundary the docs promise: only a finite nonnegative provider retryAfterMs replaces the computed delay, anything else (NaN, Infinity, a negative) is ignored as adapter noise, and every returned delay is a finite nonnegative integer clamped to the Node timer maximum, so a malformed or huge value can never arm an instant or overflowing timer. Both first party adapters stop emitting unvalidated Retry-After parses: an unparsable header (the HTTP date form included) omits retryAfterMs entirely instead of producing NaN (which also broke the WireError.data Json invariant by serializing to null), and a huge but finite value is clamped. The mapAnthropicStream TSDoc now states precisely how a truncated stream is reported (the finished flag on the return value, with the adapter synthesizing the terminal error).

    Four frozen fixture cassettes are refrozen for this release (the hashVersion-bump refreeze ceremony applies; hashVersion itself is unchanged and existing journals replay identically): in three cap freeze scenarios the main orchestrator entry now honestly settles cancelled at the cap instead of paying one more ordinary turn whose result the forced finish machinery discarded anyway, and one scenario loses a post abort wait suspension that can no longer be dispatched. Entry identities, keys, and every other row are byte identical.

Patch Changes

1.28.0

Minor Changes

  • d98eb0b: Enforce the terminal stream contract end to end (v1.27.0 deep E2E review P1 and P2). The runtime now fails closed when an adapter stream drains without a terminal finish or error event: the partial turn becomes a retryable transport fault that feeds the ordinary retry and failover machinery instead of settling as ok with truncated text, and a requested abort (cancel, budget ceiling, idle severance) remains a clean end with no fabricated provider error. Consumption stops at the first terminal event, so events after finish can no longer mutate the value, revise the authoritative bill, or trigger tool execution. The first party adapters enforce the same contract at the wire: the Chat Completions mapper no longer synthesizes finish: stop when the stream is cut before a finish_reason (usage the provider did report is still forwarded, half assembled tool calls are dropped), the Responses mapper fails closed on EOF without a response terminal event, and the Anthropic adapter surfaces a read cut before message_stop as a retryable transport error and no longer converts a caller requested abort during messages.create() into a terminal error. mapResponsesStream and mapChatCompletionsStream accept an optional signal so a requested abort keeps ending the stream without a terminal event. The VCR record wrapper now commits its cassette row even when the consumer stops reading at the terminal event (the engine always does now); adapter middleware must not rely on being drained past the terminal. The committed combined-loop-descent catalog cassette is refrozen because stopping consumption at the terminal shifts the deterministic interleaving of two parallel plan children by one scheduler turn; entry content, keys, and the actual hashVersion are unchanged, journals recorded under earlier versions replay unchanged, and this changeset carries the frozen fixture gate's hashVersion-bump ceremony token only to unlock that refreeze.

Patch Changes

1.27.0

Minor Changes

  • 884a433: Types referenced by public signatures are now exported from their package barrels, so the API docs resolve them instead of carrying known incomplete references (v1.26.0 deep E2E review): BaseAppend from @rulvar/core (the fields common to every Replayer append), Block and MappedStop from @rulvar/anthropic (the wire level content block alias and the stop reason mapping), and VcrHeader from @rulvar/testing (the first line of every cassette file). The frozen TypeDoc baseline shrinks from eleven entries to the four vendored Standard Schema notices.

Patch Changes

1.26.0

Patch Changes

1.25.0

Patch Changes

1.24.1

Patch Changes

1.24.0

Patch Changes

1.23.0

Patch Changes

  • 1f9c272: The anthropic() TSDoc no longer describes the SDK's ambient credentials as a precedence chain (v1.22.0 review P3-2). ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN are independent credentials: requests carry x-api-key for the key, bearer Authorization for the token, and BOTH headers when both are set; the config-file token-provider chain is consulted only when apiKey and authToken are both null. The providers guide already said exactly this; the source doc (and the generated API page built from it) had drifted.
  • Updated dependencies [1f9c272]

1.22.0

Patch Changes

1.21.0

Patch Changes

  • 7ee42a0: Declare usageSemantics: 'anthropic-cache-additive-v1' on the adapter: the additive reading it has always normalized under (the Anthropic wire genuinely excludes cache reads and writes from input_tokens, so canonical inputTokens is the sum of all three) now rides usage-bearing journal entries as an auditable policy stamp (v1.20.0 review P1/P2-2).
  • Updated dependencies [7ee42a0]

1.20.0

Patch Changes

1.19.0

Patch Changes

  • Updated dependencies [8cc9a9c]
  • Updated dependencies [8cc9a9c]
  • Updated dependencies [8cc9a9c]

1.18.0

Patch Changes

1.17.0

Patch Changes

1.16.2

Patch Changes

  • 9f07130: Correct five stale rows in the seed capability table: Claude Opus 4.8, Opus 4.7, Opus 4.6, Sonnet 5, and Sonnet 4.6 all carry a 1M context window and 128k max output, verified against the official models table and live GET /v1/models on 2026-07-17. Default routing, the compaction threshold, and the wire max_tokens clamp no longer under-provision runs that never call refreshCaps() (Sonnet 5 was clamped to 64k output for no reason). Every row is now pinned by a committed caps-snapshot.json: an offline test fails when the table and the snapshot disagree, and the weekly live contract workflow audits the snapshot against the model list so provider-side drift pages instead of rotting. Pricing rows are untouched.

1.16.1

Patch Changes

  • fac1ecc: Treat explicit apiKey: null/authToken: null as absent credentials for the structured-auth env suppression, not as chosen ones. The SDK types allow authToken?: string | null, and on v1.16.0 a typed null beside credentials, config, or profile defeated the === undefined suppression check, so an ambient ANTHROPIC_API_KEY (or, with apiKey: null, an ambient ANTHROPIC_AUTH_TOKEN) silently authenticated instead of the configured provider and billed a different principal. The suppression now uses nullish checks: any combination of unset and explicitly null keeps the configured provider in charge, while a real apiKey/authToken string next to structured auth still forwards verbatim under the SDK's own precedence (which never consults the provider once either is set). The Anthropic credential precedence docs now state the SDK's actual order: a set apiKey or authToken disables token providers entirely; providers run only when both are null; a named profile skips both env reads inside the SDK itself.

1.16.0

Minor Changes

  • 5f76cf2: Structured auth wins over ambient env (v1.15 review P2-2). The underlying SDK lets any apiKey, one it read from ANTHROPIC_API_KEY included, beat a configured credentials/config/profile token provider: the provider was called zero times and requests carried x-api-key from the environment. When sdkOptions carries structured auth and no apiKey/authToken is set anywhere, the adapter now passes explicit apiKey: null, authToken: null to the SDK, so the configured provider is the one that authenticates regardless of what the environment exports. Setting an apiKey or authToken yourself next to structured auth keeps verbatim forwarding and the SDK's own precedence, which is now documented exactly (apiKey, then token providers, then authToken). Covered by synthetic tests for the provider, an end-to-end file-backed profile (static user_oauth token, ANTHROPIC_CONFIG_DIR isolated, 0600 credentials), and the explicit-key-beside-provider case.

Patch Changes

1.15.0

Minor Changes

  • 4aee1f3: Production auth surface (v1.14 review P2-2). New sdkOptions on AnthropicAdapterOptions forwards official SDK construction options verbatim, maxRetries excluded from the type (AnthropicSdkOptions) and forced to 0: bearer authToken, an AccessTokenProvider via credentials, config (OIDC/workload-identity federation), profile, plus fetch, timeout, and defaultHeaders. The client option now accepts the official Anthropic instance directly under strict TypeScript, no casts, alongside the structural AnthropicClientLike mock; an injected client with SDK autoretries enabled (maxRetries !== 0) is rejected with a typed ConfigError, as are client combined with construction options and the same field set both top-level and in sdkOptions, all before any network I/O. The implicit SDK credential chain (ANTHROPIC_API_KEY, then bearer ANTHROPIC_AUTH_TOKEN, then config files) is now documented and covered by tests.

Patch Changes

1.14.0

Patch Changes

1.13.0

Patch Changes

1.12.0

Patch Changes

1.11.0

Patch Changes

1.10.0

Patch Changes

1.9.0

Minor Changes

  • 7577f8e: Correct the Anthropic fallback pricing to the official table and export versioned price tables from both first-party adapters.

    The ANTHROPIC_MODELS seed rows had never been audited against the published price list and overcharged every current Claude model: Fable 5 was seeded at exactly 2x the official rate (20/100 vs 10/50 per MTok, cache rates likewise), Opus 4.8 at 12/60 vs 5/25, Opus 4.7 at 10/50 vs 5/25, and Opus 4.6 at 15/75 vs 5/25. Claude Sonnet 5 now carries its introductory price (2/10, in effect through 2026-08-31); Haiku 4.5 and Sonnet 4.6 were already correct. Cost reports for affected models drop accordingly, and budget ceilings admit roughly twice the work they previously rejected.

    New exports ANTHROPIC_PRICING (anthropic-2026-07-16) and OPENAI_PRICING (openai-2026-07-16) publish the seed rows as versioned PriceTables for createEngine({ pricing }), so runs journal a concrete pricing version instead of unpriced and price revisions become explicit table updates. createTestEngine gained a pricing passthrough for testing against a versioned table.

Patch Changes

1.8.0

Patch Changes

  • Updated dependencies [25724b5]
  • Updated dependencies [57ea1de]
  • Updated dependencies [7884ec5]
  • Updated dependencies [52db30d]

1.7.0

Patch Changes

  • Updated dependencies [45285aa]
  • Updated dependencies [2f20d1d]
  • Updated dependencies [22f65a8]
  • Updated dependencies [2ddfa29]
  • Updated dependencies [2abd9c2]
  • Updated dependencies [1c1175d]

1.6.0

Minor Changes

  • df416fc: Correct and extend model pricing: GPT-5.6 entries, long-context tiers, no fabricated prices, no double-charged cache.

    • Pricing gains optional long-context tiers (PricingTier): the highest threshold strictly below the full prompt re-prices the entire request, input-side rates (cache included) scaling by inputMultiplier and the output rate by outputMultiplier. Existing linear rows are untouched.
    • @rulvar/openai seeds gpt-5.6-sol and its gpt-5.6 alias with the official caps and pricing (1,050,000 context, 128,000 max output, $5/$0.50/$30 per MTok, $6.25 cache write, 2x input and 1.5x output above 272K input tokens). Previously the unknown-model fallback silently priced them as gpt-5.4.
    • Unknown model ids in both first-class adapters keep conservative transport caps but no longer receive a fabricated price row: their usage surfaces in CostReport.unpriced and a USD ceiling warns that it cannot bound them. Provide a versioned createEngine({ pricing }) row for hosted models the tables do not know yet.
    • priceUsdOf no longer double-charges cache tokens: under the Usage invariant inputTokens is the full prompt, so the input rate now bills only the uncached remainder while cache reads and writes bill at their own rates (a row without cache rates bills them at the input rate). Cache-heavy runs previously over-attributed cost by the full input rate on every cached token.
    • Admission reserve estimation routes through the same priceUsdOf, so estimates and settled costs share one formula, tiers included.
    • Model id resolution picks the longest matching table prefix, so a dated gpt-5.5-pro-... snapshot resolves to the pro entry, never the shorter gpt-5.5 sibling.
  • 886d065: Make the first-class adapters genuinely streaming: every canonical event is yielded AS its provider event is consumed.

    Both adapters (and openaiCompatible) buffered the complete canonical event stream in an internal array and yielded it only after the provider response finished. Consequences fixed by this change: agent:stream was never live; the stream-idle watchdog saw zero events during healthy generation, so any turn longer than streamIdleTimeoutMs (default 120s) was falsely severed as idle and retried; a budget or external abort lost ALL partial usage (the journal recorded zero for tokens the provider billed); and every delta of a long response was retained in memory.

    • mapAnthropicStream, mapResponsesStream, and mapChatCompletionsStream are now async generators: they yield each ChatEvent as the corresponding provider event is consumed, with the consumer's pull as the only pacing (natural backpressure, no queue, no detached work). The Anthropic mapper's return value carries the accumulated pause_turn state; TurnMapping no longer has the redundant events array field. Callers of the old callback signatures (emit parameter) must switch to iterating the generator.
    • Adapter behavior is preserved: canonical id mapping, thinking/reasoning retention, pause_turn continuation and its cap (each segment now streams live before the continuation dispatches), tool argument assembly, typed refusals and errors, exactly one canonical terminal event, the degraded Chat Completions path (visible in providerMetadata.openai.degradedPath), abort propagation, usage normalization, and SDK autoretries disabled.
    • New regression tests with gated fake SDK clients prove the first stream().next() resolves before the provider terminal exists, aborts reach the in-flight provider iterable after the first delta, a paused consumer causes zero read-ahead (lock-step pulls), pause_turn segment deltas arrive before the continuation request, and exactly one terminal event survives.

Patch Changes

  • da4dbad: Write the product name as Rulvar in prose: package READMEs, npm descriptions, and the documentation site now capitalize the brand. Identifiers keep their exact casing, so package names, the rulvar binary, rulvar.config.mjs, the .rulvar store directory, the rulvar.* OTel attributes, and every URL are unchanged. Documentation and metadata only; no runtime behaviour changes.
  • Updated dependencies [da4dbad]
  • Updated dependencies [487da86]
  • Updated dependencies [df416fc]
  • Updated dependencies [a737810]
  • Updated dependencies [9eb66b4]

1.5.2

Patch Changes

1.5.1

Patch Changes

1.5.0

Patch Changes

1.4.0

Patch Changes

1.3.2

Patch Changes

  • ddef383: Every published package now ships a README, so its npm page states what the package is, how it installs, and where the documentation lives (npm includes README.md in the tarball regardless of the files allowlist, so no manifest changes are involved; @rulvar/compat gains its README on its own next release). Alongside, the repository-level pages are refreshed to the current project state: the root README is rewritten around the never-pay-twice pitch with a runnable quickstart condensation and the full package table, CONTRIBUTING.md lists the complete PR gate set, the examples README drops retired-spec citations for live docs.rulvar.com links and documents the dogfood journal replay, and the pointer README gets the same treatment.
  • Updated dependencies [ddef383]

1.3.1

Patch Changes

  • 7d1552e: Runtime message strings no longer cite the retired internal specification set: error and warning messages, validation issues, and the CLI help text drop the dangling docs/NN, section ... references, pointing at https://docs.rulvar.com pages where a pointer earns its place (the CLI help header, tool naming, toolset registries, bare resume). The umbrella package description sheds the naming-contingency note: the unscoped alias is published and owned. Three strings embedded in frozen recordings stay byte-identical on purpose (the no-progress abort reason and two testing-internal recorder strings), as does the byte-locked golden-fold fixture. Test-file comments lose their citations too; test titles are unchanged.
  • Updated dependencies [7d1552e]

1.3.0

Patch Changes

1.2.0

Patch Changes

  • 154507b: TSDoc and inline comments no longer cite the retired internal specification set (the pre-docs-site docs/NN, section ... references). The citations either became links to the public documentation at docs.rulvar.com or were dropped where the comment already carried the rule; traceability markers (DEF-n, XF-nn, FR-nnn, OQ-nn, W-nnn) are untouched. Comment-only change: no runtime behavior, no API shapes, and no runtime message strings were modified; the frozen golden-fold fixture is byte-identical.
  • Updated dependencies [3bfaec0]
  • Updated dependencies [890f42c]
  • Updated dependencies [154507b]

1.1.0

Patch Changes

  • f2253cb: The adapter scrubs constrained-decoding-unsupported keywords from the wire copy of strict tool schemas and output format schemas (minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, maxItems; measured live, docs/04 section 4.3 as amended). The orchestrator's spawn tools carry integer minimums, so every live orchestrate run died with a pre-first-call 400 ("For 'integer' type, property 'minimum' is not supported") at zero cost, which is what kept criterion 2 of the M12 checkpoint unmeasurable. The engine-side schema stays unscrubbed and still validates tool args and structured output, so the dropped keywords remain enforced; only the model-side hint is lost.
  • 63b2c01: Two defects the first live M12 checkpoint run surfaced. The Anthropic capability table lacked a Haiku 4.5 entry, so the dated id fell through to the current-generation default and the adapter sent adaptive thinking, which that model rejects with a live 400 (every haiku run died at zero cost): claude-haiku-4-5 (and its dated snapshots by the prefix rule) now resolves to the enabled-budget thinking form with real haiku pricing, meaning the default wire omits thinking entirely. And the checkpoint's criterion 2 could pass vacuously when both arms scored zero at zero cost (zero satisfies "at least equal at no more cost"): the card-informed arm must now win something real (nonzero n and pass rate) before the criterion can hold.
  • 99dc3ed: The second Haiku 4.5 wire incompatibility (the first live probe after the caps entry): the model also rejects the top-level effort parameter with a 400, so its capability entry now declares empty reasoningEfforts and the router scrubs effort off the wire (the requested effort stays in identity). Verified live: a haiku run completes ok.
  • Updated dependencies [d16b04a]

1.0.0

Patch Changes

  • Updated dependencies [0e0b569]
  • Updated dependencies [b28b7a3]
  • Updated dependencies [b53a89e]
  • Updated dependencies [4454175]
  • Updated dependencies [6599ca8]
  • Updated dependencies [6649e5f]
  • Updated dependencies [fd2f83b]
  • Updated dependencies [01d6b2d]
  • Updated dependencies [9a20dbb]
  • Updated dependencies [0fbe7ea]
  • Updated dependencies [ebe0abc]
  • Updated dependencies [a3079d0]
  • Updated dependencies [596a39b]
  • Updated dependencies [464ab6e]

0.9.0

Patch Changes

  • Updated dependencies [84f94d4]
  • Updated dependencies [65c7b2c]
  • Updated dependencies [a2a3243]
  • Updated dependencies [ebc8101]

0.8.0

Patch Changes

  • Updated dependencies [85d55cf]
  • Updated dependencies [b88c9e3]
  • Updated dependencies [f3c4613]
  • Updated dependencies [a41c20f]
  • Updated dependencies [f4e70be]
  • Updated dependencies [75d1646]
  • Updated dependencies [0627413]
  • Updated dependencies [55c0f87]
  • Updated dependencies [fd33871]
  • Updated dependencies [e70e7f4]
  • Updated dependencies [bc9c903]

0.7.0

Patch Changes

  • Updated dependencies [fd1d06c]
  • Updated dependencies [6fcf296]
  • Updated dependencies [dcc97a9]
  • Updated dependencies [434dc83]
  • Updated dependencies [03173c1]
  • Updated dependencies [11c0afc]

0.6.0

Patch Changes

  • Updated dependencies [fa05007]
  • Updated dependencies [9234dc8]
  • Updated dependencies [644512c]
  • Updated dependencies [8a41656]
  • Updated dependencies [02f7f7a]

0.5.0

Minor Changes

  • ac274f4: M4-T01 role protocol completion. The full trigger protocol for the six invocation roles lands in @rulvar/core (model/roles.ts):

    • Extract necessity is completed per docs/04 section 8.3: a separate final structured-output invocation fires when a schema is set AND (routing directs extract to a different model OR the loop model's required tier cannot ride a tools-available turn OR finalize is routed). The required-tier rule is new: a forced-tool tier pins toolChoice to emit_result and cannot ride while the agent's tools must remain available, so such agents now pay one separate extract call instead of silently losing tool access. Agents without tools keep the M1 single-shot behavior byte for byte.
    • The finalize role fires for the first time: only when configured in routing and only for tool-bearing agents, as one synthesis invocation with toolChoice 'none' over the full transcript after tools stop. Its text is the output for schema-less calls; with a schema the separate extract runs over the transcript including the synthesis.
    • A separate extract invocation over a tool-bearing transcript now carries the agent's tool contracts (both providers reject tool-use history without tool definitions) with toolChoice pinned to 'none' or to emit_result per tier.
    • Both adapters map toolChoice: 'none' to the provider's explicit none choice with the tools param present instead of dropping tools from the request.
    • createTestEngine no longer routes finalize by default: the routing key is the firing opt-in, and the old default would have summoned a synthesis call for every tool-bearing test agent. Tests that want finalize route it explicitly.

    Identity is untouched: extract and finalize resolutions never enter the spawn content key, and existing journals replay unchanged.

  • 5735d92: M4-T02 HistoryProjector. Cross-provider history projection lands in @rulvar/core (model/projector.ts) and the retention pipeline that feeds it:

    • projectHistory projects the canonical history into a target provider's view: provider-raw parts ride if and only if the target adapter's provider family matches the part's provider; everything else passes through untouched. The agent loop projects EVERY outgoing request (loop turns, finalize, extract), so per-role provider mixing inside one agent yields a valid wire history on each side.
    • Retention transport: adapters ship a turn's blocks-to-retain in stream order via finish.providerMetadata[<adapter id>].retainedParts; the runtime lifts them into provider-raw parts at the HEAD of the turn's canonical assistant message. @rulvar/anthropic ships thinking and redacted_thinking blocks (signatures intact, pause_turn continuations included); @rulvar/openai ships reasoning items with their encrypted_content. Retained blocks now actually reach the canonical history, survive checkpoints, and echo byte-exact to their own provider on every subsequent turn.
    • ProviderAdapter gains an optional provider field: the provider family for provider-raw matching (default = adapter id). The first-class adapters declare 'anthropic' and 'openai'; openaiCompatible gateways declare 'openai' whatever their custom id, so same-family adapters share retained blocks and projections.

    Identity is untouched: projection state never enters content keys, and adapters that ship no retention payload (FakeAdapter included) produce byte-identical histories.

Patch Changes

  • Updated dependencies [ac274f4]
  • Updated dependencies [5735d92]
  • Updated dependencies [46ca98e]
  • Updated dependencies [8ae129e]
  • Updated dependencies [d1c4525]
  • Updated dependencies [b840aba]

0.4.0

Patch Changes

  • Updated dependencies [dfe03b5]
  • Updated dependencies [d2089a7]
  • Updated dependencies [3f60234]
  • Updated dependencies [f668890]
  • Updated dependencies [16d7aa6]
  • Updated dependencies [6513ce8]
  • Updated dependencies [7dad493]
  • Updated dependencies [2bbf180]

0.3.0

Patch Changes

  • Updated dependencies [43444f6]
  • Updated dependencies [279881b]
  • Updated dependencies [9fd0966]
  • Updated dependencies [24ebadf]
  • Updated dependencies [a1b35d3]
  • Updated dependencies [18a5821]

0.2.0

Minor Changes

  • 527c9b4: M1-T12/T13: the two first-class adapters on the July 2026 surfaces. @rulvar/anthropic: adaptive thinking, the output_config umbrella (effort passthrough including max, native json_schema format), strict tools, cache_control compilation from cacheHint (deepest-4 kept), thinking-block retention with provider-granularity projection, pause_turn absorption without synthetic user messages, the full stop-reason table with typed refusal stop details, count_tokens, capabilities-bearing refreshCaps, retry-after/x-ratelimit/529 signaling, SDK autoretries disabled, usage normalization under the Usage invariant. @rulvar/openai: Responses API with manual item replay only (store false, encrypted reasoning echoed verbatim; previous_response_id/Conversations rejected as ConfigError), flattened strict function tools, text.format json_schema, the typed SSE catalog mapped to ChatEvent, the Chat Completions degraded path (visible via providerMetadata), effort mapping with the documented lossy max-to-xhigh downmap and provider none via providerOptions only, usage normalization.

Patch Changes

  • Updated dependencies [c24228d]
  • Updated dependencies [c50871e]
  • Updated dependencies [1af8fb9]
  • Updated dependencies [1fe0249]
  • Updated dependencies [5c4fc32]

0.1.0

Minor Changes

  • f4e2be9: M0 repo bootstrap (v0.1.0, docs/10-implementation-plan.md section "M0"): monorepo scaffold on the committed toolchain (pnpm 11 workspaces with catalogs, TypeScript 6.0, tsdown, Vitest 4, ESLint 9 flat config, Turborepo 2, changesets fixed mode, npm trusted publishing), the docs/ canon as single source of truth, the L0 contracts skeleton in @rulvar/core, and the vendored dependencies (StandardSchemaV1/StandardJSONSchemaV1 types, the @cfworker/json-schema lineage validator subset, a first-party monotonic ULID). Placeholder scaffolds only: no public API ships in this release.

Patch Changes

@rulvar/bridge-ai-sdk

1.252.0

Patch Changes

  • Updated dependencies [3ccb6cf]
  • Updated dependencies [52d807f]
  • Updated dependencies [517ed00]
  • Updated dependencies [a7e589d]
  • Updated dependencies [76e95eb]

1.251.0

Patch Changes

  • Updated dependencies [e7e829c]
  • Updated dependencies [5982be8]
  • Updated dependencies [7c58fb2]
  • Updated dependencies [b3e465a]
  • Updated dependencies [c4e5d6a]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [0ae8b85]
  • Updated dependencies [7932936]
  • Updated dependencies [7932936]
  • Updated dependencies [88da0ed]
  • Updated dependencies [06c0e85]

1.250.0

Patch Changes

  • Updated dependencies [0e240b9]
  • Updated dependencies [6fe585e]
  • Updated dependencies [c5eb19c]
  • Updated dependencies [565c13b]
  • Updated dependencies [c6d197b]
  • Updated dependencies [c9d9729]
  • Updated dependencies [fed9db6]
  • Updated dependencies [df9ed76]
  • Updated dependencies [3020912]
  • Updated dependencies [d8d598d]

1.249.0

Patch Changes

  • Updated dependencies [8862133]
  • Updated dependencies [0d7a717]
  • Updated dependencies [e4428bd]
  • Updated dependencies [d6873c1]
  • Updated dependencies [4092e8d]
  • Updated dependencies [e086590]
  • Updated dependencies [1411938]
  • Updated dependencies [737d1ee]
  • Updated dependencies [634f966]
  • Updated dependencies [052cc26]
  • Updated dependencies [bbae134]

1.248.0

Patch Changes

  • Updated dependencies [8d0cd69]
  • Updated dependencies [81065e4]
  • Updated dependencies [8573f20]
  • Updated dependencies [95f6a5e]

1.247.0

Patch Changes

  • Updated dependencies [1933ecc]
  • Updated dependencies [db0a5f0]
  • Updated dependencies [b698726]
  • Updated dependencies [48348d2]
  • Updated dependencies [4cfa1cc]
  • Updated dependencies [4b7197a]
  • Updated dependencies [5ebc842]
  • Updated dependencies [16ff6b9]
  • Updated dependencies [0c9941d]

1.246.0

Minor Changes

  • d165b0c: The profile hash sees the constructions, and counts what it cannot (RV4101; the debt RV4009 named). The regulated floor binds what flows through options, but the postures that decide whether a tool list can drift beneath a run (an mcp() source's drift and discovery bounds, RV1516/RV1808) or whether a provider executes tools outside the permission chain (the AI SDK bridge's providerExecutedTools seam) live on CONSTRUCTIONS the options never see; RV4009 excluded them from the hash by principle ("a hash must not imply what it cannot verify") and named them in prose. This train makes the verifiable part verified. A risk-bearing construction now exposes describeRegulatedPosture(), a PURE snapshot of what was chosen at build time (no wire, no connect): mcp() reports { drift, bounds }, bridgeAiSdk() reports { providerExecutedTools }, both implemented this release. compileRegulatedProfile walks every construction its options reach (adapters, named toolsets, profile toolsets, each object once), REFUSES a loosened posture by field name (construction['mcp:http:...'].drift must be 'refuse'; bounds must be declared; the bridge must deny), refuses outright a descriptor of a shape or kind it cannot judge, and folds the sorted descriptors into the hashed posture map under construction, beside an unrecognized count of the constructions that exposed nothing, so the hash names its own blind spot instead of implying totality. REGULATED_VERSION bumps to 2 (regulated:2:<hash>): the map's meaning changed, and a v1 fingerprint must never collide with a v2 reading of the same options. Deliberately open, by name: a construction mutated AFTER compile time; the descriptor is a snapshot, not a lease, and the first-use re-assertion is the RV1608 template applied in its own train. Probes pin the drift refusal and the blind-spot count.

Patch Changes

  • Updated dependencies [d165b0c]
  • Updated dependencies [d59f4a0]
  • Updated dependencies [46907ac]
  • Updated dependencies [9929ad3]
  • Updated dependencies [1790a6a]

1.245.0

Patch Changes

  • Updated dependencies [b4d47a8]
  • Updated dependencies [dee6db4]
  • Updated dependencies [b85c113]
  • Updated dependencies [bc556e7]
  • Updated dependencies [9f11d29]
  • Updated dependencies [19bcea0]
  • Updated dependencies [60b461c]
  • Updated dependencies [61e3a1a]
  • Updated dependencies [a156b81]
  • Updated dependencies [0bd7045]

1.244.0

Patch Changes

  • Updated dependencies [38d839a]
  • Updated dependencies [ce13b0f]
  • Updated dependencies [4fa23e3]
  • Updated dependencies [6841c69]
  • Updated dependencies [f56721d]
  • Updated dependencies [c894a43]
  • Updated dependencies [f6944a3]
  • Updated dependencies [23fd0e0]

1.243.0

Patch Changes

  • Updated dependencies [746d1f4]
  • Updated dependencies [009b29c]
  • Updated dependencies [1674cbe]
  • Updated dependencies [bd096bc]

1.242.0

Patch Changes

  • Updated dependencies [6e3438e]
  • Updated dependencies [ba5cf67]
  • Updated dependencies [c2d1531]

1.241.0

Patch Changes

  • Updated dependencies [dbcdd24]
  • Updated dependencies [7ae7243]
  • Updated dependencies [4f832c4]
  • Updated dependencies [7452d3d]
  • Updated dependencies [a4e22bf]
  • Updated dependencies [82df4af]

1.240.0

Patch Changes

1.239.0

Patch Changes

  • Updated dependencies [74ce99a]
  • Updated dependencies [ccd0665]
  • Updated dependencies [0c5ce21]
  • Updated dependencies [0616934]

1.238.0

Patch Changes

  • Updated dependencies [cf00947]
  • Updated dependencies [c7b9382]
  • Updated dependencies [88aea96]
  • Updated dependencies [6da8d05]
  • Updated dependencies [eae5c4c]

1.237.0

Patch Changes

  • Updated dependencies [9d6a279]
  • Updated dependencies [49a98f6]
  • Updated dependencies [a734ca0]
  • Updated dependencies [deb406f]

1.236.0

Patch Changes

1.235.0

Patch Changes

  • Updated dependencies [ba4e10d]
  • Updated dependencies [172402b]
  • Updated dependencies [2ecd787]
  • Updated dependencies [e20a5e9]
  • Updated dependencies [98c8691]
  • Updated dependencies [c70def0]

1.234.0

Patch Changes

1.233.0

Patch Changes

  • Updated dependencies [48b5200]
  • Updated dependencies [73bc32b]
  • Updated dependencies [e63b743]
  • Updated dependencies [ef45da7]

1.232.0

Patch Changes

  • Updated dependencies [1440410]
  • Updated dependencies [6e467f4]
  • Updated dependencies [0b14293]
  • Updated dependencies [e3bcab2]
  • Updated dependencies [b55a0f7]

1.231.0

Patch Changes

  • Updated dependencies [4eb4b56]
  • Updated dependencies [bc8f09e]
  • Updated dependencies [ff9b8c2]

1.230.0

Patch Changes

1.229.0

Patch Changes

  • Updated dependencies [3370342]
  • Updated dependencies [2fb6656]
  • Updated dependencies [edce170]

1.228.0

Patch Changes

  • Updated dependencies [4034fac]
  • Updated dependencies [a54b085]
  • Updated dependencies [9d0a9be]
  • Updated dependencies [be9ef28]

1.227.0

Patch Changes

  • Updated dependencies [f262e9f]
  • Updated dependencies [f191ff7]
  • Updated dependencies [fbbfbe8]
  • Updated dependencies [263b5e8]
  • Updated dependencies [db4d56d]
  • Updated dependencies [41f93a9]
  • Updated dependencies [98c8ca9]

1.226.0

Patch Changes

1.225.0

Patch Changes

1.224.0

Patch Changes

1.223.0

Patch Changes

1.222.0

Patch Changes

1.221.0

Patch Changes

1.220.0

Patch Changes

1.219.0

Patch Changes

1.218.0

Patch Changes

1.217.0

Patch Changes

1.216.0

Patch Changes

1.215.0

Patch Changes

1.214.0

Patch Changes

1.213.0

Patch Changes

1.212.0

Patch Changes

1.211.0

Patch Changes

1.210.0

Patch Changes

1.209.0

Patch Changes

1.208.0

Patch Changes

1.207.0

Patch Changes

1.206.0

Patch Changes

1.205.0

Patch Changes

1.204.0

Patch Changes

1.203.0

Patch Changes

1.202.0

Patch Changes

1.201.0

Patch Changes

1.200.0

Patch Changes

1.199.0

Patch Changes

1.198.0

Patch Changes

1.197.0

Patch Changes

1.196.0

Patch Changes

1.195.0

Patch Changes

1.194.0

Patch Changes

1.193.0

Patch Changes

1.192.0

Patch Changes

1.191.0

Patch Changes

1.190.0

Patch Changes

1.189.0

Patch Changes

1.188.0

Minor Changes

  • b6d0bc8: Provider-executed tools become a policy surface, denied by default (RV1806). The bridge used to absorb a wrapped provider's server-side tool exchanges (web search, code execution) silently into retention: calls that never pass the engine's ToolDef registry, risk classes, ask rules, or approvals, with effects on provider infrastructure no permission chain can see. Under the new default providerExecutedTools: 'deny' the first provider-executed exchange fails the turn with a typed terminal error naming the tool; 'allow' opts back into the old retention behavior and additionally names every provider-executed call on the finish metadata (providerExecutedTools: [{ toolName, toolCallId }]), so the journaled record of the turn says what the provider ran. Hosts that relied on the silent absorption must now pass bridgeAiSdk(model, { providerExecutedTools: 'allow' }); a malformed value refuses typed at construction.

Patch Changes

1.187.0

Patch Changes

1.186.0

Patch Changes

1.185.0

Patch Changes

1.184.0

Patch Changes

1.183.0

Patch Changes

1.182.0

Patch Changes

1.181.0

Patch Changes

1.180.0

Patch Changes

1.179.0

Patch Changes

1.178.0

Patch Changes

1.177.0

Patch Changes

1.176.0

Patch Changes

1.175.0

Patch Changes

1.174.0

Patch Changes

1.173.0

Patch Changes

1.172.0

Patch Changes

1.171.0

Patch Changes

1.170.0

Patch Changes

1.169.0

Patch Changes

1.168.0

Patch Changes

1.167.0

Patch Changes

1.166.0

Patch Changes

1.165.0

Patch Changes

1.164.0

Patch Changes

1.163.0

Patch Changes

1.162.0

Patch Changes

1.161.0

Patch Changes

1.160.0

Patch Changes

1.159.0

Patch Changes

1.158.0

Patch Changes

1.157.0

Patch Changes

1.156.0

Patch Changes

1.155.0

Patch Changes

1.154.0

Patch Changes

1.153.0

Patch Changes

1.152.0

Patch Changes

1.151.0

Patch Changes

1.150.0

Patch Changes

1.149.0

Patch Changes

1.148.0

Patch Changes

1.147.0

Patch Changes

1.146.0

Patch Changes

1.145.0

Patch Changes

1.144.0

Patch Changes

1.143.0

Patch Changes

1.142.0

Patch Changes

1.141.0

Patch Changes

1.140.0

Patch Changes

1.139.0

Patch Changes

1.138.0

Patch Changes

1.137.0

Patch Changes

1.136.0

Patch Changes

1.135.0

Patch Changes

1.134.0

Patch Changes

1.133.0

Patch Changes

1.132.0

Patch Changes

1.131.0

Patch Changes

1.130.0

Patch Changes

1.129.0

Patch Changes

1.128.0

Patch Changes

1.127.0

Patch Changes

1.126.0

Patch Changes

1.125.0

Patch Changes

1.124.0

Patch Changes

1.123.0

Patch Changes

1.122.0

Patch Changes

1.121.0

Patch Changes

1.120.0

Patch Changes

1.119.0

Patch Changes

1.118.0

Patch Changes

1.117.0

Patch Changes

1.116.0

Patch Changes

1.115.0

Patch Changes

1.114.0

Patch Changes

1.113.0

Patch Changes

1.112.0

Patch Changes

1.111.0

Patch Changes

1.110.0

Patch Changes

1.109.0

Patch Changes

1.108.0

Patch Changes

1.107.0

Patch Changes

1.106.0

Patch Changes

1.105.0

Patch Changes

1.104.0

Patch Changes

1.103.0

Patch Changes

1.102.0

Patch Changes

1.101.0

Patch Changes

1.100.0

Patch Changes

1.99.1

Patch Changes

1.99.0

Patch Changes

1.98.0

Patch Changes

1.97.0

Patch Changes

1.96.0

Patch Changes

1.95.0

Patch Changes

1.94.0

Patch Changes

1.93.0

Patch Changes

1.92.0

Patch Changes

1.91.0

Patch Changes

1.90.0

Patch Changes

1.89.0

Minor Changes

  • f18b671: Provider-id provenance parity across every adapter path (RV401, the eighth comparison experiment). The AI SDK bridge now ships the flat responseId the core reconciliation record reads, beside the nested response object it always emitted, and an error finish carries the accumulated response metadata and warnings on the error event instead of dropping them (retained parts stay deliberately absent there: a failed turn is discarded, never re-injected). The core agent loop captures provider metadata from error events and falls back to the AI SDK's nested response.id shape when a third-party adapter ships only that, with the flat first-class form winning when both are present. The OpenAI adapter attaches the failed response's id to its response.failed error event, so a billed failure reconciles against the provider statement exactly like an ok row. End-to-end tests pin a bridged engine run whose per-call reconciliation records carry ids on the success, retry, and billed-failure paths alike.

Patch Changes

1.88.0

Patch Changes

1.87.0

Patch Changes

1.86.0

Patch Changes

1.85.0

Patch Changes

1.84.0

Minor Changes

  • bc9105f: First-class doctrine parity for the bridge (the cycle 82 deep review). An error finish now ships the provider's usage as a usage event ahead of the terminal error, so a failed stream's paid tokens land on the meter instead of billing zero. Unparseable client tool arguments ship the {__unparsed: raw} wrapper the engine's deterministic second chance repairs, instead of destroying the whole paid turn with a terminal error, and history projection unwraps the wrapper back to the raw text the model wrote (the openai wire's imitation guard, mirrored). Retention fidelity: a retained errored provider-executed tool result reinserts as error-json instead of a success, preliminary provider-executed results are no longer retained (only the final result is), and a reasoning segment still open at finish is flushed into retention instead of silently dropped. A stream that drains without a finish part under a requested abort now ends silently (the v1.27.0 posture) instead of minting a fake transport error, and the bridge cancels the wrapped V4 stream on early termination instead of abandoning the provider connection until GC.

Patch Changes

1.83.0

Patch Changes

1.82.0

Patch Changes

1.81.2

Patch Changes

1.81.1

Patch Changes

1.81.0

Patch Changes

1.80.0

Patch Changes

1.79.0

Patch Changes

1.78.0

Patch Changes

1.77.0

Patch Changes

1.76.0

Patch Changes

1.75.1

Patch Changes

1.75.0

Patch Changes

1.74.0

Patch Changes

1.73.0

Patch Changes

1.72.0

Patch Changes

1.71.0

Patch Changes

1.70.1

Patch Changes

1.70.0

Patch Changes

1.69.0

Patch Changes

1.68.0

Patch Changes

1.67.0

Patch Changes

1.66.0

Patch Changes

1.65.0

Patch Changes

1.64.0

Patch Changes

1.63.0

Patch Changes

1.62.0

Patch Changes

1.61.0

Patch Changes

1.60.0

Patch Changes

1.59.4

Patch Changes

1.59.3

Patch Changes

1.59.2

Patch Changes

1.59.1

Patch Changes

1.59.0

Patch Changes

1.58.0

Patch Changes

1.57.0

Patch Changes

1.56.0

Patch Changes

1.55.0

Patch Changes

1.54.0

Patch Changes

1.53.0

Patch Changes

1.52.0

Patch Changes

1.51.0

Patch Changes

1.50.0

Patch Changes

1.49.0

Patch Changes

1.48.0

Patch Changes

1.47.0

Patch Changes

1.46.0

Patch Changes

1.45.0

Patch Changes

1.44.1

Patch Changes

1.44.0

Patch Changes

1.43.0

Patch Changes

1.42.0

Patch Changes

1.41.0

Patch Changes

1.40.0

Patch Changes

1.39.0

Patch Changes

1.38.0

Patch Changes

1.37.0

Patch Changes

1.36.0

Patch Changes

1.35.0

Patch Changes

1.34.0

Patch Changes

1.33.0

Patch Changes

1.32.0

Patch Changes

1.31.0

Patch Changes

1.30.0

Patch Changes

1.29.0

Patch Changes

1.28.0

Patch Changes

1.27.0

Patch Changes

1.26.0

Patch Changes

1.25.0

Patch Changes

1.24.1

Patch Changes

1.24.0

Patch Changes

1.23.0

Patch Changes

1.22.0

Patch Changes

1.21.0

Patch Changes

1.20.0

Patch Changes

1.19.0

Patch Changes

  • Updated dependencies [8cc9a9c]
  • Updated dependencies [8cc9a9c]
  • Updated dependencies [8cc9a9c]

1.18.0

Patch Changes

1.17.0

Patch Changes

1.16.2

Patch Changes

1.16.1

Patch Changes

1.16.0

Patch Changes

1.15.0

Patch Changes

1.14.0

Patch Changes

1.13.0

Patch Changes

1.12.0

Patch Changes

1.11.0

Patch Changes

1.10.0

Patch Changes

1.9.0

Patch Changes

1.8.0

Patch Changes

  • Updated dependencies [25724b5]
  • Updated dependencies [57ea1de]
  • Updated dependencies [7884ec5]
  • Updated dependencies [52db30d]

1.7.0

Patch Changes

  • Updated dependencies [45285aa]
  • Updated dependencies [2f20d1d]
  • Updated dependencies [22f65a8]
  • Updated dependencies [2ddfa29]
  • Updated dependencies [2abd9c2]
  • Updated dependencies [1c1175d]

1.6.0

Patch Changes

  • da4dbad: Write the product name as Rulvar in prose: package READMEs, npm descriptions, and the documentation site now capitalize the brand. Identifiers keep their exact casing, so package names, the rulvar binary, rulvar.config.mjs, the .rulvar store directory, the rulvar.* OTel attributes, and every URL are unchanged. Documentation and metadata only; no runtime behaviour changes.
  • Updated dependencies [da4dbad]
  • Updated dependencies [487da86]
  • Updated dependencies [df416fc]
  • Updated dependencies [a737810]
  • Updated dependencies [9eb66b4]

1.5.2

Patch Changes

1.5.1

Patch Changes

1.5.0

Patch Changes

1.4.0

Patch Changes

1.3.2

Patch Changes

  • ddef383: Every published package now ships a README, so its npm page states what the package is, how it installs, and where the documentation lives (npm includes README.md in the tarball regardless of the files allowlist, so no manifest changes are involved; @rulvar/compat gains its README on its own next release). Alongside, the repository-level pages are refreshed to the current project state: the root README is rewritten around the never-pay-twice pitch with a runnable quickstart condensation and the full package table, CONTRIBUTING.md lists the complete PR gate set, the examples README drops retired-spec citations for live docs.rulvar.com links and documents the dogfood journal replay, and the pointer README gets the same treatment.
  • Updated dependencies [ddef383]

1.3.1

Patch Changes

  • 7d1552e: Runtime message strings no longer cite the retired internal specification set: error and warning messages, validation issues, and the CLI help text drop the dangling docs/NN, section ... references, pointing at https://docs.rulvar.com pages where a pointer earns its place (the CLI help header, tool naming, toolset registries, bare resume). The umbrella package description sheds the naming-contingency note: the unscoped alias is published and owned. Three strings embedded in frozen recordings stay byte-identical on purpose (the no-progress abort reason and two testing-internal recorder strings), as does the byte-locked golden-fold fixture. Test-file comments lose their citations too; test titles are unchanged.
  • Updated dependencies [7d1552e]

1.3.0

Patch Changes

1.2.0

Patch Changes

  • 154507b: TSDoc and inline comments no longer cite the retired internal specification set (the pre-docs-site docs/NN, section ... references). The citations either became links to the public documentation at docs.rulvar.com or were dropped where the comment already carried the rule; traceability markers (DEF-n, XF-nn, FR-nnn, OQ-nn, W-nnn) are untouched. Comment-only change: no runtime behavior, no API shapes, and no runtime message strings were modified; the frozen golden-fold fixture is byte-identical.
  • Updated dependencies [3bfaec0]
  • Updated dependencies [890f42c]
  • Updated dependencies [154507b]

1.1.0

Patch Changes

1.0.0

Minor Changes

  • b728c48: M9-T01: bridgeAiSdk, the long-tail provider bridge (docs/04 section 7; FR-1xx). First real public surface of @rulvar/bridge-ai-sdk.

    • bridgeAiSdk(model, options?) wraps any Vercel AI SDK LanguageModelV4 (@ai-sdk/provider ^4, catalog-pinned per docs/13 "Dependency baseline pins") as a rulvar ProviderAdapter for the long tail (Google, Bedrock, Vertex). A wrong specificationVersion fails at construction with a typed ConfigError, so a transitive provider-package major cannot mis-wire silently.
    • The full ChatEvent vocabulary streams through: text and reasoning deltas, tool-call start/delta/end with engine-minted canonical ids mapped bijectively onto the wrapped provider's wire ids, incremental-free usage normalized under the Usage invariant (inputTokens always covers cache reads and writes), typed finish outcomes (length to max-tokens, content-filter to a typed refusal carrying the raw stop reason), and exactly one terminal event per stream.
    • Retention rides finish.providerMetadata[<id>].retainedParts (docs/04 section 2.3): assembled reasoning parts with their provider signatures, custom blocks, generated files, and provider-executed tool exchanges round-trip to same-family models; response-side providerMetadata reinserts as prompt-side providerOptions.
    • Conservative caps for a surface that has no introspection (the openaiCompatible posture, except structuredOutput 'native' because V4 responseFormat json is the interface-native mechanism); options.caps overrides per model. Canonical effort maps one to one for low/medium/high/xhigh; max downmaps to xhigh and the downmap is recorded in providerMetadata. cacheHint is ignored silently per docs/04 section 1.7.
    • Errors: aiSdkErrorToWire projects thrown APICallErrors as typed WireErrors (429 as retryable rate-limit with retryAfterMs from the retry-after header; 5xx and status-less network failures retryable transport; other statuses terminal). Documented as the highest-churn package in the set; its provider-major bumps ride BREAKING releases, never minors.

Patch Changes

  • Updated dependencies [0e0b569]
  • Updated dependencies [b28b7a3]
  • Updated dependencies [b53a89e]
  • Updated dependencies [4454175]
  • Updated dependencies [6599ca8]
  • Updated dependencies [6649e5f]
  • Updated dependencies [fd2f83b]
  • Updated dependencies [01d6b2d]
  • Updated dependencies [9a20dbb]
  • Updated dependencies [0fbe7ea]
  • Updated dependencies [ebe0abc]
  • Updated dependencies [a3079d0]
  • Updated dependencies [596a39b]
  • Updated dependencies [464ab6e]

0.9.0

Patch Changes

  • Updated dependencies [84f94d4]
  • Updated dependencies [65c7b2c]
  • Updated dependencies [a2a3243]
  • Updated dependencies [ebc8101]

0.8.0

Patch Changes

  • Updated dependencies [85d55cf]
  • Updated dependencies [b88c9e3]
  • Updated dependencies [f3c4613]
  • Updated dependencies [a41c20f]
  • Updated dependencies [f4e70be]
  • Updated dependencies [75d1646]
  • Updated dependencies [0627413]
  • Updated dependencies [55c0f87]
  • Updated dependencies [fd33871]
  • Updated dependencies [e70e7f4]
  • Updated dependencies [bc9c903]

0.7.0

Patch Changes

  • Updated dependencies [fd1d06c]
  • Updated dependencies [6fcf296]
  • Updated dependencies [dcc97a9]
  • Updated dependencies [434dc83]
  • Updated dependencies [03173c1]
  • Updated dependencies [11c0afc]

0.6.0

Patch Changes

  • Updated dependencies [fa05007]
  • Updated dependencies [9234dc8]
  • Updated dependencies [644512c]
  • Updated dependencies [8a41656]
  • Updated dependencies [02f7f7a]

0.5.0

Patch Changes

  • Updated dependencies [ac274f4]
  • Updated dependencies [5735d92]
  • Updated dependencies [46ca98e]
  • Updated dependencies [8ae129e]
  • Updated dependencies [d1c4525]
  • Updated dependencies [b840aba]

0.4.0

Patch Changes

  • Updated dependencies [dfe03b5]
  • Updated dependencies [d2089a7]
  • Updated dependencies [3f60234]
  • Updated dependencies [f668890]
  • Updated dependencies [16d7aa6]
  • Updated dependencies [6513ce8]
  • Updated dependencies [7dad493]
  • Updated dependencies [2bbf180]

0.3.0

Patch Changes

  • Updated dependencies [43444f6]
  • Updated dependencies [279881b]
  • Updated dependencies [9fd0966]
  • Updated dependencies [24ebadf]
  • Updated dependencies [a1b35d3]
  • Updated dependencies [18a5821]

0.2.0

Patch Changes

  • Updated dependencies [c24228d]
  • Updated dependencies [c50871e]
  • Updated dependencies [1af8fb9]
  • Updated dependencies [1fe0249]
  • Updated dependencies [5c4fc32]

0.1.0

Minor Changes

  • f4e2be9: M0 repo bootstrap (v0.1.0, docs/10-implementation-plan.md section "M0"): monorepo scaffold on the committed toolchain (pnpm 11 workspaces with catalogs, TypeScript 6.0, tsdown, Vitest 4, ESLint 9 flat config, Turborepo 2, changesets fixed mode, npm trusted publishing), the docs/ canon as single source of truth, the L0 contracts skeleton in @rulvar/core, and the vendored dependencies (StandardSchemaV1/StandardJSONSchemaV1 types, the @cfworker/json-schema lineage validator subset, a first-party monotonic ULID). Placeholder scaffolds only: no public API ships in this release.

Patch Changes

@rulvar/cli

1.252.0

Minor Changes

  • 517ed00: The host surface hardens on two seams (RV4803, RV4805). The price table is now SNAPSHOTTED at createEngine: pricing resolution used to read the caller's live object on every debit, so a host mutating its table mid-run silently changed what wires cost after the strict gates had judged the original; the clone severs the alias (a rates update is a new engine with a bumped pricingVersion), and a table the structured clone cannot take refuses typed at construction. The HTTP shell's POST /runs body gains the regulated posture subset of RunOptions (budgetPolicy, maxInFlightExposureUsd, configFingerprint, scope, scopePolicy), so a remote caller can start a run under the immutable lifetime ceiling and the bounded execution scope; authentication, price tables, adapters, stores, and secrets stay with the host process by doctrine and never enter the body.

Patch Changes

  • Updated dependencies [3ccb6cf]
  • Updated dependencies [52d807f]
  • Updated dependencies [517ed00]
  • Updated dependencies [a7e589d]
  • Updated dependencies [76e95eb]

1.251.0

Minor Changes

  • 7c58fb2: The portable replay descriptor (RV4602, the seventh comparison experiment's P1.2 remainder). A programmatic run records its workflow NAME in the journal, but the workflow VALUE lives in no rulvar.config.mjs, so the seventh experiment's replay --assert-no-live refused from a clean checkout. rulvar resume and rulvar replay now accept --registry FILE, an ordinary module whose named exports (workflows, engineOptions, and the new configFingerprint) merge over the config for that one command, so a run travels as a three part descriptor: the journal, the args, and the registry module naming the workflow under its recorded name; a module exporting a single workflow value serves the recorded name too. The configFingerprint export closes the drift loop the engine already enforces: rulvar run records it at genesis (from the workflow module or the config), and a resume or replay that supplies one is verified against the genesis record strictly before ownership, meta writes, or any provider call, refusing typed on drift instead of replaying under changed policy; the CLI never supplied it before, so every fingerprinted run degraded to the one sided warning. In core, a refused resume now rejects its result alone: each on() subscription of the deferred resume facade used to derive its own unhandled rejection from the refusal, and the CLI progress renderer subscribes fourteen event types. Probes pin the genesis recording, the resume verification, the replay registry load, and the quiet refusal.

Patch Changes

  • b3e465a: Precise hash and counter namespaces (RV4604, the seventh comparison experiment's P2.2 remainder). Every hash on the lineage and provenance surfaces is one recipe, sha256 over the JCS canonical value, and the seventh experiment's provenance script had to rediscover that by trial because the bare names said nothing; the invoice's 16 logical calls beside 109 wire fetches were reconciled by hand for the same reason. The precise names now ride beside the bare ones, same hex, additive everywhere: judgedJcsSha256 on the claim meta, auditedJcsSha256 on the audit meta, and judgedDocumentJcsSha256 on the semantic terminal verdict, whose bare finalHash collides with draftToFinal.finalHash while meaning the judged document. On the counter side logicalRunTelemetry now carries adapterFetches, the sum of every provider call decision's absorbed wireRequests (absent reads one) beside the decision count logicalWireRequests, plus perSegment[].adapterFetches naming which segment actually paid for them (a pure replay segment reads 0); rulvar inspect prints both counters by name on the logical wires line. Probes pin the absorption sum, both meta twins, and the verdict's referent naming.
  • Updated dependencies [e7e829c]
  • Updated dependencies [5982be8]
  • Updated dependencies [7c58fb2]
  • Updated dependencies [b3e465a]
  • Updated dependencies [c4e5d6a]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [0ae8b85]
  • Updated dependencies [7932936]
  • Updated dependencies [7932936]
  • Updated dependencies [88da0ed]
  • Updated dependencies [06c0e85]

1.250.0

Minor Changes

  • 1b39e62: The effects family in the CLI and the effect lane guide (RV4506, plan 45): rulvar effects ls <runId> prints the fold report (the epoch and its restore posture, every machine's effective state, budgets consumed, standalone records; --json for the machine-readable form), rulvar effects show <runId> <intentSeq> prints one machine in full (budgets, attempts with outcomes, receipts with duplicate classification, journaled probes, incidents, dispositions, the closer), and rulvar effects sweep <runId> runs the quarantine-only reconciler sweep through the @rulvar/effects companion (loaded dynamically per command, the planner precedent), demanding the explicit --single-process acknowledgment over the non-leasable default store, exactly the writer's doctrine. The guide page docs/guide/effects.md documents the shipped protocol end to end, the production host dossier's effects rows flip from design to shipped, and rfcs/effects.md records the implemented status with its deviations.

Patch Changes

  • Updated dependencies [0e240b9]
  • Updated dependencies [6fe585e]
  • Updated dependencies [c5eb19c]
  • Updated dependencies [565c13b]
  • Updated dependencies [c6d197b]
  • Updated dependencies [c9d9729]
  • Updated dependencies [fed9db6]
  • Updated dependencies [df9ed76]
  • Updated dependencies [3020912]
  • Updated dependencies [d8d598d]

1.249.0

Minor Changes

  • e4428bd: RV4403: the terminal has five axes (terminal status, execution completion, child acceptance, deliverable acceptance, semantic verdict), and none substitutes for another on any surface, live or restarted. The seventh comparison experiment settled exhausted with both judge metas and the ten-unsupported count only inside error.data: the outcome's top level read nothing, the settle recorded nothing, a restarted production gate answered 'not-judged' about a failure whose own message counted the findings, and rulvar inspect printed acceptance: accepted (completion complete; gate on the status and completion PAIR) over a rejected deliverable. Now: every typed semantic failure stamps the one-word verdict beside its metas (folded by the same RV4209 function the acceptance path uses, without a waiver or draft-bridge input, because a failing run has no standing acceptance to license); the engine lifts the semantic facts (claimConsistencyMeta, new citationAuditMeta, semanticTerminalVerdict) on EVERY terminal path including typed failures without a completion literal, mirrors them onto the outcome, the run:end event and the terminal envelope, and records them in the journaled settle; lastRunSettle and the persisted terminal envelope read them back defensively (a foreign or partial shape reads NOT RECORDED, never a verdict), so live and restart agree field for field. The CLI production gate reads the outcome's typed verdict field first, so it refuses with the recorded 'findings' instead of a false 'not-judged'. rulvar inspect prints the axes side by side (axes: terminal exhausted | execution complete | children accepted | deliverable rejected | semantic findings) with the semantic counts and the citation audit numbers, and the child roster verdict is labeled children:, one axis of five; the bare acceptance: label and the "gate on the status and completion PAIR" advice are gone.

  • d6873c1: RV4404: budget honesty, three opt-in answers to the seventh comparison experiment's death. The intake gate had verified the acceptance tail against DECLARED estimates and the run passed fits: true honestly; the workers then overshot their declared estimate 2.8x, and the refusal came only where the armed round could not dispatch, after the composition and both judges were already paid.

    budget.acceptanceReserve: 'checkpoint' is 'require' plus a runtime re-check of the same arithmetic before each paid acceptance-tail dispatch (the first composition, each judge pass): every ceiling on the chain up to the run root judges its spend plus its dedicated tail reserves plus the worst case still ahead, and the run refuses typed BEFORE paying the stage, journaling an acceptance_checkpoint_refused decision naming the account, the stage, and every term. In the seventh trajectory the first checkpoint fires right after the workers, saving the composition and both judge passes. Dispatch-projection holds stay out of the arithmetic: they release on settle and the tail terms already price those futures.

    budget.estIsCeiling: true turns declared spawn estimates into the fan-out's own hard allowance ceiling: tool-spawned children share the orchestrator's child scope, so the enforced bound is the AGGREGATE of the admitted estimates (RunBudget.raiseChildAllowance widens it per admitted child), exactly the number the acceptance-tail arithmetic trusted; a fan-out that overshoots its declarations refuses at ITS ceiling instead of silently eating the tail. With both opt-ins, a preflight fits: true becomes a dispatch guarantee for the declared tail.

    The pair ceiling stops laundering itself as a document defect: a declared semanticAcceptance (claimCoverage 'full') derives coverage target 1 when none is set, so the pass runs coverage-first instead of the historical first-max selection, and a truncation under a DECLARED target grades 'coverage-capped' (a new ClaimCoverageGrade literal) instead of a silent 'partial'. The strict-final and waiver-forbid refusals then name the knob: the pair ceiling max, and how many citing sentences it left uncovered. The seventh run declared full coverage, folded its pairs truncated, and reported 23 uncovered citing sentences as if the text were the problem. --strict refuses 'coverage-capped' (a capped pass breaks the contract the declaration states; plain 'partial' deliberately stays exit 0), and the semantic terminal verdict folds it into the partial bucket.

  • 634f966: RV4409: the logical run's telemetry is native. The seventh comparison experiment measured its resumed run's active and calendar walls, the operator gap between segments, and the 109-wire logical count by external script over the raw journal, and reconciled "16 versus 109" by hand because the two counter families shared a vocabulary. logicalRunTelemetry now folds, from the stamps and decisions the journal already carries: activeMs (each segment's own append window, summed), calendarMs (first to last append), gapMs (their difference, the operator time), perSegment (status, entries, active wall, and replayed: true on a pure-replay segment, so a resumed run's walls read as the original segments' work instead of a 0.0 s rerun), and logicalWireRequests (provider-call decisions across the WHOLE journal, the invoice's cardinality). Absent stamps keep the time fields absent: not recorded, never zero. rulvar inspect prints the logical run block (both time conventions, per-segment walls, the replayed marker) and the wire count under its own name, with the label spelling out that a segment's adapter fetches are a different, smaller counter by design.

  • 052cc26: RV4410: opt-in coordination checkpoints. With coordinationCheckpoints: true, every settled await round appends a compact coordination_checkpoint decision (the round ordinal, the settled handles, the spend at the checkpoint), so a timeout or kill terminal shows how far coordination durably got, and a resumed run's journal visibly continues from round N+1 instead of an opaque prefix. The seventh comparison experiment's genesis segment died on a timeout mid-coordination and the post-mortem priced the re-coordination by hand; the checkpoint makes the durable progress a journal fact. An await round the kill interrupted journals NOTHING, honestly: coordination got no farther than the journal says. Opt-in because the decisions are journal bytes; without the flag every journal stays byte identical, and the replay machinery never re-pays journaled coordination either way. rulvar inspect prints the last checkpoint (round, settled children, spend) when one exists.

Patch Changes

  • Updated dependencies [8862133]
  • Updated dependencies [0d7a717]
  • Updated dependencies [e4428bd]
  • Updated dependencies [d6873c1]
  • Updated dependencies [4092e8d]
  • Updated dependencies [e086590]
  • Updated dependencies [1411938]
  • Updated dependencies [737d1ee]
  • Updated dependencies [634f966]
  • Updated dependencies [052cc26]
  • Updated dependencies [bbae134]

1.248.0

Patch Changes

  • Updated dependencies [8d0cd69]
  • Updated dependencies [81065e4]
  • Updated dependencies [8573f20]
  • Updated dependencies [95f6a5e]

1.247.0

Minor Changes

  • 4b7197a: The candidate chain reads by hash, and absent bytes say why (RV4207, the sixth comparison experiment). finishValidation.candidatePersistence: 'transcript' | 'hash-only' supersedes retainRejectedCandidates (declaring both refuses typed): under a declared policy every finish-validation decision carries the candidate identity, the ACCEPTED verdict included (the hash names the resolved document, deterministic patch or sectional splice applied, on the same recipe the semantic judges bind), 'transcript' retains rejected bytes exactly as the boolean did, and 'hash-only' retains none on purpose, stamping bytesUnavailableReason: 'hash-only-persistence' on the decision, the fold, and the terminal row (a declared retention the store refused stamps 'store-write-failed'), so an auditor finding no blob reads a policy or a fault by name. The hash recipe is exported and documented: candidateHashOf (sha256 over the JCS canonical value; a string document hashes as its JSON encoding, and a file export with a trailing newline changes the file's sha while this hash holds) with verifyCandidateBytes(bytes, hash) as the audit predicate. rulvar inspect <runId> --candidates renders the chain (verdict, hash, chars, window, wires, money, byte address or the named reason) and --candidate-bytes <hash> recovers a retained document to stdout, verified against the journaled hash, in one command; the experiment's auditor recovered the rejected 37,645 character composition by digging messages[3] out of a binary transcript blob and re-deriving the recipe from source. Undeclared configs keep every byte: identity on non-accepted verdicts only, exactly RV2507.
  • 16ff6b9: One word answers the production question, on every surface (RV4209, the sixth comparison experiment). The acceptance envelope now carries semanticTerminalVerdict whenever claim or citation machinery is configured: 'clean' | 'findings' | 'partial' | 'vacuous' | 'waived' | 'not-judged' with the final hash, the counts (contradictions, unsupported and partial citations, repair rounds), the standing waiver, and the judge-failure codes, folded ONCE at the orchestrator settle (semanticTerminalVerdictOf, exported) with fail-closed precedence: a failed or declined judge, and a draft-stage grade the synthesis rewrote (RV3207), read not-judged; findings outrank the waiver; the waiver is never clean. The verdict is lifted onto the outcome, mirrored onto the terminal envelope (and through it the run:end event and the HTTP response), so every consumer reads the SAME derivation instead of re-deriving it from four fields. productionAcceptable is the exported fail-closed gate (only 'clean' passes; absence reads not-judged), and rulvar run --acceptance-policy production (also on resume) applies it after --strict's mechanical checks, refusing suspended runs as unsettled, with ONE stable JSON reason line on stderr per refusal. --strict itself stays byte identical, documented exits included: the experiment's run settled ok under a standing waiver with three unsupported citations, and the pipeline reading strict's exit shipped it.

Patch Changes

  • Updated dependencies [1933ecc]
  • Updated dependencies [db0a5f0]
  • Updated dependencies [b698726]
  • Updated dependencies [48348d2]
  • Updated dependencies [4cfa1cc]
  • Updated dependencies [4b7197a]
  • Updated dependencies [5ebc842]
  • Updated dependencies [16ff6b9]
  • Updated dependencies [0c9941d]

1.246.0

Patch Changes

  • Updated dependencies [d165b0c]
  • Updated dependencies [d59f4a0]
  • Updated dependencies [46907ac]
  • Updated dependencies [9929ad3]
  • Updated dependencies [1790a6a]

1.245.0

Minor Changes

  • dee6db4: The workflow answers for its own repairs (RV4002, the fifth comparison experiment). The run paid for exactly one repair (a coordination draft rejected by three validators, healed by a sectional resubmission, one more wire at $0.186) and every terminal aggregate answered truthfully for its own stage while no surface answered for the workflow: the independent judge rebuilt the count from the raw transcript and the repair wire's money drowned in 'coordination'. The exported repairLedgerFromJournal folds the workflow-wide ledger ({ draft, composition, semantic, total } plus one row per granted repair with its stage, verdict seq, failed validators, spliced sections, and the repair wire's ref and price when the billing lane covered it); the acceptance envelope carries repairs computed by the same fold over the run's own snapshot, so live and post-hoc agree by construction. The draft gate journals its voice (orchestrator_draft_gate on rejection and on the healing sectional acceptance), finish-validation decisions carry their stage and spliced markers, and the granted repair turn's own wire is stamped phase: 'repair' (ProviderCallRecord.phase), which all three byPhase folds split out of the hosting dispatch's bucket. rulvar cost-audit prints the ledger when the journal proves one, byte parity otherwise; pre-RV4002 journals fold with unstagedVerdicts named, a floor, never a guess; clean runs keep every byte (all 61 frozen fixtures verify unchanged). Kit: coordination-draft-repair pins the experiment's exact shape ({ draft: 1, composition: 0, semantic: 0, total: 1 }, the gate decisions, the stamped wire) and sectional-repair-round pins the semantic round's ledger; four mutation probes pin the wire stamp, the gate's journal voice, the round count, and the CLI line. journal-shape-revision: the wire-level phase stamp is an additive journal evolution, and the frozen cassettes whose flows contain a refused finish exchange are re-recorded under it.
  • 19bcea0: The pre-wire provider intent (RV4006, the fifth comparison experiment's P0.5). Receipts journal after a wire settles, so the wire most exposed at a crash is exactly the one being paid for: between dispatch and receipt, a death leaves money the journal never heard about. defaults.billingReceipts: 'intent' journals a provider-intent decision before every dispatched wire attempt (awaited, the executor ledger's intent-before-effect rule: a failed intent append refuses the dispatch), keyed by dispatch seq, ordinal, and attempt, carrying the serving model, role, and a sha256 request fingerprint; receipts stay awaited as under 'awaited'. An intent with neither a receipt row nor a settled terminal covering it is a wire with UNKNOWN outcome: the exported openWireIntentsOf fold names them, the invoice carries the openIntents lane (no invented dollars), rulvar cost-audit prints it, and a resume that finds one refuses the blind retry typed until ResumeOptions.acknowledgeOpenWireIntents: true is passed, which the new segment journals as open_wire_intents_acknowledged. Dispatch stays at-least-once with attempt binding; the default 'async' and 'awaited' postures keep every byte. Kit: wire-intent-unknown-outcome drives the reconstructed crash window through both resume arms; probes pin the quota-arm intent, the resume gate, and the receipt closure.

Patch Changes

  • Updated dependencies [b4d47a8]
  • Updated dependencies [dee6db4]
  • Updated dependencies [b85c113]
  • Updated dependencies [bc556e7]
  • Updated dependencies [9f11d29]
  • Updated dependencies [19bcea0]
  • Updated dependencies [60b461c]
  • Updated dependencies [61e3a1a]
  • Updated dependencies [a156b81]
  • Updated dependencies [0bd7045]

1.244.0

Minor Changes

  • f56721d: InvoiceRow.agentType? and InvoiceRow.label? (RV3906, the fourth comparison experiment): in dynamic runs the scope grammar nests every orchestrator spawn under one agent:<seq> bucket, so byScope legitimately reads two buckets and per-child money used to require a join through the journal. Every row of an attributed terminal (record rows, unattributed slice rows, and remainder rows alike) now carries the spawn's agentType and the dispatch label from the terminal's cost attribution; the empty agentType folds as absent (the root's honest non-type), and rows of journals recorded before attribution shipped stay byte for byte. rulvar cost-audit prints the same cut as a by agentType: line and carries it as invoice.byAgentType in the JSON form, both absent on pre-attribution journals. Cardinality pins unchanged; one mutation probe pins the threading.

Patch Changes

  • Updated dependencies [38d839a]
  • Updated dependencies [ce13b0f]
  • Updated dependencies [4fa23e3]
  • Updated dependencies [6841c69]
  • Updated dependencies [f56721d]
  • Updated dependencies [c894a43]
  • Updated dependencies [f6944a3]
  • Updated dependencies [23fd0e0]

1.243.0

Patch Changes

  • Updated dependencies [746d1f4]
  • Updated dependencies [009b29c]
  • Updated dependencies [1674cbe]
  • Updated dependencies [bd096bc]

1.242.0

Patch Changes

  • Updated dependencies [6e3438e]
  • Updated dependencies [ba5cf67]
  • Updated dependencies [c2d1531]

1.241.0

Patch Changes

  • Updated dependencies [dbcdd24]
  • Updated dependencies [7ae7243]
  • Updated dependencies [4f832c4]
  • Updated dependencies [7452d3d]
  • Updated dependencies [a4e22bf]
  • Updated dependencies [82df4af]

1.240.0

Minor Changes

  • b2cf668: cost-audit surfaces the invoice's orphaned receipt lane on every output form (RV3501). When a journal carries the RV3405 crash shape (a receipt row the settled terminal's record set does not cover), the single run text prints the lane totals plus one line per receipt, both JSON shapes carry the lane verbatim under invoice, and the catalog sweep appends an orphaned suffix to the carrying run's row and a carrying count to its header. The lane never moves the verdict or the exit code: an orphaned receipt is the honest double payment window of a resume, not a divergence, and before this surface such a journal passed all six checks while the money stayed invisible in every printed figure. Journals without the lane render byte for byte as before.

Patch Changes

1.239.0

Patch Changes

  • Updated dependencies [74ce99a]
  • Updated dependencies [ccd0665]
  • Updated dependencies [0c5ce21]
  • Updated dependencies [0616934]

1.238.0

Patch Changes

  • Updated dependencies [cf00947]
  • Updated dependencies [c7b9382]
  • Updated dependencies [88aea96]
  • Updated dependencies [6da8d05]
  • Updated dependencies [eae5c4c]

1.237.0

Patch Changes

  • Updated dependencies [9d6a279]
  • Updated dependencies [49a98f6]
  • Updated dependencies [a734ca0]
  • Updated dependencies [deb406f]

1.236.0

Minor Changes

  • 4701bfe: --strict binds the semantic verdict to the shipped document (RV3207). A claim-coverage grade rendered over judgedStage: 'draft' while the envelope's draftToFinal.rewritten reports the synthesis replaced that draft now exits nonzero, naming the remedy (claimConsistency.stage: 'final' or 'both'), because nothing semantically judged the artifact the run settled on: the 2026-08-11 experiment run shipped a repaired composition under a draft-stage partial grade and read green. An unchanged draft, a final-stage verdict, an absent claim meta, or an absent bridge all keep their existing exits byte for byte.

Patch Changes

1.235.0

Patch Changes

  • Updated dependencies [ba4e10d]
  • Updated dependencies [172402b]
  • Updated dependencies [2ecd787]
  • Updated dependencies [e20a5e9]
  • Updated dependencies [98c8691]
  • Updated dependencies [c70def0]

1.234.0

Minor Changes

  • 04c86d6: rulvar inspect prints the observed tool-budget calibration beside the child roster (RV3103): the RV3003 fold in operator output. The aggregate line (observed tool calls per recorded evidence entry: with the rate, the executed-call and entry sums, and the paired-dispatch count) exists only when at least one terminal carries both the RV806 evidence verdict and the RV3002 executed-call counter; unpaired sides are named instead of zeroed (declared contracts with no journaled counter, the pre-RV3002 journal shape, and counters with no declared contract); a journal carrying neither side prints nothing at all, so absence stays NOT RECORDED in operator output too.

Patch Changes

1.233.0

Patch Changes

  • Updated dependencies [48b5200]
  • Updated dependencies [73bc32b]
  • Updated dependencies [e63b743]
  • Updated dependencies [ef45da7]

1.232.0

Patch Changes

  • Updated dependencies [1440410]
  • Updated dependencies [6e467f4]
  • Updated dependencies [0b14293]
  • Updated dependencies [e3bcab2]
  • Updated dependencies [b55a0f7]

1.231.0

Minor Changes

  • ff9b8c2: The offline child roster names the children the run ABANDONED (RV2804).

    childRostersFromJournal presented a child on a discarded branch exactly like a child whose work the run kept, so a post-mortem reading "four children settled ok" was counting branches the orchestration had thrown away. The money layer has refused that conflation since RV1904: grossUsd keeps abandoned spend because the provider billed it, totalUsd does not because the run kept none of it. The roster now says the same thing.

    JournaledChild.abandoned is present and true exactly when the first-wins abandon projection covers that child's dispatch, subtree coverage included, and absent otherwise, never false (RV1209). It needs nothing that was not already written down: the fold reads the same projection the replayer disposes by, over the same journal, and a child's handle is the very seq an abandon entry targets, so journals from every prior version answer.

    rulvar inspect prints the discarded children under the roster it already prints, named by their handles.

Patch Changes

  • Updated dependencies [4eb4b56]
  • Updated dependencies [bc8f09e]
  • Updated dependencies [ff9b8c2]

1.230.0

Minor Changes

  • abe9c09: The human report says what the terminal CLAIMS, not only what it transported (RV2703).

    rulvar run printed the transport status, the value, the error, the drops, the suspensions and the money, and not one semantic field. So a run accepted with degradation, a run whose declared finish contract refused every candidate it was handed, and a clean run all printed status: ok with nothing between them. --strict has read those fields since RV2604, but strict is the machine gate: a person who does not pass the flag was left with exactly the blindness the last two releases went into curing.

    The report now names completion: with the degraded reasons behind it, deliverable: (accepted or REFUSED by the declared contract, and whether the terminal carries an artifact at all), the count of rejected finish candidates with the distinct documents among them, and children at failure: for a run that died before any policy judged its roster (RV2602), which is the only account of work that was already paid for.

    rulvar inspect gains the offline half: the completion its own lastRunSettle read has been available since the persisted-terminal tail, while inspect printed the acceptance DECISION only, which exists only where a verdict was rendered. A run that died before acceptance, or one resumed past it, showed a reader nothing.

    Absence prints nothing, everywhere (RV1209): a host that declares no contract is its own judge, and a workflow that makes no completion claim is not an incomplete run. A run with none of these fields prints exactly what it printed before.

  • 57bfb38: The child roster of a run that died before acceptance is readable OFFLINE (RV2702).

    childrenAtFailure (RV2602) answers "what had the children produced" for a consumer watching the run, and it dies with the process that held it. The settle persists the completion lift and nothing else, so a post-mortem over a journal, which is all a paid run leaves behind, had no way to ask the question at all: not for a run that crossed its ceiling mid-roster, and not for any run in an archive written before the field existed.

    childRostersFromJournal(entries) is the fold, and it reads what resume reads. A spawn-admission decision names every child the controller judged, with its ordinal, its profile, its verdict and the scope its dispatch pins to; the dispatch and terminal agent entries under that scope are the child itself, and the RV806 evidence verdict rides the terminal. Nothing new is written, nothing is re-derived and no validator runs again, so a journal from any prior version reads exactly as well as today's.

    rulvar inspect prints it: how many children were admitted, how many settled and with what statuses, how many were refused admission, and the ones that settled ok below a declared evidence floor, named by the dispatch seq the orchestrator's own turns used as their handle.

    Two things it does not claim. It is not the live roster: this reading happens after the RV1903 exit barrier settled the stragglers, so a child the live field called unsettled usually has a terminal here, and an absent status means the journal truly ends mid-flight rather than a child that failed. And it counts CHILDREN: the coordination loop, the synthesis and the judge dispatch through the same ctx.agent, and only a child carries the spawn admission that pins it to the child scope.

Patch Changes

1.229.0

Minor Changes

  • 7ce0be2: --strict reads the deliverable verdict (RV2604). The flag has always refused a partial acceptance and, since RV1702, a coverage grade that verified nothing. It never asked the one question RV2506 shipped a field for: did the declared finish contract accept the artifact this run settled on. Completion answers for the CHILDREN, and the twenty-fifth comparison run is the row that gap leaves open, with four accepted children, three syntheses the contract refused, a run that settled on unvalidated output, and a scoring harness reading status: 'ok'.

    deliverableAccepted: false now exits nonzero even under completion: 'complete', naming the contract and, when the terminal carries no artifact at all, saying so in the same line. The check precedes every coverage grade deliberately: a semantic grade over an artifact the contract rejected answers a question nobody should still be asking, and the refusal that names the contract is what a reader needs.

    An ABSENT verdict is left alone. The check is === false, not !== true, because absence means no finishValidation was declared, nothing judged anything, and a host that declares no contract is its own judge. That is the same line the normative consumer predicate draws in the observability guide.

  • edce170: rulvar inspect reports the logical run and what the contract refused (RV2605). Two surfaces shipped in v1.228.0 had no consumer in the tool people actually read a run with: inspect printed entries: N, which over a resumed run is one undifferentiated heap with no boundaries in it, and said nothing at all about finish candidates the declared contract rejected.

    segments: is logicalRunTelemetry (RV2510) printed: how many segments ran, how each settled, how many entries each appended, and the count of entries that continued PAST the last settle (RV1407) when there are any, because the last settled status is then not the run's last word. rejected finish candidates: lists the RV2507 rows with verdict, size, hash prefix, failing validators, and the blob ref when the bytes were retained, and counts DISTINCT documents beside the row count, so three rows sharing one hash reads as the model serving one text three times rather than as three genuine attempts.

    lastRunSettle gains rejectedFinishCandidates. The settle already persists the whole completion lift, so this is a read of what is recorded, not a re-fold and not a validator re-run, and every row is parsed defensively: any malformed row drops the WHOLE list, the same posture the live lift takes, because a partial history read as complete under-reports exactly the runs that misbehaved most, and offline is where nobody can check. A journal that records nothing of the kind reads as NOT RECORDED and both lines stay absent.

Patch Changes

  • Updated dependencies [3370342]
  • Updated dependencies [2fb6656]
  • Updated dependencies [edce170]

1.228.0

Patch Changes

  • Updated dependencies [4034fac]
  • Updated dependencies [a54b085]
  • Updated dependencies [9d0a9be]
  • Updated dependencies [be9ef28]

1.227.0

Minor Changes

  • 41f93a9: The claim-coverage grade sees a declined judge and a zero denominator (RV2508). claimCoverageOf never read judgeDeclined, the RV2106 degradation where the claim judge is refused ADMISSION and never dispatched, so a pass that judged nothing was graded by the counts of a pass that did not happen; over a draft carrying no citing sentence it graded 'full', the strongest word in the vocabulary. The vacuous 'full' at a zero denominator was the same failure at its extreme: RV1702 exists to stop a consumer inferring semantic health from emptiness, and an empty set graded stronger than a bounded subset.

    ClaimCoverageGrade gains two words. 'judge-declined' ranks with 'judge-failed' and above everything the counts could say, below it only because a failure at least had an invocation to fail and the two causes are worth telling apart. 'vacuous' sits below 'partial': no subset was chosen because there was no set. ClaimCoverageInput gains judgeDeclined?: true; the orchestrator already spreads that flag into the meta it grades, so no call site changes and every existing meta grades the same unless it carried one of the two states. The CLI's --strict exits nonzero on 'judge-declined' exactly as on 'judge-failed' (nothing was judged either way) and prints 'vacuous' to stderr while keeping the exit, because citing nothing breaks no contract the pass declares.

    Consumers with an exhaustive switch over ClaimCoverageGrade will see a type error until they handle the two new members. That is the intended shape of the change: both states existed before and were silently folded into words that did not describe them.

Patch Changes

  • Updated dependencies [f262e9f]
  • Updated dependencies [f191ff7]
  • Updated dependencies [fbbfbe8]
  • Updated dependencies [263b5e8]
  • Updated dependencies [db4d56d]
  • Updated dependencies [41f93a9]
  • Updated dependencies [98c8ca9]

1.226.0

Patch Changes

1.225.0

Minor Changes

  • a2770e6: The cost-audit catalog sweep (RV2209). The parity sessions verified the one-denominator contract seven journals at a time, one rulvar cost-audit <runId> invocation each; a catalog posture check should cost one command. rulvar cost-audit --all --store <dir> runs the same six checks (roster closed, settle recorded, settle is the billing boundary, fold matches invoice, wires match, incremental rows match) over EVERY run the store lists, in run-id order whatever the store returns, one summary row each naming the verdict, the passed-of-total checks with the failing names, the gross, and the wire count, and exits 1 when ANY run diverges; --json carries the same per-run shapes under runs with the sweep verdict on top. The single-run form is unchanged byte for byte and stays the deep view; a runId beside --all (or neither) refuses typed. Under the hood the grammar grows optional positionals (rendered bracketed on every usage surface, arity admits required through required plus optional), and the six checks are extracted into one audit function both forms share.

Patch Changes

1.224.0

Patch Changes

1.223.0

Patch Changes

1.222.0

Patch Changes

1.221.0

Patch Changes

1.220.0

Patch Changes

1.219.0

Patch Changes

1.218.0

Patch Changes

1.217.0

Patch Changes

1.216.0

Patch Changes

1.215.0

Patch Changes

1.214.0

Patch Changes

1.213.0

Patch Changes

1.212.0

Patch Changes

1.211.0

Patch Changes

1.210.0

Minor Changes

  • c871ddc: Incremental billing journaling (RV2008). ProviderCallRecords rode ONLY the terminal agent entry, so when the third parity rerun's process died with the root still running, ~$0.99 of its dispatches existed nowhere durable: the live ledger read $4.467 while the journal folded $3.478. Every record now journals the moment its wire call settles, as a provider-call decision row keyed by the dispatch seq and the record ordinal in the invocation's own scope; the terminal entry still carries the canonical set, replayed segments append no duplicates, and the crash window shrinks from the invocation's whole history to the one in-flight turn. invoiceFromJournal gains the additive unsettled lane: dispatches of agents still running at the journal's edge, priced from the incremental rows and kept OUTSIDE the settled totals (run_settle stays the billing boundary). rulvar cost-audit grows a sixth check, incremental-rows-match: every settled agent's terminal dispatch set must equal its incremental rows, count and per-ordinal usage alike; agents with no rows (pre-RV2008 journals, replayed invocations) pass vacuously. The frozen cassette catalog is re-recorded for the additive rows (journal-shape-revision, policy not identity: existing entries byte-identical, no hashVersion change).

Patch Changes

1.209.0

Patch Changes

1.208.0

Patch Changes

1.207.0

Patch Changes

1.206.0

Patch Changes

1.205.0

Patch Changes

1.204.0

Patch Changes

1.203.0

Patch Changes

1.202.0

Minor Changes

  • 08c1247: rulvar cost-audit <runId> verifies the one-denominator contract on a concrete stored run (RV1910). The four-role benchmark's recovery run produced four mutually inconsistent cost views, and the judge reconciled them by hand; the lifecycle now admits one, and the audit checks it instead of trusting the doctrine: the roster is closed (every agent entry has a terminal), run_settle is recorded and is the billing boundary (no agent entry follows it), and the settled fold, the invoice totals and the wire cardinality agree. Text and --json forms, exit 1 with the failing checks named when any diverge, which is exactly what a pre-RV1904 journal, the benchmark's own, reports.

Patch Changes

1.201.0

Patch Changes

1.200.0

Patch Changes

1.199.0

Patch Changes

1.198.0

Patch Changes

1.197.0

Patch Changes

1.196.0

Patch Changes

1.195.0

Patch Changes

1.194.0

Patch Changes

1.193.0

Patch Changes

1.192.0

Patch Changes

1.191.0

Minor Changes

  • 745387c: Enforceable coverage floors and two new corpus classes (RV1809). The claim pass graded itself honestly (RV1702) but nothing could enforce a floor: claimConsistency.minimumCoverageRatio and runFactCoverageRatio (each in (0, 1]) now declare the minimums, onLowCoverage: 'report' (default) stamps the machine-readable lowCoverage block on the meta with each ratio beside its floor, 'fail' fails the run typed BEFORE the judge dispatch exactly like onUncoveredCritical, the meta additionally carries runFactCandidates (the uncapped matched count, so both ratios are computable from the meta alone, live or persisted), and --strict exits nonzero on a stamped block with the ratios printed. The adversarial corpus grows two classes from the nineteenth benchmark: modality-overclaim (a mitigation stated as an unconditional guarantee: the attestation "stops any tool drift" beside the pool reading naming the contract-hash boundary) and scope-ambiguity (child-only totals printed as whole-workflow figures), both forming pairs through the same pure folds.

Patch Changes

1.190.0

Patch Changes

1.189.0

Patch Changes

1.188.0

Patch Changes

1.187.0

Patch Changes

1.186.0

Patch Changes

1.185.0

Patch Changes

1.184.0

Patch Changes

1.183.0

Patch Changes

1.182.0

Patch Changes

1.181.0

Patch Changes

1.180.0

Patch Changes

1.179.0

Minor Changes

  • 1a5a85a: The claim-coverage grade rides the acceptance envelope, and strict reads it (RV1702). The eighteenth comparison benchmark's run reported completion: 'complete' with contradictions: [] while the judge had seen 40 of 144 citing sentences, and three material falsehoods rode that gap; the counts that told the truth (RV1603) still had to be interpreted. The claim-consistency meta now carries coverage, one closed vocabulary a consumer reads instead of inferring semantic health from an empty findings array: 'full' (every citing sentence had a judged pair, nothing cut, no declared critical anchor missed, the judge settled ok; zero citing sentences grade full vacuously), 'partial' (a bound cut the fold or citing sentences went unjudged), 'critical-uncovered' (declared critical anchors got no judged pair), 'judge-failed' (nothing was judged at all), precedence strongest last. The pure claimCoverageOf helper derives the identical grade from any persisted meta, including metas written before the field shipped, so old envelopes grade without re-running. The CLI's --strict now reads the grade beside the completion contract: 'judge-failed' and 'critical-uncovered' exit nonzero, both states that previously slipped through strict as green, while 'partial' prints its counts to stderr and keeps the exit, because the bounded pass is the documented default and declaring critical anchors is the opt-in that makes the subset enforceable.

    Journal: the orchestrate acceptance envelope's claimConsistencyMeta gains the required coverage field on newly settled runs; persisted metas from older engines stay readable and grade through claimCoverageOf.

Patch Changes

1.178.0

Patch Changes

1.177.0

Patch Changes

1.176.0

Patch Changes

1.175.0

Patch Changes

1.174.0

Patch Changes

1.173.0

Patch Changes

1.172.0

Patch Changes

1.171.0

Patch Changes

1.170.0

Patch Changes

1.169.0

Patch Changes

1.168.0

Patch Changes

1.167.0

Minor Changes

  • 59765b1: Answer the SSE capability machine-readably on every run status body (the P1 tail).

    Events are process-local telemetry: GET /runs/:id/events streams a run's events only from the process that holds it live, and a run served from the store answers an immediately closing comment stream. That association lived in documentation prose, so a client discovered it by connecting. Every GET /runs/:id body now carries capabilities: { events: boolean } beside live: true exactly when this process holds the run and the events endpoint would stream, false on the persisted path. The cli guide's endpoint notes are updated, including the stale claim that a rebuilt envelope never carries completion (recoverable since the settle records the semantic lift).

Patch Changes

1.166.0

Patch Changes

1.165.0

Patch Changes

1.164.0

Patch Changes

1.163.0

Minor Changes

  • e8d9ada: Report the import bundle's reference closure, serve verify-only journal reads, and close the documentation gaps the benchmark named (RV1511, RV1512, RV1513). The sixth and final PR of the eighteenth plan.

    The import closure report (RV1511). The intake validated shapes, namespaces, and the runId, but nothing held the ENTRIES' own references against the blobs the bundle carries: a torn bundle imported whole and the missing transcript surfaced only when something later read it. importRun now returns { unresolvedRefs }, every transcript, checkpoint, artifact, and workflow-source ref the entries (and meta) name that no bundle blob resolves; the default stays permissive (retention and checkpoint pruning legitimately drop blobs their entries still name) and the report makes the gap visible, while requireClosure: true refuses typed BEFORE any write. A duplicate blob ref refuses always: last-write-wins over transcript bytes is a torn or edited bundle, never a valid export.

    The verify-only load (RV1512). The A1 salvage model repairs a torn trailing line ON LOAD, which is right for an owner about to append and wrong for an auditor: a verification read that rewrites the artifact it verifies destroys the evidence of the tear. JsonlFileStore({ repairOnLoad: false }) serves the salvageable records without touching the file, and rulvar runs audit --no-load-repair opens the default store that way (contradicting --repair is refused typed).

    The documentation debts (RV1513). The README package count now matches its own table (seventeen names, the unscoped pointer included); @rulvar/executor ships a README and LICENSE like every sibling; the package reference names the eval framework's real dependencies; and the isolated-executor guide gains "What the ledger is NOT", the explicit denial list (not an outbox, not authorization, not exactly-once, not always on) for exactly the facts the seventeenth comparison run's dossier inverted while citing the sources that state them.

Patch Changes

1.162.0

Patch Changes

1.161.0

Patch Changes

1.160.0

Patch Changes

1.159.0

Patch Changes

1.158.0

Patch Changes

1.157.0

Patch Changes

1.156.0

Patch Changes

1.155.0

Minor Changes

  • 49b08a7: Make the persisted terminal tail-aware and give offline authorities the engine's own resolution validator (RV1407, RV1408). The persisted terminal (RV1209) served the journaled settle even when the journal had CONTINUED past it, so a restarted reader could hold yesterday's envelope over a run that a detached resolution had already destined to resume, or that a successor segment was actively working, while auditRun derived a non-terminal status from exactly that evidence. persistedTerminalEnvelope now refuses not-terminal whenever entries follow the last settle, with a message naming the continuation (count and settle seq), so the persisted surface and the audit read one journal one way; the conformance table pins the new refusal (settled-then-continued) beside the five terminal paths. And the CLI server's offline resolution used a lookalike validator that demanded the plain { decision } from EVERY kind-'approval' suspension: a legitimate EscalationDecision for a flavor B escalation was refused, and a wrong-shaped plain approval payload was waved into the journal. The new export validateDetachedResolution is the engine's own detached validation (the RV1203 flavor classifier, both payload arms, the pinned schema) as one function; the engine's detached path and the CLI offline path now call the same bytes, so an escalation resolves offline with its OWN payload exactly as detached-live, and an invalid one is refused typed before anything is journaled.

Patch Changes

1.154.0

Patch Changes

1.153.0

Patch Changes

1.152.0

Patch Changes

1.151.0

Patch Changes

1.150.0

Patch Changes

1.149.0

Patch Changes

1.148.0

Minor Changes

  • c85dac9: The terminal envelope survives the process that produced it, and the invoice states how many provider requests its rows represent (RV1209, RV1210).

    A run this server never held used to answer GET /runs/:id with a bare status projection while a live consumer read the whole TerminalEnvelope, so the durability story stopped one surface short of the one a host reads after a restart. The non-live response now carries envelope too, rebuilt from the journal through the same producer and marked provenance: 'journal': the verdict comes from the journaled run settle (the authority, not the meta projection), the money from the same composed settle-pin fold GET /runs/:id/cost runs, and the usage and agentsSpawned from the same ledger fold the resume budget seed uses. Two fields are deliberately absent on a rebuilt envelope and the marker is what makes their absence honest: completion (the workflow's semantic claim rides its result value, and only that value's digest is journaled) and error (the run's terminal wire error is never journaled as the run's own), so absence there means NOT RECORDED, never "the workflow claimed nothing" or "the run did not fail". A live envelope carries no provenance at all and keeps its original byte contract. Where nothing durable records a terminal, the body carries a typed terminalUnavailable: { reason, message } (unsettled, not-terminal, or unknown-workflow) instead of an envelope; it is its own field, never error, because error on that body means the run failed. persistedTerminalEnvelope is exported, and the terminal-envelope conformance table now drives every row through a restarted server as its final surface.

    The invoice declares the dispatch-versus-wire cardinality (cardinality: { dispatchRows, wireRequests, multiWireRows, wireIdsMissing }). One row is one logical dispatch, and a dispatch that absorbed provider-side continuations is billed as several HTTP requests, so a per-request statement has more lines than the export has rows by construction: reconcile a statement line count against wireRequests, never rows.length. The per-row wireRequests behind it comes from the count the adapter reported rather than the length of wireResponseIds, because a provider that leaves an absorbed segment unnamed still billed it, and counting ids alone made the invoice contradict the quota window that settles on the same count. Single-wire dispatches carry neither field and stay byte-identical.

    Two limiter fixes ride with it. An abort landing inside an awaited quota reservation now stops the wire: a limiter that queues can hold reserve past the dispatch's own abort check, and the engine rechecks the host and budget signals when the reservation resolves, releasing the granted admission rather than reconciling it, because a settlement only ever adds while that call provably never happened. And the unused-continuation release is fail closed on the wire count: only a finish that names its wire set proves which pre-wire grants went unused, so a finish carrying no count releases nothing, instead of reading the absence as one flown wire and handing a hook-granting adapter back exactly the capacity it had consumed.

Patch Changes

1.147.0

Patch Changes

1.146.0

Patch Changes

1.145.0

Patch Changes

1.144.0

Patch Changes

1.143.0

Patch Changes

1.142.0

Minor Changes

  • 473981a: The terminal envelope conformance table (RV1106): every terminal path (ok, error, exhausted, cancelled, superseded) drives the real engine, and the envelope is checked fact for fact across the resolved outcome, the run:end event, the HTTP run status body, the SSE replay, and the OTel run span, in one truth table with the surface honesty rules pinned. The red-first fix the table found: toOtel now completes its export over every terminal path, the rejecting ones included; a rejecting result never fails an export the stream already completed, it only marks a leftover span with the refusal instead of green.

Patch Changes

1.141.0

Minor Changes

  • 4f12a62: The unified terminal envelope (RV1105, the P1-5 arc): every terminal fact of a run travels in ONE exported shape, TerminalEnvelope (run identity, status, the typed error, the completion claim, settled + settledReason, totalUsd/grossUsd with the detached per-model split, the usage aggregate, usageApprox normalized to a boolean, and agentsSpawned), assembled once at the settlement chokepoint by the exported terminalEnvelopeOf after the settlement verdict is known. Every surface carries that object: the resolved outcome (outcome.envelope, always settled: true, because an unsettled terminal rejects typed instead of resolving), the run:end event (event.envelope, where the settled: false envelopes live with the superseded reason inside), the server's GET /runs/:id response, and the OTel exporter (rulvar.run.total_usd, rulvar.run.agents_spawned beside the existing settled attributes; a persisted stream from an older engine still closes its span). Nothing pre-existing was renamed or removed: the envelope is an assembly over fields that all remain.

Patch Changes

1.140.0

Patch Changes

1.139.0

Patch Changes

1.138.0

Patch Changes

1.137.0

Patch Changes

1.136.0

Minor Changes

  • aa6ca71: A superseded segment refuses green everywhere: typed SupersededError, the distinct settledReason on run:end, and exactly one authoritative successor (RV1009, PR V of the fourteenth plan)

    The fencing design swallowed a superseded segment's LeaseHeldError on both settlement writes, so a stale segment whose settle bounced off the successor's fence resolved ok with an unmarked run:end: a green terminal that no durable store wrote, exactly the split view the RV907 doctrine forbids.

    • The stale segment now rejects handle.result with the typed SupersededError (code superseded, not retryable, data { runId, runStatus }, cause the fencing rejection): the successor owns settlement, and the authoritative outcome is its settle or the store's run meta, never the stale computation. The meta write is skipped instead of re-proving the fence.
    • run:end refuses green with settled: false and the distinct settledReason: 'superseded' (an l0-compatible extension), so an event-only consumer can tell a superseded segment from a settlement write failure; the settlement-failure path and every ordinary terminal keep their exact bytes.
    • A meta-only lease bounce over an already durable settle stays swallowed: the journal records the outcome, and only the projection belongs to the current holder (the takeover no-op contract is unchanged).
    • The CLI progress line renders settled=false (superseded; the successor owns settlement) instead of the resume hint, and the OTel exporter stamps rulvar.run.settled_reason beside the refused span status.
    • runFaultInjection (@rulvar/evals) grows the nineteenth scenario, superseded-terminal-honesty: the fenced-out segment must reject typed with the distinct reason and zero settle entries, and the successor must settle ok by replay with exactly one settle entry and no second paid call.

Patch Changes

1.135.0

Patch Changes

1.134.0

Patch Changes

1.133.0

Patch Changes

1.132.0

Patch Changes

1.131.0

Patch Changes

1.130.0

Minor Changes

  • d6bec7a: Every tool event names its call (RV908, the thirteenth experiment's OTel attribution risk). tool:start and tool:end gain toolCallId, the model-minted id the journal's messages and tool-result parts have always carried: present on every live event and on every replayed reconstruction (the id rides the checkpoint's tool-result parts, so even journals written before this release name their calls on resume), absent only on streams recorded before RV908 or written by foreign emitters.

    The OTel exporter pairs tool spans EXACTLY by the id (stamped as rulvar.tool.call_id), so concurrent same-name calls that finish out of order keep their own durations and outcomes instead of FIFO-swapping attribution. Streams without the field keep the historical FIFO pairing byte for byte, an id-bearing tool:end whose start carried no id falls back to the same FIFO (mixed streams pair no worse than before), and the orphan tolerance (a closer with no open start attaches as a span event) is unchanged.

Patch Changes

1.129.0

Minor Changes

  • 1612439: Honest terminals (RV906 + RV907, the thirteenth experiment's release risks six and seven): a forced finish names itself partial, and a failed settlement is never a green event.

    RV906: under the default budget.atCap: 'finish-with-partial', the capped terminal's value becomes the completion envelope { result, completion }, and the literal is 'partial' unless the finalizer's finish provably passed the FULL declared contract: the declared finish validators now BIND the reserved finalizer (on capped runs synthesis never runs, so that finish is the final output they must judge; a finish they reject never becomes the run value and the deterministic fallback settles the run), while a declared acceptance policy is still never judged at the cap, so with one declared the terminal stays 'partial'. The finalize fallback's synthesized partial carries the same completion: 'partial' claim on its exhausted outcome. The engine lifts the literal onto run:end and the outcome mirror, so a consumer reading only status can no longer execute a truncated plan as a full success. The journaled finalize effects also roll forward on resume: a settled capped run reuses its recorded finalize terminal (or fallback decision) instead of re-deriving the prompt from the drifted live digest, which used to mint a fresh agent identity and re-pay the reserve on every resume of an already settled capped run.

    RV907: run:end gains settled: false, present ONLY when a settlement write failed (the run_settle journal append or the terminal RunMeta projection): the status stays true as computation, but nothing durable records it and handle.result rejects with the typed SettlementError, so an event-only consumer is refused the green terminal exactly like the rejected promise. The CLI progress line appends settled=false (outcome withheld; resume re-settles), and the OTel exporter stamps rulvar.run.settled: false and refuses the OK span status. The order stays warn, then the marked run:end, then the throw; a healed resume re-settles by replay with zero paid calls and its terminal carries no field, byte for byte like every ordinary run.

Patch Changes

1.128.0

Patch Changes

1.127.0

Patch Changes

1.126.0

Patch Changes

1.125.0

Patch Changes

1.124.0

Patch Changes

1.123.0

Patch Changes

1.122.0

Patch Changes

1.121.0

Minor Changes

  • 3d67d41: Rate provenance made checkable (RV807, RV813, RV814). The pricing row grows ratesVerifiedAt (SPI), the ISO date it was last verified against the provider's documented rates or, stronger, its billing categories: the shipped seeds stamp it (the GPT-5.6 family reads 2026-07-30, the day the statement reconciliation confirmed those rates against the provider's own per-component billing categories to the cent; the pre-5.6 OpenAI rows keep their 2026-07-18 docs verification; every Anthropic row was re-verified against the documented table on 2026-07-30). The date is surfaced wherever a dollar is consumed: preflightEstimate copies it onto each spawn report and rulvar preflight renders ratesVerified=<date> with its age on the spawn line; the settle pin journals it with the rest of the applied row so it survives any later table rewrite; and rulvar invoice prints a rates verified: line naming each priced model's date and age, pinned rows first, current table past them; the twelfth run's founder read the invoice doubting the rates and nothing said the seed was 12 days stale. The doctrine ships with the mechanism: seeds bound ceilings conservatively, billing truth is established only by reconcileStatement over saved exports, and a confirmed divergence corrects the seed in its own release with a changeset, never a silent rewrite. Enforcement rides two new gates: a weekly documented-rates audit (scripts/rates-audit.mjs in the live contract workflow) re-fetches exactly the pages the seed comments cite, compares every rate, write premium, and long-context tier, and opens an issue on drift or on a page that stops extracting, and a README release-table gate (scripts/readme-release-shas.mjs, in CI) requires every cited squash SHA to be an ancestor of HEAD, catching the v1.109.0 row that pointed at an object no branch contained for eleven releases (now corrected to the real squash 58afdb5).

Patch Changes

1.120.0

Minor Changes

  • d630c9e: The partial fan-out contract and the per-child acceptance roster (RV805, RV806). parallel_agents admits children sequentially in submission order, and a mid-loop admission refusal is now part of the TYPED tool result instead of a throw: the model keeps every started handle (awaitable and cancellable), and refused names the failed index, the typed error code, and the reason; a thrown refusal used to swallow the whole call while the started children kept spending invisibly, inviting a duplicate wave. The clean-wave result stays byte for byte { handles }. The acceptance fold now journals a per-child machine roster inside its single decision and carries it as acceptanceChildren on the envelope, the RunOutcome, and run:end (same lift and malformed-drops-silently posture as the salvage lists, mirrored to OTel as rulvar.run.acceptanceChildren): each spawned child with its settled status, the salvage arm that accepted it, and, where the child declared an evidence contract, the evidence verdict { recordedEntries, minEntries, met } with waivedBySalvage: true on a below-floor child a salvage arm accepted anyway; the twelfth comparison run accepted two below-floor children through salvage and nothing machine-readable said so. Behind it, a declared evidence contract now stamps EVERY settled AgentResult with evidence (the same window-derived count as the enforce-refuse floor), absent without a contract so those results stay byte-identical. rulvar inspect prints the acceptance verdict with the completion, the salvage lists, and the per-child evidence verdicts from the journaled decision, plus journaled quota_drift decisions labeled per-minute window, not cumulative. The guides now state the gating rule outright: gate on the (status, completion) pair, never on status alone.

Patch Changes

1.119.0

Patch Changes

1.118.0

Patch Changes

1.117.0

Minor Changes

  • c15b83a: Tool executions become real OTel child spans and the agent span survives to its end (RV802, the twelfth experiment's P0 #2). Tool events ride the agent's spanId and carry no per-call id, so toOtel previously swallowed every tool:start as a duplicate opener of the agent span and let the FIRST tool:end close the agent span itself: agent:end then attached usage, cost, and the exploration counters to nothing, later tool events reopened and reclosed agent-keyed spans, and in the twelfth comparison run all 569 tool events of the live stream produced zero tool spans while every agent span carried a tool's duration and outcome. The exporter now pairs each tool:start with its tool:end under a synthetic FIFO key per (agent span, tool name) and starts a tool <name> child span of the agent span: the agent span closes only at agent:end with the whole dispatch's usage, cost, rulvar.retry_count, and rulvar.exploration.*; a denied call closes its own span with rulvar.status: 'denied' and the rulvar.tool.guard marker on the tool span; concurrent same-name calls keep exact counts, parentage, and durations (attribution may swap among identically named spans, the best the id-less vocabulary allows); and a tool:end with no matching start, the foreign or truncated stream shape, attaches as a span event instead of closing anything.

Patch Changes

1.116.0

Patch Changes

1.115.0

Patch Changes

1.114.0

Patch Changes

1.113.0

Minor Changes

  • a60807a: The pricing composition's second half names itself, and the effect-ledger quarantine is byte-true (RV706, RV707). InvoicePricingProvenance gains optional currentPricingVersion: on composed exports it is the version of the caller's current table, the one that priced everything past pinnedThroughSeq (on current-table exports, the whole fold), so an invoice folded across a rotation now names both halves of the composition where the pinned segments already declared theirs; rulvar invoice and rulvar inspect fill it from the configured table and extend their text suffix to pins composed with the current table (v-a, v-b; current v-live), byte for byte unchanged when the config declares no version. The executor ledger's torn-tail quarantine row now carries bytesBase64 and sha256 of the exact torn bytes alongside the lossy bytes string kept for old readers (two different byte tails used to collapse into one indistinguishable row), and the repair's parseable decision is made on the bytes, strict UTF-8 before JSON.parse: the lossy decode could make a fragment with invalid bytes inside a string literal parse, and the repair then terminated a line of invalid bytes in place, manufacturing exactly the corruption the fail-closed scan refuses.

Patch Changes

1.112.0

Patch Changes

1.111.0

Patch Changes

1.110.0

Patch Changes

1.109.0

Patch Changes

1.108.0

Minor Changes

  • affa3d4: Stored consumers compose the pricing pins exactly like the engine, and the invoice provenance declares every pinned version (RV611).

    JournalPricingSnapshot exports the composition the engine's outcome mirror applies at settle: composedPriceUsd(current) prices pin-covered rows at the rates their own settle recorded and everything past the last pin (a segment journaled but never settled) at the caller's current table. The engine now consumes the same method, and the three stored consumers (rulvar inspect, rulvar invoice, the server's stored-run cost endpoint) fold through it instead of passing the raw snapshot, which silently priced the tail at the last pin's rates and folded never-pinned models as unpriced even when the current table knows them. Two fallbacks stay deliberate and documented: a covered model its covering pin missed back-reprices at the last pin when that pin names it, and a model no pin resolves falls to the current table.

    The snapshot also carries segments (every pin's seq boundaries, pricingVersion, and rows in journal order), and InvoicePricingProvenance gains the 'composed' source plus segments and pinnedThroughSeq, so an invoice folded across a price-table rotation names every version that priced it instead of hiding the rotation behind the last one. The CLI exports that priced through a pin now declare source: 'composed' (previously 'snapshot'), and the pricing rates:/pricing: text lines name the composition and every pinned version.

Patch Changes

1.107.0

Patch Changes

1.106.0

Patch Changes

1.105.0

Patch Changes

1.104.0

Patch Changes

1.103.0

Patch Changes

1.102.0

Patch Changes

1.101.0

Patch Changes

1.100.0

Patch Changes

1.99.1

Patch Changes

  • ef08d73: Guarantee matrix and exactly-once claim hygiene (RV508); no runtime behavior changes. The isolated-executor guide now carries the guarantee matrix stating flatly who provides what: the library's layers give at-least-once execution with attempt binding and intent-before-effect, exactly-once effect execution is promised by NO library layer, and what IS exactly-once is pay and replay (the never-pay-twice invariant). The two claims the ninth comparison experiment's judge caught are rewritten to the precise statements ("each ran once" became attempt counting under a stable idempotency key; the approvals guide now says continuation is a run-level guarantee, not an effect-level one, with the at-least-once window named); ctx.step docs state the same window for effectful steps; a ResolutionBy note says the field records a channel, never a verified principal (identity, signatures, and separation of duties are host IAM). The worker header now points at the shipped SqliteQuotaLimiter and PostgresQuotaLimiter instead of denying that cross-process limiters exist. A new docs-lint sentinel forbids "exactly once" claims in the hand-written docs and in package source comments outside a vetted (file, heading anchor) allowlist (the durability pay doctrine and the guarantee matrix), and every remaining occurrence in doc prose and source comments was rewritten to the precise wording; string literals are deliberately out of scope (tool descriptions enter the toolset hash).
  • Updated dependencies [ef08d73]

1.99.0

Patch Changes

1.98.0

Patch Changes

1.97.0

Minor Changes

  • 5c3b453: Per-request cost accounting and per-segment pricing pins (RV504/RV505/RV511, the ninth-experiment accounting P1s).

    RV504: when a terminal entry's per-dispatch providerCalls exactly cover its usage, costReportFromJournal and invoiceFromJournal now price each provider call individually, so a nonlinear long-context tier fires per REQUEST, which is the pricing contract's stated semantics. An aggregate that crossed a threshold no single request crossed no longer re-prices the whole entry: the ninth comparison experiment's settled report ran 52.4% above the live budget's per-dispatch debits for exactly this reason, and the two figures now converge. Entries without records, or with records that do not cover their usage, fold exactly as before (the per-model aggregate), and the invoice says so: rowUsdNonAdditive is now a computed boolean (false exactly when every contributing entry is fully attributed, so the per-call rows sum to the total; allocatedUsd remains the column that sums exactly in every case). The shared fold is public: priceEntryBilling with EntryBillingUnit/EntryBillingFold beside priceEntryUsage.

    RV505: journalPricingSnapshot now composes the run-settle pricing pins by their settle seq, with no journal shape change: a seq-aware fold prices each row under the pin of ITS OWN segment (the rates its live debits actually used), so a suspend/resume across a price-table rotation no longer re-prices settled history under the new table. Seq-less callers keep the historical last-pin behavior. priceUsd callbacks across the accounting folds accept an optional third seq argument (existing two-argument implementations are unaffected), the snapshot exposes pinnedThroughSeq, and the engine's settled-outcome cost mirror composes pinned history with the live table for the segment being settled.

    RV511: the CLI invoice text output now states the pricing basis honestly per export: additive per-request rows, or the aggregate basis with the reason (a remainder or legacy entry in the fold).

Patch Changes

1.96.0

Patch Changes

1.95.0

Patch Changes

1.94.0

Minor Changes

  • 426e57d: The SSE replay buffer of createServer is finite by default (RV409): an absent maxBufferedEventsPerRun now resolves to the exported DEFAULT_MAX_BUFFERED_EVENTS_PER_RUN (50,000 events per run) instead of unbounded, with the already established drop semantics past the bound (oldest events dropped in chunks, the retained window never below seven eighths of the bound, replays carrying x-rulvar-events-dropped and a leading SSE comment naming the first retained seq; the journal remains the durable record). Migration: a deployment that relied on the historical unbounded buffer sets an explicit huge bound, for example Number.MAX_SAFE_INTEGER; nothing changes for servers that already configured the option, and the option's domain (a positive safe integer, typed ConfigError otherwise) is unchanged.

Patch Changes

1.93.0

Patch Changes

1.92.0

Minor Changes

  • 351d1f5: Historically stable invoices via the applied-pricing pin (RV407, the eighth-experiment review). The invoice and cost folds price at fold time, so a live price-table update used to silently re-price history. When createEngine({ pricing }) is configured, the settling segment now pins what it actually applied, the resolved pricing row of every model the journal used plus the table's pricingVersion, additively inside the existing run-settle decision value (the outputHash precedent: no journal shape change). The pin is gated on the configured table deliberately: caps-fallback pricing arrives ambiently from adapters and a setting the user never enabled must not change the journal, so table-less runs settle byte for byte as before; rates the fold would refuse anyway, non-finite or negative, are never pinned. New journalPricingSnapshot(entries) reads the pin back and rebuilds a priceUsd over exactly the pinned rows (absent models fold as unpriced, never a silent zero); invoiceFromJournal accepts a declared provenance and the export carries pricing: { source: 'snapshot' | 'current-table', pricingVersion?, rows? }. rulvar invoice, rulvar inspect, and the server's stored-run cost endpoint prefer the pin, so a repeated fold after the table changes reproduces the original numbers; journals settled before the pin keep the current-table fold and say so. Live pricing, budget admission, and journaled spend debits are untouched.

Patch Changes

1.91.0

Patch Changes

1.90.0

Patch Changes

1.89.0

Patch Changes

1.88.0

Patch Changes

1.87.0

Patch Changes

1.86.0

Patch Changes

1.85.0

Minor Changes

  • 6932a9f: Three fail-closed fixes from the cycle 83 sweep, plus the dependency refresh.

    Engine. A typed error thrown out of ProviderAdapter.stream() now keeps its own class instead of being laundered into a retryable transport fault. A ConfigError (a bridged model id that does not match the wrapped model, an unsupported role, a namespaced option contradicting a canonical field) used to be retried through the whole backoff ladder and then trigger transport failover, so a misconfigured primary silently served the run from a fallback model the caller never asked for while the real fault vanished behind a generic message. Typed errors that ARE retryable by class (a lost lease) keep retrying exactly as before, and an untyped throw is still a retryable transport fault.

    Planner sandbox. The realm scrub replaced Date.now and Math.random, which left three ambient sources open: a bare new Date() never consults Date.now (V8 reads the system clock directly), performance.now() is a second live clock, and WebCrypto (crypto.randomUUID(), crypto.getRandomValues()) is raw entropy. Those are the first idioms a machine-written script reaches for, and each silently produced a run that could not reproduce on replay. All of them now draw from the same seeded stream: zero-argument new Date() and Date() take the logical clock, performance.now() is that clock minus the segment base, crypto.randomUUID() is the journaled uuid shim, and crypto.getRandomValues() fills from the seed. Passing a timestamp or a date string to Date stays a pure conversion.

    Server. A tracked run whose segment REJECTS instead of settling (the genesis ownership boot refusing a run another process owns, a withheld settlement whose durable write failed) was reported as running for the life of the process, its SSE connections never closed, and neither retention nor the settled cap could release it. GET /runs/:id now answers status: "error" with the typed wire error, connected streams close with a comment naming the failure, a late subscriber gets that comment instead of an empty stream, and the tracked run becomes eligible for retention like any other terminal run.

    Dependencies. @anthropic-ai/sdk moves to ^0.115.0 (the only shipped floor its caret was blocking); in-range minors refresh across the workspace. The four majors stay held: eslint 10 and @eslint/js 10, @types/node 26 against the Node 22.12 floor, and TypeScript 7. The tsdown resolution is pinned at 0.22.3 because it generates the frozen .d.ts artifacts, including the published @rulvar/compat tarball that must repack byte identical.

Patch Changes

1.84.0

Patch Changes

1.83.0

Patch Changes

1.82.0

Patch Changes

  • 9cc5d66: The free-cleanup harvest (cycle 80). leasableStoreConformance gains the expiry option: the mandatory lease checks follow the suite's no-wall-clock convention, so the harness now hands them a store whose ttl no scheduler stall can cross, and only the wall-clock expiry check keeps a short-ttl store of its own; the legacy single-ttlMs pairing let one CI stall past 150 ms expire a just-acquired lease inside a fencing check (the flake observed on Node 22). All three shipped harnesses move to the split pairing, and the store-authors guide stops recommending the flaky shape. In @rulvar/cli, worker retention is no longer slot-bound: a worker whose every concurrency slot is busy still applies retention over settled runs during its sweeps instead of starving until idle. In @rulvar/core, concurrent cold tools() calls on an MCP source share one in-flight tools/list fetch instead of each sweeping the list, and AdmissionController's maxTotalSpawns TSDoc now tells the truth: it is the controller-lifetime cap on admitted spawns for hosts driving the controller directly (pinned by a test), while engine runs cap totals through budgetDefaults.lifetimeSpawnCap; the old comment claimed it was the per-orchestrate maxSpawns.
  • Updated dependencies [9cc5d66]

1.81.2

Patch Changes

  • 296885b: Three defects from a deep review of the MCP bus and the queue worker (cycle 79). In @rulvar/cli, createWorker().stop() now waits out a sweep that is still scanning the store before taking its cancel snapshot, and a sweep observes the stop before every lease: previously a stop() racing an in-flight sweep could resolve while that sweep went on to lease and drive a new run, leaving a live run and a held lease behind a "stopped" worker. In @rulvar/core, the MCP tool source no longer loses a listChanged notification that races the in-flight tools/list fetch (the fetched list is served but never pinned as the session cache, so the next snapshot refetches), and cursor pagination treats an empty nextCursor as exhaustion instead of spinning the import loop forever on a server that echoes it. A regression test also pins the SDK-level rejection of a declared outputSchema with no structuredContent, guarding the planned SDK v2 migration.
  • Updated dependencies [296885b]

1.81.1

Patch Changes

1.81.0

Patch Changes

1.80.0

Patch Changes

1.79.0

Minor Changes

  • 85956ab: Terminal admission at an exhausted tool budget, the two harness-shape preflight findings, and the degradation mirror (the fifth comparison experiment).

    The fifth experiment lost a complete 3984 word answer to terminal tool starvation: the harness set the synthesis tool cap to the child count, the mandatory get_child_result reads spent the whole budget, and the ready finish was cut BEFORE the terminal interception, so the validators never ran, the funded repair reserve never armed, and the run failed closed with the candidate stranded in the transcript.

    • The terminal tool is now exempt from the tool budget in both directions: it never consumed maxToolCalls or toolUnits below the cap, and an exhausted budget no longer starves it either. An admitted finish validates and, on rejection, feeds the repair grants exactly as below the cap; non-terminal calls beside it are answered with typed skipped results so the continued exchange keeps a well formed history; a batch with only non-terminal calls past the cap settles limit byte identically to before.
    • New preflight warning synthesis-terminal-tool-headroom: synthesis.exposeChildResultTools with a synthesis.limits.maxToolCalls below one read per possible child (orchestrator.maxSpawns) loses evidence access to the reads themselves.
    • New preflight warning draft-gate-below-contract: a draftPolicy.minWords below the contract's own word minimum admits drafts the final validators must reject, so the paid synthesis starts from an underlength base. The preflight input mirrors finishValidation.draftPolicy for it.
    • The completion lift now mirrors the degradation facts the acceptance envelope already emits: degradedReasons, salvagedPartialChildren, and salvagedTerminalOutputChildren ride run:end and the RunOutcome under the same shape validation as completion and childStatusCounts, and the OTel exporter maps them to rulvar.run.* attributes. An empty array is the workflow's claim of zero degradation; absence means no claim.

Patch Changes

1.78.0

Patch Changes

1.77.0

Patch Changes

1.76.0

Patch Changes

1.75.1

Patch Changes

1.75.0

Patch Changes

1.74.0

Minor Changes

  • d94beab: Quota drift telemetry and the honest zero (the v1.71 experiment review, P0.5 resized + P1.4). The experiment declared 12M TPM over a provider-real 1M, the local limiter went quiet, and seven live 429s followed with nothing recording the mismatch. Now: both wire adapters parse the provider's x-ratelimit headers on every real 429 into normalized per-minute limits (WireError.data.reportedLimits; the openai wire also gains the raw bucket capture the anthropic wire already had), the loop remembers them per (provider, model) as live telemetry, and the opt-in quota.declaredRules (the SAME rule array preflight takes) makes the engine journal a quota_drift decision plus a warn log whenever a binding declared cap EXCEEDS the provider-reported one, per invocation and dimension, with anthropic's split input and output windows summed against a combined declared tokensPerMinute. Purely observational, synthetic limiter denials never count, and without declaredRules journals and events stay byte identical. On the invoice, an unconfirmed row that recorded zero usage on every counter now carries usageUnknown: true (export-level usageUnknownRows count, CLI usage-unknown marker): the zeros mean "nothing recorded", never "the provider metered nothing"; derived at export time, no journal shape change.

Patch Changes

1.73.0

Minor Changes

  • 3e95bd1: The synthesis repair envelope (the v1.71 experiment review, P0.4/P0.8/P1.7): finishValidation.repairTurnReserve grants bounded EXTRA turns to the invocation the validators bind, one per rejected finish exchange (schema-invalid finish arguments and host validation rejections alike), derived from the message window itself so resumes recount identically and nothing new journals; the deliberately-deferred RV-204 reserve, now that the experiment showed one malformed finish plus one validator rejection killing a whole run inside maxTurns 3. Every typed synthesis failure now carries the acceptance snapshot (completion, childStatusCounts, lifted onto the error outcome by the completion mirror, so an errored run still reports "the fan-out work is complete") and the verdict-derived repair taxonomy (repairsUsed, maxRepairs, rejectedValidators) read from journaled decisions. preflightEstimate models the separate synthesis invocation (orchestrator.synthesis: limits, model, estInputTokens; echoed at budget.orchestrator.synthesis, priced into exposure.runCeiling, the gap the experiment's projection stopped short of) and folds a declared finishValidation.repairTurnReserve into the projected turns of the bound invocation; the CLI prints the synthesis projection line. Zero reserve and no synthesis declaration keep every ceiling, journal, and report byte identical.

Patch Changes

1.72.0

Patch Changes

1.71.0

Minor Changes

  • 20d02e0: The preflight quota planner follows the run past the first wave (the second experiment report, rec 9). Every declared spawn now reports projectedProviderTurns, the provider-call ceiling of its whole loop (maxTurns bounded by the executed-call ceiling plus the final no-tool turn, plus the finalization summary turn when a tool budget limiter arms it), and the orchestrator echoes its own. exposure.runCeiling totals the declared wave run to those ceilings at the declared estimates: provider calls as fan-out times per-spawn turns, and cumulative tokens with the context regrowing every turn (turn k re-sends the declared prompt plus the k-1 prior output bounds, so a K-turn loop costs K x est + outputBound x K(K+1)/2). Three findings compare that projection against the declared quotaRules when the first-wave checks stay silent: quota-requests-below-run (the loops project more wire requests than requestsPerMinute admits; the message names about how many windows the run needs at best), quota-tokens-below-run (the regrowth cumulative exceeds tokensPerMinute), and the spawn-attributed quota-turn-never-fits (by turn k the single context-grown reservation exceeds the whole token window, which the limiter denies with retryAfterMs 0 and no wait helps). The first-wave checks are byte-identical, and a run whose ceiling fits its windows produces exactly the findings it did before. rulvar preflight prints the new turn ceiling per spawn and the run ceiling on the exposure line; --json carries the fields verbatim. The experiment run behind the recommendation had zero preflight quota findings and eleven live limiter denials; this projection is what would have said so before the first dispatch.

Patch Changes

1.70.1

Patch Changes

1.70.0

Patch Changes

1.69.0

Patch Changes

1.68.0

Patch Changes

1.67.0

Minor Changes

  • 8e6006d: The honest invoice (the experiment review, items 11.2/11.3, recommendations P1.2/P1.3/P1.4). The reconciliation verdict now names exactly what it asserts: the value matched is renamed to provider-id-present, because the library never sees provider billing data and the old term read as a statement match it cannot make (deeper reconciliation tiers are host-side joins keyed on responseId). Consumers comparing row.reconciliation === 'matched' must switch to 'provider-id-present'; reconciliationFailures keeps its meaning (rows without a provider id). InvoiceExport is now self-describing about pricing: pricingBasis: 'per-call' declares that per-row usd prices each call individually at current rates, and rowUsdNonAdditive: true warns that those values need not sum to totalUsd under a nonlinear price table (long-context tiers price a split differently from its sum). For consumers whose rows must sum, every InvoiceRow gains the additive allocatedUsd column: each (entry, serving model) slice of the same gross fold the totals run is distributed across its rows in proportion to per-row usd (token weights when every row priced to zero), one row absorbs the IEEE rounding dust, and the flat sum over rows reproduces totalUsd exactly. rulvar invoice prints the declared basis in the text form and passes the new fields through --json unchanged.

Patch Changes

1.66.0

Patch Changes

1.65.0

Patch Changes

1.64.0

Patch Changes

1.63.0

Patch Changes

1.62.0

Minor Changes

  • fca5fd1: Ship the preflight effective-limits estimator and effective-config linter (the experiment-review P2.2): everything the engine derives from a configuration, computed before any provider dispatch, machine readable, with zero paid requests by construction.

    Core exports preflightEstimate(input): a pure function over the same options createEngine and engine.run receive plus a declared spawn wave, returning the JSON-serializable PreflightReport. The estimate cannot drift from the engine because it reuses the runtime's own arithmetic: mergeUsageLimits for the effective per-spawn limit merge (call over profile over engine defaults), admissionReserveUsd for the layer-1 reserve formula arm for arm (estCost, profile estCost, the priced estimate from estInputTokens, the flat default, and the unpriced-model zero), the settlement price resolution, and the shared-quota dimension match. The report carries the admission projection over the declared wave mirroring admitSpawn exactly (which spawns admit, which are denied and by what: budget, spawn cap, orchestrator maxSpawns, or an orchestrator cap its own reserve cannot fit), the per-tool and weighted-unit executed-call ceilings with the first bottleneck named, the orchestrator effective cap and finalize reserve echo, the concurrency and per-provider exposure floors with the one-more-turn overshoot floor, and the linter findings with stable kebab-case codes (errors: unrouted-role, unknown-profile, nothing-admitted, orchestrator-cap-below-reserve; warnings: partial-admission, weighted-units-bind-first, tool-unaffordable, unpriced-under-ceiling, inert-finalization-reserve, inert-tool-budget-notices, orchestrator-cap-fraction-bound, the quota-window comparisons; infos: overshoot-exposure, no-usd-ceiling, no-quota, per-tool-cap-unreachable).

    The CLI gains rulvar preflight <file|name> [--budget-usd N] [--profile NAME] [--spawns JSON] [--json]: it assembles exactly the options rulvar run would (config, module exports, run profile) but constructs no engine, opens no store, and dispatches nothing. The declared wave comes from the new preflight export of the config or workflow module ({ spawns?, orchestrator?, quotaRules? }), --spawns overrides it, --json emits the machine-readable report, and the exit code is the linter contract: 1 when any finding has severity error.

Patch Changes

1.61.0

Minor Changes

  • b4c1f1f: Durable provider reconciliation (the experiment-review P1.3): every live provider dispatch now mints a ProviderCallRecord on the terminal entry's providerCalls ledger, the CostReport splits gross from net, and invoiceFromJournal plus rulvar invoice export the rows.

    • The per-dispatch ledger. Every wire call the engine actually makes, successful or not, records { ordinal, role, servedBy, attempt, outcome, responseId?, usage, usageApprox?, errorCode?, aborted? }, minted at the single dispatch chokepoint from the same sanitized usage the phase slices accumulate. Failed and retried attempts keep their billed usage attributable instead of dissolving into the aggregate; quota denials and abort short circuits that never reached the adapter mint nothing. The provider responseId both shipped adapters already surface on every finish is now persisted. The ledger rides every checkpoint boundary (kill-and-resume keeps pre-kill calls attributable, ordinals continuing) and restores verbatim on replay with zero live calls.
    • Gross versus net. CostReport.totalUsd stays the net ledger it always was (abandoned subtrees contribute zero). New required fields make the provider's view first class: grossUsd (net plus abandoned, the figure an invoice reconciles against; abandoning a branch never shrinks it) and abandoned: { usd, unpriced, usageApprox? }. rulvar inspect prints the gross line whenever a run abandoned paid work.
    • The invoice export. invoiceFromJournal(entries, priceUsd) returns one row per billable call with a reconciliation verdict per row: matched (response id present), missing-provider-id (a finished call without one), unconfirmed (a failed or severed call without one), unattributed (pre-ledger entries and restored remainders; the spend surfaces instead of vanishing). Totals are the same slice fold the CostReport runs, so totalUsd === CostReport.grossUsd exactly. rulvar invoice <runId> [--json] is the CLI form.

    The frozen cassette catalog is re-recorded for the additive providerCalls field on terminal agent entries (journal-shape-revision, policy not identity: no hashVersion change, no matching impact).

Patch Changes

1.60.0

Patch Changes

1.59.4

Patch Changes

1.59.3

Patch Changes

1.59.2

Patch Changes

1.59.1

Patch Changes

1.59.0

Patch Changes

1.58.0

Minor Changes

  • 4fa35ce: RV-217: data protection hooks, the full close. The plan's gate ("PII never persists or emits in plaintext under policy") now holds end to end. (1) ENVELOPE ENCRYPTION on the serialization seam: createEnvelopeEncryption({provider, historicalWrappedKeys?, plaintextReads?}) returns a SerializationHook that AES-256-GCM encrypts every persisted byte (journal payloads, transcript blobs, checkpoints) with entry identity as associated data (a ciphertext moved between entries or refs fails authentication), keeping only the kernel-pinned ordering/identity fields plus spanId and timestamps plaintext; DataKeyProvider is the KMS seam (the exact shape of GenerateDataKey/Decrypt, called only in the async factory so the sync hooks run on in-memory data keys, and every envelope carries its wrapped key so reads need no live KMS); the shipped localKeyProvider derives KEKs via HKDF-SHA256 with an info partition for tenant-scoped keys (a different tenant's provider cannot unwrap, pinned by tests); reads of non-enveloped data fail closed by default with plaintextReads: 'passthrough' as the explicit migration mode; fromStored(toStored(e)) reproduces entries exactly, so replay, resume, and recovery are untouched and a run over real files greps to ZERO plaintext PII while Engine.stores reads plaintext through the one policy point. (2) REDACTION POLICY: redaction.patterns adds host-defined patterns (RegExp or strings, compiled once, typed ConfigError on an invalid one) on top of the default credential set for every emitted event, via the new exported compileSecretMasker; the OTel exporter accepts the same patterns for trace parity. (3) EXPORT/IMPORT: engine.exportRun(runId) produces the portable bundle (meta, entries, blobs) read through the policy point, so encrypted deployments export plaintext for subject-access requests; engine.importRun(bundle) writes through the target's stores (re-encrypting under its policy), keeps the original runId, and refuses an existing run typed; together with the existing deleteRun/pruneRun this completes the retention/deletion/export surface. (4) SALTED METADATA DIGESTS: security.argsHashSalt switches RunMeta.argsHash to HMAC-SHA256 under a deployment salt (equal args stop correlating across deployments; low-entropy args stop being recoverable from the digest), hashRunArgs gains the optional salt, and the CLI resume args gate picks the salt up from engineOptions.security automatically. (5) AUDIT TRAIL: reduceAuditTrail(entries) folds a journal into the typed, ordered sequence of authority events (suspensions with deadlines, resolutions with who and what, abandons with reasons, engine decisions, termination denials, run settles), tolerant across journal vintages. New guide page: https://docs.rulvar.com/guide/data-protection.

Patch Changes

1.57.0

Patch Changes

1.56.0

Patch Changes

1.55.0

Patch Changes

1.54.0

Minor Changes

  • 3f6bc03: Three improvement-plan remainders: the run:end semantic completion lift (RV-207 tail), the standard repository research toolset (RV-210), and incremental synthesis with pre-model claim deduplication (RV-211).

    The completion lift. Transport status and semantic completeness are different claims, and run:end now carries both: a workflow that returns an object result with a valid completion literal ('complete' | 'partial' | 'rejected') and optionally a childStatusCounts record, or throws a typed error whose data carries them, gets both lifted onto the run:end event. The orchestrator acceptance path emits the envelope on every terminal, including the typed rejection (its FailRunError data now carries completion: 'rejected'). Malformed shapes stay silently absent, replay recomputes identical fields, the CLI progress line renders completion=..., and the OTel exporter maps rulvar.run.completion and rulvar.run.childStatusCounts.

    The repository research toolset. repositoryResearchToolset({ root }) ships five risk: 'read' tools over a confined directory root: list_files, search_files, and read_file with deterministic byte ordering and STABLE keyset cursors (a page boundary never shifts when unrelated entries appear; every cursor embeds its query identity), plus record_evidence, which verifies citations at collection time (the file must exist under the root, lines must be a valid 1-based range inside it, quote must appear verbatim), and list_evidence. Pages are canonical: byte-identical however addressed, which is exactly what the exploration guards measure, so maxRepeatedToolSignature and maxNoNewEvidenceCalls compose with the kit instead of being defeated by marker fields. Absolute paths, .. escapes, and symlink escapes are typed error results; the host reads collected evidence via kit.evidence().

    Incremental synthesis and claim dedup. synthesis.mode: 'incremental' dispatches one bounded synthesize-role NOTE invocation per settled child the moment it settles (default noteLimits { maxTurns: 2 }), overlapping the still-running fan-out, and the final result is a DETERMINISTIC reconciliation envelope (IncrementalSynthesisResult), never another model call; a dead note falls back to that child's raw digest summary under a journaled per-child orchestrator_synthesis_note_fallback decision, replay reproduces the envelope with zero paid calls, and finishValidation plus incremental mode is a ConfigError at intake because the reconciliation has no model-composed finish to validate. synthesis.dedupeClaims: true deduplicates repeated claim lines across children BEFORE any model call (whitespace-collapsed exact matching via the exported pure dedupeRepeatedClaims, never fuzzy): in single mode the digest keeps first occurrences with a REPEATED CLAIMS index riding the prompt, in incremental mode the envelope carries repeatedClaims. Both options default off and the synthesis prompt stays byte-identical when unset.

Patch Changes

1.53.0

Patch Changes

1.52.0

Minor Changes

  • e138df9: Ship the RV-210 exploration guards (first slice): three opt-in UsageLimits fields that make an oscillating tool loop visible and boundable. toolBudgetNotices surfaces soft 50%/80% thresholds over maxToolCalls to the model as a plain user message with the exact remaining count (once per threshold, checkpoint-safe, inert with a loud warning without maxToolCalls). maxRepeatedToolSignature caps executions of the byte-identical call (tool name plus RFC 8785 canonical args): the excess call is never dispatched, the model receives a typed error result naming the count, the denial does not consume the tool budget, and tool:end carries outcome: 'denied' with guard: 'repeated-signature'. maxNoNewEvidenceCalls aborts the invocation as status limit with the new abortClass: 'exploration' when N consecutive successful executions return only already-seen result digests; the executed work is kept, the terminal memoizes, and the structured ExplorationSummary (toolCallsUsed, distinctSignatures, repeatedCalls, duplicateResultCalls, deniedRepeats, byTool) journals beside the abort class so a replayed consumer sees the same typed evidence with zero live calls. Whenever any guard field is configured the summary also rides the full AgentResult and the live agent:end event (live-only for non-abort terminals, like transportRetries); values JCS cannot serialize fail open (unique signatures, fresh evidence); on resume the guard rebuilds from the restored checkpoint messages. The CLI TUI renders the guard marker on denied tool lines and the OTel exporter maps the counters to rulvar.exploration.* and rulvar.tool.guard attributes. Unconfigured invocations are byte-identical to before. Demonstrated against published 1.51.0 first: the identical call executed six of six times with zero signal, the model never saw a remaining count, duplicate pages never flagged, and the terminal was a bare limit indistinguishable from honest work.

Patch Changes

1.51.0

Patch Changes

1.50.0

Minor Changes

  • e39a885: The structured determinism contract (RV-209): bare-nondeterminism detection is engine-owned, classified, localized, and enforceable, and replay verification is a first-class CLI gate.

    • New determinism:warning event on the run stream: a bare Date.now() or Math.random() call observed inside an in-process workflow body emits category, provenance (workflow | allowlisted), the calling frame, and the parsed file/line/column, at most once per (category, provenance) per execution segment. Installed dependencies (node_modules) and Node runtime frames are classified exempt and stay silent, so an SDK's internal randomness never brands the run nondeterministic. Never journaled; because replay re-executes the body, a violation still in the code fires again on every replay organically.
    • CreateEngineOptions.determinism: mode: 'off' | 'warn' | 'error' (warn stays the default and the pre-RV-209 dev-only behavior; the process warnings now name the callsite), allowlist (substring or RegExp patterns for confirmed-safe frames, classified allowlisted, never rejected), and redact (applied to frames and file paths before they leave in events, warnings, and errors). Config is validated loudly at createEngine.
    • mode: 'error' detects in every environment including production and rejects the run: the offending call throws a typed DeterminismError (new error code determinism, localization in data) at the call site, and a workflow that swallows it is re-thrown at settle, so the run ends 'error' instead of recording a value replay cannot reproduce.
    • The journaled run-settle decision now records outputHash (canonical JCS sha256 of the settling segment's result; absent for undefined or non-serializable values). Pure replays append no settle, so a divergent replayed result can never overwrite the live baseline. hashRunOutput and the extended lastRunSettle are exported.
    • New rulvar replay <runId> [--args JSON] [--store PATH] [--assert-no-live] [--compare-output-hash]: a dry-run resume (zero journal or meta writes, zero adapter calls) that reports replay accounting, every localized determinism warning, and the digest comparison; --assert-no-live exits 1 unless the replay is pure, --compare-output-hash exits 1 unless the replayed result's digest equals the journaled one. Deliberately no --allow-args-change: verifying a different logical run proves nothing.
    • The TUI renders determinism:warning lines, and the OTel exporter attaches the event to its span with rulvar.determinism.* plus code.filepath/code.lineno attributes.
    • The frozen cassette catalog is re-recorded for the additive outputHash field on run-settle decisions (journal-shape-revision, policy not identity: no hashVersion change, no matching impact).

Patch Changes

1.49.0

Minor Changes

  • bab7b2c: Make the agent event model unambiguous (RV-207): one agent:start/agent:end pair per logical agent span, a paired agent:phase:start/agent:phase:end per model invocation phase, an official reducer, and the OTel exporter leak the old shape caused is closed.

    Before this release one spanId emitted an extra unpaired agent:start for every phase of the dispatch (loop, then summarize per compaction, finalize, extract) with a single agent:end, so durations and attempts were underivable without heuristics: a consumer pairing starts with the end read the LAST phase's duration as the agent's, a starts-minus-ends gauge leaked one running agent per phase, and the shipped toOtel exporter (reproduced on the published 1.48.0) leaked a never-ended OTel span per multi-phase agent while the span it did close measured only the last phase. The replayed stream had a different shape than the live one (one start), so the same consumer built different tables live and on replay.

    Now every phase activation emits agent:phase:start/agent:phase:end keyed (spanId, invocation) (a 1-based activation ordinal; a summarize that fires three times gets three pairs), carrying the phase's role, the serving model, durationMs, the usage delta the activation added to its (role, model) slice (the pairs sum exactly to agent:end and to the journaled usageByModel split), costUsd priced at each serving model's own rate, a binary outcome, and retries (transport retries inside the activation). agent:end gains retryCount. The retry facts are live telemetry only, never journaled: replayed events omit them, and replayed phase pairs are reconstructed from the terminal entry's recorded slices with durationMs 0, so a live stream and its replay reduce to IDENTICAL usage and cost tables. reduceInvocationTable (new in @rulvar/core) is the official no-heuristics reducer: per-agent per-phase rows plus a per-role aggregate that matches CostReport.byRole; truncated streams stay honest (open: true), never guessed at.

    @rulvar/cli: toOtel maps each phase pair to an invocation <role> child span of its agent span with gen_ai.usage.*, rulvar.cost_usd, and rulvar.retries attributes, closes the agent span with the whole dispatch's totals and rulvar.retry_count, and an opener for an already-open span never duplicates it, so even a stream from a pre-RV-207 core cannot overwrite the tracked agent span and leak it unended. The progress renderer prints the phase lines (agent w extract phase on model, then the settle line with per-phase cost, tokens, duration, and retries). Journal bytes, cassettes, and toolset hashes are untouched: events are telemetry, never identity.

Patch Changes

1.48.0

Patch Changes

1.47.0

Minor Changes

  • a3687fe: Ship phase 3 of the fenced run state RFC, reconcile and recover. The engine now journals every run settle whose segment did durable work (or changed the recorded status) as a run_settle decision entry ordered BEFORE the meta write, so the run's outcome is part of the journal and RunMeta is a rebuildable projection; the write-on-change rule keeps pure replay byte stable, so a resume that only replays appends nothing. On top of it, auditRun names the divergences a worker sweep can never see, auditRuns sweeps the catalog, and reconcileRunMeta rewrites the sound cases from the journal with zero model calls and no workflow: meta-behind (the crash residue between the journal flush and the meta write, or a stale write contradicted by a journaled settle) takes the journaled status, and stranded (a terminal meta over live journal work, the F1 residue an unfenced store admits, demonstrated against the published 1.46.0 first) becomes sweepable again; ambiguous residues are reported as suspect and never rewritten. The CLI gains rulvar runs audit [--repair], the operator probe: it lists every divergence, repairs under a brief per-run lease on a leasable store (a live owner is skipped, never raced), and exits 0 only when the catalog ends consistent. ResolutionOutcome additionally carries woke: true exactly when a resolution settled a live in-process waiter, and the HTTP server uses it to close a quiesce-window race: a resolve that applied through the fold while the segment was closing now awaits the imminent settle and continues the run in place instead of answering resumed: false on timing grounds and stranding it suspended. The committed cassette catalog is re-frozen for the additive settle entry under the journal-shape-revision lane of the fixtures lock: an additive journal evolution that revises no identity (the hashVersion stays 2; entry identity, adapter requests, and the frozen v1 resume fixtures are untouched byte for byte).

Patch Changes

1.46.0

Patch Changes

1.45.0

Minor Changes

  • b96305d: The fenced writes capability (the fenced run state RFC, phase 2). JournalStore.putMeta and delete and TranscriptStore.put and delete accept the same optional trailing lease that append always took, and a store declares enforcement with the fencedWrites: true marker: a mutation carrying a lease that is not the current holder for the mutated run rejects with the typed LeaseHeldError, atomically and leaving nothing changed, including a live lease for a different run. The engine threads the segment's lease into every durable mutation of a leased resume (meta writes, checkpoints, compaction summaries, worktree patches, workflow sources), so over a declaring store a superseded worker can no longer overwrite the successor's meta at its late settle and strand the run from worker sweeps, and its very first refused meta write now fails the stale segment typed at boot with zero paid calls. SqliteStore declares the marker and enforces it on putMeta, delete, and append (with the run-match rule as defense in depth); the conformance kit gains fencedWritesConformance as the capability's executable definition; the queue worker's retention sweep passes its brief lease through the new optional second argument of engine.deleteRun (pruneRun takes the same); and hasFencedWrites plus assertFencedWrites let a host assert the full fence at deployment time. Stores written before the capability are untouched: without the marker the extra argument is ignored and the journal-append fence works exactly as before.

Patch Changes

1.44.1

Patch Changes

1.44.0

Patch Changes

1.43.0

Patch Changes

1.42.0

Patch Changes

1.41.0

Minor Changes

  • be589ec: Add the orchestrate acceptance policy and the CLI --strict flag (the v1.40.0 improvement plan's completion contract)

    Run status ok proves that finish validated, and nothing more: the model may call finish after any mix of child outcomes, so ok alone never proves the children succeeded. The new opt in OrchestrateOptions.acceptance turns that into a checked contract. childPolicy 'all-ok' requires every spawned child to have settled ok when finish validates (a child still running counts against it); { minSuccessful: N } tolerates failures beyond the first N successes. The verdict is journaled as one decision entry, so a resume rolls the same verdict forward, immune to drift of the live options. An accepted result becomes the acceptance envelope { result, completion, childStatusCounts, degradedReasons }; a violated policy fails the run with the typed FailRunError (code fail_run, data.source 'orchestrator_acceptance') instead of settling ok. Without acceptance nothing changes: the result value stays the raw finish payload and no new journal entry is written.

    The CLI pairs with the envelope: rulvar run --strict and rulvar resume --strict exit nonzero when a settled ok value reports completion 'partial', printing the degraded reasons (strictExitCode is exported for hosts). The guides also now state the adjacent contracts plainly: await_any and await_all return truncated TaskDigests rather than full child reports, cost totals are price registry estimates with usageApprox marking estimated usage, the fencing epoch covers journal appends while RunMeta and transcript blobs stay advisory projections, and data protection at rest is owned by the host.

Patch Changes

1.40.0

Minor Changes

  • cf33550: Fence the offline resolution append and surface approximate usage (v1.39.0 review)

    The CLI server's offline resolution path acquired a store lease but never threaded it into the Replayer, so the resolution append ran unfenced: if the process stalled past its lease ttl and a queue worker took the run over, the stale append could land alongside the new owner's writes. The append now carries the acquired lease, so a superseded owner is rejected with LeaseHeldError (HTTP 409) instead of racing the current owner.

    Approximate usage is now visible where the run is reported. usageApprox rides the agent:end and run:end events and the CostReport, and the CLI cost line marks an estimated total, so a total that includes usage estimated after a transport cut, a ceiling that severed a stream, or an abort is never shown as though it were the exact provider charge. The field is present only when true, so every exact usage report and event is byte for byte unchanged.

Patch Changes

1.39.0

Patch Changes

1.38.0

Patch Changes

1.37.0

Patch Changes

1.36.0

Minor Changes

  • 101795b: Validate createWorker timers and make the TTL match promise executable (v1.35.0 review P2). ttlMs and pollMs must be integers between 1 and 2147483647 ms, refused typed at construction (an overflow or non finite cadence collapsed to the 1 ms interval floor and stormed the store). A store exposing the optional leaseTtlMs capability is verified against the worker ttl, a mismatch is a ConfigError, and an omitted ttlMs adopts the store's value.

Patch Changes

1.35.0

Patch Changes

1.34.0

Patch Changes

1.33.0

Patch Changes

1.32.0

Patch Changes

1.31.0

Patch Changes

1.30.0

Patch Changes

1.29.0

Patch Changes

1.28.0

Minor Changes

  • d98eb0b: The documented spaced syntax of numeric flags now reaches the canonical validation for negative values: rulvar run wf --budget-usd -1 reports --budget-usd must be a positive number instead of the generic parseArgs ambiguity error (v1.27.0 review P3). The fold applies only to strictly numeric negative tokens after a numeric flag (--budget-usd, --planning-budget-usd); unknown option, duplicate flag, and missing value diagnostics are unchanged.

Patch Changes

1.27.0

Minor Changes

  • 884a433: The HTTP shell's SSE delivery is now complete and bounded per connection (v1.26.0 deep E2E review). A terminal settle closes connected streams only AFTER the segment's event pump has drained, so a client that keeps reading receives the full tail including the terminal run:end instead of a clean close that silently swallowed the backlog; when the pump itself failed, the close is preceded by an SSE comment saying the stream may be incomplete. New maxPendingEventsPerClient option (default 10000) bounds what any single SSE connection can accumulate unread, independently of the replay buffer: a consumer that stopped reading is unhooked at the bound and closed with an SSE comment naming it, the frames already queued stay readable, and the standard Last-Event-ID reconnect resumes strictly after the last frame the client consumed; a replay longer than the bound is delivered the same way, in bounded chunks across reconnects, so pending memory per connection is O(bound) while delivery stays at least once. createServer now validates its numeric caps at construction with a typed ConfigError (maxTrackedRuns accepts non negative safe integers, maxBufferedEventsPerRun and maxPendingEventsPerClient accept positive safe integers): NaN used to silently mean unbounded, Infinity looked like a cap without capping, and negative or fractional values produced policies nobody asked for. The barrel additionally exports DEFAULT_MAX_PENDING_EVENTS_PER_CLIENT and the referenced types KbSweepCliConfig, LoadedWorkflowModule, and OtelContextApi, so every public signature resolves in the API docs.

Patch Changes

1.26.0

Minor Changes

  • a4fc757: The HTTP shell decouples process memory from durable retention (v1.25.0 scale review): new memoryRetention predicate and maxTrackedRuns cap release a settled run's tracked state (args, outcome, handle, SSE buffer) while the journal and transcripts stay, and new maxBufferedEventsPerRun bounds each run's SSE replay buffer (oldest events dropped in chunks and counted; a replay that lost events carries an x-rulvar-events-dropped header, and a client whose cursor predates the retained window gets a leading SSE comment naming the first retained seq). The Last-Event-ID cursor is now a binary search over the seq ordered buffer and the replay streams by index (no buffer copy); a cursor seq the buffer does not hold replays everything strictly after it instead of re-flooding the whole buffer, which remains at least once. The queue worker sweeps candidates only (listRuns({ statuses: ['running', 'suspended'] }), widened to the full catalog only when durable retention needs terminal metas), never overlaps sweeps, keys its suspended skip cache and its poison set to the run's generation (RunMeta.genesis) so a deleteRun and recreate of the same runId is picked instead of skipped, and drops skip and poison entries for runIds that left the candidate set. Point lookups in resume, inspect, the kb gate, and the server status path go through the store's exact lookup capability when present.

Patch Changes

1.25.0

Patch Changes

  • 74851ed: CLI diagnostics stop echoing --args values and sanitize every dynamic value they embed. The invalid-JSON and non-canonical-JSON refusals now name the failure class and the way out without repeating the supplied value (workflow args may carry private data, and stderr routinely lands in CI logs). Every typed CLI error prints through one site that strips terminal control sequences, and the plain-output run renderers (outcome reports, dry-run previews, suspension prompts, resume warnings, plan lint diagnostics) sanitize untrusted text the same way the live TUI already does, so a hostile runId, suspension key, provider error message, or model ref cannot recolor, retitle, or rewrite the terminal. Exit semantics are unchanged.

1.24.1

Patch Changes

  • 0bb14db: Close a resume args-gate bypass through JSON numeric overflow (v1.24.0 review P2-1). A --args value that overflowed JavaScript's finite range (1e400 parses to Infinity) could not be canonicalized, so genesis recorded the args binding with argsProvided but no hash, and a later resume supplying entirely different args slipped past the gate with only a warning, silently changing the logical run and re-paying every args-dependent call. rulvar run and rulvar resume now reject non-finite (non-JCS) --args at parse time, before any config, store, or adapter loads. Independently, when a run recorded argsProvided without a verifiable hash (an in-process host that started it with genuinely non-JCS args), a resume supplying args is now a typed refusal unless you pass --allow-args-change, instead of the previous soft warning. Core engine policy is unchanged: in-process hosts may still pass non-JCS args and record presence without a hash.
  • Updated dependencies [0bb14db]

1.24.0

Minor Changes

  • 2b033e8: Make rulvar resume safe against forgotten or changed args and add a --dry-run preview (the v1.23.0 review: a resume without --args silently changed the logical run and paid again). The resume grammar gains --dry-run and --allow-args-change. Before the engine starts, the CLI verifies the supplied args against the genesis binding recorded in RunMeta: forgetting --args on a run started with them, adding them to a run started without them, or supplying a different value is a typed refusal naming --allow-args-change as the deliberate override; runs recorded before v1.24.0 carry no binding and demand explicit --args or the override. --dry-run passes the engine's replay-strict mode through and prints the resume preview (hits, misses, reruns, skipped, orphaned effect roots, invalid resolutions) plus what the run would settle as, with zero journal or meta writes and zero adapter calls; a preview that reaches work needing a live call reports the stopping point and exits 0. rulvar inspect now prints the args binding.

Patch Changes

1.23.0

Patch Changes

  • 1f9c272: The renderers' remaining unsanitized paths and the malformed-event gaps (v1.22.0 review P2-2, P2-3).

    • progress(): the error text surfaced when the SOURCE fails (a rejected RunHandle.result, a rejected Promise<RunHandle>, a throwing iterable) went to the sink raw; a crafted rejection could inject ANSI, forge lines, and leak a key-shaped fragment. Every catch path now routes through one helper that secret-masks FIRST (the thrown value never crossed the event masking boundary) and terminal-sanitizes second; lines mode prints the notice as its own sanitized line instead of dropping it.
    • Malformed recognized events from a raw iterable can no longer stop a view: every dynamic field in the progress() reducer, its lines formatter, renderProgress, and the CLI renderEventLine is read through typed guards (a hostile object with a throwing toString included), a backstop catch skips a bad event with a bounded diagnostic carrying no untrusted data, and the stream continues. The v1.22.0 claim of full defensive reads was narrower in reality (agent:stream without delta or phase:start without phase stopped the raw-iterable view); it is true now and pinned by a table-driven test over every consumed type.
    • posIntOption wording: a below-minimum value CLAMPS to the minimum (only non-finite values fall back to the default); the JSDoc said "falls back" for both.
    • @rulvar/cli build config migrates the deprecated tsdown external option to deps.neverBundle; the packed dist keeps the companion specifiers external, byte-for-same behavior.
  • Updated dependencies [1f9c272]

1.22.0

Patch Changes

  • 77b554f: Sanitize the CLI event line renderer (renderEventLine, used by attachProgress): every composed line passes through the shared sanitizeTerminalText before it reaches the terminal, so an untrusted provider/tool/log string in an event can no longer inject a control sequence or a second physical line into CLI output (v1.21.0 review P2-1). Clean lines stay byte-identical.
  • Updated dependencies [77b554f]

1.21.0

Patch Changes

1.20.0

Patch Changes

1.19.0

Patch Changes

  • Updated dependencies [8cc9a9c]
  • Updated dependencies [8cc9a9c]
  • Updated dependencies [8cc9a9c]

1.18.0

Minor Changes

  • 943962d: Sweep and suite reports are now monotone: paid evidence survives every budget refusal. Previously runSweepMatrix caught the envelope's SweepBudgetError around a whole cell and replaced it with an empty envelopeExhausted row, erasing already completed targets and their cost; a judge refused by the envelope erased the paid successful target the same way; and a judge run that hit its own per-run ceiling threw EvalJudgeError out of the entire matrix, losing every accumulated cell.

    Now: runEvalSuite returns partial results with plannedN, completedN, and a typed refusal marker instead of throwing when the envelope refuses a target; a judge budget event (per-run ceiling exhaustion or envelope refusal) normalizes into the owning EvalCaseResult as incomplete: { reason: 'judge-exhausted' | 'judge-refused' } with the failing judge run's actual cost counted, while non-budget grader errors still throw; SweepCellReport gains plannedN, judgeIncompleteRuns, incompleteReason, and refusedRunLabel, and any incomplete cell (n < plannedN, exhausted targets, unfinished judges, or an envelope refusal) emits no claim; runCanary records an envelope-refused probe as status: 'refused' and keeps walking, so completed probe evidence survives and allOk stays the drift-flip gate; EvalJudgeError carries costUsd. The kb sweep human renderer prints incomplete cells explicitly (INCOMPLETE: envelope refused ... after N of M case(s), unfinished-judge counts, refused-probe counts) instead of pretending nothing ran.

    Migration: runSweepMatrix and runEvalSuite no longer throw SweepBudgetError for refused targets or judges; read EvalSuiteResult.refusal, EvalCaseResult.incomplete, and the new cell fields instead. Cells now always carry plannedN.

Patch Changes

1.17.0

Minor Changes

  • 7909b6b: Every paid CLI surface is now budget-bounded, and the grammar ignores nothing (the v1.16.2 review P1-1, P1-2, P2-1, P3-1).

    • rulvar plan gained separate immutable ceilings for its two paid runs: --planning-budget-usd N freezes as the planning run's B0 at its journal's genesis (PlanOptions.run.budgetUsd) and --budget-usd N caps the execution run exactly like rulvar run. A machine-written workflow never runs unbounded silently: missing ceilings fail loudly unless --allow-unbounded waives them explicitly, and --dry-run beside --budget-usd is a contradiction, not an ignorable leftover.
    • rulvar kb sweep requires kbSweep.budgets ({ targetUsd, judgeUsd, canaryUsd, maxTotalUsd }) or an explicit kbSweep.allowUnbounded: true: every target, judge, and canary run carries an immutable per-run ceiling, the whole sweep authorizes against the debit-only maxTotalUsd envelope (falsification pool growth included), the worst-case authorized spend prints before the first provider call, and envelope-refused or ceiling-exhausted cells report honestly and emit no claim. Canary drift flips claims stale only when every probe settled ok, so a budget-starved or transiently failing probe can never blame the model.
    • The canonical grammar is one data structure now: --help, every per-command usage error, and the documented grammar block render from it and are locked together by tests. Nothing accepted is ignored: resume rejects --budget-usd and --profile at parse time (the ceiling is immutable from genesis by the documented budget invariant), every command enforces exact positional arity, duplicate value flags fail, and unknown options report as ConfigError usage lines instead of raw parseArgs stack traces. All rejections happen before any config, store, or adapter loads, with zero provider calls and byte-identical journals.

Patch Changes

1.16.2

Patch Changes

  • 9f07130: The published CLI now actually loads its command-local optional companions. The build had been inlining @rulvar/planner, @rulvar/plan, and @rulvar/evals into local chunks, so the packed rulvar plan failed with a false "install @rulvar/planner" even with the planner installed (the inlined eslint broke at load time and a bare catch reported it as missing), while rulvar kb inbox ran without @rulvar/plan installed, against the documented dependency contract. The three companions are external again (dist keeps the real import("@rulvar/...") specifiers, the planner's worker sandbox loads from the installed package, and the CLI dist shrinks from megabytes to about 82 kB), and import failures are classified: only a genuine module-not-found for the requested companion produces the install hint, while an installed companion that fails to initialize surfaces its own error with the cause preserved. A packed-consumer E2E matrix (scripts/cli-smoke.mjs) now gates releases on exactly this behavior.

1.16.1

Patch Changes

  • fac1ecc: Mark eslint's optional TypeScript-config loader jiti as external in the CLI bundle. The bundled eslint (pulled in through @rulvar/planner's programmatic Linter) lazily imports jiti only on its config-file loading path, which the CLI never executes; the import now stays an import instead of producing UNRESOLVED_IMPORT build warnings. No runtime behavior change.

1.16.0

Patch Changes

1.15.0

Patch Changes

1.14.0

Patch Changes

1.13.0

Patch Changes

1.12.0

Patch Changes

1.11.0

Patch Changes

1.10.0

Patch Changes

1.9.0

Patch Changes

1.8.0

Patch Changes

  • Updated dependencies [25724b5]
  • Updated dependencies [57ea1de]
  • Updated dependencies [7884ec5]
  • Updated dependencies [52db30d]

1.7.0

Patch Changes

  • Updated dependencies [45285aa]
  • Updated dependencies [2f20d1d]
  • Updated dependencies [22f65a8]
  • Updated dependencies [2ddfa29]
  • Updated dependencies [2abd9c2]
  • Updated dependencies [1c1175d]

1.6.0

Patch Changes

  • da4dbad: Write the product name as Rulvar in prose: package READMEs, npm descriptions, and the documentation site now capitalize the brand. Identifiers keep their exact casing, so package names, the rulvar binary, rulvar.config.mjs, the .rulvar store directory, the rulvar.* OTel attributes, and every URL are unchanged. Documentation and metadata only; no runtime behaviour changes.
  • Updated dependencies [da4dbad]
  • Updated dependencies [487da86]
  • Updated dependencies [df416fc]
  • Updated dependencies [a737810]
  • Updated dependencies [9eb66b4]

1.5.2

Patch Changes

1.5.1

Patch Changes

1.5.0

Patch Changes

1.4.0

Minor Changes

  • c4f563d: Production readiness fixes from the July 2026 full audit.

    • The budgetUsd ceiling now survives resume: the engine records it in RunMeta.budgetUsd and restores it on every resume, so the replayed spend counts against the original invocation's bound and ResumeOptions still exposes no way to raise it. Journals written before the field existed (or read through a store that drops optional RunMeta fields) resume uncapped, exactly as before; the conformance kit gains a round-trip check so custom stores cannot drop the field silently.

    • spawn:rejected and resolution:applied / resolution:superseded are now emitted: live admission rejections carry the rejection code, agentType, and the journaled decision entryRef (absent only for pre-admission config gates), and live resolution attempts report winning or losing the first-closing-wins fold. spawn:admitted now carries the decision entryRef and the admitting verdict arm. The orchestrator:budget union member now types the two payload shapes actually emitted; journal:compat stays declared but unemitted (the scan runs before a run's event stream exists) and its TSDoc says so.

    • toOtel implements real parent-child span nesting when contextApi and setSpan are passed; without them spans stay flat but attributed.

    • 'readonly' isolation now compiles a deny rule for tools declaring risk write or destructive into the spawn's permission chain, exactly as the tools guide documents; read tools and other isolation modes are unaffected.

    • VCR replay() refuses a cassette recorded outside the engine's hashVersion support window ([CURRENT-1, CURRENT]) with a typed ConfigError instead of silently drifting; in-window cassettes replay as before.

    • InMemoryStore accepts { quiet: true } to opt out of the durability warning, and the warning text now states the precise truth: nothing survives a process exit and cross-process resume is impossible (same-process resume of a kept instance works). createTestEngine constructs its store quietly, so the blessed offline tier no longer prints a misleading warning.

    • The bare Date.now() / Math.random() development warnings no longer blame workflow code for calls that originate in library internals (the engine's own retry jitter, provider SDKs): the retry jitter uses a natively captured Math.random, and the in-process guard skips callers that live under node_modules.

    • rulvar run --profile now applies the profile's per-role effort hints: entries in defaults.routing that carry no effort are seeded from RunProfile.effortByRole (an explicit host effort always wins; ladder entries and unrouted roles stay untouched).

    • rulvar --help documents the shipped kb inbox and kb gate subcommands.

    • The unscoped rulvar pointer package ships TypeScript declarations (index.d.ts with a types export condition), so strict TypeScript projects can import the bare name; the install smoke gate now packs and checks the pointer alongside the umbrella.

Patch Changes

1.3.2

Patch Changes

  • ddef383: Every published package now ships a README, so its npm page states what the package is, how it installs, and where the documentation lives (npm includes README.md in the tarball regardless of the files allowlist, so no manifest changes are involved; @rulvar/compat gains its README on its own next release). Alongside, the repository-level pages are refreshed to the current project state: the root README is rewritten around the never-pay-twice pitch with a runnable quickstart condensation and the full package table, CONTRIBUTING.md lists the complete PR gate set, the examples README drops retired-spec citations for live docs.rulvar.com links and documents the dogfood journal replay, and the pointer README gets the same treatment.
  • Updated dependencies [ddef383]

1.3.1

Patch Changes

  • 7d1552e: Runtime message strings no longer cite the retired internal specification set: error and warning messages, validation issues, and the CLI help text drop the dangling docs/NN, section ... references, pointing at https://docs.rulvar.com pages where a pointer earns its place (the CLI help header, tool naming, toolset registries, bare resume). The umbrella package description sheds the naming-contingency note: the unscoped alias is published and owned. Three strings embedded in frozen recordings stay byte-identical on purpose (the no-progress abort reason and two testing-internal recorder strings), as does the byte-locked golden-fold fixture. Test-file comments lose their citations too; test titles are unchanged.
  • Updated dependencies [7d1552e]

1.3.0

Minor Changes

  • 969974f: rulvar kb inbox (M12-T03): aggregates kb_propose-born proposals from finished runs through the RunLedger fold behind the LedgerExport seam. Matching (subject, taskClass, polarity) triples group for display ONLY (the command writes nothing, authorizes no spend and schedules no sweeps); each proposal renders with full provenance (initiating run identity, proposal entryRef, lineage, tier, trigger, evidence refs) plus the typed template statement a gated claim would carry; proposals of runs finished more than fourteen days ago expire out of the view. This is the human review surface, so the quarantined note and concrete model names render here verbatim, exactly like kb list.
  • 64aff88: rulvar kb gate (M12-T04, the closing task of ModelKnowledge phase 3): the human gate flow turning one inbox proposal into a human-editorial claim. The attribution attestation is mandatory by construction (without --ruled-out over the closed checklist the GateRecord does not assemble and nothing is written; contrast evidence rides --contrast-run or --contrast-eval); the born claim carries the typed template statement (never the quarantined note), origin provenance back to the proposing run and entry, evidence resolving into that run's journal, and the editorial TTL. The commit is CAS against the per-project rulvar.models.json, whose git review is the authenticating gate. Non-proposal entries, expired proposals (fourteen days from the run's terminal updatedAt), running runs and already-gated proposals reject with typed errors.

Patch Changes

1.2.0

Patch Changes

  • 154507b: TSDoc and inline comments no longer cite the retired internal specification set (the pre-docs-site docs/NN, section ... references). The citations either became links to the public documentation at docs.rulvar.com or were dropped where the comment already carried the rule; traceability markers (DEF-n, XF-nn, FR-nnn, OQ-nn, W-nnn) are untouched. Comment-only change: no runtime behavior, no API shapes, and no runtime message strings were modified; the frozen golden-fold fixture is byte-identical.
  • Updated dependencies [3bfaec0]
  • Updated dependencies [890f42c]
  • Updated dependencies [154507b]

1.1.0

Patch Changes

1.0.0

Minor Changes

  • 93eae2c: M10-T04: rulvar kb list (docs/05, section "Read path"; docs/06, section 10.5). The second consumption path: claims of the per-project store (./rulvar.models.json) render with full provenance for the humans who author ladders, floors, and profiles: author and gate identity, evidence refs (journal seqs and eval reports), metrics when present, supersede chains, proposal origin, and the TTL state (holds or EXPIRED) per the docs/05 decay table. No run and no pin are involved, so the maintenance view names models verbatim; only in-run cards are nameless. The grammar members kb inbox (phase 3, M12) and kb sweep (phase 2, M11) fail loudly naming their phases until they ship.

  • fef6263: M11-T05: rulvar kb sweep (docs/05, section "Grounding and decay"). Falsification sweeps run manually, from CI, or from a user cron, never engine-scheduled, configured by the kbSweep section of rulvar.config.mjs (committerId, the FIXED model pool, taskClass-tagged eval cases, optional thresholds and canary probes; @rulvar/evals loads dynamically like @rulvar/planner does for plan).

    • The falsification guarantee: the matrix is the configured pool UNIONED with every model carrying an active, unexpired negative claim, plus the re-measurement queue (expired active eval claims); the pool renders with each member's origin.
    • With canary probes configured, every pool member fingerprints BEFORE measurement and drift flips its eval claims to stale in place; the sweep then re-measures and commits threshold-crossing claims through the eval-committer identity, reporting cells, emitted claims, and the committed store version.

Patch Changes

  • Updated dependencies [0e0b569]
  • Updated dependencies [b28b7a3]
  • Updated dependencies [b53a89e]
  • Updated dependencies [4454175]
  • Updated dependencies [6599ca8]
  • Updated dependencies [6649e5f]
  • Updated dependencies [fd2f83b]
  • Updated dependencies [01d6b2d]
  • Updated dependencies [9a20dbb]
  • Updated dependencies [0fbe7ea]
  • Updated dependencies [ebe0abc]
  • Updated dependencies [a3079d0]
  • Updated dependencies [596a39b]
  • Updated dependencies [464ab6e]

0.9.0

Minor Changes

  • 65c7b2c: M8-T01: createServer, the HTTP shell (docs/02 section 8.2; FR-702), plus the Engine.stores seam it stands on (docs/06 10.2, M8 entry amendment).

    • @rulvar/cli: createServer({ engine, workflows }) returns { fetch(req: Request): Promise<Response> } with the five canonical routes: POST /runs (start a registered workflow), GET /runs/:id (status and outcome), GET /runs/:id/events (SSE; Last-Event-ID maps to the event seq, replay is at-least-once and consumers deduplicate on replayed), POST /runs/:id/external/:key (programmatic resolution, by: 'external'; a run that settled suspended in-process auto-resumes; a run not live in this process gets the documented offline append under a lease where the store is leasable, and resumes on a worker), GET /runs/:id/cost (the settled in-process CostReport, or the pure journal fold priced by the optional priceUsd). Authentication stays host middleware (docs/14, OQ-16).
    • @rulvar/core: the Engine interface gains the readonly stores accessor exposing the configured journal and transcript stores; exactly the instances createEngine received (or defaulted), no store contract widens.
    • @rulvar/testing: createTestEngine forwards the new stores accessor.
  • a2a3243: M8-T02: createWorker, the queue shell (docs/02 section 8.3; FR-703), plus the two queue seams it stands on (docs/06 10.2 and docs/03 12.3, M8 entry amendment).

    • @rulvar/cli: createWorker(engine, { store: LeasableStore, concurrency? }) leases resumable and suspended runs via acquire/renew/release with fencing epochs (renew cadence ttl/3; Appendix A reference ttl 60000 ms; concurrency default 1). A store without lease capability is a typed ConfigError at start, never a silent split-brain; leasing a store other than engine.stores.journal is equally a ConfigError. DEF-6 repeats at acquire: a journal outside the hashVersion window releases the lease and poisons the run for this worker. Stateless workers call bare engine.resume with the lease; unchanged suspended runs are skipped until their journal grows; queue semantics stay honestly at-least-once with deduplication by the journal. The OQ-21 residual (original in-process args are not journaled) is bridged by the optional argsFor hook.
    • @rulvar/core: ResumeOptions.lease carries the worker's lease through the kernel's single append site, so a stale writer's appends are rejected by the fencing epoch and never become visible (lease theft impossible by construction); bare engine.resume(runId) now falls back from the persisted CompiledWorkflow source to defaults.workflows[workflowName] (the registry the queue worker resolves through, docs/06 10.4); the Replayer accepts the lease option.
  • f920013: M8-T03: the multi-process seam soak and the queue-failover-during-forced-finish cassette (the DEF-7 final cassette; docs/09 sections 6.9 and 6.10; docs/10 section 3.9 exit criteria).

    • @rulvar/plan: the public runQueueFailoverDuringForcedFinish cassette runner: worker A loses its lease strictly between the cap decision and the final wake; worker B reclaims with a bumped fencing epoch and rolls the forced finish forward. The stale writer's appends are rejected and invisible, exactly one cap decision exists, finalization is paid once. The LeasableStore is injected (QueueFailoverDeps.makeStore) so the package stays core-only; the replay test and the record script supply the reference SqliteStore.
    • @rulvar/cli: the multi-process-fencing-soak harness: two workers over one SqliteStore file with kill/failover across the suspension, plan-revision, and forced-finish boundaries; every round asserts zero split-brain and zero double pay. Worker hardening: a failed renew now frees the concurrency slot immediately (a stale run whose landings all reject may never settle; fencing, not the stale process's cooperation, protects the journal).
    • Repo: cassettes/queue-failover-during-forced-finish.json recorded and frozen (double-run agreement; scripts/record-m8-cassettes.mjs); the queue-mode limitation stays documented (no distributed cross-process rate limiter, EXC-14/OQ-17).
  • ebc8101: M8-T04: the redaction and retention interim rules executed (docs/14 OQ-20 and OQ-22; docs/09 section 8 rewritten to the executed state; docs/03 12.4 and 12.8; docs/06 10.1 and 10.2 amendments).

    • @rulvar/core: the L0 SerializationHook (createEngine({ serialization })): redact/encrypt at the append/put boundaries, symmetric on load/get, applied by wrapping the stores so Engine.stores exposes the one policy point; kernel ordering fields are drift-checked with a loud ConfigError. Default key masking at the telemetry boundary: every emitted WorkflowEvent passes maskSecrets (provider keys, PATs, bearer tokens, JWTs, private-key blocks become [masked-secret]); opt out via redaction: { maskEvents: false }; never touches the journal. Retention: TranscriptStore.delete(ref) joins the SPI (missing ref is a no-op; InMemory and File stores implement it), Engine.deleteRun(runId) cascades blob deletion before the journal (no orphan transcripts), and Engine.pruneRun(runId) deletes checkpoint blobs of ok-terminal attempts that nothing else references (parked, cancelled, escalated, and hanging attempts keep theirs).
    • @rulvar/cli: createServer and createWorker take the opt-in retention predicate over RunMeta (the server applies it at terminal settles, the worker during sweeps under a brief lease); the OTel exporter masks string span attributes with the same policy, defense in depth over the already conservative attribute content policy.
    • @rulvar/testing: createTestEngine forwards deleteRun/pruneRun.

Patch Changes

  • Updated dependencies [84f94d4]
  • Updated dependencies [65c7b2c]
  • Updated dependencies [a2a3243]
  • Updated dependencies [ebc8101]

0.8.0

Patch Changes

  • Updated dependencies [85d55cf]
  • Updated dependencies [b88c9e3]
  • Updated dependencies [f3c4613]
  • Updated dependencies [a41c20f]
  • Updated dependencies [f4e70be]
  • Updated dependencies [75d1646]
  • Updated dependencies [0627413]
  • Updated dependencies [55c0f87]
  • Updated dependencies [fd33871]
  • Updated dependencies [e70e7f4]
  • Updated dependencies [bc9c903]

0.7.0

Minor Changes

  • 10b45f1: M6-T11: the rulvar plan command and the M6 gating cassettes. rulvar plan "<goal>" [--dry-run] (the canonical grammar) loads @rulvar/planner DYNAMICALLY (the CLI's static dependency stays @rulvar/core; a missing install is a clear error), plans against the host-config engine, prints the accepted script plus its advisory diagnostics, and runs it in the worker sandbox unless --dry-run. The three docs/09 6.10 gating cassettes are recorded on the FakeAdapter and committed under the frozen-fixture lock with exported scenario builders shared by the recorder script and the replay tests: sandbox-determinism (two fresh runs of one CompiledWorkflow produce byte-identical normalized journals matching the cassette), planner-self-repair (the failing draft round-trips through the JSON-diagnostics repair, re-planning from the committed journal is free, and the accepted script executes deterministically in the sandbox), and orchestrator-crash-resume (the committed pre-crash journal plus boundary checkpoints resume with zero re-paid spawns, no duplicate spawn decisions, and byte-stable handles).

Patch Changes

  • 9f000a7: Drop the @rulvar/planner peer declaration from the CLI: the plan command loads the planner DYNAMICALLY and reports a clear error when it is not installed, and a workspace peer dependency would major-cascade the whole fixed group on every planner bump under the changesets peer-dependents rule (0.6.0 would have released as 1.0.0 instead of 0.7.0).
  • Updated dependencies [fd1d06c]
  • Updated dependencies [6fcf296]
  • Updated dependencies [dcc97a9]
  • Updated dependencies [434dc83]
  • Updated dependencies [03173c1]
  • Updated dependencies [11c0afc]

0.6.0

Minor Changes

  • fa05007: M5-T01 workflow registry and the @rulvar/cli base.

    • @rulvar/core gains the per-engine WorkflowRegistry type and defaults.workflows on createEngine (docs/06 section 10.4): an explicit first-class value, no module-level registry; shells resolve by-name runs against it (ctx.workflow's string form arrives M6, the queue worker M8).
    • Spec-conformance fix: the M4-T09 quality floors option moves from the createEngine top level to its canonical home defaults.roleFloors (docs/06 section 10.1). Update createEngine({ floors }) call sites to createEngine({ defaults: { roleFloors } }).
    • @rulvar/cli ships its first real surface: the canonical grammar rulvar run <file|name> [--args JSON] [--store PATH] [--budget-usd N], rulvar resume <runId> [--args JSON] [--store PATH], rulvar runs ls [--store PATH], rulvar inspect &lt;runId&gt; [--store PATH] (no aliases), a line-oriented TUI progress renderer over the event stream, and interactive resolution of suspended approvals and externals (EOF leaves the run suspended, never errors). Engine assembly follows the host-config convention: rulvar.config.mjs default-exports { engineOptions?, workflows? }, a workflow module may export workflow/engineOptions/workflows, and --store selects the JsonlFileStore directory (default .rulvar), so the CLI itself depends only on @rulvar/core. The rulvar bin is included; the resume/inspect grammar amendment (--args re-supply, --store symmetry) is recorded in docs/06 section 10.5.
  • 9234dc8: M5-T03 cost reports. The CostReport builder moves to its own module (engine/cost-report.ts) and report totals become the LEDGER FOLD totals at settle: RunOutcome.usage and cost.totalUsd are computed from the journal's terminal entries (the same summation the kernel budget seed uses), so report totals equal ledger fold totals exactly, live and across resume, by construction. The new costReportFromJournal(entries, priceUsd) is the pure fold for STORED runs: byModel and totals from terminal servedBy with abandoned subtrees contributing zero; phase, agentType, and role attribution are live-run facts that entries do not carry (byRole and the orchestrator block complete in M7 per DEF-7). Unpriced models keep surfacing, never as silent zeros. rulvar inspect gains the cost view (total, byModel, unpriced) over the config-assembled price function (table wins over caps.pricing), and live run output prints the byModel/byPhase buckets.

  • 8a41656: M5-T07 RunProfile presets and M5-T08 OTel exporter.

    • engine/run-profiles.ts: RUN_PROFILES (fast/standard/deep/ultra) and runProfile(name) ship the presets as pure DATA, bundles of per-role effort hints, per-run concurrency, budget, permission preset, and spawn limits, with no functions and no named model strings (named strong defaults stay in the umbrella). They are never engine semantics: a source-scan test asserts the engine has zero branches keyed on profile names. rulvar run --profile <name> applies the chosen profile UNDER the host's own engine options (host always wins; the engine then sees only ordinary options), compiling the profile's permission preset into the engine deny/ask layers as data.
    • @rulvar/cli gains toOtel(run, tracer): it maps a settled run's spanId tree 1:1 onto OpenTelemetry spans (run > phase > agent > tool > child), with rulvar.* and gen_ai.* attributes, start/end timestamps from the lifecycle events, and payload-only events attached as span events. Prompts, completions, and tool payloads are NEVER exported; replayed events never create duplicate spans. @opentelemetry/api ^1.9 is an optional peer dependency and the exporter is typed against a minimal structural TracerLike, so an absent OTel package never breaks the CLI.

Patch Changes

  • 5c8865d: M5 exit criterion coverage: prove the CLI works end to end against SqliteStore, not only JsonlFileStore (docs/10, section 3.6). A host config that supplies a SqliteStore as engineOptions.stores.journal is honored by the CLI's engine assembly (JsonlFileStore is only the default fallback), so run/suspend, runs ls, resume, and inspect all round-trip against sqlite through the same command paths. Added as a CLI e2e test.
  • Updated dependencies [fa05007]
  • Updated dependencies [9234dc8]
  • Updated dependencies [644512c]
  • Updated dependencies [8a41656]
  • Updated dependencies [02f7f7a]

0.5.0

Patch Changes

  • Updated dependencies [ac274f4]
  • Updated dependencies [5735d92]
  • Updated dependencies [46ca98e]
  • Updated dependencies [8ae129e]
  • Updated dependencies [d1c4525]
  • Updated dependencies [b840aba]

0.4.0

Patch Changes

  • Updated dependencies [dfe03b5]
  • Updated dependencies [d2089a7]
  • Updated dependencies [3f60234]
  • Updated dependencies [f668890]
  • Updated dependencies [16d7aa6]
  • Updated dependencies [6513ce8]
  • Updated dependencies [7dad493]
  • Updated dependencies [2bbf180]

0.3.0

Patch Changes

  • Updated dependencies [43444f6]
  • Updated dependencies [279881b]
  • Updated dependencies [9fd0966]
  • Updated dependencies [24ebadf]
  • Updated dependencies [a1b35d3]
  • Updated dependencies [18a5821]

0.2.0

Patch Changes

  • Updated dependencies [c24228d]
  • Updated dependencies [c50871e]
  • Updated dependencies [1af8fb9]
  • Updated dependencies [1fe0249]
  • Updated dependencies [5c4fc32]

0.1.0

Minor Changes

  • f4e2be9: M0 repo bootstrap (v0.1.0, docs/10-implementation-plan.md section "M0"): monorepo scaffold on the committed toolchain (pnpm 11 workspaces with catalogs, TypeScript 6.0, tsdown, Vitest 4, ESLint 9 flat config, Turborepo 2, changesets fixed mode, npm trusted publishing), the docs/ canon as single source of truth, the L0 contracts skeleton in @rulvar/core, and the vendored dependencies (StandardSchemaV1/StandardJSONSchemaV1 types, the @cfworker/json-schema lineage validator subset, a first-party monotonic ULID). Placeholder scaffolds only: no public API ships in this release.

Patch Changes

@rulvar/compat

Independently versioned (docs/12, section "Exemptions"): releases are deliberate manual events, never lockstep bumps; this changelog is maintained by hand.

0.1.1

  • Packaging only: the published artifact now ships a README (purpose, install, extraDerivers usage, documentation links). dist is byte-identical to 0.1.0; no profile, export, or contract changed. The immutability manifest re-freezes on 0.1.1 after publish (node scripts/compat-immutability.mjs --update).

0.1.0

  • M2-T05: extraDerivers plumbing plus the synthetic hashVersion 0 deriver for the reject-version-too-old cassette. No real profile has aged out of the support window yet.

@rulvar/core

1.252.0

Minor Changes

  • 52d807f: The direct dispatch records what it committed, and announces it (RV4802, RV4806). The dispatch entry's value part now carries reserveUsd, the committed clamp of its admission, and a journaled rerun re-admits that RECORDED number instead of re-pricing history: the budgets doctrine (reserves are recovered, never re-estimated) extended to the direct ctx.agent path, with the recompute kept as the fallback for journals from before the field. Plain direct dispatches now emit spawn:admitted (entryRef is the dispatch entry, additive reserveUsd, the recovered re-admission marked replayed) and a budget refusal emits spawn:rejected, closing the observability asymmetry the ninth experiment surfaced; dispatches tracked by an orchestrating layer (spawn tools, the extension seam, the coordinator) or by a lineage decision keep their single existing announcement. spawn:admitted events gain optional reserveUsd (the ctx.workflow path reports its verdict reserve) and logicalTaskId becomes optional (absent on direct budget admissions). The cassette corpus is re-recorded and the frozen-fixture lock refreshed through its ceremony (hashVersion-bump): the dispatch VALUE grew one recorded field while the identity profile and every hash rule stay untouched, so replay identity is unchanged and the re-recorded fixtures are the same scenarios with the committed reserve visible on their dispatch rows.
  • a7e589d: The durable admission bracket hardens on every seam the ninth experiment named (RV4804). The queued wait honors a verdict's retryAfterMs verbatim for its next sleep (pollMs stays the fallback cadence) and ends with the RUN: the run's cancel signal rides into the wait, so host abort and the deadline stop the polling, cancel the ticket best effort, and hand the run to its own cancellation machinery, where before a cancelled run camped in the queue forever. Renew failures are announced, never fatal: the first failure warns, a verify recover that no longer answers granted emits the new admission:lease-lost event once (the scheduler expired the grant and may re-admit the capacity while the holder is alive), and the run continues, because the wire quota still gates every dispatch and the settle release is idempotent. The postgres scheduler takes its schema-scoped advisory lock under a lock_timeout bound (lockTimeoutMs, default 10 seconds, validated typed): a holder that hangs mid-transaction used to block every lifecycle call of the whole fleet forever; past the bound the call refuses with the typed retryable LeaseHeldError instead of camping.
  • 76e95eb: The await digest carries the child's tool budget pressure (RV4807). The ninth experiment's durability specialist starved at 30 of 30 tool calls and the coordinator could not see it: the aggregate reached only the synthesis policy facts, so nothing respawned the specialist or accepted the degradation knowingly. TaskDigest now folds the REPLAY-STABLE subset of the child's toolBudget (used and cap, the pair the terminal journals; the derived capHit, present and true when the executed-call cap was reached, whatever the status says; extensionsGranted and finalizationWindowEntered from their decision entries); the live-only fidelity fields stay out so a digest folds byte-identically live and resumed, and a child without a tool budget folds byte for byte as before.

Patch Changes

  • 3ccb6cf: The direct dispatch reserve bracket (RV4801, the ninth experiment P0). Admission of a direct ctx.agent commits the allowance clamped reserve, but the settle released the RAW estimate; the chain release floors at zero per account, so one clamped child's settle erased SIBLING reservations on shared ancestor accounts, and projected admission then admitted new spawns against money already promised to live children. The settle now releases exactly the committed clamp, and the release rides a finally spanning admission to settle, so a throw between them (the worktree acquire, the dispatch append, the loop itself) returns the reserve instead of parking it for the rest of the run. Regression tests pin the sibling survival, the throw path, the journaled rerun, and the ledger arithmetic; two mutation probes hold the released amount and the finally placement.
  • 517ed00: The host surface hardens on two seams (RV4803, RV4805). The price table is now SNAPSHOTTED at createEngine: pricing resolution used to read the caller's live object on every debit, so a host mutating its table mid-run silently changed what wires cost after the strict gates had judged the original; the clone severs the alias (a rates update is a new engine with a bumped pricingVersion), and a table the structured clone cannot take refuses typed at construction. The HTTP shell's POST /runs body gains the regulated posture subset of RunOptions (budgetPolicy, maxInFlightExposureUsd, configFingerprint, scope, scopePolicy), so a remote caller can start a run under the immutable lifetime ceiling and the bounded execution scope; authentication, price tables, adapters, stores, and secrets stay with the host process by doctrine and never enter the body.

1.251.0

Minor Changes

  • 5982be8: The anchor grounding lint and the grounding windows (RV4601, the seventh comparison experiment's P1.3 remainder). anchorGroundingValidator is a zero cost finish validator that catches physically valid citations pointing at the WRONG LINE, the class both of the seventh candidate's audited defects lived in: pointer/package.json:10 (the exports block) cited for a caret dependency at line 23, and packages/rulvar/package.json:2 (the name line) cited for dependencies at lines 32..34; both resolved, neither sentence asserted an inline code value, so citationTargetsValidator and citedValueValidator were structurally blind. The lint extracts the claim's identifier vocabulary (inline code spans, scoped packages, dotted, snake and camel identifiers, the caret and tilde written as words), resolves each anchor to its logical unit (citationUnitExcerptOf with a grace tail below; a .json anchor takes its structural brace block instead, because the generous paragraph unit would swallow the very lines the citation should have named), lints compound sentences per anchor against the nearest claim clause and then once as a whole, and refuses only when a deciding token is absent from the resolved window yet present elsewhere in the cited file, naming the exact lines that carry it. Plain words, identity spans, path tokens, and tokens absent from the whole file never flag; on the seventh corpus the shipped heuristic flags exactly the two wrong anchors with their exact line suggestions and nothing else. anchorGroundingFindingsOf exports the engine for harnesses. Beside it, a 'repair' round under citationAudit.resolver: 2 now carries the CITATION GROUNDING: block (citationGroundingLines, capped at 6 anchors and 4800 characters): the resolved unit of each judged anchor, recomputed from the pure snapshot resolver at prompt build, so the composer repairs a citation against the bytes the judge actually read instead of moving anchors blind, and a resumed round rebuilds byte identical windows with nothing new persisted. Probes pin that words never decide, that a flag needs somewhere to point, the structural json block, the grounding budget, and the resolver 2 gate.
  • b3e465a: Precise hash and counter namespaces (RV4604, the seventh comparison experiment's P2.2 remainder). Every hash on the lineage and provenance surfaces is one recipe, sha256 over the JCS canonical value, and the seventh experiment's provenance script had to rediscover that by trial because the bare names said nothing; the invoice's 16 logical calls beside 109 wire fetches were reconciled by hand for the same reason. The precise names now ride beside the bare ones, same hex, additive everywhere: judgedJcsSha256 on the claim meta, auditedJcsSha256 on the audit meta, and judgedDocumentJcsSha256 on the semantic terminal verdict, whose bare finalHash collides with draftToFinal.finalHash while meaning the judged document. On the counter side logicalRunTelemetry now carries adapterFetches, the sum of every provider call decision's absorbed wireRequests (absent reads one) beside the decision count logicalWireRequests, plus perSegment[].adapterFetches naming which segment actually paid for them (a pure replay segment reads 0); rulvar inspect prints both counters by name on the logical wires line. Probes pin the absorption sum, both meta twins, and the verdict's referent naming.
  • c4e5d6a: The preflight honesty pair (RV4702, RV4701; the eighth comparison experiment's first run). RV4702, the child-ceiling feasibility line: under budget.estIsCeiling the spawn's declared estimate is the child's hard ceiling (the explicit spawn budget wins), and that run's 1.35 ceiling deterministically starved its child's finalize dispatch after an honest loop; preflight admitted the plan without a word and the death cost 6.74 USD. Each spawn's report now carries estCeiling ({ ceilingUsd, requiredFloorUsd, fits }), the floor being the cheapest honest reading of the declared posture: the loop's input floor across its projected turns (cache-aware unless the policy is off) plus ONE tail turn at the declared floor, the finalize-shaped dispatch that run died on. A ceiling below the floor is the ERROR finding child-ceiling-below-loop-floor with every number named, because the starvation is deterministic at the declared prices, not a headroom taste; the run-1 config fails the line and the rerun's 2.40 passes it, exactly the two points the experiment paid to learn. RV4701: preflightEstimate accepts budget.acceptanceReserve: 'checkpoint', the posture the runtime has accepted since RV4404, instead of refusing it typed while the engine runs it (the eighth driver had to estimate its genesis arithmetic under a substituted 'require'). Checkpoint estimates with require's genesis arithmetic, the echo carries the declared literal, and an unfit tail is the same error with its own remedy line: the first paid acceptance-tail dispatch re-checks this exact sum at the money actually spent and would refuse it already at the genesis numbers. Probes pin the feasibility refusal and the widened vocabulary.
  • c6fc3da: The child's death reason survives every surface it used to die on (RV4703, the eighth comparison experiment's first run). That run's child spent under its ceiling through the whole loop and died on a synchronous budget refusal of the FINALIZE dispatch (one millisecond, zero tokens): the journaled terminal named the crossed account, but agent:end said status 'error' and nothing else, the acceptance decision said "child X settled 'error'" and nothing else, and the stage was recovered from phase forensics. Three carries close the gap. AgentError.stage names WHICH dispatch a budget refusal killed ('loop', 'summarize', 'reserve-summary', 'finalize', 'extract'): every budget gate of the agent loop stamps it, the wire projection carries it in data, and the reader restores it typed. The agent:end event carries the terminal's typed error verbatim from the journaled entry, so the live stream and the replayed stream say WHY without a journal dig, byte for byte the same value. The acceptance fold carries the reason forward: the degraded note reads "settled 'error' (budget at the finalize dispatch: ...)" with the message bounded at 200 characters, and the machine roster row (AcceptanceChildSummary) gains a typed error field with the kind, the stage when stamped, and the bounded message. Children without an error keep every byte. Probes pin the event carry, the acceptance carry, and the finalize stamp.
  • c6fc3da: An empty terminal is not validated output on limit (RV4704, the eighth comparison experiment's first run). That run's acceptance promoted a limit child as degraded-with-output on a 16-token finalize summary that carried no answer, and the decision read "validated terminal output" over bytes nobody could use. The salvage arm now holds a character floor: a limit child's STRING terminal output must clear acceptance.minTerminalOutputChars (default DEFAULT_TERMINAL_OUTPUT_FLOOR_CHARS, 80) after trim before acceptValidatedTerminalOutputOnLimit may accept it. A below-floor string is a limit WITHOUT acceptance: it counts against the policy like any unsalvageable limit child, and its degraded note names the character counts ("N of M characters after trim: not accepted as validated output"). ONE shared function judges the floor for the acceptance fold and the finish-validation salvage marker, so the validator's cited pool and the verdict can never disagree about the same child; structured (schema-validated) outputs pass by their validation exactly as before, and the coordination prompt line states the floor the fold holds. 0 restores the previous acceptance byte for byte. A probe pins the guard.
  • 0ae8b85: The scoped semantic repair reserve (RV4705, the eighth comparison experiment's rerun). That run declared maxTotalRepairRounds: 1 for the question contract's "exactly one bounded repair" and the pool spent it on a MECHANICAL composition repair before the judges ruled: the anchor grounding lint fired at the draft, the finish-validation grant consumed the pool's only token to clean it, and the post-judge semantic round was refused over 38 standing census findings, one contradiction, and 24 uncovered sentences; the contract meant the post-judge round, the config could not say so. maxSemanticRepairRounds now reserves rounds inside the pool for the semantic stage: it is BOTH a reserve and a cap. Mechanical finish-validation grants admit only while the pool holds them plus the UNSPENT reserve on top (a refusal names maxSemanticRepairRounds and semanticReserveHeld beside runRepairPoolExhausted on the verdict decision, so a reader sees "the pool has room, but not for mechanics"), and the semantic round is bounded by the reserve beside the total pool it still shares, refusing with its own name (the semantic repair bound is spent) instead of masquerading as a spent pool. A reserve greater than the pool refuses typed at construction; declared without a total pool it is the round's own cap alone; absent keeps every decision and refusal byte identical, the RV4406 covenant. The repair_pool_consume decision carries the semantic counters exactly when the reserve is declared. The static surfaces reflect the split: preflightEstimate accepts both bounds, echoes them with the mechanical allowance under budget.orchestrator.repairPool, warns repair-pool-starves-semantic-round on the rerun's exact shape (an armed round over an undivided pool the mechanical grants can drain), warns finish-repairs-exceed-repair-pool when the stage bound promises more grants than the mechanics' share, and mirrors the contradiction as the repair-pool-refused-at-intake error; wireCapacityEstimate accepts both keys and reports repairWiresCeiling, the pool-bounded worst case of every repair wire, with the capacity sheet carrying it as a derived row. Probes pin that the reserve shields the round from the mechanics and that the scoped cap binds its own round.
  • 7932936: The census fits its own verdicts, or the judge never dispatches (RV4706). A census (auditScope: 'all') carries the whole document's rows in ONE citation-judge dispatch, and the { row, verdict, reason } bijection over them must fit the judge's declared output allowance or the reply truncates mid-array: the census rejudges of the seventh and eighth comparison experiments (145 and 215 rows) both overflowed the seventh's 9000-token cap and raised it to 32000 by hand, an arithmetic nobody enforced. Before the citation judge dispatches, a DECLARED judge.limits.maxOutputTokensPerTurn is now checked against the bijection floor (CITATION_VERDICT_EST_TOKENS_PER_ROW, 70, per judged row plus CITATION_VERDICT_EST_BASE_TOKENS, 500): a cap below it refuses typed BEFORE the provider call under the default judgeOutputCapGuard: 'fail' (data.source 'orchestrator_citation_audit', with the cap, the row count, and the estimate all named), or logs the same numbers and dispatches under a declared 'warn'. A 215-row census against 9000 refuses with zero provider calls; against 32000 it dispatches once, exactly the two configurations the experiments paid to learn. An undeclared cap keeps every byte: the guard cannot judge a resolution it does not see. A probe pins the pre-wire refusal.
  • 7932936: Truncated units reach the judge whole (RV4707, the seventh candidate's census rejudge). Rows 81 and 105 of that census carried honest support 3..7 lines past the 20-line unit clip (a paragraph ending at L839 with the support at 842; a comment-declaration ending at L25 with the support at 32), and the judge honestly ruled unsupported over the incomplete windows: the verdict blamed the composer for the resolver's clipping. citationUnitExcerptOf now takes optional bounds (caps: { maxLines?, maxChars? }, positive integers, refused typed otherwise; absent keeps the default caps byte for byte), and the orchestrator's judge-side row mapping re-resolves a unit the DEFAULT cap clipped at CITATION_UNIT_JUDGE_EXTENSION_FACTOR (2) times the line and char bounds, still bounded, stamping extended: true on the unit so the prompt says which cap produced the excerpt; a unit still clipping at the extended cap keeps its truncated flag beside it. Judge side only: the anchor grounding lint keeps the default unit with its own grace tail, and untruncated units keep every byte. A probe pins the extension.
  • 88da0ed: Distinctive window coverage and composite-name conviction in the anchor grounding lint (RV4708, the seventh candidate's census row 27). Coverage counted any camel part of five or more characters, so requireBounds cited at a docstring about page caps was silenced by the word "bounds" while the real declaration lived at lines 87 and 95 of the same file, and the true wrong line stayed silent. Coverage now silences by the LONGEST camel part (the benefit of the doubt stays with the anchor: a window discussing 'execution' plausibly grounds ExecutionScope), never by a generic short half. The suggestion channel tightened symmetrically into a CONVICTION channel, because a flag needs somewhere to point and that somewhere must carry the identifier itself: the whole token; the 6+ prefix rule for single-segment tokens only (for a camel compound, a word crossing the segment boundary by one letter is indistinguishable from a plural coincidence: 'executions' is byte-for-byte a prefix of ExecutionScope the way 'postgres' is of PostgreSQL); or a camel part spelled inside a COMPOSITE identifier, marked by glue or a case seam ('telemetry' inside '@acme/telemetry' places OpenTelemetry, the pinned RV4601 case; an execution_scope decision places ExecutionScope), never a freestanding prose word or its bare plural. Validated over the eighth experiment's corpora: the rerun candidate's 215 anchors stay at zero findings, and the codex hand's document keeps its true findings while the prose-part convictions of honestly cited negative claims die. Probes pin the distinctive coverage and the composite conviction.
  • 06c0e85: handle.preview settles on a refused resume (RV4710, the RV4602 wave's observed tail). A pre-run refusal (a configFingerprint mismatch, a binding mismatch, an unknown workflow) rejected the resume's handlePromise before any inner handle existed, and handle.preview pended FOREVER: a caller awaiting the preview on a refused resume hung instead of reading the refusal. The preview now settles with the SAME terminal result reports: a pre-run refusal rejects it typed, a run that dies before its settle path rejects it with that death, and the ordinary settle keeps resolving it first (a reject on a settled promise is a no-op). The RV4602 posture holds byte for byte: a pre-attached catch keeps the refusal result's alone to REPORT, so an unobserved preview never sprays an unhandled rejection of its own. Beside it, rfcs/sibling-anchor-fold.md (RV4709) records the design question the seventh census's row 24 raised, an unsupported anchor whose clause carries a supported sibling, with the lean toward the byte-additive meta marker; code follows review, not this changeset.

Patch Changes

  • e7e829c: The negative scenario citation convention (plan 47 B1..B3): a hypothetical is never a line fact. The orchestration guide gains a section with the paste-ready composer block (cite the DEFENSE the scenario attacks, mark the scenario as inference), the profiles guide carries the census evidence the mandate question waited for (sample buys honesty, census buys completeness; the floor still does not require the census), the audit section documents its own surface (auditScope census, the RV4706 output cap guard, the RV4707 truncated unit extension), and regression fixtures pin what the convention buys from the deterministic layers: the genre form is lint silent by design, the convention form lints clean, a moved defense line convicts with line suggestions, and the contract audit lexer keeps every count over the rewrite. No runtime change.
  • 7c58fb2: The portable replay descriptor (RV4602, the seventh comparison experiment's P1.2 remainder). A programmatic run records its workflow NAME in the journal, but the workflow VALUE lives in no rulvar.config.mjs, so the seventh experiment's replay --assert-no-live refused from a clean checkout. rulvar resume and rulvar replay now accept --registry FILE, an ordinary module whose named exports (workflows, engineOptions, and the new configFingerprint) merge over the config for that one command, so a run travels as a three part descriptor: the journal, the args, and the registry module naming the workflow under its recorded name; a module exporting a single workflow value serves the recorded name too. The configFingerprint export closes the drift loop the engine already enforces: rulvar run records it at genesis (from the workflow module or the config), and a resume or replay that supplies one is verified against the genesis record strictly before ownership, meta writes, or any provider call, refusing typed on drift instead of replaying under changed policy; the CLI never supplied it before, so every fingerprinted run degraded to the one sided warning. In core, a refused resume now rejects its result alone: each on() subscription of the deferred resume facade used to derive its own unhandled rejection from the refusal, and the CLI progress renderer subscribes fourteen event types. Probes pin the genesis recording, the resume verification, the replay registry load, and the quiet refusal.

1.250.0

Minor Changes

  • 0e240b9: The effect lane fold (RV4501, plan 45, rfcs/effects.md): EffectLaneFold is the pure journal semantics of the effect intent protocol. Effect lane facts ride kind-'decision' entries with typed payloads (effect_epoch, effect_declared, effect_intent, effect_attempt, effect_outcome, effect_receipt, effect_terminal, effect_incident, effect_disposition, plus the approval_expired clock fact), a recorded deviation from the RFC's entry-kind wording: the kinds registry is versioned as part of the hashVersion 2 identity profile, so the lane mints no new kind and stores stay dumb byte stores. The fold decides consumption over the strict prefix (allow before the intent position, no prior revocation or expiry decision, latest epoch, the approval's own licensed key, one canonical intent per logical key per epoch), treats same-opId replays as the same transition, closes machines first-terminal-wins with post-terminal facts as linked incidents, disables re-dispatch on every capability row from a revocation or expiry position on, validates terminal legality (confirmed demands a verified receipt; cancelled-before-dispatch demands zero attempts), classifies duplicate receipts benign against conflicting, and derives the compensated disposition as an overlay (effectiveEffectState) because terminals are immutable.
  • 6fe585e: The effect lane writer and the admission predicate (RV4502, plan 45, rfcs/effects.md): consumeApprovalAndRecordIntent is ONE append contended through the store's (runId, seq) uniqueness, with the universal recovery rule on every lane append: an uncertain result reloads and searches for its own operation id before any retry, a loser re-verdicts at the new tail (the fold itself is the verdict authority, evaluated over the prefix plus the hypothetical candidate), and a give-up appends a durable standalone refused record. The writer re-folds before opening each attempt (a revocation with zero attempts cancels cleanly; with history it refuses reconcile-only on every capability row), refuses attempts past the recorded budget, requires a deadline on every effect approval at intake, materializes a crossed grant expiry as an appended approval_expired decision before refusing, and honors the new EffectLaneStore restoration generation (a restored store comes up with dispatch disabled until a fresh epoch cites the bumped generation). Production mode requires a leasable store with fencedWrites; singleProcess: true is the explicit conformance posture for the in-memory store. effectLaneAdmissible evaluates the five conjuncts of RFC section 5 over a terminal envelope, fail closed, naming the first conjunct that refused. New typed error EffectLaneRefusedError (code effect_refused) carries the stable protocol rule that refused.
  • c5eb19c: The restoration generation (RV4503, plan 45, rfcs/effects.md section 4.5, item 3): SqliteStore and PostgresStore implement the EffectLaneStore capability, carrying a restoration generation OUTSIDE the journal bytes (a one-row table beside the leases). The restore runbook is one rule: after a point-in-time restore, call bumpRestorationGeneration() BEFORE the restored database becomes reachable to any worker, so the effect lane comes up with dispatch disabled by construction until an operator appends a fresh effect_epoch citing the bumped generation. The new effectLaneStoreConformance suite in @rulvar/store-conformance is the executable definition: generation starts at 0 and bumps monotonically (ELS1, ELS2), a bumped generation refuses every lane append until the fresh epoch (ELS3, the kill point 25 window, driven through the real writer over the real store), and a lane append under a non-current lease dies on the store's fence with nothing consumed (ELS4, the kill point 16 shape).
  • 565c13b: The @rulvar/effects package is born (RV4504, plan 45, rfcs/effects.md sections 4.4, 6, 8, 11): the effect adapter seam that cannot send without an attempt record (dispatch receives the seq of the attempt appended BEFORE the call), the provider capability matrix types, and the crash-window dispatcher whose recovery is licensed exclusively by provider-side fencing: the idempotency-key row re-dispatches under the same key and lets the provider dedupe, the conditional-create row leans on the unique natural key, the acceptance-closing row closes the ambiguous ATTEMPT identity (so the fresh attempt stays legal while the stale one is refused at the provider), and the 'neither' row quarantines every ambiguous window with the possible late stale send named in the record. From a revocation or expiry position recovery is reconcile-only on every row: a found receipt confirms (a revocation then opens the compensation decision path as a linked incident; an expiry opens none, because it bounds the grant, not the past), a closed negative cancels with the proof on the record, and anything unresolvable quarantines. Provider fakes enforce exactly the fencing their row claims, including the deliberately stalled predecessor of kill point 17, where elapsed time licenses nothing. In core, the cancelled-before-dispatch legality widens per RFC section 4.7 row 2 (every attempt provably failed also proves no effect) and the writer gains refresh(). Kill points 4, 5, 6, 7, 8, 14, 15, 17, 27, 28, 29 are pinned by tests.
  • c6d197b: The reconciler, the trust envelope, and the whole kill point kit (RV4505, plan 45, rfcs/effects.md sections 3.1, 7, 8, 9). The sweep makes "every intent deterministically reaches confirmed, compensated, or quarantined" true: crossing reconcileBy quarantines whatever state with the state recorded, receipt waits and attempt budgets quarantine on exhaustion, lookups are bounded SEPARATELY through journaled effect_probe rows (countable from the journal alone, crash-proof), pre-terminal conflicting receipts quarantine, and effect authorizations past their deadline refuse durably instead of waiting forever. Receipt verification runs a declared trust envelope: issuer identity, per-class content bindings, key validity windows, revocation from its time forward, and the host's signature check; every failure classifies unverified, which routes to unknown. The post-restore reconciliation (kill 25) quarantines provider effects the journal cannot reconstruct by name (or the whole range without authoritative enumeration), and a restoration epoch stays undispatchable until the new effect_reconciliation_complete decision cites it. Section 9 telemetry folds effective dispositions (the compensated overlay included), pressure, duplicate classification, and open incidents. The kit exports all thirty effects.kill.* rows as named conformance checks parameterized by a store factory (ambiguous acks and restoration generations injected through delegating proxies, so any store qualifies), registered over the in-memory reference store in single-process posture and over the REAL sqlite and postgres stores in their own packages.
  • c9d9729: The durable admission SPI and the pure scheduler core (RV4507, plan 45, rfcs/admission.md): AdmissionScheduler in l0/spi/admission.ts is the seam that answers "when may this work START, and in what order relative to competing tenants", deliberately split from QuotaLimiter (a counter has no queue; the two seams degrade independently, and a granted ticket never exempts a wire from quota). Enqueue is a conditional create under the caller-minted (unitId, generation) identity; every lifecycle call is idempotent by its operation id; denied is a terminal infeasibility verdict that never camps at the head; of racing release, expiry, and cancel exactly one wins; and the run journal never records scheduler state. The pure algorithms are exact and replica-deterministic: hierarchical start-time fair queuing with cost = reserved wires over weight, V advancing to granted start tags monotonically and capping idle hoarding, arrival seq breaking ties; the sliding window ring that bounds the epoch boundary burst to one sub-window allowance; the token bucket; and the three JCS-canonical level projections (resolved tenant, tenant plus providerAccount, the full scope). MemoryAdmissionScheduler is the single-process reference: all-levels-or-nothing consumption, the emergency reserve, lease-fenced covers with the conservative expiry refund (reservation minus the covered high water), release refunds with bucket debt that never denies retroactively, the level-2 concurrency semaphore, and typed refusal of a conflicting tenant pair outside tenantFrom: 'scope'.
  • fed9db6: Durable admission over sqlite and postgres (RV4508, plan 45, rfcs/admission.md section 9): SqliteAdmissionScheduler and PostgresAdmissionScheduler persist the scheduler's WHOLE state as one plain-JSON document (AdmissionState, now exported with snapshot() and hydration on the reference core), committed atomically per lifecycle call inside a BEGIN IMMEDIATE transaction (sqlite) or an advisory-lock-serialized transaction (postgres). This is the RFC's first shipped durable shape, recorded as a deliberate decision: a single scheduler over durable state with deterministic ordering, where "state moved AND buckets moved" holds trivially because the whole document commits or none of it does; per-row schemas are an optimization the SPI does not require. A queued ticket survives its holder with position and arrival identity intact, re-enqueueing the same (unitId, generation) returns the SAME ticket, and settlement operation ids replay as durable no-ops across holders (a late-settlement debt entry lands exactly once).
  • df9ed76: The admission conformance matrix, all twelve rows (RV4509, plan 45, rfcs/admission.md section 7): admissionConformance runs the RFC's named acceptance surface over any scheduler factory, registered over the in-memory reference (snapshot/hydrate plays the crash reopen), the sqlite document, and the postgres document. The fairness rows measure GRANTED RAW SERVICE, the property itself: sixty equal tenants each receive their exact share with every consecutive sixty-grant window containing every tenant, and weights 1/2/4 grant exactly 1:2:4 in the first virtual-time cycle with weight 1 never starving (the tenant plugs that assemble the queue first carry weight equal to their cost, a uniform one-unit tag shift that preserves the burst's relative order bit for bit). The remaining rows: the minute-boundary burst bound, queued-ticket crash survival with arrival identity intact, the conservative fenced-cover expiry settlement with late debt, the denied-versus-queued state distinction, region loss without double grants, hundred-percent repair amplification held inside caps through debt, fail-closed foreign scope, multi-level all-or-nothing, the atomic failover rebind (new rebind on the SPI: the target slot acquires before the source releases, and a failed transfer changes nothing), and tenant resolution parity. The reference pump's scan is now bucket-blocking: a refused ticket blocks ITS bucket for the pass, so no later ticket of the same bucket overtakes it (the no-starvation guarantee), while independent buckets proceed; release no longer grants implicitly, making every grant an observable pump event.
  • 3020912: The engine's durable admission bracket (RV4510, plan 45, rfcs/admission.md section 5): createEngine({ admission: { scheduler, reservation?, pollMs?, tenant?, tenantFrom? } }) brackets every non-preview run as one unit of work under the run's own identity (runId, genesis). A resumed segment RECOVERS its ticket by that identity before ever enqueueing; a queued run waits for its grant (polling with the scheduler's pump, honoring retryAfterMs); the terminal denied verdict refuses typed (AdmissionRejectedError) before any store mutation or provider dispatch; the full reservation checkpoints as the maximally conservative cover at grant; the lease renews on a timer; and the release is part of settlement ordering (a caller that observed the outcome can observe the released ticket). The effective tenant resolves exactly like the limiter's (engine-configured, or the scope's under tenantFrom 'scope'; the admission config carries its own tenant fields for limiter-less deployments, with quota's taking precedence so the two seams debit the SAME identity). A settled unit re-admits on resume as a fresh ticket under the same identity; denied stays terminal. Admission is an environmental fact: nothing is journaled, replay never consults it, and the wire-level QuotaLimiter keeps being consulted per dispatch, unchanged. First-shape actuals equal the reservation and the cover is the whole reservation, both recorded as deliberate first shapes in the bracket's doc.

Patch Changes

  • d8d598d: Plan 45 closes in the documentation (RV4511): the model-routing page records the limiter/admission split as load bearing (the limiter answers "may this wire fly right now", the durable admission seam answers "when may this work START and in what order", a granted ticket never exempts a wire from quota), the durability page documents the run bracket's crash model (recover by unit identity, re-admission of settled units, conservative expiry settlement through fenced covers, nothing journaled), and rfcs/admission.md records the implemented status with its deviations and first shapes named one by one, mirroring rfcs/effects.md. Both RFCs now read as shipped protocol references rather than promises.

1.249.0

Minor Changes

  • 8862133: RV4401: the resolver v2 excerpt tells the truth about the block the cited line belongs to. This is a bugfix of resolver 2's DOCUMENTED semantics, not a third resolver: replay of existing journals is untouched (judge prompts read from the journal), only new runs change. The seventh comparison experiment's built-in citation judge returned 10 unsupported of 24 sampled, and seven of the ten were excerpt artifacts: docstring body lines start with *, which also spells a markdown list marker, so the list rule matched first and every JSDoc anchor excerpted as a ONE-LINE list item with its support hidden 3..9 lines away. Comment context now decides before any markdown rule: a star-led line is a comment only when a bounded upward scan finds the /* opener (bare markdown * item chains keep their list semantics byte for byte), a //, # or -- line is a comment only beside a same-family neighbor (a lone # heading stays a heading), and inside a comment the line classifies by its prefix-stripped text, so a stripped list item excerpts the item with its continuations and anything else carries the comment block plus the declaration it documents. A table HEADER anchor (delimiter row directly below) now carries the delimiter and body rows, because citing the header cites the table. Resolver v2 excerpts get their own bounds sized for real docstrings and guide sections, MAX_CITATION_UNIT_EXCERPT_LINES 20 and MAX_CITATION_UNIT_EXCERPT_CHARS 1600 with the truncated flag preserved; resolver v1 keeps its own smaller bounds byte for byte. The ten findings' file geometries are frozen as a test corpus: the artifact excerpts now contain their supporting lines, and the genuinely wrong citations stay exactly as damning as the judge read them.

  • 0d7a717: RV4402: the semantic terminal verdict is fail closed about the metas it trusts, and the citation judge's row set is a bijection. The seventh comparison experiment's re-audit found semanticTerminalVerdictOf({claimConsistencyMeta: {}}) folding to 'clean': every malformed field read as absent, every absent counter read as 0, and an empty or foreign meta laundered itself into the one word production gates on, the exact opposite of the fold's own docstring. Now a meta that carries NO evidence anything judged (no judgedHash/auditedHash, no judgeInvoked, no judge failure flag, no judgedStage) folds 'not-judged' with a stable trust code (claim-meta-unjudged / citation-meta-unjudged), and a counter that is PRESENT but not a count taints its meta the same way (claim-meta-malformed / citation-meta-malformed); an absent field still reads absent, because absence is honest and garbage is not. parseCitationVerdicts now refuses a row outside the judged set: a judge inventing rows is a failed parse, never surplus information. productionAcceptable distinguishes the two refusal shapes a reader used to conflate: an absent verdict reads not-recorded: ... (nothing was configured, or the run predates the fold), while a recorded 'not-judged' verdict lists its judge failure codes.

  • e4428bd: RV4403: the terminal has five axes (terminal status, execution completion, child acceptance, deliverable acceptance, semantic verdict), and none substitutes for another on any surface, live or restarted. The seventh comparison experiment settled exhausted with both judge metas and the ten-unsupported count only inside error.data: the outcome's top level read nothing, the settle recorded nothing, a restarted production gate answered 'not-judged' about a failure whose own message counted the findings, and rulvar inspect printed acceptance: accepted (completion complete; gate on the status and completion PAIR) over a rejected deliverable. Now: every typed semantic failure stamps the one-word verdict beside its metas (folded by the same RV4209 function the acceptance path uses, without a waiver or draft-bridge input, because a failing run has no standing acceptance to license); the engine lifts the semantic facts (claimConsistencyMeta, new citationAuditMeta, semanticTerminalVerdict) on EVERY terminal path including typed failures without a completion literal, mirrors them onto the outcome, the run:end event and the terminal envelope, and records them in the journaled settle; lastRunSettle and the persisted terminal envelope read them back defensively (a foreign or partial shape reads NOT RECORDED, never a verdict), so live and restart agree field for field. The CLI production gate reads the outcome's typed verdict field first, so it refuses with the recorded 'findings' instead of a false 'not-judged'. rulvar inspect prints the axes side by side (axes: terminal exhausted | execution complete | children accepted | deliverable rejected | semantic findings) with the semantic counts and the citation audit numbers, and the child roster verdict is labeled children:, one axis of five; the bare acceptance: label and the "gate on the status and completion PAIR" advice are gone.

  • d6873c1: RV4404: budget honesty, three opt-in answers to the seventh comparison experiment's death. The intake gate had verified the acceptance tail against DECLARED estimates and the run passed fits: true honestly; the workers then overshot their declared estimate 2.8x, and the refusal came only where the armed round could not dispatch, after the composition and both judges were already paid.

    budget.acceptanceReserve: 'checkpoint' is 'require' plus a runtime re-check of the same arithmetic before each paid acceptance-tail dispatch (the first composition, each judge pass): every ceiling on the chain up to the run root judges its spend plus its dedicated tail reserves plus the worst case still ahead, and the run refuses typed BEFORE paying the stage, journaling an acceptance_checkpoint_refused decision naming the account, the stage, and every term. In the seventh trajectory the first checkpoint fires right after the workers, saving the composition and both judge passes. Dispatch-projection holds stay out of the arithmetic: they release on settle and the tail terms already price those futures.

    budget.estIsCeiling: true turns declared spawn estimates into the fan-out's own hard allowance ceiling: tool-spawned children share the orchestrator's child scope, so the enforced bound is the AGGREGATE of the admitted estimates (RunBudget.raiseChildAllowance widens it per admitted child), exactly the number the acceptance-tail arithmetic trusted; a fan-out that overshoots its declarations refuses at ITS ceiling instead of silently eating the tail. With both opt-ins, a preflight fits: true becomes a dispatch guarantee for the declared tail.

    The pair ceiling stops laundering itself as a document defect: a declared semanticAcceptance (claimCoverage 'full') derives coverage target 1 when none is set, so the pass runs coverage-first instead of the historical first-max selection, and a truncation under a DECLARED target grades 'coverage-capped' (a new ClaimCoverageGrade literal) instead of a silent 'partial'. The strict-final and waiver-forbid refusals then name the knob: the pair ceiling max, and how many citing sentences it left uncovered. The seventh run declared full coverage, folded its pairs truncated, and reported 23 uncovered citing sentences as if the text were the problem. --strict refuses 'coverage-capped' (a capped pass breaks the contract the declaration states; plain 'partial' deliberately stays exit 0), and the semantic terminal verdict folds it into the partial bucket.

  • 4092e8d: RV4405: the parallel judge pair extends to the merged arming and the post-round rejudges. RV4210 dispatched the final claim pass and the audit's first pass concurrently only when NO repair round was armed; the seventh comparison experiment ran the exact posture the exclusion kept sequential (both onFound: 'repair'), and its judge wall was pure wait. With BOTH repair postures armed, no single-class round can rewrite the document between the two first passes (the one merged round fires strictly after both), so both passes read the same immutable bytes and now dispatch together, verdicts still processed in the historical order (the claim pass's typed throws first). The two post-round re-passes judge the SAME repaired bytes, so they dispatch together under the same discipline. A SINGLE armed round keeps the strict sequence byte for byte: its round rewrites the document between the passes, and the audit must read what ships. The honest cost is unchanged and documented: under a refusing posture both judges are already paid when one refuses, the price of the saved wall; the acceptance tail funded both passes either way.

  • e086590: RV4406: one run-wide repair pool. maxTotalRepairRounds bounds every provider-dispatching repair grant across the whole run, whatever gate granted it: finish-validation repair turns and the bounded semantic round consume from the same pool, and per-stage bounds (finishValidation.maxRepairs, the one semantic round) NARROW it, never widen it. The tokens are durable by construction: a finish-validation 'repair' verdict IS its consumption (the decision lands before the repair turn dispatches), and the semantic round journals a keyed repair_pool_consume decision strictly BEFORE its dispatch, so a crash between the decision and the dispatch resumes without a double consume, and the counter is folded from the journal at every consultation so live, replayed, and resumed segments read the same number. A spent pool turns a finish-validation grant into 'rejected' with runRepairPoolExhausted: true on the decision, and refuses the semantic round inside the honest could-not-dispatch envelope, naming the bound and the tokens consumed. The draft-gate pre-pass dispatches no provider work and spends nothing, by design. Absent keeps every decision and refusal byte identical.

  • 1411938: RV4407: the citation audit's scope becomes a declared mode. citationAudit.auditScope: 'sample' | 'all': 'sample' (the default) keeps the deterministic stratified sample byte for byte; 'all' judges EVERY anchor row of the document, a census instead of a sample, with no per-section pick and no maxSampled ceiling. The census requires resolver 2 (it enumerates every anchor of every citing sentence, the v2 row semantics) and refuses typed at intake otherwise. One judge invocation still carries all rows (two under an armed round, exactly the sample's worst case): the cost scales through the prompt, so judge.estCost should be sized for the whole document; the declared estimate enters the acceptance tail unchanged, one term per pass. The meta stamps auditScope: 'all' under the census so a consumer knows whether sampled counts a sample or the document; every sample-mode meta keeps its bytes. The production profiles guide RECOMMENDS the census for critical document classes with the cost arithmetic spelled out; the regulated floor does not require it yet, because after the RV4401 excerpt fix sample sensitivity is expected to drop and the floor moves on evidence.

  • 737d1ee: RV4408: sponsor joins the composite execution scope. The seventh comparison experiment's benchmark domain (a clinical trial adjudication network) runs work on behalf of a study sponsor who is neither the owning tenant nor the billing account, and the scope vocabulary could not say so. ExecutionScope.sponsor is the seventh named dimension (the RV4205 providerAccount precedent): host-defined vocabulary, carried without loss through RunMeta, the genesis execution_scope decision, the canonical scopeDigest, the invoice header, the export bundle, and the resume assertion, entering the regulated posture hash automatically through the same closed table. scopePolicy: { unknown: 'reject' } accepts it as vocabulary; a QuotaRule can pin sponsor beside the other dimensions, matching only reservations whose scope carries the same value, with dimension-less rules keeping their storage bucket keys byte identical. The durability and production-profiles guides name the new dimension.

  • 634f966: RV4409: the logical run's telemetry is native. The seventh comparison experiment measured its resumed run's active and calendar walls, the operator gap between segments, and the 109-wire logical count by external script over the raw journal, and reconciled "16 versus 109" by hand because the two counter families shared a vocabulary. logicalRunTelemetry now folds, from the stamps and decisions the journal already carries: activeMs (each segment's own append window, summed), calendarMs (first to last append), gapMs (their difference, the operator time), perSegment (status, entries, active wall, and replayed: true on a pure-replay segment, so a resumed run's walls read as the original segments' work instead of a 0.0 s rerun), and logicalWireRequests (provider-call decisions across the WHOLE journal, the invoice's cardinality). Absent stamps keep the time fields absent: not recorded, never zero. rulvar inspect prints the logical run block (both time conventions, per-segment walls, the replayed marker) and the wire count under its own name, with the label spelling out that a segment's adapter fetches are a different, smaller counter by design.

  • 052cc26: RV4410: opt-in coordination checkpoints. With coordinationCheckpoints: true, every settled await round appends a compact coordination_checkpoint decision (the round ordinal, the settled handles, the spend at the checkpoint), so a timeout or kill terminal shows how far coordination durably got, and a resumed run's journal visibly continues from round N+1 instead of an opaque prefix. The seventh comparison experiment's genesis segment died on a timeout mid-coordination and the post-mortem priced the re-coordination by hand; the checkpoint makes the durable progress a journal fact. An await round the kill interrupted journals NOTHING, honestly: coordination got no farther than the journal says. Opt-in because the decisions are journal bytes; without the flag every journal stays byte identical, and the replay machinery never re-pays journaled coordination either way. rulvar inspect prints the last checkpoint (round, settled children, spend) when one exists.

Patch Changes

  • bbae134: RV4411: plan hygiene. The docs and RFCs that said "plan 44 scope" about the effects/admission runtime now name the dedicated effects plan (plan 45): plan 44 answered the seventh comparison experiment instead, and a published scope pointer must follow the plan it points at. A pnpm pin guard (scripts/assert-pnpm-pin.mjs, pnpm run guard:pnpm-pin) runs before every CI Turbo fan-out: one loud line naming the running pnpm, the packageManager pin, and the launch path, instead of the per-child version-mismatch death the RV4306 bootstrap job documents; the RV4306 behavioral gates stay the authority, and the un-enabled Corepack path stays the documented trap this guard names rather than adopts. The release contract gate keeps its documented consumption (the one-time legacy green line was spent by v1.248.0; the next release reads a fresh classification artifact from the always-recording contract-tests workflow).

1.248.0

Minor Changes

  • 8d0cd69: Capacity stops being a constant and becomes an artifact (RV4304, plan 43; P2.2 of the sixth comparison experiment's improvement plan). wireCapacityEstimate takes the SAME four posture declarations the acceptance tail prices (claimStage, claimOnFound, citationOnFound, claimConfigured), and both derive their arming from one new exported function, semanticRoundArming, the dispatchProjectionReserveUsd precedent: money and wires cannot disagree about which rounds a declared posture arms. With the posture declared, the judge wire counts are COMPUTED from it (a hand-declared judgeWires/citationJudgeWires must agree or refuses typed with the childWires-contradiction hint, because a hand-widened count double-books the rejudges the delta already prices), and repairRoundDeltaWires is derived: 0 with nothing armed, 2 for a lone claim or citation round, 3 for the merged round or a citation round that rejudges a configured claim pass, the case the sixth run's constant 2 could not express. With no posture declared the historical bytes hold exactly: the delta is the documented legacy constant 2. And the new capacitySheet(spec) plus renderCapacitySheetMarkdown turn the estimate into a structured artifact with EVERY figure labeled given, derived, assumption, or observed: an undeclared coordination term is a NAMED assumption instead of a silent zero, throughput derives only when concurrency AND service time are both given (wire counts alone bound nothing per unit time), the worst-case envelope calls itself a shaped bound and never a percentile, and observed run measurements (the invoice's 122 physical wires) render in their own section with their source on every row, never folded into the declared arithmetic, so a reader who quotes any single line quotes its provenance with it.
  • 81065e4: The composition declares its claims, and structure is all the machine judges (RV4305, plan 43; P2.1 of the sixth comparison experiment's improvement plan). Under the opt-in synthesis.claimMap: true the synthesis invocation's finish REQUIRES a typed claimMap beside the result: one row per material claim, atomic, each with a unique id, its evidentiary grade (source, inference, assumption, live-observed), and the source anchors it rests on; the finish tool's schema and description move BY DESIGN under the opt-in (the sectional precedent), and the reserved finalizer carries the same contract so a capped run is never schema-blocked from complying. The layer split is the design: deterministic validation is STRUCTURAL only, every document anchor covered by the map and every map anchor present in the document (both directions), at most one non-source row per anchor (a row count, never a semantic verdict), the inference bridge required on inference rows (premises and reasoning; the grade never replaces it), run evidence required on live-observed rows, unanchored claims forced to declare assumption, unique ids, and schema-carried bounds; a structural failure rejects like any host validator and spends the ordinary finish repair bound. Semantic truth stays with the judges: the accepted map journals as an orchestrator_claim_map decision beside the accepted candidate, linked by the exact candidateHashOf recipe the claim judge's judgedHash binds, and the claim judge's prompt gains the map from the JOURNAL (never live state, so live and resumed passes render identical bytes) under this same opt-in; no new judge, no new rounds, no wire growth. The sectional machinery is refused beside the opt-in (a splice would move the document out from under its map), and an armed repair round resubmits the full document with a full map instead of arming the sectional shortcut. Absent, every byte holds: prompt, toolset hash, journal, envelope.
  • 8573f20: REGULATED_VERSION 4: the floor requires the deliverable contract and absorbs the plan 42 knobs (RV4303, plan 43). The v3 floor never required finishValidation, so a regulated orchestration could compile with no deliverable verdict, no candidate chain, and nothing for a lineage policy to attach to, a loosening deeper than any field the floor already refused. The v4 compile refuses its omission by name (the RV4103 doctrine: the validators are the host's own acceptance criteria, and a floor that invents them invents the contract), fills candidatePersistence: 'hash-only' inside the declared contract (the auditability minimum; 'transcript' is the legal richer declaration, and the two compile to different profileHashes), and refuses the legacy retainRejectedCandidates boolean at BOTH values, the fail-closed migration: a silent canonical rewrite would compile a lineage posture the host never wrote. The citation audit's resolver generation is pinned the same way: citationAudit.resolver fills 2 (the bounded logical-unit resolver) and an explicit 1 refuses, because the fixed four-line window is the diagnostic resolver whose truncation manufactured the sixth comparison run's false negatives. The hashed map gains the resolver generation, the persistence mode, and the required-contract fact; the fingerprint prefix moves to regulated:4:, a v3 hash never collides with a v4 reading, and the map bytes are now pinned by a golden-hash test so any movement of the hashed posture is a conscious edit beside a version thought. The new claimMap (RV4305) is deliberately NOT absorbed: the knob matures a cycle first, a candidate for v5.
  • 95f6a5e: The scope identity gains a declarative value normalization table, and the journal is its authority (RV4302, plan 43). scopePolicy.normalize is a versioned table over a closed vocabulary (trim, lowercase, nfc, applied per dimension in declared order), deliberately data and not a callback: a host function is not replay stable, not journalable, and free to read locale or time. The table applies in normalizeExecutionScope strictly AFTER the existing input validation, the result re-validates by the same rule (an all-whitespace value that trims to empty refuses typed instead of recording an identity that asserts nothing), and the canonical values exist BEFORE any digest does, so ' EU-West ' and 'eu-west' stop splitting one tenant's quota buckets and FinOps joins across two identities. The table is journaled in the genesis execution_scope decision beside the scope and digest it shaped, mirrored in RunMeta.scopeNormalize (stores must round-trip it; the conformance kit checks), and on resume the RECORDED table is what normalizes the supplied scope before any comparison, so a host that re-supplies the raw values it started with asserts true; a conflicting re-supplied table refuses typed (the args-binding rule), and a table supplied over a run that recorded none warns and is never applied. compileRegulatedProfile preserves a declared table under its pinned unknown: 'reject' and hashes it into the posture, so two compiles over the same canonical values with different declared tables carry different profileHashes; absence keeps every undeclared config byte for byte, hash included. Beside the code, rfcs/admission.md records the accepted design for the durable fairness and admission SPI (P1.4): the split from the live-only QuotaLimiter, hierarchical buckets over the resolved effective tenant, start time fair queuing with reserved wires as the one scheduler unit, conditional-create tickets with lease-fenced consumption covers, and the conformance matrix, hardened by adversarial review to the final verdict closed.

1.247.0

Minor Changes

  • 1933ecc: The atomic production posture, and the one bounded round every defect class can ride (RV4201, RV4202; the sixth comparison experiment). The experiment's run was configured knob by knob into "observe and ship anyway": report postures, a standing waiver, no repair round, every choice individually legal, their sum a run that settled accepted over a partial coverage grade, a judged contradiction and five unsupported citations. RV4201: semanticAcceptance is the one declaration that says the opposite in full (judgedStage: 'final', claimCoverage: 'full', contradictions/citations 'repair-once-then-fail' | 'fail', unresolved: 'fail', waiver: 'forbid' | { judgedHash }); intake fills nothing and refuses every underlying field that contradicts it, 'forbid' refuses typed even over a journaled waive decision (a config/journal mismatch is not an authority), the pinned form licenses exactly one reviewed document by its claim judgedHash, and the terminal invariant asserts that the claim and audit verdicts describe the shipped bytes. compileRegulatedProfile enforces it: onFound 'report'/'carry' refuse, 'fail' fills, an armed 'repair' gains coverageRepair: true, a coverageTarget below 1 refuses as unsatisfiable, a standing waiver refuses outright, and the declaration is written from the enforced postures when absent. RV4202: coverage joins the bounded round (claimConsistency.coverageRepair: a non-'full' FINAL grade arms the same one round, the uncovered citing sentences ride its prompt as the UNCOVERED CLAIMS block, the repaired document is re-graded from its new hash, and a persistent non-'full' meets the strict gate with the spent round named), and arming BOTH the claim and citation repairs is legal now: the pair grants the SAME one merged round, fired after the first audit pass with both defect lists on board, both judges re-rule on the new hash, survivors of either class fail typed, and the acceptance tail prices exactly one round composition and two passes per armed judge. The repair ledger's semantic rows gain the 'coverage' and 'combined' triggers.
  • db0a5f0: The posture map tells postures apart (RV4203, the sixth comparison experiment's headline finding). compileRegulatedProfile hashed NONE of the semantic postures: a run configured report findings beside a standing waiver and a run configured fail closed carried the identical profileHash and configFingerprint, so the attestation machinery could not distinguish a diagnostic posture from a production one, which is the exact blind spot the sixth experiment ran through. The v3 posture map (REGULATED_VERSION 3, fingerprint prefix regulated:3:) hashes the findings postures of all three passes (claimConsistency.onFound, citationAudit.onFound, contradictions.onFound when declared), the coverage arm and target, the waiver mode with its declared terms, the citation audit's sampling and judge parameters (samplePerSection, maxSampled, window, pattern, judge model and effort), the claim judge's model and effort, the semanticAcceptance declaration in full, and the declared toolset attestation pins (contract and authority hashes), so upgrading a contract-only pin to an authority-bearing one moves the fingerprint. A v2 hash can never collide with a v3 reading of the same options.
  • b698726: The first-party surface attests, and the floor loses its holes (RV4204, the sixth comparison experiment). Before this, only mcp() and the AI SDK bridge exposed describeRegulatedPosture(), so unrecognized >= 1 on nearly every real regulated compile and a zero-blind-spot floor was unsatisfiable by construction; and the floor checked toolset attestation only on defaults.profiles, accepted legacy contract-only pins that pass authority drift silently, and never walked the executors at all. Now: anthropic() and openai() attest their egress (official, a custom-base-url whose ORIGIN enters the hashed posture map, or a preconstructed-client named honestly) plus the caps pagination bound; subprocessExecutor() and containerExecutor() attest their ledger, env allowlist, resolved ceilings, and isolation seam; compileRegulatedProfile walks engine.executors and the sandbox runner beside adapters and toolsets, wraps attested executors so run() re-judges the posture at use (the RV4102 seam), refuses a regulated executor without a ToolEffectLedger by field name, refuses legacy contract-only pins (re-record with attestToolset()), and arms the new engine-wide defaults.requireToolsetAttestation, under which a spawn resolving a non-empty toolset with no pin binding it refuses typed at spawn time (the per-call-tools hole the profile pins could not see). The opt-in construction: 'require-recognized' compile floor turns the unrecognized count into a typed refusal naming the blind constructions, satisfiable now that the first-party surface attests.
  • 48348d2: Scope dimensions v2, and the quota binds to the scope (RV4205, the sixth comparison experiment's P0.2). ExecutionScope gains the three named host dimensions (legalDomain, region, providerAccount) beside the tenant/account/project trio; the genesis execution_scope decision and the invoice header carry the canonical scopeDigest (sha256 over the normalized JCS bytes, a fixed-length join column for FinOps pipelines); RunOptions.scopePolicy: { unknown: 'reject' } turns the silent unknown-field drop into a typed refusal by name (the drop default is pinned byte for byte, and compileRegulatedProfile enforces the refusal plus hashes the policy). The quota seam binds to the scope: quota.tenantFrom: 'scope' debits each run's reservations to the tenant its own scope declared instead of the engine-wide name (a scopeless run reserves tenant-less), every reservation carries the run's scope dimensions, and a QuotaRule can pin account, project, legalDomain, region, or providerAccount beside provider/model/tenant, matching only reservations whose scope carries the same value. Dimension-less rules keep their storage bucket keys byte identical, so keyed-store windows survive the upgrade.
  • 4cfa1cc: The telemetry names every judge, and the capacity intake closes (RV4206, the sixth comparison experiment). Both critical-path reducers now classify every synthesize span through one exported synthesizeSpanClassOf: the citation entailment audit judge gets its own citationJudgeMs bucket (plus span counter) on the live fold, the journal fold, and both post-fan-in itemizations, instead of folding into finalCompositionMs (the run under audit read 368889 ms of "composition" that was 214870 against 154019, with compositionSpans faking a repair round's signature and lastCandidateMs overshooting the settled candidate by the verdict tail); a PRESENT synthesize label the classifier does not know lands in unclassifiedSynthesisMs with a nonzero span counter instead of silently reading as composition, and the candidate milestones anchor to composition spans only. CostReport.byAgentType gets the RV3905 vacuum fill: the orchestrator's own dispatches attribute as orchestrator, synthesizer, claim-judge, and citation-judge (attribution policy only; explicit agent types and spawned profiles always win, events and journal identity untouched). And wireCapacityEstimate closes its intake: unknown keys refuse typed (they were silently zero), the fan-out can be declared structurally as children times turnsPerChild with a typed hint when a contradicting childWires passes a child count where the wire total belongs, the citation audit judge's wires get their citationJudgeWires key, and the output stamps basis: 'declared-estimate'.
  • 4b7197a: The candidate chain reads by hash, and absent bytes say why (RV4207, the sixth comparison experiment). finishValidation.candidatePersistence: 'transcript' | 'hash-only' supersedes retainRejectedCandidates (declaring both refuses typed): under a declared policy every finish-validation decision carries the candidate identity, the ACCEPTED verdict included (the hash names the resolved document, deterministic patch or sectional splice applied, on the same recipe the semantic judges bind), 'transcript' retains rejected bytes exactly as the boolean did, and 'hash-only' retains none on purpose, stamping bytesUnavailableReason: 'hash-only-persistence' on the decision, the fold, and the terminal row (a declared retention the store refused stamps 'store-write-failed'), so an auditor finding no blob reads a policy or a fault by name. The hash recipe is exported and documented: candidateHashOf (sha256 over the JCS canonical value; a string document hashes as its JSON encoding, and a file export with a trailing newline changes the file's sha while this hash holds) with verifyCandidateBytes(bytes, hash) as the audit predicate. rulvar inspect <runId> --candidates renders the chain (verdict, hash, chars, window, wires, money, byte address or the named reason) and --candidate-bytes <hash> recovers a retained document to stdout, verified against the journaled hash, in one command; the experiment's auditor recovered the rejected 37,645 character composition by digging messages[3] out of a binary transcript blob and re-deriving the recipe from source. Undeclared configs keep every byte: identity on non-accepted verdicts only, exactly RV2507.
  • 5ebc842: The citation excerpt reads the logical unit, and every anchor gets its row (RV4208, the sixth comparison experiment). The declared citationAudit.resolver: 2 (default 1, byte identical for every existing config) excerpts the bounded LOGICAL UNIT the cited line belongs to instead of the fixed downward window: a heading brings its whole section to the next heading, a list item its continuation lines, a table row its header pair, a code comment its block plus the declaration it documents, anything else its paragraph expanded both ways; all capped at the existing 12 lines and 800 chars with a truncated flag and the unit type on the row (citationUnitExcerptOf, exported). Under the same opt-in the sampler audits EVERY anchor of a compound sentence as its own row (anchorOrdinal) against its nearest claim clause (clauseAround, on the row and in the judge prompt), and the audit meta stamps resolverVersion: 2. The experiment's confirmed false negatives were exactly window artifacts (a section heading whose support lives below the window; only a sentence's first anchor ever sampled), and its two GENUINE unsupported citations survive v2 untouched: the 24-row gold corpus pins both directions at 24/24. Explicit path:start-end ranges keep range semantics under either resolver.
  • 16ff6b9: One word answers the production question, on every surface (RV4209, the sixth comparison experiment). The acceptance envelope now carries semanticTerminalVerdict whenever claim or citation machinery is configured: 'clean' | 'findings' | 'partial' | 'vacuous' | 'waived' | 'not-judged' with the final hash, the counts (contradictions, unsupported and partial citations, repair rounds), the standing waiver, and the judge-failure codes, folded ONCE at the orchestrator settle (semanticTerminalVerdictOf, exported) with fail-closed precedence: a failed or declined judge, and a draft-stage grade the synthesis rewrote (RV3207), read not-judged; findings outrank the waiver; the waiver is never clean. The verdict is lifted onto the outcome, mirrored onto the terminal envelope (and through it the run:end event and the HTTP response), so every consumer reads the SAME derivation instead of re-deriving it from four fields. productionAcceptable is the exported fail-closed gate (only 'clean' passes; absence reads not-judged), and rulvar run --acceptance-policy production (also on resume) applies it after --strict's mechanical checks, refusing suspended runs as unsettled, with ONE stable JSON reason line on stderr per refusal. --strict itself stays byte identical, documented exits included: the experiment's run settled ok under a standing waiver with three unsupported citations, and the pipeline reading strict's exit shipped it.
  • 0c9941d: The judge pair rules together, and the draft becomes a map (RV4210, the sixth comparison experiment). With NO round armed anywhere, the final claim pass and the citation audit's first pass now dispatch CONCURRENTLY on the same immutable document, with the verdicts processed in the historical order (the claim pass's typed refusals fire first), so every decision, meta stamp, and refusal reads exactly as the sequential path wrote it; the run under audit spent 100.8 seconds of its tail waiting for the claim judge before the citation judge could start, on two verdicts that read nothing of each other. Any armed round (claim repair, coverage arm, audit repair, merged) keeps the strict sequence byte for byte, because a round rewrites the document and the audit must read what ships; wire counts are unchanged, and the acceptance tail already funded both passes. And finishValidation.draftPolicy: 'digest' joins 'contract': for configurations that do not use skipWhenDraftValid, the coordination prompt asks up front for a compact structural evidence map (one list row per planned section naming its claims and evidence) and the gate enforces the inversion deterministically, at least one list row and at most DIGEST_DRAFT_MAX_WORDS (400) words, so the draft can neither stay prose nor decay back into it (the run's contract-policy draft cost 344.8 seconds of model output and was rewritten whole by the composition). A digest is never a shippable candidate: skipWhenDraftValid and fallbackToValidDraft refuse typed beside it.

1.246.0

Minor Changes

  • d165b0c: The profile hash sees the constructions, and counts what it cannot (RV4101; the debt RV4009 named). The regulated floor binds what flows through options, but the postures that decide whether a tool list can drift beneath a run (an mcp() source's drift and discovery bounds, RV1516/RV1808) or whether a provider executes tools outside the permission chain (the AI SDK bridge's providerExecutedTools seam) live on CONSTRUCTIONS the options never see; RV4009 excluded them from the hash by principle ("a hash must not imply what it cannot verify") and named them in prose. This train makes the verifiable part verified. A risk-bearing construction now exposes describeRegulatedPosture(), a PURE snapshot of what was chosen at build time (no wire, no connect): mcp() reports { drift, bounds }, bridgeAiSdk() reports { providerExecutedTools }, both implemented this release. compileRegulatedProfile walks every construction its options reach (adapters, named toolsets, profile toolsets, each object once), REFUSES a loosened posture by field name (construction['mcp:http:...'].drift must be 'refuse'; bounds must be declared; the bridge must deny), refuses outright a descriptor of a shape or kind it cannot judge, and folds the sorted descriptors into the hashed posture map under construction, beside an unrecognized count of the constructions that exposed nothing, so the hash names its own blind spot instead of implying totality. REGULATED_VERSION bumps to 2 (regulated:2:<hash>): the map's meaning changed, and a v1 fingerprint must never collide with a v2 reading of the same options. Deliberately open, by name: a construction mutated AFTER compile time; the descriptor is a snapshot, not a lease, and the first-use re-assertion is the RV1608 template applied in its own train. Probes pin the drift refusal and the blind-spot count.
  • d59f4a0: The construction posture holds at the seam (RV4102, the RV1608 template; closes the window RV4101 named). The descriptor is a snapshot, and the window between compileRegulatedProfile and the run is where an in-process mutation could walk a moved posture beneath the hash that licensed a different one. The compiled options now carry each attested construction wrapped: every use of its risk seam (tools() on a tool source, stream() on an adapter) re-reads describeRegulatedPosture() and re-judges it with the same judge the compile used, so a posture loosened after compile refuses with the identical field-named error (construction['mcp:...'].drift must be 'refuse'), and any other movement (a rename, a changed bound, a vanished descriptor) refuses naming the drift and the remedy: recompile deliberately instead of mutating beneath the profile. Everything else passes through untouched, close(), caps(), and identity fields included, and a profile with no attested constructions compiles to byte-identical options with no wrapper at all. The cross-process half of the window never needed one: a mutated construction compiles to a different profile hash, and the RV3210 resume assertion refuses it. The real mcp() case is pinned end to end: a config object whose drift flips to 'rekey' after compile refuses at the next tools() before any wire. A probe collapses the use-time comparison and requires the moved-posture test to go red.
  • 46907ac: The regulated floor requires the claim machinery, and the journaled waiver is the authority on resume (RV4103, RV4104; plan 41's audit of the plan-40 surfaces). Two asymmetries, both on the floor's own doctrine. First: compileRegulatedProfile refused a loosened claimConsistency field but admitted an orchestration that omitted the block entirely, and absence is the loosest claim posture there is: no claim pass, no coverage grade, no strict-final gate, exactly the unarmed shape the third comparison defeat ran. The floor now refuses the omission typed (orchestrate.claimConsistency must be declared with stage 'final' or 'both'), symmetric with citationAudit; it deliberately does not autofill, because the pass needs a judge model and an estimated cost the floor would have to invent. Second: the strict-final waiver's expiry was re-read from the live clock on every finalize execution, while the waiver's own TSDoc had promised since RV4003 that the verdict is "evaluated once at the enforcement point and journaled". A run that waived its gap, crashed before the terminal, and outlived expiresAt re-rendered the exception on resume and became unfinishable with its exception already on the record. The enforcement point now reads the journaled claim_coverage_waived decision first: a recorded waive licenses the replayed acceptance verbatim (principal, reason, expiry, waived grade), bound like the RV603 synthesis skip to the judgedHash it licensed, with pre-field entries staying reusable so journals in flight roll forward. The consumption recheck contrast (RV4008) stands deliberately: an approval's effect has not happened yet at recheck time, a waived acceptance's decision already has. Probes pin both: the absence refusal must not compile away, and the collapsed journal lookup must fail the crash-and-outlive replay.
  • 9929ad3: Semantic repair rounds own their ledger rows, and a repair wire never walks past a nearer verdict (RV4105; plan 41's audit of the RV4002 ledger). Two defects, one lane. First: the dispatched claim round (RV3307) and citation round (RV4004) counted into a bare semantic tally with no row, so a reader of semantic: 2 could not tell which machinery asked without cross-reading two metas, and the rounds' own wires had no home. Each dispatched round now folds into its own row: stage: 'semantic', the dispatch entry's seq, and a trigger ('claim' or 'citation') read from the new costAttribution.repairTrigger stamped at dispatch beside the RV3905 phase; journals written before the stamp fold the row with the trigger honestly absent (NOT RECORDED, RV1209). Second: the wire pairing scan attached a phase: 'repair' billing row to the nearest earlier same-scope row WITHOUT a wire yet, which let a wire walk PAST an already-claimed or rowless neighbor onto an older repair's row; concretely, a semantic round following a composition repair whose own billing row never landed (the RV2008 async posture allows exactly that) signed the round's money onto the composition's row, and its costUsd named another repair's price. The window now closes at the next row of the scope: the first wire in a window claims the row (wireRef stays "the first incremental billing row after this verdict"), and a later wire stays unattached rather than misattached, absence over a guess. Probes pin both: the collapsed row push and the reopened scan window each fail the misattribution regressions.
  • 1790a6a: The pair is primary over the coordination bucket, and the compile floor judges its own intake edges (RV4106, RV4107; the tail of plan 41's audit). RV4106: the RV4010 coordination bucket checked the dispatch ROLE before the evidence/counter pair, so a synthesize (or orchestrate) dispatch that carried BOTH a declared evidence contract and an executed-call counter was swallowed by the bucket and its declared contract fell out of calibration entirely, where before RV4010 it had been an observed row. The pair is primary now: a coordination-side dispatch carrying both sides folds as an observed row like any worker, and the bucket takes the contract-less rest. RV4107, three edges of compileRegulatedProfile: budgetUsd must be a positive finite ceiling (a bare typeof check let NaN and Infinity compile into the posture map as a ceiling that bounds nothing); the hashed scope is the NORMALIZED copy (the engine drops junk fields downstream, so a junk field moved the hash while the effective posture stood still; the same normalizeExecutionScope call now refuses an empty or malformed scope at compile time, and the copy rides the compiled run so later host mutation of the passed object cannot move what genesis records); and citationAudit.resolve is judged a function at compile time instead of orchestrate intake, because a declared audit that cannot resolve a single anchor is a posture in name only. Probes pin the pair primacy, the finite ceiling, and the normalized-scope hash.

1.245.0

Minor Changes

  • b4d47a8: One acceptance-tail formula for the runtime gate and preflight (RV4001, the fifth comparison experiment). The RV3907 boot gate shipped with its own inline arithmetic and preflight kept another on different terms: the experiment's plan passed preflight green at a $4.54 cap and the runtime refused it typed at $4.82 before the first wire, and the gate's own copy additionally undercounted stage: 'both' at one worst-case judge pass where the posture dispatches two (three with an armed repair round). The exported acceptanceTailRequiredUsd (with acceptanceJudgePasses and formatAcceptanceTailTerms, the dispatchProjectionReserveUsd precedent) now serves both callers term for term. Preflight mirrors the missing declarations (orchestrator.synthesis.estCost, finishValidation.estRepairCostUsd), reports budget.orchestrator.acceptanceReserve ({ declared, requiredUsd, effectiveCapUsd, fits, terms }; exact fill fits, exactly the gate) whenever the posture is declared, and surfaces an unfit tail as the acceptance-reserve-unfit finding: an ERROR under declared 'require' (the run would refuse to start, and the experiment's harness gated on error findings only) and a warning under 'warn'. A differential grid pins runtime and preflight to the same number and the same printed arithmetic; three mutation probes pin the summed terms, the 'both' pass count, and the error severity.
  • dee6db4: The workflow answers for its own repairs (RV4002, the fifth comparison experiment). The run paid for exactly one repair (a coordination draft rejected by three validators, healed by a sectional resubmission, one more wire at $0.186) and every terminal aggregate answered truthfully for its own stage while no surface answered for the workflow: the independent judge rebuilt the count from the raw transcript and the repair wire's money drowned in 'coordination'. The exported repairLedgerFromJournal folds the workflow-wide ledger ({ draft, composition, semantic, total } plus one row per granted repair with its stage, verdict seq, failed validators, spliced sections, and the repair wire's ref and price when the billing lane covered it); the acceptance envelope carries repairs computed by the same fold over the run's own snapshot, so live and post-hoc agree by construction. The draft gate journals its voice (orchestrator_draft_gate on rejection and on the healing sectional acceptance), finish-validation decisions carry their stage and spliced markers, and the granted repair turn's own wire is stamped phase: 'repair' (ProviderCallRecord.phase), which all three byPhase folds split out of the hosting dispatch's bucket. rulvar cost-audit prints the ledger when the journal proves one, byte parity otherwise; pre-RV4002 journals fold with unstagedVerdicts named, a floor, never a guess; clean runs keep every byte (all 61 frozen fixtures verify unchanged). Kit: coordination-draft-repair pins the experiment's exact shape ({ draft: 1, composition: 0, semantic: 0, total: 1 }, the gate decisions, the stamped wire) and sectional-repair-round pins the semantic round's ledger; four mutation probes pin the wire stamp, the gate's journal voice, the round count, and the CLI line. journal-shape-revision: the wire-level phase stamp is an additive journal evolution, and the frozen cassettes whose flows contain a refused finish exchange are re-recorded under it.
  • b85c113: The coverage grade becomes a gate under a declared policy (RV4003, the fifth comparison experiment). The run's claim pass covered 54 of 74 citing sentences, graded itself 'partial' honestly, MET its own declared 0.72 coverage target at 0.7297, and the run still shipped three unsupported citations inside exactly the uncovered fraction: every ratio floor held and none of them binds the grade. claimConsistency.coveragePolicy: 'strict-final' refuses acceptance typed when the FINAL pass's grade is anything but 'full' (partial, vacuous, critical-uncovered, judge-declined, judge-failed alike), unless the declared waiver { principal, reason, expiresAt? } stands: the waived acceptance journals a claim_coverage_waived decision and carries claimCoverageWaiver on the envelope verbatim beside the meta, so a non-full grade on a strict run always names who accepted it and why; an expired waiver refuses exactly like none. The default 'observed' keeps every existing byte; the policy requires stage 'final' or 'both', and a waiver without the policy is a ConfigError. Kit: strict-coverage-policy drives both arms (the typed refusal and the journaled waiver); probes pin the gate and the expiry; the orchestration guide names the doctrine ("0 findings" was never "semantically verified" without its denominator) and the doctrine-pin gate now requires it.
  • bc556e7: The citation entailment audit (RV4004, the fifth comparison experiment): the independent judge's method, internalized. The run's built-in verification judged VALUES (cited-value), TARGETS (citation-targets), and CONSISTENCY (the claim pass, child readings against draft claims), and the shipped answer still carried three citations whose cited lines do not entail the sentences citing them, every one mechanically valid, value-clean, and invisible to a pool that held no reading of those files (20 of 74 citing sentences had no candidates at all; child-against-final pairing can never cover them). citationAudit runs over the FINAL document: a deterministic stratified sample (per H2 section, seeded from the audited document's own hash, replay-stable, capped), excerpts read through the host's pure snapshot resolver (the citedValueValidator channel; a citation whose first cited line does not resolve is unsupported mechanically), one bounded judge invocation ruling supported | partial | unsupported per sampled citation. The envelope carries citationAuditMeta and citationFindings; onFound: 'report' | 'fail' | 'repair' decides the consequence, with 'repair' riding the RV3307 bounded round (one more composition carrying the findings, a fresh audit from the repaired document's new hash, a configured claim pass rejudging the rewritten document, survivors failing typed; arming it beside the claim round is a ConfigError, one bounded round per run). The declared judge.estCost enters the RV4001 acceptance-tail formula on both the runtime gate and preflight (citationAudit mirror on the preflight orchestrator spec), one pass or two, with the round composition and the claim rejudge priced. Kit: citation-entailment-audit drives the flagship shape (the unsupported citation caught, the supported control clean, the armed fail typed); three probes pin the mechanical unresolved verdict, the fail gate, and the round's re-audit.
  • 9f11d29: The wire capacity of a plan has one exported source (RV4005, the fifth comparison experiment). The run's own terminal answer modeled this runtime's repair round as one extra wire (34 to 35) and multiplied retry share by 1 + r, losing the decisive correctness point to arithmetic the codebase already states: a triggered round is TWO wires past the plan (its composition PLUS the rejudge, RV3307) and r retries over a base of B wires multiply totals by 1 + r/B. wireCapacityEstimate prices a declared plan (child, coordination, synthesis, judge, extract wires) into { baseWires, repairRoundDeltaWires: 2, mechanicalRepairDeltaWires: 1, wiresWithRound, roundOverheadShare }, and retryWireMultiplier is the retry share formula; golden tests pin the healthy 34/36/5.88-percent example. The budgets guide gains the worked example and two REQUIRED doctrine pins hold the two-invoice round cost and the retry formula on their pages.
  • 19bcea0: The pre-wire provider intent (RV4006, the fifth comparison experiment's P0.5). Receipts journal after a wire settles, so the wire most exposed at a crash is exactly the one being paid for: between dispatch and receipt, a death leaves money the journal never heard about. defaults.billingReceipts: 'intent' journals a provider-intent decision before every dispatched wire attempt (awaited, the executor ledger's intent-before-effect rule: a failed intent append refuses the dispatch), keyed by dispatch seq, ordinal, and attempt, carrying the serving model, role, and a sha256 request fingerprint; receipts stay awaited as under 'awaited'. An intent with neither a receipt row nor a settled terminal covering it is a wire with UNKNOWN outcome: the exported openWireIntentsOf fold names them, the invoice carries the openIntents lane (no invented dollars), rulvar cost-audit prints it, and a resume that finds one refuses the blind retry typed until ResumeOptions.acknowledgeOpenWireIntents: true is passed, which the new segment journals as open_wire_intents_acknowledged. Dispatch stays at-least-once with attempt binding; the default 'async' and 'awaited' postures keep every byte. Kit: wire-intent-unknown-outcome drives the reconstructed crash window through both resume arms; probes pin the quota-arm intent, the resume gate, and the receipt closure.
  • 60b461c: The bounded execution scope (RV4007, the fifth comparison experiment's P0.4). Who a run executes for, as the host names it, carried WITHOUT LOSS and never interpreted: RunOptions.scope ({ tenant?, account?, project? }, own properties, non-empty strings, at least one field, copied at intake so later mutation moves nothing) records at genesis into RunMeta and a journaled execution_scope decision, is immutable for the run's life (no resume door), rides the invoice header as executionScope (a pure fold from the entries, so a FinOps pipeline reads the owner off the money document), travels in the export bundle via its meta, and ResumeOptions.scope asserts it back (mismatch refuses typed before ownership; a supplied scope over a run that recorded none warns). On the provider side, ProviderAdapter.scopeKey names the ACCOUNT within a family: the retention transport then keys provider-raw blocks by (family, scopeKey) instead of family alone, so cache handles and thinking blocks minted under one account never ride a request served by another; undeclared adapters keep the family-wide sharing byte for byte, and routing, pricing, and quota keys are untouched. The store conformance kit pins the RunMeta round-trip; probes pin the genesis decision and the retention separation. Attribution envelope, not IAM: tenancy semantics stay host decisions.
  • 61e3a1a: A grant can be taken back (RV4008, the fifth comparison experiment's P0.6 revocation half). An allow is a recorded fact history cannot unwrite, so handle.revokeApproval(key, { principal, reason }) makes revocation its own journaled truth: a still-open approval is denied through the ordinary first-closing-wins arbitration (races stay deterministic by the journal), and a RECORDED allow gains an approval_revoked decision that beats it at the CONSUMPTION recheck, the moment the allow is about to license the effect, live or re-matched on resume, so an allow granted, crashed over, and revoked never dispatches its tool; the typed deny names the principal and the reason. The grant is boundable too: an allow resolution may carry expiresAt (validated at the registry; the recheck fails CLOSED on an unparsable expiry recorded past it), and an expired grant denies exactly like a revocation. ApprovalDecision gains entryRef and expiresAt (additive). Revocation gates dispatch and never chases it: an executing or executed tool is outside its reach, the documented at-least-once window. Probes pin the recheck and the fail-closed expiry.
  • a156b81: The regulated floor is one call (RV4009, the fifth comparison experiment's harness lesson, previously gated behind its own word and confirmed with plan 40). Every assurance posture this library grew is an opt-in knob, which is correct for a library and hazardous for an unreviewed config: the compared runs armed gates to observe because assembling the posture by hand was the only path. compileRegulatedProfile({ engine, run, orchestrate? }) composes the floor in one place: strictApprovals armed (the RV1507 monotonic mode), billingReceipts: 'intent' (RV4006), determinism: { mode: 'error' }, strictPricing with a required budgetUsd under budgetPolicy: 'immutable-lifetime' (RV3902), a required execution scope (RV4007), and, when orchestrate options ride along, acceptanceReserve: 'require' (RV3907/RV4001), a declared citationAudit (RV4004), and coveragePolicy: 'strict-final' (RV4003); a profile that declares tools must carry its toolset attestation (RV1607). A field that loosens the floor REFUSES typed, naming the field, never a silent overwrite. The returned profileHash (sha256 over the enforced posture map) rides run.configFingerprint as regulated:1:<hash>, so genesis records it and a mismatched resume refuses through the existing RV3210 machinery: no new meta surface, no engine branch, the compiled options are data. The hash deliberately excludes construction-side postures the options never see (MCP drift: 'refuse' and bounds, the AI SDK bridge's providerExecutedTools: 'deny'); the production profiles guide names them beside the call, because a hash must not imply what it cannot verify. Probes pin the refusal (a loosening must throw, not compile away) and the hash's coverage of the ceiling.
  • 0bd7045: The calibration fold names the coordination side's own executed tool calls (RV4010, the fifth comparison experiment). toolCalibrationFromJournal gains coordination ({ dispatches, toolCallsUsed }): terminal dispatches whose role is orchestrate or synthesize with the RV3002 counter journaled, the spawn/await/finish exchanges no evidence contract ever binds. The experiment's telemetry counted 407 tool starts against 390 worker calls and the 17-call coordination remainder had to be explained by hand because those counters drowned in budgetOnly as if declared contracts had lost their pairs; workers' counters plus this bucket now account for a dynamic run's executed tool calls. Absent when no counted coordination dispatch exists, so those reports keep their bytes; a probe pins the bucket.

1.244.0

Minor Changes

  • 38d839a: RunOptions.budgetPolicy: 'segment' | 'immutable-lifetime' (RV3902, the fourth comparison experiment): the regulated posture the docs used to promise by accident is now a real, opt-in invariant. Default 'segment' is today's behavior byte for byte. Under 'immutable-lifetime' the posture is recorded in RunMeta at genesis (only the non-default is written; the store conformance kit holds stores to the round-trip) and restored on every resume, and a resume carrying ANY applying ResumeOptions.run override refuses with a typed ConfigError before ownership, meta writes, or any append, raising and lowering alike; the empty run: {} object stays the documented no-op, a bare resume stays a pure replay, and a store that drops the field degrades to 'segment' (the door works again), never to an invented refusal. The fault kit gains the budget-policy-immutable scenario (typed refusal, zero wires, zero durable mutations, bare replay intact); two mutation probes pin the refusal gate and the genesis recording. The source TSDoc sweep retires the last immutable after start comments (engine, budget, termination, orchestrate, plan), and the docs doctrine pins now scan docs/api too.

  • ce13b0f: parseTerminalEnvelope (RV3903): a runtime contract gate over the terminal envelope, exported beside the type. The one producer is a compile-time promise, and the fourth comparison experiment probed the built dist straight past it: the typed copy accepted status: 'green', NaN dollars, and negative counters without a sound. The gate validates the contract fields (enum status/completion, finite nonnegative money with totalUsd <= grossUsd, usage and counters, settledReason only beside settled: false, the costBasis/provenance literals, the typed error shape) and refuses with a ConfigError naming the field and the defect; unknown top-level fields pass through, because the contract evolves additively. persistedTerminalEnvelope now runs every journal-rebuilt envelope through the gate under one catch with the fold's own overflow guard, refusing as the new typed reason 'malformed-envelope' instead of serving a green envelope or throwing bare at a serving surface; the server's non-live responses inherit the gate by construction. Four mutation probes pin the enum check, the money guard, the settledReason coherence, and the persisted wiring.

  • 4fa23e3: The verdict lineage on the acceptance envelope (RV3904, the fourth comparison experiment): the run's terminal read findings: 0 over a lineage whose first judge pass had caught a real contradiction, and only the journal could say so. Under the armed claim repair round, claimConsistencyMeta now carries passes, firstPassFindings (when passes exceeds 1), and semanticRepairRounds, so a repaired verdict is distinguishable from a clean first one on the envelope; absent fields mean NOT RECORDED (no round armed, or an older journal), and the mechanical repairsUsed keeps its byte contract untouched. Beside it, the acceptance envelope gains deterministicPatches (the RV3801 machine-patch aggregate: accepted decisions, total patches, the last patch's canonical before/after hashes), derived from the same journaled finish decisions the patches live on, so live and resumed envelopes agree by construction. The sectional and deterministic-patch kit scenarios pin the lineage and the aggregate; two mutation probes pin the pass count and the envelope block; the observability guide documents what zero findings does and does not mean.

  • 6841c69: Dynamic stage phases (RV3905): the fourth comparison run's cost.byPhase read 100% unknown over stages the journal held plainly apart, because the fold reads costAttribution.phase and the dynamic orchestrator never stamped one. Each engine-owned dispatch now names its stage on the dispatch scope state: fan-out (children), coordination (the loop and the forced-finish wake), composition (the synthesis invocation and incremental notes), judge (the claim passes), repair (the bounded claim repair round). The stamp is policy, never identity: journal keys and resumed runs are untouched, live and journal folds read the same field by construction, and an explicit host ctx.phase around the orchestration wins, so the stage names fill only the vacuum (phase-wrapped hosts also stop losing their bucket on spawned children, which never inherited the calling phase before). Two mutation probes pin the fan-out and judge stamps.

    journal-shape-revision: dynamic dispatches now journal costAttribution.phase (an additive policy field; old journals replay unchanged and fold the absent field under unknown exactly as before), so the committed plan cassettes are re-recorded with the stamped stage names.

  • f56721d: InvoiceRow.agentType? and InvoiceRow.label? (RV3906, the fourth comparison experiment): in dynamic runs the scope grammar nests every orchestrator spawn under one agent:<seq> bucket, so byScope legitimately reads two buckets and per-child money used to require a join through the journal. Every row of an attributed terminal (record rows, unattributed slice rows, and remainder rows alike) now carries the spawn's agentType and the dispatch label from the terminal's cost attribution; the empty agentType folds as absent (the root's honest non-type), and rows of journals recorded before attribution shipped stay byte for byte. rulvar cost-audit prints the same cut as a by agentType: line and carries it as invoice.byAgentType in the JSON form, both absent on pre-attribution journals. Cardinality pins unchanged; one mutation probe pins the threading.

  • c894a43: budget.acceptanceReserve: 'warn' | 'require' (RV3907, the fourth comparison experiment): preflight has long priced the acceptance tail and warned (reserve-line-headroom, orchestrator-working-room), and the experiment's run started anyway with both warnings on record. Under 'require' the declared acceptance tail (the held synthesisReserveUsd, the claim judge's estCost times one plus the armed semantic repair round, the declared finishValidation.estRepairCostUsd, and the armed round's declared synthesis.estCost composition floor) plus one coordination turn floor must fit the effective cap at exact fill or better, or the run refuses with a typed OrchestratorCapConfigError BEFORE the first wire, journaling an acceptance_reserve_refused decision that names every term. Undeclared estimates contribute zero, so the gate binds exactly what the host declared; the default 'warn' keeps today's behavior byte for byte. The fault kit gains acceptance-reserve-refusal (typed refusal, zero dispatches, term-by-term decision); boundary tests pin exact fill as admission; one mutation probe pins the gate.

  • f6944a3: The judge wire economy (RV3908, the fourth comparison experiment): every final claim judge paid TWO wires, and the extract wire re-sent the whole conversation at the full input rate with zero cache read; the run's extract role cost $0.28, 5.2% of all money. Two fixes at the agent loop, both verdict-neutral: (1) even when the separate extract invocation is armed (extract routed to a different model than the loop), a final loop turn whose text already validates against the schema IS the structured result and the wire is skipped, exactly the semantics of the no-separate-extract path; the separate invocation stays the repair lane for prose-wrapped or malformed finals. (2) The separate extract request now compiles the same prompt-cache hint the loop turns compile (RV2006 posture: explicit-caching adapters only, transport-level only), so the repair lane's re-sent prefix reads from cache instead of re-paying the input rate. Two mutation probes pin the ride-along guard and the cache compilation.

  • 23fd0e0: The stale-doctrine corpus class and the proactive sectional reminder (RV3909, the fourth comparison experiment). The corpus gains stale-doctrine-echo: a draft echoing a DOCUMENTED doctrine while the pool holds the diverging source fact, both sides cited, the experiment's decisive failure shape ("immutable after start" echoed from a guide six weeks stale into a pool that never carried the source side); the honest formulation naming the override door is pinned as a test-side control (the source-claim pairing is polarity-blind by design, and the exoneration belongs to the judge, who now holds both sides). The sectional repair round's prompt gains a deterministic evidence-discipline reminder (the experiment's rewritten section birthed two new evidence-grade offenders that the RV3801 patch then healed; a prompt line is cheaper than a healed failure), present only under the sectional block so every other prompt stays byte-identical; the kit's sectional scenario pins the line present in the round and absent from the initial composition. Two mutation probes pin the reminder and the class roster.

1.243.0

Minor Changes

  • 746d1f4: The finish loop performs the evidence-grade prescription host side (RV3801). The third comparison run died fail closed twice on one failure class: sentences in the graded register with no artifact, whose verdict already told the model exactly which sentences to fix and exactly which id to write; the initial composition spent the mechanical pool on it, and the repair round's candidate hit it again with nothing left. evidenceGradeValidator, with the runtime's runId in hand, now attaches structured repair hints to its failure (FinishRepairHint: the offending sentence's exact offsets and bytes, and the prescribed insertion), and the finish loop, when EVERY failure of a string candidate carries hints, applies the edit itself: the id lands inside each offending sentence before its trailing terminator, every other byte stays identical, and the FULL validator set re-judges the patched document. A surviving patch is an accepted verdict with no provider wire and no repair spent; the decision journals it (deterministicRepair: before and after hashes, the patch windows, the healed failures), the healed failures still feed the HOST VALIDATION LESSONS block, and everything short of a surviving patch falls through to the ordinary model repair pool with the original verdict bytes. Masking is excluded by construction: the claim judge rules on the PATCHED document, so an inserted id can satisfy provenance mechanics but never protect a false claim from the semantic pass. The fault kit gains deterministic-provenance-patch (the adversarial arc on the real engine: a false positive production claim healed mechanically, then caught semantically, the lesson carried into the round), and validator-guidance-conflict now pins the c3 trap healing in ZERO model repairs with the guidance bytes journaled on the healed verdict.
  • 009b29c: The convergence hold grows its mechanical leg (RV3802). RV3701 holds the repair round's verdict money and RV3602 gives the round its own mechanical pool, but the one repair turn that pool can grant was funded by nothing: the third comparison run's round entered exactly that turn's price short of certainty. The round now holds a second named leg beside the verdict money from the moment it is admitted, sized from the declared finishValidation.estRepairCostUsd first (a new opt, refused typed unless a nonnegative finite number), else from the run's own observed last mechanical repair window (lastMechanicalRepairCostUsd, a new pure fold over the journal's synthesis candidates, which also gain spanSeq so the pairing never crosses invocations), else zero and inert. The leg joins the projected admission sum and both remainders (repairReserveUsd on the account state and view), a refusal names BOTH legs in its printed arithmetic, and the release is STAGED: the mechanical leg frees at the round invocation's first journaled finish verdict (a repair verdict is about to spend it on the granted turn; an accepted one never needed it), while the verdict leg lives until the judge dispatch as before. The fault kit gains repair-round-mechanical-reserve (the ceiling where the round could pay its composition and verdict but not the granted repair: pre dispatch refusal, both clauses named), and two mutation probes hold the admission sum and the staged release.
  • 1674cbe: The claim repair round is sectional when it can be exact (RV3803). The third comparison run's round regenerated the whole 43k character document to consume findings living in a handful of sentences, inside a tail that was 80.1 percent of the run's wall. The round now plans its repair before dispatching (sectionalRoundPlan, exported): each judged finding's excerpt is located in the accepted pre-repair document through a collapse-aware scan and owned by the nearest H2 heading above it; when every excerpt locates and the markers are unique, the round's prompt retains the accepted document and asks for ONLY the target sections through the RV808b splice vocabulary, the host splices the resubmitted bodies into the retained document with every other byte identical, and the FULL validator set plus the final judge rule on the spliced whole. Mechanics refusals journal nothing and spend no repair; the model may still resubmit the full document; and every inexact plan (no headings, duplicated markers, an unlocatable excerpt, no finish contract) falls back to the FULL regeneration, byte for byte the historical round. The fault kit gains sectional-repair-round (byte identity of untouched sections, whole-document judging, no mechanical repair spent) and sectional-repair-round-fallback; two mutation probes hold the splice and the fallback.
  • bd096bc: The cost report gains the byScope rollup (RV3805). The children versus whole workflow cut used to require hand-aggregating invoice rows (the third comparison analysis did exactly that to say the children cost $2.75 of the $5.58 run); CostReport.byScope now carries one addressable row per journal scope under the same net inclusion policy as totalUsd, so the rows sum to it, on both builders through one rule (scopeBucket): the root's OWN scope is the empty string by construction, present data rather than an absence, so it folds under the named root bucket; children keep their scope strings verbatim; and unknown stays reserved for a scope that is truly missing, the RV3604 fallback. Live accumulation and the pure journal fold agree by construction, abandoned subtrees contribute zero exactly like the net total, and one mutation probe holds the parity.

1.242.0

Minor Changes

  • 6e3438e: The claim repair round pays for its verdict up front (RV3701, the third comparison experiment's arc). The round is a two invocation bargain, and admitting its composition on money that cannot also seat the second judge pass buys a candidate nobody can rule on: the budget now holds the verdict money (a new convergenceReserveUsd hold with exactly the synthesis reserve mechanics: counted by projected admission and the layer-2b clamp, released to the pass it was held for) from the moment the round is admitted until its judge dispatches, sized from the declared claimConsistency.judge.estCost first (the same figure that pass reserves at admission, so the guarantee is exact) and else from the run's own observed post draft judge price. A round the budget can only start now refuses through the honest pre dispatch decline before any wire call. And when the verdict still cannot be ruled after a dispatched round, the typed failure carries the round context (roundDispatched: true, repairsUsed: 1, preRepairHash, the unconsumed findings) instead of describing a draft death while a paid repaired candidate sits in the journal.
  • ba5cf67: The host rejection becomes legible at the span level (RV3702, the third comparison experiment's arc). The third comparison run's reader saw the repair round's composition span end cancelled with both wires fine and had nothing to name the layer split. The finish contract's final rejection now aborts with the exported FINISH_REJECTION_ABORT_REASON, and the settle layer stamps hostRejected: true onto the terminal agent entry (a policy field, replay carries it) and the live agent:end event; a defective throwing validator aborts with its own distinct reason and never stamps, because a host defect is not a verdict on the candidate. Both surfaces of the critical path cut count the stamps as hostRejectedSpans (unconditional, zero when none), and the invocation table's rows carry the flag, so a reader can tell a document refused by host validation from a provider failure without a journal dig.
  • c2d1531: The price table provenance grows its content tail (RV3703, the R11 remainder of the third comparison experiment's arc). Every pinned pricing segment, and the snapshot's top level, now carries rowsHash (sha256 over the canonical JSON of the pinned rows) and the ratesVerifiedAt freshness range of its dated rows (oldest and newest, absent when no row is dated). The version string is a label the table author chose, and the arc held a price defect a label cannot expose: the hash is the content, so two tables sharing a version string but disagreeing on rates are distinguishable in any stored export, and two folds of one journal always derive the same hex. Computed at read time from the pinned bytes: the journal is unchanged and every existing pin gains the tail; invoice provenance and the CLI pass the segments through unchanged.

1.241.0

Minor Changes

  • dbcdd24: The candidate milestones ride both critical path surfaces (RV3605). The third comparison run composed a mechanically accepted candidate at its 103rd journal seq and failed typed roughly 25 minutes later; the latent document its judge later scored 6.65 existed the whole time, and nothing on any timing surface said WHEN it materialized, so the analysts dug spans by hand. reduceCriticalPath and criticalPathFromJournal now report firstCandidateMs (run start to the first completed composition-side synthesize span's end, when a candidate deliverable first existed) and lastCandidateMs (the same anchor to the last one), classified by the same one classifier as the RV3404 stage split, so both surfaces read the same run identically by construction. On a terminal carrying deliverableAccepted: true the last milestone is the time to the accepted deliverable; on a failed run it is when the last losing candidate settled, and the docs say to pair it with the acceptance verdict instead of reading latency off an error terminal, the comparison rule the third experiment wrote down. The journal side needs one segment (wall figures anchored at the first stamp) plus the full labelling condition of the split (an unlabelled span could be a judge, and a judge is not a candidate); absent otherwise, never guessed.
  • 7ae7243: The cost fold names its fallback buckets (RV3604). The third comparison run's report read byPhase {"": 5.58} for the whole run and a '' agentType bucket beside the named ones: the journal fold's phase fallback WAS the empty string, and the agentType ?? 'unknown' fallback let an empty string straight through, so the report minted keys no downstream table can address. One exported rule now covers every path: attributionBucket folds absent AND empty phase and agentType under 'unknown', applied at the two report boundaries, the journal fold and the exported live builder (which normalizes and MERGES the accumulated live keys, a host supplied '' beside an existing 'unknown' becoming one bucket), so live spend and the replay accumulation of a resume land in the named bucket through the same boundary the fold enforces. The sum invariant holds on every surface: every breakdown still sums to totalUsd, live, replayed and folded alike, and CostReport.byPhase/byAgentType docs state the named fallback. The span level provider versus host outcome marker considered for this train stays a recorded candidate: the terminal already carries the machine truth under finishValidation since RV3601, and agent span status vocabulary is its own contract.
  • 4f832c4: The mechanical repair pool belongs to one composition invocation (RV3602). finishValidation.maxRepairs used to count non accepted verdicts run wide, so the bounded claim repair round (RV3307) entered with zero mechanical retries whenever the initial composition had spent its own, and under the default bound of one its first regression was final by construction; that arithmetic is how the third comparison run died honest but unconverged with $1.42 of headroom left. The pool now restarts at each composition dispatch: the boundary is the journaled verdict count at dispatch (replay derives the identical index from the identical prefix, no new journal fields), the cycle 73 contract generation rule still applies on top, and validators bound to the coordination loop keep the run wide reading byte for byte, one loop being one invocation. Worst case stays bounded: at most two invocations (the initial and one repair round), each granting at most maxRepairs repair turns; preflight's RV3402 working room term already prices the round at the declared synthesis reserve, which is the host's estimate of exactly one invocation with its repairs, and the RV2504 reserve tail sizing needs no doubling (comments and guide now say so). The fault kit gains repair-round-own-pool: the frozen third comparison sequence carried to the convergence the old pool made impossible, verdicts repair/accepted twice with repairsUsed restarting at the boundary.
  • 7452d3d: The bounded repair round keeps the lessons the run already bought (RV3603). The third comparison run's repair round regressed provenance, the exact failure class the initial composition's mechanical loop had fixed 18 seconds and $0.16 earlier, because the round is a fresh invocation with no memory of exchanges it never saw. The round's prompt now carries a HOST VALIDATION LESSONS: block beside CLAIM CONTRADICTIONS:, folded only from the journaled finish validation failures of the current contract generation (validator names and reasons, deduplicated, journal order), so a resume re derives identical bytes. Present exactly when the prompt already carries judged findings and at least one rejected attempt exists: the initial composition predates any findings and a clean history folds nothing, so every existing prompt stays byte identical. Capped at FINISH_LESSON_CAP_CHARS (2000) with the dropped row count named, never silent. The repair-round-own-pool kit scenario now also pins the lesson riding the round's prompt and absent from the initial composition's.
  • a4e22bf: The repair round's terminal names which death occurred (RV3601). The third comparison run's bounded repair round dispatched, paid two wires and produced a candidate its own finish contract rejected, and the terminal read could not dispatch with repairsUsed: 0 beside a null judge meta and null findings. A throw carrying the orchestrator_finish_validation source is now its own class: the message names the dispatch and the host rejection, data carries roundDispatched: true, repairsUsed: 1, the judge meta beside the findings, and the finish verdict facts verbatim under finishValidation (the failed validators with reasons, candidateHash, candidateChars, mirrored from the decision the journal already holds; the typed finish failure itself now carries the candidate identity too). The true pre dispatch decline keeps its frame and gains the judge meta plus roundDispatched: false. The engine lifts claimContradictions onto RunOutcome, the journaled settle and run:end beside the meta, from the acceptance envelope or the typed error data alike, under the same defensive posture as the meta lift; the compact terminal envelope keeps the meta alone, its findings count standing in for the details. The fault kit gains repair-round-host-rejection driving the arc end to end on the real engine.
  • 82df4af: Two hygiene defects the 181st session tripped over, made structural (RV3606). First, pnpm --filter <package> test exited 0 silently for every workspace package: no package declared a test script and pnpm treats an absent script as a no-op, so a targeted test command was structurally incapable of failing. Every package holding *.test.ts files now declares "test": "pnpm -w exec vitest run --project <its name>", delegating to the single root Vitest config through its project filter (docs/11 still forbids per-package Vitest configs; the script adds an entry point, never a config), and a new scripts/package-test-scripts.test.mjs gate keeps the invariant for future packages, including refusing a script that selects a neighbor's project. The one documented exemption is @rulvar/compat: its published artifact is immutable (the compat-immutability gate compares packed bytes against the registry), so its package.json cannot gain a script until a real compat release; its single test still runs in the root suite. Second, the RV508 exactly once tombstone judged link TARGETS as claims: the durability registry's own anchor slug carries the vetted phrase, so linking #at-least-once-dispatch-exactly-once-pay from any page outside the allowlist tripped the sentinel and pages linked the bare page instead, making the one vetted claim unaddressable. The sentinel now cuts [text](target) targets, autolinks and bare absolute URLs before judging, in markdown and source comments alike (an address is a quotation, not a claim), while link TEXT keeps being judged; the cost-audit row in the CLI guide links the precise fragment again.

1.240.0

1.239.0

Minor Changes

  • 74ce99a: The durable wire receipt (RV3405): the payment evidence of the at least once window becomes loss proof and reconciled, in three layers. The billing seam's return type widens to void | Promise<void> and the loop AWAITS a returned promise; defaults.billingReceipts: 'awaited' makes the ctx layer return each RV2008 receipt append so it lands durably before the turn proceeds (the RV601 intent before effect precedent), at the cost of one journal IO await per wire call; a failed append still degrades loudly to the terminal lane and never fails the run; the default 'async' stays byte identical. The invoice gains the orphanedReceipts lane: receipts of agents whose TERMINAL record set does not cover them, the real money a crash between the receipt and the checkpoint makes the resumed terminal forget; coverage is decided by response id whenever either side carries one (a resume redispatch reuses the ordinal, and reading the replacement as the orphan would absorb the double payment the resume honestly made), else by the full coordinate plus byte equal usage; summed apart from the settled totals exactly like unsettled. And reconcileStatement accepts the invoice's receipt lanes: a per request statement row matching a receipt id reports under receiptMatchedRows/receiptMatchedUsd instead of counting foreign, its dollars never entering the totals, the coverage, settleable or monetarySettleable, because money the run did not settle must not close, it must be legible. Probes: the-awaited-receipt-blocks-the-next-wire, the-orphan-is-decided-by-id-evidence, the-receipt-join-explains-the-statement-row.
  • ccd0665: Preflight prices the claim consistency posture (RV3402). The input mirror gains claimConsistency.onFound and claimConsistency.stage, and the static tail arithmetic now counts passes, not declarations: the orchestrator-working-room finding seats the judge estimate across the worst case pass count ('both' is two, an armed 'repair' adds one more) plus one repair round composition priced at the declared budget.synthesisReserveUsd, and its consequence clause names the truth of the armed posture (a declined judge under 'fail' or 'repair' stops the run typed, RV3307, instead of degrading to the journaled verdict). The tail-spawn-budget count gains the same passes and the round's composition, and spawns the judge off the configured pass itself, estimate declared or not. Pairings orchestrate() refuses at intake (repair at the draft stage, repair without a synthesis, carry at the final stage, RV3301) surface as claim-posture-refused-at-intake error findings: the run would refuse to start, and a planner should read that beside the budget findings instead of meeting the ConfigError live. Undeclared postures keep every reading byte for byte. Probe: the-armed-round-is-priced-in-passes.
  • 0c5ce21: The orchestration modes guide documents the bounded repair round (RV3401): a new section between the claim consistency pass and the assurance posture explains when to choose report, carry, fail or repair, the intake contract ('single' synthesis, stage 'final' or 'both'), what one round costs, the fail closed edges (an undispatchable repair round, a dead or declined judge under the armed posture), and what the envelope and the typed failure payload carry (repairsUsed, preRepairHash, repairedHash). The assurance posture section points at the round as the armed polarity with one bounded correction. Docs only: no runtime change.
  • 0616934: The post fan in tail becomes legible on both surfaces (RV3404). Both critical path folds gain the stage split of the judge wall (draftJudgeMs, finalJudgeMs: the exact label is the draft pass, every suffixed variant is a post draft pass, the final judge and the repair re-judge included) and span counters (compositionSpans, judgeSpans: two compositions in one run is the legible signature of the bounded repair round, RV3307), decided by ONE exported classifier, claimJudgeStageOf, the RV3302 doctrine extended from the judge predicate to the stage. The journal fold additionally gains the window itemization a journal CAN answer, postFanIn: the union of settled synthesize spans clipped to the window (synthesisCoveredMs, computed through the same exported unionOfIntervalsMs the live RV710 decomposition uses), the clipped halves under the same all or nothing labelling condition, and unaccountedMs, the window time no settled synthesize span accounts for, deliberately NOT named residueMs because the coordinator's tail time lives in it here and the live residue subtracts that. On the journal side every new field keeps the absence doctrine: absent where the stamps cannot answer, never zero. Probe: the-stage-classifier-holds-the-split.

1.238.0

Minor Changes

  • cf00947: The bounded post judge repair (RV3307), the honest carry for the final stage. RV3301 made stage: 'final' with onFound: 'carry' refuse, because the final pass has no prompt left to ride; onFound: 'repair' is what that host actually wanted: when the final claim consistency judge names findings, they ride ONE more synthesis invocation (the same CLAIM CONTRADICTIONS block, over a prompt that now lies ahead again), the repaired document is judged again, and only a clean second verdict settles. Findings that survive the round fail the run typed (source: 'orchestrator_claim_consistency', with repairsUsed, the pre repair and repaired hashes, and the acceptance snapshot), a repair round that cannot dispatch fails the same way, and a dead or declined judge under the armed posture fails like it does under 'fail', because a gate armed to repair must not pass silently. 'repair' needs stage: 'final' or 'both' and a 'single' synthesis (ConfigError otherwise); the draft pass keeps 'carry'. The headline claimConsistencyMeta describes the last judged document, judgedHash equal to the shipped draftToFinal.finalHash, and the first verdict stays readable in the journal as an ordinary judge entry.
  • c7b9382: One declaration for the shape a host prompts for and gates on (RV3308). The 2026-08-12 comparison run drifted exactly at this seam: the harness prompt named one heading while its finish contract named an older one, the host accepted its own contract, and the common audit refused the answer; separately, the answer's "all publishable packages" table dropped four of seventeen names under a passing shape contract, because no validator could see an enumerable universe. requiredMentionsValidator({ terms }) holds every declared literal against the finish text and names the missing ones with the universe size. OutputContractManifest plus manifestValidators() and renderContractRequirements() derive the gate and the prompt block from the same object, headings, word bounds, citation floor and mention universe byte for byte, so the two surfaces cannot disagree by construction.
  • 88aea96: The ceiling headroom floor can block (RV3310). RV3208's ceiling-headroom-thin finding was always a warning, and the 2026-08-12 comparison harness threw on error findings only: its declared 2 percent floor held against a 2.857 percent plan and the class nobody gated on never spoke. orchestrator.ceilingHeadroomSeverity: 'error' makes a breached floor blocking for exactly such hosts; the default 'warning' keeps RV3208 byte for byte, and the literal fails closed at intake. The orchestration guide gains the assurance posture section: the polarity flip (stage: 'final', onFound: 'fail', coverageTarget with onLowCoverage: 'fail', declared criticals, run facts, a 10 percent headroom floor at error severity) for runs whose output a consumer acts on, beside what the terminal then proves.
  • 6da8d05: The evidence call floor accepts a journal observed prior (RV3309). EvidenceContract.calibration carries the callsPerEntry figure toolCalibrationFromJournal folds from a prior run of the same profile (fractional on purpose) plus a source label; preflightEstimate computes the evidence call floor from the HIGHER of the declared estimate and the prior, never the lower, and names a raise in an evidence-estimate-below-observed info finding. The 2026-08-12 comparison run observed 4.211 calls per entry where the default estimate says 3: a floor computed from the wish is how an evidence contract meets a cap it cannot actually fit. Contracts without a calibration are byte identical, integer floors included.
  • eae5c4c: Every invoice row carries the same usage envelope, and the CLI names its billing basis (RV3311). The 2026-08-12 comparison run's invoice had 77 rows with reasoningTokens and one (the judge verdict extraction) without, so a FinOps consumer folding the column had to know that absence meant zero on exactly one row shape: rows now always carry the field (0 when the provider reported none) and the usage object is detached from the journal entry it was read from. The run summary and the inspect cost view print billing basis: locally-estimated (a local estimate, never a provider statement) beside the dollars, the audit's ask said out loud on the surface an operator actually reads.

1.237.0

Minor Changes

  • 9d6a279: The statement reconciliation names its dollar ground (RV3305, RV3306). settleable deliberately never required a dollar claim, so a usage-only request export that matched on response ids and token counts read settleable: true while carrying not one dollar of provider evidence, the 2026-08-12 audit's counterexample. StatementReconciliation now carries dollarCoverage ('complete' when every matched export row or component line claims money, a row total or a component split; 'partial'; 'none') and monetarySettleable, which is settleable AND complete dollar coverage, the predicate to gate monetary closure on; settleable itself is byte identical and its docs now say out loud what it does not require. The docs honesty pair rides along: agents.md no longer states an unqualified never-pay-twice (dispatch is at-least-once and one partial turn is the documented worst case, matching durability.md), and providers.md documents the new fields.
  • 49a98f6: claimConsistency.stage 'final' with onFound: 'carry' is now a ConfigError at intake (RV3301). The carry posture rides the 'single' synthesis prompt, and the final pass runs strictly after that prompt was built and consumed, so the pair read as a gate while behaving as 'report': the 2026-08-12 comparison run settled ok/complete with a contradiction its own final judge had already named. Under stage: 'both' the carry keeps binding the draft pass, whose findings the synthesis prompt still lies ahead of, and the final pass reports; stage: 'draft' with 'carry' stays byte identical. Hosts that armed the refused pair should pick 'report' (the previous effective behavior, now named), 'fail', or a carried draft pass via 'both'.
  • a734ca0: One judge label predicate for both critical path surfaces (RV3302). The final claim consistency pass dispatches under claim-consistency-judge-final (RV2509); the live reduceCriticalPath compared the span label for exact equality while the journal fold accepted the suffix, so the 2026-08-12 comparison run reported semanticJudgeMs 0 on the live surface, with the whole 272923 ms window read as final composition, while the journal fold correctly split 224864 against 48059. Both folds now classify through the exported isClaimJudgeLabel(), and a parity test pins that run's shape to the same split on both surfaces.
  • deb406f: The terminal envelope carries the semantic outcome (RV3304). The 2026-08-12 comparison run settled ok/complete over a contradiction its own final judge had named, and neither the HTTP response nor a restarted reader could see the acceptance verdict a live SDK consumer held: TerminalEnvelope now mirrors deliverableAccepted, resultAvailable, acceptedArtifactRef and a detached claimConsistencyMeta from the outcome, plus the run's declared configFingerprint (RV3210), so the surface a consumer gates on says what was verified, over which document, what the judge found, and under which configuration. OrchestrateClaimConsistencyMeta gains findings, the judged contradiction count, present exactly when the judge settled ok, because the meta travels alone onto surfaces the findings array never reaches. The journaled run settle has recorded the whole lift all along; lastRunSettle now reads the semantic fields back defensively (a malformed meta drops whole, absence means NOT RECORDED), and persistedTerminalEnvelope rebuilds the same envelope a live consumer held, GET /runs/:id included, with zero server changes. Everything is additive: envelopes from runs that declared or judged nothing are byte identical.

1.236.0

Minor Changes

  • 26306ea: The config fingerprint (RV3210), the honest answer to hashWorkflowBody's closure blindness the 2026-08-11 experiment confirmed: the body-text hash cannot see captured values, so two byte-identical bodies over different closures pin identically. RunOptions.configFingerprint (an opaque host string, at most 512 characters) records in RunMeta at genesis; ResumeOptions.configFingerprint asserts it back, and a mismatch refuses the resume typed BEFORE ownership, meta writes, or any append, with no posture knob, because supplying the fingerprint IS the assertion. One-sided states warn instead of failing (RULVAR_RESUME_FINGERPRINT_UNCHECKED for a recorded pin the resume ignores, RULVAR_RESUME_FINGERPRINT_UNRECORDED for an assertion the run never declared): absence means NOT RECORDED. Runs that declare nothing are byte identical, and the preferred pattern remains closing over nothing and passing config through args.
  • 709b942: The admission cliff becomes a one-field read (RV3208). Preflight already named the whole-wave requiredMinimumCeilingUsd, but the DISTANCE to the declared ceiling was left to the operator's subtraction: the 2026-08-11 experiment ran its whole workflow on a $0.20 remainder of a $7.00 ceiling (2.86 percent) that a small pricing or context drift would have refused at admission. The admission block now carries ceilingHeadroomUsd and ceilingHeadroomShare (present exactly when both sides are recorded, absence means NOT RECORDED), and the opt-in orchestrator.minCeilingHeadroomShare threshold turns a thin share into the ceiling-headroom-thin warning finding. The default threshold is 0, so existing preflight reports gain the two fields and change nothing else.

1.235.0

Minor Changes

  • ba4e10d: A lost journal append now fails the settle closed (RV3201). Deterministic shims journal fire-and-forget through the serialized append queue, whose chain swallows rejections to keep later appends flowing, and the settle barrier used to swallow the flush verdict on top, so a failed persist was visible to nobody: the run settled ok/complete over a journal missing a record it believes it wrote, and a resume regenerated a different ctx.random() value without one provider call. The first lost append now latches in the Replayer, flush() rethrows it as the new typed JournalIntegrityError, and the engine converts a would-be ok (or suspended) outcome into an error terminal whose settle decision records the converted status. Runs that never lose an append are byte identical.
  • 172402b: The MCP discovery deadline binds the page call itself (RV3205). timeouts.discoveryMs was checked only between pages, so a hung or slow CURRENT tools/list call was unbounded by it and the last (or only) page never paid the deadline at all: a single 86 ms page sailed under a 10 ms cap. Every page call now carries the smaller of listMs and the remaining discovery budget as its wire timeout, a page cut at the remaining budget reports in the discovery deadline's vocabulary with the transport failure as its cause, and sweeps that never approach the deadline are byte identical.
  • 2ecd787: The extension finish gate (RV3202). OrchestratorExtension gains finishGate?(), consulted FIRST on every ordinary coordination finish: a refusal returns as the finish tool's typed error result (nothing journals, no repair spent), so the model resolves the named blockers and finishes again; the forced-finalization and synthesis finishes are never gated. PlanRunner implements it: finish is now refused while any plan node is ready or running, with the stragglers named, because quiescence participation alone gated only wakes and a root could settle a bare ok while the exit barrier cancelled a running node. allowEarlyFinish: true restores the old behavior deliberately. Runs without an extension finish gate are byte identical. journal-shape-revision: the oscillation-freeze cassette re-recorded for the gate's live path (the scripted finish over the still-running frozen-signature node is now refused typed, and the scenario closes the straggler deliberately before finishing); already-journaled entries replay verbatim, so existing journals stay valid.
  • e20a5e9: record_evidence binds the quote to the cited lines (RV3206). The quote check searched the WHOLE loaded file, so a quote taken from the next line over verified against a citation it never belonged to, and every evidence floor counted the misbound entry. With both lines and quote given, the quote must now appear verbatim inside the cited range; the refusal tells the model to widen the range or fix the citation. Quote-only entries keep the whole-file check (with no lines claimed there is no location to bind), lines-only and file-only entries are untouched, and correctly bound citations are byte identical.
  • c70def0: strictPricing enforces the presence the Pricing type promises (RV3204). The gate's rate checks were conditional on each field being present, so an untyped or JSON-loaded {} price row satisfied all of them and the downstream fold priced it at a zero debit: a "strictly priced" dispatch that debited nothing against every ceiling. Under strictPricing a resolved row must now CARRY finite non-negative inputUsdPerMTok and outputUsdPerMTok; a missing rate refuses typed before the wire, naming the field, with allowUnpriced unchanged as the explicit exception. Cache rates and long-context tiers stay optional exactly as the type declares them, and rows that already satisfied the type are byte identical.

Patch Changes

  • 98c8691: The RV3205 discovery-deadline rewrap classifies by the SDK's request-timeout code instead of re-checking the wall clock: the re-check raced the SDK's own timer by a millisecond on a slow runner and leaked the raw MCP error -32001 where the deadline vocabulary was promised.

1.234.0

Minor Changes

  • 8420c04: The synthesis mode gates (RV3102, the evidenceIndex precedent): every single-prompt surface now refuses typed at intake under mode: 'incremental' instead of silently rendering nowhere, and the mirror holds. An armed synthesis.policyFacts, synthesis.runFacts (either form), synthesis.exposeChildResultTools, synthesis.context: 'full', or a declared synthesis.limits under the deterministic incremental reconciliation is a ConfigError, because that mode dispatches no synthesis model at all; synthesis.noteLimits under mode 'single' (explicit or defaulted) refuses the same way, because that mode dispatches no note invocations, and until now the field was documented as ignored. Inert forms (an explicit false, the 'digests' default) stay valid in both modes: they promise nothing. The draft-gate family (skipWhenDraftValid, carryDraftGaps, fallbackToValidDraft) was already gated transitively through its finishValidation requirement. A config that used to no-op silently now fails loudly at intake; that is the change, and it is deliberate.

1.233.0

Minor Changes

  • 48b5200: ResumeOptions.bodyHash: 'warn' | 'refuse' (RV3001): the opt-in pin for hosts that treat an edited workflow body as a different workflow. Under the default 'warn' an in-process body-hash mismatch keeps the historical design byte for byte: the loud RULVAR_RESUME_HASH_MISMATCH warning fires and the resume proceeds, because the journal decides replay versus live per content keys and reports orphans honestly. Under 'refuse' the same mismatch is a typed ConfigError raised before ownership, meta writes, or any append, so a refused resume mutates nothing durable. Name mismatches and compiled-source mismatches remain hard errors under either value, and any other value refuses typed before any store read.
  • 73bc32b: Terminal agent entries journal the durable tool-budget subset (RV3002): toolBudget: { used, cap? }, the loop's executed-call counter and the effective cap at settle, written whenever the live result carried the pressure snapshot. The counter has always been durable in the terminal checkpoint, but checkpoints are blobs and journal folds read entries only, so observed calls-per-evidence-entry calibration could not be a pure fold. Replay now restores AgentResult.toolBudget unconditionally from the entry on new journals, grant-free runs included, with the RV509 decision-backed fields (extensionsGranted, finalizationWindowEntered) merged on top; journals written before the field shipped keep the RV509 decision-conditional restoration byte for byte. Live-only summary fields (unitsUsed, noticesFired, limiter, and the rest) never journal, exactly as before.
  • e63b743: synthesis.runFacts widens to boolean | { workflowSoFar?: boolean } (RV3004). runFacts: true keeps today's child-only RUN FACTS line byte for byte. The object form keeps that line and, under workflowSoFar: true, appends one RUN FACTS SO FAR: sibling scoped run-so-far-at-this-dispatch: the same counters folded over the settled children PLUS the orchestration's own settled internal spans as of the composing dispatch (the coordination dispatch, claim judges, synthesis notes, and any earlier settled composition), with children and internalSpans counted separately. The nineteenth benchmark quoted child-only totals beside the whole-run invoice and invited a false drift reading; the sibling closes most of that gap from inside the prompt while its suffix names what stays outside (the composing dispatch itself and anything still running), so the terminal envelope and invoice remain the only whole-run truth. Folded from replay-stable settled material in deterministic settle order (a resumed composition re-derives identical bytes, zero live calls); dollars stay absent for the same replay reason; unknown keys and non-boolean values refuse typed.
  • ef45da7: toolCalibrationFromJournal(entries) (RV3003): the observed calls-per-evidence-entry calibration as a pure fold over the journal. The ninth comparison run declared the stock estCallsPerEntry of 3 behind its preflight call floor and its workers actually spent 5.5 executed calls per recorded evidence entry, a number that had to be recomputed by hand from worker transcripts. The fold pairs the RV806 evidence verdict with the RV3002 executed-call counter on each terminal agent dispatch: observed rows carry both sides and their per-dispatch rate, the aggregate divides summed calls by summed entries across observed rows only (unproductive calls included; a paired row with zero recorded entries keeps its calls visible and carries no ratio), and the unpaired sides are named per RV1209 (evidenceOnly for pre-RV3002 journals, budgetOnly for counters with no declared contract, unobserved for neither), never counted as zero. childRostersFromJournal children additionally carry the toolBudget subset beside their evidence verdict, so a post-mortem reads spend beside the verdict without a second fold.

1.232.0

Minor Changes

  • 1440410: The synthesis candidates a journal already holds (RV2902). synthesisCandidatesFromJournal(entries, priceUsd?) folds each journaled finish verdict into a candidate with the window of wall, wires, usage, and per-call priced cost that produced it, so the cost of a repair is separable from the cost of the candidate it repaired: the one question the ninth comparison run's frozen telemetry could not answer, with both candidates inside a single 177 second synthesize span priced as one number. Sequence numbers partition a settled span's incremental billing rows between its verdicts exactly; the fold refuses to price a window when the rows do not cover the terminal's own call records (they append asynchronously by design), counts verdicts outside every settled synthesize span instead of inventing candidates for them, and reports wires after the last verdict as an attributed-to-nobody tail. No new journal fields.
  • 6e467f4: The downloaded billing export parses fail-closed (RV2908). statementRowsFromDelimited(text, { delimiter? }) turns the CSV/TSV a provider console hands a host into the header-keyed rows statementFromRows consumes, closing the last manual step between a downloaded export and reconcileStatement. The library still hard-codes no provider's format: the host owns the column map, this owns only the strict delimited grammar (RFC 4180 quoting, CRLF or LF records, one trailing newline ignored). A ragged record, a torn quote, a stray quote in an unquoted cell, and an empty or duplicated header name each refuse typed with their line, because a column shifted one to the left prices outputTokens as dollars and calls it evidence.
  • e3bcab2: The engine labels its own synthesize dispatches (RV2901). criticalPathFromJournal splits the synthesize bucket into final composition and claim judge only when EVERY synthesize span carries a journaled label, and the comparison run's journal refused that split because the final composition dispatch stayed anonymous while the claim judge was labelled. The final composition now dispatches under the new exported FINAL_COMPOSITION_LABEL and incremental synthesis notes under SYNTHESIS_NOTE_LABEL, both policy on the attribution facts and never identity, so the journal of a fresh run reports the split by construction while journals written before the labels keep refusing it honestly.
  • b55a0f7: The claim-consistency pass sizes itself from a declared coverage target (RV2903). The ninth comparison run judged 43 of 115 citing sentences because its host guessed max: 56 and the run-fact pass cut 30 candidates to an unraisable default of 8: the honest 'partial' grade was a constant's echo, not a policy. claimConsistency.coverageTarget (a share in (0, 1]) makes the goal the input: the pairing selects coverage-first (every critical candidate, then one pair per still-uncovered sentence in draft order until the target is met, with max kept as a hard ceiling and truncated meaning exactly that the ceiling cut wanted selection), the run-fact pass judges every matched candidate instead of the default bound, an undeclared minimumCoverageRatio defaults to the target so the RV1809 floor machinery (lowCoverage, onLowCoverage, the strict CLI exit) enforces the same number that sized the pass, and the meta echoes coverageTarget so a persisted outcome says what its coverage was held against. Unset, every selection reproduces byte for byte.

Patch Changes

  • 0b14293: Three truths the ninth comparison audit caught, fixed with a tombstone (RV2905). Two guide pages still claimed "the current release enforces only the in-process tool executor" (one adding that subprocess and container "fail at registration") after that class was fixed on the architecture page: both now state the seam, a non-inprocess executor tag is a typed ConfigError at spawn time until a matching ToolExecutorProvider is registered under EngineOptions.executors, and @rulvar/executor ships both references. The roles.ts header now says its firing predicates cover six OF THE SEVEN invocation roles ('synthesize' is dispatched explicitly by the orchestrator, never by the trigger protocol). And because the executor claim already returned once after being fixed, the docs lint gains a tombstone sentinel forbidding it everywhere the lint reads, docs prose and source comments alike.

1.231.0

Minor Changes

  • 4eb4b56: The critical path is readable OFFLINE (RV2803).

    reduceCriticalPath folds the shape of a run's wall clock out of the event stream, and a post-mortem has no event stream: the process that emitted it is gone, and what a paid run leaves behind is a journal. So postFanInShare, the one number the comparison series steers by (RV2210 wrote the targeting rule around it), was a live-only reading, and the archived runs it was meant to judge could not answer for themselves.

    criticalPathFromJournal(entries) is the same reading taken from what survives. Every ingredient was already written down: a terminal agent entry carries its own span (startedAt copied from the running entry it closes, endedAt stamped at the settle, so the interval is exact rather than reconstructed) and costAttribution.role says whether the span was coordination, synthesis, or a worker. Nothing is re-derived and no validator runs again, so a journal from any prior version reads exactly as well as today's.

    Two things it refuses to claim, both because the alternative is a confident fiction:

    • The wall figures (runWallMs, postFanInMs, postFanInShare, synthesisShare) are ABSENT for a journal holding more than one segment. A killed run's first and last stamps are separated by however long the operator took to resume, and that difference is not a duration of anything. segments rides the reading so a consumer can see which case it is in.
    • The synthesize split (RV1604's finalCompositionMs and semanticJudgeMs) needs the dispatch LABEL, which rode the event stream alone. CostAttributionFacts.label now carries it: policy, never identity, absent on every unlabelled dispatch, so unlabelled runs journal exactly what they did before. The split is reported only when EVERY synthesize span in the journal carries a label, because one unlabelled span makes it a guess, and this split exists because a guess here read a 54 second judge as a second final composition.

    unclassifiedSpans counts settled spans whose entry records no role at all, so on a journal older than the attribution facts the worker count reads as a floor rather than quietly absorbing them.

  • bc8f09e: The telemetry scope table's promise becomes its gate, and three of its declarations turn out to have been wrong (RV2801).

    TERMINAL_TELEMETRY_SCOPE exists so a reader of a killed-and-resumed run never has to reconcile two terminals by hand, and RV2701 made its completeness a compile error. Both halves of that promise were bigger than the gate behind them.

    The type stopped at depth one. TerminalTelemetryScopes required every key of RunOutcome and then admitted nested paths through a string index signature, which requires nothing. So cost.orchestrator.wakes and its four siblings were declared by hand and by luck, cost.usageApprox, cost.abandoned.usd, cost.abandoned.usageApprox and cost.orchestrator.share were not declared at all, and the doc promised every path was required. The type now requires every counted leaf under cost (numbers and flags, derived from CostReport itself, breakdown maps excluded because their keys are data), so a new cost figure does not compile until it says what it counts. That is the RV2701 blindness one level down: a gate whose subject is nested figures cannot stop at the top level.

    Nothing checked whether a declared scope was TRUE. The two assertions that stood for that restated the table's own literal, so they could only fail together with the table. A doctrine test now suspends a real run on an approval, resumes it to ok, and holds every declared figure against its own claim over the two terminals.

    It found three wrong declarations immediately. An outcome's cost is costReportFromJournal over the replayer's snapshot, and a resumed segment's snapshot holds every prior segment, so cost.orchestrator.wakes, cost.orchestrator.forcedFinish and cost.orchestrator.reserveUsedUsd have always been folded over the whole logical run while the table called them 'segment'. They are now 'cumulative', which makes the taxonomy true rather than merely complete: every figure an outcome carries covers the logical run, and the only segment-scoped counters are the live-only ones that never reach a journal (the transport retries and the schema-exchange counters on an agent result). A wrong scope is worse than a missing one, because a missing one is noticed and a wrong one is believed.

  • ff9b8c2: The offline child roster names the children the run ABANDONED (RV2804).

    childRostersFromJournal presented a child on a discarded branch exactly like a child whose work the run kept, so a post-mortem reading "four children settled ok" was counting branches the orchestration had thrown away. The money layer has refused that conflation since RV1904: grossUsd keeps abandoned spend because the provider billed it, totalUsd does not because the run kept none of it. The roster now says the same thing.

    JournaledChild.abandoned is present and true exactly when the first-wins abandon projection covers that child's dispatch, subtree coverage included, and absent otherwise, never false (RV1209). It needs nothing that was not already written down: the fold reads the same projection the replayer disposes by, over the same journal, and a child's handle is the very seq an abandon entry targets, so journals from every prior version answer.

    rulvar inspect prints the discarded children under the roster it already prints, named by their handles.

1.230.0

Minor Changes

  • e9bf910: Every terminal field declares its telemetry scope, enforced by the type (RV2701).

    TERMINAL_TELEMETRY_SCOPE promised that a new terminal field cannot ship without saying whether it counts the segment, the logical run, or nothing at all, and the gate behind that promise read the keys of one SUCCESSFUL outcome. A field that exists only where a run FAILED is absent from every such sample by construction, so RV2602's childrenAtFailure (present exactly when no acceptance verdict exists) shipped straight through it. A table whose whole subject is killed and resumed runs cannot be defended by an outcome that neither died nor resumed.

    The table's type is now TerminalTelemetryScopes: every key of RunOutcome is required, so an undeclared field is a compile error at the table itself, and a string index signature still admits the nested paths a consumer reads off the same outcome (cost.orchestrator.wakes). childrenAtFailure is declared 'cumulative', for the loss-list reason: a resumed segment re-admits every recovered child into the same roster before it dispatches anything new, so the fold covers the logical run rather than the segment that happened to die. Two doctrine tests hold the table against real terminals, one ok and one dead before acceptance, because a key that reaches an outcome without reaching the type would satisfy the compiler and still leave a reader guessing.

  • 57bfb38: The child roster of a run that died before acceptance is readable OFFLINE (RV2702).

    childrenAtFailure (RV2602) answers "what had the children produced" for a consumer watching the run, and it dies with the process that held it. The settle persists the completion lift and nothing else, so a post-mortem over a journal, which is all a paid run leaves behind, had no way to ask the question at all: not for a run that crossed its ceiling mid-roster, and not for any run in an archive written before the field existed.

    childRostersFromJournal(entries) is the fold, and it reads what resume reads. A spawn-admission decision names every child the controller judged, with its ordinal, its profile, its verdict and the scope its dispatch pins to; the dispatch and terminal agent entries under that scope are the child itself, and the RV806 evidence verdict rides the terminal. Nothing new is written, nothing is re-derived and no validator runs again, so a journal from any prior version reads exactly as well as today's.

    rulvar inspect prints it: how many children were admitted, how many settled and with what statuses, how many were refused admission, and the ones that settled ok below a declared evidence floor, named by the dispatch seq the orchestrator's own turns used as their handle.

    Two things it does not claim. It is not the live roster: this reading happens after the RV1903 exit barrier settled the stragglers, so a child the live field called unsettled usually has a terminal here, and an absent status means the journal truly ends mid-flight rather than a child that failed. And it counts CHILDREN: the coordination loop, the synthesis and the judge dispatch through the same ctx.agent, and only a child carries the spawn admission that pins it to the child scope.

1.229.0

Minor Changes

  • 3370342: The finalization window entry explains a reserve it did not configure (RV2601). With reserveForEvidenceDeficit the effective reserve is widened by the outstanding evidence floor (RV1208), and the journaled finalization_window_entry decision carried only {remaining, reserveCalls, budget}: a reader after the fact could neither explain a reserve of 25 under a configured 20, nor see that the agent stopped searching owing its ENTIRE floor. The fourth parity run settled exactly there, and reconstructing it took the transcript.

    The decision now carries evidenceDeficit and minEntries, exactly when the widening happened. Both numbers are the loop's own, and the same predicate feeds the notice the model reads and the fact the journal keeps, so the two can no longer disagree. Absence is the honest answer that the configured reserve is what bound: a run without the opt-in, without a declared contract, or with the floor already met journals what it always did, byte for byte.

    The doctrine is the one RV2203, RV2205 and RV2207 already ship under: a number the loop APPLIED belongs in the journal with the arithmetic that produced it, not only in prose addressed to a model nobody kept.

  • 2fb6656: A run that dies before acceptance names what its children produced (RV2602). Every child-naming field on the envelope hangs off the acceptance fold: childStatusCounts, belowFloorOkChildren, acceptanceChildren, all of them assembled inside the acceptance decision and enriched onto a failure only when that decision exists. A run that crosses its ceiling mid-roster therefore settled with completion absent and said NOTHING about work already paid for, even though every child terminal was in the journal one entry at a time. That is the last row of the deliverable truth table, and the only one where the terminal was silent about spend.

    RunOutcome and the run:end event gain childrenAtFailure: spawned, settled, statusCounts, the belowFloorOkChildren that settled ok under a declared evidence contract they never met (the fourth parity run's silent worker, sixty one successful tool calls and not one recorded entry), and the unsettled children still running when the run gave up. Nothing new is written; it folds the children's own journaled terminals.

    Three lines draw its boundaries. It reports ONLY where no acceptance verdict exists, live or rolled forward from the journal, so one set of children never carries two folds under two authorities. It is deliberately not called childStatusCounts, because that name belongs to the policy's number and a fold done by no policy must not borrow it. And it is lifted independently of the completion lift, because that lift bails out the moment there is no completion literal, which is exactly the terminal this field exists for.

    The roster is frozen at the moment of death, ahead of the RV1903 exit barrier, so it is the roster a verdict would have frozen rather than the one the stragglers land on afterwards; that is why unsettled can be non-empty. The error class is preserved exactly and only its data widens, an already-present field is never overwritten, and a run that spawned no child adds nothing.

  • edce170: rulvar inspect reports the logical run and what the contract refused (RV2605). Two surfaces shipped in v1.228.0 had no consumer in the tool people actually read a run with: inspect printed entries: N, which over a resumed run is one undifferentiated heap with no boundaries in it, and said nothing at all about finish candidates the declared contract rejected.

    segments: is logicalRunTelemetry (RV2510) printed: how many segments ran, how each settled, how many entries each appended, and the count of entries that continued PAST the last settle (RV1407) when there are any, because the last settled status is then not the run's last word. rejected finish candidates: lists the RV2507 rows with verdict, size, hash prefix, failing validators, and the blob ref when the bytes were retained, and counts DISTINCT documents beside the row count, so three rows sharing one hash reads as the model serving one text three times rather than as three genuine attempts.

    lastRunSettle gains rejectedFinishCandidates. The settle already persists the whole completion lift, so this is a read of what is recorded, not a re-fold and not a validator re-run, and every row is parsed defensively: any malformed row drops the WHOLE list, the same posture the live lift takes, because a partial history read as complete under-reports exactly the runs that misbehaved most, and offline is where nobody can check. A journal that records nothing of the kind reads as NOT RECORDED and both lines stay absent.

1.228.0

Minor Changes

  • 4034fac: The terminal states its deliverable verdict (RV2506). status says whether the run RAN and completion is the acceptance policy's claim over CHILD statuses; neither says whether the artifact the terminal carries ever passed the declared finish contract. The twenty-fifth comparison run accepted four ok children, failed its synthesis against the same bundle three times, and settled carrying nothing the contract accepted, and the harness scoring it read status: 'ok' and could not tell. The answer lived only in the journal, behind a transcript dig.

    RunOutcome and the run:end event gain three lifted fields, computed once and spread onto both surfaces exactly like the completion lift they join. deliverableAccepted is the contract's verdict on THIS artifact. resultAvailable says whether there is an artifact to read at all. acceptedArtifactRef is the journal seq of the decision recording the acceptance, so the validators that rendered it and the draft hash they judged are one rulvar inspect away. Three different decisions answer to that ref, which is why one field is worth having: the accepted orchestrator_finish_validation verdict on the ordinary path, the orchestrator_synthesis_skip decision when the RV510 gate settled on a valid draft, and the orchestrator_synthesis_regressed decision when the RV2505 floor handed a failing synthesis back to its draft. All three are acceptances by the same bundle, and the terminal now says so.

    deliverableAccepted is ABSENT, never false, when no finishValidation was declared: nothing judged anything, and absence means NOT RECORDED (RV1209). The verdict rides FAILED terminals too, lifted from the enriched error data the way RV2203 carries the pass truth, because the terminal a post-mortem policy must read is precisely the one where the children were accepted and the artifact was not. Malformed values mirror nothing, so a consumer gating on === true cannot be defeated by a truthy string.

    The guide gains the truth table over every reading the fields can produce, and the normative consumer predicate written in them.

  • a54b085: The rejected finish candidates become first-class terminal artifacts (RV2507). A run that fails its contract already tells you the LAST verdict's validators and nothing else: not how many candidates were judged, not whether they differed from each other, not where to read them. The twenty-fifth comparison run rejected three syntheses, and the only way to see them was an external script that re-parsed the whole agent transcript.

    RunOutcome and the run:end event gain rejectedFinishCandidates, one row per candidate the declared contract did not accept, in judgement order: the callId, the verdict ('repair' when another turn was granted, 'rejected' when it was the last), the sha256 hash naming WHICH document drew the verdict, its size in chars, and the failed validator diffs. It is a pure fold over decisions the journal already holds, so a resume re-derives the identical list without re-running a validator, and a superseded contract generation drops out of it exactly as it drops out of the repair budget. The list rides the ok terminal as well as the failed one, because a run that recovered on its second attempt still owes a post-mortem the first, and it is absent when a finish passed first try. One reading it makes possible was invisible before: three rows carrying ONE hash is the model serving the same document three times, a different failure from three genuine attempts.

    The BYTES are a separate, declared decision. finishValidation.retainRejectedCandidates (default off) writes each rejected candidate to its own transcript blob at <runId>/finish-rejected/<callId> and puts the ref on the row, one transcripts.get from the document; Engine.deleteRun cascades over those blobs like every other run artifact, and the count is bounded by maxRepairs + 1 per finish-validated invocation. The split is the point: identity costs nothing and is therefore always recorded, a copy costs storage and is therefore the host's call. A store that refuses the write costs the run nothing, since the row keeps its identity and drops its ref.

    Malformed rows drop the whole list rather than a subset, the acceptance-roster posture: a partial history read as complete would under-report exactly the runs that misbehaved most.

  • 9d0a9be: The semantic gate reaches the FINAL artifact, and every verdict names the document it read (RV2509). The claim-consistency pass runs strictly before the synthesis by design, so that a draft contradicting its own pool never pays for a composition. The cost of that ordering was never stated: the verdict describes the DRAFT, the synthesis then rewrites it, and the terminal reported the cleared verdict beside the replaced document with nothing to tell them apart. The twenty-fifth comparison run's judge cleared a draft its synthesis replaced three times over.

    OrchestrateClaimConsistencyMeta gains judgedStage ('draft' or 'final') and judgedHash, stamped at the one assembly every exit path of the pass passes through, so a coverage: 'full' can never be read as a claim about the shipped artifact when it was rendered over a draft that no longer exists. The acceptance envelope gains draftToFinal (draftHash, finalHash, rewritten, and claimsJudgedOn) whenever a synthesis is configured: claimConsistencyMeta.judgedHash === draftToFinal.finalHash is the machine test for "this verdict is about the document I received", and it works under the DEFAULT setting, where the answer is usually no.

    claimConsistency.stage moves or duplicates the gate. 'draft' is the default and is byte identical to the historical behavior. 'final' runs the pass after the synthesis over the artifact the run settles on, so an armed onFound: 'fail' stops a run whose composition contradicts the pool it was composed from. 'both' keeps the cheap pre-synthesis gate and adds a second judge over the final; the terminal then reports the final pass in claimConsistencyMeta, because the shipped document is what a consumer gates on, and the earlier verdict in the new claimConsistencyDraftMeta. A stage past 'draft' without a synthesis is a ConfigError, since there the draft IS the final.

    The two invocations of 'both' stay separable: the final judge carries its own telemetry label and a declined admission journals under its own key, so one run can honestly record two different degradations instead of the second reusing the first's arithmetic.

  • be9ef28: Resume telemetry says what it counts (RV2510). A resumed run's terminal mixes two kinds of figure with nothing marking which is which: money and usage are cumulative over the whole logical run (they fold from the journal), the spawn count resumes from the journaled ledger, and cost.orchestrator.wakes, the schema-exchange counters and the transport retries count ONLY the segment that produced the terminal. The twenty-fifth comparison run was killed and resumed, and turning its two terminals into one honest account of the logical run was hand work over a joined journal.

    TERMINAL_TELEMETRY_SCOPE declares it as one exported table: every terminal field mapped to 'segment', 'cumulative', or 'terminal' (not a count at all, but a claim about the run as it stands at this settle, which a later segment can only replace). A doctrine test holds the table against the keys a REAL outcome carries, so a new terminal field cannot ship without declaring what it counts.

    logicalRunTelemetry(entries) is the aggregate for the whole run: how many segments ran, how each settled, how many entries each one appended, and entriesAfterLastSettle, nonzero exactly when the journal continued past its terminal (RV1407) so the last status is not the run's last word. It adds no journal field and folds only what the settle already records, so it reads journals written by every prior version exactly as well as today's, and no existing journal changes by a byte.

    The replay dedup is the design. The aggregate deliberately carries no money and no usage: those already fold from the WHOLE journal, and re-summing them per segment would count every replayed operation once per segment that replayed it. What it reports instead is a PARTITION of the journal at the settle boundaries, so nothing is counted twice by construction, and the per-segment figures a terminal carries can finally be read against the segment that produced them.

1.227.0

Minor Changes

  • f262e9f: The run's own id becomes an artifact the evidence grade accepts (RV2501). evidenceGradeValidator demands that a live-observed, provider bill or production-proven sentence name an artifact IN THAT SENTENCE, and DEFAULT_ARTIFACT_PATTERN only ever matched a path:line citation or a ULID behind the literal word run. Every other run id was therefore unnameable: the 1.226.0 comparison run carried the id comparison-rulvar-v12260-aug09-1786272840549, its verdict told the synthesis to state that id, the pattern matched nothing it could write, and the run spent both granted repairs and failed closed on two sentences telling the truth about the run they were part of. FinishValidationInput now carries runId, and the orchestrator runtime supplies it at every gate that judges a finish: the validator-bound finish, the contract draft gate, and the skipWhenDraftValid pre-pass. A sentence carrying that id verbatim as a whole identifier satisfies the grade, and the verdict NAMES the id it wants written, so the repair instruction is executable instead of aspirational while the RV2202 composition warning stands (a run id written beside a path:line citation is not in the cited window and trades this failure for a cited-value one, so the graded sentence must carry no source citation). The intake is bounded like every sibling: an id shorter than six characters is ignored, because a two character id would satisfy nearly every sentence by accident, the same fail open the empty-pattern guard refuses; the id is credited only as a whole identifier, so x<id>y is never an artifact; and with no runId supplied the verdict is byte identical to the historical one. The same defect had a second half in the prompt: the opt-in RUN FACTS: line (RV1503) ends in the live-observed register, the composing model is told to reproduce run facts only from it, and the line named no artifact at all, so the engine was steering its own synthesis into a sentence its own default bundle refuses. The line now carries runId in its JSON and reads live-observed by run <id> in the same sentence as the graded phrase, so quoting it faithfully passes evidenceGradeValidator and, carrying no source citation, passes citedValueValidator beside it; a test asserts exactly that over the bytes the engine actually writes, with the id-less contrast asserted as the historical failure. The RUN FACTS line stays folded only from replay-stable material, so a resumed synthesis re-derives identical bytes; hosts that pin synthesis prompt bytes across engine versions should expect this line to have changed. The validator-guidance-conflict fault scenario drives the new arm end to end: its corrected finish now carries the run's OWN id rather than a fabricated ULID, and the scenario asserts the repair exchange names that id verbatim beside the citation-free composition, so the guidance the fault kit gates is the guidance a run can actually execute.

  • f191ff7: Identity spans are not asserted values (RV2502). citedValueValidator reads every non-citation inline span in a citing sentence as a value asserted about that citation, and the 1.226.0 comparison run showed the class that rule over-reaches: its synthesis wrote the frozen commit sha f8d9c5131c99c843ed23da22af20651f95377dd0 beside source citations, and the verdict demanded the sha appear in the cited source, an impossible repair delivered in the same reason list as three real value fixes; both granted repairs burned and the finish was rejected. A span naming the artefact under review says which commit, run, or release the document is about and asserts nothing about any cited line. Three shapes are now structural and always excluded: a commit sha (12 to 64 hex characters, a floor low enough for every real abbreviation and high enough that ordinary hex literals like deadbeef stay judged), a release version (1.2.3, v1.2.3, optional prerelease or build tail), and the run's own id when the runtime supplies runId, on the same six-character floor the evidence grade uses. Host vocabulary is declared rather than guessed: the new notValues option lists the spans a document writes as identity, verdict words like conditionally ready among them, matched whole and case sensitively; a malformed list is a ConfigError at construction. Nothing else relaxes, and a genuine value the cited line does not carry still fails in the very same sentence as an excused sha.

    The run-id exclusion makes the shipped bundle self consistent. evidenceGradeValidator instructs a failing model to write this run's id inside the offending sentence (RV2501), and RV2202's warning existed because obeying that beside a citation traded an evidence-grade failure for a cited-value one, the trap that burned both repairs of the third subscription run. The two arms of the grade's reason now each name the composition that is TRUE for them: with the id in hand the graded sentence may carry a citation as well, and without one the older separation advice stands, because there the sibling has no id to recognise. The validator-guidance-conflict fault scenario converges on the direct shape, the run's own id written beside the citation in the graded sentence, which neither validator could accept before.

  • fbbfbe8: The exposure ceiling clamps a lone dispatch instead of refusing it (RV2503). The budget ceiling has clamped every turn's maxOutputTokens to what the remaining money affords since layer 2b existed; the in-flight exposure ceiling only ever answered yes or no, so a dispatch whose FULL plan overshot the line was refused even when a shorter one fit and the budget could still pay for it. The 1.226.0 comparison run died exactly there: nothing was in flight, the budget held $0.8642, the mandatory repair turn's 18,000 token plan priced $0.7066 against $0.5642 of room, and the dispatch was refused before any provider call; the same work, re-issued after an operator raised the ceiling, wrote 12,840 output tokens for $0.4788 and fit the very ceiling that refused it. RunOptions.clampTurnToExposure arms the other answer: RunBudget.maxExposureOutputTokens prices the room with the same function the admission charges it with, and the loop lowers the plan to fit.

    Scoped by what a refusal actually means. It applies only to a dispatch with NOTHING else in flight, because that refusal is permanent: no hold will ever release to fund the full plan, which is precisely why the RV2003 sweep wakes such a waiter drained. With siblings live the refusal is transient, the RV1902 and RV2002 waits park on it, and the wave keeps the full-length turn RV711 promised. When the room cannot fund even the serving model's output floor the clamp stands aside, so a real exposure exhaustion still refuses through the typed in-flight-exposure path and the drained terminals of RV1902, RV2002 and RV2003 keep their shapes. The clamp is independent of the USD ceiling: a run with an exposure cap and no budgetUsd clamps too.

    Off unless declared, and validated on intake: a non boolean is a synchronous ConfigError before any journal write, because a truthy string arming a dispatch posture nobody asked for is the same hazard as a typo'd flag silently reading as off. Absent, dispatch behavior is byte identical, which is why the existing exposure suites pass unchanged. Like strictPricing, it is a per-segment posture: not recorded in RunMeta, so a resumed segment carries only what its own options declare. Flipping the default would rewrite the drained-refusal doctrine those three trains built out of live parity deaths, and that is a separate decision with its own gates.

  • 263b5e8: Preflight prices the mandatory synthesis tail off maxRepairs and against both ceilings (RV2504). The 1.226.0 comparison run declared a 1.53 USD synthesis reserve, a 45000 token input floor, an 18000 token output allowance and maxRepairs: 2: one composition turn priced 0.7650 USD, so the hold was EXACTLY two of them and the RV2104 check, which priced one composition plus one repair, passed a config whose mandatory tail is three turns and 2.2950 USD. The run then died on its second repair with 0.385 USD of its 6.00 envelope unspent. synthesis-reserve-below-cap-composition now counts the tail off maxRepairs rather than off repairTurnReserve: the reserve is a TURN budget, while the money is spent by every repair the runtime is willing to GRANT, out of reserved turns or ordinary ones alike (the default grant is one, so existing arithmetic is unchanged). The message prints the multiplication. The finding also prices that tail against the second ceiling: an in-flight exposure cap below the run ceiling leaves the tail only maxInFlightExposureUsd - (ceiling - reserve) above the reserve line however much money the hold carries, and the comparison run's 5.70 cap left its 2.2950 USD tail 1.23 USD of room. One short room warns; a tail neither room can pay is an error finding, so rulvar preflight exits non-zero on it.

  • db4d56d: A no-regression floor under the synthesis (RV2505). The 1.226.0 comparison run's coordination draft satisfied the FULL declared contract (its draftPolicy: 'contract' gate had judged it against the same bundle, which is why the synthesis dispatched at all), skipWhenDraftValid was off because the operator wanted the composing pass anyway, and the synthesis then failed that bundle three times over: the run settled with NO result at all, having paid for four workers, for the draft that would have passed, and for three rejected compositions. The opt-in synthesis.fallbackToValidDraft: true catches a synthesis failure at the post-fan-in chokepoint, judges the coordination draft by the SAME finishValidation.validators that bind the synthesis finish, and settles the run on a draft every validator accepts, under a journaled orchestrator_synthesis_regressed decision (the truncated failure message, the validator names, the contract hash when one is declared, the hash of the judged draft) plus a warn log event; the envelope carries synthesisRegressed. It is the validated sibling of the existing orchestrator_synthesis_fallback, which already falls back to the draft when NO validators are configured. A draft that fails too journals orchestrator_synthesis_fallback_declined naming its own failing validators with their reasons, and the original failure rethrows untouched. A ConfigError is never caught: a broken contract is a defect to fix and resume, not a reason to settle on a draft. Deterministic by construction, so a resume that re-fails the synthesis re-derives the identical verdict and reuses the journaled decision. Requires finishValidation, orthogonal to skipWhenDraftValid (with both on, a valid draft skips before there is anything to regress), and default off: no catch, no decision entry, no envelope field, byte for byte.

  • 41f93a9: The claim-coverage grade sees a declined judge and a zero denominator (RV2508). claimCoverageOf never read judgeDeclined, the RV2106 degradation where the claim judge is refused ADMISSION and never dispatched, so a pass that judged nothing was graded by the counts of a pass that did not happen; over a draft carrying no citing sentence it graded 'full', the strongest word in the vocabulary. The vacuous 'full' at a zero denominator was the same failure at its extreme: RV1702 exists to stop a consumer inferring semantic health from emptiness, and an empty set graded stronger than a bounded subset.

    ClaimCoverageGrade gains two words. 'judge-declined' ranks with 'judge-failed' and above everything the counts could say, below it only because a failure at least had an invocation to fail and the two causes are worth telling apart. 'vacuous' sits below 'partial': no subset was chosen because there was no set. ClaimCoverageInput gains judgeDeclined?: true; the orchestrator already spreads that flag into the meta it grades, so no call site changes and every existing meta grades the same unless it carried one of the two states. The CLI's --strict exits nonzero on 'judge-declined' exactly as on 'judge-failed' (nothing was judged either way) and prints 'vacuous' to stderr while keeping the exit, because citing nothing breaks no contract the pass declares.

    Consumers with an exhaustive switch over ClaimCoverageGrade will see a type error until they handle the two new members. That is the intended shape of the change: both states existed before and were silently folded into words that did not describe them.

Patch Changes

  • 98c8ca9: The invocation-role prose is generated from the exported union, and the B0 sentence tells the truth (RV2511). InvocationRole has carried SEVEN members since the synthesis role shipped, and the docs disagreed with themselves about it: the model-routing guide and the agents guide said seven, while the architecture guide and the design principles said six and listed six, dropping synthesize. That prose is also what the llms-full.txt bundle ships, so the machine-readable surface carried the wrong contract. The new scripts/docs-role-truth.mjs gate (wired into the docs CI job and pnpm docs:roles) parses the union out of the source and holds every marked count and list against it, with --write regenerating them; an unmarked count in front of "invocation roles" fails too, even when it is currently correct, because a number nothing owns is exactly what went stale last time. Page frontmatter is checked and rewritten in place, since HTML markers are not valid YAML. The changelog is excluded: its entries describe the contract of the release they announce, and rewriting them would falsify the record.

    budget.ts said "B0 is immutable after start: no API tops it up", which RV2208 made false when ResumeOptions.run shipped. The comment now states the invariant that actually holds: B0 is immutable WITHIN a segment, and the one thing that can change it is an explicit host decision, journaled as its own entry, taking effect only by opening a new segment, so a live run can never raise the bound it is already being measured against.

1.226.0

1.225.0

1.224.0

Minor Changes

  • 4eca1a3: The resume-time budget override (RV2208). A run that died against its own budgetUsd was unfinishable by doctrine: the RunMeta-recorded ceiling governed every later segment, ResumeOptions deliberately carried no budget field, and the only way forward re-paid the whole journaled prefix as a fresh run. ResumeOptions.run ({ budgetUsd?, maxInFlightExposureUsd? }) is the one explicit door: each value is validated exactly like its RunOptions counterpart, applies to the resumed segment and the run's remaining life, and is recorded back by the segment's first meta write, so a LATER bare resume restores the overridden posture rather than the genesis one. The change is never silent: before the meta mirror flips, the segment journals a run_budget_override decision naming the recorded value, the applied value, the source, and the settled spend it was judged against (null records a run that started uncapped). A budgetUsd below the journal's settled spend refuses with a typed ConfigError before ownership, meta, or any append: such a ceiling would exhaust the segment before its first turn and read like a fresh money death. Absent fields keep the recorded values, an absent object keeps the historical behavior byte for byte, and strictPricing deliberately stays out of the override: pricing hygiene is not a per-segment decision.

1.223.0

Minor Changes

  • 549aabd: The bare root ceiling folds documented (RV2205). A coordination turn refused by the RUN account's own hard crossing was the last undocumented money death of the loop: the exposure and reserve-line arms fold typed (RV1902, RV2101), but a crossing that named the run root itself, whether the ctx boundary re-mint (source: 'root', a crossing detected at or after execution: the first parity run's shape, B0 drained by children while the root sat at 16% of its cap) or a pre-admission refusal of the coordinator's own seat (account: 'run'), rethrew bare and tore the run down around its settled children. Both shapes now fold through the SAME forced-finish machinery: the journaled orchestrator_finalize_fallback decision gains reason 'budget-ceiling' beside 'budget-floor' and 'exposure-abort', the settled children ride the partial envelope, and the synthesis redemption stays free to try: past a crossed run ceiling its spawn admission declines with the arithmetic and journals the declined verdict (RV2102), which is the honest record, not a special case; with nothing settled the redemption arm correctly stays out. Orchestrator-cap crossings keep their dedicated atCap machinery, and unrecognized budget shapes still rethrow.
  • 549aabd: The unfunded repair grant declines typed (RV2207). Validation can grant a repair the budget will never execute: the seventh parity run's synthesis died between a granted repair verdict and its dispatch, and even with the refusal's message riding the terminal (RV2104) the death stayed a generic budget re-mint with no journal record of the grant the money never covered. The agent loop now marks exactly that refusal (a would-be turn following a rejected terminal-tool exchange carries the granted repair turn could not be funded: in front of the crossed-account arithmetic), the coordination path reads the marked terminal behind the re-mint's entryRef (the RV2103 pattern), journals orchestrator_repair_grant_declined with the reason, the terminal reference, and the remainder, and fails the run as a TYPED validation failure (FailRunError: the orchestrator finish could not complete its granted repair) instead of the generic budget error; on the synthesis path the redemption's declined verdict repeats the same marked message through its terminal read, so both repair surfaces tell one story.

1.222.0

Minor Changes

  • 8326268: Counted section collections join the finish contract (RV2206). The parity contract demands numbered collections (48 N01.-style negative scenarios, 16 C01. counterexamples), and nothing enforced them: the second accepted subscription dossier carried 0 and 0 against a synthesis instruction naming both, and only a runner-side format pre-teach closed the gap, by hope rather than contract, while citations enjoyed per-section validation since v1.71. finishContract grows sectionPatterns: per entry, at least min matches of a regex INSIDE a named section's slice, DISTINCT by first capture when the pattern captures (a repeated id counts once), with literal samples embedded in the golden fixtures and quoted by the prompt statement (with a capturing pattern the samples must carry min distinct captures, because the accept skeleton must satisfy the demand it embeds, and a boundary-sharp reject golden drops exactly one sample line). The standalone validator is sectionPatternCountValidator (contract-section-patterns inside the bundle); a deficit reason names the section, the label, the found-against-required count, and how many are missing, so a repair turn knows exactly what to add. Absent the field, the manifest normalizes, hashes, and behaves byte-identically.

1.221.0

Minor Changes

  • 032ce93: The exposure drain grants a mid-work seat one clamped finalization turn (RV2204). The third parity rerun killed three workers ~30 turns into research with evidence pools of 17 and 22 under a floor of 24 and a CONFIGURED finalization window: the drain came before the window, and the window's play needs the very wire the drain refuses, because a drained seat's next ordinary turn re-prices the whole per-turn allowance the pool just refused. With limits.finalizationReserve.maxOutputTokens declared, a drained seat that already completed a turn now spends ONE finalization turn before its typed exposure-drained terminal: the output clamp shrinks the turn's exposure estimate to the summary allowance, the finalizationWindow.allow list rides as the turn's only tools so outstanding record_evidence calls land in parallel through the ordinary tool machinery, and the drain instruction is request-only, mirroring the tool-budget reserve turn. Best effort on every edge: a refusal of even the clamped estimate warns and keeps the typed terminal, and a seat with NO completed turns keeps dying at zero provider attempts (the RV2002 doctrine, pinned). Preflight learns the funding truth: drained-finalization-unfunded (info) names a window declared under an in-flight exposure cap with no reserve to fund the grant, and inert-finalization-reserve stops warning when the exposure cap alone gives the reserve a trip path.

1.220.0

Minor Changes

  • 0babe70: The failure envelope carries the pass truth (RV2203). Two live terminals hid facts their journals held: the RV2106 mirror run's error terminal read claimConsistencyMeta: null over a journaled declined-judge verdict, and the seventh subscription parity resume settled exhausted with completion: null and childStatusCounts: null over a journaled accepted acceptance with four ok children, because the exhausted path lifted only from the partial value and the raw BudgetExhaustedError carried nothing. Three fixes: the orchestrator enriches every synthesis-path failure with the acceptance snapshot, the claim-consistency meta, and the {ran, reason} pass summaries (the budget class is preserved, so exhausted stays exhausted, and the ok envelope and the failure enrichment now build their summaries with one shared builder whose synthesis arm reads synthesis-failed on the failure path); the run-completion lift falls back to the enriched error data on the exhausted path; and the lift itself (with run:end and RunOutcome) grows claimConsistencyMeta and synthesisSkipped under the established mirror posture, valid shapes mirrored, malformed shapes silently absent, on every terminal, ok and failed alike.

1.219.0

Minor Changes

  • 65a4ce7: The evidence-grade repair guidance is composition-safe (RV2202). The RV2106 mirror run reached the synthesis finish and lost the run to two individually correct validators: evidence-grade demanded "name a run id or a file:line citation beside it", the synthesis obeyed literally and wove inline run ids into sentences that already carried source citations, and cited-value then rejected exactly those sentences, because a run id is never in the cited window; the model sat between the two verdicts and both granted repairs burned ($5.31 with no dossier, after an accepted acceptance and a typed judge degradation). A validator reason is a repair instruction, so it must be executable without violating any sibling in the bundle: the verdict now steers to the safe shape (a file:line citation in the claim's own sentence, or the run id in a SEPARATE sentence carrying no source citation, with the trade named explicitly), the pairwise rule is documented for validator authors beside the audited built-in bundle, and the regression suite pins the guided shape passing evidence-grade AND cited-value together while the trap shape keeps failing exactly one of them. The reason text is API for repair prompts; hosts matching the old bytes must update.

1.218.0

Minor Changes

  • 088bda6: The lifetime spawn counter survives resume, the accepted-finish synthesis decline journals its verdict, and preflight prices the tail's spawn budget (RV2201). The seventh subscription parity run was killed mid-fan-out and resumed: the resumed segment seeded the counter from the journal fold (5 agents) and the roll-forward of the four journaled child admissions incremented it AGAIN, so the post-acceptance tail starved at 9 against a cap of 8 with its money whole: the claim judge declined typed (the RV2106 catch holds for non-monetary refusals), and the synthesis spawn refusal reached the terminal as a bare message with no decision entry while its 1.40 reserve sat intact. Three fixes: admitRecovered no longer increments the lifetime counter, so each spawned agent counts a single time across the run's whole life, never twice (at its fresh admission, or through the seed of whichever segment rolls it forward), and the c7 kill-and-resume shape now seats its judge and synthesis; a synthesis admission refused after the validated coordination finish journals orchestrator_synthesis_redemption_declined with the refusal's reason, the remainder, the live spawnHeadroom, and path: 'accepted-finish', the same verdict the redemption path writes, so a journal reader asks one question either way; and preflight grows tail-spawn-budget (the declared wave rows are already denied row by row against the cap, but the claim judge and the synthesis spawn after the fan-out and no row priced them) plus the orchestrator.headroomTurns knob for the previously hardwired reserve-line-headroom threshold (default 2, 0 silences the fence). journal-shape-revision: statsBefore.spawnsBefore embedded in post-resume admission decisions now reflects the once-per-life count (the crash-during-revision and config-drift-resume cassettes re-recorded with exactly that one value changed); already-journaled entries replay verbatim, so existing journals stay valid.

1.217.0

Minor Changes

  • ab80b97: The declined judge admission degrades typed, the refusal names its holds, and preflight prices the working room (RV2106). The ninth parity run finished its whole fan-out (four ok children, a composed and accepted draft) and then died bare: the claim-consistency judge's 0.28 admission estimate did not fit the orchestrator account's working room past the held 1.40 synthesis reserve, the pre-dispatch refusal flew out of the coordination uncaught, and the run settled exhausted with no fold and the funded synthesis never dispatched, while the refusal message printed arithmetic that fit with room to spare because the hold was in the sum and not in the text. Three fixes: the declined judge admission journals orchestrator_claim_judge_declined with the refusal text and the post-refusal remainder, the meta carries judgeDeclined: true beside the judgeFailed precedent, the synthesis still runs, and only the armed 'fail' posture stops the run; the admission refusal message gains a plus the held synthesis reserve N USD clause exactly when a hold exists (hold-free refusals keep their bytes) with synthesisReserveUsd and finalizeReserveUsd stamped on the error data; and preflight grows orchestrator-working-room, judging effectiveCap - synthesisReserveUsd against one coordination turn floor plus the newly declarable orchestrator.claimConsistency.judge.estCost.

1.216.0

Minor Changes

  • b357f4a: The evidence-grade verdict names its offending sentences (RV2105). The eighth parity run's synthesis was told evidence-grade claims cite no run or repro artifact in their own sentence: live-observed over a 5000-word document, repaired blind twice (the second repair fixed production-proven and never found the live-observed sentences), and the run failed closed with half its budget unspent. evidenceGradeValidator reasons now carry the offending sentences verbatim beside the phrase list, bounded to five and truncated per sentence (whitespace-normalized), with an and N more offending sentences tail, so a granted repair turn reads exactly the lines the verdict judged. The blindness audit covered every other finish validator: each already names its material (sections, headings, fields, citations, missing pool items, codepoints with context), so the fix is exactly one validator wide.

1.215.0

Minor Changes

  • e1da4c7: The refused turn's message rides the terminal, and the synthesis reserve is priced against its own composition (RV2104). The seventh parity run's synthesis composed to its 40000-token output allowance, failed the section validator on the truncation, was granted a repair, and the repair turn was refused at the crossed ceiling; the terminal journaled a bare agent terminated with status error because every beforeTurn catch discarded the refusal's text, and the RV2103 declined verdict repeated it. The pre-dispatch ceiling guard's own message, naming the crossed account and the spent-of-ceiling arithmetic, now rides the agent terminal from all five refusal sites (the loop turn, summarize, finalize, extract, and the finalization-reserve skip's warn log), so the ctx terminal entry and the redemption's declined verdict tell the refusal's truth. Preflight grows synthesis-reserve-below-cap-composition: the minimal-payload check prices the shortest accepting finish, but a reasoning model writes to its allowance, so the finding prices one allowance-sized turn (plus the declared input floor) and one more when the validation declares a repair reserve, and warns when the committed budget.synthesisReserveUsd is smaller; the seventh run's 0.70 hold funded a composition it could not repair.

1.214.0

Minor Changes

  • c8af0ec: The declined verdict tells the terminal's truth, and a severed synthesis is retried once (RV2103). The sixth parity run's synthesis dispatched for the first time in six runs (the RV2102 drain worked by the book) and died as stream idle for 240000ms with $0.9077 still uncommitted; the declined verdict then journaled the ctx boundary's generic run budget ceiling reached because the exhausted flag is armed at the fallback by design. The declined reason now reads the terminal entry behind the re-mint's data.entryRef and carries the message that actually ended the attempt, with terminalRef naming the entry and transportRetries counting the second wire; a refusal thrown before dispatch keeps its own admission arithmetic. A synthesis attempt severed on the wire (a transport-class terminal marked retryable, past the loop's own wire retries) is granted at most one retry from the same remainder: the journaled orchestrator_synthesis_redemption_retry decision keeps the second attempt auditable, and an unaffordable retry declines through spawn admission instead of dispatching.

1.213.0

Minor Changes

  • 61680df: The redemption drains the stragglers first (RV2102). The fifth parity pair reached the RV2101 redemption twice and lost the synthesis to the same next layer both times: a still-running child's committed admission reserve pushed the synthesis spawn past the ceiling (spent ~5.0 + straggler reserve 0.66 + est 0.78 > 6.00), the refusal lived only in a swallowed throw, and the straggler's post-boundary finalize burned 148k input tokens before teardown cancelled it. At the reserve line every remaining child faces the same refused arithmetic, so the redemption now aborts and awaits every unsettled child BEFORE the synthesis dispatch: their reserves release at their terminals, no NEW wire dispatches past the boundary, and a severed in-flight stream bills as the documented layer-3 overshoot. A redemption that still cannot fund the synthesis journals its verdict instead of folding silently: the orchestrator_synthesis_redemption_declined decision carries the refusal text, the post-release remainder, and the drained-straggler count. With the drain in place both fifth-pair runs would have funded their synthesis from the freed remainder.

1.212.0

Minor Changes

  • e6f8516: The reserve line is a boundary, not a death (RV2101). The third and fourth parity runs died on the two denominators the settle-time reserves sat in: the third at spent $4.7064 plus the $1.00 synthesis reserve against the 5.70 in-flight exposure cap (drain cascade with zero live estimates), the fourth at spent $5.065 against ceiling - reserve = 5.00 (the root refused one output token, the intact $1.00 reserve unreachable, no synthesis). Three fixes, one doctrine: money promised to the tail is fenced by the budget chain alone, and reaching its line runs the tail instead of killing the run. The in-flight exposure admission now counts spent + live estimates only (the finalize and synthesis reserves left the sum; the budget chain already fences them). The coordination loop's typed output-floor refusal (a new AgentError.reason beside exposure-drained, preserved across the ctx boundary like the in-flight marker) now settles the documented forced-finish partial with the journaled fallback decision (reason budget-floor beside exposure-abort), and when a synthesis step is configured with its reserve still committed and at least one settled child, the synthesis promise is REDEEMED: the ordinary synthesis invocation runs from the released reserve with no coordination draft and its contracted output rides the partial envelope as result. Preflight prices the budget-side trajectory beside the re-priced exposure floor: admission.reserveLineUsd and admission.reserveLineHeadroomUsd, with the reserve-line-headroom warning when the admitted wave's steady state sits within two coordination turn floors of the line, and exposure.requiredMinimumExposureUsd drops the tail reserves in lockstep with the live formula.

1.211.0

Patch Changes

  • d5a8a36: The third parity rerun's crash shapes become permanent fault-kit gates (RV2009), zero paid calls. parity-quiescence-deadlock drives the exact terminal shape in miniature: the coordination turn eats the exposure cap, every worker is refused DRAINED (typed exposure-drained, zero provider attempts, RV2001/RV2002), the root forced-finishes partial (RV1902), and the gate asserts the exhausted terminal, the closed roster, run_settle after every agent entry, one wire denominator, and no unsettled invoice lane (RV2003/RV2008); any revert reads matched:false. parity-sequential-roster-floor drives the seat-by-seat roster under an unreachable acceptance floor and asserts the FIRST seat's typed roster_floor refusal with the whole-roster arithmetic journaled and zero paid children (RV2005). The docs truth pass lands the no-silent-exit invariant in the README and the design principles (no path ends the process while a run has no journaled terminal) and extends the observability denominator map with the RV2008 incremental lane and its settled boundary.

1.210.0

Minor Changes

  • c871ddc: Incremental billing journaling (RV2008). ProviderCallRecords rode ONLY the terminal agent entry, so when the third parity rerun's process died with the root still running, ~$0.99 of its dispatches existed nowhere durable: the live ledger read $4.467 while the journal folded $3.478. Every record now journals the moment its wire call settles, as a provider-call decision row keyed by the dispatch seq and the record ordinal in the invocation's own scope; the terminal entry still carries the canonical set, replayed segments append no duplicates, and the crash window shrinks from the invocation's whole history to the one in-flight turn. invoiceFromJournal gains the additive unsettled lane: dispatches of agents still running at the journal's edge, priced from the incremental rows and kept OUTSIDE the settled totals (run_settle stays the billing boundary). rulvar cost-audit grows a sixth check, incremental-rows-match: every settled agent's terminal dispatch set must equal its incremental rows, count and per-ordinal usage alike; agents with no rows (pre-RV2008 journals, replayed invocations) pass vacuously. The frozen cassette catalog is re-recorded for the additive rows (journal-shape-revision, policy not identity: existing entries byte-identical, no hashVersion change).

1.209.0

Minor Changes

  • 514c7bb: Cache-aware preflight (RV2007). Every spawn report now prices its loop input floors both ways: uncachedLoopInputFloorUsd (the declared estInputTokens re-billed at the full input rate on every projected provider turn, exactly what the third parity rerun paid at ~$1.10 per worker cycle) and cachedLoopInputFloorUsd (one cache write plus a read per later turn at the price row's cache rates, the RV2006 policy's economics, ~$0.19 for the same shape). The new uncached-long-loop warning fires when a shape projecting four or more provider turns is about to run with the cache policy OFF on an adapter that declares explicit prompt caching, naming both figures; under the default policy the loop caches and nothing fires. The budgets guide's sizing section carries the worked parity numbers.

1.208.0

Minor Changes

  • e7d426f: First-class prompt-cache policy (RV2006). ChatRequest.cacheHint existed and the Anthropic adapter compiled it into cache_control, but nothing in the core ever populated it: the third parity rerun's workers re-paid the full input rate on every turn of their ~550k-token contexts (cacheReadTokens 0 across the run), and the $6 envelope sized on OpenAI's implicit server cache was incomparable on Anthropic. The agent loop now compiles the hint on every tool-cycle turn: breakpoints after tools, after system, and after the deepest message, sliding with the history. Default ON exactly where the adapter declares the new ModelCaps.promptCaching: 'explicit' (the Anthropic adapter does); OpenAI declares 'implicit' and undeclared adapters get byte-identical requests. Configure with defaults.cache, AgentProfile.cache, or per-call opts.cache (CachePolicy { mode?: 'auto' | 'off'; ttl?: '5m' | '1h' }), call over profile over engine. Billing note: on cache-capable Anthropic models this changes the wire requests of every loop turn to carry cache breakpoints, typically cutting long-cycle input cost several-fold (cached reads bill at a tenth of the input rate); CostReport cache accounting is unchanged, the hint never enters identity or journals, and @rulvar/testing's requestHash strips it so existing cassettes replay byte for byte.

1.207.0

Minor Changes

  • 99beee2: Sequential roster feasibility (RV2005). The third parity rerun's model ignored the one-batch instruction and spawned seat by seat through spawn_agent, so the RV1908 batchGate never saw a batch: three seats were paid in full under an acceptance floor of four the money could never reach, and the settle verdict was bound to reject them. Under a declared acceptance.minSpawnedChildren, every SINGLE spawn_agent admission now projects the whole remaining roster with the shared RV2004 arithmetic (this seat's own dispatch projection per remaining seat, live in-flight exposure included) and refuses the FIRST infeasible seat with the typed roster_floor verdict, its arithmetic journaled on the decision, zero paid children. Batch seats skip the per-seat check (their batchGate judged the wave entire), and spawn-admission decisions now journal their true origin (parallel_agents seats no longer read as spawn_agent). For hosts that want the policy unsplittable, OrchestrateOptions.requireBatchSpawn: 'reject-spawn-agent' refuses every single spawn_agent call typed (code 'batch_required', nothing journaled, nothing paid) so the model re-issues the wave as one parallel_agents batch.

1.206.0

Minor Changes

  • ec8e1f1: One admission arithmetic for preflight and the live spawn_agent verdict (RV2004). The third parity rerun's spawn verdicts journaled reserve/childCeiling $0.50 (the derived childBudgetFraction cap) under a declared profile estCost of $0.70 that dispatch actually committed: the journal lied about the held money, resume would have rolled the lie forward, and the 0.50 allowance would have severed the child mid-work. On the spawn-tool path (spawn_agent, parallel_agents), where the fraction never materializes as an account, the verdict reserve now IS the shared dispatch projection (the declared estimate or the flat default, clamped by an explicit budgetUsd alone), and every verdict names its derivation (reserve.source: estCost | default; reserve.clampedBy: explicit-budget | fraction-ceiling). Origins with a real allowance account (ctx.workflow) keep the historical fraction ceiling and clamp. Preflight gains the live-root-exposure term: the orchestrator's own worst-case turn floor now rides the embedded spawn gate and admission.requiredMinimumCeilingUsd (published as admission.liveRootExposureTermUsd), so the parity envelope's fourth seat, which fit the plain 5.95-under-6.00 arithmetic and was refused live, is refused in preflight too. The frozen cassette catalog is re-recorded for the additive source/clampedBy fields on journaled admission verdicts (journal-shape-revision, policy not identity: existing entries byte-identical, no hashVersion change).

1.205.0

Minor Changes

  • 6d224da: The quiescence guarantee: no silent exit (RV2003). The third parity rerun's process exited mid-run with an unsettled top-level await: the parked root's exposure wait held nothing on the event loop, and the journal kept a forever-running root with no run_settle and no terminal. Three guards close the class. A parked exposure waiter arms a ref'd keepalive interval (disarmed with the last waiter), so a process whose only remaining work is the wait hangs visibly instead of vanishing; each tick sweeps for the drained state (no holder of any kind left) and wakes waiters 'drained' as defense in depth behind the event-driven wakes. The engine registers every unsettled run with a process beforeExit quiescence watchdog: an event loop about to die with an unsettled run forces that run through the ordinary cancel path, the RV1903 terminal barrier, run_settle, and a terminal envelope, even when the body is stuck on a bare promise no signal reaches (the settle race gains a watchdog arm). The invariant, pinned by a regression on the exact parity deadlock shape: no path ends the process while a run has no journaled terminal.

1.204.0

Minor Changes

  • efaec9b: Spawned children wait out exposure refusals instead of dying (RV2002). The third parity rerun terminally killed three of four workers, each ~550k tokens into research, with a pre-wire in-flight exposure refusal that would have been a parking for the root. Orchestrator-spawned children (spawn_agent and parallel_agents) now share the RV1902 wait posture: the refused child parks (the budget:exposure-wait event carries scope: 'child'), retries pre-wire when a live hold releases, and pays zero provider attempts while parked. Only a drained refusal (no live holder left to wait out) ends the seat, and it ends typed and cheap: AgentError.reason 'exposure-drained', carried into the journaled terminal's error.data.reason, so the orchestrator tells a starved seat apart from a crashed child and can re-spawn it once money frees. The root keeps its documented forced-finish partial on the drained arm.

1.203.0

Minor Changes

  • fb08c10: Every agent terminal returns its live exposure holds (RV2001). The third parity rerun died on the hole: three children killed pre-wire by the in-flight exposure cap left $0.478 of live dispatch estimates parked against the cap forever, and the root's exposure wait starved on money no live dispatch was holding. Holds are now attributed to the invocation whose dispatch they cover; every settle of that invocation (ok, error, exhausted, cancelled, thrown paths included) releases whatever a lost attempt closure leaked and wakes the parked waiters, a late closure can no longer eat the money of another holder, and the live total snaps to exactly zero when the last hold of any kind is gone. RunBudget.releaseExposureHolder and RunBudget.liveExposureHolderCount publish the surface; zero holders beside live waiters is the drained signal the wait machinery keys on.

1.202.0

1.201.0

Minor Changes

  • 7e01189: The documentation says exactly what the lifecycle now guarantees (RV1909). The README and the design principles carried "a full cost report" as a promise the twenty-first benchmark falsified; with the exit barrier (RV1903), the settle drain and the journal seal (RV1904) the promise became a lifecycle guarantee, and the docs now state the enforcement rather than the aspiration. The observability guide gains the denominator map: the settled fold, the run:end totals, the terminal envelope and invoiceFromJournal are one fold that agrees by construction; a mid-run budget:update or a refusal's spent is an instant of the live ledger, never the terminal; and a later re-fold reproduces the settled figures byte for byte because the seal forbids the journal to move. The benchmark's four views were honest clocks over a roster that kept moving; the lifecycle now stops the roster before the first terminal figure exists.

1.200.0

Minor Changes

  • e2ddbdf: The parallel_agents admission policy (RV1908). The four-role benchmark's batch died fail-fast at the third task: the fourth mandated specialist was never attempted, and the run paid two workers in full under a roster floor of four the wave could never reach. OrchestrateOptions.parallelAdmission names the alternatives: 'fail-fast' (the default, the RV805 shape) stops at the first refusal; 'try-all' attempts every task and reports every refusal in a refusals list beside the historical refused slot; 'all-or-none' projects the whole batch against the live remainder with the embedded gate's own formula and refuses it typed (code 'batch_atomic') with zero admissions when it cannot seat entirely, cancelling admitted siblings on a non-budget mid-batch failure. Independent of the policy, a declared acceptance.minSpawnedChildren arms the roster pre-check: a batch large enough to seat the floor whose feasible count cannot reach it is refused (code 'roster_floor') before the first child is paid. Runtime behavior only: the tool's schema and description never move, so toolset hashes stay byte identical.

1.199.0

Minor Changes

  • 29891c6: The preflight prices the two minimums the benchmark lacked (RV1907). admission.requiredMinimumCeilingUsd is the whole-wave fill: every declared row's reserve plus the finalize and synthesis carve-outs, the figure a viable budgetUsd must strictly exceed; the four-role benchmark's $6.00 ceiling sat $0.98 below its own wave's 6.98 and lost two of four mandated workers to it. exposure.requiredMinimumExposureUsd is the breathing floor of maxInFlightExposureUsd: the carve-outs plus the maxInFlight most expensive concurrent turn floors, the orchestrator's own turn among them; the recovery arm's $3.20 cap sat below it and stalled the coordinating turn beside its own full child wave. A declared cap below the floor draws the warning finding exposure-cap-tight with the equation priced term by term, naming the RV1902 park it predicts. The budgets guide gains the sizing arithmetic with a worked four-worker example.

1.198.0

Minor Changes

  • c097c96: The terminal event semantics say what happened (RV1906). The four-role benchmark's primary stream read a root agent:end with status ok followed by a run:end error with nothing between them naming the policy fold, and its artifacts carried contradictions: null and claimConsistencyMeta: null that the judge had to annotate by hand as NOT RUN. The acceptance verdict now speaks on the stream: orchestrator:acceptance carries verdict, completion, childStatusCounts and the declared roster floor, emitted from the one journaled decision, fresh and on the resume roll-forward alike. And every semantic pass reports an explicit summary: semanticPasses ({contradictions, claimConsistency, synthesis}, each {ran, reason?} with reasons 'not-configured', 'run-rejected', 'valid-draft', 'not-run') rides the acceptance envelope, the typed rejection data, the RunOutcome and run:end through the same validated lift as the acceptance roster, so an absent findings field can never be read as a clean pass.

1.197.0

1.196.0

Minor Changes

  • ec9c3e3: One terminal denominator (RV1904). The four-role benchmark's recovery run reported four mutually inconsistent cost views because the settle raced the roster: RV1903 barriered orchestrations, and this train closes the remaining lanes. The engine's settle drain terminates every live agent invocation of a PLAIN workflow (an un-awaited ctx.agent a body returned over) to a journaled terminal before run_settle exists. The journal's billing lanes seal after the durable settle: a late append rejects with the typed JournalSealedError (code 'journal_sealed'), while the detached resolution lane stays open by contract, because resolutions answering a suspension or a parked approval are the documented post-settle appends. And the terminal grows the wire denominator: CostReport.wireRequests and TerminalEnvelope.wireRequests carry the per-dispatch ledger's provider request count, absorbed continuations included, equal to the invoice cardinality's wireRequests on ledger-covered runs by construction, so the terminal a consumer gates on and the invoice a finance pipeline folds finally agree on how many wires the run made.

1.195.0

Minor Changes

  • 5702a70: The terminal child barrier (RV1903). The four-role benchmark's recovery journal recorded run_settle at sequence 18 and three successful child terminals at sequences 19..21: the returned RunOutcome, the terminal invoice, the captured event stream and the final journal each reported a different total, and none was wrong by its own clock. Every orchestration exit, returned or thrown, an accepted or rejected finish, a typed failure, a budget or exposure terminal alike, now passes a terminal child barrier before the workflow settles: OrchestrateOptions.onUnsettledAtExit: 'cancel' (the default) aborts the stragglers and awaits their journaled cancelled terminals, 'drain' awaits their natural terminals bounded by their own limits and budgets, preserving their evidence at the price of the wait. The verdict the run settles with is journaled before the barrier runs, so late children never change it; what ends is the settle racing the roster, and with it the post-settle journal mutation that split the cost views.

    The frozen cassette catalog is re-recorded for the barrier's additive cancelled child terminals in runs that previously left stragglers running past the settle (journal-shape-revision, additive terminals only: existing entries byte-identical, no hashVersion change).

1.194.0

Minor Changes

  • 360a659: The orchestrate root waits out transient exposure refusals (RV1902). The four-role benchmark's recovery arm died on a contract violation: the budgets guide names an in-flight exposure refusal transient, but when the refused agent was the workflow's coordinating root, the typed refusal escaped the orchestration and settled the whole run exhausted with a null completion while four admitted children were still finalizing. An orchestrate-owned root dispatch (the coordination loop, the synthesis invocation, the forced-finish wake) now parks the refused turn until a live exposure hold releases and retries pre-wire, zero provider attempts while parked, emitting the typed budget:exposure-wait event with the refusal arithmetic (capUsd, spentUsd, inFlightUsd, estimateUsd, willWait: true). A drained refusal (no live hold left to wait out; spend never shrinks, so nothing can turn it into a fit) settles the documented forced-finish partial instead of a bare escape: the run exhausts with the settled children's fold as its value, a journaled orchestrator_finalize_fallback decision (reason 'exposure-abort') for replay identity, and willWait: false on the event. Plain agents keep the documented settle-as-budget-error behavior, because their caller can catch and decide.

1.193.0

Minor Changes

  • 2bca1d1: The admission projection holds the synthesis reserve exactly like the live gates (RV1901). The four-role benchmark's primary arm configured a $6.00 ceiling, a $4.50 orchestrator cap, a $1.00 synthesis reserve and four workers at estCost $0.62; preflight read the wave 5/5 green while the live gate refused the third worker, because the projection netted the synthesis carve-out out of the orchestrator's own row and then held nothing for it at the run root, where the runtime registers it before any spawn admits and both live gates (refuseSpawnIfInfeasible, remainderOf) count it. The wave arithmetic now carries the hold in both projection layers, and the exact benchmark configuration projects 2 of 4 seats before the first wire, matching the live gate for the same reason. The report exposes the equation: admission.synthesisReserveUsd names the hold, every wave row carries heldAtEvaluationUsd (the money already held when the row was evaluated), and the declared orchestrator.acceptance slice accepts minSpawnedChildren, so a wave whose budget seats fewer children than the acceptance floor demands (minSpawnedChildren or childPolicy.minSuccessful) draws the error finding admission-below-roster-floor instead of paying for a roster the settle verdict is bound to reject.

1.192.0

Minor Changes

  • 8757601: quota:denied becomes the primary event for recoverable pre-wire waits (RV1810). The twentieth benchmark's run emitted 13 agent:error events that were all healthy token-window waits (a clean run, zero provider errors, zero transport retries), so any alert keyed to the event TYPE read a failing run. A recoverable denial now emits quota:denied (the denied model, the limiter's reason, retryAfterMs, willRetry: true); the legacy agent:error twin is gone by default and createEngine({ telemetry: { quotaDeniedAgentError: true } }) restores it, the versioned compat posture. Terminal denial exhaustion still ends in the real agent:error. The observability guide gains the vocabulary section beside it: throttling versus failure, why orchestrator.wakes counts durable wait suspensions and not progressive await completions, and why internal root work reads from byRole while byAgentType and byPhase keep their honest empty-string buckets (synthetic phase wrappers would move journal bytes and re-key resumes).

1.191.0

Minor Changes

  • 745387c: Enforceable coverage floors and two new corpus classes (RV1809). The claim pass graded itself honestly (RV1702) but nothing could enforce a floor: claimConsistency.minimumCoverageRatio and runFactCoverageRatio (each in (0, 1]) now declare the minimums, onLowCoverage: 'report' (default) stamps the machine-readable lowCoverage block on the meta with each ratio beside its floor, 'fail' fails the run typed BEFORE the judge dispatch exactly like onUncoveredCritical, the meta additionally carries runFactCandidates (the uncapped matched count, so both ratios are computable from the meta alone, live or persisted), and --strict exits nonzero on a stamped block with the ratios printed. The adversarial corpus grows two classes from the nineteenth benchmark: modality-overclaim (a mitigation stated as an unconditional guarantee: the attestation "stops any tool drift" beside the pool reading naming the contract-hash boundary) and scope-ambiguity (child-only totals printed as whole-workflow figures), both forming pairs through the same pure folds.

1.190.0

Minor Changes

  • 8e02021: MCP discovery gains the visited-cursor guard, the whole-sweep deadline, and the production bounds demand (RV1808). The RV1602 cycle guard caught only the immediate self-echo, so an alternating cursor pair (A, then B, then A again) paginated forever whenever maxPages was left unset; the sweep now refuses typed on ANY cursor it has already queried with, unconditionally, like the echo guard. timeouts.discoveryMs adds the wall clock over one whole tools/list sweep: per-page listMs cannot bound a crawl of promptly-answered pages, and maxPages binds only when declared, so the deadline is the bound that watches the sweep as a unit, refusing typed with the page count. And requireBounds: true is the production posture: the source refuses at construction unless maxTools, maxPages, maxSchemaBytes, and timeouts.discoveryMs are all declared, one typed error naming what is missing instead of four silent unboundeds; the production profiles guide now says to set it.

1.189.0

Minor Changes

  • 6a5cc2d: The settled-set consume path, structured tool failure reasons, the labeled fact-sheet scope, and the machine-readable late-child boundary (RV1807). The nineteenth benchmark's root consumed six children with fourteen get_child_result calls, eight of them speculative probes that errored on not-settled handles, its answer printed the child-only fact sheet as "the current workflow" totals, and public tool events said only outcome: 'error' throughout. Every await_any digest now carries settledHandles (the settled subset of the waited set at return time, recorded truth like the digest itself); exposeSettledResultsTool: true adds get_settled_child_results(handles, maxCharsPerChild?), the bulk first-page read that refuses typed BEFORE any read when a handle is unknown or still running, under its own opt-in so no existing run's toolset hash moves; tool:end events carry a structured errorCode on failures (unknown-tool, invalid-arguments, child-not-settled, unknown-handle, and the RV1807 data.errorCode convention for tools that stamp their own); the RUN FACTS synthesis sheet names its scope in the quoted bytes (scope: 'settled-children-only', with the whole-run totals delegated to the terminal envelope and invoice); and a finish that validates over a still-running child names it in the structured unsettledAtFinish list on the acceptance decision and the result envelope, beside the existing prose degradation note, with the pool boundary documented: a late child's output never re-enters the frozen contradiction and claim pools.

1.188.0

1.187.0

Minor Changes

  • c9798ef: The absorbed pause_turn wire set survives the error arms (RV1805). The Anthropic adapter published the whole segment set (wireRequests = { count, responseIds }) only on the successful terminal finish, so an error after absorbed continuations, a create() failure, a truncated read, the continuation cap, or a pre-wire segment denial, yielded bare and orphaned exactly the paid wires a per-request statement join needs most (the segments' usage already survives through mid-stream reports; the ids and the count did not). Every error arm now rides the COMPLETED absorbed segments' wire set on its error data, the agent loop's provider call record reads it when the finish that would have named the set never came (a single absorbed segment included, since an errored dispatch has no plain responseId to join by), the invoice row keeps the ids and the count, and a first-segment failure stays a bare error with nothing invented.

1.186.0

Minor Changes

  • 242647e: Three accounting-truth gates (RV1804). The admission countTokens probe becomes a policy surface: it is full-prompt provider egress billed to no invoice row, so defaults.countTokens: 'deny' (engine-wide) or AgentProfile.countTokens (profile wins) forbids the control wire outright, the flat reserve admits exactly like an adapter without countTokens, and every probe outcome is a typed control:wire event (ok with the counted tokens, failed, denied) instead of a log line only. Strict pricing's declared freshness bound now clamps the future too: a ratesVerifiedAt more than one day ahead of the engine clock refuses typed, because a stale-only check reads any future date (the classic typo'd year) as eternally fresh; the one-day tolerance absorbs date-only strings authored ahead of UTC. And statement reconciliation holds the join key unique on both sides: a duplicate response id among the local invoice rows (multi-wire segment ids included) now refuses typed exactly like a statement-side duplicate, because a usage-only export would otherwise settle match with a double-booked local row silently absorbed.

1.185.0

Minor Changes

  • 1248623: A finalize route declared at the workflow level now fires the finalize phase (RV1803). The role trigger read [call, profile, engine] while model resolution read all four layers, so defineWorkflow({ routing: { finalize: … } }) resolved the finalize model and then never dispatched the phase; the route worked only when repeated at the call, profile, or engine layer. The trigger now reads the same four layers resolution reads, a workflow-only route fires exactly one finalize dispatch, resume replays the journaled synthesis without paying a second one, and a workflow layer without a finalize route still never fires the phase.

1.184.0

Minor Changes

  • 8a9caca: The toolset attestation gains an authority side (RV1802). toolsetHash pins exactly the model-facing contract tuple {name, description, parameters, version} by design, so under an attested profile a tool whose risk flipped from read to write, whose needsApproval gate was dropped, or whose executor/executorSpec routing changed passed the pin silently while changing what the ask rules and the approval flow would do. resolveToolset now derives a per-tool authority record { contract, risk, needsApproval, executor, executorSpec: sha256(JCS(spec)) } and an aggregate authorityHash riding ResolvedToolset; attestToolset() records both sides; enforceToolsetAttestation refuses authority drift at the same pre-wire site as contract drift, naming the drifted field per tool, with missing and unexpected tools listed and shapes validated at createEngine time. Execute bodies stay deliberately unhashable on both sides (version remains the lever), and pins recorded before this release keep their documented contract-only posture until re-recorded with attestToolset().

1.183.0

Minor Changes

  • dd3767c: The decision chain reads the canonical payloads the engine journals (RV1801). The fold shipped in RV1705 read a resolution's by/target/decisionRef and an abandon's target/authorizedBy from entry.value, but the engine writes those facts in the canonical entry.resolution and entry.abandon payloads with no entry value at all, so on a live journal the reconstructed authority record lost who resolved, what sanctioned an abandon, and the decision value itself; the fields survived only on hand-authored journals that carried them in value. reduceDecisionChain now reads the canonical payloads first and keeps the value-carried forms as the fallback, a resolution row's value is the decision the ask was resolved WITH when the entry itself carries none, and the operational host acceptance test pins fold-to-journal parity on a live engine run: every canonical field the engine journaled (the external by, the referenced ask, the allow, and the deny with its reason) is exactly what the chain row reports.

1.182.0

Minor Changes

  • 144d026: The operational host reference ships executed, with the decision-chain audit fold in core (RV1705). The eighteenth comparison benchmark's operational acceptance named four behaviors a production host must prove, not describe: a tenant cannot read or effect across a tenant boundary, a revoked approval is never executed, a redelivered attempt cannot duplicate an external effect, and an audit reconstructs the decision chain. The new operational host guide walks the reference arrangement of shipped primitives for all four, and examples/src/operational-host.ts executes them through the full engine on FakeAdapter: per-tenant engines by construction (own store, own toolset, strict approvals, ask on every mutating class, the journaled approval deadline), a pre-effect deny path proven empty-ledgered, a guarded effect whose idempotency key suppresses the re-fired side effect while the ledger records both attempts honestly, and a replay on an adapter that refuses to serve leaving the effect count at one. The core half is reduceDecisionChain(entries): one pure l0 fold that reconstructs a run's authority record (approvals with what was asked, resolutions referencing the ask by seq, admissions, abandons, terminations) in the journal's own total order, never inventing a field an entry did not record, tolerant of unknown kinds by the reader obligation, so "who allowed this and when" is a fold instead of an investigation.

1.181.0

1.180.0

Minor Changes

  • b124d26: Statement reconciliation is core, with a fail-closed intake for raw exports and a fixed adapter contract matrix (RV1703). reconcileStatement was provider-neutral from birth, typing only against the invoice and the pricing SPI, but it lived in @rulvar/openai and forced Anthropic-only consumers into an OpenAI dependency for a join that never touched OpenAI code; the eighteenth comparison benchmark graded provider readiness "conditionally ready" partly on exactly this asymmetry. The module now lives in @rulvar/core and the historical @rulvar/openai import paths keep serving the identical functions as re-exports, so no consumer rebuild or import rewrite is forced. New beside it: statementFromRows({ kind, rows, map }) normalizes a raw keyed export (a parsed CSV, a JSON download) into a ProviderStatement under one explicit StatementColumnMap, deliberately shipping no per-provider schema knowledge; every mapped cell validates fail-closed with the row index and column name (non-numeric dollars, fractional or negative token counts, empty response ids, unknown component names all refuse typed), absent cells omit their field, and a requests row left with no dollars, no component split, and no usage refuses, because a row without evidence cannot reconcile anything. The providers guide now fixes the per-adapter billing contract in one matrix: what each adapter surface contributes to the join (continuation absorption and the any-id-of-the-set rule for pause_turn dispatches, the one-response-id-per-wire contract of the Responses API, the coverage posture for compatible endpoints and the AI SDK bridge), so reconciliation readiness is a documented contract per adapter instead of an inference.

1.179.0

Minor Changes

  • 1a5a85a: The claim-coverage grade rides the acceptance envelope, and strict reads it (RV1702). The eighteenth comparison benchmark's run reported completion: 'complete' with contradictions: [] while the judge had seen 40 of 144 citing sentences, and three material falsehoods rode that gap; the counts that told the truth (RV1603) still had to be interpreted. The claim-consistency meta now carries coverage, one closed vocabulary a consumer reads instead of inferring semantic health from an empty findings array: 'full' (every citing sentence had a judged pair, nothing cut, no declared critical anchor missed, the judge settled ok; zero citing sentences grade full vacuously), 'partial' (a bound cut the fold or citing sentences went unjudged), 'critical-uncovered' (declared critical anchors got no judged pair), 'judge-failed' (nothing was judged at all), precedence strongest last. The pure claimCoverageOf helper derives the identical grade from any persisted meta, including metas written before the field shipped, so old envelopes grade without re-running. The CLI's --strict now reads the grade beside the completion contract: 'judge-failed' and 'critical-uncovered' exit nonzero, both states that previously slipped through strict as green, while 'partial' prints its counts to stderr and keeps the exit, because the bounded pass is the documented default and declaring critical anchors is the opt-in that makes the subset enforceable.

    Journal: the orchestrate acceptance envelope's claimConsistencyMeta gains the required coverage field on newly settled runs; persisted metas from older engines stay readable and grade through claimCoverageOf.

1.178.0

1.177.0

Minor Changes

  • 94db8ff: Name and pin the progressive drafting pattern (RV1607). The eighteenth comparison benchmark measured 56% of a real run's wall sitting after fan-in, dominated not by validation or repair (both repair turns took seconds) but by the first full draft, composed only after await_all even though every primitive for starting earlier already shipped. Two changes make the better shape first-class. The per-child guarantees are now pinned by tests: await_any returns the first settled digest while siblings are mid-flight, and get_child_result serves a settled child immediately, gated on that child's own settlement and nothing else. And under exposeChildResultTools the default orchestrator prompt gains a conditional nudge naming the pattern (spawn the wave, await_any, read the settled child in full, draft the sections its evidence supports, fold the rest in as they settle); the line rides only with the opt-in whose toolset carries the tools it names, so a run without it keeps its exact historical prompt bytes. The docs' orchestration-modes guide gains the pattern section, with reduceCriticalPath.postFanInShare as the measure of whether it worked.

1.176.0

Minor Changes

  • a74304d: Ship the read-only pilot posture as one factory (RV1606). The production-profiles guide documents the controlled-pilot assembly; the eighteenth comparison benchmark's improvement plan asked for it as a deliverable profile with typed, pre-effect refusals. pilotAgentProfile(options) (async: the attestation pins the resolved toolset) builds on researchAgentProfile and returns { profile, evidence, attestation }: the confined read-only research toolset with the progress contract and stop conditions, the resolved toolset attested so a drifted registration refuses typed at spawn (RV1514), permissions hard-denying write, network, execute, destructive, and undeclared risk in one rule with strictApprovals armed and inheritPermissions off, and isolation pinned to 'none'. A write-risk tool smuggled through extraTools is attested but still refused at dispatch by the risk rule before its execute ever runs. Engine-level posture (budget ceiling, exposure cap, strict pricing, acceptance floors) stays explicit engine and run configuration, deliberately outside the profile's reach.

1.175.0

Minor Changes

  • 1999c5d: Adopt recovered spawn decisions by the full canonical spec on a regenerated spawn turn (RV1605). When a dynamic-orchestrator root resumes without its turn-boundary checkpoint (a lost transcript store, or a crash before the first boundary), it regenerates the spawn turn instead of continuing past it. The recovery path for that shape compared only agentType and prompt at a colliding ordinal, but recovery advances the ordinal counter past every journaled admission, so the collision could not occur: every regenerated spawn re-decided and re-paid its child even with an identical spec, and the eighteenth comparison benchmark separately flagged the two-field comparison as a stale-child hazard had it fired (a changed model hint, schema, or toolset reference would have adopted a child produced under the old spec). Adoption is now content-addressed: a regenerated call whose full spec matches an unclaimed journaled admission byte for byte (jcsSerialize over every field) claims the first such decision in journal order, with the settled child replaying free, a dangling one redispatching pinned to its journaled scope, and a recovered rejection rolling forward typed; a call diverging in any field decides fresh, and the prior decision's child stays paid (at-least-once). Checkpoint-continued resumes are untouched: they never re-execute the spawn turn.

1.174.0

Minor Changes

  • aa9a772: Split the critical-path synthesize wall by purpose (RV1604). The claim-consistency judge dispatches under role 'synthesize', so reduceCriticalPath folded its wall into synthesisMs and one number conflated two different tails: the eighteenth comparison benchmark's harness had to annotate a 54-second synthesisMs by hand because the run had skipped synthesis (synthesis_skipped_by_valid_draft) and the bucket was entirely the judge and its extract phase. CriticalPath (and the clipped postFanIn breakdown) now carry finalCompositionMs (synthesize spans that are not the judge) and semanticJudgeMs (spans dispatched under the exported CLAIM_JUDGE_LABEL, which the orchestrator's judge invocation now uses as its label constant); synthesisMs stays their exact sum, so existing consumers read the same number they always did.

1.173.0

Minor Changes

  • 67d27ac: Make the claim-consistency pass say what it did not judge, steer its bounded budget, and hold the draft against the run's own facts (RV1603). The eighteenth comparison benchmark ran the judge over a real dossier: 40 pairs over 144 citing sentences, truncated honestly, with nothing steering which 40 and two run-fact falsehoods sailing through with executionFacts enabled ("each role recorded 18-20 evidence entries" over recorded profiles of 23/18/22/20/20/20; "real models were not run" beside 125 recorded wire requests). Three additions close it. claimConsistencyMeta.coveredCitingSentences counts the citing sentences with at least one judged pair, so partial coverage is one division away instead of an inference. claimConsistency.critical declares anchors (a file, a directory prefix, or a span) whose pairs sort first, before the max cap; the meta names every critical draft anchor left unjudged (criticalUncovered capped at 32, criticalUncoveredTotal beside it), and onUncoveredCritical: 'fail' fails the run typed BEFORE the judge dispatch so a run whose declared claims cannot be verified never pays for a partial verdict. claimConsistency.runFacts adds the run's recorded execution facts (children, statuses, evidence entry counts, wire and token totals) as a pool reading under the (run-facts) anchor: draft sentences naming a minted id, a standalone recorded value of two or more digits, or a runFactTerms phrase are paired with the sheet and ruled on by the same judge invocation. All three are opt-in; unset configuration derives byte-identical judge prompts, and the pure fold half (pairDraftClaims with critical, the new pairRunFactClaims) is exported.

1.172.0

Minor Changes

  • 0d4770b: Bound the MCP tools/list pagination itself (RV1602). The eighteenth comparison benchmark called out the gap the RV1515 bounds left open: a server answering unique cursors over empty pages grows neither the tool count (maxTools never trips) nor any timeout (each page answers inside listMs), so the sweep could spin wire calls forever. Two guards close it. The cursor-echo cycle guard is unconditional: a page whose nextCursor equals the cursor it was queried with makes no pagination progress and is never a legitimate step, so the sweep refuses with a typed ConfigError on the second page at the latest. The new opt-in maxPages (positive integer, validated with the other bounds) caps the sweep's wire call count for the general no-progress case; like maxTools it fails closed, refusing a server that still reports another page past the cap rather than silently importing a subset of its declared surface. Absent config preserves previous behavior except the cycle refusal, which only ever fires on a protocol-violating server.

1.171.0

Minor Changes

  • f6116b9: Enforce the retry namespace separation mechanically (RV1601). The eighteenth comparison benchmark caught the RV1510 promise leaking live: 21 pre-wire quota-limiter denials exported as agent:end retryCount 21 against an invoice holding zero provider error rows, and each post-denial success record read attempt 2 with no attempt-1 sibling. Three changes close it. A denied turn no longer increments transportRetries, so retryCount reads clean against the provider ledger (the denial stays diagnosable on agent:error via error.data.source: 'quota-limiter'). A denied turn no longer advances the dispatched try counter, so ProviderCallRecord.attempt is the dense 1-based dispatched ordinal by construction and a busy window can no longer exhaust RetryPolicy.attempts before the wire ever opens. Denied turns instead retry against their own budget: the new quota.maxDenials (positive integer, default DEFAULT_MAX_QUOTA_DENIALS = 8, validated at createEngine intake) bounds consecutive pre-wire denials per serving target, each still waiting the limiter's own retryAfterMs, and exhaustion takes the unchanged failover path, so a permanently denied primary still fails over on the rate-limit trigger and terminates typed as rate-limit with no fallback left.

1.170.0

Minor Changes

  • 86e4c06: Name the MCP session posture: per-request auth refresh and the drift policy (RV1516, the P1 tail).

    The auth story and the drift story of an mcp() source get host-owned contracts. http.headers (streamable-http only, forbidden typed elsewhere) injects headers into every wire request through a wrapped fetch; the hook form is awaited before each send, which makes it the refresh point for rotating tokens, with no reconnect and no library-invented 401 retry. drift names what a listChanged notification means: 'rekey' is the documented default (the changed list re-keys subsequently spawned agents), and 'refuse' fails closed: the notification poisons the source, every later tools() refuses typed, and only close() clears it, so importing a changed list is always a deliberate host action. In-flight spawn snapshots are untouched either way, and the two refusal layers compose with the toolset attestation: refuse at the source vs refuse at the spawn.

1.169.0

Minor Changes

  • 623b2ae: Bound the MCP import surface: the tools/list sweep, per-tool schema bytes, and per-source timeouts (RV1515, the P1 tail).

    An MCP server sits across a trust boundary, and three of its behaviors were unbounded on the host side. mcp() now takes three opt-in bounds: maxTools caps the tools/list sweep itself (checked after each page against the accumulated WIRE tools, pre-filter, so a hostile server cannot stream past it and an allow list cannot admit past it), maxSchemaBytes caps each admitted tool's serialized inputSchema plus outputSchema (the allow/deny filter runs first, so a denied tool's schema bomb costs nothing), and timeouts bounds the latencies: connectMs races the handshake and releases the client (and a stdio child) on expiry with a typed refusal, while listMs and callMs ride the SDK request timeout per page and per call, tightening the SDK's own 60s default; a call timeout surfaces as that tool's error result and never propagates past policy. Every bound refuses typed with the measured value and the declared cap in the message; absent bounds preserve the previous behavior byte for byte.

1.168.0

Minor Changes

  • ebba79a: Pin a profile's toolset with an attestation and refuse drift typed at spawn time (RV1514, the P1 tail).

    Provider-side drift of an imported tool's description or schema re-keys new spawns silently by design, so a poisoned MCP tool description still reached the model, just under a new content key. AgentProfile.toolsetAttestation now pins the hash itself: a spawn whose resolved toolset hashes to anything else refuses with a typed ConfigError before any provider call or budget admission. attestToolset() records the pin from a resolution (the aggregate toolsetHash plus per-tool toolContractHash values, both exported), and the refusal names the drift (changed / missing / unexpected tools with both hashes) when the per-tool hashes are present, or lists the resolved per-tool hashes so a stale pin can be corrected from the refusal itself. The pin binds the spawn's RESOLVED toolset, so a call-level tools override and the opt-in escalate tool drift it deliberately; the attestation shape is validated at createEngine (64 lowercase hex chars, tool names inside the tool-name pattern), and unattested profiles keep today's re-keying behavior byte for byte.

1.167.0

1.166.0

Minor Changes

  • d8262c3: Record the semantic completion lift in the run settle and read it back on the persisted terminal (the persisted-terminal tail of the P1 list).

    The persisted terminal (RV1209) documented its own gap: completion was unrecoverable by construction, because the workflow's semantic claim rides its result value and only the value's DIGEST is journaled. An offline reader, a restarted server, or a second replica saw the transport status and the money but never whether the work was COMPLETE, which is the one field the consumers doctrine (RV1414) says to gate on beside status.

    The settle now records the lift it already computed. The engine lifts the completion envelope once at the settlement chokepoint (RV-207); the same object now rides the journaled run_settle decision value flat beside the output digest (completion, childStatusCounts, degradedReasons, the salvage lists, belowFloorOkChildren, acceptanceChildren), the outputHash precedent: additive, appended only by segments that computed the value, so a pure replay never overwrites the live baseline. lastRunSettle parses the literal back defensively, and persistedTerminalEnvelope passes it through the one producer, so a rebuilt envelope carries the same completion the live consumer saw. A settle written before the lift rode it stays honestly absent under provenance: 'journal' (absence means NOT RECORDED), and the run's own error remains the one deliberately unrecoverable field.

    The six plan gating cassettes whose settles gained the recorded lift are re-recorded, and the frozen-fixture lock is refreshed through its ceremony (hashVersion-bump): the settle VALUE grew richer while the identity profile and every hash rule stay untouched, so replay identity is unchanged and the re-recorded fixtures are the same scenarios with the lift visible in their settle rows.

1.165.0

Minor Changes

  • 6391274: Carry the recorded evidence entries through the agent terminal and pair the claim pool against them (the deferred RV1501 entries plumbing).

    The seventeenth comparison run's decisive finding had one more half. The worker RECORDED the correct reading through record_evidence with the right anchor, its composed output paraphrased the citation away, and the root inverted the reading at synthesis. The claim-consistency pool was OUTPUTS only, so the recorded entry could never pair with the inverted draft, and nothing about the entries survived resume: a replayed child restored neither its evidence verdict nor its recorded content.

    Four halves, one plumbing. The loop collects the CONTENT behind the evidence counter from the same message window and the same result-recorded rule (claim plus file or file:lines citation, bounded: 40 entries, 400 chars per claim), on AgentResult.evidenceEntries whenever at least one entry exists, contract or not. The agent terminal journals both the evidence verdict and the entries (JournalEntry.evidence, JournalEntry.evidenceEntries), additive and policy-only, exactly the artifacts precedent. Replay restores both verbatim, so a resumed orchestrate holds the same settled facts a live run holds. And the claim pool reads a SECOND source per accepted child from the restored entries, one sentence per claim with its citation in the anchor syntax, so a draft contradicting the recorded reading pairs even when the composed output carries no anchor at all; poolChildren counts children, never sources.

    Validated live this cycle without paid API traffic: the judge ruling on these pairs was exercised against a real model through the Codex subscription CLI (an adapter over codex exec, structured output through the prompt tier) and caught the benchmark inversion and a numeric flip while judging a paraphrased agreement clean, three for three, one dispatch each.

1.164.0

Minor Changes

  • 9f2dda9: Seed re-opened budget accounts from the settled journal fold and re-admit reruns of journaled invocations as recovered (RV1505, closing the DEF-7 remainder the eighteenth plan recorded).

    The recovered rerun (the unblock). The reserve recovery rule already said reserves are recovered from the journal and never re-estimated, but the dispatch itself still re-cleared projected admission live: a rerun of a journaled invocation (a dangling dispatch, or a non-replayable terminal retried by resume) was held to spent plus a fresh reserve against the ceiling, and the resume seed already carries the dollars that invocation's prior attempt burned. At an exact-fill ceiling this refused the continuation of the very work the money was spent on, with the ROOT seed alone, before any account seeding: a rerun after an error terminal resumed 'exhausted' with zero provider calls. The ctx.agent dispatch layer now follows the recoverInFlight rule: journaled reruns commit their reserve through admitRecovered, the pre-count feasibility floor gates NEW work only, and the per-turn guard, the pre-dispatch output bound, and the severing signal still bound every dollar a rerun actually spends.

    The per-account seed (the reopened half). With reruns safe, the engine now seeds every re-opened sub-account from the per-account rows of the SAME settled fold the root already seeds from (accountSpendFromJournal, RunBudget seed.accounts), so a resumed segment admits new work and prices its turns against the history a continuous run would have accumulated. Before the seed, sub-account spend was per-process amnesia: a resumed child re-opened at zero and could silently overspend the very allowance its admission verdict recorded. Two deliberate exemptions keep the seed honest: the root row is ignored (the root seeds from the same fold's total, byte for byte as before), and orchestrator-cap accounts re-arm per segment, because the cap is a per-segment coordination bound and the documented resume after a budget-cancelled root exists precisely to continue past a crossed cap under the root ceiling. A malformed seeded row (non-finite or negative) refuses loud at construction, naming the account, exactly the root seed's poisoned-journal rule.

1.163.0

Minor Changes

  • e8d9ada: Report the import bundle's reference closure, serve verify-only journal reads, and close the documentation gaps the benchmark named (RV1511, RV1512, RV1513). The sixth and final PR of the eighteenth plan.

    The import closure report (RV1511). The intake validated shapes, namespaces, and the runId, but nothing held the ENTRIES' own references against the blobs the bundle carries: a torn bundle imported whole and the missing transcript surfaced only when something later read it. importRun now returns { unresolvedRefs }, every transcript, checkpoint, artifact, and workflow-source ref the entries (and meta) name that no bundle blob resolves; the default stays permissive (retention and checkpoint pruning legitimately drop blobs their entries still name) and the report makes the gap visible, while requireClosure: true refuses typed BEFORE any write. A duplicate blob ref refuses always: last-write-wins over transcript bytes is a torn or edited bundle, never a valid export.

    The verify-only load (RV1512). The A1 salvage model repairs a torn trailing line ON LOAD, which is right for an owner about to append and wrong for an auditor: a verification read that rewrites the artifact it verifies destroys the evidence of the tear. JsonlFileStore({ repairOnLoad: false }) serves the salvageable records without touching the file, and rulvar runs audit --no-load-repair opens the default store that way (contradicting --repair is refused typed).

    The documentation debts (RV1513). The README package count now matches its own table (seventeen names, the unscoped pointer included); @rulvar/executor ships a README and LICENSE like every sibling; the package reference names the eval framework's real dependencies; and the isolated-executor guide gains "What the ledger is NOT", the explicit denial list (not an outbox, not authorization, not exactly-once, not always on) for exactly the facts the seventeenth comparison run's dossier inverted while citing the sources that state them.

1.162.0

Minor Changes

  • 2031e82: Reject invisible format characters in dossier text and split the retry namespaces on the result surface (RV1509, RV1510). The fifth PR of the eighteenth plan.

    The format-character lint (RV1509). The seventeenth comparison run's answer carried five U+200B characters immediately before hidden-file citations, and every configured check passed: the citation pattern's boundary class simply excluded the invisible byte from the match, so the extracted citations were clean while the LITERAL text was not byte-identical to any repository path. formatCharacterValidator rejects the whole Unicode format category (Cf) with each distinct character's codepoint, first index, occurrence count, and a visible-context excerpt, so the repair turn can find the exact bytes; allow admits named characters for content that legitimately needs them (bidi marks in RTL prose), each entry itself required to be a single Cf character.

    The retry namespaces (RV1510). The same benchmark exported one conflated "retries" number, and 17 pre-wire quota denials read as 17 provider retries. The agent result (and agent:end) now carries quotaDenials beside transportRetries: pre-wire limiter denials split by dimension (requests versus tokens, classified by the limiter's own reason vocabulary) with the recovered-episode count. A denial never reached the provider and never billed; provider retry attempts stay in transportRetries, and the journaled providerCalls records keep the wire cardinality the invoice sums. Live telemetry only, the transportRetries rule exactly: never journaled, absent on a replayed result, absent means "zero or unknown".

1.161.0

Minor Changes

  • d4547b7: Refuse unpriced, malformed, and stale-priced dispatches before the wire under the opt-in strict pricing gate (RV1508). The fourth PR of the eighteenth plan.

    Dollars come from the price table, and a model absent from it debits NOTHING, so every USD ceiling silently fails to bound it; the docs called that hole honest, and the seventeenth comparison benchmark asked for a mode that closes it. RunOptions.strictPricing arms the gate: every paid dispatch must resolve a well-formed price row for its serving model BEFORE the wire call, at the same dispatch chokepoint the exposure admission holds, or the dispatch refuses with a typed ConfigError naming the model and the defect (no row, a non-finite or negative rate, a malformed long-context tier). maxRatesAgeDays additionally demands a fresh ratesVerifiedAt on the row, binding only when declared; allowUnpriced lists the exact model refs the host KNOWS are free, the one explicit exception. Each model vets once per run, since the price table is fixed for the run's life.

    The posture follows the exposure cap's durability rule (RV1504): canonicalized and recorded in RunMeta at genesis, restored by every resume with no ResumeOptions override, absence stays absent, and the store conformance kit holds stores to the round-trip, because a FinOps gate a resumed segment silently drops is not a gate.

1.160.0

Minor Changes

  • 1c6f0d0: Require an explicit flavor B default decision and add the monotonic approval composition (RV1506, RV1507). The third PR of the eighteenth plan. BREAKING for flavor B configurations that omitted defaultDecision.

    The explicit timeout meaning (RV1506). Flavor B escalation suspends the worker under a journaled deadline, and the deadline's expiry APPLIES the defaultDecision; when none was declared the engine invented accept, so an unattended scope escalation resolved fail open, the seventeenth comparison benchmark's top authority hardening ask. Enabling flavor B now requires an explicit defaultDecision beside the already-required deadlineMs, a ConfigError before any LLM call; there is no engine default. The tool-approval channel already holds the opposite posture (an unattended approval DENIES at its approvalDeadlineMs), so { kind: 'cancel' } is the declaration that makes both timeouts close the same way. Migration is one line on each flavor B config; the runtime semantics of a declared decision are unchanged, and a racing live decision still wins first-closed.

    The monotonic approval composition (RV1507). The permission chain's documented order lets a generic ALLOW (a hook or canUseTool) clear a needsApproval: true tool, which is deliberate for tests and trusted hosts and a fail-open hazard for a platform profile. permissions.strictApprovals: true makes such an allow fall through instead of deciding, so the terminal default still asks for exactly the tools that declared the need; deny and ask keep their power, { modifiedInput } still applies, tools without the declaration keep the historical composition byte for byte, and the flag merges as OR across the engine and profile layers, so a profile cannot loosen an engine-armed mode. A non-boolean value refuses at compile.

1.159.0

Minor Changes

  • e881c8b: Record the in-flight exposure cap in RunMeta and restore it on every resume, and fold each budget account's settled spend for audits (RV1504, RV1505 first half). The second PR of the eighteenth plan.

    The durable exposure cap (RV1504). RunOptions.maxInFlightExposureUsd was operational and per-invocation, so a resumed segment silently ran WITHOUT the exposure bound the original invocation declared, the seventeenth comparison benchmark's top FinOps gap. The cap now follows the ceiling's exact rule: recorded in RunMeta at genesis, restored by every resume, no ResumeOptions field to override it, absence stays absent (a run started uncapped stays uncapped, a pre-field journal resumes exactly as before), and the store conformance kit holds stores to the round-trip. One honest asymmetry is documented rather than papered over: limits stay per-invocation, so a resumed segment that does not re-supply them prices turn estimates from the model's full output allowance, and a tight restored cap then refuses dispatches the original clamped estimates admitted; that direction is fail closed, never silent uncapping.

    The per-account audit fold (RV1505, the audit half). accountSpendFromJournal, exported from @rulvar/core, folds the same settled entries the cost report folds into each budget account's INCLUSIVE spend, with the account tree read from the journaled spawn-admission decisions, so a host can hold any orchestrator cap or child allowance against what its subtree actually spent on a plain stored journal. Abandoned subtrees and unpriced slices contribute zero, exactly like the net total. Seeding the fold into re-opened accounts on resume is deliberately NOT wired yet: a rerun of a journaled invocation re-admits with exact-fill arithmetic today, so spend-at-reopen would refuse the continuation of the very work the money was spent on; the reopen seeding lands together with a seed-aware rerun re-admission, and the docs name the remaining amnesia instead of hiding it.

1.158.0

Minor Changes

  • a266bc7: Hold the composed draft to the pool it composed from, with a bounded model judge over anchor-paired claims, and show the run its own execution facts (RV1501, RV1502, RV1503). The first PR of the eighteenth plan.

    The claim pairing fold (RV1501). The seventeenth comparison run's security child read packages/executor/src/subprocess.ts:256-296 correctly (a failed audit write does not mask success), and the ROOT inverted the claim in the final draft while citing the very same span; every configured check passed because each judged the draft alone, never against the pool that contradicted it. pairDraftClaims, exported from @rulvar/core, is the pure half that closes the gap: every draft sentence citing an anchor (path:line or path:start-end, the citation pattern extended with a range suffix) is paired with the accepted pool sentences citing an intersecting span of the same file, verbatim agreement dropped, everything bounded (pair cap, per-pair pool cap, excerpt cap) and fail closed at intake, deterministic and journal-free like findContradictions.

    The claim-consistency judge (RV1502). orchestrate({ claimConsistency }) wires the fold to the post-fan-in chokepoint, strictly after the contradiction pass and before any synthesis dispatch, and rules on the pairs with ONE bounded structured-output invocation under role 'synthesize' (judge.model/judge.effort/judge.limits/judge.estCost override the routing chain). No pairs means no judge dispatch. The verdict is an ordinary journaled agent entry, so a resume replays it with zero paid calls. onFound speaks the contradiction pass's vocabulary: 'report' puts claimContradictions and claimConsistencyMeta on the acceptance envelope, 'carry' rides a CLAIM CONTRADICTIONS: line in the single-mode synthesis prompt and blocks the valid-draft skip while findings stand, and 'fail' fails the run typed with data.source 'orchestrator_claim_consistency' before anything pays to compose the inversion away. A dead judge is a named fact (judgeFailed on the meta, findings absent, never an empty list that would claim agreement) and fails the run only under 'fail'.

    The execution self-facts (RV1503). The same run graded its whole dossier live-observed: no while the harness had just watched 118 wire requests settle, because no surface ever showed the composing model what its run executed. executionFacts: true puts a replay-stable facts block (wire requests, missing response ids, journaled token totals; dollars deliberately absent because replay re-prices) on every await TaskDigest and every get_child_result page, and synthesis.runFacts: true folds the aggregate RUN FACTS: line into the synthesis prompt, naming its own boundary: live-observed by this run's own harness, production evidence it is not. Both off by default, byte-identical surfaces without them.

1.157.0

Minor Changes

  • 1883421: Hold ok children to their declared evidence floor, declare the cost basis on every money surface, and document the terminal contract for consumers (RV1412, RV1413, RV1414). The sixth and final PR of the seventeenth plan.

    The ok-child evidence floor (RV1412). RV1207 made a declared evidence contract binding for the salvage arms, but a child that settled 'ok' below its declared floor sailed through acceptance behind a clean headline: its roster row said met: false while completion said 'complete' and degradedReasons stayed empty. The shortfall is now a degradation note by default, so the completion claim stays honest ('partial', never 'complete' over an unmet declared contract) while the verdict and the status counts stay exactly what they were, and the envelope, the run:end lift, and the RunOutcome mirror carry belowFloorOkChildren naming such children machine-readably. Under the existing acceptance.requireEvidenceFloor flag the floor binds for ok children exactly as it does for the salvage arms: the child counts against the policy ('all-ok' rejects, { minSuccessful: N } does not count it), its roster row is marked floorRequired: true, and in an accepted run it stays out of the contradiction pool and the synthesis evidence index, read from the decision's own roster rows so live and resume derive the same pool. What neither mode changes: childStatusCounts stays factual and the child's output stays visible through the digest and get_child_result. Deliberately out of scope: the pre-acceptance finish validators keep reading ok children's citations as evidence, because validation runs before the verdict and paid journaled text is real either way.

    The cost provenance marker (RV1413). Every dollar the engine reports is journaled usage priced at the CALLER'S pricing table, never a provider statement, and the seventeenth comparison run's "$4.79" read as an invoice figure precisely because nothing said otherwise. CostReport.basis and TerminalEnvelope.costBasis now declare 'locally-estimated' as a literal, stamped by both report builders and at the envelope's one producer (journal rebuilds included), mirroring InvoiceExport.pricingBasis. No field is renamed; reconcile real bills through the invoice export and reconcileStatement, which carry their own provenance.

    The terminal contract for consumers (RV1414). A new documentation section pins the doctrine the vocabulary was built for: status is transport, completion is the work's own claim, the acceptance verdict is a policy over statuses, and none of them, alone or together, authorizes a side effect. Effects during the run belong to tools behind the permission chain and approvals; effects after the run belong to the consumer's own policy over the terminal facts, read from the settled authority (the persisted envelope or its typed refusal), with the money read as what costBasis declares and absence read by each field's absence doctrine.

1.156.0

Minor Changes

  • 537144e: Validate every restored counter at the checkpoint decode boundary, count single-wire rows in the invoice join-coverage aggregate, and resolve run profiles by own property (RV1409, RV1410, RV1411).

    decodeCheckpoint now refuses a blob whose required counters are not non-negative finite numbers: turns, toolCallsUsed, schemaAttempts, every usage field (the optional ones when present), and the compaction points (RV1409). Those counters seed the loop's limit arithmetic and are reported to the budget as paid spend, and none of the refused shapes was ever produced by a boundary write (JSON delivers the NaN corruption as null and 1e999 as Infinity), so the blob as a whole is untrustworthy and the dangling dispatch reruns from the top, exactly like a blob that does not parse. Before this shipped, a store-side corruption or a hostile writer could restore turns: -2 and credit the maxTurns ceiling with turns nobody paid. Deliberately not judged at decode: the Usage invariant, integer rules, and TTL splits. Checkpoints written before those invariants shipped are honest evidence of paid work and still decode; the restore path sanitizes them exactly as it always has.

    InvoiceCardinality.wireIdsMissing now counts the requests across EVERY dispatch row that carry no join key (RV1410). A single-wire row is its one request, joined by the row's own responseId, so an id-less single-wire row contributes one missing key; failed requests count like any other, because the provider may have billed them and a statement line cannot be joined to a row with no id either way. Before this shipped the counter looked only inside multi-wire rows, so a fleet of single-wire dispatches whose adapter surfaced no response ids read as fully joined (wireIdsMissing: 0) while every row-level verdict said missing-provider-id: the aggregate contradicted its own rows.

    runProfile() resolves the shipped preset roster by own property (RV1411, the last prototype-sensitive surface of the RV1205 class): an inherited object name (toString, constructor, __proto__) is not a profile and now returns undefined, the value hosts key their unknown-name refusal on. The CLI's --profile toString becomes the typed unknown-profile ConfigError naming the shipped roster instead of a silently accepted empty profile.

1.155.0

Minor Changes

  • 49b08a7: Make the persisted terminal tail-aware and give offline authorities the engine's own resolution validator (RV1407, RV1408). The persisted terminal (RV1209) served the journaled settle even when the journal had CONTINUED past it, so a restarted reader could hold yesterday's envelope over a run that a detached resolution had already destined to resume, or that a successor segment was actively working, while auditRun derived a non-terminal status from exactly that evidence. persistedTerminalEnvelope now refuses not-terminal whenever entries follow the last settle, with a message naming the continuation (count and settle seq), so the persisted surface and the audit read one journal one way; the conformance table pins the new refusal (settled-then-continued) beside the five terminal paths. And the CLI server's offline resolution used a lookalike validator that demanded the plain { decision } from EVERY kind-'approval' suspension: a legitimate EscalationDecision for a flavor B escalation was refused, and a wrong-shaped plain approval payload was waved into the journal. The new export validateDetachedResolution is the engine's own detached validation (the RV1203 flavor classifier, both payload arms, the pinned schema) as one function; the engine's detached path and the CLI offline path now call the same bytes, so an escalation resolves offline with its OWN payload exactly as detached-live, and an invalid one is refused typed before anything is journaled.

1.154.0

Minor Changes

  • 9259f24: Reserve the tail of the turns axis and project it in preflight (RV1405, RV1406). The seventeenth comparison experiment's worker burned maxTurns 28 at 66 of 96 executed tool calls and settled limit with no finalize phase, because the finalization reserve fires on tool-budget limiters and the finalization window watches tool-budget counts, and nothing watched the turns. The new opt-in limits.finalizationTurns: { reserveTurns, allow? } extends the SAME window regime to the turns dimension: once the remaining turns against maxTurns drop to reserveTurns, non-allowlisted calls receive the typed window refusal, the one-time notice names the turns arithmetic, and the terminal tool stays admitted. The regime keeps one allowlist (finalizationWindow.allow, else finalizationTurns.allow, else the zero-cost tools); with both dimensions inside their reserves the smaller remaining binds, and the notice, every refusal, and the RV509 decision entry (budget: 'turns') all name the binding dimension's own reserve. The tail lives INSIDE maxTurns (the ceiling stays a ceiling), the RV1208 deficit widening stays calls-only, repair-turn grants are deliberately not counted, resume re-arms identically, and configuring the reserve alone makes the toolBudget snapshot (and the policy-facts window line) present so a turns-only run has a home for finalizationWindowEntered. Preflight gains the turns-axis projection turns-bind-before-tool-budget (RV1406): when maxTurns fits fewer serial executed calls (one per turn plus the final answer turn) than the effective executed-call ceiling, extension grants included, the finding says the turns axis binds first, as a warning without the reserve and an info with it, never a stop; and finalization-turns-covers-max-turns (warning) when reserveTurns is not below maxTurns.

1.153.0

Minor Changes

  • d8bebcb: The contradiction pass and every evidence pool judge the ACCEPTED roster, and the carry posture becomes an invariant (RV1403, RV1404).

    The seventeenth comparison run exposed both halves. Its pass judged five of six accepted children, because a limit child accepted as a structured partial carried no terminal output and the pool's eligibility only knew the output arm; and its onFound: 'carry' configuration would have silently carried nothing had the pool disputed itself, because a valid draft skipped the synthesis the carry line was supposed to ride.

    RV1403 makes the roster the acceptance decision counted the one pool every downstream surface reads. The contradiction pass and the synthesis evidenceIndex judge the ok children plus both salvage arms, taken from the decision itself (fresh or rolled forward from the journal, so live and resume derive the same set): an accepted structured partial's rival reading can now dispute the pool and its citations index, while a child blocked by the binding evidence floor (RV1207) stays out even when it carries a validated terminal output, because a reading the policy refused to count must not steer what composes the result. The finish validation snapshot predicts the same arms: a partial-accepted child is marked with the new FinishValidationChild.salvageablePartial (so evidencePreservedValidator counts the accepted partial's citations and requireKnown no longer flags an honest quote of it as fabricated), and a below-floor child is no longer marked salvageableOutput, mirroring exactly what acceptance will do.

    RV1404 adds two honesty guarantees. Non-empty findings under 'carry' disable the skipWhenDraftValid gate for that draft, announced in an info log (orchestrator synthesis skip blocked by contradictions); a clean pool keeps the skip byte for byte, and a skip already journaled stays the authority on resume. And the envelope gains contradictionsMeta beside contradictions, present exactly when the pass is configured: poolChildren says how many accepted children were judged, and truncated says whether more contradictions existed than max allowed to report, so a capped findings list can never read as a complete one. The pass log event carries the same flag, and the 'fail' posture's typed error data carries the meta beside the findings.

1.152.0

Minor Changes

  • dd6a616: Rebuild the repository-aware citation surface from scratch (RV1401, RV1402). v1.151.0 first shipped this surface; this release replaces that implementation with a fresh cut of the same declared contract, and the public signature is unchanged.

    citationTargetsValidator (RV1401) resolves EVERY citation of the result text against the host's frozen source snapshot, inline code and plain prose alike, with no sentence-level precondition. The seventeenth comparison run's answer carried ghost.ts:0, a location no checkout ever held, and the whole configured chain passed it: the citation pattern accepts any digits (a line of 0 included), evidencePreservedValidator's requireKnown proves only that some child SAID the string, and citedValueValidator resolves a citation only when its sentence asserts an inline value beside it, so a fabricated location nobody asserted anything about counted as provenance and licensed the valid-draft skip. Three refusals, each fail closed: a match of the citation pattern that does not parse as path:line with a safe integer line is refused rather than skipped, because the host's own pattern claims it IS a citation; a line below 1 is refused BEFORE the resolver runs, because source lines are 1-based and a sloppy resolver might well answer line 0; and a location the resolver does not know is refused, because a citation nothing resolves is not provenance. Repeated occurrences are judged once, refusal reasons cap at 20 listed offenders, fencedCode: 'excluded' strips fenced code before scanning (default 'counted'), a text carrying no citation at all passes (demanding citations exist is minMatchesValidator's job), and intake is fail closed in the RV610 posture: a pattern that does not compile or that can match the empty string is refused typed. Wired into finishValidation, the refusal reaches the skipWhenDraftValid gate like every other validator verdict, so a draft carrying a fabricated citation can no longer skip the synthesis it was supposed to earn.

    citedValueValidator (RV1402) now matches asserted values as WHOLE tokens instead of substrings. The boundary class is word characters plus the dot: an asserted 3 no longer counts as carried by a line saying 30 or 3.5 (the seventeenth comparison judge's repro), retry.ts no longer matches inside myretry.ts, and the value itself is matched literally with regex metacharacters escaped.

1.151.0

Minor Changes

  • 1de0610: Every citation in a finish result can now be resolved against the host's own source snapshot, and cited values match as whole tokens (RV1401, RV1402).

    The seventeenth comparison run shipped an answer carrying ghost.ts:0, a location no checkout ever held, and every configured check passed: the citation pattern accepts any digits (a line of 0 included), evidencePreservedValidator's requireKnown proves only that a child SAID the string, and citedValueValidator resolves a citation only when its sentence asserts an inline value beside it. A fabricated location that no sentence asserted anything about therefore counted as provenance and licensed the valid-draft skip.

    citationTargetsValidator closes the hole at the root. Every match of the citation pattern in the result text, inline code and plain prose alike, is parsed as path:line and resolved through the same pure resolve(target) snapshot contract citedValueValidator takes, with no sentence-level precondition. Three refusals, each fail closed: a match that does not parse as path:line is refused rather than skipped, a line below 1 is refused BEFORE the resolver runs (source lines are 1-based, and a sloppy host resolver might well answer line 0), and a citation the resolver does not know is refused, because a citation nothing resolves is not provenance. Repeated occurrences are judged once, fencedCode: 'excluded' strips fenced code first for hosts whose contracts already exclude it, and intake is fail closed in the RV610 posture: a pattern that cannot compile or can match the empty string is refused typed. Wired into finishValidation, the refusal also reaches the skipWhenDraftValid gate, so a draft carrying an unresolvable citation can no longer skip the synthesis it was supposed to earn.

    citedValueValidator now requires an asserted value to appear in the cited line as a WHOLE token instead of a substring: judged by includes, a claim of 3 was satisfied by a line saying 30, which is the seventeenth judge's repro. The boundary class is word characters plus the dot, so 3 matches neither inside 30 nor inside 3.5, and retry.ts no longer matches inside myretry.ts; spaces, punctuation, operators, and the line edges still bound a token.

1.150.0

Minor Changes

  • a331211: The settled child pool is checked against itself before anything composes it (RV1301, RV1302, RV1303).

    A fan-out produces N independent children, and nothing in the pipeline compared their claims against EACH OTHER. Acceptance judges each child alone, the finish validators judge the final text mechanically, citedValueValidator judges a claim against the SOURCE rather than against another child, and dedupeClaims matches on agreement, so it is blind to disagreement by construction. A run where one child read attempts: 3 at src/retry.ts:33 and another read attempts: 5 at the same line put both into the synthesis prompt, the composing model picked one, and the run settled confident with no surface recording that its own evidence had disputed itself. This is the sixteenth comparison judge's P2-1 remainder, deferred at the time as a phase that deserved its own release.

    orchestrate({ contradictions }) folds the settled evidence pool at the post-fan-in chokepoint: after the accepted acceptance verdict, before any synthesis dispatch. It is bounded in the strongest sense available, a pure fold with no model call, no clock, no host code, and no journal entry of its own, so it costs nothing in the post-fan-in window reduceCriticalPath measures and a resume re-derives the identical finding for free. The rule is deliberately narrow, so a finding is always explainable in one sentence: two DIFFERENT children credit the same cited location with different values for the same key. It reads the same span vocabulary the RV1212 validators read (inline-code spans that parse as path:line are the anchors, the rest are the values asserted about them) and splits each value at its first : or = into a key and a reading.

    Three non-findings are as deliberate as the finding. Two keys on one line (attempts: 3 beside backoffMs: 100) are aspects of that line, not a dispute, so the key must match. A span with no separator names something without asserting anything about it, and two such spans can never conflict. And one child holding both readings is narrative inside a single document, not a pool contradiction, while two independent children disagreeing is exactly the signal the pool cannot resolve by itself. The pool judged is the evidence pool evidenceIndex indexes, ok children plus salvage-accepted ones, so a dead child's error text can never dispute a real finding.

    onFound picks the posture. 'report' (the default) puts the findings on the acceptance envelope and in an info log event and changes nothing else. 'carry' additionally rides a CHILD CONTRADICTIONS: line in the 'single' synthesis prompt demanding each disagreement be resolved explicitly instead of silently picked, and requires that synthesis (a ConfigError at intake otherwise, and the deterministic 'incremental' reconciliation has no prompt at all). 'fail' fails the run typed with data.source 'orchestrator_contradictions', the findings, and the acceptance snapshot the run already earned, BEFORE any synthesis dispatch, so a self-contradicting pool never pays for the invocation that would compose the disagreement away.

    The envelope field distinguishes two facts that look alike: contradictions is present whenever the pass was configured and EMPTY when it ran and the pool agreed, while its absence means nothing looked. That is the RV1209 absence doctrine applied to a second surface. max bounds the findings (default 20) and pattern overrides the anchor shape, refused fail closed at intake on a pattern that can match the empty string. Everything stays byte identical without the option, and a 'carry' run whose pool agrees emits the identical synthesis prompt bytes as a run without the pass.

    One honest bound: this is the mechanical half. Two children disagreeing in prose, with no shared citation and no shared key, are invisible to it, and closing that needs a bounded model pass with its own budget, journal, and resume semantics, which will consume this same Contradiction shape. The pure fold ships first because it is free, deterministic, and reproduces on replay. findContradictions is exported from @rulvar/core so a host can run the same rule over any pool it holds.

1.149.0

Minor Changes

  • 08b4537: The post-fan-in model bucket is profiled, the final answer gets two evidence validators, and the terminal envelope's typed error is detached (RV1211, RV1212, RV1213).

    PostFanInBreakdown splits the coordination model bucket three ways. coordinationModelMsByPhase keys the activation wall by the activation's OWN invocation role, so a tail spent compacting is distinguishable from a tail spent drafting. coordinationModelOnlyMs is that wall with the tool executions NESTED inside it removed, the exact set difference of the two clipped unions rather than a subtraction of sums, because a tool an activation called runs inside the activation's wall and reading the wall as thinking time overstates it by exactly the tool share. coordinationToolCallsByName counts the executions beside their milliseconds, so one slow pagination and twenty fast ones stop reading as the same tail. The sixteenth comparison experiment put 222.6 seconds (50.9% of wall) in this bucket with a zero synthesis share, and one number for it could not say what the coordinator was doing.

    Two new finish validators judge the answer's evidence rather than its shape. evidenceGradeValidator requires every sentence claiming something is live-observed, came from the provider bill, or is production-proven to name a run id or a file:line citation in THAT sentence; the phrase list and the artifact pattern are configurable, and a pattern that can match the empty string is refused typed because it would satisfy every graded claim silently. citedValueValidator checks that a cited location actually carries the value its sentence asserts, against a source snapshot the host resolves: within one sentence the inline-code spans that are not citations are the asserted values, each must appear in the cited line (or within window lines after it), a location the resolver does not know is a failure rather than a pass, and a sentence that cites without asserting an inline value passes untouched. resolve must be pure over a snapshot frozen before the run, like every finish validator.

    TerminalEnvelope.error is now a detached copy, its data nesting included, exactly like costByModel: a consumer that annotates the error it holds can no longer reach back into the outcome the engine still owns.

1.148.0

Minor Changes

  • c85dac9: The terminal envelope survives the process that produced it, and the invoice states how many provider requests its rows represent (RV1209, RV1210).

    A run this server never held used to answer GET /runs/:id with a bare status projection while a live consumer read the whole TerminalEnvelope, so the durability story stopped one surface short of the one a host reads after a restart. The non-live response now carries envelope too, rebuilt from the journal through the same producer and marked provenance: 'journal': the verdict comes from the journaled run settle (the authority, not the meta projection), the money from the same composed settle-pin fold GET /runs/:id/cost runs, and the usage and agentsSpawned from the same ledger fold the resume budget seed uses. Two fields are deliberately absent on a rebuilt envelope and the marker is what makes their absence honest: completion (the workflow's semantic claim rides its result value, and only that value's digest is journaled) and error (the run's terminal wire error is never journaled as the run's own), so absence there means NOT RECORDED, never "the workflow claimed nothing" or "the run did not fail". A live envelope carries no provenance at all and keeps its original byte contract. Where nothing durable records a terminal, the body carries a typed terminalUnavailable: { reason, message } (unsettled, not-terminal, or unknown-workflow) instead of an envelope; it is its own field, never error, because error on that body means the run failed. persistedTerminalEnvelope is exported, and the terminal-envelope conformance table now drives every row through a restarted server as its final surface.

    The invoice declares the dispatch-versus-wire cardinality (cardinality: { dispatchRows, wireRequests, multiWireRows, wireIdsMissing }). One row is one logical dispatch, and a dispatch that absorbed provider-side continuations is billed as several HTTP requests, so a per-request statement has more lines than the export has rows by construction: reconcile a statement line count against wireRequests, never rows.length. The per-row wireRequests behind it comes from the count the adapter reported rather than the length of wireResponseIds, because a provider that leaves an absorbed segment unnamed still billed it, and counting ids alone made the invoice contradict the quota window that settles on the same count. Single-wire dispatches carry neither field and stay byte-identical.

    Two limiter fixes ride with it. An abort landing inside an awaited quota reservation now stops the wire: a limiter that queues can hold reserve past the dispatch's own abort check, and the engine rechecks the host and budget signals when the reservation resolves, releasing the granted admission rather than reconciling it, because a settlement only ever adds while that call provably never happened. And the unused-continuation release is fail closed on the wire count: only a finish that names its wire set proves which pre-wire grants went unused, so a finish carrying no count releases nothing, instead of reading the absence as one flown wire and handing a hook-granting adapter back exactly the capacity it had consumed.

1.147.0

Minor Changes

  • 6367231: A declared evidence floor can be made binding, and the finalization window can reserve the calls that close it (RV1207, RV1208). Two opt-ins answer the sixteenth comparison run, where a worker spent 108 tool calls, settled limit with 10 of its 14 declared evidence entries, and was promoted through terminal-output salvage with the floor waived, so the run reported status: 'ok' with completion: 'partial' over an unmet contract.

    acceptance.requireEvidenceFloor: true makes the declared floor binding: a child that declared an evidence contract it did not meet is never promoted by a salvage arm, so it counts against the policy exactly like an unsalvageable limit child ('all-ok' rejects; { minSuccessful: N } does not count it toward N). Salvage stays diagnostic: the acceptance roster still records the arm that would have applied and the evidence verdict, marked floorRequired: true instead of waivedBySalvage: true, the degradedReasons name the shortfall with its counts, and the child's output stays visible through the digest and get_child_result. A child with no declared contract, or one that met its floor, is untouched.

    limits.finalizationWindow.reserveForEvidenceDeficit: true makes the reserved tail evidence-aware: with an evidence contract declared, the effective reserve is the larger of reserveCalls and the outstanding deficit plus one summary call, recomputed at every boundary from the same successful-record_evidence window the floor refusal and the RV809 deficit trigger read. A fixed reserve can be outgrown by the deficit it was meant to cover; this one cannot, so searching stops while the floor is still closable. The reserve collapses back to reserveCalls as entries land and never narrows below it, and the one-time window notice names the live deficit. Both options are off by default and the surrounding behavior is byte-identical without them.

1.146.0

Minor Changes

  • 5d9bbc8: Profile vocabularies are what the host registered, nothing inherited, and importRun applies the one safe runId guard (RV1205, RV1206). Every profile map read went through a bare index, which resolves the JavaScript prototype chain: an agentType naming toString, constructor, or hasOwnProperty resolved a function as its "profile", passed the profiles allowlist, recorded a spawn:admitted decision, and burned the slot before dying downstream on the inherited value (the sixteenth experiment's judge reproduced it as R3). All four surfaces now read own properties: the orchestrate advertisement filter (which additionally builds a null-prototype advertised map), the allowlist enforcement and profile resolution at spawn, ctx.agent's agentType registration check, and the preflight spawn-spec resolution. A prototype name is now exactly as unknown as any unregistered name: it refuses typed before admission and consumes nothing. Separately, engine.importRun now applies assertSafeRunId at its intake, the same guard engine.run and engine.resume use: an import previously validated only "non-empty string", so a bundle claiming .., a slashed path, or an over-length id reached the stores raw.

1.145.0

1.144.0

Minor Changes

  • c11bcd6: Detached resolution picks its validator by the suspension's journaled flavor, and journaled deadlines are range-checked and corruption-checked (RV1203, RV1204). The v1.143.0 opt-in approval deadline made the detached resolver's deadline-presence heuristic wrong: a settled run's TIMED tool approval rejected the plain { decision: 'allow' } as a malformed escalation decision, so nobody could resolve it detached and the parked approval always died at its deny-by-timeout (the sixteenth experiment's judge reproduced it as R2). The detached path now classifies by the suspension's own shape: an escalation is recognized by its structural invariant (a required deadline plus the hardcoded toolName escalate, true by construction since flavor B shipped), every approval suspension written since v1.144.0 journals an explicit flavor: 'approval' in its payload to pin the one ambiguous name (an ordinary tool literally called escalate that opted into the deadline), and the validator follows that flavor, deadline or not. Both deadline knobs (permissions.approvalDeadlineMs, the escalation deadlineMs) now share a compile-time deadline ceiling of one hundred years in milliseconds, so now + interval always journals as a valid absolute date instead of passing the positive-integer check and dying generic with Invalid time value at the Date conversion (judge repro R4). A journaled deadlineAt that does not parse as a date refuses typed as journal corruption, at importRun intake (the journal shape gate) and again before any timer arms; the old Date.parse(...) || now fallback silently resolved such an entry immediately, an instant deny for an approval and an instant default decision for an escalation.

1.143.0

Minor Changes

  • f412169: The opt-in approval deadline (RV1107): permissions.approvalDeadlineMs (engine-wide or per profile, most specific wins) journals an absolute deadline on the ask suspension entry, and an approval nobody resolves by then is DENIED by a resolution by: 'timeout' through the same first-closing-wins arbiter every live decision uses. The machinery is the flavor B escalation deadline's, one suspension kind over: the timer arms FROM THE ENTRY (so the deadline survives resume and a config change never moves an already-journaled one), a live decision cancels it, the deny fails closed with a typed reason the model sees as the denied tool result, and a run parked 'suspended' in a live process still denies at its deadline, the resolution appending durably for the next resume to fold. Absent config keeps the documented indefinite wait. The docs gain the deployment boundary section (RV1108): what the engine enforces versus advises, and the IAM, KMS, DLP, case-store, and PII-canary posture that deliberately lives outside the library.

1.142.0

1.141.0

Minor Changes

  • 4f12a62: The unified terminal envelope (RV1105, the P1-5 arc): every terminal fact of a run travels in ONE exported shape, TerminalEnvelope (run identity, status, the typed error, the completion claim, settled + settledReason, totalUsd/grossUsd with the detached per-model split, the usage aggregate, usageApprox normalized to a boolean, and agentsSpawned), assembled once at the settlement chokepoint by the exported terminalEnvelopeOf after the settlement verdict is known. Every surface carries that object: the resolved outcome (outcome.envelope, always settled: true, because an unsettled terminal rejects typed instead of resolving), the run:end event (event.envelope, where the settled: false envelopes live with the superseded reason inside), the server's GET /runs/:id response, and the OTel exporter (rulvar.run.total_usd, rulvar.run.agents_spawned beside the existing settled attributes; a persisted stream from an older engine still closes its span). Nothing pre-existing was renamed or removed: the envelope is an assembly over fields that all remain.

1.140.0

1.139.0

Minor Changes

  • 03a2141: The live budget debits each provider call marginally against the call's own accumulated price (RV1101): a long-context tier crossed by the call's sum that no single mid-stream slice reached now re-prices the whole call live at the crossing slice, exactly the dollars the settled fold records, and a ceiling between the per-slice and tiered readings severs the run instead of settling ok over its own hard cap. RunBudget.openCallMeter and the optional BudgetHooks.openCallMeter carry the seam (one meter per provider call, the settled fold's billing basis; the mid-stream deltas and the settle remainder of one call share one accumulation; a marginal debit never credits; the tier still never fires on a run aggregate no single call crossed). The fault kit gains the tier-crossing-live-parity scenario (RV1102), pinning both money paths and the marginal live ladder on the real engine.

1.138.0

Minor Changes

  • ed0c4fb: Pre-wire continuation reservation, the self-describing fault kit, and the run-id surface (RV1013 + RV1014, PR VII closing the fourteenth plan)

    • Pre-wire continuation admission (RV1013, opt-in). Post-hoc settlement is accounting, not admission: a hard provider RPM cap needs each pause_turn continuation reserved BEFORE its egress. With quota: { reserveContinuations: true } the engine admits every provider-side continuation through the new adapter-side StreamHooks seam (ProviderAdapter.stream gains an optional third parameter; the Anthropic adapter honors it): under a 2-request window the third wire of one absorbed dispatch never leaves and the denial rides the provider-429 machinery verbatim, the main settlement stops re-adding individually admitted segments (the window is never double-counted), and a granted admission whose wire never left is RELEASED back to the window through the new optional QuotaLimiter.release(reservationId) (implemented by memoryQuotaLimiter; a release returns exactly what admission consumed, and unknown or expired ids are no-ops). Adapters unaware of the hook keep the documented post-hoc semantics byte for byte, and the default stays post-hoc. The midstream-versus-finish usage confirmation now fires only when a finish CLAIM exists: an error-terminal absorption (a segment denial, a transport cut) no longer manufactures an invariant violation that shadows the real wire error.
    • The self-describing kit (RV1014). runFaultInjection refuses an empty only selection typed (a gate that runs zero scenarios used to report allMatched: true), and the report carries requested and selected counts so the gate can never quietly shrink. The audit scenario grows the RV1007 arcs (a page-only long-context tier and a NaN scalar are findings, never silent passes), completing kit coverage of every real defect of the fourteenth plan on its real path.
    • The run-id boundary surface (assertSafeRunId, MAX_RUN_ID_LENGTH) is now exported from @rulvar/core, so hosts can pre-validate ids before engine.run.

1.137.0

Minor Changes

  • 96f6788: Integrity and boundaries: importRun fails closed with rollback, opts.profiles is an enforced allowlist, and a secret-shaped runId refuses at intake (RV1010 + RV1011 + RV1012, PR VI of the fourteenth plan)

    • importRun hardening (RV1010). The intake fails closed before the first write: every bundle blob ref must live in the bundle runId's own namespace (<runId>/...), so a crafted bundle for run A can never overwrite run B's blobs, and every entry must pass the journal codec's shape validation, so an import never appends garbage it would later refuse to replay. Writes land blobs, then entries, then meta, and a mid-import store failure rolls the partial import back best-effort: the exists-refusal never bricks the retry.
    • opts.profiles is an enforced allowlist (RV1011). The advertisement was filtered but the dispatch resolved from the FULL registry, so a spawn naming a registered-but-hidden profile by a guessed name went straight through. The dispatch now resolves from the same filtered set, and with opts.profiles passed, a spawn naming anything outside the allowlist refuses with a typed ConfigError before admission (no slot burned, nothing journaled); without opts.profiles behavior is unchanged.
    • Secret-shaped runId refusal (RV1012). The runId is a correlation key: it rides every event envelope UNMASKED (body masking runs before the envelope is assembled), so a secret-shaped runId was a masking-bypass channel the host created itself. Under an active masking policy, engine.run now refuses typed a runId the policy would rewrite (the default credential patterns and any host redaction.patterns alike), and assertSafeRunId gains a 200-character ceiling (MAX_RUN_ID_LENGTH); with maskEvents: false nothing is masked anywhere and the check does not apply.

1.136.0

Minor Changes

  • aa6ca71: A superseded segment refuses green everywhere: typed SupersededError, the distinct settledReason on run:end, and exactly one authoritative successor (RV1009, PR V of the fourteenth plan)

    The fencing design swallowed a superseded segment's LeaseHeldError on both settlement writes, so a stale segment whose settle bounced off the successor's fence resolved ok with an unmarked run:end: a green terminal that no durable store wrote, exactly the split view the RV907 doctrine forbids.

    • The stale segment now rejects handle.result with the typed SupersededError (code superseded, not retryable, data { runId, runStatus }, cause the fencing rejection): the successor owns settlement, and the authoritative outcome is its settle or the store's run meta, never the stale computation. The meta write is skipped instead of re-proving the fence.
    • run:end refuses green with settled: false and the distinct settledReason: 'superseded' (an l0-compatible extension), so an event-only consumer can tell a superseded segment from a settlement write failure; the settlement-failure path and every ordinary terminal keep their exact bytes.
    • A meta-only lease bounce over an already durable settle stays swallowed: the journal records the outcome, and only the projection belongs to the current holder (the takeover no-op contract is unchanged).
    • The CLI progress line renders settled=false (superseded; the successor owns settlement) instead of the resume hint, and the OTel exporter stamps rulvar.run.settled_reason beside the refused span status.
    • runFaultInjection (@rulvar/evals) grows the nineteenth scenario, superseded-terminal-honesty: the fenced-out segment must reject typed with the distinct reason and zero settle entries, and the successor must settle ok by replay with exactly one settle entry and no second paid call.

1.135.0

Minor Changes

  • cf75e22: The rates comparator fails closed on page-only tiers and NaN, and the checkpoint decoder honors never-throws on top-level nulls (RV1007 + RV1008, PR IV of the fourteenth plan)

    The fourteenth comparison experiment found two small holes in fail-closed surfaces. compareRates ran its tier comparison only when the SEED declared tiers, so a long-context premium the provider's page documents and the seed never declared produced no finding: exactly the silent underpricing channel the comparator's own doctrine names (the RV902 both-directions rule). Its scalar branch compared Math.abs(a - b) > 1e-9, and NaN > epsilon is false, so a page extraction that stopped parsing read as agreement. And decodeCheckpoint let JSON.parse('null') through the try/catch, then threw a raw TypeError on parsed.v out of a function whose documented contract is never-throws (the RV804 fix closed the nested shapes and left the top level open).

    • compareRates (RV1007): a page-only tier list is now a finding (tiers: the page shows N but the seed declares none; an empty page list claims nothing), and scalars compare in the negated NaN-safe form the tier fields always used, so NaN on either side is a finding, never agreement.
    • decodeCheckpoint (RV1008): a top-level payload that is not an object (null, a primitive, an array) decodes to undefined like every other malformed shape; the dangling dispatch reruns from the top, and the malformed corpus runs without a single throw.

1.134.0

1.133.0

1.132.0

Minor Changes

  • 2bec904: Live-budget parity for the cache-write TTL split, and the fault kit gates it on the real live path (RV1001 + RV1002, PR I of the fourteenth plan)

    The fourteenth comparison experiment reproduced a hard-ceiling breach: a run with budgetUsd: 4 settled ok at $4.50, because the mid-stream usage inlet, the reported/remainder fold, and every usage aggregate dropped cacheWrite5mTokens/cacheWrite1hTokens, so the live ledger priced a differentiated cache write at the plain 5m rate ($3.75) while settlement priced the split ($4.50). The two money paths now read one provider usage identically:

    • The mid-stream cleaner and the finish remainder carry the TTL split to the live debit, so the layer-3 ceiling holds against the same dollars settlement records; a ceiling between the unsplit and split readings severs the run instead of letting it settle ok over the ceiling.
    • @rulvar/core exports sumUsage, the canonical usage adder: aggregates (the run outcome, the settled ledger fold, the budget telemetry, reduceInvocationTable buckets) keep the split they were billed under, and an undifferentiated side's writes count as the 5m share so mixed aggregates stay canonical under the split-sum invariant.
    • Mid-stream TTL counts the finish total does not confirm are a usage-invariant violation, loud like every other telemetry anomaly; per-field catch-up over a shifted attribution only ever overcharges, never credits.
    • runFaultInjection (@rulvar/evals) grows a sixteenth scenario, ttl-live-budget-parity: a mid-stream differentiated write against the real engine must debit live and settle to the same $4.50, keep the split on the aggregate, and refuse to settle ok under a $4 ceiling. Reverting the fix reports matched: false in the kit, not only in the unit suite that shipped it.

1.131.0

Minor Changes

  • 256cae1: The thirteenth plan's probes become permanent gates, and the three moneys get their vocabulary (RV909, RV910; closes the thirteenth plan).

    runFaultInjection grows eight fail-closed scenarios driving the plan's fixed defects end to end on the real engine, zero provider calls and zero keys: nan-statement-refusal (unsummable statement dollars refuse typed at reconciliation intake, never verdict match over NaN totals), token-mismatch-divergence (provider-reported counts that disagree with our recorded usage decide the verdict even when the dollars agree, with tokenComparison: 'informational' still the declared opt-out), audit-missing-field-finding (the documented-rates comparator fails closed in both directions), anthropic-1h-priced (the shipped Anthropic table prices the 1h cache-write share at the documented 2x-input premium under its pinned pricingVersion, on the per-call reconciliation ledger where the TTL split lives), pause-turn-units (continuations absorbed into one dispatch settle at true wire units across the quota window, the invoice row's segment set, and the all-or-nothing statement join, with a partial segment set reading partial-coverage, never no-overlap), pre-admission-count-refusal (a spawn the budget could never admit refuses before the countTokens egress, so the full child prompt never leaves the process), forced-finish-completion (a budget-capped adaptive orchestration settles ok with the honest completion envelope mirrored onto the outcome), and settlement-terminal-honesty (a failed settlement write rejects typed with settled: false on run:end; the healed resume re-settles by replay with zero live calls). Reverting any of the fixes now reports matched: false in the kit, not only in the unit suite that shipped the fix. To reach those surfaces @rulvar/evals gains @rulvar/openai, @rulvar/anthropic, and @rulvar/plan as dependencies.

    @rulvar/core publishes compareRates (with its DocumentedRates input type), the both-directions documented-rates comparator the weekly audit runs: moved from the audit script to a published home so the kit can drive it as a gate, with the script importing the same function from dist inside its entrypoint exactly like the seeds, one source of truth. And the pricing docs now name the three moneys of one run in one place: recorded money (settled history under the pricingVersion pins its own settles wrote, the number CostReport, rulvar inspect, and the invoice's pinned rows show), the docs estimate (repricing at the current table, what preflightEstimate projects and the invoice prints past the pins), and the provider bill (established only by reconcileStatement over saved exports, never by a dashboard headline), plus the rate-update order: audit, then release, then new pinned runs.

1.130.0

Minor Changes

  • d6bec7a: Every tool event names its call (RV908, the thirteenth experiment's OTel attribution risk). tool:start and tool:end gain toolCallId, the model-minted id the journal's messages and tool-result parts have always carried: present on every live event and on every replayed reconstruction (the id rides the checkpoint's tool-result parts, so even journals written before this release name their calls on resume), absent only on streams recorded before RV908 or written by foreign emitters.

    The OTel exporter pairs tool spans EXACTLY by the id (stamped as rulvar.tool.call_id), so concurrent same-name calls that finish out of order keep their own durations and outcomes instead of FIFO-swapping attribution. Streams without the field keep the historical FIFO pairing byte for byte, an id-bearing tool:end whose start carried no id falls back to the same FIFO (mixed streams pair no worse than before), and the orphan tolerance (a closer with no open start attaches as a span event) is unchanged.

1.129.0

Minor Changes

  • 1612439: Honest terminals (RV906 + RV907, the thirteenth experiment's release risks six and seven): a forced finish names itself partial, and a failed settlement is never a green event.

    RV906: under the default budget.atCap: 'finish-with-partial', the capped terminal's value becomes the completion envelope { result, completion }, and the literal is 'partial' unless the finalizer's finish provably passed the FULL declared contract: the declared finish validators now BIND the reserved finalizer (on capped runs synthesis never runs, so that finish is the final output they must judge; a finish they reject never becomes the run value and the deterministic fallback settles the run), while a declared acceptance policy is still never judged at the cap, so with one declared the terminal stays 'partial'. The finalize fallback's synthesized partial carries the same completion: 'partial' claim on its exhausted outcome. The engine lifts the literal onto run:end and the outcome mirror, so a consumer reading only status can no longer execute a truncated plan as a full success. The journaled finalize effects also roll forward on resume: a settled capped run reuses its recorded finalize terminal (or fallback decision) instead of re-deriving the prompt from the drifted live digest, which used to mint a fresh agent identity and re-pay the reserve on every resume of an already settled capped run.

    RV907: run:end gains settled: false, present ONLY when a settlement write failed (the run_settle journal append or the terminal RunMeta projection): the status stays true as computation, but nothing durable records it and handle.result rejects with the typed SettlementError, so an event-only consumer is refused the green terminal exactly like the rejected promise. The CLI progress line appends settled=false (outcome withheld; resume re-settles), and the OTel exporter stamps rulvar.run.settled: false and refuses the OK span status. The order stays warn, then the marked run:end, then the throw; a healed resume re-settles by replay with zero paid calls and its terminal carries no field, byte for byte like every ordinary run.

1.128.0

Minor Changes

  • 27c4e38: pause_turn continuations become accounted wire units (RV905, the thirteenth experiment's fifth release risk). The Anthropic adapter absorbs server-side turn pauses by re-sending, making up to six wire requests inside ONE core dispatch; until now the request quota window, the provider call record, and the invoice row all saw one, and a per-request provider statement matched one segment while the rest read statement-only.

    The adapter's finish metadata now names the whole segment set (providerMetadata.anthropic.wireRequests = { count, responseIds }); the provider call record and the invoice row carry wireResponseIds; and the quota reconciliation settles the reservation against the TRUE wire request count. The QuotaLimiter.reconcile SPI gains an optional actual.requests argument, honored by all three reference limiters through one shared arithmetic (quotaActualRequestsDelta), so a window that admitted one request per reservation now reflects what the provider's own RPM meter saw; a settlement only ever adds, never denies retroactively, and implementations written against the two-argument form remain valid. reconcileStatement joins a multi-wire invoice row by ANY id of its segment set, all-or-nothing: a partially delivered segment set reads partial-coverage with its delivered segments never counted as statement-only (and never no-overlap when segments touched our data), and provider-reported token counts compare as the SUM over the segments against the dispatch's recorded usage. Single-wire dispatches carry none of the new fields and stay byte-identical, journals and events included.

1.127.0

Minor Changes

  • b3b1805: Admission before egress for the pre-dispatch token count (RV904, the thirteenth experiment's pre-admission egress probe). ctx.agent calls the adapter's optional countTokens with the FULL child prompt to tighten the admission reserve; before this release that network call ran before the budget decided anything, so a spawn the budget could never admit still sent the prompt to the provider, the call honored no abort signal, and nothing observable recorded the egress.

    The reserve is monotone in the count, so the smallest reserve any count outcome could produce is computable without it: the priced floor at zero input tokens, or the flat fallback the count-failed path admits under. The engine now checks that floor against the budget first, through the exact refusal arithmetic admitSpawn itself uses (RunBudget.refuseSpawnIfInfeasible, the refusal arm factored out so the two layers can never disagree), and a spawn that could never be admitted (the lifetime spawn cap, a full account, an exhausted ceiling) refuses with zero network calls. The provider SPI's countTokens gains an options argument with an AbortSignal; the Anthropic adapter threads it into the SDK request, and an abort mid-count cancels the spawn instead of silently falling back to the flat reserve and dispatching behind a cancelled spawn. Every count is now observable: an admission.countTokens info log names the model and the counted tokens, and a failed count warns with the failure the flat reserve then covers. An explicit estCost (per call or per profile) remains the zero-egress path that skips the count entirely, now documented as the posture for hosts whose privacy gates must run before any prompt byte reaches a provider. Spawns on adapters without countTokens, and spawns carrying estCost, behave byte-identically to v1.126.0.

1.126.0

1.125.0

1.124.0

Minor Changes

  • 37fd1f2: The twelfth plan's closing trio (RV809, RV810, RV811). The tool budget extension gains coverEvidenceDeficit: with an evidence contract declared, the extension grants at a tool-turn boundary whenever the remaining call budget cannot cover the declared floor's outstanding deficit, under the same money, progress, and maxExtensions gates, so a limited child at 7 of 11 entries converts headroom into the missing evidence BEFORE the cap instead of dumping through the reserved tail; the journaled grant decision carries trigger: 'evidence-deficit' and the announcement names the exact deficit. Canonical Usage gains the optional cache-write TTL split (cacheWrite5mTokens and cacheWrite1hTokens, invariant: the split sums to cacheWriteTokens); priceUsdOf bills the 1h share at cacheWrite1hUsdPerMTok with everything unclaimed at the plain write rate (byte-identical arithmetic without a split), sanitize repairs broken splits with 1h priority (never an undercharge), and the Anthropic adapter fills the split from the cache_creation breakdown when it agrees with the flat total. @rulvar/evals gains the fault-injection kit: runFaultInjection drives the never-observed-live fail-closed branches (in-flight-exposure refusal, duplicate quota rule, torn and glued JSONL tails, the settle-boundary crash resume, pricing rotation with an uncovered tail, unknown provider id) on the real engine offline, verifies each documented typed observable fail closed, and leaves experiment-grade artifacts.

1.123.0

Minor Changes

  • 5c46468: Sectional bounded repair and the structured evidence index (RV808b, the second half of the split RV808). finishValidation.sectionalRepair: { sections } teaches every gated finish a second repair shape: after a rejection the model resubmits ONLY the repaired sections as finish({ sections }), and the host splices them into the retained rejected attempt (the exported spliceSections, line anchored, missing declared sections append) and validates the reconstructed document whole; the synthesis invocation is seeded with the coordination draft as its retained base, so with carryDraftGaps the post-fan-in window collapses to one small patch instead of a full re-derivation. Mechanics refusals are typed, journal nothing, and spend no repairs; the gated invocations' finish tool schema moves only under the opt-in. synthesis.evidenceIndex adds a deterministic EVIDENCE INDEX: prompt line (per settled child: the distinct citations its output carries, evidence-pool children only, artifacts, chars, the handle when the read tools are exposed), so the composing model pages exactly what it needs instead of re-reading the whole pool; replay-stable, fail-closed pattern intake, byte identical when unset.

1.122.0

Minor Changes

  • 8cf45c5: The post-fan-in double rework closed at its cheapest point (RV808a, the first half of RV808). The twelfth comparison run paid 80.157% of wall time AFTER fan-in: the coordination draft was repaired only against the weak draftPolicy subset, the skipWhenDraftValid pre-pass then judged it by the FULL contract and failed, that verdict was silently discarded, and the synthesis invocation re-derived the whole document blind to the known defects and failed the same contract once more itself. Two opt-ins close the loop. finishValidation.draftPolicy: 'contract' gates the coordination draft by the full declared validator set (same validators, same children snapshot the synthesis-bound validation reads), with rejection feedback naming the failing validators, so the coordination repair loop drives the draft toward exactly what the pre-pass will judge and the skip becomes reachable; the preflight draft-gate-below-contract warning cannot fire under it, and the preflight input type accepts the sentinel. synthesis.carryDraftGaps: true (requires skipWhenDraftValid) journals a failing pre-pass as an orchestrator_synthesis_draft_gaps decision (failed validator names and reasons, bound to the contract generation and draft hash exactly like the skip decision) and feeds the synthesis prompt a DRAFT CONTRACT GAPS: line instructing it to repair the named gaps and preserve the draft otherwise; a resume reuses the journaled verdict without re-running a validator, so prompt bytes re-derive identically and the paid invocation replays. Both default off: journals and prompt bytes stay byte-identical without them. The sectional bounded repair and the structured evidence index (RV808b) follow separately, and the live acceptance measurement of the post-fan-in share stays gated on an explicit founder go.

1.121.0

Minor Changes

  • 3d67d41: Rate provenance made checkable (RV807, RV813, RV814). The pricing row grows ratesVerifiedAt (SPI), the ISO date it was last verified against the provider's documented rates or, stronger, its billing categories: the shipped seeds stamp it (the GPT-5.6 family reads 2026-07-30, the day the statement reconciliation confirmed those rates against the provider's own per-component billing categories to the cent; the pre-5.6 OpenAI rows keep their 2026-07-18 docs verification; every Anthropic row was re-verified against the documented table on 2026-07-30). The date is surfaced wherever a dollar is consumed: preflightEstimate copies it onto each spawn report and rulvar preflight renders ratesVerified=<date> with its age on the spawn line; the settle pin journals it with the rest of the applied row so it survives any later table rewrite; and rulvar invoice prints a rates verified: line naming each priced model's date and age, pinned rows first, current table past them; the twelfth run's founder read the invoice doubting the rates and nothing said the seed was 12 days stale. The doctrine ships with the mechanism: seeds bound ceilings conservatively, billing truth is established only by reconcileStatement over saved exports, and a confirmed divergence corrects the seed in its own release with a changeset, never a silent rewrite. Enforcement rides two new gates: a weekly documented-rates audit (scripts/rates-audit.mjs in the live contract workflow) re-fetches exactly the pages the seed comments cite, compares every rate, write premium, and long-context tier, and opens an issue on drift or on a page that stops extracting, and a README release-table gate (scripts/readme-release-shas.mjs, in CI) requires every cited squash SHA to be an ancestor of HEAD, catching the v1.109.0 row that pointed at an object no branch contained for eleven releases (now corrected to the real squash 58afdb5).

1.120.0

Minor Changes

  • d630c9e: The partial fan-out contract and the per-child acceptance roster (RV805, RV806). parallel_agents admits children sequentially in submission order, and a mid-loop admission refusal is now part of the TYPED tool result instead of a throw: the model keeps every started handle (awaitable and cancellable), and refused names the failed index, the typed error code, and the reason; a thrown refusal used to swallow the whole call while the started children kept spending invisibly, inviting a duplicate wave. The clean-wave result stays byte for byte { handles }. The acceptance fold now journals a per-child machine roster inside its single decision and carries it as acceptanceChildren on the envelope, the RunOutcome, and run:end (same lift and malformed-drops-silently posture as the salvage lists, mirrored to OTel as rulvar.run.acceptanceChildren): each spawned child with its settled status, the salvage arm that accepted it, and, where the child declared an evidence contract, the evidence verdict { recordedEntries, minEntries, met } with waivedBySalvage: true on a below-floor child a salvage arm accepted anyway; the twelfth comparison run accepted two below-floor children through salvage and nothing machine-readable said so. Behind it, a declared evidence contract now stamps EVERY settled AgentResult with evidence (the same window-derived count as the enforce-refuse floor), absent without a contract so those results stay byte-identical. rulvar inspect prints the acceptance verdict with the completion, the salvage lists, and the per-child evidence verdicts from the journaled decision, plus journaled quota_drift decisions labeled per-minute window, not cumulative. The guides now state the gating rule outright: gate on the (status, completion) pair, never on status alone.

1.119.0

Minor Changes

  • 1e4ff3c: Validation symmetry closes two crash-shaped gaps the twelfth experiment found (RV803, RV804). preflightEstimate now validates run.budgetUsd with the same typed guard the runtime applies to RunOptions.budgetUsd: a NaN, negative, or infinite ceiling refuses as a ConfigError naming preflight.run.budgetUsd instead of flowing silently into every projection the report is built from; preflight already validated run.limits, run.maxInFlightExposureUsd, and every spawn budget, and the run ceiling was the one raw read left. decodeCheckpoint now validates the nested message structure: a parseable blob whose messages are malformed ({v:1,messages:[{}]}, a message without a string role, a non-array parts, a garbage part) returns undefined per the function's own undefined-on-unparseable contract, so the dangling dispatch reruns from the top, instead of throwing a raw TypeError out of msg.parts.map mid-resume. Well-formed checkpoints round-trip byte for byte as before, and both refusal shapes carry mutation-probe entries.

1.118.0

Minor Changes

  • f8341a3: Provider statement reconciliation as a machine (RV812, the twelfth experiment's billing lesson). The run's billing question (a dashboard headline of 4.45 then 4.77 USD against the settled 7.304885) was closed by hand with screenshots; nothing in the system could close it. Now @rulvar/openai exports reconcileStatement(invoice, statement, { pricingOf }): it joins the machine-readable invoice against a NORMALIZED provider export, per-request rows by response id or per-model per-component category totals (the Spend categories shape), and refuses a headline aggregate typed, because an eventually consistent dashboard total is not evidence. The report carries response-id coverage (a partially delivered export reads as partial-coverage, never as false divergence: component deltas fold over the covered subset only), per-component deltas per serving model, and the implied actual rate of every component beside our effective rate over the same token base, so a real divergence NAMES the rate-card line that moved with the rate the provider actually applied. Unpriced models and usage-unknown rows are declared apart, never folded or silent; verdicts are match, divergence, partial-coverage, no-overlap. Backing it, @rulvar/core exports priceComponentsOf(pricing, usage): the four billing components (uncached input, output, cached input, cache writes) with token bases and dollars, decomposed with exactly the settled fold's arithmetic; priceUsdOf is now defined as the sum of those four terms in the historical order, byte for byte the same number, so the reconciliation and the settled fold can never disagree about what a usage costs. Validated against the real twelfth-run artifacts offline: the founder's eight dashboard categories reconcile to match with every delta under 0.0005 (3-decimal rounding), response-id coverage reads 120 of 120, a 100-row truncation reads partial coverage with zero divergence, and a synthetically distorted write rate names gpt-5.6-terra cache-write with implied 2.5 USD/MTok against effective 3.125.

1.117.0

1.116.0

Minor Changes

  • a213878: One settled number on every public money surface (RV801, the twelfth experiment's P0). run:end now spreads outcome.cost.totalUsd itself, so the terminal event and the settled report cannot disagree under any pricing table; live in the twelfth comparison run the event said 10.4148235 USD against 7.304885 USD on every other surface, because the kernel ledger re-priced per-phase usage aggregates through the 272k long-context tier no single request crossed. Replayer.ledger() folds dollars on the settled billing basis (RV504): per provider call where an entry's dispatch records cover its usage, the per-slice aggregate otherwise; usage sums and the spawn count are unchanged. The resume budget seed is the settled fold too, per-call basis composed with the per-segment pricing pins (RV505), so resuming a tier-heavy run no longer inherits aggregate-priced dollars and falsely exhausts a ceiling the real spend never crossed (the escalation on the experiment's finding: that exact journal would have resumed with 10.41 of a 10.00 ceiling already counted as spent), and a resume across a price-table rotation starts from the figure the prior segment actually reported instead of re-pricing settled history at the rotated rates.

1.115.0

Minor Changes

  • 63642ae: Post-fan-in attribution and the opt-in in-flight exposure cap (RV710, RV711). reduceCriticalPath now decomposes the post-fan-in window whenever it exists: CriticalPath.postFanIn folds the coordination spans' model activations and tool executions (by tool name, so child-result pagination and the finish exchanges show up under their own names) and the synthesize span wall, each clipped to the window, with coveredMs as the exact interval union and residueMs/residueShare naming what no recorded interval covers, from the same event vocabulary with no new types. RunOptions.maxInFlightExposureUsd bounds spent money plus the summed worst-case estimates of live dispatches: the admission holds each turn's own estimate from right before the provider call until the attempt settles, refuses the dispatch whose estimate does not fit with a typed BudgetExhaustedError (data.reason 'in-flight-exposure') instead of waiting, and thereby bounds the worst concurrent overshoot to the estimate error of the in-flight turns instead of one whole turn per agent; off by default with byte-identical wire traffic, and preflight reports a configured cap as the in-flight-exposure-cap finding.

1.114.0

Minor Changes

  • 5759731: The fixed-window quota boundary is pinned as a named compromise, and the final model can opt into the run's own observed evidence (RV708, RV709). QuotaRule and the model-routing guide now name the window semantics exactly: every PerMinute cap counts over fixed epoch-aligned 60 s windows, each window enforces its cap exactly, and a burst placed astride a boundary can consume up to two caps inside one sliding 60 s, the bounded price of cross-process parity, pinned by test as intended behavior with no semantics change. runAgent gains the opt-in policyFacts: the finalize synthesis request carries ONE additional request-only message digesting what the loop observed (quota denials and recoveries, tool budget pressure and extension grants, the finalization window, recorded spend with its cost basis), never touching the durable transcript or spawn identity; orchestrate gains the symmetric synthesis.policyFacts, a deterministic POLICY FACTS: prompt line folded only from replay-stable settled child facts (statuses, extension grants, finalization windows and reserves), so a resumed synthesis re-derives identical prompt bytes. Both are off by default and every request and prompt stays byte identical when unset.

1.113.0

Minor Changes

  • a60807a: The pricing composition's second half names itself, and the effect-ledger quarantine is byte-true (RV706, RV707). InvoicePricingProvenance gains optional currentPricingVersion: on composed exports it is the version of the caller's current table, the one that priced everything past pinnedThroughSeq (on current-table exports, the whole fold), so an invoice folded across a rotation now names both halves of the composition where the pinned segments already declared theirs; rulvar invoice and rulvar inspect fill it from the configured table and extend their text suffix to pins composed with the current table (v-a, v-b; current v-live), byte for byte unchanged when the config declares no version. The executor ledger's torn-tail quarantine row now carries bytesBase64 and sha256 of the exact torn bytes alongside the lossy bytes string kept for old readers (two different byte tails used to collapse into one indistinguishable row), and the repair's parseable decision is made on the bytes, strict UTF-8 before JSON.parse: the lossy decode could make a fragment with invalid bytes inside a string literal parse, and the repair then terminated a line of invalid bytes in place, manufacturing exactly the corruption the fail-closed scan refuses.

1.112.0

Minor Changes

  • 00ae55b: Duplicate quota rules are refused at construction in every reference limiter (RV704). snapshotQuotaRules, the shared construction chokepoint of memoryQuotaLimiter, SqliteQuotaLimiter, and PostgresQuotaLimiter, now throws a typed ConfigError naming both indexes and the canonical quotaRuleKey when a rule set contains two identical rules. Before the refusal, the same duplicated configuration admitted differently per storage: the memory reference buckets by rule index, so each copy counted independently and the full cap admitted, while the store references bucket by rule key, so one shared bucket was debited once per matching copy and half the cap admitted (a cap-4 set granted 4 in memory and 2 on sqlite), breaking storage parity with a configuration nothing had refused. @rulvar/store-conformance gains quotaRulesConformance, the executable construction contract any limiter implementation can register.

1.111.0

Minor Changes

  • fd25169: A covered model's invoice rows are now exactly its recorded provider calls (RV703). Coverage is decided per model (RV604), but the remainder pass subtracted records per model AND role, so a covered model whose record roles differed from its slice roles (the schema-extract default splits one model's usage by role while the record carries one role, or none) fabricated a phantom unattributed remainder row: the export then carried more tokens than the run used, sum(rows[].usd) exceeded totalUsd under a rowUsdNonAdditive: false promise, and the allocation pass siphoned dollars from the real call's row onto the phantom. invoiceFromJournal now skips the per-slice remainder arithmetic entirely for models the billing fold covered; uncovered models keep the historical per-slice remainders byte for byte. EntryBillingFold publishes the fold's per-model coverage decision as coveredModels, so row builders honor the same decision instead of recomputing it under a different key.

1.110.0

Minor Changes

  • 58afdb5: Price the live and replayed event telemetry per provider request, exactly like the settled fold, and label every money-bearing event with its basis (RV702).

    The eleventh comparison experiment measured the defect live: agent:phase:end priced the phase-aggregate usage delta in one call, so a nonlinear long-context tier fired on aggregates no single request crossed; agent:end and reduceInvocationTable inherited the inflated dollars (raw sum +60.2%, loop bucket +82.9%) while the settled CostReport and invoice priced per request (RV504). Now every recorded provider call is priced individually at its own chokepoint, phase events carry the delta of that per-call accumulator, agent:end carries its sum, the replay path folds the terminal entry through the same priceEntryBilling the invoice uses, and the reducer's rows and byRole buckets match the settled fold whenever records cover the usage.

    New costBasis: 'per-call' | 'aggregate-estimate' on agent:phase:end, agent:end, AgentResult, and the reducer's rows and buckets: 'aggregate-estimate' appears only where per-request records cannot cover the number (a checkpoint written before the reconciliation ledger shipped restores usage without call records; the invocation total then keeps the aggregate-priced figure, labeled, instead of silently dropping restored spend), and the reducer defaults an absent field to 'aggregate-estimate', never to a per-call claim the stream cannot back.

1.109.0

Minor Changes

  • 85b1d39: Close the two fail-closed gaps the eleventh comparison experiment proved live (RV701, RV705).

    RV701, JsonlFileStore: a crash that persisted every JSON byte of an append but not its trailing \n left a parseable unterminated tail; load served it, the next append glued the following record onto the same line, and the load after that classified the glued line as one torn fragment and repaired BOTH accepted records away (a first-line glue rewrote the journal to zero bytes; a later second append buried the glue mid-file and made the journal unreadable). append now terminates a parseable unterminated tail in place before this instance's first write, and torn-tail repair salvages every complete record a glued last line carries, discarding only the unacknowledged trailing fragment. An entry load has served once can no longer be un-served by a later repair.

    RV705, buildCostReport: the exported live builder returned whatever numbers the host fed it, so an Infinity or NaN total, bucket, or abandoned ledger serialized into null downstream, while costReportFromJournal had refused exactly that since RV610. The builder now runs the same deep finite validation and refuses non-finite reports with the same typed ConfigError.

1.108.0

Minor Changes

  • affa3d4: Stored consumers compose the pricing pins exactly like the engine, and the invoice provenance declares every pinned version (RV611).

    JournalPricingSnapshot exports the composition the engine's outcome mirror applies at settle: composedPriceUsd(current) prices pin-covered rows at the rates their own settle recorded and everything past the last pin (a segment journaled but never settled) at the caller's current table. The engine now consumes the same method, and the three stored consumers (rulvar inspect, rulvar invoice, the server's stored-run cost endpoint) fold through it instead of passing the raw snapshot, which silently priced the tail at the last pin's rates and folded never-pinned models as unpriced even when the current table knows them. Two fallbacks stay deliberate and documented: a covered model its covering pin missed back-reprices at the last pin when that pin names it, and a model no pin resolves falls to the current table.

    The snapshot also carries segments (every pin's seq boundaries, pricingVersion, and rows in journal order), and InvoicePricingProvenance gains the 'composed' source plus segments and pinnedThroughSeq, so an invoice folded across a price-table rotation names every version that priced it instead of hiding the rotation behind the last one. The CLI exports that priced through a pin now declare source: 'composed' (previously 'snapshot'), and the pricing rates:/pricing: text lines name the composition and every pinned version.

1.107.0

Minor Changes

  • 9f5f6f6: Fail the evidence-preservation contract closed at intake and refuse non-finite accounting anywhere in a public report (RV610); the exactly-once claim sentinel now judges normalized prose blocks and the guarantee matrix states the two-phase row shape honestly (RV612).

    evidencePreservedValidator intake is fail closed: a pattern that can match the empty string is refused with a typed ConfigError at construction (an empty match would enter the citation pool as fabricated evidence, trivially "preserved" by every result and defeating requireNonEmptyPool), zero-length matches never enter the pool even when a lookaround produces them in context past the construction probe, and requireKnown and requireNonEmptyPool must be real booleans, so a stray 'true' or 1 can never silently disable the strict mode it names.

    Accounting refuses non-finite numbers at every layer: the per-entry price folds (priceEntryUsage, priceEntryBilling) throw a typed ConfigError the moment individually finite prices overflow the running sum, and costReportFromJournal and invoiceFromJournal walk their finished public objects and refuse any Infinity or NaN before returning, because JSON serializes both as null and a published report that quietly carries null where dollars belong is silent telemetry corruption. Previously two individually valid Number.MAX_VALUE prices produced totalUsd: Infinity and allocatedUsd: NaN.

    The docs claim sentinel (RV508) now normalizes contiguous markdown prose and source comment blocks before matching, so a forbidden claim wrapped across a line break or spaced with double whitespace is caught at the block's first line, and the prior shipped recurrence "each ran once" is recognized as the same claim; the (file, anchor) allowlist is unchanged. The widened rule immediately caught one live wrapped occurrence in a core comment, which is rewritten with the precise guarantee. The guarantee matrix's effect-accounting cell no longer contradicts the ledger format: a completed two-phase attempt has TWO rows (intent and outcome, one attemptId), a crash between the phases leaves the intent row alone as the orphan, and the legacy no-intent ledger keeps its one-outcome-row contract.

1.106.0

Minor Changes

  • 9a4ce49: Alias recovered child attempts by admission identity, so a restored coordinator's old handles reach the reborn attempt (RV609).

    The handle-stability alias required the old and new running entries to share (scope, key, ordinal), but occurrence ordinals are strictly monotonic per (scope, key): a rerun always takes the NEXT ordinal, so the alias was unreachable for ANY rerun, not just a cancelled child. A restored coordinator transcript that kept calling the handle it saw (await_all, cancel_agent, get_child_result) got unknown handle repair turns instead of the reborn attempt and could exhaust before the acceptance policy or the minSpawnedChildren floor (RV507) was ever evaluated. The seam predates the ninth plan: it shipped in v1.7.0.

    Recovery now aliases by what is actually stable, the admission identity: every prior attempt's RUNNING row of the redispatched admission's (scope, key) under the pinned child scope aliases to the reborn record (every handle is a running row's seq, so the claimable set is exactly the prior running rows; a terminal is a separate row and never a handle). A transiently claimed same-key sibling is content-interchangeable and is rebound the moment its own redispatch lands. Because several handles can now map to one record, every roster-shaped walk (wake digests, quiescence, finish-validation children, the forced-finish fold, incremental synthesis reconciliation, the acceptance decision, and the synthesis digest, now one row per spawn under its current handle) iterates the per-spawn-ordinal roster instead of the handle map, so an aliased child is never counted or digested twice. Without aliases the per-spawn walks are byte-identical to the old per-handle ones, so synthesis prompt bytes and existing journals roll forward unchanged.

    Fresh runs are byte-identical; the change is confined to recovery. Also freezes the clock in a real-clock store-postgres test that could straddle a minute boundary in CI (test-only).

1.105.0

Minor Changes

  • 531dc88: Make quota rules an immutable snapshot with a canonical denial order in all three limiters, and give the postgres limiter rotation generations, a fenced stale host, a bounded bootstrap, and strict intake (RV608).

    Immutable snapshot (all three limiters): memoryQuotaLimiter, SqliteQuotaLimiter, and PostgresQuotaLimiter now admit under the new exported snapshotQuotaRules(rules): a validated, frozen copy carrying only the known rule fields, taken at construction. Mutating the caller's array or rule objects afterwards (a pushed rule, a reassigned cap) can no longer change a decision, a bucket key, telemetry, or the fingerprint the postgres schema records; previously the caller's live graph was read on every admission and the fingerprint was computed lazily from it at first boot. The canonical per-rule content key is also exported as quotaRuleKey, and every limiter folds a denial over matching rules in that canonical order, so permuted but identical rule sets now produce the byte-identical refusal object (reason and retryAfterMs), not just the same fingerprint.

    Rotation generations (postgres): rulvar_quota_meta now records a rules generation beside the fingerprint. Every admission re-reads both inside its own locked transaction and, on a mismatch, is refused with the new typed QuotaGenerationError instead of admitting under retired bucket keys, so a host that booted before a rotation is fenced rather than silently splitting the budget; its next call re-boots into the honest boot-time ConfigError, and its outstanding reservations age out with their window. Rotation (acceptRulesUpdate: true) now serializes with in-flight admissions on the same advisory lock, bumps the generation, and carries current-window consumption conservatively: a new bucket inherits the retired bucket's counters for the same (provider, model, tenant) dimension triple (the maximum when several retired rules share it), so a raised cap grants only the difference, a lowered cap counts what was already consumed, and a genuinely new dimension starts empty. The carry decision is conservative by design: estimates held by fenced hosts settle nowhere and age out, which errs toward under-admission inside the rotation window, never over.

    Bounded bootstrap and honest deadline phases (postgres): the bootstrap transaction now runs under the same SET LOCAL lock_timeout as admissions (a held boot lock used to wait unboundedly), and its connection is registered with the full-path deadline, which destroys it on expiry so an abandoned bootstrap can never commit DDL or a rotation after the caller was already refused. QuotaDeadlineError.phase gains 'bootstrap', and each phase's message now narrates only what actually happened: an 'acquire' refusal held no connection and no longer claims one was destroyed.

    Strict intake (postgres): acceptRulesUpdate is runtime-checked as a real boolean (the string "false" used to enable rotation by truthiness), and admissionDeadlineMs is refused above the Node timer maximum (2147483647 ms, now exported from @rulvar/core as MAX_TIMER_DELAY_MS) before the pool is constructed; above it, the deadline timer used to clamp and refuse every admission after about a millisecond.

    Migration note: hosts running mixed rule sets over one schema now fail loud during a rotation instead of silently splitting the budget: old booted hosts receive QuotaGenerationError on their next admission the moment a new deployment boots with acceptRulesUpdate: true. That refusal is the designed rollout signal, not a regression; roll the refused hosts to the new rule set and remove the flag. Existing recorded fingerprints keep matching (the key encoding is unchanged), and pre-generation schemas are backfilled to generation 1 on the first matching boot.

1.104.0

1.103.0

Minor Changes

  • f2b809e: Symmetric billing coverage and the per-slice invoice residual (RV604, RV605: the round-52 accounting P1s).

    Coverage is decided per model with a symmetric key (RV604). The per-call billing fold compared each usage slice against the per-MODEL sum of the provider-call records, while the slices split one model's usage by role. Several roles on one model, which is the DEFAULT configuration under a schema (the same-model extract), therefore always refused coverage and re-priced the aggregate, firing nonlinear long-context tiers no single request crossed: the audit reproduction turned 700 honest monetary units into 1900 while the live ceiling had debited ~700. Both sides now aggregate by serving model, coverage is decided per model, a covered model prices each of its records individually (the role rides the record, so byRole survives), and an uncovered model honestly keeps the aggregate basis. fullyAttributed is true exactly when every slice model is covered and no record names a model absent from the slices. The engine-level coherence obligation gets its own test: on a fully attributed multi-role run under a tiered table, the settled fold equals what the live ceiling debited.

    The invoice residual is computed per slice (RV605). The unattributed remainder used to be one whole-entry row published under entry.servedBy: a slice of another model with no records left its allocation pool rowless, and the dust pass dumped that model's whole USD onto the largest row of a different model so the column would sum. The remainder is now computed per usage slice, subtracting only the records of the slice's own serving model (and role, when the slice carries one), and each non-zero remainder becomes a row under that slice's model and role. The dust pass refuses to transfer a target into a pool with no rows: the amount is excluded from the reconciliation and declared in the new unallocatedUsd field (absent when zero, which is every well-formed journal), so cross-model transfer is structurally impossible and additivity is honest rather than forced.

1.102.0

Minor Changes

  • 3eb6515: Durable authorization before the authorized effect (RV601, RV602, RV603: the round-52 review of the ninth plan's own surface).

    A tool budget grant is durable before it takes effect (RV601). The grant and finalization-window decision entries introduced by RV509 were journaled fire-and-forget, so a tool call could run under a raised cap whose authorizing decision never reached the store, and a rejected append left the run settling with the decision silently absent. Both hooks on RunAgentOptions.toolBudgetDurability now return Promise<void> and the loop awaits them before the grant lifts an expiry, before the window binds a call, and before either announcement is queued. A refused append issues no grant and marks no entry, and the failure propagates exactly like a failed boundary checkpoint instead of being swallowed. Migration: a host wiring these hooks directly must return a promise, and a grant can now fail when the journal store is unavailable rather than proceeding unrecorded.

    The journaled cap anchors a resumed ceiling (RV602). maxToolCalls and increment are not part of the dispatch identity, so a host may legitimately change them between segments; recomputing the resumed cap from live limits revoked a raise the model had already been promised on the live-resume path while a pure replay honored it from the journal. toolBudgetDurability.restored now carries the journaled cap, the loop measures from it, and grants taken after the restore point apply the current increment to that anchor. A restored cap that is not an integer at or above the base cap is ignored with a warning, leaving the executed-call derivation as the floor.

    A synthesis skip is bound to its contract generation and draft (RV603). The orchestrator_synthesis_skip decision written by RV510 was looked up by scope and key alone, so the documented fix-and-resume remedy was defeated: a crash between the skip and the run settle, followed by a contract fix, resumed with the stale skip and settled ok carrying output the current contract rejects. The entry now records the contract hash (when a finishValidation.contract is declared) and the hash of the draft it judged, and is reused only when the contract generation, the draft, and the validator names all still match; otherwise the gate re-runs on the current contract. Without a contract descriptor the binding falls back to draft plus validator names, which is honestly weaker and documented as such. Entries journaled before this field existed stay reusable, so runs in flight roll forward unchanged.

1.101.0

Minor Changes

  • 51b215c: Conditional synthesis (RV510, the ninth-experiment review): the opt-in synthesis.skipWhenDraftValid: true runs the coordination draft through the FULL declared finish contract before the synthesis span starts. A draft that passes every validator becomes the final result without the synthesis invocation ever dispatching, under a journaled orchestrator_synthesis_skip decision with the new machine-readable reason synthesis_skipped_by_valid_draft (the existing OrchestrateSynthesisSkipReason vocabulary, additively extended); the info log and the acceptance envelope carry the same reason, and a resume rolls the journaled skip forward with zero paid calls. A draft that fails any validator goes to synthesis exactly as before, with the repair budget untouched. Deterministic by construction (only the declared contract judges, no semantic-delta heuristic); requires finishValidation at intake; default off, byte-identical journals and cassettes.

1.100.0

Minor Changes

  • 9785bea: Durable parallel of the ToolBudgetSummary (RV509, the ninth-experiment review): an adaptive tool-budget extension grant and the finalization-window entry now journal as decision entries of the existing vocabulary (tool_budget_extension, finalization_window_entry), bound to the agent dispatch by targetRef the moment each fires. A crash-resume restores the granted cap and the window-entry fact from the journal, so a granted-but-unspent extension is honored instead of silently revoked (the conservative executed-call derivation stays as the floor beneath a lost journal tail) and finalizationWindowEntered stays truthful when a later grant moved the counts back out of the window. A replayed result now carries the journal-backed summary subset (used from the terminal checkpoint, the granted cap, extensionsGranted, finalizationWindowEntered) with zero provider calls. Pressure notices stay events, grant-free runs journal nothing new, and their journals and cassettes remain byte-identical.

1.99.1

Patch Changes

  • ef08d73: Guarantee matrix and exactly-once claim hygiene (RV508); no runtime behavior changes. The isolated-executor guide now carries the guarantee matrix stating flatly who provides what: the library's layers give at-least-once execution with attempt binding and intent-before-effect, exactly-once effect execution is promised by NO library layer, and what IS exactly-once is pay and replay (the never-pay-twice invariant). The two claims the ninth comparison experiment's judge caught are rewritten to the precise statements ("each ran once" became attempt counting under a stable idempotency key; the approvals guide now says continuation is a run-level guarantee, not an effect-level one, with the at-least-once window named); ctx.step docs state the same window for effectful steps; a ResolutionBy note says the field records a channel, never a verified principal (identity, signatures, and separation of duties are host IAM). The worker header now points at the shipped SqliteQuotaLimiter and PostgresQuotaLimiter instead of denying that cross-process limiters exist. A new docs-lint sentinel forbids "exactly once" claims in the hand-written docs and in package source comments outside a vetted (file, heading anchor) allowlist (the durability pay doctrine and the guarantee matrix), and every remaining occurrence in doc prose and source comments was rewritten to the precise wording; string literals are deliberately out of scope (tool descriptions enter the toolset hash).

1.99.0

Minor Changes

  • 9e00888: Runtime floors for evidence and acceptance (RV507), all additive and opt in; defaults change nothing. evidenceContract.enforce: 'refuse' makes the declared floor binding at the child's terminal: an ok finish whose transcript carries fewer successful record_evidence executions (the tool's own recorded: true; duplicates and failed verifications never count) than minEntries becomes a typed terminal error whose journaled data carries the machine-readable evidenceFloor: { recordedEntries, minEntries }, memoized so a resume rolls the refusal forward instead of re-paying (the default 'warn' keeps the historical preflight-only signal). evidencePreservedValidator({ requireNonEmptyPool: true }) refuses the empty known citation pool with an empty child citation pool reason instead of the vacuous pass. OrchestrateAcceptance.minSpawnedChildren: N rejects a finish whose spawned roster is smaller than N under both child policies (zero spawned children stop being vacuously complete for a fan-out-shaped task), with the actual roster carried beside the floor in the journaled decision and the rejection's error data.

1.98.0

1.97.0

Minor Changes

  • 5c3b453: Per-request cost accounting and per-segment pricing pins (RV504/RV505/RV511, the ninth-experiment accounting P1s).

    RV504: when a terminal entry's per-dispatch providerCalls exactly cover its usage, costReportFromJournal and invoiceFromJournal now price each provider call individually, so a nonlinear long-context tier fires per REQUEST, which is the pricing contract's stated semantics. An aggregate that crossed a threshold no single request crossed no longer re-prices the whole entry: the ninth comparison experiment's settled report ran 52.4% above the live budget's per-dispatch debits for exactly this reason, and the two figures now converge. Entries without records, or with records that do not cover their usage, fold exactly as before (the per-model aggregate), and the invoice says so: rowUsdNonAdditive is now a computed boolean (false exactly when every contributing entry is fully attributed, so the per-call rows sum to the total; allocatedUsd remains the column that sums exactly in every case). The shared fold is public: priceEntryBilling with EntryBillingUnit/EntryBillingFold beside priceEntryUsage.

    RV505: journalPricingSnapshot now composes the run-settle pricing pins by their settle seq, with no journal shape change: a seq-aware fold prices each row under the pin of ITS OWN segment (the rates its live debits actually used), so a suspend/resume across a price-table rotation no longer re-prices settled history under the new table. Seq-less callers keep the historical last-pin behavior. priceUsd callbacks across the accounting folds accept an optional third seq argument (existing two-argument implementations are unaffected), the snapshot exposes pinnedThroughSeq, and the engine's settled-outcome cost mirror composes pinned history with the live table for the segment being settled.

    RV511: the CLI invoice text output now states the pricing basis honestly per export: additive per-request rows, or the aggregate basis with the reason (a remainder or legacy entry in the fold).

1.96.0

1.95.0

1.94.0

1.93.0

Minor Changes

  • c62150a: The mid-batch checkpoint boundary (RV408, the eighth-experiment review). Checkpoints write once per completed tool turn, so a kill inside one large parallel batch re-paid every executed call of that batch on resume; with the whole executed-call budget fitting into a single batch (the tool-cap-before-checkpoint preflight warning), the re-paid window was the entire budget. The opt-in limits.checkpointEveryToolCalls: K bounds it: after every K executed calls within a batch the loop durably writes the same pending state the ask-approval suspension already checkpoints (the executed prefix verbatim, the next call, the remaining tail), and the existing restore path reuses the prefix and re-runs at most the calls since the last boundary. Denied and refused calls never advance the cadence, the batch's last call writes no extra boundary, and isolated-executor idempotency keys are unchanged. Off by default and byte-identical when absent: no journal bytes and no model requests change, only the transcript checkpoint cadence. A cadence below the executed-call ceiling silences the tool-cap-before-checkpoint warning, whose message now names the mitigation.

1.92.0

Minor Changes

  • 351d1f5: Historically stable invoices via the applied-pricing pin (RV407, the eighth-experiment review). The invoice and cost folds price at fold time, so a live price-table update used to silently re-price history. When createEngine({ pricing }) is configured, the settling segment now pins what it actually applied, the resolved pricing row of every model the journal used plus the table's pricingVersion, additively inside the existing run-settle decision value (the outputHash precedent: no journal shape change). The pin is gated on the configured table deliberately: caps-fallback pricing arrives ambiently from adapters and a setting the user never enabled must not change the journal, so table-less runs settle byte for byte as before; rates the fold would refuse anyway, non-finite or negative, are never pinned. New journalPricingSnapshot(entries) reads the pin back and rebuilds a priceUsd over exactly the pinned rows (absent models fold as unpriced, never a silent zero); invoiceFromJournal accepts a declared provenance and the export carries pricing: { source: 'snapshot' | 'current-table', pricingVersion?, rows? }. rulvar invoice, rulvar inspect, and the server's stored-run cost endpoint prefer the pin, so a repeated fold after the table changes reproduces the original numbers; journals settled before the pin keep the current-table fold and say so. Live pricing, budget admission, and journaled spend debits are untouched.

1.91.0

1.90.0

Minor Changes

  • 9603940: Scope the isolated-executor idempotency key to the run incarnation (RV403, the eighth-experiment review). A fresh run stamps the additive optional RunMeta.execKeyDerivation field (version 2) at genesis and every resume segment carries it verbatim; version 2 keys bind the run's generation token, so a deleteRun-then-recreate of the same explicit runId never reuses the deleted incarnation's keys against a long-lived external dedup store, while a crash-and-resume redispatch inside one incarnation keeps its key exactly as before. Runs recorded without the stamp derive the original genesis-free version 1 keys for their whole life, across resume and upgrade, so external dedup state accumulated for them stays valid; a recorded derivation the engine does not know, or a version 2 stamp whose store dropped the genesis token, is a typed resume refusal when executors are configured, never a silent fallback. The store conformance kit now checks the field's round trip alongside genesis.

1.89.0

Minor Changes

  • f18b671: Provider-id provenance parity across every adapter path (RV401, the eighth comparison experiment). The AI SDK bridge now ships the flat responseId the core reconciliation record reads, beside the nested response object it always emitted, and an error finish carries the accumulated response metadata and warnings on the error event instead of dropping them (retained parts stay deliberately absent there: a failed turn is discarded, never re-injected). The core agent loop captures provider metadata from error events and falls back to the AI SDK's nested response.id shape when a third-party adapter ships only that, with the flat first-class form winning when both are present. The OpenAI adapter attaches the failed response's id to its response.failed error event, so a billed failure reconciles against the provider statement exactly like an ok row. End-to-end tests pin a bridged engine run whose per-call reconciliation records carry ids on the success, retry, and billed-failure paths alike.
  • f18b671: The synthesis reserve lifecycle decision now journals BEFORE the finish-validation termination throw (RV402, the eighth comparison experiment): a synthesis the validators terminally reject was still paid for out of the released reserve, and the run now keeps the frozen configured/held/released/remaining/consumed record on that failure path exactly as on success, idempotently across resume. Docs drift closed alongside: the FAQ now says the subprocess and container executors ship in @rulvar/executor instead of calling them a plan, the workflow guide no longer promises deadlines on approval suspensions (escalations only, per the durability table), the server guide scopes the approved tool's "exactly once" to its continuation segment under the documented at-least-once tool window, the RunMeta.argsHash doc points at security.argsHashSalt as the salted HMAC option, and the ctx dispatch comment names the full five-part idempotency key.

1.88.0

Minor Changes

  • 3b339d9: The evidence floor, the exact-fill parity proof, and the direct container e12 (PR III of the seventh-comparison-experiment plan: RV303, RV307, RV308, and the recommended tool budget posture).

    RV303, the declared evidence contract: AgentProfile.evidenceContract and PreflightSpawnSpec.evidenceContract ({ minEntries, estCallsPerEntry?, overheadCalls? }, the spawn declaration winning over the profile's, researchAgentProfile passing it through) declare how many evidence entries a spawn MUST record. Preflight compares the resulting call floor (minEntries * estCallsPerEntry + overheadCalls, defaults 3 and 8, exported as DEFAULT_EVIDENCE_CALLS_PER_ENTRY and DEFAULT_EVIDENCE_OVERHEAD_CALLS) against the spawn's effective executed-call ceiling (weighted units and extension grants included) and warns tool-cap-below-evidence-floor when the cap cannot fit the contract. Purely declarative, validated typed at both intake boundaries; the runtime never enforces it. The experiment relation nobody computed: 14 mandatory entries against a cap two workers exhausted at 10.

    RV307, the exact-fill parity proof (the judge's P1.8): a scenario suite pinning that the strict-at-fill admission projection and the live layer-2 gate deny THE SAME child for THE SAME reason on one set of numbers, at the exact-fill boundary specifically, while the below-fill retry admits in both layers, riding the existing slot-ledger guarantees (a rejection burns no maxSpawns slot; resume recounts journaled admits only).

    RV308, the direct container e12 (the judge's P1.9): the container executor now carries a DIRECT conformance test for the protocol-failure-at-clean-exit-0 case (typed protocol error, ledger outcome error with exitCode: 0), both against the daemonless docker stub (runs everywhere) and against the real daemon (docker-gated), instead of relying on source symmetry with the subprocess executor.

    Docs: the new "The recommended tool budget posture" section in the agents guide (default no cap: the USD ceiling plus exploration guards bound spend; a cap is a safety valve, never bare, always with notices, an extension, a reserve or window, and a deliberate salvage decision; the full findings table), cross-linked from the budgets guide findings enumeration.

1.87.0

Minor Changes

  • c4c02b1: The finalization window, the bare-cap linter, and the synthesis reserve lifecycle (PR II of the seventh-comparison-experiment plan: RV302, RV305, RV306, and the deferred half of RV304).

    RV302, limits.finalizationWindow: { reserveCalls, allow? }: once the remaining tool budget (executed calls against the effective maxToolCalls, or remaining weighted units against toolUnits.max, whichever is closer) drops to reserveCalls, only finalization tools may execute. A call outside the allowlist receives a typed refusal (guard: 'finalization-window', visible to the model, never terminal, consuming no budget), and the model is told once, via a plain user message, to record its evidence and finish. The allowlist defaults to the tools priced at toolUnits cost 0; the engine terminal tool is always admitted, and escalate is structurally exempt. With toolBudgetExtension configured, remaining money converts into a grant BEFORE any window refusal, so the two features form one policy: spend the headroom first, then finalize. On resume the window re-arms from the restored counts without re-announcing; without the field every request, journal, and cassette stays byte identical. The toolBudget pressure snapshot gains finalizationWindowEntered, and the tool:end guard union is now honest about all three engine guards (repeated-signature, per-tool-cap, finalization-window).

    RV305, the bare-cap linter: preflight warns bare-tool-cap when a positive maxToolCalls or a toolUnits budget has no softener at all (no toolBudgetNotices, no toolBudgetExtension, no finalizationReserve, no finalizationWindow); a cap of 0 is a deliberate no-tools spawn and stays quiet. Orchestrate waves with a DECLARED acceptance additionally get the info capped-children-without-salvage when capped children meet a policy with both salvage arms off. The window itself gets three findings: inert-finalization-window, finalization-window-covers-cap, finalization-window-empty-allowlist, and PreflightOrchestratorSpec gains the declarable acceptance slice.

    RV304 second half (the judge's P1.7): a configured budget.synthesisReserveUsd now reports its whole lifecycle { configuredUsd, heldUsd, releasedUsd, remainingBeforeSynthesisUsd?, consumedUsd }, frozen into a journaled decision (orchestrator_synthesis_reserve) when the synthesis invocation settles, emitted as a log info event, and attached to the acceptance result envelope as synthesisReserve. heldUsd: 0 under a configured reserve makes the silently inert no-cap case visible; a resume reads the frozen decision instead of recomputing. Without a configured reserve nothing is journaled, emitted, or attached.

    RV306: the engine-level terminal-at-exhausted-budget scenario suite (the judge's P0.3): the terminal finish dispatches after the cap through a real engine run with the journal underneath, its validator rejection travels back, the repair lands, batch neighbors get the typed skip, and the non-terminal control still journals limit.

1.86.0

Minor Changes

  • 2f71894: The adaptive tool budget and the pressure snapshot (RV301/RV304, the seventh comparison experiment). limits.toolBudgetExtension: { increment, maxExtensions, minHeadroomUsd?, requireNewEvidence? } converts remaining budget headroom into more executed tool calls at a maxToolCalls expiry instead of settling limit: up to maxExtensions grants of increment calls, each admitted only with chain headroom remaining (the same arithmetic the per-turn output clamp prices, now exposed as RunBudget.remainingUsd and the BudgetHooks.remainingUsd seam), by default only with new evidence since the previous grant (the exploration guard's digest chain; a result the canonical serialization cannot digest fails the grant closed), announced to the model as a deterministic user message with the exact new counts, and re-derived conservatively from the restored executed-call count on resume, so nothing new is journaled or checkpointed. A terminal finish never spends a grant (it already rides the v1.79 budget exemption), toolUnits is never extended, and an invocation without the field stays byte identical. The experiment that motivated it starved two of four mandatory workers at a fixed 84-call cap while $3.85 of the $10 ceiling sat unspent.

    Preflight assumes the fully extended cap in every projection (executed-call ceilings, projected provider turns, quota windows, the checkpoint loss window) and adds two findings: inert-tool-budget-extension (warning; an extension with no maxToolCalls to extend) and tool-budget-extension-exposure (info; the declared worst case).

    Every invocation with maxToolCalls, toolUnits, or the extension configured now carries the toolBudget pressure snapshot — { used, cap?, unitsUsed?, unitsMax?, extensionsGranted?, noticesFired?, finalizationReserveUsed?, limiter? } — on the full AgentResult, the live agent:end event, and the invocation table's agent rows, so a host sees cap pressure before a starved worker ever settles limit. Live telemetry only, exactly like transportRetries: never journaled, absent on a replayed result. The synthesis reserve lifecycle telemetry (the judge's P1.7) is deliberately deferred to the next cycle. Docs: the stores guide frontmatter now names PostgreSQL beside the other shipped stores, and the README states the exact never-pay-twice boundary (recorded as complete), matching the durability guide.

1.85.0

Minor Changes

  • 6932a9f: Three fail-closed fixes from the cycle 83 sweep, plus the dependency refresh.

    Engine. A typed error thrown out of ProviderAdapter.stream() now keeps its own class instead of being laundered into a retryable transport fault. A ConfigError (a bridged model id that does not match the wrapped model, an unsupported role, a namespaced option contradicting a canonical field) used to be retried through the whole backoff ladder and then trigger transport failover, so a misconfigured primary silently served the run from a fallback model the caller never asked for while the real fault vanished behind a generic message. Typed errors that ARE retryable by class (a lost lease) keep retrying exactly as before, and an untyped throw is still a retryable transport fault.

    Planner sandbox. The realm scrub replaced Date.now and Math.random, which left three ambient sources open: a bare new Date() never consults Date.now (V8 reads the system clock directly), performance.now() is a second live clock, and WebCrypto (crypto.randomUUID(), crypto.getRandomValues()) is raw entropy. Those are the first idioms a machine-written script reaches for, and each silently produced a run that could not reproduce on replay. All of them now draw from the same seeded stream: zero-argument new Date() and Date() take the logical clock, performance.now() is that clock minus the segment base, crypto.randomUUID() is the journaled uuid shim, and crypto.getRandomValues() fills from the seed. Passing a timestamp or a date string to Date stays a pure conversion.

    Server. A tracked run whose segment REJECTS instead of settling (the genesis ownership boot refusing a run another process owns, a withheld settlement whose durable write failed) was reported as running for the life of the process, its SSE connections never closed, and neither retention nor the settled cap could release it. GET /runs/:id now answers status: "error" with the typed wire error, connected streams close with a comment naming the failure, a late subscriber gets that comment instead of an empty stream, and the tracked run becomes eligible for retention like any other terminal run.

    Dependencies. @anthropic-ai/sdk moves to ^0.115.0 (the only shipped floor its caret was blocking); in-range minors refresh across the workspace. The four majors stay held: eslint 10 and @eslint/js 10, @types/node 26 against the Node 22.12 floor, and TypeScript 7. The tsdown resolution is pinned at 0.22.3 because it generates the frozen .d.ts artifacts, including the published @rulvar/compat tarball that must repack byte identical.

1.84.0

1.83.0

1.82.0

Patch Changes

  • 9cc5d66: The free-cleanup harvest (cycle 80). leasableStoreConformance gains the expiry option: the mandatory lease checks follow the suite's no-wall-clock convention, so the harness now hands them a store whose ttl no scheduler stall can cross, and only the wall-clock expiry check keeps a short-ttl store of its own; the legacy single-ttlMs pairing let one CI stall past 150 ms expire a just-acquired lease inside a fencing check (the flake observed on Node 22). All three shipped harnesses move to the split pairing, and the store-authors guide stops recommending the flaky shape. In @rulvar/cli, worker retention is no longer slot-bound: a worker whose every concurrency slot is busy still applies retention over settled runs during its sweeps instead of starving until idle. In @rulvar/core, concurrent cold tools() calls on an MCP source share one in-flight tools/list fetch instead of each sweeping the list, and AdmissionController's maxTotalSpawns TSDoc now tells the truth: it is the controller-lifetime cap on admitted spawns for hosts driving the controller directly (pinned by a test), while engine runs cap totals through budgetDefaults.lifetimeSpawnCap; the old comment claimed it was the per-orchestrate maxSpawns.

1.81.2

Patch Changes

  • 296885b: Three defects from a deep review of the MCP bus and the queue worker (cycle 79). In @rulvar/cli, createWorker().stop() now waits out a sweep that is still scanning the store before taking its cancel snapshot, and a sweep observes the stop before every lease: previously a stop() racing an in-flight sweep could resolve while that sweep went on to lease and drive a new run, leaving a live run and a held lease behind a "stopped" worker. In @rulvar/core, the MCP tool source no longer loses a listChanged notification that races the in-flight tools/list fetch (the fetched list is served but never pinned as the session cache, so the next snapshot refetches), and cursor pagination treats an empty nextCursor as exhaustion instead of spinning the import loop forever on a server that echoes it. A regression test also pins the SDK-level rejection of a declared outputSchema with no structuredContent, guarding the planned SDK v2 migration.

1.81.1

Patch Changes

  • c030982: The side-effect ledger records the outcome a dispatch actually had: a tool whose stdout violates the result protocol (non-JSON output from a clean exit) now ledgers error instead of ok, in both the subprocess and container executors, and the executor conformance kit pins it as check e12. In @rulvar/core, stripFencedBlocks closes fences in CRLF text (a trailing carriage return no longer keeps a fence open and swallows the rest of the document), which fencedCode: 'excluded' validators and headingStructureValidator inherit. Docs drift closed alongside: the package count, tables, and dependency graphs catch up to @rulvar/executor and @rulvar/store-postgres, the durability page reflects the shipped data protection hooks instead of denying them, and the architecture page no longer claims only the in-process executor exists.

1.81.0

Minor Changes

  • ce4c392: The sixth comparison experiment's P2 harvest (cycle 77). maxSpawns now counts ADMITTED children instead of attempt ordinals: an admission-rejected spawn (budget, quota, depth) consumes no slot, so the orchestrator can retry a rejected mandated role at a viable budget instead of losing it to orchestrate maxSpawns N reached (the rematch's run 2 shape); recovery rebuilds the same ledger from journaled admits, and attempt volume stays bounded by the coordination turn's tool budget. New stock validator headingStructureValidator({ sections, ordered, exclusive }): the markdown headings of one level (derived from the shared marker) held to the declared set, in declaration order, each exactly once and none undeclared, fenced code always stripped first (the judge's P1.3: line presence proves existence, not structure). The near-JSON finish recovery is durable (the judge's P1.5): AgentResult.schemaRecoveredTerminalExchanges counts the terminal exchanges the unparsed second chance salvaged (a live process counter like transportRetries, absent when zero), and orchestrations fold both windows into schemaRecoveredFinishExchanges on the acceptance ok envelope and the typed failure data, beside the rejected twin.

1.80.0

Minor Changes

  • 262e397: The synthesis budget reserve and the strict admission projection (the sixth comparison experiment, cycle 76). The opt-in budget.synthesisReserveUsd holds absolute dollars out of the orchestrator sub-account while the coordination loop runs: spawn admission and the per-turn output clamp treat the hold as spent (the severing check does not, so a coordination running against the hold is clamped smaller, never aborted), and the hold is released to the synthesis invocation just before it dispatches. Without it the rematch's first run lost a full paid run: the default 0.2 sub-account funded the coordination prefix and the budget clamp shrank the synthesis turns below the contract's minimal accepting payload, so the finish was cut at its output allowance before any tool call and the validator-bound run failed closed at maxTurns. The reserve requires the synthesis option (single mode), must stay below the effective cap (OrchestratorCapConfigError otherwise), and nets out of the capped orchestrator's exact-fill admission hint exactly like the finalize carve-out. preflightEstimate prices the contract's minimal accepting payload at the synthesis model's output rate and reports the warning synthesis-reserve-unfunded when a contract binds the synthesis and the hold is missing or too small.

    The admission projection is now STRICT at exact fill for the children of an orchestrate wave: the coordination turn that issues the spawn tools is paid before any spawn executes, so a child whose reserve fits only at exact fill is certain to be rejected live, and the projection now says so (partial-admission) instead of promising the full wave. The rematch's second run lost its mandated fourth specialist to exactly that promise: the estimator projected 5 of 5 admitted while the live gate rejected the fourth spawn with reason budget. The orchestrator's own row keeps its exact-fill admission (it admits at run start, before any spend exists), and plain waves are unchanged. Absent the new option every budget account, journal, prompt, and cassette stays byte identical.

1.79.0

Minor Changes

  • 85956ab: Terminal admission at an exhausted tool budget, the two harness-shape preflight findings, and the degradation mirror (the fifth comparison experiment).

    The fifth experiment lost a complete 3984 word answer to terminal tool starvation: the harness set the synthesis tool cap to the child count, the mandatory get_child_result reads spent the whole budget, and the ready finish was cut BEFORE the terminal interception, so the validators never ran, the funded repair reserve never armed, and the run failed closed with the candidate stranded in the transcript.

    • The terminal tool is now exempt from the tool budget in both directions: it never consumed maxToolCalls or toolUnits below the cap, and an exhausted budget no longer starves it either. An admitted finish validates and, on rejection, feeds the repair grants exactly as below the cap; non-terminal calls beside it are answered with typed skipped results so the continued exchange keeps a well formed history; a batch with only non-terminal calls past the cap settles limit byte identically to before.
    • New preflight warning synthesis-terminal-tool-headroom: synthesis.exposeChildResultTools with a synthesis.limits.maxToolCalls below one read per possible child (orchestrator.maxSpawns) loses evidence access to the reads themselves.
    • New preflight warning draft-gate-below-contract: a draftPolicy.minWords below the contract's own word minimum admits drafts the final validators must reject, so the paid synthesis starts from an underlength base. The preflight input mirrors finishValidation.draftPolicy for it.
    • The completion lift now mirrors the degradation facts the acceptance envelope already emits: degradedReasons, salvagedPartialChildren, and salvagedTerminalOutputChildren ride run:end and the RunOutcome under the same shape validation as completion and childStatusCounts, and the OTel exporter maps them to rulvar.run.* attributes. An empty array is the workflow's claim of zero degradation; absence means no claim.

1.78.0

Minor Changes

  • 941b6e1: Contract exactness (the v1.74 experiment review, cycle 74, the last fourth-report slice). The finishContract bundle is now DEEPLY frozen: the nested manifest objects, the sections array, the validators array, and each validator object, so a post-construction mutation throws a TypeError instead of silently diverging enforcement from the journaled contract hash (on 1.77.0, pushing into manifest.sections changed the live validator through a shared array reference while hash kept claiming the original manifest). The contract now carries one reject golden PER validator (goldenRejects), each proven at construction, and both the orchestrate construction self test and preflightEstimate hold the CONFIGURED validator of each name against its golden: a same-name replacement weaker than the contract's own validator (a words minimum of one standing in for fifty, which on 1.77.0 passed the single shared reject fixture on the strength of an unrelated validator and let an end-to-end run accept a five-word result against a words.min: 50 contract) is now a ConfigError at construction and the new error finding output-contract-validator-weakened in preflight; selfTestFinishValidation accepts the goldens via the new rejects option. Two manifest knobs sharpen matching: sectionsMatch: 'line' demands each section marker as its own line (a mid-sentence mention or a marker echoed inside a code fence no longer satisfies a heading), and fencedCode: 'excluded' removes fenced code blocks (the exported stripFencedBlocks grammar) before section matching, per-section slicing, word counting, and citation matching, so code samples can neither pad words.min nor donate citations, and a fenced marker occurrence can no longer mis-anchor a section's citation slice. Both knobs default to the historical behavior, normalize away at their defaults, join the hash and the prompt statement only when non-default, and exist on the standalone validators too (match on requiredSectionsValidator and sectionCitationsValidator, fencedCode on those plus wordCountValidator and minMatchesValidator). Absent knobs and untouched bundles keep every existing configuration byte-identical: prompts, hashes, journals, and validator verdicts.

1.77.0

Minor Changes

  • 6aba271: The v1.74 experiment review, cycle 73: contract turn feasibility in preflight, contract generation scoping, and error-outcome parity.

    Preflight now proves a conforming answer can physically fit one finish turn of the invocation the validators bind: the contract's minimal accepting payload priced at the loop's four characters per token heuristic against the effective output bound is the error finding output-contract-turn-infeasible when it cannot fit and the warning output-contract-turn-headroom when the margin is under double; validators with repairs possible but no repairTurnReserve draw the warning repair-reserve-unfunded, and the preflight finishValidation input mirrors maxRepairs.

    The fix-and-resume remedy is generation scoped: finish-validation decisions written under a contract carry contractHash, repairsUsed counts only the current generation, and a final rejection a superseded generation left in the crash window neither rolls forward at boot nor re-arms on replay (the stale exchange replays byte identical and the loop continues into a live repair turn). Pre 1.77 decisions carry no hash and bind to the current contract only while the journal holds a single bundle descriptor.

    Typed finish failures now mirror the full acceptance snapshot (degradedReasons and the salvage lists beside completion and childStatusCounts) and count the invisible exchange class: AgentResult.schemaRejectedTerminalExchanges reports the terminal exchanges that died at the schema gate (window derived, absent when zero), and orchestrate folds the coordination and synthesis windows into schemaRejectedFinishExchanges on the failure data. Absent options and contractless configurations keep byte-identical journals, prompts, and cassettes.

1.76.0

Minor Changes

  • 22cba47: Synthesis evidence symmetry and the coordination draft gate (the v1.74 comparison review, P0.2 + P0.3). The finish validators judge the synthesis result against the FULL child outputs while the synthesis model saw only the draft and 400 char digest rows on a finish-only toolset; when the v1.74 experiment's draft collapsed to 'test', preserving the demanded 66 citations was model-impossible and the run ended answerless. Three opt-ins, each byte identical when unset: synthesis.exposeChildResultTools gives the synthesis invocation the RV-201 read tools get_child_result and read_child_artifact (the digest rows then carry each child's handle); synthesis.context: 'full' embeds a CHILD OUTPUTS section with every settled child's full serialized output beside the digests; finishValidation.draftPolicy (minWords, requireSections) rejects a schema-valid but collapsed coordination draft as the call's error result BEFORE any paid synthesis dispatch, with deterministic library checks that journal nothing and the same repairTurnReserve headroom the synthesis finish gets. preflightEstimate reports the asymmetric shape as the new warning finding synthesis-evidence-asymmetry, and the preflight synthesis input mirrors the two new fields.

1.75.1

Patch Changes

  • 82bc0f0: The unparsed-arguments second chance now covers the terminal tool (the v1.74 experiment review, P1.5 completion). The terminal tool validates its arguments at its own interception site, so 1.75.0 recovered regular tools only while the experiment's actual casualty was the coordination finish. Both sites now share one validation path: a near-JSON finish payload recovers deterministically and ends the loop in one turn with the recovered result; truncations and imitated wrappers keep the exact old error result.

1.75.0

Minor Changes

  • c486de8: The provider output floor and the finish arguments second chance (the v1.74 comparison review, P0.1 + P1.5). ModelCaps.minOutputTokensPerTurn declares the smallest request output cap the provider accepts (OpenAI Responses: 16; absent means one), and the layer-2b budget clamp never dispatches below it: the last-gasp turn goes out AT the floor instead of one token, a remainder that cannot buy the floor is refused as a typed BudgetExhaustedError with zero wire calls, and a configured per-turn cap below the floor is a ConfigError; preflightEstimate reports that configuration as the error finding output-cap-below-provider-minimum. Tool arguments an adapter delivered as the parse-failure wrapper {__unparsed: raw} now get one deterministic second chance before the schema rejection: a strict re-parse, then one bounded normalization (markdown fence, first balanced object, raw control characters escaped inside string literals); a recovered object that passes the tool schema executes as if it had parsed on the wire, with a warn log naming the pass, and replay or resume recovers identically with nothing journaled. The OpenAI wire re-projects an unparseable call as the ORIGINAL raw arguments string instead of the wrapper JSON, so a model no longer learns to imitate {"__unparsed": ...} from its own rewritten history. Both wires drop unsafe-integer x-ratelimit values instead of normalizing 400 digits into Infinity. FakeAdapter gains capsOverrides so offline tests can drive caps-declared behavior like the floor.

1.74.0

Minor Changes

  • d94beab: Quota drift telemetry and the honest zero (the v1.71 experiment review, P0.5 resized + P1.4). The experiment declared 12M TPM over a provider-real 1M, the local limiter went quiet, and seven live 429s followed with nothing recording the mismatch. Now: both wire adapters parse the provider's x-ratelimit headers on every real 429 into normalized per-minute limits (WireError.data.reportedLimits; the openai wire also gains the raw bucket capture the anthropic wire already had), the loop remembers them per (provider, model) as live telemetry, and the opt-in quota.declaredRules (the SAME rule array preflight takes) makes the engine journal a quota_drift decision plus a warn log whenever a binding declared cap EXCEEDS the provider-reported one, per invocation and dimension, with anthropic's split input and output windows summed against a combined declared tokensPerMinute. Purely observational, synthetic limiter denials never count, and without declaredRules journals and events stay byte identical. On the invoice, an unconfirmed row that recorded zero usage on every counter now carries usageUnknown: true (export-level usageUnknownRows count, CLI usage-unknown marker): the zeros mean "nothing recorded", never "the provider metered nothing"; derived at export time, no journal shape change.

1.73.0

Minor Changes

  • 3e95bd1: The synthesis repair envelope (the v1.71 experiment review, P0.4/P0.8/P1.7): finishValidation.repairTurnReserve grants bounded EXTRA turns to the invocation the validators bind, one per rejected finish exchange (schema-invalid finish arguments and host validation rejections alike), derived from the message window itself so resumes recount identically and nothing new journals; the deliberately-deferred RV-204 reserve, now that the experiment showed one malformed finish plus one validator rejection killing a whole run inside maxTurns 3. Every typed synthesis failure now carries the acceptance snapshot (completion, childStatusCounts, lifted onto the error outcome by the completion mirror, so an errored run still reports "the fan-out work is complete") and the verdict-derived repair taxonomy (repairsUsed, maxRepairs, rejectedValidators) read from journaled decisions. preflightEstimate models the separate synthesis invocation (orchestrator.synthesis: limits, model, estInputTokens; echoed at budget.orchestrator.synthesis, priced into exposure.runCeiling, the gap the experiment's projection stopped short of) and folds a declared finishValidation.repairTurnReserve into the projected turns of the bound invocation; the CLI prints the synthesis projection line. Zero reserve and no synthesis declaration keep every ceiling, journal, and report byte identical.

1.72.0

Minor Changes

  • 662e9e0: The unified output contract (the v1.71 experiment review, P0.1/P0.2/P0.3/P1.1): finishContract(manifest) generates the prompt statement, the stock validator set, a stable sha256 hash, and golden self-test fixtures from ONE immutable manifest, so the prompt a model follows and the validators a host enforces cannot drift apart by construction. finishValidation.contract wires it into orchestrate: the construction-time golden self test fails a stale validator as a ConfigError BEFORE any provider call (the experiment burned a full paid run on three renamed section headings), the contract statement is injected into the coordination and synthesis prompts, every contract validator must be present in the configured set by name, and the run journals a frozen bundle descriptor (orchestrator_finish_validation_bundle) with supersession on resume under a fixed contract. preflightEstimate accepts the same declaration and reports drift as the error finding output-contract-validator-mismatch with a finishValidation echo block. Two new stock validators: wordCountValidator (formal length bounds as code) and sectionCitationsValidator (per-section citation coverage, because a total count hides sections with zero provenance). Absent contract and selfTest, every existing configuration keeps byte-identical prompts, journals, and reports.

1.71.0

Minor Changes

  • 20d02e0: The preflight quota planner follows the run past the first wave (the second experiment report, rec 9). Every declared spawn now reports projectedProviderTurns, the provider-call ceiling of its whole loop (maxTurns bounded by the executed-call ceiling plus the final no-tool turn, plus the finalization summary turn when a tool budget limiter arms it), and the orchestrator echoes its own. exposure.runCeiling totals the declared wave run to those ceilings at the declared estimates: provider calls as fan-out times per-spawn turns, and cumulative tokens with the context regrowing every turn (turn k re-sends the declared prompt plus the k-1 prior output bounds, so a K-turn loop costs K x est + outputBound x K(K+1)/2). Three findings compare that projection against the declared quotaRules when the first-wave checks stay silent: quota-requests-below-run (the loops project more wire requests than requestsPerMinute admits; the message names about how many windows the run needs at best), quota-tokens-below-run (the regrowth cumulative exceeds tokensPerMinute), and the spawn-attributed quota-turn-never-fits (by turn k the single context-grown reservation exceeds the whole token window, which the limiter denies with retryAfterMs 0 and no wait helps). The first-wave checks are byte-identical, and a run whose ceiling fits its windows produces exactly the findings it did before. rulvar preflight prints the new turn ceiling per spawn and the run ceiling on the exposure line; --json carries the fields verbatim. The experiment run behind the recommendation had zero preflight quota findings and eleven live limiter denials; this projection is what would have said so before the first dispatch.

1.70.1

1.70.0

1.69.0

Minor Changes

  • b21a681: The tool-cap-before-checkpoint preflight warning (the experiment review, recommendation P1.8). The runtime checkpoints once per COMPLETED tool turn, and nothing in the limits vocabulary bounds a parallel batch below the executed-call ceiling, so a worker on a parallel-tools model can consume its whole tool budget inside the first batch, before any checkpoint exists: a kill mid-batch re-pays every executed call on resume. preflightEstimate now emits the stable warning tool-cap-before-checkpoint for every declared spawn whose effective executed-call ceiling is finite and positive while the resolved model's caps report parallel tool support, with the exact ceiling named in the message. Serial models (one call per turn, a one-call loss window), uncapped spawns, and zero caps stay silent, and reports over such shapes are byte-identical to before.

1.68.0

Minor Changes

  • b227874: The machine-readable synthesis-skip reason (the experiment review, item 11.4, recommendation P1.5). A run that configures the post-fan-in synthesis invocation and never runs it used to show zero synthesize spend with no recorded cause: the artifacts of a rejected run with synthesis configured were byte-indistinguishable from a run that never configured synthesis at all, and a host had to infer the skip from the acceptance decision and the RV-211 design. Both designed skips now record the exported OrchestrateSynthesisSkipReason: the journaled decision that causes the skip freezes synthesisSkipped ('synthesis_skipped_by_acceptance' on the rejected acceptance decision, 'synthesis_skipped_by_budget_cap' on the budget-cap decision, immune to live-option drift on resume), the typed FailRunError data of the failing paths carries the same field, and an info log event (orchestrator synthesis skipped) announces it beside the zero spend, on the live pass and on every resume roll-forward alike. The field is absent when synthesis is not configured or when it actually ran, so existing runs stay byte identical.

1.67.0

Minor Changes

  • 8e6006d: The honest invoice (the experiment review, items 11.2/11.3, recommendations P1.2/P1.3/P1.4). The reconciliation verdict now names exactly what it asserts: the value matched is renamed to provider-id-present, because the library never sees provider billing data and the old term read as a statement match it cannot make (deeper reconciliation tiers are host-side joins keyed on responseId). Consumers comparing row.reconciliation === 'matched' must switch to 'provider-id-present'; reconciliationFailures keeps its meaning (rows without a provider id). InvoiceExport is now self-describing about pricing: pricingBasis: 'per-call' declares that per-row usd prices each call individually at current rates, and rowUsdNonAdditive: true warns that those values need not sum to totalUsd under a nonlinear price table (long-context tiers price a split differently from its sum). For consumers whose rows must sum, every InvoiceRow gains the additive allocatedUsd column: each (entry, serving model) slice of the same gross fold the totals run is distributed across its rows in proportion to per-row usd (token weights when every row priced to zero), one row absorbs the IEEE rounding dust, and the flat sum over rows reproduces totalUsd exactly. rulvar invoice prints the declared basis in the text form and passes the new fields through --json unchanged.

1.66.0

Minor Changes

  • 1b8987e: The RunOutcome completion mirror (the 1.65.0 experiment review, P0.5). The semantic completion lift (completion, childStatusCounts) rode ONLY the run:end telemetry event, so a host consuming handle.result had to parse the workflow-shaped value on the accepted path and dig the typed error data on the rejected one. The engine now computes the lift once and spreads the same object onto both surfaces: RunOutcome.completion and RunOutcome.childStatusCounts are present exactly when run:end carries them (an ok/exhausted run whose result value makes a valid completion claim, or an error run whose typed error data does, the orchestrator acceptance path emits both), absent otherwise, so the outcome and the event can never disagree and a replayed resume mirrors the identical fields.

1.65.0

Minor Changes

  • 0b6b859: Terminal-output salvage for limit children (the 1.64.0 experiment review, P0.4 + P1.1). A child that hits its tool budget with limits.finalizationReserve configured can end limit CARRYING a terminal output that already validated against its declared output schema; published bits discarded that paid, journaled work at every orchestrator surface. Now the digest appends final: {...} and get_child_result pages the full output unconditionally, and the new opt-in acceptance.acceptValidatedTerminalOutputOnLimit lets the completion policy count such a child as a success: the accepted envelope reports completion: 'partial' and lists the children in salvagedTerminalOutputChildren, an invalid summary keeps output: null and still rejects (validation runs before acceptance by construction), and a child carrying both an output and a progress partial salvages by its output. The finish validation input gains FinishValidationChild.salvageableOutput (set only under the option), and evidencePreservedValidator counts a marked child's citations in the cited pool, so requireKnown no longer flags the orchestrator for quoting salvaged evidence. Every configuration without the option keeps byte-identical prompts and acceptance folds.

1.64.0

Minor Changes

  • 991f9b5: Preflight and live admission share one reserve arithmetic (the 1.63.0 experiment review, P0.3).

    Published 1.63.0 drifted from the runtime in both directions for orchestrate waves. A capped orchestrator below the flat reserve made preflightEstimate emit the error-tier orchestrator-cap-below-reserve finding (exit 1 in CI) while the live run started fine, because the live dispatch admits the capped orchestrator at EXACT FILL with the effectiveCap - committedFinalizeReserve estimate hint. And the projection admitted children whose priced layer-1 arm was tiny while the live embedded layer-2 spawn gate, which never sees the priced estimate, rejected every one of them against the remainder net of the orchestrator's own hold.

    Now the two formulas are exported pure functions the live paths themselves call, and preflightEstimate calls the same two: dispatchProjectionReserveUsd (the layer-2 spawn-gate projection: the declared estimate or the flat default, clamped by the spawn's explicit budget) and orchestratorAdmissionEstCostUsd (the capped orchestrator's exact-fill dispatch hint). An orchestrate wave now mirrors the runtime's two gates per spawn in live order; a plain wave keeps the parity-proven admitSpawn mirror. New inputs: PreflightSpawnSpec.budgetUsd (the spawn param; layer-2 clamp only) and PreflightOrchestratorSpec.estInputTokens (the uncapped orchestrator's goal-prompt stand-in). Removed: the false orchestrator-cap-below-reserve error finding and the 'orchestrator-cap' deniedBy value (a tight cap is a tight loop budget, never a refused run). Three new parity tests run live orchestrations beside the projection: the capped-below-flat config, the all-children-denied wave, and the layer-2-pass-layer-1-bust spawn.

1.63.0

Minor Changes

  • 8a28aed: Durable settlement acknowledgement and the fencing-epoch tombstone (the 1.62.0 experiment review, P0.1 and P0.2).

    Settlement acknowledgement: a NON-fencing failure of either settlement write now rejects handle.result with the new typed SettlementError (code settlement, retryable; stage names the write, data carries the runId and the computed run status) instead of resolving as if nothing happened. Only a superseded segment's LeaseHeldError stays swallowed, on both writes, because the successor owns settlement. A failed run_settle append also skips the terminal meta write, so the projection can never run ahead of the journal (published 1.62.0 wrote meta ok over a journal with no settle record when the append failed). Recovery is deterministic and free: the run's work entries are already durable, engine.resume replays to the same outcome without one paid provider call and re-attempts the settlement writes (a non-empty journal with no recorded settle now re-settles on pure replay), and rulvar runs audit [--repair] reconciles offline.

    Fencing-epoch tombstone: SqliteStore and PostgresStore no longer erase the per-run epoch high-water mark on delete, so a recreate of the same explicit runId always acquires a strictly higher epoch and a zombie lease from the deleted incarnation (same runId, same stable owner identity) is rejected on every fenced surface instead of fencing green. The LeasableStore contract now states the rule, and the conformance kit enforces it with two new mandatory checks (fencing-epoch-tombstone in leasableStoreConformance, fenced-tombstone-zombie-rejected in fencedWritesConformance). The tombstone holds only the runId and a counter, never run content; the data-protection guide documents the erasure boundary.

1.62.0

Minor Changes

  • fca5fd1: Ship the preflight effective-limits estimator and effective-config linter (the experiment-review P2.2): everything the engine derives from a configuration, computed before any provider dispatch, machine readable, with zero paid requests by construction.

    Core exports preflightEstimate(input): a pure function over the same options createEngine and engine.run receive plus a declared spawn wave, returning the JSON-serializable PreflightReport. The estimate cannot drift from the engine because it reuses the runtime's own arithmetic: mergeUsageLimits for the effective per-spawn limit merge (call over profile over engine defaults), admissionReserveUsd for the layer-1 reserve formula arm for arm (estCost, profile estCost, the priced estimate from estInputTokens, the flat default, and the unpriced-model zero), the settlement price resolution, and the shared-quota dimension match. The report carries the admission projection over the declared wave mirroring admitSpawn exactly (which spawns admit, which are denied and by what: budget, spawn cap, orchestrator maxSpawns, or an orchestrator cap its own reserve cannot fit), the per-tool and weighted-unit executed-call ceilings with the first bottleneck named, the orchestrator effective cap and finalize reserve echo, the concurrency and per-provider exposure floors with the one-more-turn overshoot floor, and the linter findings with stable kebab-case codes (errors: unrouted-role, unknown-profile, nothing-admitted, orchestrator-cap-below-reserve; warnings: partial-admission, weighted-units-bind-first, tool-unaffordable, unpriced-under-ceiling, inert-finalization-reserve, inert-tool-budget-notices, orchestrator-cap-fraction-bound, the quota-window comparisons; infos: overshoot-exposure, no-usd-ceiling, no-quota, per-tool-cap-unreachable).

    The CLI gains rulvar preflight <file|name> [--budget-usd N] [--profile NAME] [--spawns JSON] [--json]: it assembles exactly the options rulvar run would (config, module exports, run profile) but constructs no engine, opens no store, and dispatches nothing. The declared wave comes from the new preflight export of the config or workflow module ({ spawns?, orchestrator?, quotaRules? }), --spawns overrides it, --json emits the machine-readable report, and the exit code is the linter contract: 1 when any finding has severity error.

1.61.0

Minor Changes

  • b4c1f1f: Durable provider reconciliation (the experiment-review P1.3): every live provider dispatch now mints a ProviderCallRecord on the terminal entry's providerCalls ledger, the CostReport splits gross from net, and invoiceFromJournal plus rulvar invoice export the rows.

    • The per-dispatch ledger. Every wire call the engine actually makes, successful or not, records { ordinal, role, servedBy, attempt, outcome, responseId?, usage, usageApprox?, errorCode?, aborted? }, minted at the single dispatch chokepoint from the same sanitized usage the phase slices accumulate. Failed and retried attempts keep their billed usage attributable instead of dissolving into the aggregate; quota denials and abort short circuits that never reached the adapter mint nothing. The provider responseId both shipped adapters already surface on every finish is now persisted. The ledger rides every checkpoint boundary (kill-and-resume keeps pre-kill calls attributable, ordinals continuing) and restores verbatim on replay with zero live calls.
    • Gross versus net. CostReport.totalUsd stays the net ledger it always was (abandoned subtrees contribute zero). New required fields make the provider's view first class: grossUsd (net plus abandoned, the figure an invoice reconciles against; abandoning a branch never shrinks it) and abandoned: { usd, unpriced, usageApprox? }. rulvar inspect prints the gross line whenever a run abandoned paid work.
    • The invoice export. invoiceFromJournal(entries, priceUsd) returns one row per billable call with a reconciliation verdict per row: matched (response id present), missing-provider-id (a finished call without one), unconfirmed (a failed or severed call without one), unattributed (pre-ledger entries and restored remainders; the spend surfaces instead of vanishing). Totals are the same slice fold the CostReport runs, so totalUsd === CostReport.grossUsd exactly. rulvar invoice <runId> [--json] is the CLI form.

    The frozen cassette catalog is re-recorded for the additive providerCalls field on terminal agent entries (journal-shape-revision, policy not identity: no hashVersion change, no matching impact).

1.60.0

Minor Changes

  • 59bbeaa: The finalization reserve (the experiment-review P1.1): limits.finalizationReserve guarantees the model one bounded summary turn when a tool budget expires, so a research agent that pays for its evidence no longer dies mid-batch without its final report.

    Before this, a maxToolCalls or toolUnits expiry inside a tool batch dropped the batch tail silently (dangling tool calls without results in the transcript), settled limit before any further model turn, and named no limiter on the terminal. With the reserve configured (an object; {} enables it):

    • The batch tail closes explicitly: every call the budget did not admit gets a typed error tool result { error: 'skipped: the tool budget is exhausted; the call was not executed', limiter, skipped: true }, keeping the transcript well formed and the skipped calls visible to the model and to transcript readers.
    • The model always gets ONE summary turn on the loop chain (failover, retry policy, quota, and the budget all apply; usage is attributed to the loop role) with tools withheld and a request-only instruction naming the limiter, its counts, and the skipped calls. finalizationReserve.maxOutputTokens bounds this turn alone.
    • The limit terminal names the exact limiter: error: { kind: 'terminal' } with an errorMessage such as tool budget exhausted: maxToolCalls (72/72); skipped tool calls: 3.
    • The summary becomes the limit result's output (typed when a ridden schema parses it; one attempt, no re-prompt), the terminal journals the value, and a replayed result restores the same output with zero live calls. The structured terminal partial from report_progress still derives beside it.

    The reserve fires only for the two tool-budget limiters, never for maxTurns, timeoutMs, or the exploration aborts. A transport failure on the summary turn keeps the earned limit terminal with a log warning; host cancellation and the budget ceiling keep their own semantics. Without the field every byte stays as before, exactly like the other opt-in limits.

1.59.4

Patch Changes

  • c49d7a1: The genesis ownership protocol (P0.2): over a leasable journal store, every execution segment now holds the run's lease while it drives. A fresh engine.run and an in-process engine.resume that were not handed a lease acquire their own before their first durable write, renew it at a third of the store TTL exactly like a queue worker, and release it at settle; a second driver (a worker sweep adopting a live fresh run, a double resume from another process, a simultaneous genesis of one explicit runId) rejects at its own boot with the typed LeaseHeldError, before any journal write, meta write, or provider dispatch. Previously a fresh run held no lease at all, so a worker sweep on the same store adopted the live run, redispatched its in-flight provider turn (double spend), raced the journal from a stale tail, and could overwrite the settled meta with a stale error status. RunOptions.lease now exists as the genesis twin of ResumeOptions.lease for hosts that acquire at admission time and keep the lifecycle; createEngine({ ownership: 'none' }) opts an engine out of automatic acquisition; dry-run previews never acquire. Journals stay byte-identical: leases live beside the journal and never enter run identity. The serialization wrapper now also forwards the store's leaseTtlMs, so the renew cadence over an encrypted store follows the configured expiry.

1.59.3

Patch Changes

  • deaef36: Bind the isolated-executor idempotency key to the logical invocation, not just the arguments (v1.59.x review P0.4). The key was sha256(runId, tool, args), so two intentionally separate out-of-process tool calls in one run with byte-identical arguments received the same key, and an external system deduplicating on it would silently drop the second intended effect. The key now folds in the containing agent entry's journal seq and the call's ordinal within that agent's tool loop; both are journal- and checkpoint-stable, so distinct calls (different ordinals, or different agents) never collide, while an at-least-once crash-resume of the same logical call reuses the same agent entry and the restored ordinal and therefore the same key. deriveExecIdempotencyKey and the internal ToolRuntime.executeExternal gain the invocation parameters; the key never enters run identity (no content key or toolset hash), so journals stay byte-identical.

1.59.2

Patch Changes

  • dd0e10f: Bind envelope-encrypted journal ciphertext to the full entry identity (RV-217 follow-up from the external experiment review). The v1 associated data covered only seq and key, so a ciphertext could be transplanted between two runs of the same tenant wherever (seq, key) matched, and a stored entry's clear identity fields (status, scope, ordinal, kind) could be rewritten on disk without failing authentication. The new v2 envelope schema authenticates over the runId plus every immutable clear field (hashVersion, seq, ref, scope, key, ordinal, kind, status); a transplant into another run or entry, or a rewritten clear field, now fails typed instead of decrypting. The journal serialization hook gains an optional JournalSerializationContext carrying the runId (the wrapping store always supplies it; a host hook written against the original single-argument shape stays valid). Writes always emit v2; pre-upgrade v1 envelopes still decrypt on read, so an encrypting store upgrades in place with no migration step. Transcript blobs were already ref-bound (the ref embeds the runId) and are unchanged.

1.59.1

Patch Changes

  • c127770: Two fixes from the v1.59.0 external experiment review. CostReport.byRole.synthesize folded to NaN in the journal cost report and in settled run outcomes because the role-bucket initializer predated the synthesize role; the initializer is now an uncast exhaustive literal, so a future role that misses it is a compile error instead of a NaN bucket. The engine's own retry jitter defaulted to the live Math.random, which the bare-nondeterminism detector classified as workflow provenance when rulvar is imported from a checkout build rather than node_modules; the default retry rng is now bound at module load, the same convention as the engine clock, so engine-internal retries never emit RULVAR_BARE_MATH_RANDOM or fail a run under determinism.mode: 'error'.

1.59.0

Minor Changes

  • 615dc90: RV-216: the isolated tool executor, the last open item in the improvement plan. In-process tools are ordinary function calls with full host capabilities (an execution convenience, never a sandbox for hostile or model-generated code); this release adds an official out-of-process executor contract so a tool whose input is untrusted cannot reach host capabilities. (1) THE SEAM in @rulvar/core: a ToolExecutorProvider SPI, registered on the engine as createEngine({ executors: { subprocess, container } }). A tool declaring executor: 'subprocess' or 'container' (previously a hard "only inprocess in v1" rejection) dispatches through the matching provider instead of running its execute closure; an unregistered tag is a typed ConfigError at spawn time, before any provider or model call. The dispatch mints the tool span exactly like an inprocess call and derives a stable idempotency key (a pure function of runId, tool name, and canonical args) so a side-effecting tool can fold an at-least-once retry into effectively-once; the tag never enters toolsetHash, so opting a tool into isolation does not change run identity, and inprocess dispatch stays byte-identical. (2) THE REFERENCE ADAPTERS in the new @rulvar/executor package: subprocessExecutor runs the tool in a child process with a REPLACED environment (host credentials scrubbed; the usual exfiltration path removed), a fresh ephemeral working directory per call, per-call short-lived credentials, a hard timeout that escalates SIGTERM to SIGKILL, and a bounded output capture, plus a sandbox launcher hook where bwrap/firejail/sandbox-exec plug in for filesystem and network isolation; containerExecutor runs it in a one-shot container with the network dropped (--network none), the root filesystem read-only, memory/CPU/pid caps, and all Linux capabilities dropped, which is where the strong isolation the subprocess adapter cannot promise on its own actually holds (a microVM adapter implements the same seam). subprocessTool defines a tool that dispatches through them; a ToolEffectLedger records every dispatch (idempotency key, tool, argsHash, workdir, outcome) so a host can bind an approval to the effect it authorized. (3) THE CONFORMANCE KIT: executorConformance is the executable shared-contract battery any command-based executor must pass, foremost the gate the epic exists for, a hostile tool cannot read the host's ambient credentials; the subprocess reference passes all of it, and the container reference additionally proves the network and filesystem isolation against a real runtime. New guide page: https://docs.rulvar.com/guide/isolated-executor.

1.58.0

Minor Changes

  • 4fa35ce: RV-217: data protection hooks, the full close. The plan's gate ("PII never persists or emits in plaintext under policy") now holds end to end. (1) ENVELOPE ENCRYPTION on the serialization seam: createEnvelopeEncryption({provider, historicalWrappedKeys?, plaintextReads?}) returns a SerializationHook that AES-256-GCM encrypts every persisted byte (journal payloads, transcript blobs, checkpoints) with entry identity as associated data (a ciphertext moved between entries or refs fails authentication), keeping only the kernel-pinned ordering/identity fields plus spanId and timestamps plaintext; DataKeyProvider is the KMS seam (the exact shape of GenerateDataKey/Decrypt, called only in the async factory so the sync hooks run on in-memory data keys, and every envelope carries its wrapped key so reads need no live KMS); the shipped localKeyProvider derives KEKs via HKDF-SHA256 with an info partition for tenant-scoped keys (a different tenant's provider cannot unwrap, pinned by tests); reads of non-enveloped data fail closed by default with plaintextReads: 'passthrough' as the explicit migration mode; fromStored(toStored(e)) reproduces entries exactly, so replay, resume, and recovery are untouched and a run over real files greps to ZERO plaintext PII while Engine.stores reads plaintext through the one policy point. (2) REDACTION POLICY: redaction.patterns adds host-defined patterns (RegExp or strings, compiled once, typed ConfigError on an invalid one) on top of the default credential set for every emitted event, via the new exported compileSecretMasker; the OTel exporter accepts the same patterns for trace parity. (3) EXPORT/IMPORT: engine.exportRun(runId) produces the portable bundle (meta, entries, blobs) read through the policy point, so encrypted deployments export plaintext for subject-access requests; engine.importRun(bundle) writes through the target's stores (re-encrypting under its policy), keeps the original runId, and refuses an existing run typed; together with the existing deleteRun/pruneRun this completes the retention/deletion/export surface. (4) SALTED METADATA DIGESTS: security.argsHashSalt switches RunMeta.argsHash to HMAC-SHA256 under a deployment salt (equal args stop correlating across deployments; low-entropy args stop being recoverable from the digest), hashRunArgs gains the optional salt, and the CLI resume args gate picks the salt up from engineOptions.security automatically. (5) AUDIT TRAIL: reduceAuditTrail(entries) folds a journal into the typed, ordered sequence of authority events (suspensions with deadlines, resolutions with who and what, abandons with reasons, engine decisions, termination denials, run settles), tolerant across journal vintages. New guide page: https://docs.rulvar.com/guide/data-protection.

1.57.0

Patch Changes

  • 5897232: Two follow-ups from the RV-210 and RV-215 cycles. (1) Resume of a run that already SETTLED ok no longer re-dispatches plain cap-expiry limit children live: the canonical replay predicate now takes a runSettledOk input (computed by the engine from the loaded journal's run settle entry), and the memoize-limit rule replays unstamped limit entries when the run is finished history, so resuming a completed run makes ZERO adapter calls and replay --assert-no-live style verification holds. Non-ok settles and never-settled journals keep the rerun retry semantics (a crashed segment still resumes into a second chance), and an explicit invalidate still forces a rerun. (2) SqliteQuotaLimiter carries its own class TSDoc (the api page previously inherited the bare SPI interface line), documenting the single-transaction admission, cross-process reconciliation, identical-rules requirement, pruning, and the busy_timeout contract.

1.56.0

Minor Changes

  • f26dba0: RV-215: distributed provider limiting. The new QuotaLimiter SPI is the extension seam for SHARED rate/quota limiting across engine instances and OS processes: createEngine({quota: {limiter, tenant?, onLimiterError?}}) makes the engine reserve capacity before EVERY live wire dispatch (initial attempts, transport retries, and failover takeovers alike, in every phase), dimensioned by provider/model/tenant with a heuristic token estimate, and reconcile each granted reservation with the attempt's actual usage after the outcome settles. A denial becomes a synthetic rate-limit-class WireError that rides the existing provider-429 retry and failover machinery verbatim, except no wire call is paid: the limiter's retryAfterMs (the honest window remainder) drives the interruptible backoff, attempts stay bounded by RetryPolicy, exhaustion fails over (the takeover reserves under its own model), and the terminal is the typed error of kind rate-limit. onLimiterError decides what a limiter INFRASTRUCTURE failure means: 'deny' (default) fails closed as a retryable transport-class denial, 'allow' logs a warning and dispatches without a reservation. Quota admission is live-only by construction (nothing journaled; replay and resume of memoized work never touch the limiter), and an unconfigured engine takes the exact pre-quota dispatch path down to promise-tick identity. Two reference implementations share one rule model (QuotaRule: optional provider/model/tenant dimensions; requestsPerMinute exact and hard, tokensPerMinute estimated at admission and settled to actual; every matching rule must admit; fixed epoch-aligned one-minute windows; validateQuotaRules at intake): memoryQuotaLimiter in @rulvar/core coordinates engines inside one process, and SqliteQuotaLimiter in @rulvar/store-sqlite coordinates PROCESSES over one database file, with admission inside a single BEGIN IMMEDIATE transaction, cross-process reconciliation via reservation rows, lazy two-window pruning, and the store's boot-scoped busy retry; a multi-process test fleet of real engines proves the global cap holds (dispatched wire calls exactly equal recorded window consumption, no window over cap). createTestEngine in @rulvar/testing passes a quota option through to the engine.

1.55.0

Minor Changes

  • e9b005b: Close RV-210 in full: the partial-work contract. (1) Weighted tool units and per-tool call caps: UsageLimits.toolUnits { max, costs? } terminates as a plain limit when the weighted budget is reached (each executed call of tool T costs costs[T] ?? 1; denied calls cost nothing), and UsageLimits.maxCallsPerTool { name: cap } denies the excess call of a NAMED tool pre-dispatch with a typed error result (guard: 'per-tool-cap', no budget or unit consumed; 0 bans the tool); both validate at intake, merge as whole-object per layer, and surface in ExplorationSummary as toolUnitsUsed / deniedToolCap. (2) The progress contract and the structured terminal partial: the stock progressReportTool() (report_progress) lets an agent state its facts, evidence refs, and open questions after every batch, and a limit terminal now keeps the LAST successful report as AgentResult.partial (derived deterministically from the transcript; a final boundary checkpoint pins the window so replay and recovery rebuild the identical partial; invocations that never report stay byte-identical). (3) Partial-child salvage: the digest of a limit child appends partial: {...}, get_child_result pages the full report, and acceptance.acceptPartialChildren: true counts a partial-bearing limit child as a success for both child policies (completion 'partial', the salvaged children listed in salvagedPartialChildren on the envelope and inside the single journaled acceptance decision; a bare limit child still rejects; one deterministic coordination-prompt line appears only when the option is on). (4) Profile templates with the stop conditions built in: researchAgentProfile({ root }) composes the repository research toolset, the progress tool, and RESEARCH_PROFILE_LIMITS; implementationAgentProfile / reviewAgentProfile preset the caller's task tools with report_progress prepended under their own exported limit constants. Unconfigured behavior is byte-identical everywhere.

1.54.0

Minor Changes

  • 3f6bc03: Three improvement-plan remainders: the run:end semantic completion lift (RV-207 tail), the standard repository research toolset (RV-210), and incremental synthesis with pre-model claim deduplication (RV-211).

    The completion lift. Transport status and semantic completeness are different claims, and run:end now carries both: a workflow that returns an object result with a valid completion literal ('complete' | 'partial' | 'rejected') and optionally a childStatusCounts record, or throws a typed error whose data carries them, gets both lifted onto the run:end event. The orchestrator acceptance path emits the envelope on every terminal, including the typed rejection (its FailRunError data now carries completion: 'rejected'). Malformed shapes stay silently absent, replay recomputes identical fields, the CLI progress line renders completion=..., and the OTel exporter maps rulvar.run.completion and rulvar.run.childStatusCounts.

    The repository research toolset. repositoryResearchToolset({ root }) ships five risk: 'read' tools over a confined directory root: list_files, search_files, and read_file with deterministic byte ordering and STABLE keyset cursors (a page boundary never shifts when unrelated entries appear; every cursor embeds its query identity), plus record_evidence, which verifies citations at collection time (the file must exist under the root, lines must be a valid 1-based range inside it, quote must appear verbatim), and list_evidence. Pages are canonical: byte-identical however addressed, which is exactly what the exploration guards measure, so maxRepeatedToolSignature and maxNoNewEvidenceCalls compose with the kit instead of being defeated by marker fields. Absolute paths, .. escapes, and symlink escapes are typed error results; the host reads collected evidence via kit.evidence().

    Incremental synthesis and claim dedup. synthesis.mode: 'incremental' dispatches one bounded synthesize-role NOTE invocation per settled child the moment it settles (default noteLimits { maxTurns: 2 }), overlapping the still-running fan-out, and the final result is a DETERMINISTIC reconciliation envelope (IncrementalSynthesisResult), never another model call; a dead note falls back to that child's raw digest summary under a journaled per-child orchestrator_synthesis_note_fallback decision, replay reproduces the envelope with zero paid calls, and finishValidation plus incremental mode is a ConfigError at intake because the reconciliation has no model-composed finish to validate. synthesis.dedupeClaims: true deduplicates repeated claim lines across children BEFORE any model call (whitespace-collapsed exact matching via the exported pure dedupeRepeatedClaims, never fuzzy): in single mode the digest keeps first occurrences with a REPEATED CLAIMS index riding the prompt, in incremental mode the envelope carries repeatedClaims. Both options default off and the synthesis prompt stays byte-identical when unset.

1.53.0

Minor Changes

  • b821bd1: Ship the RV-211 synthesis role and critical-path metrics. InvocationRole gains 'synthesize': the dynamic orchestrator's opt-in post-fan-in synthesis invocation (OrchestrateOptions.synthesis { model?, effort?, limits?, instructions?, estCost? }). With it configured, the coordination loop's finish({ result }) becomes a draft and one fresh finish-only invocation with role synthesize composes the final run result from the goal, the draft, and the settled child digest, routable independently of coordination through the ordinary chain (the routing key picks its model and never summons it; no role effort default, like loop and finalize). Ordering and failure posture are strict: synthesis runs only after an accepted acceptance verdict; finishValidation validators bind the synthesis finish instead of the draft (same repair loop, same journaled verdicts); a dead synthesis falls back to the draft under a journaled orchestrator_synthesis_fallback decision and a warn log without validators, or fails the run typed (data.source 'orchestrator_synthesis') with them. The invocation is an ordinary journaled agent entry, so a resume replays it with zero paid calls (the prompt derives from journaled state, and the replayed root now awaits recovery before the digest fold). Telemetry: full synthesize span and phase pairs (CostReport.byRole.synthesize), a debug log event with the actual draft/digest/prompt sizes, and the new pure reducer reduceCriticalPath(events) (CriticalPath), which computes run wall, the post-fan-in interval, the synthesis wall, and their shares, so the improvement plan's post-fan-in gate (at most 40% of wall time) is a field read; the benchmark kit can expose any of them as metric extractors. createTestEngine routes synthesize to the fake model like every other model-picking key. Demonstrated against published 1.52.0 first: the whole orchestration emitted only orchestrate/loop roles, the final synthesis request ran on the coordination model, byRole had no synthesize bucket, the post-fan-in share was hand-rolled or nothing, and the synthesis vocabulary was silently ignored words.

1.52.0

Minor Changes

  • e138df9: Ship the RV-210 exploration guards (first slice): three opt-in UsageLimits fields that make an oscillating tool loop visible and boundable. toolBudgetNotices surfaces soft 50%/80% thresholds over maxToolCalls to the model as a plain user message with the exact remaining count (once per threshold, checkpoint-safe, inert with a loud warning without maxToolCalls). maxRepeatedToolSignature caps executions of the byte-identical call (tool name plus RFC 8785 canonical args): the excess call is never dispatched, the model receives a typed error result naming the count, the denial does not consume the tool budget, and tool:end carries outcome: 'denied' with guard: 'repeated-signature'. maxNoNewEvidenceCalls aborts the invocation as status limit with the new abortClass: 'exploration' when N consecutive successful executions return only already-seen result digests; the executed work is kept, the terminal memoizes, and the structured ExplorationSummary (toolCallsUsed, distinctSignatures, repeatedCalls, duplicateResultCalls, deniedRepeats, byTool) journals beside the abort class so a replayed consumer sees the same typed evidence with zero live calls. Whenever any guard field is configured the summary also rides the full AgentResult and the live agent:end event (live-only for non-abort terminals, like transportRetries); values JCS cannot serialize fail open (unique signatures, fresh evidence); on resume the guard rebuilds from the restored checkpoint messages. The CLI TUI renders the guard marker on denied tool lines and the OTel exporter maps the counters to rulvar.exploration.* and rulvar.tool.guard attributes. Unconfigured invocations are byte-identical to before. Demonstrated against published 1.51.0 first: the identical call executed six of six times with zero signal, the model never saw a remaining count, duplicate pages never flagged, and the terminal was a bare limit indistinguishable from honest work.

1.51.0

1.50.0

Minor Changes

  • e39a885: The structured determinism contract (RV-209): bare-nondeterminism detection is engine-owned, classified, localized, and enforceable, and replay verification is a first-class CLI gate.

    • New determinism:warning event on the run stream: a bare Date.now() or Math.random() call observed inside an in-process workflow body emits category, provenance (workflow | allowlisted), the calling frame, and the parsed file/line/column, at most once per (category, provenance) per execution segment. Installed dependencies (node_modules) and Node runtime frames are classified exempt and stay silent, so an SDK's internal randomness never brands the run nondeterministic. Never journaled; because replay re-executes the body, a violation still in the code fires again on every replay organically.
    • CreateEngineOptions.determinism: mode: 'off' | 'warn' | 'error' (warn stays the default and the pre-RV-209 dev-only behavior; the process warnings now name the callsite), allowlist (substring or RegExp patterns for confirmed-safe frames, classified allowlisted, never rejected), and redact (applied to frames and file paths before they leave in events, warnings, and errors). Config is validated loudly at createEngine.
    • mode: 'error' detects in every environment including production and rejects the run: the offending call throws a typed DeterminismError (new error code determinism, localization in data) at the call site, and a workflow that swallows it is re-thrown at settle, so the run ends 'error' instead of recording a value replay cannot reproduce.
    • The journaled run-settle decision now records outputHash (canonical JCS sha256 of the settling segment's result; absent for undefined or non-serializable values). Pure replays append no settle, so a divergent replayed result can never overwrite the live baseline. hashRunOutput and the extended lastRunSettle are exported.
    • New rulvar replay <runId> [--args JSON] [--store PATH] [--assert-no-live] [--compare-output-hash]: a dry-run resume (zero journal or meta writes, zero adapter calls) that reports replay accounting, every localized determinism warning, and the digest comparison; --assert-no-live exits 1 unless the replay is pure, --compare-output-hash exits 1 unless the replayed result's digest equals the journaled one. Deliberately no --allow-args-change: verifying a different logical run proves nothing.
    • The TUI renders determinism:warning lines, and the OTel exporter attaches the event to its span with rulvar.determinism.* plus code.filepath/code.lineno attributes.
    • The frozen cassette catalog is re-recorded for the additive outputHash field on run-settle decisions (journal-shape-revision, policy not identity: no hashVersion change, no matching impact).

1.49.0

Minor Changes

  • bab7b2c: Make the agent event model unambiguous (RV-207): one agent:start/agent:end pair per logical agent span, a paired agent:phase:start/agent:phase:end per model invocation phase, an official reducer, and the OTel exporter leak the old shape caused is closed.

    Before this release one spanId emitted an extra unpaired agent:start for every phase of the dispatch (loop, then summarize per compaction, finalize, extract) with a single agent:end, so durations and attempts were underivable without heuristics: a consumer pairing starts with the end read the LAST phase's duration as the agent's, a starts-minus-ends gauge leaked one running agent per phase, and the shipped toOtel exporter (reproduced on the published 1.48.0) leaked a never-ended OTel span per multi-phase agent while the span it did close measured only the last phase. The replayed stream had a different shape than the live one (one start), so the same consumer built different tables live and on replay.

    Now every phase activation emits agent:phase:start/agent:phase:end keyed (spanId, invocation) (a 1-based activation ordinal; a summarize that fires three times gets three pairs), carrying the phase's role, the serving model, durationMs, the usage delta the activation added to its (role, model) slice (the pairs sum exactly to agent:end and to the journaled usageByModel split), costUsd priced at each serving model's own rate, a binary outcome, and retries (transport retries inside the activation). agent:end gains retryCount. The retry facts are live telemetry only, never journaled: replayed events omit them, and replayed phase pairs are reconstructed from the terminal entry's recorded slices with durationMs 0, so a live stream and its replay reduce to IDENTICAL usage and cost tables. reduceInvocationTable (new in @rulvar/core) is the official no-heuristics reducer: per-agent per-phase rows plus a per-role aggregate that matches CostReport.byRole; truncated streams stay honest (open: true), never guessed at.

    @rulvar/cli: toOtel maps each phase pair to an invocation <role> child span of its agent span with gen_ai.usage.*, rulvar.cost_usd, and rulvar.retries attributes, closes the agent span with the whole dispatch's totals and rulvar.retry_count, and an opener for an already-open span never duplicates it, so even a stream from a pre-RV-207 core cannot overwrite the tracked agent span and leak it unended. The progress renderer prints the phase lines (agent w extract phase on model, then the settle line with per-phase cost, tokens, duration, and retries). Journal bytes, cassettes, and toolset hashes are untouched: events are telemetry, never identity.

1.48.0

1.47.0

Minor Changes

  • a3687fe: Ship phase 3 of the fenced run state RFC, reconcile and recover. The engine now journals every run settle whose segment did durable work (or changed the recorded status) as a run_settle decision entry ordered BEFORE the meta write, so the run's outcome is part of the journal and RunMeta is a rebuildable projection; the write-on-change rule keeps pure replay byte stable, so a resume that only replays appends nothing. On top of it, auditRun names the divergences a worker sweep can never see, auditRuns sweeps the catalog, and reconcileRunMeta rewrites the sound cases from the journal with zero model calls and no workflow: meta-behind (the crash residue between the journal flush and the meta write, or a stale write contradicted by a journaled settle) takes the journaled status, and stranded (a terminal meta over live journal work, the F1 residue an unfenced store admits, demonstrated against the published 1.46.0 first) becomes sweepable again; ambiguous residues are reported as suspect and never rewritten. The CLI gains rulvar runs audit [--repair], the operator probe: it lists every divergence, repairs under a brief per-run lease on a leasable store (a live owner is skipped, never raced), and exits 0 only when the catalog ends consistent. ResolutionOutcome additionally carries woke: true exactly when a resolution settled a live in-process waiter, and the HTTP server uses it to close a quiesce-window race: a resolve that applied through the fold while the segment was closing now awaits the imminent settle and continues the run in place instead of answering resumed: false on timing grounds and stranding it suspended. The committed cassette catalog is re-frozen for the additive settle entry under the journal-shape-revision lane of the fixtures lock: an additive journal evolution that revises no identity (the hashVersion stays 2; entry identity, adapter requests, and the frozen v1 resume fixtures are untouched byte for byte).

1.46.0

Minor Changes

  • 865e7bf: Close finding F2 of the fenced run state RFC with the sqlite transcript twin. SqliteStore.transcripts() returns a TranscriptStore that declares fencedWrites because its blobs live in the store's own database, beside the lease rows: a lease-carrying put or delete verifies the current holder of the run the ref's leading path segment names atomically with the blob mutation, in the same one-immediate-transaction shape as the journal side, and rejects stale or cross-run holders with the typed LeaseHeldError leaving the prior blob byte intact. Demonstrated against the published 1.45.0 first: the engine threaded the superseded segment's lease into its late checkpoint save, both shipped transcript stores ignored it, and the blob at the deterministic ref both segments share regressed to older turn state (the state a later boot decodes, replaying turns the successor already paid for) while the same holder's journal append bounced typed. Over the { journal: store, transcripts: store.transcripts() } pair, assertFencedWrites now passes and every durable run mutation is fenced. The conformance kit gains fencedTranscriptsConformance, the executable definition of the transcript-side promise, taking a factory for the pair that shares the fencing domain; staleness is produced with release plus reacquire, so the suite needs no wall sleeps.

1.45.0

Minor Changes

  • b96305d: The fenced writes capability (the fenced run state RFC, phase 2). JournalStore.putMeta and delete and TranscriptStore.put and delete accept the same optional trailing lease that append always took, and a store declares enforcement with the fencedWrites: true marker: a mutation carrying a lease that is not the current holder for the mutated run rejects with the typed LeaseHeldError, atomically and leaving nothing changed, including a live lease for a different run. The engine threads the segment's lease into every durable mutation of a leased resume (meta writes, checkpoints, compaction summaries, worktree patches, workflow sources), so over a declaring store a superseded worker can no longer overwrite the successor's meta at its late settle and strand the run from worker sweeps, and its very first refused meta write now fails the stale segment typed at boot with zero paid calls. SqliteStore declares the marker and enforces it on putMeta, delete, and append (with the run-match rule as defense in depth); the conformance kit gains fencedWritesConformance as the capability's executable definition; the queue worker's retention sweep passes its brief lease through the new optional second argument of engine.deleteRun (pruneRun takes the same); and hasFencedWrites plus assertFencedWrites let a host assert the full fence at deployment time. Stores written before the capability are untouched: without the marker the extra argument is ignored and the journal-append fence works exactly as before.

1.44.1

1.44.0

Minor Changes

  • 299f7d2: Evidence preservation contract for the orchestrator finish (the improvement plan's RV-202 slice). The finish validation input now carries children: every spawned child at finish time, in spawn order, with its handle, nodeId, status, and full output text, a pure read of the durable state the orchestrator already tracks, so validators can hold the finish result against the evidence the children actually produced. The new evidencePreservedValidator enforces the plan's gate: at least minShare (default 0.95) of the distinct citations found in the outputs of children settled ok must appear literally in the result text, with the missing ones listed in the rejection so the bounded repair turn can restore them; requireKnown: true additionally rejects citations no child ever produced, closing the fabrication path that satisfied a plain count check. Purely textual and deterministic; verdicts journal exactly like every finish validation verdict, so replay and resume reproduce them without re-running validator code.

1.43.0

Minor Changes

  • 71b7181: Deterministic finish validators with bounded repair for the dynamic orchestrator (the improvement plan's RV-204 slice). OrchestrateOptions.finishValidation runs host validators over every schema valid finish({ result }) call: a rejection returns the failure reasons to the model as the call's error tool result and grants a bounded repair turn (maxRepairs, default one); a rejection past the bound fails the run with the typed FailRunError (code fail_run, data.source 'orchestrator_finish_validation') BEFORE the acceptance settle, so acceptance never judges a rejected finish. Every verdict journals as a decision entry keyed by the finish call id, so a resume rolls the same verdicts forward without re-running validator code, and a journaled final rejection short circuits at boot without a model call. The toolset never changes and zero configuration adds zero journal entries, so existing runs and frozen cassettes replay byte for byte. Ships requiredSectionsValidator, requiredFieldsValidator, and minMatchesValidator, plus the FinishValidator contract for custom checks.

1.42.0

Minor Changes

  • 9b70f27: Add the opt in child-result evidence tools get_child_result and read_child_artifact (the v1.40.0 improvement plan, narrow RV-201 slice)

    The digest an await returns is a wake signal truncated to 400 characters, so an evidence-heavy child settles with its findings intact in the journal but only a snippet in the digest, and until now there was no way for the orchestrator to fetch the rest. OrchestrateOptions.exposeChildResultTools now adds two pure read tools. get_child_result pages a settled child's FULL output (its string or JSON; a failed child's error message, so the orchestrator can read why it failed), reporting totalChars and hasMore and clamping maxChars to 20000 per call so one read can never flood the orchestrator context. read_child_artifact pages a settled child's artifact content by id: inline data, an offloaded transcript blob decoded as UTF-8, or a patch's changed-file list.

    Both are pure reads of already-durable journal state, so a resume reproduces them with no new spend. The option is off by default: adding the tools changes the orchestrator toolset hash by design (exactly like the extension's plan tools), so a run that does not opt in keeps the default toolset, and every frozen cassette, unchanged.

1.41.0

Minor Changes

  • be589ec: Add the orchestrate acceptance policy and the CLI --strict flag (the v1.40.0 improvement plan's completion contract)

    Run status ok proves that finish validated, and nothing more: the model may call finish after any mix of child outcomes, so ok alone never proves the children succeeded. The new opt in OrchestrateOptions.acceptance turns that into a checked contract. childPolicy 'all-ok' requires every spawned child to have settled ok when finish validates (a child still running counts against it); { minSuccessful: N } tolerates failures beyond the first N successes. The verdict is journaled as one decision entry, so a resume rolls the same verdict forward, immune to drift of the live options. An accepted result becomes the acceptance envelope { result, completion, childStatusCounts, degradedReasons }; a violated policy fails the run with the typed FailRunError (code fail_run, data.source 'orchestrator_acceptance') instead of settling ok. Without acceptance nothing changes: the result value stays the raw finish payload and no new journal entry is written.

    The CLI pairs with the envelope: rulvar run --strict and rulvar resume --strict exit nonzero when a settled ok value reports completion 'partial', printing the degraded reasons (strictExitCode is exported for hosts). The guides also now state the adjacent contracts plainly: await_any and await_all return truncated TaskDigests rather than full child reports, cost totals are price registry estimates with usageApprox marking estimated usage, the fencing epoch covers journal appends while RunMeta and transcript blobs stay advisory projections, and data protection at rest is owned by the host.

1.40.0

Minor Changes

  • cf33550: Fence the offline resolution append and surface approximate usage (v1.39.0 review)

    The CLI server's offline resolution path acquired a store lease but never threaded it into the Replayer, so the resolution append ran unfenced: if the process stalled past its lease ttl and a queue worker took the run over, the stale append could land alongside the new owner's writes. The append now carries the acquired lease, so a superseded owner is rejected with LeaseHeldError (HTTP 409) instead of racing the current owner.

    Approximate usage is now visible where the run is reported. usageApprox rides the agent:end and run:end events and the CostReport, and the CLI cost line marks an estimated total, so a total that includes usage estimated after a transport cut, a ceiling that severed a stream, or an abort is never shown as though it were the exact provider charge. The field is present only when true, so every exact usage report and event is byte for byte unchanged.

1.39.0

1.38.0

1.37.0

Minor Changes

  • e6b1481: Validate the persisted KnowledgeSnapshot on every FileModelKnowledgeStore read (v1.36.0 review P2-6). The old read checked only that version was a number, hash a string, and claims an array, so a hand edited or torn rulvar.models.json could forge a negative or fractional version and a mismatched hash, and a null or partial claim flowed on to crash the card render with an untyped TypeError. The read now requires a nonnegative integer version, a lowercase sha256 hash that MATCHES knowledgeHash(claims), and structurally sound claims (a persisted snapshot may hold non active statuses), refusing any inconsistency as a typed ConfigError that names the offending path. commit reads first, so it refuses to append onto a corrupt base.
  • e6b1481: Contain FileTranscriptStore refs under their configured root (v1.36.0 review SEC-P1). The per-segment check accepted . and .. (dots are in its alphabet), so join let a .. segment escape: a caller passing an untrusted ref to put, get, list, or delete, or an untrusted runId (which prefixes the checkpoint and workflow source refs), could read, write, or delete .bin files outside the directory. Every segment now must be a nonempty safe token that is neither . nor .., and the resolved path must stay under the resolved root. The engine also refuses an unsafe runId with a typed ConfigError before its first store write, so a compiled run cannot persist its source outside the transcript root.

1.36.0

Minor Changes

  • 101795b: Fix the v1.35.0 review P1 and the core P2 groups. The parked flavor B decision wait is abort aware: handle.cancel(), a RunOptions.signal abort, the run deadlineAt, and fail fast sibling aborts settle the run in bounded time instead of waiting out the escalation deadline; the suspension entry stays open so resume re parks it, worktree salvage still precedes destruction, and the wait rejects with the new EscalationDecisionAbortedError. budget.atCap: 'fail-run' is executable: the journaled cap decision drives the branch, the reserved finalizer is skipped, and the run fails with the new FailRunError (registry code fail_run), rolled forward deterministically on resume. OrchestrateOptions validate at construction (maxSpawns, renderBudgetChars, budget.capUsd, budget.capFraction, budget.finalizeReserveUsd, budget.finalizeTurns, and the atCap literal), and the digest render budget is a hard upper bound of the rendered row, marker included, at both distillation tiers. The extension seam gains an optional terminate capability so a journaled policy verdict can close the run typed. Knowledge and isolation intake validate too: FileModelKnowledgeStore.activeClaimsCap, GitWorktreeProvider.maxPinnedWorktrees, and modelKnowledgeCard budgetChars (now a hard bound of the whole card). The sweep also validated escalation.minSpendUsd (a NaN silently disabled the minimum spend gate) and gave LeasableStore the optional readonly leaseTtlMs capability.

1.35.0

Minor Changes

  • d4ac3bf: Validate every numeric engine option at its intake and survive far future deadlines (v1.34.0 review P2-1, P2-2, P2-3, P2-4). createEngine now refuses malformed concurrency.perRun and concurrency.perProvider caps, budgetDefaults fields, engine and profile limits, profile estCost, escalation deadlineMs, and compaction thresholds with a typed ConfigError; engine.run validates budgetUsd and limits synchronously and requires deadlineAt to be an ISO 8601 date-time with an explicit UTC designator or offset (an impossible calendar day is refused rather than silently rolled into the next month, and a malformed string no longer cancels the run after the first provider dispatch). ctx.agent validates estCost and limits per call, so a negative reserve can no longer shrink the committed total and admit a sibling past its ceiling, and the admission gate refuses a non finite reserve as a backstop. The per run semaphore requires a positive integer limit (a NaN cap used to park the first request forever with cancel() unable to settle the run) and queue waits are abort aware, so a cancelled run always drains its queued calls in FIFO order. Absolute deadlines (RunOptions.deadlineAt and the journaled escalation deadline) are honored through sliced timers beyond the Node timer maximum instead of firing immediately, while streamIdleTimeoutMs is bounded by that maximum like retry policy delays. validateUsageLimits is exported for hosts that want the same check at their own boundary.

1.34.0

Minor Changes

  • f1505ec: mcp() now returns a McpToolSource: the frozen ToolSource seam plus an idempotent close() that releases everything the source created on first use, the SDK client, its transport, and, for stdio, the spawned child process. Without it a one shot host that ran a workflow over a stdio MCP server could never exit naturally, because the child and its pipes kept the event loop alive (v1.33.0 review P2). close() resolves even when the connection never succeeded, and it resets the source, so a later tools() call connects afresh; a failed connect now also releases its transport and child on the way out instead of leaking them behind the error it rethrows. The engine still never closes a source, because one source may serve many runs: the host owns the lifecycle, and the MCP guide documents the try/finally pattern for one shot scripts. Real stdio and streamable http integration tests now cover both external transports, including child process release, reconnect after close, and cleanup after a failed connect.

1.33.0

1.32.0

1.31.0

1.30.0

Minor Changes

  • 87ce985: Validate every RetryPolicy before anything runs under it (v1.29.0 review P2). Published 1.29.0 accepted attempts: 0, fractional and NaN attempts, negative backoff numbers, and a NaN factor, then dispatched the adapter under them: the invalid values silently reshaped retry semantics (zero or NaN attempts behaved as no retries; a negative initialMs or NaN factor collapsed the delay to zero, removing backoff entirely). The new exported validateRetryPolicy enforces the documented contract, a positive safe integer attempts (the engine always makes the first try, so zero attempts has no meaning), timer safe integer initialMs and maxMs (maxMs below initialMs stays legal as a Math.min ceiling), a finite positive factor (below 1 is a legal decaying backoff), a boolean jitter, and unique known retryOn classes, and throws a typed ConfigError naming the offending field and its config source. createEngine validates defaults.retry and every profile retry at construction; the per call merge in ctx.agent validates the winning policy before identity, admission, or any journal append, so an invalid policy can never reach a provider or record a partial agent execution.

1.29.0

Minor Changes

  • 621d566: Make the retry and failover backoff interruptible and validate every provider supplied retry delay (v1.28.0 review P1 and P2).

    The retry engine now races its backoff wait against the host cancel signal (which the run deadline also drives) and the budget ceiling signal: an abort wakes the wait immediately, settles through the canonical aborted outcome (cancelled or exhausted, with every already recorded usage kept), and forbids every further dispatch, including the one behind a keyed limiter queue, so an adapter that ignores its signal can no longer be re entered after an abort. Previously a provider supplied retryAfterMs armed an uninterruptible sleep: a cancel, a crossed deadline, and a crossed budget ceiling all waited out the full backoff and the adapter was dispatched again. The injected retry.sleep(ms) test hook keeps its signature; a hook that loses the race is abandoned without an unhandled rejection, and the native timer path clears its timer so an abandoned long backoff never pins the event loop.

    retryDelayMs is now the defensive boundary the docs promise: only a finite nonnegative provider retryAfterMs replaces the computed delay, anything else (NaN, Infinity, a negative) is ignored as adapter noise, and every returned delay is a finite nonnegative integer clamped to the Node timer maximum, so a malformed or huge value can never arm an instant or overflowing timer. Both first party adapters stop emitting unvalidated Retry-After parses: an unparsable header (the HTTP date form included) omits retryAfterMs entirely instead of producing NaN (which also broke the WireError.data Json invariant by serializing to null), and a huge but finite value is clamped. The mapAnthropicStream TSDoc now states precisely how a truncated stream is reported (the finished flag on the return value, with the adapter synthesizing the terminal error).

    Four frozen fixture cassettes are refrozen for this release (the hashVersion-bump refreeze ceremony applies; hashVersion itself is unchanged and existing journals replay identically): in three cap freeze scenarios the main orchestrator entry now honestly settles cancelled at the cap instead of paying one more ordinary turn whose result the forced finish machinery discarded anyway, and one scenario loses a post abort wait suspension that can no longer be dispatched. Entry identities, keys, and every other row are byte identical.

1.28.0

Minor Changes

  • d98eb0b: Enforce the terminal stream contract end to end (v1.27.0 deep E2E review P1 and P2). The runtime now fails closed when an adapter stream drains without a terminal finish or error event: the partial turn becomes a retryable transport fault that feeds the ordinary retry and failover machinery instead of settling as ok with truncated text, and a requested abort (cancel, budget ceiling, idle severance) remains a clean end with no fabricated provider error. Consumption stops at the first terminal event, so events after finish can no longer mutate the value, revise the authoritative bill, or trigger tool execution. The first party adapters enforce the same contract at the wire: the Chat Completions mapper no longer synthesizes finish: stop when the stream is cut before a finish_reason (usage the provider did report is still forwarded, half assembled tool calls are dropped), the Responses mapper fails closed on EOF without a response terminal event, and the Anthropic adapter surfaces a read cut before message_stop as a retryable transport error and no longer converts a caller requested abort during messages.create() into a terminal error. mapResponsesStream and mapChatCompletionsStream accept an optional signal so a requested abort keeps ending the stream without a terminal event. The VCR record wrapper now commits its cassette row even when the consumer stops reading at the terminal event (the engine always does now); adapter middleware must not rely on being drained past the terminal. The committed combined-loop-descent catalog cassette is refrozen because stopping consumption at the terminal shifts the deterministic interleaving of two parallel plan children by one scheduler turn; entry content, keys, and the actual hashVersion are unchanged, journals recorded under earlier versions replay unchanged, and this changeset carries the frozen fixture gate's hashVersion-bump ceremony token only to unlock that refreeze.

1.27.0

Minor Changes

  • 884a433: Types referenced by public signatures are now exported from their package barrels, so the API docs resolve them instead of carrying known incomplete references (v1.26.0 deep E2E review): BaseAppend from @rulvar/core (the fields common to every Replayer append), Block and MappedStop from @rulvar/anthropic (the wire level content block alias and the stop reason mapping), and VcrHeader from @rulvar/testing (the first line of every cassette file). The frozen TypeDoc baseline shrinks from eleven entries to the four vendored Standard Schema notices.

1.26.0

Minor Changes

  • a4fc757: Scale fixes from the v1.25.0 review. RunHandle.events keeps its gapless contract (buffered from handle creation) but drains linearly: the iterator queue uses a head index with in place compaction instead of Array.shift(), so a late read of a 100k event backlog takes milliseconds instead of seconds, and delivered events are released eagerly. engine.pruneRun now collects exact whole string references in one recursive pass over the journal (values and object keys) instead of a per terminal substring scan: a checkpoint ref that is a prefix of another (ckpt/2 inside ckpt/20) no longer survives pruning, matching what the stores and durability guides always promised, and the scan is linear in journal size instead of quadratic in entries. New optional store capability MetaLookupStore.getMeta(runId) with the hasMetaLookup guard and the readRunMeta helper: engine.resume and every shell point lookup use it when present and fall back to the historical listRuns scan otherwise; all three shipped stores implement it and the serialization wrapper preserves it. RunFilter gains an advisory statuses array (match any, combining with status as either matches; the shared predicate ships as metaMatchesFilter). RunMeta gains genesis, a generation token minted at the fresh start and preserved verbatim across resume segments, so a deleteRun and recreate of the same explicit runId is distinguishable from the original run.

1.25.0

1.24.1

Patch Changes

  • 0bb14db: Correct the RunMeta.argsHash documentation (v1.24.0 review P2-2). The digest is a deterministic, unsalted SHA-256 over the JCS form of a run's genesis args, so it reveals when two runs shared identical args and low-entropy args (a boolean, an approval flag, a role, a short id) are recoverable by hashing candidate values. The TSDoc on RunMeta.argsHash and hashRunArgs no longer claims that nothing sensitive lands in meta; it now states the digest is sensitive-derived metadata that confers no confidentiality and must be access-controlled like the journal and transcripts. The raw args are still never journaled, and no runtime behavior changes.

1.24.0

Minor Changes

  • 2b033e8: Record the genesis args binding in RunMeta and make the dry-run preview mutation-free (the v1.23.0 review). RunMeta gains argsProvided (whether the run started with defined args) and argsHash (sha256 over the JCS canonical serialization of the genesis args, never the raw value), written by the engine at genesis and preserved verbatim by every resume segment, so hosts can refuse a resume whose re-supplied args silently diverge from the original invocation; the new public hashRunArgs() derives the same hash host-side. Legacy metas never gain the marker retroactively, and unserializable args record presence without a hash. A dryRun resume now performs ZERO store mutations by invariant: putMeta is skipped entirely (no status flip, no segments bump), the compiled-source blob is not re-put, and the Replayer's single append site refuses any journal append under replay-strict with a typed JournalMissError. The store conformance kit checks the round-trip of both new fields.

1.23.0

Minor Changes

  • 1f9c272: Resume correctness and telemetry integrity, the v1.22.0 review's two P1 findings plus the event-layer P2s.

    • Resume ordinal continuation (P1-1). Since M2, the ordinal map key minted for new operations and the key used to seed that map from prior entries on resume were built by two hand-written composites whose separators differed, and the minting one contained an INVISIBLE literal NUL byte in the source, so the seeding filled a bucket mint() never read. Every identical-identity live operation after any resume re-minted ordinal 0, duplicating the journal identity triple (scope, key, ordinal) and corrupting sibling binding on the next replay. Both sites now go through one ordinalMapKey helper (escaped U+0000 separators, no printable-separator aliasing), the seeding computes an order-independent max, and the regression suite covers identical siblings across suspend, process recreation, and replay-strict re-resume. No CURRENT_HASH_VERSION change: ordinal bookkeeping never entered content-key derivation, and journals written by broken versions still load (their ordinals seed the map exactly as recorded).
    • Event seq and spanId durability across segments (P1-2). Every resume segment restarted the telemetry counters at 0, so one runId repeated seq: 0 and spanId: 's0' per segment, against the documented per-run contracts, and the CLI SSE Last-Event-ID cursor became ambiguous. RunMeta gains an optional segments count, bumped durably at every segment start strictly BEFORE the segment's first emission (crash-safe: a killed segment still advanced it), and each segment seeds EventBus and SpanRegistry at segments * EVENT_SEGMENT_STRIDE (exported, informational). seq stays a plain number, strictly increasing across the whole run and NOT contiguous across segments; span ids never repeat. Stores must round-trip the new field (the conformance kit now checks); a store that drops it degrades telemetry counters to per-segment, never the journal.
    • Listener-failure ordering and masking (P2-1). The v1.22.0 subscriber-isolation warn was delivered mid-fan-out (observers saw it BEFORE the event that caused it, with descending seq) and was built outside the masking boundary (a key-shaped fragment of the listener's error reached observers raw). The warn now goes through the ordinary emit() after the triggering event's fan-out completes: masked like every event, seq stamped at delivery, [event, warn] order on every surface, still at most once per bus and recursion-proof.
    • Spawn admission events on every boundary (P2-5). spawn:admitted only ever fired from the dynamic orchestrator's spawn tools; ctx.agent lineage admissions and ctx.workflow child admissions emitted nothing (ctx.workflow not even spawn:rejected). All admission boundaries now emit both events through one helper; journal-recovered decisions re-announce with replayed: true when they take effect, and a cleanly replayed dispatch does not re-announce. spawn:admitted.spawnUnitsAfter is now optional: absent on lineage-layer admissions, whose spawn-unit debit rides the dispatch itself.
    • The replayed flag actually reaches observers. The engine's internal event sink dropped the third argument of emit, so every replayed: true marker (replayed agent and tool lifecycle events, recovered suspensions, recovered rejections) was silently stripped since M2 and rendered as live. The sink now forwards it; the documented replay re-emission table is true again.
    • SANDBOX_AGENT_OPT_KEYS exported. The sanctioned sandbox agent-option allowlist is a documented public constant, the single source for the runtime validator and the planner API card.

1.22.0

Minor Changes

  • 77b554f: Add sanitizeTerminalText, the rendering-boundary counterpart to maskSecrets: it neutralizes terminal control sequences and control characters in one untrusted string so a provider error message, tool name, model id, or log line can never inject a control sequence or a second physical line into a rendered terminal line (v1.21.0 review P2-1). After sanitization the result carries no C0 control, no DEL, no C1 byte (including every 8-bit escape-sequence introducer), and no ESC-initiated CSI/OSC/DCS sequence; control runs collapse to a single space and visible text is preserved. The bundled renderers use it internally, and it is exported for host terminal sinks.

    Also isolate event-bus subscribers: a throwing on() listener (a renderer, a metrics hook) is best-effort telemetry and can no longer propagate out of emit to disrupt a paid run; the failure surfaces once as a warn log on the same bus instead (v1.21.0 review follow-up).

1.21.0

Minor Changes

  • 7ee42a0: Enforce the financial-telemetry invariant at the adapter boundary for every adapter, injected clients and mocks included (v1.20.0 review P1-1). Every canonical token count must be a finite nonnegative integer with the cache subsets inside the full input; a violation fails the call loud as a typed transport-class terminal while accounting sees only conservatively sanitized values (garbage floors to zero, fractions round up, so a repaired charge is never an undercharge and never a credit). Both inlets are guarded: finish usage and mid-stream usage deltas, which previously reached the budget with no clamp at all. New exports usageViolations, sanitizeUsage, sanitizeUsageDelta, snapshotUsage, and sanitizeTokenCount carry the shared rules; the accounting boundaries snapshot adapter-owned usage objects before validating them, mid-stream deltas are repaired per field without the whole-usage subset rule (partial increments legitimately carry cache counts alone), counts are bounded to the safe integer range, mid-stream reports the finish total does not confirm fail the call loud with over-reported cache reads re-debited conservatively at the input rate, a duplicate finish or a post-finish usage event is refused, checkpoint restores sanitize the persisted counts exactly like the resume seed, and every cost fold treats a NaN or negative priced amount as unpriced instead of poisoning the totals. RunBudget grows defense in depth behind the validator: hostile priced amounts clamp to zero with a one-time error event, spentUsd stays finite and monotone under fuzzed hostile usage, and NaN or negative ceilings and resume seeds reject up front as ConfigError instead of silently disarming every comparison. Journal entries also gain the optional policy field usageSemantics (adapter-declared, never identity), and resuming a journal whose unstamped OpenAI entries carry cache writes emits a one-time RULVAR_LEGACY_CACHE_SEMANTICS warning pointing at the audit procedure (v1.20.0 review P1/P2-2).

1.20.0

Minor Changes

  • 9367030: CostReport.byRole now attributes every paid invocation phase to its own bucket. Usage accumulates by (invocation role, serving model): UsageSlice gains an optional role, terminal entries and turn-boundary checkpoints persist the roled slices, and both the live buckets and the pure journal fold bump byRole per priced slice, so a routed finalize, a separate extract, or a mid-loop compaction summarize lands under finalize/extract/summarize even when one model serves several phases of one agent (previously the whole entry folded under its single primary role and those documented buckets could never be nonzero). Backward compatible end to end: slices without a role and entries without slices fold under the entry's primary costAttribution.role exactly as before, pre-split checkpoints restore under the primary pair, a single-phase single-model call still writes no slices (those journals stay byte-identical), and role buckets and model buckets both sum to the same total on live runs, same-engine replay, and fresh-engine replay.

1.19.0

Minor Changes

  • 8cc9a9c: The finalize synthesis invocation now appends a deterministic synthesis instruction (FINALIZE_SYNTHESIS_INSTRUCTION, exported) to its request, and a non-truncated empty synthesis falls back to the loop turn's text instead of erasing it. Previously the routed finalize call sent the projected transcript ending at the assistant message with no instruction at all; a real model reads that as a fresh conversation opening, and its greeting unconditionally replaced the loop's correct answer as the schema-free output (reproduced live: a tool loop that had already answered 42 returned How can I help?). The instruction is request-only: the durable transcript keeps the raw history, so journal identity, extract input, and replay are untouched, and no recorded fixture moves. The truncated-empty synthesis case stays a bounded output-truncated failure. An opt-in live smoke (RULVAR_LIVE_TESTS=1 plus OPENAI_API_KEY) pins the contract on a real provider.
  • 8cc9a9c: orchestrate(engine, goal, opts?, runOptions?) and orchestratePlanned(engine, goal, opts?, runOptions?) accept the created run's RunOptions as an optional fourth argument, threaded verbatim to engine.run. runOptions.budgetUsd is the ROOT hard ceiling over the whole tree (the orchestrator and every child), immutable after start and frozen into RunMeta, while opts.budget only shapes the orchestrator's own sub-account inside that ceiling; the two layers were previously conflatable, and the canonical shortcuts could not set a root ceiling (or signal, runId, limits, deadline) at all without dropping to engine.run(makeOrchestratorWorkflow(goal, opts), undefined, runOptions). Purely additive; existing calls are unchanged, and a call without runOptions still starts an UNCAPPED run, which the docs now state explicitly.

Patch Changes

  • 8cc9a9c: Internal real-time reads bind the wall clock at module load, never the live global, eliminating false RULVAR_BARE_DATE_NOW warnings for consumers whose rulvar frames live outside node_modules (workspace dists, monorepo checkouts). Two composing defects: createEngine captured Date.now per call, so an engine created after a previous run had installed the dev-mode patch bound the PATCHED wrapper as its real clock (its EventBus then warned from the engine's own frames), and the ULID factory read the live global at every mint, so ids minted mid-run (the orchestrator extension IO, PlanRunner revisions, adapter id maps) routed through the patch too. The engine now uses a module-load realNow binding (module load always precedes the first patch install), the vendored ULID factory defaults to its own module-load clock, and @rulvar/store-sqlite follows the same convention. The dev-mode guard itself is untouched and stays exactly as sharp for workflow code, which keeps reading the live global.

1.18.0

Minor Changes

  • 943962d: Registered toolset names now resolve everywhere a tools option is taken. A string entry of AgentOpts.tools, a profile's tools, or the sandbox dialect's tools names a toolset registered under createEngine({ defaults: { toolsets } }), expanded through the same canonical resolveToolset path as ToolDef and ToolSource values: unknown names are a typed ConfigError at spawn time before any provider call, duplicates and collisions are validated after the union, the resolved contracts land in toolsetHash and the journal identity exactly like directly passed definitions, and registry values may not nest other names, so no cycle can exist. This closes the v1.17.0 review P1-3: the planner API card and the docs taught tools: ["name"], the sandbox bridge required strings, and the core rejected every string, so the documented construct could never run. profileCard now renders the registered toolset names as a closing line when the registry is non-empty (byte-identical output for engines without toolsets), so a planner can only name declared registries. Migration: strings previously always threw (tools by registered name ... are not supported here); they now resolve or fail with unknown registered toolset '<name>'. No behavior changes for ToolDef and ToolSource entries.

1.17.0

1.16.2

1.16.1

1.16.0

1.15.0

1.14.0

1.13.0

1.12.0

Patch Changes

  • 46edcc0: An exhausted run settle no longer drops the typed failure when the throw was an AgentCallError: the exhausted branch now projects it through agentResultWire exactly like the error branch, so outcome.error keeps the agent's typed budget failure (and any engine-decided abort class) in the parallel-exhaustion race where one branch's agent fails while the run budget is already exhausted. The common paths were already typed: a direct BudgetExhaustedError carried its wire before, and the in-loop turn-guard denial surfaces as budget_exhausted with zero over-ceiling calls, now pinned by an engine-level regression test.

1.11.0

Minor Changes

  • 0c70c5e: Close the execution segment at settle: exactly one segment owns a run (v1.10 deep E2E review, P1). Previously, resolveExternal on a handle whose result had already settled 'suspended' silently woke the parked body through the live registry, and the documented resolve-then-resume sequence then started a second segment over the same journal: the approved tool executed twice, the post-approval turn was paid twice, and both segments minted the same journal seq (two terminal agent entries with duplicate seqs). Now every settle closes the registry: parked branches never run again, a post-settle resolveExternal validates like the live path and appends the durable resolution through the journal fold WITHOUT waking anything, and the one continuation belongs to the next engine.resume. The pre-settle live path (resolving from an approval:pending listener) is unchanged. Repeated resolution is now the documented no-op instead of a throw: once the target suspension is closed, resolveExternal returns { applied: false, reason: 'already_resolved' } (journaled through the first-closing-wins arbiter) rather than InvalidResolutionError; an unknown key still throws, and an invalid payload still throws without journaling. Segment ownership is also enforced at the front door: a second concurrent engine.run or engine.resume of a runId that already has a live segment in the same engine throws a typed ConfigError before any side effect. Defense in depth at the store boundary: InMemoryStore and JsonlFileStore now enforce the monotonic-seq obligation, rejecting an append whose seq is not strictly greater than the stored tail with the typed JournalOrderViolation, so two stale-tail writers can never both persist. New public API: ExternalRegistry.close(), ExternalRegistry.closed, and ExternalRegistry.suspensionKeyOf(entry). Docs: guide/durability#resolving-a-settled-run states the ownership rule and both safe orders; tools, testing, troubleshooting, CLI, stores, and store-authors pages align with it, and the documented sequences are now executable regression fixtures.

1.10.0

Minor Changes

  • 0e8d78e: Settle empty max-tokens turns as a typed output truncation, never an empty success. A schema-less turn (no schema, no required terminal tool) whose completion ends with finish reason max-tokens and no visible text now settles limit with the new abortClass: 'output-truncated', a terminal-kind error, and an actionable message, instead of ok with ''. The same check covers a routed finalize invocation, whose synthesis is the schema-less answer; a max-tokens turn with visible text keeps settling ok with the partial text. Like the no-progress abort, the truncation stamps memoizeOutcome on the terminal entry, so every resume replays the typed outcome with zero provider calls. The abort class now rides every projection of the failure: the journaled terminal error payload, the run-level outcome.error.data, dropped items, and thrown AgentCallError wires, so consumers such as the planner see the typed truncation instead of burning self-repair rounds on compile/empty-source under an unchanged output limit. AbortClass widens to 'no-progress' | 'output-truncated', and the projection helper is exported as agentResultWire(result, fallbackMessage) alongside agentErrorToWire.

1.9.0

Minor Changes

  • 3a53383: Report pricingVersion drift on resume.

    The orchestrator_budget_reserve decision already pins the pricingVersion in effect when a run started, but the resume recovery only compared the frozen cap dollars. A resumed run now also compares the journaled version against the live table (unpriced when priced from the adapter caps fallback) and emits termination:config-drift with field pricingVersion when they differ. The divergence is reported, never honored or refused: price interpretation is live by design (the journal stores usage; dollars are re-derived from the current table against the frozen cap dollars), replay stays byte-identical, and no provider work is repeated. Reserve decisions journaled before the field shipped resume quietly.

1.8.0

Minor Changes

  • 57ea1de: ResumeReport.orphaned now follows entry-type pairing rules and lists only effect roots that genuinely need recovery: dangling dispatches (a running entry with no terminal) and suspensions with no resolution, neither consumed by a live call nor covered by abandon. Terminal decisions, termination.* and plan.* entries, settled roots (whatever their terminal status), and resolved suspensions are complete by construction and never appear, so a fully successful replay reports orphaned: []. Previously the list contained every journaled operation not consumed through forward matching, which flagged spawn-admission decisions, plan revisions, settled agent roots, and resolved wake suspensions on perfectly healthy replays (the v1.7.0 follow-up review's finding).

    Deleted settled calls are still silently skipped and never re-paid; they are just no longer listed. A deleted call whose dispatch was left dangling still reports, which is the case that actually needs attention.

  • 7884ec5: PlanRunner plan admission is now atomic with child dispatch admission (the v1.7.0 follow-up review's P1). Previously a plan_revise op could be journaled as admit (consuming its spawn unit) and only then have scheduleReady's dispatch rejected by the engine budget, stranding the node ready forever, losing the plan:revised event, and burning the orchestrator budget with no worker output.

    • An add_task op whose resolved profile estCost cannot fit the effective child ceiling (rung-resolved maxCostUsd, else budgetUsd) is bounced at rebase time with the new typed reason reserve_exceeds_budget naming the child account, requested and resolved reserve, ceiling, and minimum correction. No plan state changes and no spawn unit is consumed; the plan_revise tool result carries the reason verbatim.
    • The read-only admission branch now projects the SAME reserve the dispatch layer will commit (estimate clamped by the explicit child budget only), plus the pending reserves of earlier ops in the same revision, so every embedded admit of one batch is dispatchable under the snapshot it was decided on. The dynamic spawn_agent path passes the profile estimate into admission for the same reason.
    • Layer 1 (ctx.agent) clamps its committed reserve to the tightest child-allowance account headroom on the chain (a plan node's own sub-account, a ctx.workflow child ceiling): an allowance already bounds the child's lifetime spend, so an estimate above it clamps instead of denying, which is what makes "admit implies dispatchable" hold by construction. The run root and orchestrator cap are never clamped against; their headroom is shared money that projected admission keeps protecting.
    • plan:revised and termination:debit now emit strictly after the durable revision append and before the scheduling effects, so a scheduling fault cannot erase an applied revision from the event stream.
    • The residual class (facts that genuinely changed between admit and dispatch, e.g. the engine lifetime spawn cap) lands the node terminally failed through a journaled plan.decision with the new origin/cause dispatch-rejected; other ready nodes still dispatch and the run proceeds.

    Acceptance tests cover the review's live shape (profile estCost 0.015 against budgetUsd 0.01), the positive control, resume idempotence, the containment path, and an admit-implies-dispatchable property grid over estimates, budgets, ceilings, flat reserves, and prior commitments.

  • 52db30d: termination.init now freezes the ACTUAL orchestrator budget dollars instead of zeros, closing the journal-contract gap the v1.7.0 follow-up review found: the budgets guide documents orchestratorCapUsd and finalizeReserveUsd as frozen in the same limits vector as the counters, but PlanRunner journals stored 0 for both and only the later orchestrator_budget_reserve decision carried the real values.

    • The engine resolves the effective cap and finalize reserve strictly before extension boot and exposes them on OrchestratorExtensionIO (orchestratorCapUsd, finalizeReserveUsd); PlanRunner writes them into termination.init.
    • On resume the cap dollars are now recovered from the frozen orchestrator_budget_reserve decision instead of being re-derived from live options (DEF-2 config-drift-resume: the journal wins). A diverging live capUsd/capFraction/finalizeReserveUsd emits termination:config-drift and is never honored.
    • Journals recorded before this release (zeros in termination.init) replay unchanged: the fold reads the init entry by kind, and the reserve decision remains their authority.
    • The reserve-decision presence guard is now scoped to the orchestrate call, so nested capped orchestrations each journal their own freeze.

    The frozen cassette catalog is re-recorded (the init limits vector and its content key change); hashVersion stays 2, and the fixture lock refresh carries the required hashVersion-bump token.

Patch Changes

  • 25724b5: The no-progress abort message now links the public docs (https://docs.rulvar.com/guide/agents#the-agent-loop-and-turns) instead of the retired internal spec reference "docs/06 Appendix A". Runtime-visible errors reference public documentation only. The stall-streak cassette embedding the message was re-recorded byte-for-byte otherwise; hashVersion stays 2, but the fixture lock refresh requires the hashVersion-bump token.

1.7.0

Minor Changes

  • 45285aa: Budget exhaustion errors now name the ceiling that actually ended the work. BudgetExhaustedError from agent execution reports the first closed account walking up from the debited scope (its scope, ceiling, spend, and reserves) plus the run root state, classified as root, orchestrator-cap, or child-account, both in the message and in typed data; a crossed orchestrator cap no longer masquerades as run budget ceiling reached. RunBudget gains the exhaustionDiagnostics(scope) projection behind this, and the orchestrator emits a warn log when an explicit budget.capUsd is silently bounded by the default capFraction 0.2 of the run ceiling (pass capFraction: 1.0 to make capUsd the sole bound; the docs now spell out the min formula trap).
  • 2f20d1d: CostReport is now replay stable and internally consistent: the engine builds every settled outcome's report from one pure journal fold (costReportFromJournal), so a replay only resume reproduces the complete report byte for byte, including the orchestrator block (spentUsd, share, wakes, forcedFinish, reserveUsedUsd), which previously read this process's live budget accounts and collapsed to zero on replay. Terminal entries now carry additive costAttribution facts (phase, agent type, primary role, debited budget account, finalize reserve flag); they are policy, never identity, exactly like usageByModel, and entries written before the field shipped fold under documented fallback buckets. One inclusion policy applies to the total and every breakdown alike: non abandoned terminal usage exactly once, so byModel, byPhase, byAgentType, and byRole each sum to totalUsd even after resumes that re paid attempts. orchestrator.wakes now counts armed (journaled) wake suspensions. The frozen cassette catalog was re recorded for the new journal byte form; identity derivation is untouched and old journals replay unchanged (the hashVersion stays 2; the token hashVersion-bump here sanctions the fixture lock refresh ceremony, not a version change).

Patch Changes

  • 22f65a8: The development mode bare nondeterminism detector no longer warns when Node's own machinery consults Date.now or Math.random inside a run's async context. Frames with node: specifiers (the undici transport behind global fetch, timers, stream internals) are now classified as library provenance alongside node_modules, eliminating the false RULVAR_BARE_DATE_NOW observed at processResponseEndOfBody during in run fetch calls. Direct calls from workflow files still warn exactly once per run.
  • 2ddfa29: Documentation: the mode (c) resume contract is now stated as it actually works. orchestrate() builds its workflow internally and never registers it, so bare engine.resume(runId) cannot resolve it; the orchestration modes guide and the resume table now document the two working forms, engine.resume(runId, makeOrchestratorWorkflow(goal, opts)) with the original inputs or a one time registration under defaults.workflows with ORCHESTRATE_WORKFLOW_NAME, with an executable test covering both, and the troubleshooting guide gains the symptom first entry for the rulvar-orchestrate not registered error.
  • 2abd9c2: A resumed dynamic orchestration now honors the documented mode (c) contract after a budget cancelled root. Recovery is orchestration scoped instead of attempt scoped: journaled spawn decisions recover across root attempts (they live at the orchestrate call's own stable scope), recovered children re dispatch pinned to their journaled child scope so settled ones replay by content key for free and only dangling ones rerun, prior attempt handles alias to the recovered records so a restored transcript's await and cancel calls keep working, and the rerun root boots from the cancelled attempt's last turn boundary checkpoint instead of re planning from scratch. A regenerated turn that diverges from a lost one decides fresh (the recovered verdict binds only when the incoming spec matches the journaled one). Previously the rerun derived its recovery scope from the new dispatch seq, saw nothing, re decided every spawn, and re paid completed children.
  • 1c1175d: An agent configured with a required terminal tool (the dynamic orchestrator's finish) no longer settles ok on a turn that ends without any tool call. Such a turn, including one cut by the output token bound before any call, now consumes the no progress budget and re prompts the model toward the tool, so orchestrate() returns ok only after a validated finish({ result }) was intercepted; a model that never complies terminates as a bounded typed limit, never as ok with unproven output. The forced finish exhaustion path keeps synthesizing its documented partial. Ordinary ctx.agent calls without a terminal tool are unchanged.

1.6.0

Minor Changes

  • df416fc: Correct and extend model pricing: GPT-5.6 entries, long-context tiers, no fabricated prices, no double-charged cache.

    • Pricing gains optional long-context tiers (PricingTier): the highest threshold strictly below the full prompt re-prices the entire request, input-side rates (cache included) scaling by inputMultiplier and the output rate by outputMultiplier. Existing linear rows are untouched.
    • @rulvar/openai seeds gpt-5.6-sol and its gpt-5.6 alias with the official caps and pricing (1,050,000 context, 128,000 max output, $5/$0.50/$30 per MTok, $6.25 cache write, 2x input and 1.5x output above 272K input tokens). Previously the unknown-model fallback silently priced them as gpt-5.4.
    • Unknown model ids in both first-class adapters keep conservative transport caps but no longer receive a fabricated price row: their usage surfaces in CostReport.unpriced and a USD ceiling warns that it cannot bound them. Provide a versioned createEngine({ pricing }) row for hosted models the tables do not know yet.
    • priceUsdOf no longer double-charges cache tokens: under the Usage invariant inputTokens is the full prompt, so the input rate now bills only the uncached remainder while cache reads and writes bill at their own rates (a row without cache rates bills them at the input rate). Cache-heavy runs previously over-attributed cost by the full input rate on every cached token.
    • Admission reserve estimation routes through the same priceUsdOf, so estimates and settled costs share one formula, tiers included.
    • Model id resolution picks the longest matching table prefix, so a dated gpt-5.5-pro-... snapshot resolves to the pro entry, never the shorter gpt-5.5 sibling.
  • a737810: Make budget admission projected and add a pre-dispatch output bound (layer 2b).

    • Projected admission (layer 1). A spawn is admitted only when spent + committedReserve + finalizeReserve + proposedReserve fits the ceiling of every account in its ancestor chain, checked atomically before anything commits. An exact fill is allowed; one dollar past the ceiling is not. Previously the proposed reserve was not part of the check, so the first call under a budgetUsd: 0.001 run with a 0.01 estimate was admitted and one full provider turn was paid (10.5x the ceiling in the live reproduction). The denial happens strictly before any provider dispatch, journal entry, spawn counter, or reserve commit.
    • Pre-dispatch output bound (layer 2b). Every turn's wire maxOutputTokens is clamped to min(model capability, limits.maxOutputTokensPerTurn, budget-derived limit), where the budget-derived limit is what the tightest remaining ceiling in the chain buys at the serving model's output price (long-context tiers included) after a heuristic prompt-cost estimate. A turn is denied outright only when the remainder cannot buy one output token at zero input (exact, no heuristic); when only the prompt estimate says the turn does not fit, it dispatches with a one-token output floor and the exact layers settle the difference. RunBudget.maxAffordableOutputTokens and the pure affordableOutputTokens helper are new public API; BudgetHooks gains the optional hook.
    • Reserves never exceed what a spawn can spend. A child with its own sub-account ceiling reserves at most that ceiling; a capped orchestrator reserves its cap minus the committed finalize carve-out (the forced finish has its own reserve); an unpriced model reserves nothing unless an explicit estCost is given, because a USD ceiling cannot bound it anyway (the existing loud warning and CostReport.unpriced still apply).
    • admissionReserveUsd accepts maxOutputTokensPerTurn and clamps the priced worst-case output term with it, so hosts can bound reserves through limits instead of hand-written estimates.

    Migration: runs whose ceilings are smaller than their spawns' reserves now fail fast at admission with BudgetExhaustedError instead of overshooting. Give calls realistic estCost hints (see the updated Quickstart), set limits.maxOutputTokensPerTurn, or raise the ceiling.

  • 9eb66b4: Scope the dev-mode bare-nondeterminism detector to the workflow's async context.

    The RULVAR_BARE_DATE_NOW / RULVAR_BARE_MATH_RANDOM detector patched Date.now and Math.random per execute inside a process-global window and restored them on exit. Anything on the event loop during that window (host code, telemetry, code entirely unrelated to the run) could trigger a false warning, and two overlapping runs could race the patch/restore pair, leaving a stale patched global installed forever that then warned outside any run. The published Quickstart reproduced a false RULVAR_BARE_DATE_NOW this way.

    The globals are now patched once per process (dev mode only, never restored) and attribution rides an AsyncLocalStorage store entered around the workflow body: only code inside a run's own async context can warn, at most once per run per global. Host code running concurrently with a run, engine internals awaiting the result, and other runs are structurally silent; the node_modules exemption for provider SDKs and installed dependencies stays as the secondary check. Direct Date.now() / Math.random() inside workflow code still warns exactly as before.

Patch Changes

  • da4dbad: Write the product name as Rulvar in prose: package READMEs, npm descriptions, and the documentation site now capitalize the brand. Identifiers keep their exact casing, so package names, the rulvar binary, rulvar.config.mjs, the .rulvar store directory, the rulvar.* OTel attributes, and every URL are unchanged. Documentation and metadata only; no runtime behaviour changes.

  • 487da86: Align every budget claim with the enforced contract.

    One precise formulation now appears everywhere the budget is described (README, docs landing, quickstart, budgets guide, design principles, invariants table, and the RunOptions.budgetUsd API comment): an immutable run budget with pre-dispatch reservation (projected admission, exact fill allowed), a budget-derived maxOutputTokens clamp on every turn, live stream cuts on crossing, and a documented provider-dependent residual overshoot of at most one clamped in-flight turn per concurrent agent. No surface claims a literal hard dollar cap without stating the bound in the same breath.

1.5.2

Patch Changes

  • 54936a0: Assemble the Slack and Google credential samples in the masking policy test at runtime so public secret scanners stop flagging the source blob; the runtime strings the policy masks are unchanged.

1.5.1

Patch Changes

  • 6c6d56f: The too-old-journal refusal no longer points at an export that does not exist.

    JournalCompatibilityError with subCode HASH_VERSION_TOO_OLD interpolated the version into a symbol name, so a v0 journal produced the hint enable deriverV0 from @rulvar/compat via extraDerivers. @rulvar/compat ships deriverV0Synthetic; there is no deriverV0. A reader with a genuinely too-old journal was sent to an import that is not there, and a dead end is worse than no hint.

    The hint now names the mechanism and the package, never a symbol, so it cannot go stale when a frozen profile is named something else:

    register a hashVersion 0 KeyDeriver through createEngine({ extraDerivers });
    @rulvar/compat ships the frozen profiles

    Nothing else changes: the refusal is still typed, still raised before any live call, append, or admission reserve, and extraDerivers still reopens the window exactly as before.

1.5.0

Minor Changes

  • 4fba3c7: Cost attribution is now correct for agent calls that span several models, and the two adjacent holes around them are closed.

    • Per-serving-model pricing. The loop, extract, finalize, and summarize roles resolve independently, so one ctx.agent call routinely spans models at different prices. The whole call was priced at the loop model's rate, which billed a cheap extract as if it had been the expensive loop and made routing extraction to a small model look free of savings. Usage is now split by the model that actually served it, and every fold (the live CostReport, the kernel ledger behind outcome.cost.totalUsd, costReportFromJournal, and replay) prices each slice at its own rate. The split rides the terminal journal entry as the new optional usageByModel field and the turn checkpoint, so it survives a crash and a resume; it is written only when a call genuinely spanned models, leaving single-model journals byte-identical. usage and servedBy were never part of the content key, so identity and replay are untouched.
    • CostReport.byModel is keyed consistently. The live path bucketed by the requested model while the journal fold bucketed by the serving one, so the same run reported two different breakdowns under transport failover. Both now key by the serving model, and AgentResult carries the optional usageByModel breakdown.
    • An unpriced model can no longer escape a ceiling in silence. A model absent from the price table debits nothing, so a USD ceiling does not bound it. That is honest for a local model and a hole for a hosted one whose price row is merely missing: the run now emits a warning-level log event, once per model, naming the model and saying the ceiling does not bound it. Its usage still surfaces under CostReport.unpriced.
    • Routing to an unregistered adapter names the role and the adapters you do have. Every schema-bearing ctx.agent call resolves the extract role up front, so a routing default that crosses providers (the recommended extract default targets OpenAI) failed with a bare "no adapter registered for 'openai'". The error now reads role 'extract': no adapter registered for 'openai' (ModelRef 'openai:gpt-5.4-mini'); registered: anthropic. Pass the adapter to createEngine, or route this role to a registered adapter through defaults.routing.
  • 8655c0f: defineWorkflow accepts model, routing, and effort, wiring the workflow-defaults layer the resolution chain always documented.

    The router has always taken a workflow layer and the model routing guide has always described a four-layer chain (call override, agent profile, workflow defaults, engine defaults), but nothing could populate layer 3: defineWorkflow took only { name, args, errorPolicy }, so a workflow could not carry a model policy of its own. It now can, which is what you usually want for a whole class of work ("triage is cheap; the incident report is not") instead of repeating the routing on every ctx.agent call.

    ts
    const triage = defineWorkflow(
      { name: 'triage', routing: { loop: 'anthropic:claude-haiku-4-5' } },
      async (ctx, args: { issues: string[] }) =>
        ctx.parallel(args.issues.map((i) => () => ctx.agent(`Classify: ${i}`))),
    );

    The layer rides the scope, so it follows the call tree, not the file: a child spawned through ctx.workflow contributes its own defaults inside its scope and they stop at its boundary. It sits under the agent profile and the call override and over the engine defaults, exactly as documented, and it applies to every invocation role the call resolves (loop, extract, finalize, summarize, and each failover fallback).

    Backward compatible by construction: a workflow that declares nothing contributes no layer and resolves precisely as before, so existing journals keep their content keys. A CompiledWorkflow has no routing surface and contributes no layer.

1.4.0

Minor Changes

  • c4f563d: Production readiness fixes from the July 2026 full audit.

    • The budgetUsd ceiling now survives resume: the engine records it in RunMeta.budgetUsd and restores it on every resume, so the replayed spend counts against the original invocation's bound and ResumeOptions still exposes no way to raise it. Journals written before the field existed (or read through a store that drops optional RunMeta fields) resume uncapped, exactly as before; the conformance kit gains a round-trip check so custom stores cannot drop the field silently.

    • spawn:rejected and resolution:applied / resolution:superseded are now emitted: live admission rejections carry the rejection code, agentType, and the journaled decision entryRef (absent only for pre-admission config gates), and live resolution attempts report winning or losing the first-closing-wins fold. spawn:admitted now carries the decision entryRef and the admitting verdict arm. The orchestrator:budget union member now types the two payload shapes actually emitted; journal:compat stays declared but unemitted (the scan runs before a run's event stream exists) and its TSDoc says so.

    • toOtel implements real parent-child span nesting when contextApi and setSpan are passed; without them spans stay flat but attributed.

    • 'readonly' isolation now compiles a deny rule for tools declaring risk write or destructive into the spawn's permission chain, exactly as the tools guide documents; read tools and other isolation modes are unaffected.

    • VCR replay() refuses a cassette recorded outside the engine's hashVersion support window ([CURRENT-1, CURRENT]) with a typed ConfigError instead of silently drifting; in-window cassettes replay as before.

    • InMemoryStore accepts { quiet: true } to opt out of the durability warning, and the warning text now states the precise truth: nothing survives a process exit and cross-process resume is impossible (same-process resume of a kept instance works). createTestEngine constructs its store quietly, so the blessed offline tier no longer prints a misleading warning.

    • The bare Date.now() / Math.random() development warnings no longer blame workflow code for calls that originate in library internals (the engine's own retry jitter, provider SDKs): the retry jitter uses a natively captured Math.random, and the in-process guard skips callers that live under node_modules.

    • rulvar run --profile now applies the profile's per-role effort hints: entries in defaults.routing that carry no effort are seeded from RunProfile.effortByRole (an explicit host effort always wins; ladder entries and unrouted roles stay untouched).

    • rulvar --help documents the shipped kb inbox and kb gate subcommands.

    • The unscoped rulvar pointer package ships TypeScript declarations (index.d.ts with a types export condition), so strict TypeScript projects can import the bare name; the install smoke gate now packs and checks the pointer alongside the umbrella.

1.3.2

Patch Changes

  • ddef383: Every published package now ships a README, so its npm page states what the package is, how it installs, and where the documentation lives (npm includes README.md in the tarball regardless of the files allowlist, so no manifest changes are involved; @rulvar/compat gains its README on its own next release). Alongside, the repository-level pages are refreshed to the current project state: the root README is rewritten around the never-pay-twice pitch with a runnable quickstart condensation and the full package table, CONTRIBUTING.md lists the complete PR gate set, the examples README drops retired-spec citations for live docs.rulvar.com links and documents the dogfood journal replay, and the pointer README gets the same treatment.

1.3.1

Patch Changes

  • 7d1552e: Runtime message strings no longer cite the retired internal specification set: error and warning messages, validation issues, and the CLI help text drop the dangling docs/NN, section ... references, pointing at https://docs.rulvar.com pages where a pointer earns its place (the CLI help header, tool naming, toolset registries, bare resume). The umbrella package description sheds the naming-contingency note: the unscoped alias is published and owned. Three strings embedded in frozen recordings stay byte-identical on purpose (the no-progress abort reason and two testing-internal recorder strings), as does the byte-locked golden-fold fixture. Test-file comments lose their citations too; test titles are unchanged.

1.3.0

Minor Changes

  • 7d1a287: ModelKnowledge phase 3, first slice (M12-T02, unlocked by the passed measured-value checkpoint): the kb_propose orchestrator tool and the quarantined modelObservations write path. PlanRunner registers kb_propose on explicit opt-in (PlanRunnerOptions.kbPropose, like any opt-in tool); its payload is tier-relative (the orchestrator never names a model) and the engine resolves the tier against the referenced lineage's declared ladder into the concrete KbProposal subject, validates that the tier has a journaled attempt and that evidence refs resolve to this run's decision entries, and journals the proposal as the observation_add ledger.op through the single-writer path. Quarantine is absolute: the ack is entryRef only, ledger_read withholds observation content behind a count (byte-stable for observation-free renders), worker prompts never see it, and nothing can commit during a run (the runtime handle has no write path by API shape); proposals reach the human gate only through the post-run LedgerExport. Core exports KbProposal, KbProposalTrigger and the typed model-free proposalStatement template. The kb-propose-quarantine cassette joins the frozen catalog (61 IDs).

1.2.0

Minor Changes

  • 890f42c: The knowledge card gains the profile-evidence section (docs/05 section 4.3 as amended): eval-measured claims project onto the advertised spawn vocabulary, one line per concrete-model profile with a conservative weakness-over-strength fold across efforts, plus a fixed spawn-guidance line. FR-607 commits the card to feeding agentType choice at spawn, and the M12 checkpoint measured that tier-relative rows alone carry no agentType-actionable signal (criterion 2: equal quality, cost overhead, no steering). Ladder declarers and model-less profiles do not participate; the section renders only when at least one profile line exists, so every previously recorded card stays byte-identical; model names still never render.

Patch Changes

  • 3bfaec0: A capped orchestrator dispatches its own agent with estCost equal to its effectiveCap, and the forced-finish agent with the finalize reserve (docs/07 section 12.2 as amended): layer 2 makes those the true admission worst cases. Without the hints the default reserve priced the model's full maxOutputTokens (about one dollar on strong tiers) and the commitment rode the whole ancestor chain for the orchestrator's lifetime, so small run ceilings sat at zero admission remainder and every child spawn died with a budget rejection. Found live by the M12 checkpoint: no orchestrated child was ever admitted under the case ceilings, and both A/B arms measured a self-solving orchestrator instead of agentType selection.
  • 154507b: TSDoc and inline comments no longer cite the retired internal specification set (the pre-docs-site docs/NN, section ... references). The citations either became links to the public documentation at docs.rulvar.com or were dropped where the comment already carried the rule; traceability markers (DEF-n, XF-nn, FR-nnn, OQ-nn, W-nnn) are untouched. Comment-only change: no runtime behavior, no API shapes, and no runtime message strings were modified; the frozen golden-fold fixture is byte-identical.

1.1.0

Patch Changes

  • d16b04a: Plain orchestrate treats ladder-declaring profiles as declaration-only (docs/07 section 10 as amended): the spawn vocabulary in the profile card advertises concrete profiles and lists declarers on a separate context line, and spawn_agent naming a declarer is rejected with a typed ConfigError before admission instead of dying later at wire resolution. Found live by the fifth M12 checkpoint run: the knowledge card praises ladder tiers by profile name, so the card-informed arm kept spawning the declarers and measured far below the uninformed baseline.

1.0.0

Major Changes

  • 464ab6e: rulvar v1.0.0: the first published release. An embeddable TypeScript engine for durable, budget-bounded, testable multi-agent LLM workflows: an append-only journal with byte-deterministic replay and crash resume over JSONL or SQLite (multi-process workers with lease fencing), hermetic VCR cassettes gating CI through a frozen 60-cassette defect catalog, hard per-run USD ceilings with orchestrator sub-budgets, finalize reserves and admission control, adaptive orchestration (typed plan revisions with rebase, escalation protocols, model ladders, wake digests, lineage and reuse), ModelKnowledge phases 1 and 2 (the git-reviewed model-suitability claim store with TTL decay, eval-measured claims from matrix sweeps, canary fingerprints, and the one-rung-clamped verified layer), provider adapters for Anthropic, OpenAI-compatible and Google plus a Vercel AI SDK bridge, an eval framework, and the rulvar CLI (run, resume, runs, inspect, plan, kb). Licensed Apache-2.0. The six core SPI seams are frozen; ModelKnowledgeStore freezes with this release per docs/05. Ships the M9 through M11 scope together per the 2026-07-11 amendment to docs/12 section 2.

Minor Changes

  • 0e0b569: M10 entry: the render budgets of docs/06 Appendix A are committed (the TBD-before-M10 rule) and wired as engine defaults; OQ-04 (the renderBudget measure) closes on the CHARACTER measure.

    • WakeDigest: 400 chars per outputSummary row, one exported constant (WAKE_SUMMARY_RENDER_BUDGET_CHARS) now serving both the distillation cap (adopted unchanged, the value frozen into every cassette since M6) and the digest render default of renderBudgetChars, which stays overridable per orchestration.
    • ledger_read render: 65536 chars over the serialized view via the new pure boundLedgerRender (exported with LEDGER_RENDER_BUDGET_CHARS): over budget, rows drop deterministically oldest-first (auto-derived joins before authored sections, the mission brief slices last) and every drop renders as a FLAGGED discrepancy line. The section caps stay the primary bound, so under default termination limits the belt never engages; all frozen fixtures are byte-identical.
    • KB card: 4096 chars, committed in docs and consumed by the M10-T03 card renderer.
  • b28b7a3: M10-T01: the ModelKnowledgeStore SPI and the default file store (docs/05, sections "Data model" and "Commit discipline"). The engine-scoped, per-project, append-only claim store lands as a new SPI seam, a neighbor of JournalStore, freezing with knowledge-base phase 1 post-1.0 (never touching the six frozen core seams).

    • ModelKnowledgeStore { current; commit(ops, expectedVersion) } with CAS on the monotonic snapshot version, mirroring the lease fencing discipline; concurrent commits serialize through the retryable KnowledgeCasError and rebase. There is NO propose() method in the SPI at all, and the runtime handle type ModelKnowledgeHandle = Pick<..., 'current'> physically lacks commit (docs/05 security channels 2 and 3).
    • The full docs/05 claim data model as types: ModelClaim (subject with effort as part of identity, mandatory taskClass and evidence, TTL fields, append-only supersede), GateRecord (the human variant does not assemble without the attribution attestation), ClaimOp, EvidenceRef (entryRef is the journal seq), KnowledgeSnapshot. The TaskClass vocabulary upgrades from bare string to the docs/05 union (the six floor-aligned classes plus open extension), canonically resident with the knowledge SPI and re-exported by the floors module.
    • FileModelKnowledgeStore defaulting to ./rulvar.models.json: git-diffable pretty JSON with atomic temp-plus-rename replace; append-only mechanics (supersede and archive flip status, never delete, preserving the audit trail); referential integrity as typed ConfigErrors; the empty snapshot (version 0) when no file exists.
  • b53a89e: M10-T02: the editorial claim path, validated (docs/05, sections "Data model", "The human gate", "Grounding and decay"). The runtime enforcement the T01 types promise:

    • A gated op without the attribution attestation is now a RUNTIME error at commit, not only a type error: the human gate requires a non-empty ruledOut checklist over the docs/05 vocabulary, and the eval-confirmed gate rejects as reserved for v2.
    • The editorial path is the only committable path in phase 1: eval-measured claims and the metrics block reject until the M11 eval-committer identity ships (the validators already model the identity flag M11 will pass).
    • The active-claims cap holds at commit: 8 per (model, taskClass) by default (docs/06, Appendix A), configurable per store; supersede chains keep only the head active, so a supersede never grows the count.
    • Statement bounds (200 chars), mandatory evidence and taskClass, date coherence, and the asymmetric TTL table land as pure helpers: claimExpiry (eval 90/30, editorial 120/45 days by polarity) and claimExpired for the read-path filters of M10-T03.
  • 4454175: M10-T03: the ModelKnowledge read path (docs/05, sections "Read path" and "Security"). kb_pinned and kb_repinned land, the card renders, and the whole feature is store-gated: an engine without stores.modelKnowledge writes no kb entries at all, so every existing journal and cassette stays byte-stable (zero added awaits on the off path).

    • createEngine accepts stores.modelKnowledge; the runtime holds ONLY the current() handle (commit is physically absent inside runs).
    • One read at run admission for orchestrate-role runs: the engine filters claims (active, unexpired, reachable through the run's declared ladders after the role-floor filter) and journals kb_pinned { version, hash, cardText } with the card bytes EMBEDDED, strictly before the first orchestrator turn. Resume and replay read the entry bytes and never touch the live store.
    • A fresh kb_repinned lands on every wait_for_events wake under the same filtering rules against a FRESH store read, so expired, stale, and archived claims never steer spawns after pauses; a mid-run store commit affects only subsequent pins.
    • modelKnowledgeCard: deterministic, two-layer, tier-relative, 4096-char budget (oldest notes withhold behind an explicit marker). The verified layer compiles EXCLUSIVELY from eval-measured claims (empty in phase 1) with the one-rung clamp; editorial notes render dated and explicitly marked, never compiled into a tier; the orchestrator never sees model names. The card docks into the spawn tool description beside the profile card.
    • OQ-11 closes: editorial notes render for every taskClass with no self-description suppression (the nameless tier-relative render already blunts the feared bias).
    • Two catalog cassettes (docs/09, new section 6.11): kb-pin-replay and kb-repin-expiry, recorded offline over a deterministic stub store with time-stable dates; the cassette-catalog CI job runs them.
  • 6599ca8: M10-T05: the taskClass binding interim rule becomes the phase-1 resolution (docs/05, section "Phases and placement"; docs/14 OQ-12 CLOSED). The classification source is author declaration: the optional taskClass on AgentProfile, TaskSpec, and spawn_agent params; absence means unclassified and stores no literal string anywhere. Card recommendations never apply to unclassified spawns (in phase 1 no recommendation application exists at all; the M11 compiler inherits the rule as normative).

    • The plan dispatch now forwards the declared TaskSpec.taskClass onto the ExtensionDispatchSpec, completing the substrate: a declared class journals inside the spawn-admission decision (spawn_agent path) and the plan.revision spec of record (PlanRunner path), so M11 matrix sweeps and the recommendation compiler slice attempts by class from journals alone.
    • Byte-neutral: journals without declared classes are unchanged; floors stay profile-driven per docs/04.
  • 6649e5f: M11-T01: the eval-committer identity activates eval-measured claims (docs/05, sections "Data model" and "Commit discipline", amended with the dedicated eval-committer GateRecord variant, distinct from the v2-reserved eval-confirmed proposal auto-gate).

    • Commit validation is now GATE-DRIVEN and the coherence square is schema-enforced in both directions: an eval-committer-gated op MUST carry class eval-measured, author kind eval-pipeline, and the metrics block; a human-gated op MUST NOT carry any of the three (a human-authored op with metrics keeps rejecting). Observational data never carries metrics and never auto-promotes.
    • @rulvar/evals ships the pipeline side: evalMeasuredClaim (the docs/05 TTL table applied by polarity: strength 90 days, weakness 30) and commitEvalMeasured with the documented CAS-rebase recipe against any ModelKnowledgeStore.
  • fd2f83b: M11-T03: TTL and staleness (docs/05, section "Grounding and decay"). The decay module (src/knowledge/decay.ts) becomes the decay owner: the asymmetric TTL table (eval 90/30, editorial 120/45; inbox 14 days exported as a constant, reserved for M12) and claimExpiry/claimExpired move there with their names re-exported through the claims module unchanged.

    • The re-measurement queue lands as documented: remeasureQueue(claims, at) is JUST a status filter over expired, still-active eval-measured claims (nothing archives them: the next sweep re-measures the subjects); ttlState feeds maintenance views.
    • Archive-never-delete maintenance: archiveDeprecatedModelOps(claims, models) produces archive ops (reason deprecated) for every live claim of a deprecated model; historical runs keep their audit trail.
    • Expiry stays enforced at every pin AND repin through the M10-T03 read-path filter; the acceptance test drives the same filter across the boundary clock: an expired claim stops influencing the card at the next pin or repin.
  • 01d6b2d: M11-T04: modelEpoch capture and the canary fingerprint (docs/05, section "Grounding and decay"; OQ-06 CLOSED with the committed design).

    • Core: modelEpochOf/capsHashOf build the honestly coarse epoch signal (registry version, pricing version, caps hash; silent alias re-pointing stays a documented uncaught case absent probes). The ClaimOp union gains mark_stale (docs/05 amended): section 6 requires status stale at fingerprint drift and the closed op set could not produce it; active flips to stale, already-stale is an idempotent noop, terminals never revive.
    • Evals: canaryFingerprint(engine, probes) runs the FIXED caller-versioned probe set sequentially through the ordinary engine and hashes NFC-normalized, whitespace-collapsed outputs (the probe count prefixes the hash so probe-set edits never collide with drift). flipStaleOnCanaryDrift flips the model's active eval-measured claims whose recorded fingerprint differs, in one CAS-rebased command; claims without a baseline stay untouched. Sweeps stamp the epoch per pool member via modelEpochFor.
  • 9a20dbb: M11-T06: the verified-layer compiler goes public (docs/05, sections "Read path" and "Composition with the model layer"). compileVerifiedLayer(claims, ladders) compiles start-tier recommendations per (ladder, taskClass) EXCLUSIVELY from eval-measured claims with the one-rung clamp (the price of any false belief stays one rung; ties hold the default and compile nothing; editorial claims never compile); the card renders from it and future consumers read the structured rows, never the card text. Floors and ModelCaps stay hard; budget is touched only through the existing admission path.

    Property-tested over seeded random snapshots: no compiled recommendation ever exceeds one rung of displacement or leaves the ladder, editorial-only snapshots compile to nothing, and compilation is deterministic. The M11 OQ sweep rides along in docs/14: OQ-09 closes with the defined M12 gate criteria (A/B sweeps, rung and agentType selection against the no-card baseline); OQ-07, OQ-08, and OQ-10 carry honestly (their triggers cannot fire while every release is founder-deferred).

  • 0fbe7ea: M9-T04 (part 1): the DEF-2 and DEF-3 catalog rows deferred at M7 (docs/09 sections 6.2 and 6.3; docs/10 M9 row "Complete catalog green in one CI run"), plus the producers and liveness fixes the rows exposed.

    • Nine new frozen cassettes with public runners and byte-for-byte replay tests: combined-loop-descent, config-drift-resume, class-storm-single-turn, oscillation-bounded, race-timeout-vs-live (DEF-2); respawn-preserves-counter, reworded-lessons-collide, stall-streak-classes-and-pinning, legacy-journal-resume (DEF-3). The class and race rows additionally round-trip their frozen bytes through BOTH reference stores (JsonlFileStore and SqliteStore) with identical loads, per the store-independence rule.
    • @rulvar/plan: the class-level escalation decision producer lands (docs/07 6.5): two or more same-kind reports resolved by ONE revision merge into ONE escalation-decision entry with per-lineage debits rows and resolvedBy 'class'; a denied per-lineage debit degrades the group to single-target decisions so denial semantics stay per report. The folds already consumed this form; single-target behavior and all existing cassette bytes are unchanged.
    • @rulvar/plan: termination:config-drift now actually fires on resume when a live termination knob diverges from the journaled termination.init (the journal wins, the divergence is reported per field; docs/07 11.2). Events are never journaled, so frozen cassettes are unaffected.
    • @rulvar/plan: a retry escalation decision re-opens the node AND clears its stale dispatch handle; previously the re-opened node sat ready forever while the scheduler skipped it (the re-dispatch liveness gap behind Flavor B defaultDecision retry).
    • @rulvar/plan: lesson_add keys once (docs/07 9.2): a repeated add with the same content key acks the recorded lesson instead of appending a duplicate; re-executed-turn recovery is unchanged.
    • @rulvar/core: an extension dispatch whose agent dies BEFORE its root entry lands now surfaces the underlying failure loudly to the dispatching caller instead of hanging the dispatch await forever (the pre-root cousin of the stale-writer liveness rule). Healthy paths and replays are byte- and timing-identical.
    • Known residual, unchanged: repeated Flavor B suspensions on ONE re-opened node dedup onto the first suspension's decision key; the recorded cassettes route around it and the at-cap immediate-resolution flavor rows stay with M9-T04's later parts.
  • ebe0abc: M9-T04 (part 2): the six DEF-5 catalog cassettes (docs/09 section 6.5; docs/03 section 9), plus the reuse-producer completions the rows forced.

    • Six new frozen cassettes with public runners and byte-for-byte replay tests: oscillation-full-reuse (escalated-terminal donor, shared full link, by-ref root, reclaimedUsdAtLink carries the donor spend), graft-partial-subtree (a three-rung limit ladder severed mid-top-rung grafts exclusively; the completed rung attempts forward-match through the scope alias and only the interrupted rung reruns live, exactly once), crash-between-link-and-root (cut strictly between the durable node.link and the by-ref root; the resume rolls forward with zero repayment), oscillation-guard-trip (the third re-add at maxOscillationsPerKey 2 rejects osc_guard with the embedded verdict and the run closes non-HITL), worktree-disposed-degrade (an unpinned worktree graft donor degrades to a fresh admit with DedupNote graft_unsafe; reuse_full stays allowed for a worktree donor with a terminal root), claim-exclusivity-and-chain (two identical adds in ONE revision: the first grafts exclusively, the second degrades donor_active; the severed grafted node becomes the chain head and the third add drains the chain transitively; oscillationCount reaches 2).
    • @rulvar/core (docs/03 9.3/9.6 producer completions, folds and bytes of existing journals unchanged): evaluateReuse now skips exclusively-claimed donors (first-wins) and degrades to a fresh admit with the documented donor_active reason when every candidate is captured; a severed grafted node inherits its captured link's chain (ancestry plus chain-tail graft eligibility), so the next add links to the chain head and drains transitively; agent dispatch roots record their resolved isolation (value.isolation, only when not 'none') so the DedupIndex worktree rules can read it from the journal.
    • @rulvar/plan: exclusive captures are first-wins WITHIN one revision too: the second identical add of the same revision degrades to donor_active instead of double-claiming the donor.
    • All fifteen M9 cassettes re-record byte-identically under the double-run agreement; the nine part-1 fixtures are untouched by the producer changes. fixtures.sha256 covers 50 frozen files.
  • a3079d0: M9-T04 (part 3): the six DEF-8 catalog cassettes plus the DEF-7 reserve-survives-run-exhaustion row (docs/09 sections 6.7 and 6.8), with the roll-forward and reserve producers the rows exposed.

    • Seven new frozen cassettes with public runners and byte-for-byte replay tests: revise-racing-defaultDecision (the mandatory stale-wake trio dropping dep_already_resolved with blockingRef, node_escalated, node_already_done in ONE revision), crash-after-append-before-effects (the pre-effects kill point; both children spawn live exactly once on resume and the request-only cancel lands on the redispatched branch), amend-vs-running-then-cancel-add, intra-revision-self-conflict (sequential intra-revision semantics), bad-base-streak-terminates (three fabricated-base all-dropped entries then the non-HITL guards fallback), park-races-child-completion (parkRequested extinguished by the child-result transition, no park retention), and reserve-survives-run-exhaustion (adds that would invade the committed finalize reserve drop admission_denied inside the revision outcomes; the forced finish executes FROM the reserve and closes the run ok).
    • @rulvar/plan: the idempotent plan_revise recovery path now also re-lands request-only cancels and parks by aborting the redispatched mid-flight branch; previously the crash-after-append-before-effects roll-forward left the cancelled branch running forever.
    • @rulvar/plan: an accepted escalation resolution records the node's done reference (doneRefs), so a later waive_dep against the resolved dependency drops dep_already_resolved with the blockingRef pointing at the resolving reference, exactly like a child-result transition.
    • @rulvar/core: the forced finish now RELEASES the finalize reserve as it begins (releaseFinalizeReserve): the reserve stops subtracting from the admission remainder at the moment it is being spent, or the finalize agent could never draw the money reserved for it under a tight run ceiling. Admissions stay frozen past the cap, so nothing else can take it. Cap behavior under unlimited ceilings (all existing cassettes) is byte-identical.
    • All 22 M9 cassettes re-record byte-identically under the double-run agreement; fixtures.sha256 covers 57 frozen files.
  • 596a39b: The project is renamed to rulvar (the founder decision of 2026-07-11 closing OQ-24; the official domain is rulvar.com). Every package moves to the @rulvar scope (the umbrella is @rulvar/rulvar, the ESLint plugin is eslint-plugin-rulvar), the CLI binary is rulvar, the config convention is rulvar.config.mjs, the knowledge store default is rulvar.models.json, the default journal directory is .rulvar, engine warnings use the RULVAR_ prefix, and the orchestrator workflow name is rulvar-orchestrate. Because journaled bytes embed the workflow name and content keys, the entire frozen catalog (60 cassettes and the dogfood journals) was re-recorded under the new name and re-frozen; the turbo lint task now orders after upstream builds (a latent race the rename surfaced). Nothing was ever published under the former name, so no consumer migration exists.

0.9.0

Minor Changes

  • 84f94d4: The v0.9.0 BREAKING release notes (M8 server and queue; the flagged BREAKING sections of the pre-1.0 convention, docs/12 registry).

    BREAKING: TranscriptStore gains the REQUIRED delete(ref) method (docs/03 12.4; the OQ-20 interim rule executed at M8-T04: retention is impossible without blob deletion, and JournalStore.delete alone would orphan every transcript). How it fails: third-party TranscriptStore implementations stop compiling against the widened SPI. Migration: implement delete(ref); deleting a missing ref MUST be a no-op, never an error; the cascade over a run's blobs stays ENGINE-side (Engine.deleteRun), never a store obligation. The shipped InMemoryTranscriptStore and FileTranscriptStore already implement it.

    BREAKING: the Engine interface gains required members stores, deleteRun, and pruneRun (docs/06 10.2; the M8 seam and retention amendments: the shells read the run picture through the engine's stores, and retention needs the cascade and the checkpoint pruning as first-class engine operations). How it fails: custom Engine implementations and structural Engine test doubles stop compiling; ordinary consumers of createEngine are unaffected, and ResumeOptions.lease stays additive-optional. Migration: expose the configured stores and delegate deleteRun/pruneRun to the underlying engine (the pattern in @rulvar/testing's createTestEngine).

  • 65c7b2c: M8-T01: createServer, the HTTP shell (docs/02 section 8.2; FR-702), plus the Engine.stores seam it stands on (docs/06 10.2, M8 entry amendment).

    • @rulvar/cli: createServer({ engine, workflows }) returns { fetch(req: Request): Promise<Response> } with the five canonical routes: POST /runs (start a registered workflow), GET /runs/:id (status and outcome), GET /runs/:id/events (SSE; Last-Event-ID maps to the event seq, replay is at-least-once and consumers deduplicate on replayed), POST /runs/:id/external/:key (programmatic resolution, by: 'external'; a run that settled suspended in-process auto-resumes; a run not live in this process gets the documented offline append under a lease where the store is leasable, and resumes on a worker), GET /runs/:id/cost (the settled in-process CostReport, or the pure journal fold priced by the optional priceUsd). Authentication stays host middleware (docs/14, OQ-16).
    • @rulvar/core: the Engine interface gains the readonly stores accessor exposing the configured journal and transcript stores; exactly the instances createEngine received (or defaulted), no store contract widens.
    • @rulvar/testing: createTestEngine forwards the new stores accessor.
  • a2a3243: M8-T02: createWorker, the queue shell (docs/02 section 8.3; FR-703), plus the two queue seams it stands on (docs/06 10.2 and docs/03 12.3, M8 entry amendment).

    • @rulvar/cli: createWorker(engine, { store: LeasableStore, concurrency? }) leases resumable and suspended runs via acquire/renew/release with fencing epochs (renew cadence ttl/3; Appendix A reference ttl 60000 ms; concurrency default 1). A store without lease capability is a typed ConfigError at start, never a silent split-brain; leasing a store other than engine.stores.journal is equally a ConfigError. DEF-6 repeats at acquire: a journal outside the hashVersion window releases the lease and poisons the run for this worker. Stateless workers call bare engine.resume with the lease; unchanged suspended runs are skipped until their journal grows; queue semantics stay honestly at-least-once with deduplication by the journal. The OQ-21 residual (original in-process args are not journaled) is bridged by the optional argsFor hook.
    • @rulvar/core: ResumeOptions.lease carries the worker's lease through the kernel's single append site, so a stale writer's appends are rejected by the fencing epoch and never become visible (lease theft impossible by construction); bare engine.resume(runId) now falls back from the persisted CompiledWorkflow source to defaults.workflows[workflowName] (the registry the queue worker resolves through, docs/06 10.4); the Replayer accepts the lease option.
  • ebc8101: M8-T04: the redaction and retention interim rules executed (docs/14 OQ-20 and OQ-22; docs/09 section 8 rewritten to the executed state; docs/03 12.4 and 12.8; docs/06 10.1 and 10.2 amendments).

    • @rulvar/core: the L0 SerializationHook (createEngine({ serialization })): redact/encrypt at the append/put boundaries, symmetric on load/get, applied by wrapping the stores so Engine.stores exposes the one policy point; kernel ordering fields are drift-checked with a loud ConfigError. Default key masking at the telemetry boundary: every emitted WorkflowEvent passes maskSecrets (provider keys, PATs, bearer tokens, JWTs, private-key blocks become [masked-secret]); opt out via redaction: { maskEvents: false }; never touches the journal. Retention: TranscriptStore.delete(ref) joins the SPI (missing ref is a no-op; InMemory and File stores implement it), Engine.deleteRun(runId) cascades blob deletion before the journal (no orphan transcripts), and Engine.pruneRun(runId) deletes checkpoint blobs of ok-terminal attempts that nothing else references (parked, cancelled, escalated, and hanging attempts keep theirs).
    • @rulvar/cli: createServer and createWorker take the opt-in retention predicate over RunMeta (the server applies it at terminal settles, the worker during sweeps under a brief lease); the OTel exporter masks string span attributes with the same policy, defense in depth over the already conservative attribute content policy.
    • @rulvar/testing: createTestEngine forwards deleteRun/pruneRun.

0.8.0

Minor Changes

  • 85d55cf: The v0.8.0 BREAKING release notes (M7 adaptive orchestration full; the flagged BREAKING minor of the pre-1.0 convention, docs/12 registry).

    BREAKING: the unified AdmitVerdict union is extended with the reuse verdicts (reuse_full, admit_graft) and the new reject codes (termination_exhausted, ladder_exceeds_frozen, lineage_exhausted, lineage_busy, osc_guard) (DEF-5). How it fails: exhaustive switches over the verdict kind or reject code in custom shells and admission SPI extensions stop compiling. Migration: add branches for the new arms; reject-code switches should route unknown codes to their generic-denial path.

    BREAKING: reuse-by-reference is the DEFAULT (DEF-5). A byte-identical add_task after a cancel or abandon no longer re-executes the subtree: the result returns by reference (reuse_full) or continues from the paid prefix (admit_graft). How it fails: changed semantics; runs that relied on re-execution against a changed world observe referenced results instead. This is the only intentional change of visible semantics in the pre-1.0 line. Migration: set reuse.enabled: false on the admission config, or fresh: true on the specific add_task.

    BREAKING: the config key maxEscalationsPerNode is renamed to maxEscalationsPerLogicalTask (XF-10): escalations count per logical task across respawns via the lineage chain. How it fails: a typed ConfigError naming the new key rejects the old one. Migration: rename the key; the default stays 2.

    BREAKING: the plan-size-scaled revision budget option is removed without deprecation (DEF-2). maxRevisionsPerRun is an absolute, non-replenishable counter (default 32) debited by exactly 1 per journaled plan_revise; nothing increments it. How it fails: the removed option is rejected at config validation. Migration: size maxRevisionsPerRun directly.

    BREAKING: plan_revise result and error schemas widen (rebase outcomes, embedded admissions, revisionUnitsRemaining) and WakeDigest gains the MANDATORY termination field beside planHash, budget, and reuse (DEF-2/DEF-8). How it fails: schemaHash and toolsetHash of orchestrator scopes change, so VCR cassettes recorded over orchestrator turns invalidate. Migration: re-record affected cassettes; consumers of the digest type add the new mandatory blocks (all-zero outside PlanRunner).

    BREAKING: B0, the run budget ceiling, is immutable after start (DEF-2): no API, including HITL decisions, can top it up. How it fails: code that mutated the run budget mid-run or expected an HITL top-up hits a typed runtime error; overshoot stays bounded by one turn per in-flight agent. Migration: size the ceiling at start; use the orchestrator cap and the finalize reserve (DEF-7) for graceful degradation instead of top-ups.

    BREAKING: PlanRunner requires a resolvable orchestrator cap (DEF-7). orchestratePlanned with no run USD ceiling and no explicit budget.capUsd, or with effectiveCap < finalizeReserve, refuses to start with a typed OrchestratorCapConfigError before any LLM call. Migration: pass budget: { capUsd } (or run under a USD ceiling and rely on capFraction, default 0.2; up to 1.0 opts out explicitly with a telemetry warning).

  • b88c9e3: M7-T02: lineage LogicalTaskId (DEF-3). New src/journal/lineage.ts: LogicalTaskId/LineageRelation/LineageRef/SpawnLineage, AttemptOutcomeClass, LineageStats, SpawnLineageOpt; approach signatures (normalizeApproachTag, approachSigCoarse, approachSigOf, canonicalIsolationTag, sigVersion 1) with prompt prose excluded by construction; EscalationLimits with the committed defaults (maxEscalationsPerLogicalTask 2, maxAttemptsPerLogicalTask 8) and a validator that rejects the pre-rename maxEscalationsPerNode with a migration hint (XF-10); LineageIndex, the incremental pure counter fold (attemptsUsed / escalationsUsed under first-closing-wins and class-decision rules / stallStreak with class skips and resets / approaches grouping), pinnable to a snapshot seq, with deterministic legacy: contentHash LTIDs canonized onto journals written before lineage existed (random ULIDs on replay are forbidden). AdmissionController: AdmitSpec widens (lineage: SpawnLineageOpt, approach, ancestry, signature), evaluateLineage enforces the single-live-attempt invariant (lineage_busy) and monotonic attempt consumption (lineage_exhausted) strictly BEFORE the carrying decision entry is appended, and every non-reject decision now embeds the computed SpawnLineage value block reused byte-exact on replay. ctx.agent and ctx.workflow gain lineage/approach options; a ctx.agent declaration journals one spawn-admission decision entry before dispatch and recovers it on resume without re-minting. budgetDefaults.lineage configures the limits engine-wide.

  • f3c4613: M7-T03: TerminationAccount and the termination lemma (DEF-2). New src/journal/termination.ts: the frozen TerminationLimits vector (V0 32, S0 128, E0 2, D0, kMax from the profile-registry snapshot, B0 immutable, orchestratorCapUsd and finalizeReserveUsd per XF-09) with a validator rejecting the pre-rename maxEscalationsPerNode (XF-10); the debit-only TerminationAccount (no credit operation exists by construction) with per-resource debits embedding balance-after, atomic NEW-lineage allocation (E0 plus K_l minus 1 rungs) on the spawn debit, strictly monotone rung indices, and the debit() surface that writes termination.denied strictly BEFORE resolving an underflow; the variant function Phi with phiInitialOf (V0 + C by S0, C = E0 + kMax); buildTerminationInitValue / readTerminationInit for the termination.init entry; foldTermination, the replay-strict recomputation that rebuilds the account from init, asserts every embedded balance (revisionUnitsAfter, spawnUnitsAfter, escalationUnitsAfter, rungIndexAfter/rungsRemainingAfter) at exactly the diverging entry, debits class-level decision arrays once per lineage, counts timeout defaultDecision resolutions once under first-closing-wins, and collects denials for zero-live-call re-issue; terminationConfigDrift (the journal always wins). AdmissionController gains bindTermination: under a bound account every admitted spawn of any origin debits one spawnUnit atomically with its decision entry (spawnUnitsAfter becomes the account balance), a declared ladder longer than the frozen kMax rejects with ladder_exceeds_frozen, and exhaustion rejects with termination_exhausted; AdmitSpec.ladderLength and the recorded AdmissionDecision.ladderLength feed the fold. The closed AdaptiveEvents catalog (docs/09 section 1.4) joins WorkflowEventBody, including termination:debit / termination:denied / termination:config-drift.

  • a41c20f: M7-T05: PlanRunner scheduling and toolset. Core gains the PUBLIC orchestrator extension seam (docs/02 section 4 seam-sufficiency: orchestration packages build exclusively from the public API): OrchestrateOptions.extension hosts an OrchestratorExtension with boot strictly before the orchestrator's first agent entry, extension tools appended to the mode (c) toolset, an activity hook running after every child settlement strictly before wake evaluation, quiescence participation (nothing running AND nothing ready), digest extras, wake observation, prompt lines, and an OrchestratorExtensionIO exposing total-order appends into extension-owned scopes, the journal snapshot, the single admission point, explicit-scope child dispatch through the ordinary ctx.agent path (plan/NodeId sub-accounts open beside the orchestrator account), settled lookups, cancel, ULID minting, and telemetry. outputSchemaRef/toolsetRef now RESOLVE against the new defaults.schemas and defaults.toolsets engine registries (unknown names stay typed tool errors); TerminationAccount.bindDeniedWriter binds I/O onto fold-rebuilt accounts. @rulvar/plan ships planRunner(options) and orchestratePlanned(engine, goal, opts): boot writes termination.init (frozen limits with kMax and the profile-registry snapshot hash) strictly before the first scheduling entry and binds the account into admission; plan_view renders the pinned pure fold (plan state, per-node LineageStats, the TerminationAccount snapshot) at the last delivered WakeDigest, with digestSeq 0 seeded as the empty-plan bootstrap snapshot; plan_revise (normative docs/07 4.7 schema) debits one revisionUnit per journaled revision (underflow writes termination.denied first), evaluates the committed rebase at the fold head, appends ONE plan.revision strictly before effects, schedules newly-ready nodes under plan/NodeId scopes, lands cancel requests, re-issues idempotently on re-executed turns (roll-forward), and emits plan:revised plus termination:debit; the engine (never the model) schedules ready nodes and journals ready-to-running and terminal transitions as plan.decision entries whose terminal transitions extinguish pending flags; quiescence completes (nothing running and nothing ready). The end-to-end revise-mid-run shape and a full crash-resume with zero live calls and no duplicate entries are covered by integration tests against the public engine API.

  • f4e70be: M7-T07: reuse-by-reference (DEF-5). Core: new journal/reuse.ts with the rich DonorRef (replacing the M6 seq placeholder inside the closed AdmitVerdict union), GraftBoot, DedupNote, ReuseConfig, NodeLinkValue and its content identity (nodeLinkKey over {kind, spawnKey, donorScope, targetNodeId}), the DedupIndex pure fold (severed roots become donor candidates when their pre-abandon effective status is not error, memoized failures excluded, exclusive claims resolve first-wins, plan-node scopes sweep their own branch payments, unpinned worktree donors degrade), evaluateReuse with the four-outcome verdict table (reuse_full | admit_graft | fresh-with-note | reject osc_guard at the link count), and the abandoned-spend ledger fold (abandonedUsd/reclaimedUsd/netLostUsd, per-key oscillation counts). The kernel matcher gains scope-prefix aliasing (docs/03 9.5): registerAlias merges donor-scope candidates into the target scope in journal order at every nested level, and the alias disposition bypasses the abandon overlay so donor entries regain their pre-abandon status ONLY through the alias (the standalone old scope stays skipped); a dangling donor root through the alias IS the graft frontier (rerun-dangling continues from the donor checkpoint). AbandonAttempt carries logicalTaskId (XF-04); the extension IO gains abandonBranch, registerAlias, and priceUsd. Plan: PlanRunner wires the DedupIndex at the fold head under the PlanWriteLock into the rebase dedup hook (transforms embed the verdict, the donor descriptor, and the placement into the revision entry), applies the per-SpawnKey osc_guard rejection, attaches DedupNotes to fresh admits, compiles applied cancel_task (and cancel-landed) into severing abandon entries with lineage attribution, lands node.link entries and by-ref roots in the mandatory write order with idempotent roll-forward, registers aliases (rebuilt by fold at boot), completes full-linked nodes by reference through an engine decision instead of a dispatch, debits a spawnUnit per reuse link, and renders the abandoned-spend view in plan_view (pinned) and the WakeDigest extras; PlanRunnerOptions.reuse carries the docs/03 9.9 config.

  • 75d1646: M7-T08: park and unpark. Core: the internal boot-checkpoint channel lets a FRESH dispatch boot from a retained transcript checkpoint (ExtensionDispatchSpec.bootCheckpointRef; dangling redispatch checkpoints take precedence), serving park/unpark continuation and the DEF-5 graft boot. Plan: new park.ts with the PinLedger fold (live pins counted from abandon entries carrying retainWorktree, park pinning and DEF-5 retention SHARE maxPinnedWorktrees, default 4), parkDispositionOf (checkpoints always retained; worktrees pinned only under capacity, overflow keeps the checkpoint but drops the tree), and unparkPlacementOf (continuation from the retained checkpoint; restart when no checkpoint exists or a worktree-isolated node lost its tree: silent resume against a fresh tree is impossible). PlanRunner lands parks at the turn boundary: a park-requested running child is aborted, the park-landed plan.decision transitions running to parked carrying the checkpoint anchor (set_node_status gains the optional checkpointRef field, applied by the fold), the branch is severed with retainCheckpoint plus retainWorktree per the pin disposition, the dispatch slot frees for the unpark, and node:parked emits. unpark_task applies with the embedded admission: a previously dispatched branch is a lineage rebirth (relation 'unpark-restart' continuing the node's LTID), while a never-started parked node resumes scheduling without consuming an attempt; the unparked dispatch boots from checkpointRefFor(runId, anchor) on the continuation path and restarts otherwise. The park-unpark integration test drives the full shape deterministically (one paid tool turn, park inside the second turn, unpark continuation whose booted history carries the paid turn) plus the pin-cap overflow and placement rows as units.

  • 0627413: M7-T10: ModelLadder full (docs/07 section 10; docs/04 section 12; FR-119/FR-313). Core: ladders now RESOLVE through the chain (canonicalizeLadder validates the declaration once, FR-119 undeclared-judge-rung ConfigError included, and resolves every rung's effort explicitly; ladderRungChoice yields the concrete per-rung ModelChoice; a higher concrete layer shadows a lower ladder and vice versa; a ladder that WINS wire resolution stays a typed ConfigError since rung attempts always carry a concrete override). ladderLengthOf reads the normative declaration points (profile model: { ladder } or the loop-role routing entry). foldTermination debits the rung RESPAWN's embedded admission on raising ladder verdicts (docs/07 11.3 b). New per-engine mechanical gate registry defaults.gates (MechanicalGateProfile over AgentResult.artifacts). The extension seam gains io.random (journaled ctx.random for spot-checks), io.gates, and dispatch fields model (the concrete rung resolution entering the attempt's identity hash), memoizeOutcome, and inline schema for the engine-synthesized judge. Plan: new ladder.ts plus the PlanRunner ladder driver: rung attempts are ordinary agent scopes on the concrete rung model with rung caps binding (tier N+1 = new content key = one live attempt, all sharing the LTID via relation rung-retry registered from the raising verdict's nextAttempt); triggers classify typed (error, limit, schema-exhausted, no-progress first-class via the abort class, verify-failed from gates only); acceptance gates run per ok attempt in declaration order with journaled gate-verdict decisions (mechanical registry profiles, judge on a declared rung >= the executing rung or explicit override with a forced verdict schema and derived identity, spot-check selection strictly via the journaled draw); every ladder verdict is a decision entry computed once live and recovered by content key, so folds consume only journaled values; a denied respawn writes termination.denied strictly before the fallback lands; an ok attempt whose acceptance fails with no raise left lands failed, never done. Mid-flight resume redispatches running nodes through forward matching (dangling attempts continue, settled ones replay instantly): the half-escalated-ladder shape resumes without repaying completed rungs, proven by the truncated-journal test.

  • 55c0f87: M7-T11: EscalationProtocol completion (docs/07 section 6; DEF-2/3/4). Core: Flavor B now REQUIRES an explicit deadlineMs (the knob has no engine default per the frozen Appendix A row; a flavor B spawn without it is a typed ConfigError before any LLM call); SpawnRecord captures the dispatch's escalation flavor and the WakeDigest escalations block reports it (a flavor B report reaching the digest is already decided by the DEF-4 winner). Plan: new escalation.ts with the authoritative escalation-decision entry contract (decide-once per report by content key; countsAgainstLimit derived from the report kind, XF-06; the counting debit atomic with the append embedding escalationUnitsAfter; a DENIED debit writes termination.denied strictly before and flips the entry to capExceeded with countsAgainstLimit: false, so the cap yields the flagged decision plus the final report, never a bare limit, and the folds stay replay-strict). PlanRunner completes the decision flow: the cancel_task revision transform on an escalated node lands the verdict cancel decision, the resolve_escalation plan.decision (origin escalation-live), and the severing abandon strictly after the revision append; a settled Flavor B suspension's DEF-4 winner (timeout defaultDecision by timeout, a live decision, or a class fan-out) is absorbed into the authoritative entry (origins escalation-default/escalation-class) and the fate applies through the single applier (retry re-opens the node in place with the journaled amendedPrompt/startTier honored at re-dispatch, accept closes the paid partial result done, cancel closes cancelled, decompose leaves the node escalated while the proposed children enter through spawn_admitted ops with FRESH lineages and embedded admissions debiting spawn units through the decision entry).

  • fd33871: M7-T12: orchestrator cap and finalize reserve (DEF-7; docs/07 section 12). BREAKING for PlanRunner runs (v0.8.0 registry, docs/12): orchestratePlanned now REQUIRES a resolvable orchestrator cap; a run with no USD ceiling and no explicit budget.capUsd, or with effectiveCap < finalizeReserve, refuses to start with a typed OrchestratorCapConfigError BEFORE the first LLM call and before any journal entries (an uncapped orchestrator was precisely the defect; capFraction up to 1.0 opts out explicitly). effectiveCapUsd = min(capUsd, capFraction x runCeiling), default fraction 0.2. The engine writes ONE orchestrator_budget_reserve decision entry strictly after termination.init and strictly before the orchestrator's first agent entry, freezing the cap and the finalize reserve (explicit, or finalizeTurns x the deterministic per-turn estimate) in absolute dollars, recovered by content key on resume and never re-evaluated. The reserve registers on the orchestrator account AND the run root (kept separate from committedReserve; the admission block checks add it), so no spawn ever eats the finalization money. At the pre-wake soft boundary (orchSpent + turnEstimate > effectiveCap - finalizeReserve) the engine writes exactly ONE orchestrator_budget_cap decision strictly before any effects (an in-flight latch closes the wake-ordinal race): the plan freezes for adaptation but not for work (the rebase context frozen flag drops every op plan_frozen while admitted nodes run to completion), all wake triggers except quiescence disarm, and the orchestrator unwinds to the reserved FINAL wake: a fresh agent entry on the restricted single-finish toolset with a finalizeTurns limit, paid from the reserve; success yields outcome ok with forcedFinish marked in the CostReport. If the final finish fails, orchestrator_finalize_fallback journals and the engine SYNTHESIZES a deterministic partial result by pure fold with zero LLM calls; the run ends exhausted with the non-null partial (RunOutcome.value now survives exhaustion). Every digest carries the WakeBudgetBlock (run and orchestrator spend, cap, reserve, the epsilon-floored orchestrator share, softWarning at 0.8) with orchestrator:budget telemetry at each wake boundary and at the cap; CostReport.orchestrator populates spentUsd, wakes, forcedFinish, and reserveUsedUsd for H-OrchShare.

  • e70e7f4: M7-T13: the FINAL normative WakeDigest in ONE coordinated schema change (docs/07 section 5; XF-08/XF-12, inside the frozen hashVersion-2 identity rules). WakeDigest now declares every block first-class: digestSeq, planHash (emission-time plan hash, empty outside PlanRunner), coversToOrdinal, completedDigests ordered by spawn ordinal, escalations (with the Flavor B deadlineAt), the MANDATORY termination snapshot (DEF-2, contributed by the PlanRunner extension as a pure fold), the MANDATORY budget block (WakeBudgetBlock, DEF-7), and the reuse stats (the AbandonedSpendView shape, DEF-5). Runs without the PlanRunner extension ship all-zero blocks (emptyDigestBlocks), mirroring the CostReport convention. The digest render is bounded deterministically: the new renderBudgetChars option clamps each TaskDigest outputSummary by CHARACTERS (the model-independent interim measure; the tokenizer choice stays the docs/14 open question, the numeric default TBD before M10). Pinning semantics are unchanged: the digest is part of the wake snapshot and a re-executed turn reads identical bytes.

  • bc9c903: M7-T14: the M7 gating cassettes and the remaining metric wiring (docs/09 sections "Metrics" and "Mandatory defect cassette catalog"). Thirteen frozen cassettes record the round-2 set (revise-mid-run, crash-during-revision, park-unpark, oscillation-freeze, half-escalated-ladder, budget-denied-rung), the DEF-7 set minus queue-failover (cap-freeze-then-finish, crash-between-cap-and-effects, finalize-fallback-synthesized, escalation-storm-frozen), and representative DEF-2/DEF-3 rows (revision-exhaustion, rung-retry-lineage, decompose-mints-children), each double-run at record time and replayed byte-for-byte in CI through the new public @rulvar/plan cassette runners with deterministic journal normalization (ULIDs, content hashes, wall clock, spans, and refs collapse to first-appearance placeholders). Metric events: orchestrator:woke now carries planHash, coversToOrdinal, and renderSize (the deterministic character measure of the delivered digest, the wake-render-size metric); the escalated landing emits escalation:raised with the report kind, the lineage attribution, agentType (the escalation-rate slice), and costToDateUsd; the abandoned/reclaimed/netLost USD view rides every digest through the T13 reuse block and ledger:op plus spawn:* events already feed ledger-ops-per-spawn.

0.7.0

Minor Changes

  • fd1d06c: M6-T02: WorkerSandboxRunner and the sandbox contract. @rulvar/planner gains WorkerSandboxRunner (accepts CompiledWorkflow ONLY; worker_threads with the exact curated 12-global scope; timeoutMs 300000 / memoryMb 512 breaches terminate the worker with the new typed SandboxError, code sandbox_limit). Core gains the public host half, createSandboxBridge: proxied primitives (agent, step, workflow, awaitExternal, parallel, pipeline, phase, budget) served against the canonical run ctx with worker thunks executing under host-allocated scope tokens; the worker's SYNC seeded now/random/uuid (and the Date.now/Math.random replacements) mirror-journal as ordinary kind rand entries with match-first resume semantics; a busy-state protocol keeps suspension and quiescence behavior identical to in-process runs. createEngine gains runners.sandbox; engine.run/engine.resume accept CompiledWorkflow, persist the source blob plus workflowSourceRef/workflowHash at start, and resume(runId) with no workflow rehydrates the hash-pinned source (a differing supplied source is a typed ConfigError). New FileTranscriptStore makes compiled runs resumable across processes. The sandbox dialect exposes async budget.spent()/remaining(); import/fetch/process are absent from the worker scope.
  • 6fcf296: M6-T04: profileCard and the API card. Core gains profileCard(profiles): the one agent vocabulary both orchestration modes speak, feeding the planner prompt (mode b) and spawn_agent agentType guidance (mode c) with IDENTICAL text; pure function of the registry, sorted, byte-stable, rendering only model-agnostic fields (name, description, tool names, taskClass, estCost, escalation opt-in; models are never named). The planner gains apiCard(): the byte-stable card teaching exactly the curated 12-global sandbox dialect (schema literals only, tools by profile name, onError throw|null, async budget, no imports, the opts.key repeat rule) with usage patterns distilled from the examples corpus.
  • dcc97a9: M6-T05: the plan agent and the self-repair loop (mode b). plan(engine, goal, { model?, profiles?, repairRounds? }) asks a planner model under role plan to write a script against the API card plus the engine's profile card, lints it (eslint-plugin-rulvar preset + compileScript), self-repairs up to repairRounds (default 3) from the machine-readable JSON diagnostics, and returns { source, workflow, lint }. The planner conversation is an ordinary journaled run with a goal-derived deterministic runId, so re-planning the same goal replays the unchanged prefix free; exhausting the rounds throws a typed ScriptRejected carrying the last diagnostics. runPlanned(engine, goal, args?) composes plan-then-sandbox-run (async by amendment). Core gains AgentOpts.role ('loop' | 'plan' | 'orchestrate', the primary invocation role threading through resolution, effort defaults, floors, cost buckets, and events) and the narrow Engine.profileCard(names?) accessor rendering the registered profiles through the public API.
  • 434dc83: M6-T06: AdmissionController v1 and nested workflows. ctx.workflow(wf | 'name', args, { key? }) runs a child workflow under the single admission point: a spawn-admission decision entry embeds the closed AdmitVerdict union (admit | reuse_full | admit_graft | reject with the merged reject-code set; reuse branches produced from M7), the committed reserve, and statsBefore strictly before the two-phase child dispatch entry, so replay recovers verdicts and reserves without re-evaluating admission. Enforced: maxDepth (default 1, hard ceiling 4), maxChildrenPerNode (16), childBudgetFraction (0.3 of the parent remainder minus the parent finalize reserve), and the engine lifetime cap. The budget grows into a hierarchical account tree (run root plus one sub-account per child) with spend propagating to every ancestor, per-account layer-2 guards, and per-subtree layer-3 severing. Structural rejections throw the new typed AdmissionRejectedError (code admission_rejected); budget-class rejections keep BudgetExhaustedError semantics. The string form resolves against defaults.workflows; budgetDefaults gains childBudgetFraction and maxDepth, and flatReserveUsd is now honored. The abandon fold covers child-workflow scopes via the recorded dispatch payload.
  • 03173c1: M6-T07 and M6-T08: the mode (c) dynamic orchestrator. orchestrate(engine, goal, { model?, profiles?, maxSpawns?, budget?, limits? }) and ctx.orchestrate(goal, opts) share one implementation: an ordinary workflow whose agent (role orchestrate) holds the typed toolset with the normative docs/07 schemas: spawn_agent, parallel_agents, await_any, await_all, cancel_agent, and the loop-terminal finish (a new engine interception alongside escalate). Every spawn is an ordinary kind agent entry under the orchestrator's agent:<seq> scope, admitted through the single AdmissionController with the verdict, evaluated reserve, and statsBefore embedded in a spawn-admission decision entry (the budget debit itself rides the child's dispatch: one debit, never two); rejections surface as typed tool errors and never kill the run. Handles ARE the child dispatch seqs and stay stable across resume: a crashed orchestrator restores its transcript from the mandatory turn-boundary checkpoint, rebuilds its spawn records from the journal, redispatches only what was in flight, and finds settled children by content keys with zero re-paid spawns and no duplicate spawn decisions. await_any/await_all deliver deterministic TaskDigests; cancel_agent aborts an in-flight child to a cancelled terminal (caller intent; abandon coverage arrives with M7 cancel_task). The nested surface rides ctx.workflow, so maxDepth and the budget account tree clamp it for free; the orchestrator gets its own budget sub-account when a cap resolves (reserve decisions and the at-cap freeze are M7, DEF-7).
  • 11c0afc: M6-T09 and M6-T10: wait_for_events, the WakeDigest substrate, and ctx.brief. wait_for_events (the normative docs/07 4.8 schema) parks the orchestrator on an ordinary DEF-4 suspension; the closed v1 trigger vocabulary is quiescence (always armed), child_terminal, escalation, and budget_threshold at the fixed 50/80 percents; a REQUESTED trigger set that can never fire (no run ceiling, unknown or fully delivered handles, no live children) is an immediate typed tool error, so an embedded run cannot hang unrecoverably. The wake is the closing resolution whose value IS the coalesced WakeDigest (substrate fields: digestSeq, coversToOrdinal, completedDigests ordered by spawn ordinal, escalations with reportRef): a re-executed post-crash turn reads exactly the same digest bytes, replay never rebuilds a digest, and simultaneous ready triggers journal one applied resolution plus noop losers under first-closing-wins. Trigger evaluation runs at arm time and on every child settlement; the orchestrator sleeps between wakes and its context grows O(wakes). ctx.brief({ content, instruction?, model?, agentType? }) is a journaled summarize-role invocation (one agent-kind entry, free on replay) for handing an inheritable brief to a child.

0.6.0

Minor Changes

  • fa05007: M5-T01 workflow registry and the @rulvar/cli base.

    • @rulvar/core gains the per-engine WorkflowRegistry type and defaults.workflows on createEngine (docs/06 section 10.4): an explicit first-class value, no module-level registry; shells resolve by-name runs against it (ctx.workflow's string form arrives M6, the queue worker M8).
    • Spec-conformance fix: the M4-T09 quality floors option moves from the createEngine top level to its canonical home defaults.roleFloors (docs/06 section 10.1). Update createEngine({ floors }) call sites to createEngine({ defaults: { roleFloors } }).
    • @rulvar/cli ships its first real surface: the canonical grammar rulvar run <file|name> [--args JSON] [--store PATH] [--budget-usd N], rulvar resume <runId> [--args JSON] [--store PATH], rulvar runs ls [--store PATH], rulvar inspect &lt;runId&gt; [--store PATH] (no aliases), a line-oriented TUI progress renderer over the event stream, and interactive resolution of suspended approvals and externals (EOF leaves the run suspended, never errors). Engine assembly follows the host-config convention: rulvar.config.mjs default-exports { engineOptions?, workflows? }, a workflow module may export workflow/engineOptions/workflows, and --store selects the JsonlFileStore directory (default .rulvar), so the CLI itself depends only on @rulvar/core. The rulvar bin is included; the resume/inspect grammar amendment (--args re-supply, --store symmetry) is recorded in docs/06 section 10.5.
  • 9234dc8: M5-T03 cost reports. The CostReport builder moves to its own module (engine/cost-report.ts) and report totals become the LEDGER FOLD totals at settle: RunOutcome.usage and cost.totalUsd are computed from the journal's terminal entries (the same summation the kernel budget seed uses), so report totals equal ledger fold totals exactly, live and across resume, by construction. The new costReportFromJournal(entries, priceUsd) is the pure fold for STORED runs: byModel and totals from terminal servedBy with abandoned subtrees contributing zero; phase, agentType, and role attribution are live-run facts that entries do not carry (byRole and the orchestrator block complete in M7 per DEF-7). Unpriced models keep surfacing, never as silent zeros. rulvar inspect gains the cost view (total, byModel, unpriced) over the config-assembled price function (table wins over caps.pricing), and live run output prints the byModel/byPhase buckets.

  • 644512c: M5-T05 permission presets, audit, dry-run and M5-T06 argv shell matcher.

    • compilePermissionPreset('strict' | 'standard' | 'open') (tools/presets.ts) compiles the shipped presets to the documented verdict-by-risk tables and folds INTO the existing deny/ask chain layers, after host-authored rules, never a fifth layer and never an allow-override (a needsApproval tool still asks under every preset). open compiles to empty tables. AgentProfilePermissions.preset now compiles instead of throwing; undeclared tool risk is matched conservatively via a first-class { risk: 'undeclared' } rule.
    • The argv shell matcher (tools/shell-matcher.ts) replaces the M5 fail-early stub for { tool, argv } rules: a POSIX-like lexer honors quotes and escapes with no expansion, splits on ;/&&/||/|/&/ newline, poisons segments containing command or process substitution or here-docs to ask, strips leading env assignments, and retains redirections as tokens. Verdicts compose strictest-across-segments, so npm test; rm -rf / yields deny (or ask) even with npm test allow-listed, and any unmatched segment yields ask.
    • evaluatePermission gains an offline overload (by tool name, no execution) for the docs/08 4.5 dry-run/shell-tooling API, and every verdict carries the audit payload (verdict, deciding layer, matched rule) that now rides tool:end events; advisory network-domain rules are reported there but never enforced outside first-party fetch (honest posture, docs/08 4.4).
  • 8a41656: M5-T07 RunProfile presets and M5-T08 OTel exporter.

    • engine/run-profiles.ts: RUN_PROFILES (fast/standard/deep/ultra) and runProfile(name) ship the presets as pure DATA, bundles of per-role effort hints, per-run concurrency, budget, permission preset, and spawn limits, with no functions and no named model strings (named strong defaults stay in the umbrella). They are never engine semantics: a source-scan test asserts the engine has zero branches keyed on profile names. rulvar run --profile <name> applies the chosen profile UNDER the host's own engine options (host always wins; the engine then sees only ordinary options), compiling the profile's permission preset into the engine deny/ask layers as data.
    • @rulvar/cli gains toOtel(run, tracer): it maps a settled run's spanId tree 1:1 onto OpenTelemetry spans (run > phase > agent > tool > child), with rulvar.* and gen_ai.* attributes, start/end timestamps from the lifecycle events, and payload-only events attached as span events. Prompts, completions, and tool payloads are NEVER exported; replayed events never create duplicate spans. @opentelemetry/api ^1.9 is an optional peer dependency and the exporter is typed against a minimal structural TracerLike, so an absent OTel package never breaks the CLI.

Patch Changes

  • 02f7f7a: M5-T09 examples corpus. A new (unpublished) examples/ vitest project ships runnable reference implementations of the documented quality patterns as recipes over the public ctx API, never engine flags: adversarial panel (N independent skeptics prompted to refute; majority survives), judge panel (N angled attempts each scored; top wins), loop-until-dry (keep finding until K consecutive empty rounds), and completeness critic (draft, then gap-driven revision passes). Each example is a real defineWorkflow and doubles as an integration test under FakeAdapter with zero live calls, so an example that stops compiling fails CI like any test. The corpus is registered in the pnpm workspace and the single Vitest project set; the umbrella marker package is unchanged (patch to carry the changeset).

0.5.0

Minor Changes

  • ac274f4: M4-T01 role protocol completion. The full trigger protocol for the six invocation roles lands in @rulvar/core (model/roles.ts):

    • Extract necessity is completed per docs/04 section 8.3: a separate final structured-output invocation fires when a schema is set AND (routing directs extract to a different model OR the loop model's required tier cannot ride a tools-available turn OR finalize is routed). The required-tier rule is new: a forced-tool tier pins toolChoice to emit_result and cannot ride while the agent's tools must remain available, so such agents now pay one separate extract call instead of silently losing tool access. Agents without tools keep the M1 single-shot behavior byte for byte.
    • The finalize role fires for the first time: only when configured in routing and only for tool-bearing agents, as one synthesis invocation with toolChoice 'none' over the full transcript after tools stop. Its text is the output for schema-less calls; with a schema the separate extract runs over the transcript including the synthesis.
    • A separate extract invocation over a tool-bearing transcript now carries the agent's tool contracts (both providers reject tool-use history without tool definitions) with toolChoice pinned to 'none' or to emit_result per tier.
    • Both adapters map toolChoice: 'none' to the provider's explicit none choice with the tools param present instead of dropping tools from the request.
    • createTestEngine no longer routes finalize by default: the routing key is the firing opt-in, and the old default would have summoned a synthesis call for every tool-bearing test agent. Tests that want finalize route it explicitly.

    Identity is untouched: extract and finalize resolutions never enter the spawn content key, and existing journals replay unchanged.

  • 5735d92: M4-T02 HistoryProjector. Cross-provider history projection lands in @rulvar/core (model/projector.ts) and the retention pipeline that feeds it:

    • projectHistory projects the canonical history into a target provider's view: provider-raw parts ride if and only if the target adapter's provider family matches the part's provider; everything else passes through untouched. The agent loop projects EVERY outgoing request (loop turns, finalize, extract), so per-role provider mixing inside one agent yields a valid wire history on each side.
    • Retention transport: adapters ship a turn's blocks-to-retain in stream order via finish.providerMetadata[<adapter id>].retainedParts; the runtime lifts them into provider-raw parts at the HEAD of the turn's canonical assistant message. @rulvar/anthropic ships thinking and redacted_thinking blocks (signatures intact, pause_turn continuations included); @rulvar/openai ships reasoning items with their encrypted_content. Retained blocks now actually reach the canonical history, survive checkpoints, and echo byte-exact to their own provider on every subsequent turn.
    • ProviderAdapter gains an optional provider field: the provider family for provider-raw matching (default = adapter id). The first-class adapters declare 'anthropic' and 'openai'; openaiCompatible gateways declare 'openai' whatever their custom id, so same-family adapters share retained blocks and projections.

    Identity is untouched: projection state never enters content keys, and adapters that ship no retention payload (FakeAdapter included) produce byte-identical histories.

  • 46ca98e: M4-T03 compaction ownership. The Agent Runtime owns compaction (runtime/compaction.ts):

    • Compaction is ON by default for every agent at threshold 0.8 of the loop model's contextWindow (docs/06 Appendix A); AgentProfile.compaction.threshold adjusts it per profile. The context estimate is the last loop turn's inputTokens + outputTokens.
    • At a tool turn boundary past the threshold the summarize role fires through the resolution chain (falling back to the loop model when routing resolves no summarize model; the low role-effort default applies either way), and the transcript after the first message is replaced by one user-role summary message. The summarize request is projected like any other and carries the tool contracts with toolChoice 'none'.
    • Compaction points (the turn numbers at which compaction fired) ride every checkpoint and restore verbatim: a resumed run continues from the compacted history and never re-summarizes it. Full-journal replay stays free as before.
    • A failed or empty summarize disables compaction for the rest of the run with a warning instead of failing paid work; budget and cancellation aborts propagate normally.
  • 8ae129e: M4-T04 failover and M4-T05 RetryPolicy under the journal.

    • Transport RetryPolicy (model/retry.ts): the Appendix A defaults (attempts 3; backoff 500ms x2 max 8000ms with equal jitter; retryOn transport, rate-limit, overloaded) now actually retry around every adapter.stream dispatch: loop turns, extract, finalize, and summarize alike. Retries live UNDER the journal: a retried-then-successful call is one journal entry with one usage total, one turn, and no lineage attempts (DEF-3). A provider retryAfterMs replaces the computed delay; task-class failures never retry by construction; stream-idle severance retries as transport-class. Configure per call (AgentOpts.retry), per profile, or engine-wide (defaults.retry).
    • Transport failover (model/failover.ts): ModelChoice.fallbacks now works. When a serving model exhausts its tries on a transport or rate-limit failure, the sticky chain advances to the next resolved fallback (per-phase, effort defaults and caps scrubbing re-applied per serving model). The content key hashes the REQUESTED spec, so a failover-served response replays for free; only servedBy records the actual server (now surfaced on AgentResult and stamped on the terminal entry). Budget is explicitly excluded as a trigger.
    • The degenerate fallback field (AgentOpts.fallback, docs/04 11.3): an agent-level second attempt on a stronger model when the terminal matches on (error, limit, schema-exhausted), with exactly one journaled decision entry (decisionType: 'model.fallback') reused on resume, and the fallback attempt under its own content key. Cancelled, escalated, and budget outcomes never trigger it.

    AgentResult gains the required servedBy field (additive for consumers reading results; literal constructions in tests need the new member).

  • d1c4525: M4-T06 versioned price table and M4-T07 per-provider concurrency keys.

    • model/pricing.ts: PriceTable { pricingVersion, models } configured via createEngine({ pricing }). The table wins over adapter-reported caps.pricing (a fallback only); unpriced models keep surfacing in CostReport, never as a silent zero. Engine-written model.fallback decision entries pin the active pricingVersion so replayed cost attribution is stable against later table bumps; a price update is a registry update with a version bump, never a caps refresh side effect (refreshCaps() remains the adapter-level caps path).
    • model/concurrency.ts: KeyedLimiter, engine-scoped, configured via createEngine({ concurrency: { perProvider } }) per adapter id. The Appendix A default stays unlimited: the per-run semaphore remains the only default bound and provider 429s ride RetryPolicy. When configured, every wire dispatch (retries and failover re-acquire) gates under its serving adapter's key, adapters throttle independently, and queueing surfaces as agent:queued telemetry with the provider key. There is deliberately no distributed cross-process limiter (docs/14).
  • b840aba: M4-T08 canonical effort completion and M4-T09 role quality floors.

    • Effort semantics are complete: the role effort defaults and the per-adapter mapping tables (Anthropic passthrough including max, OpenAI max downmapped to xhigh and recorded in providerMetadata, provider none only via namespaced providerOptions) shipped earlier milestones; this change completes VISIBLE scrubbing everywhere it was still silent: the summarize invocation surfaces its scrubs at fire time and a failover takeover surfaces the fallback's scrubs the moment it starts serving. Scrubbed effort is never mapped into max_tokens.
    • The effort-defaults-shift cassette is now RECORDED through the live runtime (docs/10 M4 gating row): the frozen v1 prefix, closed offline the way an operator would, resumes live under explicit high effort with the completed semantics; every v1 entry matches and the one new spawn carries canonical effort in v2 identity. The recorder output is pinned byte-for-byte by the frozen-drift suite and the fixture lock now covers 18 files.
    • Quality floors (model/floors.ts, M4-T09): per-role and per-declared-taskClass allow/deny lists supplied via createEngine({ floors }), enforced INSIDE the router at resolution, before any live call and before any journal entry, for every invocation the chain produces (primaries, failover fallbacks, and the summarize fallback alike). AgentProfile.taskClass declares the class; unclassified profiles see only byRole floors. A violation is a typed ConfigError.
    • The umbrella rulvar package now ships floors opinions next to its strong routing defaults: recommendedDefaults.floors pins orchestrate and plan to strong named models. The core itself ships no named model strings, and the umbrella suite enforces that with a source scan.

0.4.0

Minor Changes

  • dfe03b5: M3-T11 gating cassettes and the v0.4.0 BREAKING release notes.

    BREAKING (pre-1.0 convention, docs/12): AgentStatus now produces 'escalated' at runtime and AgentResult carries the optional escalation: EscalationReport field (present if and only if the status is escalated). This is the third kernel amendment of the replay predicate (escalated-replays-as-ok, DEF-1) whose table row shipped frozen in M2; the producers ship here. Migration: add an escalated branch to every switch over AgentStatus; consumers not adopting the protocol are advised to map escalated to limit (paid partial work, output null, the report stays available for logs). isEscalated and EscalatedResult are exported for narrowing. Status production stays gated by opt-in: workflows that never pass escalation options cannot observe the new status at runtime.

    Cassettes: the DEF-1 live set (escalate-replay, crash-between-report-and-decision, flavor-b-timeout) is recorded through the live runtime and replayed strict; the M2 synthetic DEF-1 subset is re-recorded (memoize-classifier fully live; abandon-subtree through the kernel write APIs with a realistic escalated child report and an authorizing owner cancel decision; both re-record again with the orchestrator producers in M7). FakeAdapter gains fakeToolCalls and fakeWireError responder markers; replayRun gains the onEscalation pass-through so replay tests can prove the hook stays cold. The deliberate fixture regeneration updates fixtures.sha256 in the same change (the identity profile is UNCHANGED; this is the docs/10 M3-T11 ordered re-record, not an identity-pipeline revision).

  • d2089a7: M3-T02 turn-boundary checkpoints. The runtime writes a canonical-history checkpoint into TranscriptStore at every turn boundary where the loop continues (tool boundaries and schema re-prompts), at a deterministic ref derived from the dispatch seq; the terminal entry records checkpointRef. A dangling-dispatch resume (kill-and-resume) re-enters at the last boundary with zero re-paid turns, restored usage folds into the terminal exactly once, and an unreadable or unknown-format blob falls back to a full redispatch (tools stay at-least-once between execution and the checkpoint write). The blob format is engine-internal with a leading format byte; replayed agents recover their turn count from the checkpoint and re-emit tool:start/tool:end with the replay marker.

  • 3f60234: M3-T07 terminal escalated status and EscalationProtocol producers (the BREAKING section for v0.4.0 rides the milestone release notes). Typed EscalationKind/EscalationReport/EscalationDecision/EscalationOptions; the escalate tool registers under escalation opt-in of either flavor through the same path as any tool (opting in changes toolsetHash by design) and is engine-intercepted after the permission chain. Status production is gated: without opt-in the escalate tool does not exist and 'escalated' is physically unproducible. Flavor A terminates the worker with a runtime-completed report (costToDate and salvage are never model-authored; the request schema rejects them; the full report is validated BEFORE append; usage/costUsd/turns/transcriptRef as for ok, output null). Flavor B suspends on the approval machinery with a journaled deadlineAt (explicit deadlineMs required); a live decision and the deadline timer race through the ResolutionArbiter first-closing-wins (timeout applies defaultDecision, default accept); dispose collects the worktree patch into salvage BEFORE destruction; the terminal escalated entry and the authoritative escalation-decision entry follow strictly after, with countsAgainstLimit derived once (true iff scope_bigger). Replays synthesize the byte-identical report with zero adapter calls and read the owner's decision from the decision entry (a crash between report and decision pays the decision live exactly once). In ctx.parallel an escalated child is a settled outcome that never aborts siblings; a plain value-form call opting in requires the onEscalation hook (ConfigError before any LLM call otherwise). The in-run minSpend gate (M3-T09) rejects early scope_bigger escalations with a bounded "keep working" re-prompt; scope_different and blocked_with_evidence are exempt and never debit the counter.

  • f668890: M3-T05 worktree isolation and M3-T06 openaiCompatible. GitWorktreeProvider implements the IsolationProvider seam: acquire creates a detached worktree from HEAD or a given ref (non-git host is a typed ConfigError), tools receive cwd inside the tree, collect() snapshots changed files and a binary patch, dispose removes the tree with keepOnError retention under the shared maxPinnedWorktrees cap (default 4). ctx.agent resolves isolation call-over-profile into spawn identity, stores the collected patch in TranscriptStore, and surfaces it as a kind 'patch' Artifact on AgentResult.artifacts and the terminal journal entry, so replays reconstruct artifacts with zero live calls; applying the patch stays with the caller. isolation 'readonly' is accepted as a declaration (its compiled deny rule ships with risk presets in M5).

    @rulvar/openai gains openaiCompatible({ id, baseURL, apiKey?, caps? }) for Ollama, vLLM, and gateways: the Chat Completions dialect by construction, explicit ids so several endpoints coexist (duplicate id stays a ConfigError at createEngine), and the most conservative caps when unprobed (prompt-tier structured output, no parallel tools, no pricing; supplied caps merge over the floor).

  • 16d7aa6: M3-T04 MCP ToolSource. mcp(cfg) imports Model Context Protocol tools over stdio, streamable-http, or an in-process server instance (pinned SDK line @modelcontextprotocol/sdk ^1.29; the v2 migration is the logged post-M3 task M5-T10). tools/list is fetched with cursor pagination until exhaustion and cached per session; a listChanged notification invalidates the cache for subsequently spawned agents only (a spawn's toolset snapshot stays immutable). allow/deny filters apply to pre-prefix names with deny winning; prefix namespaces collisions; approval maps to needsApproval per tool; host-supplied risk labels feed the permission presets. inputSchema becomes bare-JSON-Schema parameters (form 3); outputSchema validates structuredContent; isError maps to an error tool result surfaced to the model, never a protocol error; MCP tools hash version as absent, so provider-side contract drift re-keys new spawns by design.

  • 6513ce8: M3-T08 no-progress abort class and M3-T10 UsageLimits completion. The engine-defined detector implements the committed docs/06 Appendix A interim rule (N consecutive turns without tool calls or artifact deltas, N = 3, configurable via the new UsageLimits.noProgressTurns knob): the abort journals as the agent's terminal entry with status 'limit', the dedicated 'no-progress' class marker in the error payload (AgentResult.abortClass), and memoizeOutcome stamped by the ENGINE on the terminal entry, so it replays on every resume without a live rerun regardless of the user's dispatch-time memoize policy (the predicate's entry-read consults the terminal stamp first; docs/03 section 6.6 amendment). Tool-calling turns reset the streak: a working agent never trips. UsageLimits is complete: maxTurns, maxToolCalls, maxOutputTokensPerTurn, timeoutMs, streamIdleTimeoutMs, noProgressTurns, and the run-level deadline each independently produce their documented outcome, with per-limit tests including the memoized-limit replay/unmemoized rerun predicate integration. The M3-T09 minSpend gate gains the accumulation path test (scope_bigger passes once spend crosses minSpendUsd).

  • 7dad493: M3-T03 permission chain and ask suspensions. The normative layered chain (hooks -> deny rules -> ask rules -> canUseTool -> terminal default) is the single approval surface for every tool dispatch; hooks run in deterministic registration order with modifiedInput substitution; rules never yield allow; an explicit canUseTool allow is decisive including over needsApproval; argv/domain rules and presets fail early until M5. Engine-wide defaults.permissions merges under profile permissions; inheritPermissions is carried as data for subagent spawning (mode c). An ask verdict journals a suspended approval entry (kind 'approval', identity {toolName, post-hook input}, agent child scope) together with the turn checkpoint; the run settles 'suspended' with the synthesized approval:<seq> key; RunHandle.resolveExternal validates { decision: 'allow' | 'deny' } and a denial surfaces to the model as an error tool result carrying the reason. An approval round-trip across process exit resumes the SAME turn: executed tool results are reused from the checkpoint, the resolved decision applies without re-suspension, and only post-approval turns are paid live.

  • 2bbf180: M3-T01 tool system core plus the M3 entry-gate docs amendment. tool() definitions over the three SchemaSpec forms with definition-time validation (name pattern, schema projection, recursive/remote ref rejection); the ToolSource SPI seam types (ToolDef, ToolRisk, ToolContext, ToolSourceSession); per-spawn toolset resolution with duplicate-name and executor fail-early ConfigErrors; toolsetHash derived from contracts only (editing an execute body never re-keys a journal, bumping version does) and wired into spawn identity; agent-loop tool dispatch with argument validation, bounded ModelRetry conversion, NonSerializableValueError surfacing, maxToolCalls expiry as terminal limit, and tool:start / tool:end telemetry. The docs/06 Appendix A knob "no-progress detector N" is committed at 3 consecutive turns without tool calls or artifact deltas (consumed by M3-T08).

0.3.0

Minor Changes

  • 43444f6: M2-T11/T12: the executable store conformance kit and the M2 gating cassettes with frozen fixtures.

    @rulvar/store-conformance ships its first real API: journalStoreConformance (A1 append atomicity, A2 total per-run order, A3 read-your-writes, A4 opaque payload with read-side-only normalization, meta separation, the golden fold-state fixture with a frozen reference hash, the decide-once oracle, and the abandon-derived-skip fixture) and leasableStoreConformance (typed LeaseHeldError on held acquire, monotonic fencing epochs, stale-epoch appends rejected and invisible, released leases fenced from renew and append, optional ttl/renew-cadence timing checks), plus registerConformance for Vitest/Jest and the stableStringify fold-state hasher. InMemoryStore and JsonlFileStore pass; deliberately broken stores (reordering, normalizing, tearing, fencing-less) fail loudly.

    @rulvar/core kernel closes three DEF-1/DEF-4 gaps the cassettes gate: an abandon-covered hanging dispatch derives skipped instead of redispatching, abandon-covered operations contribute a zero ledger increment, the resume report lists covered entries as skipped (never orphaned), and an abandon over an already-resolved suspension folds to a noop with already_resolved (first-closing-wins per target, both closer kinds).

    @rulvar/testing ships the M2 cassette suite over committed frozen fixtures: the DEF-1 synthetic subset (abandon-subtree, memoize-classifier, v1-journal-on-v2), the DEF-4 set (timeout-vs-live-race, class-decision-fanout, abandon-then-crash-then-resume, abandon-vs-resolution-race, offline-invalid-then-valid, double-abandon-idempotent), the DEF-6 six IDs (resume-v1-on-engine-v2, resume-v1-with-inserted-call, suspended-v1-resolves-on-v2, reject-version-too-old via deriverV0Synthetic, reject-version-from-future, effort-defaults-shift), the mandatory mixed-version scenarios (ordinal-space split, forward-cursor preference, cross-version resolution, the compatibility and never-pay-twice-through-upgrade lemmas), and KeyDeriver contract tests against the frozen v2 golden identities including the docs/03 worked example. Fixture regeneration is deliberate: scripts/record-m2-cassettes.mjs rebuilds, and CI write protection (scripts/check-frozen-fixtures.mjs plus fixtures.sha256) fails any fixture diff shipped without the explicit bump token (the hyphenated compound of hashVersion and bump) in a changeset.

  • 279881b: M2-T05/T06: the hashVersion mechanism and the canonical replay predicate. Frozen KeyDeriver profiles (v2 current; v1 with the effort-stripping projection, round-1 disposition table, and foldDefaults), the per-engine deriver registry with extraDerivers validation as the only window extender, the side-effect-free compatibility scan raising JournalCompatibilityError with sub-codes and hints, versioned matching through the registry KeyRing (live calls projected DOWN, incomparable is a guaranteed non-match, keys memoized per call and version); the single canonical replayDisposition with the three kernel amendments (memoizeOutcome on task-class failures via classifyAgentError, abandon-derived skipped through the append-order AbandonFold with transitive child-scope coverage, escalated-replays-as-ok), version dispatch by the entry's own profile, and the invalidate/retry unpinning API. @rulvar/compat ships the extraDerivers plumbing plus the synthetic hashVersion 0 deriver (manually versioned 0.1.0 per the lockstep exemption).

  • 9fd0966: M2-T03/T04: scoped forward-matching and the kinds/grammar freeze. The JournalMatcher (per-scope insertion-stable cursors, first unconsumed match wins, cache/never per-call modes, orphan reporting) integrated into the Replayer with seeded seq/ordinal spaces and the resume ledger fold; ctx.agent/step/now/random/uuid replay journaled results byte-identically with zero adapter calls, dangling running entries redispatch with the terminal referencing the original dispatch, and replayed lifecycle events carry replayed: true. Kinds registry v2 payload validators enforce the docs/03 shapes on engine-written entries; the scope grammar gains a parser with round-trip guarantees. The interim disposition is round-1; the full DEF-1 table plugs in with M2-T06.

  • 24ebadf: M2-T07/T08: suspension machinery (DEF-4). Strict ResolutionPayload and AbandonPayload with the normative by-source mapping; the first-closing-wins ResolutionFold (schema validation at consumption against the schema pinned inside the suspended entry, invalid offline resolutions never close, abandon coverage with transitive child scope-prefix and the AbandonFold projection consumed by the replay predicate); the per-target FIFO ResolutionArbiter (classify, durable append, settle exactly once; losing attempts are journaled noops); rule O2 hard errors on forward or dangling refs; Replayer resolveSuspended/abandonBranch/suspensionState; ctx.awaitExternal (NO deadline in v1, duplicate key in scope is a typed error) with run outcome 'suspended' plus pending[] on quiescence; and RunHandle.resolveExternal returning ResolutionOutcome, validating live payloads BEFORE append and journaling nothing on InvalidResolutionError.

  • a1b35d3: M2-T09/T10: engine.resume under the run-to-definition binding contract (wf required for in-process runs, name mismatch is a typed ConfigError, body-hash mismatch warns loudly and proceeds; the compatibility scan runs strictly before any side effect; the resumed run seeds the budget from the ledger fold, re-emits open suspensions, and reports ResumePreview hits/misses/reruns/orphans plus invalid offline resolutions), the dryRun option (replay-strict matching: the first would-be-live call settles the run with the typed journal_miss error and zero live calls), and @rulvar/testing replayRun (tier 3: strict replay of any journal with JournalMissError on ANY live call; suspended journals finish suspended with zero live calls).

  • 18a5821: M2-T01/T02 groundwork: JsonlFileStore (one JSON entry per line, the journal doubles as an event log; torn-trailing-line tolerance and repair for A1 atomicity; atomic temp-plus-rename meta replace; listRuns without payload parsing; mid-file corruption is a hard JournalOrderViolation) and the committed large-value soft warn threshold (262144 bytes, docs/06 Appendix A M2 entry gate) wired into the journal append path as a warning event, never an error.

0.2.0

Minor Changes

  • c24228d: M1-T10/T11: the WorkflowEvent envelope and M1 catalog (per-run telemetry seq distinct from JournalEntry.seq, span hierarchy run > phase > agent), the per-run EventBus feeding RunHandle.events and on(), RunOutcome with exhausted-overrides-error precedence and the normative CostReport (byModel/byPhase/byAgentType/byRole, the all-zero orchestrator block, unpriced evidence); createEngine with per-engine registries and engine.run over the ScriptRunner seam; InProcessRunner with the dev-mode bare-Date.now/Math.random warnings; run cancellation (host signal, handle.cancel, run deadline) and RunMeta run-to-definition binding fields. The umbrella ships the minimal terminal progress renderer (renderProgress) and re-exports the core surface.
  • c50871e: M1-T04/T05: journal write path and model router core. JournalEntry form with the kinds registry v2 and hashVersion (written as 2 from day one), IdentityInput records per spawn kind with content-key derivation (sha256 over RFC 8785 JCS; reproduces the docs/03 worked example byte-identically), the scope-path grammar, ordinal assignment, the per-run serialized append queue with the JSON-serializability check, the budget-ledger fold, JournalStore/LeasableStore/TranscriptStore SPI types, InMemoryStore (loud one-time resume-disabled warning) and InMemoryTranscriptStore; the per-engine adapter registry (duplicate adapterId is a ConfigError), strict ModelRef parsing, the per-invocation resolution chain with role effort defaults, CanonicalModelSpec canonicalization, visible caps scrubbing (effort and sampling parameters), and structured-output tier selection with the strict-compatibility predicate.
  • 1af8fb9: M1-T01/T02/T03: L0 foundations. Wire contracts (Msg/Part with provider-raw, ChatRequest, the ChatEvent union with typed refusal finish outcomes, the Usage invariant, CanonicalId minting, cacheHint, canonical five-level Effort, the ModelSpec family declarations); the closed error taxonomy (RulvarError base, WireError projection, all named error classes, the AgentError value projection); SchemaSpec in its three forms with Out<S> inference, StandardJSONSchemaV1 projection (draft 2020-12 with draft-07 fallback), canonical schema derivation (JCS, local $ref inlining, annotation stripping), schemaHash/toolsetHash, and runtime validation via the vendored draft 2020-12 validator.
  • 1fe0249: M1-T06/T07/T08/T09: agent runtime v1 (single subagent loop, structured output in three tiers with client validation and the bounded re-prompt, typed AgentResult with the ok/error/limit/cancelled/skipped vocabulary, ModelRetry declaration, UsageLimits with the normative merge and defaults, typed refusal handling, Usage-invariant verification at the adapter boundary); ctx primitives (defineWorkflow with the errorPolicy literal generic, ctx.agent overloads including result: 'full', ctx.parallel with Settled and abortSiblings semantics, ctx.pipeline with up to six stages and onItemError drop/throw/collect, ctx.step with useMemo-style deps keying, ctx.phase cost attribution, ctx.log, ctx.budget, and the deterministic now/random/uuid shims journaled as rand entries); the per-run FIFO semaphore scheduler; and the three-layer budget (admission reserves, the per-turn guard, the AbortSignal ceiling with usageApprox, immutable B0, BudgetExhaustedError thrown uniformly by every ctx primitive, run.dropped evidence for every silent loss).
  • 5c4fc32: M1-T14/T15: @rulvar/testing tier 1 (FakeAdapter matching on agentType/label/prompt regex with a '*' fallback, honoring the selected structured-output tier, zero USD by construction; createTestEngine over the full real engine with recorded event streams; toHaveCalledAgent and toStayUnderBudget matchers at '@rulvar/testing/matchers') and the completed umbrella (re-exports of @rulvar/core and both first-class adapters, renderProgress, the umbrella-only recommendedDefaults strong model slots, the M1 exit-criteria example workflow, and the CI install smoke on packed tarballs). The core now populates the reserved providerOptions 'rulvar' telemetry namespace on every request (docs/04 section 1.8 as amended) and AgentResult carries errorMessage detail for journaled WireError fidelity.

0.1.0

Minor Changes

  • f4e2be9: M0 repo bootstrap (v0.1.0, docs/10-implementation-plan.md section "M0"): monorepo scaffold on the committed toolchain (pnpm 11 workspaces with catalogs, TypeScript 6.0, tsdown, Vitest 4, ESLint 9 flat config, Turborepo 2, changesets fixed mode, npm trusted publishing), the docs/ canon as single source of truth, the L0 contracts skeleton in @rulvar/core, and the vendored dependencies (StandardSchemaV1/StandardJSONSchemaV1 types, the @cfworker/json-schema lineage validator subset, a first-party monotonic ULID). Placeholder scaffolds only: no public API ships in this release.

@rulvar/effects

1.252.0

Patch Changes

  • Updated dependencies [3ccb6cf]
  • Updated dependencies [52d807f]
  • Updated dependencies [517ed00]
  • Updated dependencies [a7e589d]
  • Updated dependencies [76e95eb]

1.251.0

Patch Changes

  • Updated dependencies [e7e829c]
  • Updated dependencies [5982be8]
  • Updated dependencies [7c58fb2]
  • Updated dependencies [b3e465a]
  • Updated dependencies [c4e5d6a]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [0ae8b85]
  • Updated dependencies [7932936]
  • Updated dependencies [7932936]
  • Updated dependencies [88da0ed]
  • Updated dependencies [06c0e85]

1.250.0

Minor Changes

  • 565c13b: The @rulvar/effects package is born (RV4504, plan 45, rfcs/effects.md sections 4.4, 6, 8, 11): the effect adapter seam that cannot send without an attempt record (dispatch receives the seq of the attempt appended BEFORE the call), the provider capability matrix types, and the crash-window dispatcher whose recovery is licensed exclusively by provider-side fencing: the idempotency-key row re-dispatches under the same key and lets the provider dedupe, the conditional-create row leans on the unique natural key, the acceptance-closing row closes the ambiguous ATTEMPT identity (so the fresh attempt stays legal while the stale one is refused at the provider), and the 'neither' row quarantines every ambiguous window with the possible late stale send named in the record. From a revocation or expiry position recovery is reconcile-only on every row: a found receipt confirms (a revocation then opens the compensation decision path as a linked incident; an expiry opens none, because it bounds the grant, not the past), a closed negative cancels with the proof on the record, and anything unresolvable quarantines. Provider fakes enforce exactly the fencing their row claims, including the deliberately stalled predecessor of kill point 17, where elapsed time licenses nothing. In core, the cancelled-before-dispatch legality widens per RFC section 4.7 row 2 (every attempt provably failed also proves no effect) and the writer gains refresh(). Kill points 4, 5, 6, 7, 8, 14, 15, 17, 27, 28, 29 are pinned by tests.
  • c6d197b: The reconciler, the trust envelope, and the whole kill point kit (RV4505, plan 45, rfcs/effects.md sections 3.1, 7, 8, 9). The sweep makes "every intent deterministically reaches confirmed, compensated, or quarantined" true: crossing reconcileBy quarantines whatever state with the state recorded, receipt waits and attempt budgets quarantine on exhaustion, lookups are bounded SEPARATELY through journaled effect_probe rows (countable from the journal alone, crash-proof), pre-terminal conflicting receipts quarantine, and effect authorizations past their deadline refuse durably instead of waiting forever. Receipt verification runs a declared trust envelope: issuer identity, per-class content bindings, key validity windows, revocation from its time forward, and the host's signature check; every failure classifies unverified, which routes to unknown. The post-restore reconciliation (kill 25) quarantines provider effects the journal cannot reconstruct by name (or the whole range without authoritative enumeration), and a restoration epoch stays undispatchable until the new effect_reconciliation_complete decision cites it. Section 9 telemetry folds effective dispositions (the compensated overlay included), pressure, duplicate classification, and open incidents. The kit exports all thirty effects.kill.* rows as named conformance checks parameterized by a store factory (ambiguous acks and restoration generations injected through delegating proxies, so any store qualifies), registered over the in-memory reference store in single-process posture and over the REAL sqlite and postgres stores in their own packages.

Patch Changes

  • Updated dependencies [0e240b9]
  • Updated dependencies [6fe585e]
  • Updated dependencies [c5eb19c]
  • Updated dependencies [565c13b]
  • Updated dependencies [c6d197b]
  • Updated dependencies [c9d9729]
  • Updated dependencies [fed9db6]
  • Updated dependencies [df9ed76]
  • Updated dependencies [3020912]
  • Updated dependencies [d8d598d]

eslint-plugin-rulvar

1.252.0

1.251.0

1.250.0

1.249.0

1.248.0

1.247.0

1.246.0

1.245.0

1.244.0

1.243.0

1.242.0

1.241.0

1.240.0

1.239.0

1.238.0

1.237.0

1.236.0

1.235.0

1.234.0

1.233.0

1.232.0

1.231.0

1.230.0

1.229.0

1.228.0

1.227.0

1.226.0

1.225.0

1.224.0

1.223.0

1.222.0

1.221.0

1.220.0

1.219.0

1.218.0

1.217.0

1.216.0

1.215.0

1.214.0

1.213.0

1.212.0

1.211.0

1.210.0

1.209.0

1.208.0

1.207.0

1.206.0

1.205.0

1.204.0

1.203.0

1.202.0

1.201.0

1.200.0

1.199.0

1.198.0

1.197.0

1.196.0

1.195.0

1.194.0

1.193.0

1.192.0

1.191.0

1.190.0

1.189.0

1.188.0

1.187.0

1.186.0

1.185.0

1.184.0

1.183.0

1.182.0

1.181.0

1.180.0

1.179.0

1.178.0

1.177.0

1.176.0

1.175.0

1.174.0

1.173.0

1.172.0

1.171.0

1.170.0

1.169.0

1.168.0

1.167.0

1.166.0

1.165.0

1.164.0

1.163.0

1.162.0

1.161.0

1.160.0

1.159.0

1.158.0

1.157.0

1.156.0

1.155.0

1.154.0

1.153.0

1.152.0

1.151.0

1.150.0

1.149.0

1.148.0

1.147.0

1.146.0

1.145.0

1.144.0

1.143.0

1.142.0

1.141.0

1.140.0

1.139.0

1.138.0

1.137.0

1.136.0

1.135.0

1.134.0

1.133.0

1.132.0

1.131.0

1.130.0

1.129.0

1.128.0

1.127.0

1.126.0

1.125.0

1.124.0

1.123.0

1.122.0

1.121.0

1.120.0

1.119.0

1.118.0

1.117.0

1.116.0

1.115.0

1.114.0

1.113.0

1.112.0

1.111.0

1.110.0

1.109.0

1.108.0

1.107.0

1.106.0

1.105.0

1.104.0

1.103.0

1.102.0

1.101.0

1.100.0

1.99.1

1.99.0

1.98.0

1.97.0

1.96.0

1.95.0

1.94.0

1.93.0

1.92.0

1.91.0

1.90.0

1.89.0

1.88.0

1.87.0

1.86.0

1.85.0

1.84.0

1.83.0

1.82.0

1.81.2

1.81.1

1.81.0

1.80.0

1.79.0

1.78.0

1.77.0

1.76.0

1.75.1

1.75.0

1.74.0

1.73.0

1.72.0

1.71.0

1.70.1

1.70.0

1.69.0

1.68.0

1.67.0

1.66.0

1.65.0

1.64.0

1.63.0

1.62.0

1.61.0

1.60.0

1.59.4

1.59.3

1.59.2

1.59.1

1.59.0

1.58.0

1.57.0

1.56.0

1.55.0

1.54.0

1.53.0

1.52.0

1.51.0

1.50.0

1.49.0

1.48.0

1.47.0

1.46.0

1.45.0

1.44.1

1.44.0

1.43.0

1.42.0

1.41.0

1.40.0

1.39.0

Minor Changes

  • 0cff035: Close the dynamic code generation parity gap in the planner sandbox dialect (v1.38.0 review P2-CODEGEN-PARITY).

    compileScript and the rulvar/no-code-generation ESLint rule now share one AST policy (scanDialect), so both reach the same decision for every statically visible constructor reconstruction form: .constructor, ["constructor"], a computed key that folds to the constant, { constructor: x } destructuring, and Reflect.get(fn, "constructor"). The previous regex compile gate matched only the dotted form, so a bracket or computed key passed compile while the linter flagged some of them; moving to an AST also drops the regex false positives, where a property merely named eval, Function, or constructor was wrongly rejected.

    A key assembled only at runtime (fn[parts.join("")]) cannot be decided statically without rejecting every dynamic property access, so the worker realm now neutralizes the constructor reconstruction path at runtime by replacing the constructor slot on all four Function family prototypes with a thrower. A script that compiles clean can no longer reach the Function constructor through a dynamic key.

    The planner and orchestration docs are corrected to state the exact boundary: the dialect rejects the statically visible forms and the worker neutralizes the runtime path, but a worker in the same process shares its intrinsics with the code it runs and remains a determinism and blast radius boundary, not a hostile code wall.

1.38.0

Minor Changes

  • 3e2d591: Reject dynamic code generation in the planner sandbox dialect (v1.37.0 review SEC-P2). compileScript banned import but not eval, the Function constructor, or .constructor access, so a machine script could reach the Function constructor and compile a dynamic import the literal scan never saw, recovering the import allowlist and, through node:child_process, arbitrary host capability at run status ok. compileScript now rejects eval, Function, and .constructor (diagnostic ids no-eval, no-function-constructor, no-constructor-access); a new rulvar/no-code-generation ESLint rule carries the same ban into the workflows preset and the self repair loop; and the worker additionally unbinds eval and Function as defense in depth. This keeps the import allowlist meaningful and the dialect consistent. It is not a hostile code boundary, which the sandbox has never claimed to be: JavaScript intrinsics can still reconstruct the constructors, so the docs continue to call the sandbox a determinism and blast radius boundary, not a security one.

1.37.0

1.36.0

1.35.0

1.34.0

1.33.0

1.32.0

1.31.0

1.30.0

1.29.0

1.28.0

1.27.0

1.26.0

1.25.0

1.24.1

1.24.0

1.23.0

1.22.0

1.21.0

1.20.0

1.19.0

1.18.0

1.17.0

1.16.2

1.16.1

1.16.0

1.15.0

1.14.0

1.13.0

1.12.0

1.11.0

1.10.0

1.9.0

1.8.0

1.7.0

1.6.0

Patch Changes

  • da4dbad: Write the product name as Rulvar in prose: package READMEs, npm descriptions, and the documentation site now capitalize the brand. Identifiers keep their exact casing, so package names, the rulvar binary, rulvar.config.mjs, the .rulvar store directory, the rulvar.* OTel attributes, and every URL are unchanged. Documentation and metadata only; no runtime behaviour changes.

1.5.2

1.5.1

1.5.0

1.4.0

1.3.2

Patch Changes

  • ddef383: Every published package now ships a README, so its npm page states what the package is, how it installs, and where the documentation lives (npm includes README.md in the tarball regardless of the files allowlist, so no manifest changes are involved; @rulvar/compat gains its README on its own next release). Alongside, the repository-level pages are refreshed to the current project state: the root README is rewritten around the never-pay-twice pitch with a runnable quickstart condensation and the full package table, CONTRIBUTING.md lists the complete PR gate set, the examples README drops retired-spec citations for live docs.rulvar.com links and documents the dogfood journal replay, and the pointer README gets the same treatment.

1.3.1

Patch Changes

  • 7d1552e: Runtime message strings no longer cite the retired internal specification set: error and warning messages, validation issues, and the CLI help text drop the dangling docs/NN, section ... references, pointing at https://docs.rulvar.com pages where a pointer earns its place (the CLI help header, tool naming, toolset registries, bare resume). The umbrella package description sheds the naming-contingency note: the unscoped alias is published and owned. Three strings embedded in frozen recordings stay byte-identical on purpose (the no-progress abort reason and two testing-internal recorder strings), as does the byte-locked golden-fold fixture. Test-file comments lose their citations too; test titles are unchanged.

1.3.0

1.2.0

Patch Changes

  • 154507b: TSDoc and inline comments no longer cite the retired internal specification set (the pre-docs-site docs/NN, section ... references). The citations either became links to the public documentation at docs.rulvar.com or were dropped where the comment already carried the rule; traceability markers (DEF-n, XF-nn, FR-nnn, OQ-nn, W-nnn) are untouched. Comment-only change: no runtime behavior, no API shapes, and no runtime message strings were modified; the frozen golden-fold fixture is byte-identical.

1.1.0

1.0.0

0.9.0

0.8.0

0.7.0

Minor Changes

  • 4aaf2d5: M6-T03: the determinism rule set with structural JSON diagnostics (docs/06 8.4). Rules: no-bare-date (Date.now and new Date), no-bare-random (Math.random), no-fetch (bare and globalThis.fetch), no-process-env, no-promise-all-over-ctx (Promise.all/allSettled/race/any spawning ctx or bare sandbox calls; ctx.parallel instead), and the duplicate-identical-call advisory (byte-identical ctx.agent/ctx.workflow calls in one function forward-match to one journal entry; opts.key distinguishes deliberate repeats). Locally shadowed globals are never flagged. The flat preset configs.workflows wires every rule at its intended severity, and toJsonDiagnostics projects lint messages into the machine-readable shape the mode (b) self-repair loop consumes.

0.6.0

0.5.0

0.4.0

0.3.0

0.2.0

0.1.0

Minor Changes

  • f4e2be9: M0 repo bootstrap (v0.1.0, docs/10-implementation-plan.md section "M0"): monorepo scaffold on the committed toolchain (pnpm 11 workspaces with catalogs, TypeScript 6.0, tsdown, Vitest 4, ESLint 9 flat config, Turborepo 2, changesets fixed mode, npm trusted publishing), the docs/ canon as single source of truth, the L0 contracts skeleton in @rulvar/core, and the vendored dependencies (StandardSchemaV1/StandardJSONSchemaV1 types, the @cfworker/json-schema lineage validator subset, a first-party monotonic ULID). Placeholder scaffolds only: no public API ships in this release.

@rulvar/evals

1.252.0

Patch Changes

1.251.0

Minor Changes

  • ba7e1e1: The shared contract audit lexer (RV4603, the seventh comparison experiment's P2.1). Every hand rolled comparison harness rediscovers the same two counting defects, and the seventh experiment's post audit shipped both: it counted acceptance.minSpawnedChildren:4, a config property in citation clothing, as a citation occurrence, and recognized zero of the winning answer's 88 requirement ids because they were written as dash led list items instead of the colon form the counter expected; both texts carried the full N48/R24/C16 sets and the report recounted them by hand. lexContractAudit(text, options?) exports that recount as a grammar: the citation shape is the engine's own DEFAULT_CITATION_PATTERN with the citation audit's range tail semantics, fenced code strips by default via the shared stripFencedBlocks, a citation must name a known source file extension (DEFAULT_CITATION_EXTENSIONS) and, under a supplied pure snapshot resolve, must resolve its first line; refused spans land in rejected with reasons instead of disappearing. Requirement ids accept the colon, dash and table notations as one vocabulary with per occurrence forms and DISTINCT per family counts. On the seventh experiment's frozen records the lexer reproduces the corrected numbers exactly: 292/276 for the winner with the one property notation rejected, 145/128 for the candidate, 48/24/16 on both sides in both notations. Probes pin the extension gate, the one vocabulary, the fence strip, and the range tail.

Patch Changes

  • e7e829c: The negative scenario citation convention (plan 47 B1..B3): a hypothetical is never a line fact. The orchestration guide gains a section with the paste-ready composer block (cite the DEFENSE the scenario attacks, mark the scenario as inference), the profiles guide carries the census evidence the mandate question waited for (sample buys honesty, census buys completeness; the floor still does not require the census), the audit section documents its own surface (auditScope census, the RV4706 output cap guard, the RV4707 truncated unit extension), and regression fixtures pin what the convention buys from the deterministic layers: the genre form is lint silent by design, the convention form lints clean, a moved defense line convicts with line suggestions, and the contract audit lexer keeps every count over the rewrite. No runtime change.
  • Updated dependencies [e7e829c]
  • Updated dependencies [5982be8]
  • Updated dependencies [7c58fb2]
  • Updated dependencies [b3e465a]
  • Updated dependencies [c4e5d6a]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [0ae8b85]
  • Updated dependencies [7932936]
  • Updated dependencies [7932936]
  • Updated dependencies [88da0ed]
  • Updated dependencies [06c0e85]

1.250.0

Patch Changes

1.249.0

Patch Changes

  • Updated dependencies [8862133]
  • Updated dependencies [0d7a717]
  • Updated dependencies [e4428bd]
  • Updated dependencies [d6873c1]
  • Updated dependencies [4092e8d]
  • Updated dependencies [e086590]
  • Updated dependencies [1411938]
  • Updated dependencies [737d1ee]
  • Updated dependencies [634f966]
  • Updated dependencies [052cc26]
  • Updated dependencies [bbae134]
  • Updated dependencies [67a8d72]

1.248.0

Patch Changes

1.247.0

Patch Changes

1.246.0

Patch Changes

1.245.0

Minor Changes

  • dee6db4: The workflow answers for its own repairs (RV4002, the fifth comparison experiment). The run paid for exactly one repair (a coordination draft rejected by three validators, healed by a sectional resubmission, one more wire at $0.186) and every terminal aggregate answered truthfully for its own stage while no surface answered for the workflow: the independent judge rebuilt the count from the raw transcript and the repair wire's money drowned in 'coordination'. The exported repairLedgerFromJournal folds the workflow-wide ledger ({ draft, composition, semantic, total } plus one row per granted repair with its stage, verdict seq, failed validators, spliced sections, and the repair wire's ref and price when the billing lane covered it); the acceptance envelope carries repairs computed by the same fold over the run's own snapshot, so live and post-hoc agree by construction. The draft gate journals its voice (orchestrator_draft_gate on rejection and on the healing sectional acceptance), finish-validation decisions carry their stage and spliced markers, and the granted repair turn's own wire is stamped phase: 'repair' (ProviderCallRecord.phase), which all three byPhase folds split out of the hosting dispatch's bucket. rulvar cost-audit prints the ledger when the journal proves one, byte parity otherwise; pre-RV4002 journals fold with unstagedVerdicts named, a floor, never a guess; clean runs keep every byte (all 61 frozen fixtures verify unchanged). Kit: coordination-draft-repair pins the experiment's exact shape ({ draft: 1, composition: 0, semantic: 0, total: 1 }, the gate decisions, the stamped wire) and sectional-repair-round pins the semantic round's ledger; four mutation probes pin the wire stamp, the gate's journal voice, the round count, and the CLI line. journal-shape-revision: the wire-level phase stamp is an additive journal evolution, and the frozen cassettes whose flows contain a refused finish exchange are re-recorded under it.
  • b85c113: The coverage grade becomes a gate under a declared policy (RV4003, the fifth comparison experiment). The run's claim pass covered 54 of 74 citing sentences, graded itself 'partial' honestly, MET its own declared 0.72 coverage target at 0.7297, and the run still shipped three unsupported citations inside exactly the uncovered fraction: every ratio floor held and none of them binds the grade. claimConsistency.coveragePolicy: 'strict-final' refuses acceptance typed when the FINAL pass's grade is anything but 'full' (partial, vacuous, critical-uncovered, judge-declined, judge-failed alike), unless the declared waiver { principal, reason, expiresAt? } stands: the waived acceptance journals a claim_coverage_waived decision and carries claimCoverageWaiver on the envelope verbatim beside the meta, so a non-full grade on a strict run always names who accepted it and why; an expired waiver refuses exactly like none. The default 'observed' keeps every existing byte; the policy requires stage 'final' or 'both', and a waiver without the policy is a ConfigError. Kit: strict-coverage-policy drives both arms (the typed refusal and the journaled waiver); probes pin the gate and the expiry; the orchestration guide names the doctrine ("0 findings" was never "semantically verified" without its denominator) and the doctrine-pin gate now requires it.
  • bc556e7: The citation entailment audit (RV4004, the fifth comparison experiment): the independent judge's method, internalized. The run's built-in verification judged VALUES (cited-value), TARGETS (citation-targets), and CONSISTENCY (the claim pass, child readings against draft claims), and the shipped answer still carried three citations whose cited lines do not entail the sentences citing them, every one mechanically valid, value-clean, and invisible to a pool that held no reading of those files (20 of 74 citing sentences had no candidates at all; child-against-final pairing can never cover them). citationAudit runs over the FINAL document: a deterministic stratified sample (per H2 section, seeded from the audited document's own hash, replay-stable, capped), excerpts read through the host's pure snapshot resolver (the citedValueValidator channel; a citation whose first cited line does not resolve is unsupported mechanically), one bounded judge invocation ruling supported | partial | unsupported per sampled citation. The envelope carries citationAuditMeta and citationFindings; onFound: 'report' | 'fail' | 'repair' decides the consequence, with 'repair' riding the RV3307 bounded round (one more composition carrying the findings, a fresh audit from the repaired document's new hash, a configured claim pass rejudging the rewritten document, survivors failing typed; arming it beside the claim round is a ConfigError, one bounded round per run). The declared judge.estCost enters the RV4001 acceptance-tail formula on both the runtime gate and preflight (citationAudit mirror on the preflight orchestrator spec), one pass or two, with the round composition and the claim rejudge priced. Kit: citation-entailment-audit drives the flagship shape (the unsupported citation caught, the supported control clean, the armed fail typed); three probes pin the mechanical unresolved verdict, the fail gate, and the round's re-audit.
  • 19bcea0: The pre-wire provider intent (RV4006, the fifth comparison experiment's P0.5). Receipts journal after a wire settles, so the wire most exposed at a crash is exactly the one being paid for: between dispatch and receipt, a death leaves money the journal never heard about. defaults.billingReceipts: 'intent' journals a provider-intent decision before every dispatched wire attempt (awaited, the executor ledger's intent-before-effect rule: a failed intent append refuses the dispatch), keyed by dispatch seq, ordinal, and attempt, carrying the serving model, role, and a sha256 request fingerprint; receipts stay awaited as under 'awaited'. An intent with neither a receipt row nor a settled terminal covering it is a wire with UNKNOWN outcome: the exported openWireIntentsOf fold names them, the invoice carries the openIntents lane (no invented dollars), rulvar cost-audit prints it, and a resume that finds one refuses the blind retry typed until ResumeOptions.acknowledgeOpenWireIntents: true is passed, which the new segment journals as open_wire_intents_acknowledged. Dispatch stays at-least-once with attempt binding; the default 'async' and 'awaited' postures keep every byte. Kit: wire-intent-unknown-outcome drives the reconstructed crash window through both resume arms; probes pin the quota-arm intent, the resume gate, and the receipt closure.

Patch Changes

1.244.0

Minor Changes

  • 38d839a: RunOptions.budgetPolicy: 'segment' | 'immutable-lifetime' (RV3902, the fourth comparison experiment): the regulated posture the docs used to promise by accident is now a real, opt-in invariant. Default 'segment' is today's behavior byte for byte. Under 'immutable-lifetime' the posture is recorded in RunMeta at genesis (only the non-default is written; the store conformance kit holds stores to the round-trip) and restored on every resume, and a resume carrying ANY applying ResumeOptions.run override refuses with a typed ConfigError before ownership, meta writes, or any append, raising and lowering alike; the empty run: {} object stays the documented no-op, a bare resume stays a pure replay, and a store that drops the field degrades to 'segment' (the door works again), never to an invented refusal. The fault kit gains the budget-policy-immutable scenario (typed refusal, zero wires, zero durable mutations, bare replay intact); two mutation probes pin the refusal gate and the genesis recording. The source TSDoc sweep retires the last immutable after start comments (engine, budget, termination, orchestrate, plan), and the docs doctrine pins now scan docs/api too.
  • 4fa23e3: The verdict lineage on the acceptance envelope (RV3904, the fourth comparison experiment): the run's terminal read findings: 0 over a lineage whose first judge pass had caught a real contradiction, and only the journal could say so. Under the armed claim repair round, claimConsistencyMeta now carries passes, firstPassFindings (when passes exceeds 1), and semanticRepairRounds, so a repaired verdict is distinguishable from a clean first one on the envelope; absent fields mean NOT RECORDED (no round armed, or an older journal), and the mechanical repairsUsed keeps its byte contract untouched. Beside it, the acceptance envelope gains deterministicPatches (the RV3801 machine-patch aggregate: accepted decisions, total patches, the last patch's canonical before/after hashes), derived from the same journaled finish decisions the patches live on, so live and resumed envelopes agree by construction. The sectional and deterministic-patch kit scenarios pin the lineage and the aggregate; two mutation probes pin the pass count and the envelope block; the observability guide documents what zero findings does and does not mean.
  • c894a43: budget.acceptanceReserve: 'warn' | 'require' (RV3907, the fourth comparison experiment): preflight has long priced the acceptance tail and warned (reserve-line-headroom, orchestrator-working-room), and the experiment's run started anyway with both warnings on record. Under 'require' the declared acceptance tail (the held synthesisReserveUsd, the claim judge's estCost times one plus the armed semantic repair round, the declared finishValidation.estRepairCostUsd, and the armed round's declared synthesis.estCost composition floor) plus one coordination turn floor must fit the effective cap at exact fill or better, or the run refuses with a typed OrchestratorCapConfigError BEFORE the first wire, journaling an acceptance_reserve_refused decision that names every term. Undeclared estimates contribute zero, so the gate binds exactly what the host declared; the default 'warn' keeps today's behavior byte for byte. The fault kit gains acceptance-reserve-refusal (typed refusal, zero dispatches, term-by-term decision); boundary tests pin exact fill as admission; one mutation probe pins the gate.
  • 23fd0e0: The stale-doctrine corpus class and the proactive sectional reminder (RV3909, the fourth comparison experiment). The corpus gains stale-doctrine-echo: a draft echoing a DOCUMENTED doctrine while the pool holds the diverging source fact, both sides cited, the experiment's decisive failure shape ("immutable after start" echoed from a guide six weeks stale into a pool that never carried the source side); the honest formulation naming the override door is pinned as a test-side control (the source-claim pairing is polarity-blind by design, and the exoneration belongs to the judge, who now holds both sides). The sectional repair round's prompt gains a deterministic evidence-discipline reminder (the experiment's rewritten section birthed two new evidence-grade offenders that the RV3801 patch then healed; a prompt line is cheaper than a healed failure), present only under the sectional block so every other prompt stays byte-identical; the kit's sectional scenario pins the line present in the round and absent from the initial composition. Two mutation probes pin the reminder and the class roster.

Patch Changes

1.243.0

Minor Changes

  • 746d1f4: The finish loop performs the evidence-grade prescription host side (RV3801). The third comparison run died fail closed twice on one failure class: sentences in the graded register with no artifact, whose verdict already told the model exactly which sentences to fix and exactly which id to write; the initial composition spent the mechanical pool on it, and the repair round's candidate hit it again with nothing left. evidenceGradeValidator, with the runtime's runId in hand, now attaches structured repair hints to its failure (FinishRepairHint: the offending sentence's exact offsets and bytes, and the prescribed insertion), and the finish loop, when EVERY failure of a string candidate carries hints, applies the edit itself: the id lands inside each offending sentence before its trailing terminator, every other byte stays identical, and the FULL validator set re-judges the patched document. A surviving patch is an accepted verdict with no provider wire and no repair spent; the decision journals it (deterministicRepair: before and after hashes, the patch windows, the healed failures), the healed failures still feed the HOST VALIDATION LESSONS block, and everything short of a surviving patch falls through to the ordinary model repair pool with the original verdict bytes. Masking is excluded by construction: the claim judge rules on the PATCHED document, so an inserted id can satisfy provenance mechanics but never protect a false claim from the semantic pass. The fault kit gains deterministic-provenance-patch (the adversarial arc on the real engine: a false positive production claim healed mechanically, then caught semantically, the lesson carried into the round), and validator-guidance-conflict now pins the c3 trap healing in ZERO model repairs with the guidance bytes journaled on the healed verdict.
  • 009b29c: The convergence hold grows its mechanical leg (RV3802). RV3701 holds the repair round's verdict money and RV3602 gives the round its own mechanical pool, but the one repair turn that pool can grant was funded by nothing: the third comparison run's round entered exactly that turn's price short of certainty. The round now holds a second named leg beside the verdict money from the moment it is admitted, sized from the declared finishValidation.estRepairCostUsd first (a new opt, refused typed unless a nonnegative finite number), else from the run's own observed last mechanical repair window (lastMechanicalRepairCostUsd, a new pure fold over the journal's synthesis candidates, which also gain spanSeq so the pairing never crosses invocations), else zero and inert. The leg joins the projected admission sum and both remainders (repairReserveUsd on the account state and view), a refusal names BOTH legs in its printed arithmetic, and the release is STAGED: the mechanical leg frees at the round invocation's first journaled finish verdict (a repair verdict is about to spend it on the granted turn; an accepted one never needed it), while the verdict leg lives until the judge dispatch as before. The fault kit gains repair-round-mechanical-reserve (the ceiling where the round could pay its composition and verdict but not the granted repair: pre dispatch refusal, both clauses named), and two mutation probes hold the admission sum and the staged release.
  • 1674cbe: The claim repair round is sectional when it can be exact (RV3803). The third comparison run's round regenerated the whole 43k character document to consume findings living in a handful of sentences, inside a tail that was 80.1 percent of the run's wall. The round now plans its repair before dispatching (sectionalRoundPlan, exported): each judged finding's excerpt is located in the accepted pre-repair document through a collapse-aware scan and owned by the nearest H2 heading above it; when every excerpt locates and the markers are unique, the round's prompt retains the accepted document and asks for ONLY the target sections through the RV808b splice vocabulary, the host splices the resubmitted bodies into the retained document with every other byte identical, and the FULL validator set plus the final judge rule on the spliced whole. Mechanics refusals journal nothing and spend no repair; the model may still resubmit the full document; and every inexact plan (no headings, duplicated markers, an unlocatable excerpt, no finish contract) falls back to the FULL regeneration, byte for byte the historical round. The fault kit gains sectional-repair-round (byte identity of untouched sections, whole-document judging, no mechanical repair spent) and sectional-repair-round-fallback; two mutation probes hold the splice and the fallback.
  • 4e516f3: The claim corpus gains the three failure classes the third comparison experiment validated (RV3804). bound-conflation: a draft lists opt-in caps and unconditional guards as one mode (the run's exact shape: the MCP byte and page caps beside the cursor guards), and the pool reading distinguishes the halves for the judge. derived-premise: a derived figure whose premise contradicts the declared input (2,000 slots computed from a 30 minute window where the input declares a 20 minute burst; the honest arithmetic is 1,333). cost-basis: a locally estimated total printed as the provider's bill, against a fact sheet naming the 'locally-estimated' basis and the absence of any reconciled statement. Each case is a pure fold through the same pairDraftClaims/pairRunFactClaims/claimCoverageOf layers the orchestrator runs, forms judge-gradeable pairs with both polarities attached, and ships with an honest-formulation negative control that triggers nothing.

Patch Changes

1.242.0

Patch Changes

1.241.0

Minor Changes

  • 4f832c4: The mechanical repair pool belongs to one composition invocation (RV3602). finishValidation.maxRepairs used to count non accepted verdicts run wide, so the bounded claim repair round (RV3307) entered with zero mechanical retries whenever the initial composition had spent its own, and under the default bound of one its first regression was final by construction; that arithmetic is how the third comparison run died honest but unconverged with $1.42 of headroom left. The pool now restarts at each composition dispatch: the boundary is the journaled verdict count at dispatch (replay derives the identical index from the identical prefix, no new journal fields), the cycle 73 contract generation rule still applies on top, and validators bound to the coordination loop keep the run wide reading byte for byte, one loop being one invocation. Worst case stays bounded: at most two invocations (the initial and one repair round), each granting at most maxRepairs repair turns; preflight's RV3402 working room term already prices the round at the declared synthesis reserve, which is the host's estimate of exactly one invocation with its repairs, and the RV2504 reserve tail sizing needs no doubling (comments and guide now say so). The fault kit gains repair-round-own-pool: the frozen third comparison sequence carried to the convergence the old pool made impossible, verdicts repair/accepted twice with repairsUsed restarting at the boundary.
  • 7452d3d: The bounded repair round keeps the lessons the run already bought (RV3603). The third comparison run's repair round regressed provenance, the exact failure class the initial composition's mechanical loop had fixed 18 seconds and $0.16 earlier, because the round is a fresh invocation with no memory of exchanges it never saw. The round's prompt now carries a HOST VALIDATION LESSONS: block beside CLAIM CONTRADICTIONS:, folded only from the journaled finish validation failures of the current contract generation (validator names and reasons, deduplicated, journal order), so a resume re derives identical bytes. Present exactly when the prompt already carries judged findings and at least one rejected attempt exists: the initial composition predates any findings and a clean history folds nothing, so every existing prompt stays byte identical. Capped at FINISH_LESSON_CAP_CHARS (2000) with the dropped row count named, never silent. The repair-round-own-pool kit scenario now also pins the lesson riding the round's prompt and absent from the initial composition's.
  • a4e22bf: The repair round's terminal names which death occurred (RV3601). The third comparison run's bounded repair round dispatched, paid two wires and produced a candidate its own finish contract rejected, and the terminal read could not dispatch with repairsUsed: 0 beside a null judge meta and null findings. A throw carrying the orchestrator_finish_validation source is now its own class: the message names the dispatch and the host rejection, data carries roundDispatched: true, repairsUsed: 1, the judge meta beside the findings, and the finish verdict facts verbatim under finishValidation (the failed validators with reasons, candidateHash, candidateChars, mirrored from the decision the journal already holds; the typed finish failure itself now carries the candidate identity too). The true pre dispatch decline keeps its frame and gains the judge meta plus roundDispatched: false. The engine lifts claimContradictions onto RunOutcome, the journaled settle and run:end beside the meta, from the acceptance envelope or the typed error data alike, under the same defensive posture as the meta lift; the compact terminal envelope keeps the meta alone, its findings count standing in for the details. The fault kit gains repair-round-host-rejection driving the arc end to end on the real engine.

Patch Changes

1.240.0

Patch Changes

1.239.0

Minor Changes

  • d3568b6: The fault kit drives the post fan in tail arc (RV3403). The 2026-08-12 comparison run settled ok/complete over a finding its own final judge had named, and the fixes that followed shipped with unit suites but no kit scenario ever drove the arc end to end on the real engine. Three scenarios close that: repair-round-honesty (the final judge finds, the findings ride one more composition, the re-judge clears it, the settled envelope reports the repaired document as the judged one, two compositions and two final judge passes in the journal, the invoice in the same denominator), repair-survivor-refusal (the re-judge still finds: typed failure with repairsUsed: 1 and two distinct document hashes, never a silent ok), and claim-judge-dead-armed-refusal (a judge that dies on the wire under 'fail' and under 'repair' fails the run typed with the armed posture named, one composition paid, no round dispatched). Probe: the-kit-actually-drives-the-repair-round.

Patch Changes

1.238.0

Patch Changes

1.237.0

Patch Changes

1.236.0

Patch Changes

1.235.0

Patch Changes

1.234.0

Patch Changes

1.233.0

Patch Changes

1.232.0

Patch Changes

1.231.0

Patch Changes

1.230.0

Patch Changes

1.229.0

Patch Changes

1.228.0

Patch Changes

1.227.0

Minor Changes

  • f262e9f: The run's own id becomes an artifact the evidence grade accepts (RV2501). evidenceGradeValidator demands that a live-observed, provider bill or production-proven sentence name an artifact IN THAT SENTENCE, and DEFAULT_ARTIFACT_PATTERN only ever matched a path:line citation or a ULID behind the literal word run. Every other run id was therefore unnameable: the 1.226.0 comparison run carried the id comparison-rulvar-v12260-aug09-1786272840549, its verdict told the synthesis to state that id, the pattern matched nothing it could write, and the run spent both granted repairs and failed closed on two sentences telling the truth about the run they were part of. FinishValidationInput now carries runId, and the orchestrator runtime supplies it at every gate that judges a finish: the validator-bound finish, the contract draft gate, and the skipWhenDraftValid pre-pass. A sentence carrying that id verbatim as a whole identifier satisfies the grade, and the verdict NAMES the id it wants written, so the repair instruction is executable instead of aspirational while the RV2202 composition warning stands (a run id written beside a path:line citation is not in the cited window and trades this failure for a cited-value one, so the graded sentence must carry no source citation). The intake is bounded like every sibling: an id shorter than six characters is ignored, because a two character id would satisfy nearly every sentence by accident, the same fail open the empty-pattern guard refuses; the id is credited only as a whole identifier, so x<id>y is never an artifact; and with no runId supplied the verdict is byte identical to the historical one. The same defect had a second half in the prompt: the opt-in RUN FACTS: line (RV1503) ends in the live-observed register, the composing model is told to reproduce run facts only from it, and the line named no artifact at all, so the engine was steering its own synthesis into a sentence its own default bundle refuses. The line now carries runId in its JSON and reads live-observed by run <id> in the same sentence as the graded phrase, so quoting it faithfully passes evidenceGradeValidator and, carrying no source citation, passes citedValueValidator beside it; a test asserts exactly that over the bytes the engine actually writes, with the id-less contrast asserted as the historical failure. The RUN FACTS line stays folded only from replay-stable material, so a resumed synthesis re-derives identical bytes; hosts that pin synthesis prompt bytes across engine versions should expect this line to have changed. The validator-guidance-conflict fault scenario drives the new arm end to end: its corrected finish now carries the run's OWN id rather than a fabricated ULID, and the scenario asserts the repair exchange names that id verbatim beside the citation-free composition, so the guidance the fault kit gates is the guidance a run can actually execute.

  • f191ff7: Identity spans are not asserted values (RV2502). citedValueValidator reads every non-citation inline span in a citing sentence as a value asserted about that citation, and the 1.226.0 comparison run showed the class that rule over-reaches: its synthesis wrote the frozen commit sha f8d9c5131c99c843ed23da22af20651f95377dd0 beside source citations, and the verdict demanded the sha appear in the cited source, an impossible repair delivered in the same reason list as three real value fixes; both granted repairs burned and the finish was rejected. A span naming the artefact under review says which commit, run, or release the document is about and asserts nothing about any cited line. Three shapes are now structural and always excluded: a commit sha (12 to 64 hex characters, a floor low enough for every real abbreviation and high enough that ordinary hex literals like deadbeef stay judged), a release version (1.2.3, v1.2.3, optional prerelease or build tail), and the run's own id when the runtime supplies runId, on the same six-character floor the evidence grade uses. Host vocabulary is declared rather than guessed: the new notValues option lists the spans a document writes as identity, verdict words like conditionally ready among them, matched whole and case sensitively; a malformed list is a ConfigError at construction. Nothing else relaxes, and a genuine value the cited line does not carry still fails in the very same sentence as an excused sha.

    The run-id exclusion makes the shipped bundle self consistent. evidenceGradeValidator instructs a failing model to write this run's id inside the offending sentence (RV2501), and RV2202's warning existed because obeying that beside a citation traded an evidence-grade failure for a cited-value one, the trap that burned both repairs of the third subscription run. The two arms of the grade's reason now each name the composition that is TRUE for them: with the id in hand the graded sentence may carry a citation as well, and without one the older separation advice stands, because there the sibling has no id to recognise. The validator-guidance-conflict fault scenario converges on the direct shape, the run's own id written beside the citation in the graded sentence, which neither validator could accept before.

Patch Changes

1.226.0

Minor Changes

  • bef8621: The parity crash shapes become permanent fault-kit gates, and the telemetry target gets its honest boundary (RV2210). Three scenarios, zero paid calls, each driving a branch a parity run died on and asserting the documented typed observable, fail closed. parity-reserve-line-redemption (RV2101): the coordination turn refused at spent + held synthesis reserve + proposed folds typed 'budget-floor' and the held reserve then FUNDS the synthesis whose result rides the partial envelope; the scenario drives the same shape through makeOrchestratorWorkflow AND through the PlanRunner extension and demands the identical fold and the identical redeemed result from both, the DEF-7 redemption parity verified rather than assumed. resume-spawn-famine (RV2201): the kill-mid-fan-out journal resumes at the EXACT lifetime spawn cap to the finished dossier, recovered agents re-admitted but never re-counted, no cap decline journaled, only the unsettled workers re-paid. validator-guidance-conflict (RV2202): the c3 trap finish repairs in ONE round because the evidence-grade reason names the composition that cannot trip its cited-value sibling, with the guidance bytes asserted on the repair exchange itself. Docs truths ride along: the evals scenario list catches up (the RV2009 parity gates included), the observability guide gains the postFanInShare targeting rule (on clustered-settle profiles the share's overlap ceiling is the settle spread itself, so target postFanIn.coordinationModelMs and finalCompositionMs absolutes, with the subscription series' accepted-dossier baseline as the worked example), and the terminal contract names the RV2203 guarantee that failed terminals carry the same lifted facts.

Patch Changes

1.225.0

Patch Changes

1.224.0

Patch Changes

1.223.0

Patch Changes

1.222.0

Patch Changes

1.221.0

Patch Changes

1.220.0

Patch Changes

1.219.0

Patch Changes

1.218.0

Patch Changes

1.217.0

Patch Changes

1.216.0

Patch Changes

1.215.0

Patch Changes

1.214.0

Patch Changes

1.213.0

Patch Changes

1.212.0

Patch Changes

1.211.0

Minor Changes

  • d5a8a36: The third parity rerun's crash shapes become permanent fault-kit gates (RV2009), zero paid calls. parity-quiescence-deadlock drives the exact terminal shape in miniature: the coordination turn eats the exposure cap, every worker is refused DRAINED (typed exposure-drained, zero provider attempts, RV2001/RV2002), the root forced-finishes partial (RV1902), and the gate asserts the exhausted terminal, the closed roster, run_settle after every agent entry, one wire denominator, and no unsettled invoice lane (RV2003/RV2008); any revert reads matched:false. parity-sequential-roster-floor drives the seat-by-seat roster under an unreachable acceptance floor and asserts the FIRST seat's typed roster_floor refusal with the whole-roster arithmetic journaled and zero paid children (RV2005). The docs truth pass lands the no-silent-exit invariant in the README and the design principles (no path ends the process while a run has no journaled terminal) and extends the observability denominator map with the RV2008 incremental lane and its settled boundary.

Patch Changes

1.210.0

Patch Changes

1.209.0

Patch Changes

1.208.0

Patch Changes

1.207.0

Patch Changes

1.206.0

Patch Changes

1.205.0

Patch Changes

1.204.0

Patch Changes

1.203.0

Patch Changes

1.202.0

Patch Changes

1.201.0

Patch Changes

1.200.0

Patch Changes

1.199.0

Patch Changes

1.198.0

Patch Changes

1.197.0

Minor Changes

  • 4d0487e: The four-role benchmark's two defect shapes join the fault-injection kit as permanent gates (RV1905). benchmark-primary-preflight-parity drives the exact primary configuration ($6.00 ceiling, $4.50 orchestrator cap, $1.00 synthesis reserve, four workers at estCost $0.62) and matches only when the projection seats 2 of 4 with the synthesis hold and per-row held terms exposed and the roster shortfall named admission-below-roster-floor (RV1901). benchmark-recovery-root-exposure drives the recovery arm's shape on the real engine with scripted adapters and zero provider calls: a root turn refused by the exposure cap beside live gated children parks and completes after a hold releases (RV1902), every child terminal precedes run_settle (RV1903), and the terminal envelope and the invoice cardinality agree on the wire count (RV1904). Reverting any of the four fixes reports matched: false here, not only in the unit suites that shipped them.

Patch Changes

1.196.0

Patch Changes

1.195.0

Patch Changes

1.194.0

Patch Changes

1.193.0

Patch Changes

1.192.0

Patch Changes

1.191.0

Minor Changes

  • 745387c: Enforceable coverage floors and two new corpus classes (RV1809). The claim pass graded itself honestly (RV1702) but nothing could enforce a floor: claimConsistency.minimumCoverageRatio and runFactCoverageRatio (each in (0, 1]) now declare the minimums, onLowCoverage: 'report' (default) stamps the machine-readable lowCoverage block on the meta with each ratio beside its floor, 'fail' fails the run typed BEFORE the judge dispatch exactly like onUncoveredCritical, the meta additionally carries runFactCandidates (the uncapped matched count, so both ratios are computable from the meta alone, live or persisted), and --strict exits nonzero on a stamped block with the ratios printed. The adversarial corpus grows two classes from the nineteenth benchmark: modality-overclaim (a mitigation stated as an unconditional guarantee: the attestation "stops any tool drift" beside the pool reading naming the contract-hash boundary) and scope-ambiguity (child-only totals printed as whole-workflow figures), both forming pairs through the same pure folds.

Patch Changes

1.190.0

Patch Changes

1.189.0

Patch Changes

1.188.0

Patch Changes

1.187.0

Patch Changes

1.186.0

Patch Changes

1.185.0

Patch Changes

1.184.0

Patch Changes

1.183.0

Patch Changes

1.182.0

Patch Changes

1.181.0

Minor Changes

  • f794c11: The adversarial claim corpus ships as a regression gate (RV1704). The eighteenth comparison benchmark's three worst failures were semantic, and each rode straight past a green mechanical surface: "real models were not run" beside 125 recorded wire requests, @rulvar/plan described through a packages/planner citation, and a store default inverted in prose. A judge model can only rule on what the folds put in front of it, so the offline regression that matters is the precondition: for every named failure class, the deterministic layers must still form the pair, trigger on the run facts, prioritize the declared claim, and grade the coverage honestly. CLAIM_CORPUS pins that precondition as data, one adversarial case per class (live-fact, package-identity, inverted-default, numeric-range, negation, bounded-coverage), and runClaimCorpus() executes every case through the same pure folds the orchestrator runs (pairDraftClaims, pairRunFactClaims, claimCoverageOf), no engine and no model, reporting per-case verdicts with the formed pairs attached for judge handoff. The shipped test asserts every case passes, so a change that stops forming any of these pairs fails the suite by case id instead of surfacing in the next paid benchmark. The corpus deliberately does not claim the pairs would be judged correctly: the pool excerpts ride every verdict so a host can adjudicate the semantic half with a real judge on their own budget.

Patch Changes

1.180.0

Patch Changes

1.179.0

Patch Changes

1.178.0

Patch Changes

1.177.0

Patch Changes

1.176.0

Patch Changes

1.175.0

Patch Changes

1.174.0

Patch Changes

1.173.0

Patch Changes

1.172.0

Patch Changes

1.171.0

Patch Changes

1.170.0

Patch Changes

1.169.0

Patch Changes

1.168.0

Patch Changes

1.167.0

Patch Changes

1.166.0

Patch Changes

1.165.0

Patch Changes

1.164.0

Patch Changes

1.163.0

Patch Changes

1.162.0

Patch Changes

1.161.0

Patch Changes

1.160.0

Patch Changes

1.159.0

Patch Changes

1.158.0

Patch Changes

1.157.0

Patch Changes

1.156.0

Patch Changes

1.155.0

Patch Changes

1.154.0

Patch Changes

1.153.0

Patch Changes

1.152.0

Patch Changes

1.151.0

Patch Changes

1.150.0

Patch Changes

1.149.0

Patch Changes

1.148.0

Patch Changes

1.147.0

Patch Changes

1.146.0

Patch Changes

1.145.0

Patch Changes

1.144.0

Patch Changes

1.143.0

Patch Changes

1.142.0

Patch Changes

1.141.0

Patch Changes

1.140.0

Patch Changes

1.139.0

Minor Changes

  • 03a2141: The live budget debits each provider call marginally against the call's own accumulated price (RV1101): a long-context tier crossed by the call's sum that no single mid-stream slice reached now re-prices the whole call live at the crossing slice, exactly the dollars the settled fold records, and a ceiling between the per-slice and tiered readings severs the run instead of settling ok over its own hard cap. RunBudget.openCallMeter and the optional BudgetHooks.openCallMeter carry the seam (one meter per provider call, the settled fold's billing basis; the mid-stream deltas and the settle remainder of one call share one accumulation; a marginal debit never credits; the tier still never fires on a run aggregate no single call crossed). The fault kit gains the tier-crossing-live-parity scenario (RV1102), pinning both money paths and the marginal live ladder on the real engine.

Patch Changes

1.138.0

Minor Changes

  • ed0c4fb: Pre-wire continuation reservation, the self-describing fault kit, and the run-id surface (RV1013 + RV1014, PR VII closing the fourteenth plan)

    • Pre-wire continuation admission (RV1013, opt-in). Post-hoc settlement is accounting, not admission: a hard provider RPM cap needs each pause_turn continuation reserved BEFORE its egress. With quota: { reserveContinuations: true } the engine admits every provider-side continuation through the new adapter-side StreamHooks seam (ProviderAdapter.stream gains an optional third parameter; the Anthropic adapter honors it): under a 2-request window the third wire of one absorbed dispatch never leaves and the denial rides the provider-429 machinery verbatim, the main settlement stops re-adding individually admitted segments (the window is never double-counted), and a granted admission whose wire never left is RELEASED back to the window through the new optional QuotaLimiter.release(reservationId) (implemented by memoryQuotaLimiter; a release returns exactly what admission consumed, and unknown or expired ids are no-ops). Adapters unaware of the hook keep the documented post-hoc semantics byte for byte, and the default stays post-hoc. The midstream-versus-finish usage confirmation now fires only when a finish CLAIM exists: an error-terminal absorption (a segment denial, a transport cut) no longer manufactures an invariant violation that shadows the real wire error.
    • The self-describing kit (RV1014). runFaultInjection refuses an empty only selection typed (a gate that runs zero scenarios used to report allMatched: true), and the report carries requested and selected counts so the gate can never quietly shrink. The audit scenario grows the RV1007 arcs (a page-only long-context tier and a NaN scalar are findings, never silent passes), completing kit coverage of every real defect of the fourteenth plan on its real path.
    • The run-id boundary surface (assertSafeRunId, MAX_RUN_ID_LENGTH) is now exported from @rulvar/core, so hosts can pre-validate ids before engine.run.

Patch Changes

1.137.0

Patch Changes

1.136.0

Minor Changes

  • aa6ca71: A superseded segment refuses green everywhere: typed SupersededError, the distinct settledReason on run:end, and exactly one authoritative successor (RV1009, PR V of the fourteenth plan)

    The fencing design swallowed a superseded segment's LeaseHeldError on both settlement writes, so a stale segment whose settle bounced off the successor's fence resolved ok with an unmarked run:end: a green terminal that no durable store wrote, exactly the split view the RV907 doctrine forbids.

    • The stale segment now rejects handle.result with the typed SupersededError (code superseded, not retryable, data { runId, runStatus }, cause the fencing rejection): the successor owns settlement, and the authoritative outcome is its settle or the store's run meta, never the stale computation. The meta write is skipped instead of re-proving the fence.
    • run:end refuses green with settled: false and the distinct settledReason: 'superseded' (an l0-compatible extension), so an event-only consumer can tell a superseded segment from a settlement write failure; the settlement-failure path and every ordinary terminal keep their exact bytes.
    • A meta-only lease bounce over an already durable settle stays swallowed: the journal records the outcome, and only the projection belongs to the current holder (the takeover no-op contract is unchanged).
    • The CLI progress line renders settled=false (superseded; the successor owns settlement) instead of the resume hint, and the OTel exporter stamps rulvar.run.settled_reason beside the refused span status.
    • runFaultInjection (@rulvar/evals) grows the nineteenth scenario, superseded-terminal-honesty: the fenced-out segment must reject typed with the distinct reason and zero settle entries, and the successor must settle ok by replay with exactly one settle entry and no second paid call.

Patch Changes

1.135.0

Patch Changes

1.134.0

Minor Changes

  • cb50ea0: An internally contradictory statement refuses typed at intake, totals decide beside components, and the reconciliation states the settlement-grade predicate first class (RV1005 + RV1006, PR III of the fourteenth plan)

    The fourteenth comparison experiment fed reconcileStatement an export row carrying usd: 100 beside a component split summing to 1 and read verdict match: each claim sat inside its own tolerance and nothing compared them to each other, because the presence of components suppressed the totals comparison entirely. The same review showed that a match verdict is a weaker claim than settlement needs: an export can cover every KNOWN row to the cent while a usage-unknown attempt still holds unattributed money.

    • Intake internal consistency (RV1005): a request row carrying both usd and a componentsUsd split must have them agree within totalToleranceUsd, else it refuses with a typed ConfigError naming the row; an export whose own total contradicts its own components is not evidence.
    • Totals decide beside components (RV1005): a split's presence no longer suppresses the totals comparison. It decides exactly when both sides' dollar claims cover the same set (every matched export row carries usd in requests mode; nothing statement-only and every component line claimed in categories mode; no covered model unpriced), so a total drifting beyond totalToleranceUsd reads divergence even while every component line sits inside its own tolerance, and a scope mismatch stays the coverage machinery's business instead of manufactured divergence.
    • StatementReconciliation.settleable (RV1006): the settlement-grade composite first class, true exactly when the verdict is match AND coverage is complete AND no row settled usageUnknown AND no model went unpriced. A safe consumer no longer assembles that predicate by hand.
    • runFaultInjection (@rulvar/evals) grows the eighteenth scenario, statement-settleable-guard: a REAL run whose first attempt dies before any usage report seeds a genuine usage-unknown ledger row, the clean export over it reads match with complete coverage yet settleable: false, the clean twin reads settleable: true, and the contradictory row refuses typed at intake. Reverting any of the fixes reports matched: false in the kit.

Patch Changes

1.133.0

Minor Changes

  • 2659f54: A legitimate pause_turn survives the engine end to end, and an invalid continuation cap refuses typed before the first wire (RV1003 + RV1004, PR II of the fourteenth plan)

    The fourteenth comparison experiment drove the real Anthropic adapter through the real engine and a legitimate two-segment pause_turn killed the run: every segment's message_start emitted its own usage mid-stream (5 then 6), the terminal finish carried only the LAST segment's counts, and the engine's midstream-versus-finish invariant read 11 > 6, losing the paid segments from the money. The same experiment fed pauseTurnMaxContinuations: NaN and the cap silently disarmed (continuations > NaN is always false), turning every further continuation into unplanned paid traffic.

    • The terminal finish now speaks for the WHOLE logical turn (RV1003): the adapter accumulates each absorbed segment's normalized usage (sumUsage, cache counts and the TTL split included) and the finish carries the sum, so the invariant confirms the per-segment mid-stream reports, the per-call record and the invoice price every paid segment, and the quota window still settles at true wire units. Mid-stream events stay per-segment deltas; a single-segment turn stays byte-identical. TurnMapping gains the segment's own usage.
    • pauseTurnMaxContinuations must be a nonnegative safe integer (RV1004): any other present value (NaN, Infinity, negatives, fractions, strings) refuses with a typed ConfigError before the first wire, instead of silently disarming the continuation bound.
    • runFaultInjection (@rulvar/evals) grows the seventeenth scenario, pause-turn-real-adapter: the two-segment absorption through the REAL adapter and engine must settle ok at usage 11/2 with both wire ids on the invoice row and the quota window at 2, and the NaN cap must refuse before any wire. Reverting either fix reports matched: false in the kit.

Patch Changes

1.132.0

Minor Changes

  • 2bec904: Live-budget parity for the cache-write TTL split, and the fault kit gates it on the real live path (RV1001 + RV1002, PR I of the fourteenth plan)

    The fourteenth comparison experiment reproduced a hard-ceiling breach: a run with budgetUsd: 4 settled ok at $4.50, because the mid-stream usage inlet, the reported/remainder fold, and every usage aggregate dropped cacheWrite5mTokens/cacheWrite1hTokens, so the live ledger priced a differentiated cache write at the plain 5m rate ($3.75) while settlement priced the split ($4.50). The two money paths now read one provider usage identically:

    • The mid-stream cleaner and the finish remainder carry the TTL split to the live debit, so the layer-3 ceiling holds against the same dollars settlement records; a ceiling between the unsplit and split readings severs the run instead of letting it settle ok over the ceiling.
    • @rulvar/core exports sumUsage, the canonical usage adder: aggregates (the run outcome, the settled ledger fold, the budget telemetry, reduceInvocationTable buckets) keep the split they were billed under, and an undifferentiated side's writes count as the 5m share so mixed aggregates stay canonical under the split-sum invariant.
    • Mid-stream TTL counts the finish total does not confirm are a usage-invariant violation, loud like every other telemetry anomaly; per-field catch-up over a shifted attribution only ever overcharges, never credits.
    • runFaultInjection (@rulvar/evals) grows a sixteenth scenario, ttl-live-budget-parity: a mid-stream differentiated write against the real engine must debit live and settle to the same $4.50, keep the split on the aggregate, and refuse to settle ok under a $4 ceiling. Reverting the fix reports matched: false in the kit, not only in the unit suite that shipped it.

Patch Changes

1.131.0

Minor Changes

  • 256cae1: The thirteenth plan's probes become permanent gates, and the three moneys get their vocabulary (RV909, RV910; closes the thirteenth plan).

    runFaultInjection grows eight fail-closed scenarios driving the plan's fixed defects end to end on the real engine, zero provider calls and zero keys: nan-statement-refusal (unsummable statement dollars refuse typed at reconciliation intake, never verdict match over NaN totals), token-mismatch-divergence (provider-reported counts that disagree with our recorded usage decide the verdict even when the dollars agree, with tokenComparison: 'informational' still the declared opt-out), audit-missing-field-finding (the documented-rates comparator fails closed in both directions), anthropic-1h-priced (the shipped Anthropic table prices the 1h cache-write share at the documented 2x-input premium under its pinned pricingVersion, on the per-call reconciliation ledger where the TTL split lives), pause-turn-units (continuations absorbed into one dispatch settle at true wire units across the quota window, the invoice row's segment set, and the all-or-nothing statement join, with a partial segment set reading partial-coverage, never no-overlap), pre-admission-count-refusal (a spawn the budget could never admit refuses before the countTokens egress, so the full child prompt never leaves the process), forced-finish-completion (a budget-capped adaptive orchestration settles ok with the honest completion envelope mirrored onto the outcome), and settlement-terminal-honesty (a failed settlement write rejects typed with settled: false on run:end; the healed resume re-settles by replay with zero live calls). Reverting any of the fixes now reports matched: false in the kit, not only in the unit suite that shipped the fix. To reach those surfaces @rulvar/evals gains @rulvar/openai, @rulvar/anthropic, and @rulvar/plan as dependencies.

    @rulvar/core publishes compareRates (with its DocumentedRates input type), the both-directions documented-rates comparator the weekly audit runs: moved from the audit script to a published home so the kit can drive it as a gate, with the script importing the same function from dist inside its entrypoint exactly like the seeds, one source of truth. And the pricing docs now name the three moneys of one run in one place: recorded money (settled history under the pricingVersion pins its own settles wrote, the number CostReport, rulvar inspect, and the invoice's pinned rows show), the docs estimate (repricing at the current table, what preflightEstimate projects and the invoice prints past the pins), and the provider bill (established only by reconcileStatement over saved exports, never by a dashboard headline), plus the rate-update order: audit, then release, then new pinned runs.

Patch Changes

1.130.0

Patch Changes

1.129.0

Patch Changes

1.128.0

Patch Changes

1.127.0

Patch Changes

1.126.0

Patch Changes

1.125.0

Patch Changes

1.124.0

Minor Changes

  • 37fd1f2: The twelfth plan's closing trio (RV809, RV810, RV811). The tool budget extension gains coverEvidenceDeficit: with an evidence contract declared, the extension grants at a tool-turn boundary whenever the remaining call budget cannot cover the declared floor's outstanding deficit, under the same money, progress, and maxExtensions gates, so a limited child at 7 of 11 entries converts headroom into the missing evidence BEFORE the cap instead of dumping through the reserved tail; the journaled grant decision carries trigger: 'evidence-deficit' and the announcement names the exact deficit. Canonical Usage gains the optional cache-write TTL split (cacheWrite5mTokens and cacheWrite1hTokens, invariant: the split sums to cacheWriteTokens); priceUsdOf bills the 1h share at cacheWrite1hUsdPerMTok with everything unclaimed at the plain write rate (byte-identical arithmetic without a split), sanitize repairs broken splits with 1h priority (never an undercharge), and the Anthropic adapter fills the split from the cache_creation breakdown when it agrees with the flat total. @rulvar/evals gains the fault-injection kit: runFaultInjection drives the never-observed-live fail-closed branches (in-flight-exposure refusal, duplicate quota rule, torn and glued JSONL tails, the settle-boundary crash resume, pricing rotation with an uncovered tail, unknown provider id) on the real engine offline, verifies each documented typed observable fail closed, and leaves experiment-grade artifacts.

Patch Changes

1.123.0

Patch Changes

1.122.0

Patch Changes

1.121.0

Patch Changes

1.120.0

Patch Changes

1.119.0

Patch Changes

1.118.0

Patch Changes

1.117.0

Patch Changes

1.116.0

Patch Changes

1.115.0

Patch Changes

1.114.0

Patch Changes

1.113.0

Patch Changes

1.112.0

Patch Changes

1.111.0

Patch Changes

1.110.0

Patch Changes

1.109.0

Patch Changes

1.108.0

Patch Changes

1.107.0

Patch Changes

1.106.0

Patch Changes

1.105.0

Patch Changes

1.104.0

Patch Changes

1.103.0

Patch Changes

1.102.0

Patch Changes

1.101.0

Patch Changes

1.100.0

Patch Changes

1.99.1

Patch Changes

1.99.0

Patch Changes

1.98.0

Patch Changes

1.97.0

Patch Changes

1.96.0

Patch Changes

1.95.0

Patch Changes

1.94.0

Patch Changes

1.93.0

Patch Changes

1.92.0

Patch Changes

1.91.0

Patch Changes

1.90.0

Patch Changes

1.89.0

Patch Changes

1.88.0

Patch Changes

1.87.0

Patch Changes

1.86.0

Patch Changes

1.85.0

Patch Changes

1.84.0

Patch Changes

1.83.0

Minor Changes

  • ca9cf6c: The deep review of the eval package (cycle 81), three fail-closed gaps in the measurement-to-belief pipeline. runSweepMatrix no longer mints a claim from a cell containing a target that settled neither ok nor exhausted: an 'error', 'cancelled', or 'suspended' target is a measurement artifact, and a passRate deflated by a provider failure or a host cancellation could previously commit a false weakness through the eval-committer gate; such runs are now counted in the new nonOkRuns cell field and suppress the claim exactly like exhaustedRuns. runValueCheckpoint marks a cell or criterion contaminated when either A/B arm carried a measurement artifact (an envelope refusal, an incomplete row, a non-ok target): the arms are not comparable, the verdict can never pass, and criterion 1 fails with contaminatedCells reported; previously an envelope drained by the baseline left an empty refused treatment arm (n 0, cost 0) that mechanically beat any baseline under the cheaper-at-equal-quality branch, passing the gate on nothing. runBenchmark ends a series monotonically on a target-run envelope refusal (report.refusal, every completed repeat preserved) instead of throwing away the paid evidence, mirroring the eval suite's refusal contract; judge refusals keep rejecting their own run as 'judge:refused'. Also: renderCheckpointReport counts recommended cells in its criterion 1 denominator instead of all cells (neutral cells are excluded from the majority), contaminated cells render marked, and the missing-rung error names the tier that is actually absent.

Patch Changes

1.82.0

Patch Changes

1.81.2

Patch Changes

1.81.1

Patch Changes

1.81.0

Patch Changes

1.80.0

Patch Changes

1.79.0

Patch Changes

1.78.0

Patch Changes

1.77.0

Patch Changes

1.76.0

Patch Changes

1.75.1

Patch Changes

1.75.0

Patch Changes

1.74.0

Patch Changes

1.73.0

Patch Changes

1.72.0

Patch Changes

1.71.0

Patch Changes

1.70.1

Patch Changes

1.70.0

Patch Changes

1.69.0

Patch Changes

1.68.0

Patch Changes

1.67.0

Patch Changes

1.66.0

Patch Changes

1.65.0

Patch Changes

1.64.0

Patch Changes

1.63.0

Patch Changes

1.62.0

Patch Changes

1.61.0

Patch Changes

1.60.0

Patch Changes

1.59.4

Patch Changes

1.59.3

Patch Changes

1.59.2

Patch Changes

1.59.1

Patch Changes

1.59.0

Patch Changes

1.58.0

Patch Changes

1.57.0

Patch Changes

1.56.0

Patch Changes

1.55.0

Patch Changes

1.54.0

Patch Changes

1.53.0

Patch Changes

1.52.0

Patch Changes

1.51.0

Minor Changes

  • 11bf944: The reproducible benchmark kit (RV-213): runBenchmark(engine, spec, options) turns one workflow into a citable measurement series and enforces the distinction a hand-rolled loop silently skips: a run that finished is not yet a run that counts.

    • Each of the spec's repeats runs sequentially and is verified by the replay-strict gate before it may score: a dry-run resume must replay it with zero misses and reruns, reproduce the journaled settle status and the outputHash digest, and raise zero workflow-provenance determinism warnings across the live and replayed streams. A non-reproducible run (a result mixing in bare Math.random(), an output JCS cannot hash, a diverged replay) lands in its record with machine-readable rejectedReasons and stays out of the series.
    • Percentiles (min/p50/p90/max/mean, nearest-rank, no interpolation) are computed over SCORED runs only for wall time, cost, and any named per-run metric extractor, and are absent entirely when nothing scored: the kit never fabricates a series. Wall time comes from each run's own run:start/run:end event timestamps; the kit reads no clock.
    • Graders reuse the eval contract unchanged (golden, rubric, and LLM-judge graders compose as-is; judge runs stay journaled, budgeted, VCR-recordable, and blind: the judge sees the output and the rubric, never a system label, ordinal, or runId). A failing grader rejects the run; judge budget events normalize to judge:refused/judge:exhausted rejections with the spend counted.
    • The report carries every run's full record (runId for independent rulvar replay re-verification, verification verdict with both digests, dispatch and invocation counts, per-run metrics), honest totals (totalCostUsd includes rejected work), and a BenchmarkFingerprint: Node version, platform, arch, resolved rulvar package versions, the first run's start timestamp, and host-supplied labels (commit, pricing snapshot, corpus hash, cache series). The kit never shells out or guesses identity.
    • SpendEnvelope composes exactly like the eval runners: every target and judge run authorizes its ceiling before starting; a target refusal throws typed, a judge refusal rejects that run.

Patch Changes

1.50.0

Patch Changes

1.49.0

Patch Changes

1.48.0

Patch Changes

1.47.0

Patch Changes

1.46.0

Patch Changes

1.45.0

Patch Changes

1.44.1

Patch Changes

1.44.0

Patch Changes

1.43.0

Patch Changes

1.42.0

Patch Changes

1.41.0

Patch Changes

1.40.0

Patch Changes

1.39.0

Patch Changes

1.38.0

Patch Changes

1.37.0

Patch Changes

1.36.0

Minor Changes

  • 101795b: Validate the CAS rebase attempts of commitEvalMeasured and flipStaleOnCanaryDrift as positive integers before the first store read (v1.35.0 review P2). Unvalidated, NaN or a nonpositive count skipped the loop entirely and surfaced the generic unreachable Error instead of a typed refusal, while a fraction over ran by an attempt.

Patch Changes

1.35.0

Patch Changes

1.34.0

Patch Changes

1.33.0

Patch Changes

1.32.0

Patch Changes

1.31.0

Patch Changes

1.30.0

Patch Changes

1.29.0

Minor Changes

  • 621d566: Validate eval thresholds before they can classify anything (v1.28.0 review P2).

    rubricGrader now throws a typed ConfigError at construction when passThreshold is not a finite fraction in [0, 1]; previously a negative threshold made every zero score verdict pass. runSweepMatrix validates its effective thresholds before engineFor, envelope reservation, or any provider and store activity: both bands must be finite fractions in [0, 1] with weakness strictly below strength, so the bands stay ordered and the uninformative mid band exists. Previously a reversed or out of range configuration turned a failing cell (pass rate 0) into a committed strength claim, which a connected ModelKnowledge store would then feed into routing as false knowledge.

Patch Changes

1.28.0

Patch Changes

1.27.0

Patch Changes

1.26.0

Patch Changes

1.25.0

Patch Changes

1.24.1

Patch Changes

1.24.0

Patch Changes

1.23.0

Minor Changes

  • 1f9c272: PlanRunner spawn telemetry, the missing evals export, and the conformance kit's new meta field (v1.22.0 review P2-5, P2-6, P1-2).

    • @rulvar/plan: PlanRunner journals every admission INSIDE a carrying entry (decomposition rows in escalation decisions, ladder-verdict respawns, reuse and graft links, revision admissions) and emitted no spawn:admitted/spawn:rejected at all; a live PlanRunner run with admitted roots showed an event count of zero. Every embedded admission row now announces through one formatter, identically on the live path and on replay absorb, with replayed: true on recovered rows, entryRef on the journaled carrying entry, and agentType resolved from the landed specs.
    • @rulvar/evals: agentTypeRuleHolds joins the package root next to rungRuleHolds, exactly as the v1.21.0 changelog had already announced; a public-API test now imports the checkpoint quartet from the root. The evals guide gains a full measured-value checkpoint section (ladder/pool/cell/arm vocabulary, both criteria, the vacuous-pass guard, cost discipline, a runnable example).
    • @rulvar/store-conformance: the meta round-trip case now also pins the new optional RunMeta.segments field, which the engine bumps durably at every resume to keep event seq/spanId unique per run.

Patch Changes

1.22.0

Patch Changes

1.21.0

Patch Changes

1.20.0

Patch Changes

  • 9367030: SpendEnvelope rejects amounts at or above 2^49 micro-USD (about $562,949,953.42). The 4-ULP representation-noise window grows with magnitude and reaches half a micro-USD at that boundary, where the nearest integer stops being unique: a ceil debit could snap DOWN and admit an aggregate whose raw requests sum above the ceiling (the v1.19.0 review reproduced a sub-micro overshoot at a $570M cap). Out-of-domain caps and ceilings now throw a typed ConfigError that debits nothing. The class documents the exact input interpretation (a double within the noise window of an integer micro value IS that integer) and the honest raw-double bound (at most half a micro per admitted amount, the finest distinction double precision carries at the top of the domain); boundary and adversarial ULP-neighbor properties pin both.
  • Updated dependencies [9367030]

1.19.0

Patch Changes

  • 8cc9a9c: SpendEnvelope directed rounding survives dollar magnitudes and rejects out-of-domain amounts. The previous conservative-rounding fix snapped to the nearest integer micro-USD within a RELATIVE 1e-6 tolerance, which already reaches half a micro at $0.50 and turns directed rounding into round-to-nearest: two $0.5000004 authorizations (true sum $1.0000008) both fit a $1 cap, a $0.5000006 cap admitted a $0.500001 debit, and a Number.MAX_VALUE cap overflowed to Infinity micro where every authorization is admitted and remainingUsd is NaN. The snap window now scales with the ULP of usd * 1e6, so only genuine IEEE-754 representation noise snaps (0.1 + 0.2 against a 0.3 envelope stays a fit) while real sub-micro fractions keep the conservative floor (caps) or ceil (debits), and after conversion both the cap and every ceiling must be safe integers in micro-USD (at most $9007199254.740991), rejected otherwise with a typed ConfigError that debits nothing. Property tests now cover dollar magnitudes and the domain edges.
  • Updated dependencies [8cc9a9c]
  • Updated dependencies [8cc9a9c]
  • Updated dependencies [8cc9a9c]

1.18.0

Minor Changes

  • 943962d: SpendEnvelope is now provably conservative at the representation boundary. Nearest rounding on both the cap and the debits let any positive ceiling below $0.0000005 round to a zero debit, admitting an unbounded number of authorizations past maxTotalUsd. Accounting stays integer micro-USD, but the cap now converts down (floor), every debit converts up (ceil, minimum one micro-USD), and a maxTotalUsd below $0.000001 is rejected as a ConfigError, so for any admitted sequence the sum of the original ceilings can never exceed maxTotalUsd and no positive ceiling ever debits zero. Amounts that are integer micro-USD up to float noise stay exact (0.1 + 0.2 against 0.3 remains a fit). Migration: an envelope constructed with a sub-micro cap now throws instead of admitting everything, and sub-micro ceilings now consume a full micro-USD each.

  • 943962d: Sweep and suite reports are now monotone: paid evidence survives every budget refusal. Previously runSweepMatrix caught the envelope's SweepBudgetError around a whole cell and replaced it with an empty envelopeExhausted row, erasing already completed targets and their cost; a judge refused by the envelope erased the paid successful target the same way; and a judge run that hit its own per-run ceiling threw EvalJudgeError out of the entire matrix, losing every accumulated cell.

    Now: runEvalSuite returns partial results with plannedN, completedN, and a typed refusal marker instead of throwing when the envelope refuses a target; a judge budget event (per-run ceiling exhaustion or envelope refusal) normalizes into the owning EvalCaseResult as incomplete: { reason: 'judge-exhausted' | 'judge-refused' } with the failing judge run's actual cost counted, while non-budget grader errors still throw; SweepCellReport gains plannedN, judgeIncompleteRuns, incompleteReason, and refusedRunLabel, and any incomplete cell (n < plannedN, exhausted targets, unfinished judges, or an envelope refusal) emits no claim; runCanary records an envelope-refused probe as status: 'refused' and keeps walking, so completed probe evidence survives and allOk stays the drift-flip gate; EvalJudgeError carries costUsd. The kb sweep human renderer prints incomplete cells explicitly (INCOMPLETE: envelope refused ... after N of M case(s), unfinished-judge counts, refused-probe counts) instead of pretending nothing ran.

    Migration: runSweepMatrix and runEvalSuite no longer throw SweepBudgetError for refused targets or judges; read EvalSuiteResult.refusal, EvalCaseResult.incomplete, and the new cell fields instead. Cells now always carry plannedN.

Patch Changes

1.17.0

Minor Changes

  • 7909b6b: Budget surfaces for sweeps and the canary (the v1.16.2 review P1-2).

    • New SpendEnvelope(maxTotalUsd): the debit-only aggregate bound over a whole sweep. Every target, judge, and canary run authorizes its immutable per-run ceiling against it BEFORE starting (integer micro-USD accounting, so exact fits pass); a refusal throws the new SweepBudgetError before any provider work, and authorizations are never returned, not on completion, not on replay, not on CAS retries.
    • runEvalCase, runEvalSuite, and runSweepMatrix accept envelope; an envelope requires the matching per-run ceiling (budgetUsd, and judgeBudgetUsd once a grader judges), because an unbounded run under an aggregate envelope would be unaccountable.
    • Sweep cells now separate measurement from budget artifacts: a cell the envelope refused reports envelopeExhausted, a cell whose target runs hit their own ceiling reports exhaustedRuns, and neither emits a claim, so a budget-starved measurement can never become a false weakness belief about the model.
    • New runCanary(engine, probes, { budgetUsd?, envelope? }) returns { fingerprint, allOk, probes }: each probe run carries the optional immutable ceiling, and allOk is the drift-flip gate, because a non-ok probe fingerprints differently without the model having drifted. canaryFingerprint stays exported (now accepting the same options) for fingerprint-only callers.

Patch Changes

1.16.2

Patch Changes

1.16.1

Patch Changes

1.16.0

Patch Changes

1.15.0

Patch Changes

1.14.0

Patch Changes

1.13.0

Patch Changes

1.12.0

Patch Changes

1.11.0

Patch Changes

1.10.0

Patch Changes

1.9.0

Patch Changes

1.8.0

Patch Changes

1.7.0

Patch Changes

  • Updated dependencies [45285aa]
  • Updated dependencies [2f20d1d]
  • Updated dependencies [22f65a8]
  • Updated dependencies [2ddfa29]
  • Updated dependencies [2abd9c2]
  • Updated dependencies [1c1175d]

1.6.0

Patch Changes

  • da4dbad: Write the product name as Rulvar in prose: package READMEs, npm descriptions, and the documentation site now capitalize the brand. Identifiers keep their exact casing, so package names, the rulvar binary, rulvar.config.mjs, the .rulvar store directory, the rulvar.* OTel attributes, and every URL are unchanged. Documentation and metadata only; no runtime behaviour changes.
  • Updated dependencies [da4dbad]
  • Updated dependencies [487da86]
  • Updated dependencies [df416fc]
  • Updated dependencies [a737810]
  • Updated dependencies [9eb66b4]

1.5.2

Patch Changes

1.5.1

Patch Changes

1.5.0

Patch Changes

1.4.0

Patch Changes

1.3.2

Patch Changes

  • ddef383: Every published package now ships a README, so its npm page states what the package is, how it installs, and where the documentation lives (npm includes README.md in the tarball regardless of the files allowlist, so no manifest changes are involved; @rulvar/compat gains its README on its own next release). Alongside, the repository-level pages are refreshed to the current project state: the root README is rewritten around the never-pay-twice pitch with a runnable quickstart condensation and the full package table, CONTRIBUTING.md lists the complete PR gate set, the examples README drops retired-spec citations for live docs.rulvar.com links and documents the dogfood journal replay, and the pointer README gets the same treatment.
  • Updated dependencies [ddef383]

1.3.1

Patch Changes

  • 7d1552e: Runtime message strings no longer cite the retired internal specification set: error and warning messages, validation issues, and the CLI help text drop the dangling docs/NN, section ... references, pointing at https://docs.rulvar.com pages where a pointer earns its place (the CLI help header, tool naming, toolset registries, bare resume). The umbrella package description sheds the naming-contingency note: the unscoped alias is published and owned. Three strings embedded in frozen recordings stay byte-identical on purpose (the no-progress abort reason and two testing-internal recorder strings), as does the byte-locked golden-fold fixture. Test-file comments lose their citations too; test titles are unchanged.
  • Updated dependencies [7d1552e]

1.3.0

Patch Changes

1.2.0

Patch Changes

  • 5ac56b4: Criterion 2 of the measured-value checkpoint gains the quality branch per the founder's OQ-09 amendment (2026-07-12): the card-informed arm passes by matching the baseline pass rate at no more than 105 percent of its cost, OR by beating it by at least 15 points at no more than 115 percent. The reopened gate measured plus 40 and plus 20 points at 107.9 and 106.6 percent: the baseline fails cheaply, so the flat cost bar tightened exactly when the card won on quality. The vacuous-pass guard stands unchanged; the rule now lives in the exported agentTypeRuleHolds next to rungRuleHolds.
  • 154507b: TSDoc and inline comments no longer cite the retired internal specification set (the pre-docs-site docs/NN, section ... references). The citations either became links to the public documentation at docs.rulvar.com or were dropped where the comment already carried the rule; traceability markers (DEF-n, XF-nn, FR-nnn, OQ-nn, W-nnn) are untouched. Comment-only change: no runtime behavior, no API shapes, and no runtime message strings were modified; the frozen golden-fold fixture is byte-identical.
  • Updated dependencies [3bfaec0]
  • Updated dependencies [890f42c]
  • Updated dependencies [154507b]

1.1.0

Minor Changes

  • 00f1ab5: M12-T01: the measured-value checkpoint harness (docs/05, section "Phases and placement"; the OQ-09 criteria). runValueCheckpoint executes the M12 gate as two A/B experiments under one fixed pool: criterion 1 (rung selection) runs every eval case per (ladder, taskClass) cell at the ladder's default start tier versus the tier recommended by compileVerifiedLayer over the store's claims, judging each recommended cell by the OQ-09 rule (equal-or-better pass rate at 90 percent of the cost, or five points better at cost) with unrecommended cells neutral for the majority but included in the pooled aggregate; criterion 2 (agentType selection) runs the same orchestrate-role cases with and without the knowledge store and requires the card-informed arm to match or beat the baseline pass rate within 105 percent of its cost. The checkpoint passes only when both hold; renderCheckpointReport produces the docs-ready record, and an unmeasured criterion 2 honestly counts as failed. The fixed mixed corpus (extraction, code-edit, judging; a seeded LCG keeps it byte-stable; the seed/eval split prevents leakage into the treatment arm) and the budget-guarded live Anthropic runner ship as repo scripts.

Patch Changes

  • 63b2c01: Two defects the first live M12 checkpoint run surfaced. The Anthropic capability table lacked a Haiku 4.5 entry, so the dated id fell through to the current-generation default and the adapter sent adaptive thinking, which that model rejects with a live 400 (every haiku run died at zero cost): claude-haiku-4-5 (and its dated snapshots by the prefix rule) now resolves to the enabled-budget thinking form with real haiku pricing, meaning the default wire omits thinking entirely. And the checkpoint's criterion 2 could pass vacuously when both arms scored zero at zero cost (zero satisfies "at least equal at no more cost"): the card-informed arm must now win something real (nonzero n and pass rate) before the criterion can hold.
  • 42050b5: The checkpoint's orchestrated arms take their own suite options (orchestratedSuite, defaulting to suite): the third live run showed the shared per-case budget starving the orchestrator cap math (a $0.10 run ceiling cannot host the default finalize reserve, so every orchestrate-role run died at OrchestratorCapConfigError before the first model call, at zero cost).
  • Updated dependencies [d16b04a]

1.0.0

Minor Changes

  • 6649e5f: M11-T01: the eval-committer identity activates eval-measured claims (docs/05, sections "Data model" and "Commit discipline", amended with the dedicated eval-committer GateRecord variant, distinct from the v2-reserved eval-confirmed proposal auto-gate).

    • Commit validation is now GATE-DRIVEN and the coherence square is schema-enforced in both directions: an eval-committer-gated op MUST carry class eval-measured, author kind eval-pipeline, and the metrics block; a human-gated op MUST NOT carry any of the three (a human-authored op with metrics keeps rejecting). Observational data never carries metrics and never auto-promotes.
    • @rulvar/evals ships the pipeline side: evalMeasuredClaim (the docs/05 TTL table applied by polarity: strength 90 days, weakness 30) and commitEvalMeasured with the documented CAS-rebase recipe against any ModelKnowledgeStore.
  • eaacdeb: M11-T02: matrix sweeps (docs/05, section "Grounding and decay"). runSweepMatrix measures a FIXED pool (workflow x model x taskClass; sweep volume is never authorized by proposal volume) through the ordinary engine, sequentially in declaration order for deterministic cassette consumption, and aggregates per (model, taskClass) cell.

    • Threshold-crossing cells emit eval-measured claims (strength at or above 0.9, weakness at or below 0.5 by default; the mid band emits nothing): typed statement templates, metrics {passRate, n, graderId}, EvidenceRef eval reports with the case ids, confidence from n, the docs/05 TTL table from observedAt, and deterministic report-scoped claim ids.
    • With a store given, claims commit through the eval-committer identity (the M11-T01 gate); the sweep e2e records against fake adapters and replays hermetically from the cassette with zero live calls, byte-identical reports.
  • 01d6b2d: M11-T04: modelEpoch capture and the canary fingerprint (docs/05, section "Grounding and decay"; OQ-06 CLOSED with the committed design).

    • Core: modelEpochOf/capsHashOf build the honestly coarse epoch signal (registry version, pricing version, caps hash; silent alias re-pointing stays a documented uncaught case absent probes). The ClaimOp union gains mark_stale (docs/05 amended): section 6 requires status stale at fingerprint drift and the closed op set could not produce it; active flips to stale, already-stale is an idempotent noop, terminals never revive.
    • Evals: canaryFingerprint(engine, probes) runs the FIXED caller-versioned probe set sequentially through the ordinary engine and hashes NFC-normalized, whitespace-collapsed outputs (the probe count prefixes the hash so probe-set edits never collide with drift). flipStaleOnCanaryDrift flips the model's active eval-measured claims whose recorded fingerprint differs, in one CAS-rebased command; claims without a baseline stay untouched. Sweeps stamp the epoch per pool member via modelEpochFor.
  • e679c6e: M9-T02: the @rulvar/evals base (docs/09 section 7; docs/11 "Eval CI"; FR-5xx). First real public surface of @rulvar/evals, built strictly on the public APIs (L6).

    • EvalCase = { workflow, args, graders[] } exactly as documented, with runEvalCase and runEvalSuite runners: the target workflow runs as its own journaled run; latency is derived from run:start and run:end event timestamps (no separate measurement channel); duplicate workflow names disambiguate by ordinal.
    • Three grader families: goldenGrader (deep JSON equality with diff evidence), rubricGrader (named pure criteria, per-criterion verdicts, fraction score against a pass threshold), and judgeGrader (an LLM verdict against a schema). The judge runs THROUGH the engine via GraderContext.judge as an ordinary journaled, budgeted invocation, so judge calls are VCR-recordable and eval CI replays them deterministically with zero live calls. @rulvar/evals ships NO default judge model: weak judge defaults are forbidden by the router quality floors, so model is required. Judge invocations are skipped deterministically when the target run did not settle ok.
    • runEvalMatrix compares configuration cells (profile vs profile, cheap workers vs premium, reviewer on or off): each cell supplies its own engine and the report carries pass-rate, cost, and latency per cell from the existing usage and cost fields. No failure clustering, no vector dependency (EXC registry).
    • Acceptance held in-suite: a suite recorded through the VCR adapters replays byte-deterministically (latency excluded as the one wall-clock measurement) from the cassette under onMiss 'throw', and the cassette carries its hashVersion header (DEF-6).

Patch Changes

  • Updated dependencies [0e0b569]
  • Updated dependencies [b28b7a3]
  • Updated dependencies [b53a89e]
  • Updated dependencies [4454175]
  • Updated dependencies [6599ca8]
  • Updated dependencies [6649e5f]
  • Updated dependencies [fd2f83b]
  • Updated dependencies [01d6b2d]
  • Updated dependencies [9a20dbb]
  • Updated dependencies [0fbe7ea]
  • Updated dependencies [ebe0abc]
  • Updated dependencies [a3079d0]
  • Updated dependencies [807d1f9]
  • Updated dependencies [596a39b]
  • Updated dependencies [464ab6e]

0.9.0

Patch Changes

0.8.0

Patch Changes

  • Updated dependencies [85d55cf]
  • Updated dependencies [b88c9e3]
  • Updated dependencies [f3c4613]
  • Updated dependencies [a41c20f]
  • Updated dependencies [f4e70be]
  • Updated dependencies [75d1646]
  • Updated dependencies [0627413]
  • Updated dependencies [55c0f87]
  • Updated dependencies [fd33871]
  • Updated dependencies [e70e7f4]
  • Updated dependencies [bc9c903]

0.7.0

Patch Changes

  • Updated dependencies [fd1d06c]
  • Updated dependencies [6fcf296]
  • Updated dependencies [dcc97a9]
  • Updated dependencies [434dc83]
  • Updated dependencies [03173c1]
  • Updated dependencies [11c0afc]
  • Updated dependencies [10b45f1]

0.6.0

Patch Changes

  • Updated dependencies [fa05007]
  • Updated dependencies [9234dc8]
  • Updated dependencies [638d9a1]
  • Updated dependencies [644512c]
  • Updated dependencies [8a41656]
  • Updated dependencies [02f7f7a]

0.5.0

Patch Changes

  • Updated dependencies [ac274f4]
  • Updated dependencies [5735d92]
  • Updated dependencies [46ca98e]
  • Updated dependencies [8ae129e]
  • Updated dependencies [d1c4525]
  • Updated dependencies [b840aba]

0.4.0

Patch Changes

  • Updated dependencies [dfe03b5]
  • Updated dependencies [d2089a7]
  • Updated dependencies [3f60234]
  • Updated dependencies [f668890]
  • Updated dependencies [16d7aa6]
  • Updated dependencies [6513ce8]
  • Updated dependencies [7dad493]
  • Updated dependencies [2bbf180]

0.3.0

Patch Changes

  • Updated dependencies [43444f6]
  • Updated dependencies [279881b]
  • Updated dependencies [9fd0966]
  • Updated dependencies [24ebadf]
  • Updated dependencies [a1b35d3]
  • Updated dependencies [18a5821]

0.2.0

Patch Changes

  • Updated dependencies [c24228d]
  • Updated dependencies [c50871e]
  • Updated dependencies [1af8fb9]
  • Updated dependencies [1fe0249]
  • Updated dependencies [5c4fc32]

0.1.0

Minor Changes

  • f4e2be9: M0 repo bootstrap (v0.1.0, docs/10-implementation-plan.md section "M0"): monorepo scaffold on the committed toolchain (pnpm 11 workspaces with catalogs, TypeScript 6.0, tsdown, Vitest 4, ESLint 9 flat config, Turborepo 2, changesets fixed mode, npm trusted publishing), the docs/ canon as single source of truth, the L0 contracts skeleton in @rulvar/core, and the vendored dependencies (StandardSchemaV1/StandardJSONSchemaV1 types, the @cfworker/json-schema lineage validator subset, a first-party monotonic ULID). Placeholder scaffolds only: no public API ships in this release.

Patch Changes

@rulvar/executor

1.252.0

Patch Changes

  • Updated dependencies [3ccb6cf]
  • Updated dependencies [52d807f]
  • Updated dependencies [517ed00]
  • Updated dependencies [a7e589d]
  • Updated dependencies [76e95eb]

1.251.0

Patch Changes

  • Updated dependencies [e7e829c]
  • Updated dependencies [5982be8]
  • Updated dependencies [7c58fb2]
  • Updated dependencies [b3e465a]
  • Updated dependencies [c4e5d6a]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [0ae8b85]
  • Updated dependencies [7932936]
  • Updated dependencies [7932936]
  • Updated dependencies [88da0ed]
  • Updated dependencies [06c0e85]

1.250.0

Patch Changes

  • Updated dependencies [0e240b9]
  • Updated dependencies [6fe585e]
  • Updated dependencies [c5eb19c]
  • Updated dependencies [565c13b]
  • Updated dependencies [c6d197b]
  • Updated dependencies [c9d9729]
  • Updated dependencies [fed9db6]
  • Updated dependencies [df9ed76]
  • Updated dependencies [3020912]
  • Updated dependencies [d8d598d]

1.249.0

Patch Changes

  • Updated dependencies [8862133]
  • Updated dependencies [0d7a717]
  • Updated dependencies [e4428bd]
  • Updated dependencies [d6873c1]
  • Updated dependencies [4092e8d]
  • Updated dependencies [e086590]
  • Updated dependencies [1411938]
  • Updated dependencies [737d1ee]
  • Updated dependencies [634f966]
  • Updated dependencies [052cc26]
  • Updated dependencies [bbae134]

1.248.0

Patch Changes

  • Updated dependencies [8d0cd69]
  • Updated dependencies [81065e4]
  • Updated dependencies [8573f20]
  • Updated dependencies [95f6a5e]

1.247.0

Minor Changes

  • b698726: The first-party surface attests, and the floor loses its holes (RV4204, the sixth comparison experiment). Before this, only mcp() and the AI SDK bridge exposed describeRegulatedPosture(), so unrecognized >= 1 on nearly every real regulated compile and a zero-blind-spot floor was unsatisfiable by construction; and the floor checked toolset attestation only on defaults.profiles, accepted legacy contract-only pins that pass authority drift silently, and never walked the executors at all. Now: anthropic() and openai() attest their egress (official, a custom-base-url whose ORIGIN enters the hashed posture map, or a preconstructed-client named honestly) plus the caps pagination bound; subprocessExecutor() and containerExecutor() attest their ledger, env allowlist, resolved ceilings, and isolation seam; compileRegulatedProfile walks engine.executors and the sandbox runner beside adapters and toolsets, wraps attested executors so run() re-judges the posture at use (the RV4102 seam), refuses a regulated executor without a ToolEffectLedger by field name, refuses legacy contract-only pins (re-record with attestToolset()), and arms the new engine-wide defaults.requireToolsetAttestation, under which a spawn resolving a non-empty toolset with no pin binding it refuses typed at spawn time (the per-call-tools hole the profile pins could not see). The opt-in construction: 'require-recognized' compile floor turns the unrecognized count into a typed refusal naming the blind constructions, satisfiable now that the first-party surface attests.

Patch Changes

  • Updated dependencies [1933ecc]
  • Updated dependencies [db0a5f0]
  • Updated dependencies [b698726]
  • Updated dependencies [48348d2]
  • Updated dependencies [4cfa1cc]
  • Updated dependencies [4b7197a]
  • Updated dependencies [5ebc842]
  • Updated dependencies [16ff6b9]
  • Updated dependencies [0c9941d]

1.246.0

Patch Changes

  • Updated dependencies [d165b0c]
  • Updated dependencies [d59f4a0]
  • Updated dependencies [46907ac]
  • Updated dependencies [9929ad3]
  • Updated dependencies [1790a6a]

1.245.0

Patch Changes

  • Updated dependencies [b4d47a8]
  • Updated dependencies [dee6db4]
  • Updated dependencies [b85c113]
  • Updated dependencies [bc556e7]
  • Updated dependencies [9f11d29]
  • Updated dependencies [19bcea0]
  • Updated dependencies [60b461c]
  • Updated dependencies [61e3a1a]
  • Updated dependencies [a156b81]
  • Updated dependencies [0bd7045]

1.244.0

Patch Changes

  • Updated dependencies [38d839a]
  • Updated dependencies [ce13b0f]
  • Updated dependencies [4fa23e3]
  • Updated dependencies [6841c69]
  • Updated dependencies [f56721d]
  • Updated dependencies [c894a43]
  • Updated dependencies [f6944a3]
  • Updated dependencies [23fd0e0]

1.243.0

Patch Changes

  • Updated dependencies [746d1f4]
  • Updated dependencies [009b29c]
  • Updated dependencies [1674cbe]
  • Updated dependencies [bd096bc]

1.242.0

Patch Changes

  • Updated dependencies [6e3438e]
  • Updated dependencies [ba5cf67]
  • Updated dependencies [c2d1531]

1.241.0

Patch Changes

  • Updated dependencies [dbcdd24]
  • Updated dependencies [7ae7243]
  • Updated dependencies [4f832c4]
  • Updated dependencies [7452d3d]
  • Updated dependencies [a4e22bf]
  • Updated dependencies [82df4af]

1.240.0

Patch Changes

1.239.0

Patch Changes

  • Updated dependencies [74ce99a]
  • Updated dependencies [ccd0665]
  • Updated dependencies [0c5ce21]
  • Updated dependencies [0616934]

1.238.0

Patch Changes

  • Updated dependencies [cf00947]
  • Updated dependencies [c7b9382]
  • Updated dependencies [88aea96]
  • Updated dependencies [6da8d05]
  • Updated dependencies [eae5c4c]

1.237.0

Patch Changes

  • Updated dependencies [9d6a279]
  • Updated dependencies [49a98f6]
  • Updated dependencies [a734ca0]
  • Updated dependencies [deb406f]

1.236.0

Patch Changes

1.235.0

Patch Changes

  • Updated dependencies [ba4e10d]
  • Updated dependencies [172402b]
  • Updated dependencies [2ecd787]
  • Updated dependencies [e20a5e9]
  • Updated dependencies [98c8691]
  • Updated dependencies [c70def0]

1.234.0

Patch Changes

1.233.0

Patch Changes

  • Updated dependencies [48b5200]
  • Updated dependencies [73bc32b]
  • Updated dependencies [e63b743]
  • Updated dependencies [ef45da7]

1.232.0

Patch Changes

  • Updated dependencies [1440410]
  • Updated dependencies [6e467f4]
  • Updated dependencies [0b14293]
  • Updated dependencies [e3bcab2]
  • Updated dependencies [b55a0f7]

1.231.0

Patch Changes

  • Updated dependencies [4eb4b56]
  • Updated dependencies [bc8f09e]
  • Updated dependencies [ff9b8c2]

1.230.0

Patch Changes

1.229.0

Patch Changes

  • Updated dependencies [3370342]
  • Updated dependencies [2fb6656]
  • Updated dependencies [edce170]

1.228.0

Patch Changes

  • Updated dependencies [4034fac]
  • Updated dependencies [a54b085]
  • Updated dependencies [9d0a9be]
  • Updated dependencies [be9ef28]

1.227.0

Patch Changes

  • Updated dependencies [f262e9f]
  • Updated dependencies [f191ff7]
  • Updated dependencies [fbbfbe8]
  • Updated dependencies [263b5e8]
  • Updated dependencies [db4d56d]
  • Updated dependencies [41f93a9]
  • Updated dependencies [98c8ca9]

1.226.0

Patch Changes

1.225.0

Patch Changes

1.224.0

Patch Changes

1.223.0

Patch Changes

1.222.0

Patch Changes

1.221.0

Patch Changes

1.220.0

Patch Changes

1.219.0

Patch Changes

1.218.0

Patch Changes

1.217.0

Patch Changes

1.216.0

Patch Changes

1.215.0

Patch Changes

1.214.0

Patch Changes

1.213.0

Patch Changes

1.212.0

Patch Changes

1.211.0

Patch Changes

1.210.0

Patch Changes

1.209.0

Patch Changes

1.208.0

Patch Changes

1.207.0

Patch Changes

1.206.0

Patch Changes

1.205.0

Patch Changes

1.204.0

Patch Changes

1.203.0

Patch Changes

1.202.0

Patch Changes

1.201.0

Patch Changes

1.200.0

Patch Changes

1.199.0

Patch Changes

1.198.0

Patch Changes

1.197.0

Patch Changes

1.196.0

Patch Changes

1.195.0

Patch Changes

1.194.0

Patch Changes

1.193.0

Patch Changes

1.192.0

Patch Changes

1.191.0

Patch Changes

1.190.0

Patch Changes

1.189.0

Patch Changes

1.188.0

Patch Changes

1.187.0

Patch Changes

1.186.0

Patch Changes

1.185.0

Patch Changes

1.184.0

Patch Changes

1.183.0

Patch Changes

1.182.0

Patch Changes

1.181.0

Patch Changes

1.180.0

Patch Changes

1.179.0

Patch Changes

1.178.0

Patch Changes

1.177.0

Patch Changes

1.176.0

Patch Changes

1.175.0

Patch Changes

1.174.0

Patch Changes

1.173.0

Patch Changes

1.172.0

Patch Changes

1.171.0

Patch Changes

1.170.0

Patch Changes

1.169.0

Patch Changes

1.168.0

Patch Changes

1.167.0

Patch Changes

1.166.0

Patch Changes

1.165.0

Patch Changes

1.164.0

Patch Changes

1.163.0

Patch Changes

  • e8d9ada: Report the import bundle's reference closure, serve verify-only journal reads, and close the documentation gaps the benchmark named (RV1511, RV1512, RV1513). The sixth and final PR of the eighteenth plan.

    The import closure report (RV1511). The intake validated shapes, namespaces, and the runId, but nothing held the ENTRIES' own references against the blobs the bundle carries: a torn bundle imported whole and the missing transcript surfaced only when something later read it. importRun now returns { unresolvedRefs }, every transcript, checkpoint, artifact, and workflow-source ref the entries (and meta) name that no bundle blob resolves; the default stays permissive (retention and checkpoint pruning legitimately drop blobs their entries still name) and the report makes the gap visible, while requireClosure: true refuses typed BEFORE any write. A duplicate blob ref refuses always: last-write-wins over transcript bytes is a torn or edited bundle, never a valid export.

    The verify-only load (RV1512). The A1 salvage model repairs a torn trailing line ON LOAD, which is right for an owner about to append and wrong for an auditor: a verification read that rewrites the artifact it verifies destroys the evidence of the tear. JsonlFileStore({ repairOnLoad: false }) serves the salvageable records without touching the file, and rulvar runs audit --no-load-repair opens the default store that way (contradicting --repair is refused typed).

    The documentation debts (RV1513). The README package count now matches its own table (seventeen names, the unscoped pointer included); @rulvar/executor ships a README and LICENSE like every sibling; the package reference names the eval framework's real dependencies; and the isolated-executor guide gains "What the ledger is NOT", the explicit denial list (not an outbox, not authorization, not exactly-once, not always on) for exactly the facts the seventeenth comparison run's dossier inverted while citing the sources that state them.

  • Updated dependencies [e8d9ada]

1.162.0

Patch Changes

1.161.0

Patch Changes

1.160.0

Patch Changes

1.159.0

Patch Changes

1.158.0

Patch Changes

1.157.0

Patch Changes

1.156.0

Patch Changes

1.155.0

Patch Changes

1.154.0

Patch Changes

1.153.0

Patch Changes

1.152.0

Patch Changes

1.151.0

Patch Changes

1.150.0

Patch Changes

1.149.0

Patch Changes

1.148.0

Patch Changes

1.147.0

Patch Changes

1.146.0

Patch Changes

1.145.0

Patch Changes

1.144.0

Patch Changes

1.143.0

Patch Changes

1.142.0

Patch Changes

1.141.0

Patch Changes

1.140.0

Patch Changes

1.139.0

Patch Changes

1.138.0

Patch Changes

1.137.0

Patch Changes

1.136.0

Patch Changes

1.135.0

Patch Changes

1.134.0

Patch Changes

1.133.0

Patch Changes

1.132.0

Patch Changes

1.131.0

Patch Changes

1.130.0

Patch Changes

1.129.0

Patch Changes

1.128.0

Patch Changes

1.127.0

Patch Changes

1.126.0

Patch Changes

1.125.0

Patch Changes

1.124.0

Patch Changes

1.123.0

Patch Changes

1.122.0

Patch Changes

1.121.0

Patch Changes

1.120.0

Patch Changes

1.119.0

Patch Changes

1.118.0

Patch Changes

1.117.0

Patch Changes

1.116.0

Patch Changes

1.115.0

Patch Changes

1.114.0

Patch Changes

1.113.0

Minor Changes

  • a60807a: The pricing composition's second half names itself, and the effect-ledger quarantine is byte-true (RV706, RV707). InvoicePricingProvenance gains optional currentPricingVersion: on composed exports it is the version of the caller's current table, the one that priced everything past pinnedThroughSeq (on current-table exports, the whole fold), so an invoice folded across a rotation now names both halves of the composition where the pinned segments already declared theirs; rulvar invoice and rulvar inspect fill it from the configured table and extend their text suffix to pins composed with the current table (v-a, v-b; current v-live), byte for byte unchanged when the config declares no version. The executor ledger's torn-tail quarantine row now carries bytesBase64 and sha256 of the exact torn bytes alongside the lossy bytes string kept for old readers (two different byte tails used to collapse into one indistinguishable row), and the repair's parseable decision is made on the bytes, strict UTF-8 before JSON.parse: the lossy decode could make a fragment with invalid bytes inside a string literal parse, and the repair then terminated a line of invalid bytes in place, manufacturing exactly the corruption the fail-closed scan refuses.

Patch Changes

1.112.0

Patch Changes

1.111.0

Patch Changes

1.110.0

Patch Changes

1.109.0

Patch Changes

1.108.0

Patch Changes

1.107.0

Patch Changes

1.106.0

Patch Changes

1.105.0

Patch Changes

1.104.0

Minor Changes

  • 3edecd8: Make the effect ledger's tail repair mutually exclusive across processes and its scan fail closed on every malformed line (RV606, RV607).

    Repair exclusion (RV606): the destructive half of the torn-tail repair (truncate plus quarantine) now runs under a sidecar <path>.repair-lock taken with O_EXCL, and the ledger file is re-read AFTER the lock is held, so a writer never truncates by a boundary computed from a stale read. A waiter polls; a lock whose mtime is further than ten seconds from now (either direction, so a skewed clock cannot pin the file) is presumed abandoned by a crashed holder and stolen, and ownership is re-verified immediately before the truncate. Two writer processes meeting on one torn file previously could erase each other's confirmed intents: the slower instance truncated at its stale boundary, cutting away the faster instance's quarantine and any rows appended after it. A clean file is still returned untouched, byte for byte, without the lock ever existing. The considered alternative, an append-only repair that terminates the fragment in place and quarantines it without truncating, was rejected because it turns the fragment into an unparseable interior line: a repaired file must stay readable by EVERY reader version, and pre-1.104 scans fail closed on exactly that construction. The writer contract is now stated publicly on jsonlEffectLedger and in the guide: prefer one writer per path (an effects.<worker>.jsonl per worker process, merged at reconciliation), and when writers do meet on one local path, the repair lock makes the meeting cost duplicated effort at worst, never a truncated confirmed row.

    Fail-closed scan (RV607): loadEffectLedger now decodes every physical line with TextDecoder('utf-8', { fatal: true }) and validates the shape before anything dereferences it: the phase must be exactly intent, outcome, or torn, and every required field of that phase must carry its type (extra fields still pass through). Invalid UTF-8, non-object JSON (null, 42, "str", arrays), a missing or mistyped required field, and an unknown phase are all CorruptLedgerLines: the default scan throws the typed LedgerCorruptionError, and { tolerateCorrupt: true } returns the same lines as data and never leaks a raw TypeError (a null line used to pierce both modes as one). An unterminated tail that fails to decode or parse remains the tolerated, named tornTail; an unterminated line that PARSES but fails the shape is corruption, because a torn prefix of the writer's own flat record can never parse, so such a line is foreign, not a crash artifact.

    Migration note: scans that previously resolved while silently skipping malformed rows (a replacement-character key entering reconciliation as genuine, an unknown phase erasing an orphan, primitives vanishing) now surface them: the default mode throws LedgerCorruptionError where it previously returned a partial result or leaked a TypeError. Hosts that hit the new refusal on an existing file should triage with { tolerateCorrupt: true }, which reports each offending line's number, byte offset, and sha256. Rows written by jsonlEffectLedger itself are unaffected: every line the writer emits passes the validation it now demands.

Patch Changes

1.103.0

Patch Changes

1.102.0

Patch Changes

1.101.0

Patch Changes

1.100.0

Patch Changes

1.99.1

Patch Changes

1.99.0

Patch Changes

1.98.0

Patch Changes

1.97.0

Patch Changes

1.96.0

Minor Changes

  • 89fd032: Attempt-exact effect-ledger identity, torn-tail repair, and workdir cleanup on a failed audit write (RV501/RV502/RV503, the two ninth-experiment P0s plus their P1 neighbor).

    RV501: every reference-executor dispatch now mints a unique attemptId, written into the intent row and copied verbatim onto the same attempt's outcome row, and loadEffectLedger pairs the two phases exactly: an outcome of ANY class resolves only its own attempt (rows written before the id shipped pair by the legacy (idempotencyKey, startedAt) join). This deliberately changes orphanedIntents in the conservative direction: a sibling retry's outcome no longer clears an older attempt whose effect may already have applied, so files that previously scanned clean can now (correctly) report orphans. Closing the logical idempotency key belongs to the host reconciler, against the effect provider's receipt. A SIGKILL test drives the real crash window against the built package.

    RV502: before its first append, jsonlEffectLedger repairs a torn tail left by a crashed predecessor: a complete record missing only its newline is terminated in place; an unparseable fragment is truncated and quarantined verbatim as a {"phase":"torn"} line (surfaced as tornArtifacts), so an append can never glue onto torn bytes and hide the next valid record. loadEffectLedger now tolerates and NAMES a live unterminated trailing fragment (tornTail) but fails closed on an unparseable interior line with a typed LedgerCorruptionError (line numbers, byte offsets, sha256 hashes); pass { tolerateCorrupt: true } to receive those lines as corrupt data for triage. Previously both records vanished silently after an append over a torn tail, and interior corruption was skipped without a signal.

    RV503: the outcome record write and the workdir removal are now nested, so the ephemeral workdir never survives the dispatch even when the audit write fails, and a rejected ledger.record surfaces as a typed ExecutorError with code ledger (naming the dispatch failure too when both broke) instead of an untyped rejection that leaked the directory.

Patch Changes

1.95.0

Patch Changes

1.94.0

Patch Changes

1.93.0

Patch Changes

1.92.0

Patch Changes

  • 351d1f5: Honest ledger outcomes for dispatches that never ran. A failure between the intent point and the spawn (a credentials mint that throws, a sandbox launcher that throws, cancellation mid-mint) used to ledger outcome: 'ok' with a null exit code even though nothing was dispatched. Both reference executors now default the outcome to error and set ok at exactly one place, the successful protocol return, so every unclassified throw ledgers as the error it is. All previously classified paths (spawn failure, timeout, abort, output cap, non-zero exit, protocol violation, success) keep byte-identical records.
  • Updated dependencies [351d1f5]

1.91.0

Minor Changes

  • f93f5ca: Two-phase intent protocol for external effects (RV404, the eighth-experiment review, variant a). A ToolEffectLedger that implements the new optional intent method opts into the capability: both reference executors durably record the intent (idempotency key, tool, argsHash, runId, spanId, workdir, the attempt's startedAt) strictly BEFORE the external effect is dispatched and the outcome record after it, so a host crash between the effect and the outcome row leaves an orphan intent, the mandatory reconciliation signal, instead of an untracked effect. A failed intent write refuses the dispatch with the new typed ledger error code; a ledger without the method keeps the historical single-record contract byte for byte. Ships the durable JSONL reference (jsonlEffectLedger, loadEffectLedger with orphanedIntents precomputed, a torn trailing line skipped), the two-phase memoryEffectLedger upgrade with intents(), the conformance scenario e13 (a simulated kill between the phases must leave the orphan intent, recorded before the effect), and the documented host reconciliation contract. Full outbox, business authorization, and monetary reconciliation remain host obligations built on the ledger, not inside it.

Patch Changes

1.90.0

Patch Changes

1.89.0

Patch Changes

1.88.0

Patch Changes

1.87.0

Patch Changes

1.86.0

Patch Changes

1.85.0

Patch Changes

1.84.0

Patch Changes

1.83.0

Patch Changes

1.82.0

Patch Changes

1.81.2

Patch Changes

1.81.1

Patch Changes

  • c030982: The side-effect ledger records the outcome a dispatch actually had: a tool whose stdout violates the result protocol (non-JSON output from a clean exit) now ledgers error instead of ok, in both the subprocess and container executors, and the executor conformance kit pins it as check e12. In @rulvar/core, stripFencedBlocks closes fences in CRLF text (a trailing carriage return no longer keeps a fence open and swallows the rest of the document), which fencedCode: 'excluded' validators and headingStructureValidator inherit. Docs drift closed alongside: the package count, tables, and dependency graphs catch up to @rulvar/executor and @rulvar/store-postgres, the durability page reflects the shipped data protection hooks instead of denying them, and the architecture page no longer claims only the in-process executor exists.
  • Updated dependencies [c030982]

1.81.0

Patch Changes

1.80.0

Patch Changes

1.79.0

Patch Changes

1.78.0

Patch Changes

1.77.0

Patch Changes

1.76.0

Patch Changes

1.75.1

Patch Changes

1.75.0

Patch Changes

1.74.0

Patch Changes

1.73.0

Patch Changes

1.72.0

Patch Changes

1.71.0

Patch Changes

1.70.1

Patch Changes

1.70.0

Patch Changes

1.69.0

Patch Changes

1.68.0

Patch Changes

1.67.0

Patch Changes

1.66.0

Patch Changes

1.65.0

Patch Changes

1.64.0

Patch Changes

1.63.0

Patch Changes

1.62.0

Patch Changes

1.61.0

Patch Changes

1.60.0

Patch Changes

1.59.4

Patch Changes

1.59.3

Patch Changes

1.59.2

Patch Changes

1.59.1

Patch Changes

1.59.0

Minor Changes

  • 615dc90: RV-216: the isolated tool executor, the last open item in the improvement plan. In-process tools are ordinary function calls with full host capabilities (an execution convenience, never a sandbox for hostile or model-generated code); this release adds an official out-of-process executor contract so a tool whose input is untrusted cannot reach host capabilities. (1) THE SEAM in @rulvar/core: a ToolExecutorProvider SPI, registered on the engine as createEngine({ executors: { subprocess, container } }). A tool declaring executor: 'subprocess' or 'container' (previously a hard "only inprocess in v1" rejection) dispatches through the matching provider instead of running its execute closure; an unregistered tag is a typed ConfigError at spawn time, before any provider or model call. The dispatch mints the tool span exactly like an inprocess call and derives a stable idempotency key (a pure function of runId, tool name, and canonical args) so a side-effecting tool can fold an at-least-once retry into effectively-once; the tag never enters toolsetHash, so opting a tool into isolation does not change run identity, and inprocess dispatch stays byte-identical. (2) THE REFERENCE ADAPTERS in the new @rulvar/executor package: subprocessExecutor runs the tool in a child process with a REPLACED environment (host credentials scrubbed; the usual exfiltration path removed), a fresh ephemeral working directory per call, per-call short-lived credentials, a hard timeout that escalates SIGTERM to SIGKILL, and a bounded output capture, plus a sandbox launcher hook where bwrap/firejail/sandbox-exec plug in for filesystem and network isolation; containerExecutor runs it in a one-shot container with the network dropped (--network none), the root filesystem read-only, memory/CPU/pid caps, and all Linux capabilities dropped, which is where the strong isolation the subprocess adapter cannot promise on its own actually holds (a microVM adapter implements the same seam). subprocessTool defines a tool that dispatches through them; a ToolEffectLedger records every dispatch (idempotency key, tool, argsHash, workdir, outcome) so a host can bind an approval to the effect it authorized. (3) THE CONFORMANCE KIT: executorConformance is the executable shared-contract battery any command-based executor must pass, foremost the gate the epic exists for, a hostile tool cannot read the host's ambient credentials; the subprocess reference passes all of it, and the container reference additionally proves the network and filesystem isolation against a real runtime. New guide page: https://docs.rulvar.com/guide/isolated-executor.

Patch Changes

@rulvar/openai

1.252.0

Patch Changes

  • Updated dependencies [3ccb6cf]
  • Updated dependencies [52d807f]
  • Updated dependencies [517ed00]
  • Updated dependencies [a7e589d]
  • Updated dependencies [76e95eb]

1.251.0

Patch Changes

  • Updated dependencies [e7e829c]
  • Updated dependencies [5982be8]
  • Updated dependencies [7c58fb2]
  • Updated dependencies [b3e465a]
  • Updated dependencies [c4e5d6a]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [0ae8b85]
  • Updated dependencies [7932936]
  • Updated dependencies [7932936]
  • Updated dependencies [88da0ed]
  • Updated dependencies [06c0e85]

1.250.0

Patch Changes

  • Updated dependencies [0e240b9]
  • Updated dependencies [6fe585e]
  • Updated dependencies [c5eb19c]
  • Updated dependencies [565c13b]
  • Updated dependencies [c6d197b]
  • Updated dependencies [c9d9729]
  • Updated dependencies [fed9db6]
  • Updated dependencies [df9ed76]
  • Updated dependencies [3020912]
  • Updated dependencies [d8d598d]

1.249.0

Minor Changes

  • 67a8d72: The Sol rates follow the documented page. The fresh contract classification dispatched for the plan-44 release gate caught a real drift: the provider's model page for gpt-5.6-sol now documents input 4, output 20, cache read 0.4, cache write 5 USD per MTok against the seeded 5 / 30 / 0.5 / 6.25. Per the rates audit's own doctrine (a confirmed rate change ships as its own changeset, never an automatic rewrite), the seed row and the derived OPENAI_PRICING table move to the page's numbers under the distinct pricingVersion: 'openai-2026-08-23', with ratesVerifiedAt: '2026-08-23' on Sol and its gpt-5.6 alias (Terra and Luna keep their 2026-07-31 verification). Runs recorded before this release overstated Sol spend relative to the cut, never under; the distinct version string surfaces the revision on resume instead of silently reinterpreting past spend. Sol's previous rates were billing-confirmed by the 2026-07-30 statement reconciliation; the new rates await theirs over a future export.

Patch Changes

  • Updated dependencies [8862133]
  • Updated dependencies [0d7a717]
  • Updated dependencies [e4428bd]
  • Updated dependencies [d6873c1]
  • Updated dependencies [4092e8d]
  • Updated dependencies [e086590]
  • Updated dependencies [1411938]
  • Updated dependencies [737d1ee]
  • Updated dependencies [634f966]
  • Updated dependencies [052cc26]
  • Updated dependencies [bbae134]

1.248.0

Patch Changes

  • Updated dependencies [8d0cd69]
  • Updated dependencies [81065e4]
  • Updated dependencies [8573f20]
  • Updated dependencies [95f6a5e]

1.247.0

Minor Changes

  • b698726: The first-party surface attests, and the floor loses its holes (RV4204, the sixth comparison experiment). Before this, only mcp() and the AI SDK bridge exposed describeRegulatedPosture(), so unrecognized >= 1 on nearly every real regulated compile and a zero-blind-spot floor was unsatisfiable by construction; and the floor checked toolset attestation only on defaults.profiles, accepted legacy contract-only pins that pass authority drift silently, and never walked the executors at all. Now: anthropic() and openai() attest their egress (official, a custom-base-url whose ORIGIN enters the hashed posture map, or a preconstructed-client named honestly) plus the caps pagination bound; subprocessExecutor() and containerExecutor() attest their ledger, env allowlist, resolved ceilings, and isolation seam; compileRegulatedProfile walks engine.executors and the sandbox runner beside adapters and toolsets, wraps attested executors so run() re-judges the posture at use (the RV4102 seam), refuses a regulated executor without a ToolEffectLedger by field name, refuses legacy contract-only pins (re-record with attestToolset()), and arms the new engine-wide defaults.requireToolsetAttestation, under which a spawn resolving a non-empty toolset with no pin binding it refuses typed at spawn time (the per-call-tools hole the profile pins could not see). The opt-in construction: 'require-recognized' compile floor turns the unrecognized count into a typed refusal naming the blind constructions, satisfiable now that the first-party surface attests.

Patch Changes

  • Updated dependencies [1933ecc]
  • Updated dependencies [db0a5f0]
  • Updated dependencies [b698726]
  • Updated dependencies [48348d2]
  • Updated dependencies [4cfa1cc]
  • Updated dependencies [4b7197a]
  • Updated dependencies [5ebc842]
  • Updated dependencies [16ff6b9]
  • Updated dependencies [0c9941d]

1.246.0

Patch Changes

  • Updated dependencies [d165b0c]
  • Updated dependencies [d59f4a0]
  • Updated dependencies [46907ac]
  • Updated dependencies [9929ad3]
  • Updated dependencies [1790a6a]

1.245.0

Patch Changes

  • Updated dependencies [b4d47a8]
  • Updated dependencies [dee6db4]
  • Updated dependencies [b85c113]
  • Updated dependencies [bc556e7]
  • Updated dependencies [9f11d29]
  • Updated dependencies [19bcea0]
  • Updated dependencies [60b461c]
  • Updated dependencies [61e3a1a]
  • Updated dependencies [a156b81]
  • Updated dependencies [0bd7045]

1.244.0

Patch Changes

  • Updated dependencies [38d839a]
  • Updated dependencies [ce13b0f]
  • Updated dependencies [4fa23e3]
  • Updated dependencies [6841c69]
  • Updated dependencies [f56721d]
  • Updated dependencies [c894a43]
  • Updated dependencies [f6944a3]
  • Updated dependencies [23fd0e0]

1.243.0

Patch Changes

  • Updated dependencies [746d1f4]
  • Updated dependencies [009b29c]
  • Updated dependencies [1674cbe]
  • Updated dependencies [bd096bc]

1.242.0

Patch Changes

  • Updated dependencies [6e3438e]
  • Updated dependencies [ba5cf67]
  • Updated dependencies [c2d1531]

1.241.0

Patch Changes

  • Updated dependencies [dbcdd24]
  • Updated dependencies [7ae7243]
  • Updated dependencies [4f832c4]
  • Updated dependencies [7452d3d]
  • Updated dependencies [a4e22bf]
  • Updated dependencies [82df4af]

1.240.0

Patch Changes

1.239.0

Patch Changes

  • Updated dependencies [74ce99a]
  • Updated dependencies [ccd0665]
  • Updated dependencies [0c5ce21]
  • Updated dependencies [0616934]

1.238.0

Patch Changes

  • Updated dependencies [cf00947]
  • Updated dependencies [c7b9382]
  • Updated dependencies [88aea96]
  • Updated dependencies [6da8d05]
  • Updated dependencies [eae5c4c]

1.237.0

Patch Changes

  • Updated dependencies [9d6a279]
  • Updated dependencies [49a98f6]
  • Updated dependencies [a734ca0]
  • Updated dependencies [deb406f]

1.236.0

Patch Changes

1.235.0

Patch Changes

  • Updated dependencies [ba4e10d]
  • Updated dependencies [172402b]
  • Updated dependencies [2ecd787]
  • Updated dependencies [e20a5e9]
  • Updated dependencies [98c8691]
  • Updated dependencies [c70def0]

1.234.0

Patch Changes

1.233.0

Patch Changes

  • Updated dependencies [48b5200]
  • Updated dependencies [73bc32b]
  • Updated dependencies [e63b743]
  • Updated dependencies [ef45da7]

1.232.0

Patch Changes

  • Updated dependencies [1440410]
  • Updated dependencies [6e467f4]
  • Updated dependencies [0b14293]
  • Updated dependencies [e3bcab2]
  • Updated dependencies [b55a0f7]

1.231.0

Patch Changes

  • Updated dependencies [4eb4b56]
  • Updated dependencies [bc8f09e]
  • Updated dependencies [ff9b8c2]

1.230.0

Patch Changes

1.229.0

Patch Changes

  • Updated dependencies [3370342]
  • Updated dependencies [2fb6656]
  • Updated dependencies [edce170]

1.228.0

Patch Changes

  • Updated dependencies [4034fac]
  • Updated dependencies [a54b085]
  • Updated dependencies [9d0a9be]
  • Updated dependencies [be9ef28]

1.227.0

Patch Changes

  • Updated dependencies [f262e9f]
  • Updated dependencies [f191ff7]
  • Updated dependencies [fbbfbe8]
  • Updated dependencies [263b5e8]
  • Updated dependencies [db4d56d]
  • Updated dependencies [41f93a9]
  • Updated dependencies [98c8ca9]

1.226.0

Patch Changes

1.225.0

Patch Changes

1.224.0

Patch Changes

1.223.0

Patch Changes

1.222.0

Patch Changes

1.221.0

Patch Changes

1.220.0

Patch Changes

1.219.0

Patch Changes

1.218.0

Patch Changes

1.217.0

Patch Changes

1.216.0

Patch Changes

1.215.0

Patch Changes

1.214.0

Patch Changes

1.213.0

Patch Changes

1.212.0

Patch Changes

1.211.0

Patch Changes

1.210.0

Patch Changes

1.209.0

Patch Changes

1.208.0

Minor Changes

  • e7d426f: First-class prompt-cache policy (RV2006). ChatRequest.cacheHint existed and the Anthropic adapter compiled it into cache_control, but nothing in the core ever populated it: the third parity rerun's workers re-paid the full input rate on every turn of their ~550k-token contexts (cacheReadTokens 0 across the run), and the $6 envelope sized on OpenAI's implicit server cache was incomparable on Anthropic. The agent loop now compiles the hint on every tool-cycle turn: breakpoints after tools, after system, and after the deepest message, sliding with the history. Default ON exactly where the adapter declares the new ModelCaps.promptCaching: 'explicit' (the Anthropic adapter does); OpenAI declares 'implicit' and undeclared adapters get byte-identical requests. Configure with defaults.cache, AgentProfile.cache, or per-call opts.cache (CachePolicy { mode?: 'auto' | 'off'; ttl?: '5m' | '1h' }), call over profile over engine. Billing note: on cache-capable Anthropic models this changes the wire requests of every loop turn to carry cache breakpoints, typically cutting long-cycle input cost several-fold (cached reads bill at a tenth of the input rate); CostReport cache accounting is unchanged, the hint never enters identity or journals, and @rulvar/testing's requestHash strips it so existing cassettes replay byte for byte.

Patch Changes

1.207.0

Patch Changes

1.206.0

Patch Changes

1.205.0

Patch Changes

1.204.0

Patch Changes

1.203.0

Patch Changes

1.202.0

Patch Changes

1.201.0

Patch Changes

1.200.0

Patch Changes

1.199.0

Patch Changes

1.198.0

Patch Changes

1.197.0

Patch Changes

1.196.0

Patch Changes

1.195.0

Patch Changes

1.194.0

Patch Changes

1.193.0

Patch Changes

1.192.0

Patch Changes

1.191.0

Patch Changes

1.190.0

Patch Changes

1.189.0

Patch Changes

1.188.0

Patch Changes

1.187.0

Patch Changes

1.186.0

Patch Changes

1.185.0

Patch Changes

1.184.0

Patch Changes

1.183.0

Patch Changes

1.182.0

Patch Changes

1.181.0

Patch Changes

1.180.0

Minor Changes

  • b124d26: Statement reconciliation is core, with a fail-closed intake for raw exports and a fixed adapter contract matrix (RV1703). reconcileStatement was provider-neutral from birth, typing only against the invoice and the pricing SPI, but it lived in @rulvar/openai and forced Anthropic-only consumers into an OpenAI dependency for a join that never touched OpenAI code; the eighteenth comparison benchmark graded provider readiness "conditionally ready" partly on exactly this asymmetry. The module now lives in @rulvar/core and the historical @rulvar/openai import paths keep serving the identical functions as re-exports, so no consumer rebuild or import rewrite is forced. New beside it: statementFromRows({ kind, rows, map }) normalizes a raw keyed export (a parsed CSV, a JSON download) into a ProviderStatement under one explicit StatementColumnMap, deliberately shipping no per-provider schema knowledge; every mapped cell validates fail-closed with the row index and column name (non-numeric dollars, fractional or negative token counts, empty response ids, unknown component names all refuse typed), absent cells omit their field, and a requests row left with no dollars, no component split, and no usage refuses, because a row without evidence cannot reconcile anything. The providers guide now fixes the per-adapter billing contract in one matrix: what each adapter surface contributes to the join (continuation absorption and the any-id-of-the-set rule for pause_turn dispatches, the one-response-id-per-wire contract of the Responses API, the coverage posture for compatible endpoints and the AI SDK bridge), so reconciliation readiness is a documented contract per adapter instead of an inference.

Patch Changes

1.179.0

Patch Changes

1.178.0

Patch Changes

1.177.0

Patch Changes

1.176.0

Patch Changes

1.175.0

Patch Changes

1.174.0

Patch Changes

1.173.0

Patch Changes

1.172.0

Patch Changes

1.171.0

Patch Changes

1.170.0

Patch Changes

1.169.0

Patch Changes

1.168.0

Patch Changes

1.167.0

Patch Changes

1.166.0

Patch Changes

1.165.0

Patch Changes

1.164.0

Patch Changes

1.163.0

Patch Changes

1.162.0

Patch Changes

1.161.0

Patch Changes

1.160.0

Patch Changes

1.159.0

Patch Changes

1.158.0

Patch Changes

1.157.0

Patch Changes

1.156.0

Patch Changes

1.155.0

Patch Changes

1.154.0

Patch Changes

1.153.0

Patch Changes

1.152.0

Patch Changes

1.151.0

Patch Changes

1.150.0

Patch Changes

1.149.0

Patch Changes

1.148.0

Patch Changes

1.147.0

Patch Changes

1.146.0

Patch Changes

1.145.0

Minor Changes

  • faf7d95: An affirmatively declared empty claim is not settlement evidence (RV1201). A per-request statement row whose usage or componentsUsd was an object with no figures used to read verdict match with complete coverage and settleable: true on the object's mere presence, exactly the false settlement-grade evidence the sixteenth experiment's judge reproduced as R1: {usage:{}} and {componentsUsd:{}} both settled. The intake now refuses such a row with a typed ConfigError naming the row and the empty field, at the same fail-closed gate that already refuses non-finite dollars, malformed token counts, and self-contradicting rows. The documented partial-declaration model is unchanged: a row declaring only its responseId still joins the coverage set, because presence is coverage, not a figure claim; and a single declared figure (one token count, one component line) remains evidence exactly as before.

Patch Changes

1.144.0

Patch Changes

1.143.0

Patch Changes

1.142.0

Patch Changes

1.141.0

Patch Changes

1.140.0

Patch Changes

1.139.0

Patch Changes

1.138.0

Patch Changes

1.137.0

Patch Changes

1.136.0

Patch Changes

1.135.0

Patch Changes

1.134.0

Minor Changes

  • cb50ea0: An internally contradictory statement refuses typed at intake, totals decide beside components, and the reconciliation states the settlement-grade predicate first class (RV1005 + RV1006, PR III of the fourteenth plan)

    The fourteenth comparison experiment fed reconcileStatement an export row carrying usd: 100 beside a component split summing to 1 and read verdict match: each claim sat inside its own tolerance and nothing compared them to each other, because the presence of components suppressed the totals comparison entirely. The same review showed that a match verdict is a weaker claim than settlement needs: an export can cover every KNOWN row to the cent while a usage-unknown attempt still holds unattributed money.

    • Intake internal consistency (RV1005): a request row carrying both usd and a componentsUsd split must have them agree within totalToleranceUsd, else it refuses with a typed ConfigError naming the row; an export whose own total contradicts its own components is not evidence.
    • Totals decide beside components (RV1005): a split's presence no longer suppresses the totals comparison. It decides exactly when both sides' dollar claims cover the same set (every matched export row carries usd in requests mode; nothing statement-only and every component line claimed in categories mode; no covered model unpriced), so a total drifting beyond totalToleranceUsd reads divergence even while every component line sits inside its own tolerance, and a scope mismatch stays the coverage machinery's business instead of manufactured divergence.
    • StatementReconciliation.settleable (RV1006): the settlement-grade composite first class, true exactly when the verdict is match AND coverage is complete AND no row settled usageUnknown AND no model went unpriced. A safe consumer no longer assembles that predicate by hand.
    • runFaultInjection (@rulvar/evals) grows the eighteenth scenario, statement-settleable-guard: a REAL run whose first attempt dies before any usage report seeds a genuine usage-unknown ledger row, the clean export over it reads match with complete coverage yet settleable: false, the clean twin reads settleable: true, and the contradictory row refuses typed at intake. Reverting any of the fixes reports matched: false in the kit.

Patch Changes

1.133.0

Patch Changes

1.132.0

Patch Changes

1.131.0

Patch Changes

1.130.0

Patch Changes

1.129.0

Patch Changes

1.128.0

Minor Changes

  • 27c4e38: pause_turn continuations become accounted wire units (RV905, the thirteenth experiment's fifth release risk). The Anthropic adapter absorbs server-side turn pauses by re-sending, making up to six wire requests inside ONE core dispatch; until now the request quota window, the provider call record, and the invoice row all saw one, and a per-request provider statement matched one segment while the rest read statement-only.

    The adapter's finish metadata now names the whole segment set (providerMetadata.anthropic.wireRequests = { count, responseIds }); the provider call record and the invoice row carry wireResponseIds; and the quota reconciliation settles the reservation against the TRUE wire request count. The QuotaLimiter.reconcile SPI gains an optional actual.requests argument, honored by all three reference limiters through one shared arithmetic (quotaActualRequestsDelta), so a window that admitted one request per reservation now reflects what the provider's own RPM meter saw; a settlement only ever adds, never denies retroactively, and implementations written against the two-argument form remain valid. reconcileStatement joins a multi-wire invoice row by ANY id of its segment set, all-or-nothing: a partially delivered segment set reads partial-coverage with its delivered segments never counted as statement-only (and never no-overlap when segments touched our data), and provider-reported token counts compare as the SUM over the segments against the dispatch's recorded usage. Single-wire dispatches carry none of the new fields and stay byte-identical, journals and events included.

Patch Changes

1.127.0

Patch Changes

1.126.0

Minor Changes

  • e5e9526: Fail-closed statement reconciliation (RV903, the thirteenth experiment's two false-match probes): reconcileStatement now refuses at intake, with a typed ConfigError naming the row and field, any statement number that cannot be evidence: non-finite or negative dollars (usd and every componentsUsd entry, requests and categories alike), non-integer or negative provider-reported token counts, and non-finite or negative tolerances. Before this, a request row with usd: NaN flowed through the totals, Math.abs(NaN) > tolerance evaluated false, and a corrupted export read verdict: 'match' with NaN statementUsd and deltaUsd; negative amounts are refused too, because credits and adjustments are not per-request billing evidence and a negative row could mask a rate divergence of its own size.

    Provider-reported token counts now decide the verdict by default: our recorded counts are the provider's own wire-reported numbers, so an export that disagrees with them describes a different request than the wire served, and any token mismatch reads as divergence even when the dollars agree (the second probe: verdict: 'match' beside tokenMismatches: 1). The new tokenComparison: 'informational' option restores the pre-v1.126 dollar-only verdict for exports whose token semantics legitimately differ from the wire's; the mismatch count and tokenMismatchSample report either way. Reports for clean exports are byte-identical to v1.125.0.

Patch Changes

1.125.0

Minor Changes

  • 109e9fa: Pricing-table truth: the Anthropic 1h cache-write premium is seeded, the rates audit fails closed on documented rates the seed never declared, and the OpenAI Terra/Luna price cut ships as a versioned revision (RV901, RV902, RV911; the thirteenth experiment's underpricing probes).

    @rulvar/anthropic seeds now carry all five published pricing columns: cacheWrite1hUsdPerMTok lands on every priced row at the documented 2x base input (Fable 5 $20, Opus 4.8/4.7/4.6 $10, Sonnet 5 $4 under the introductory price, Sonnet 4.6 $6, Haiku 4.5 $2), under the new pricingVersion anthropic-2026-07-31. v1.124.0 taught the wire to fill the canonical 5m/1h split and priceUsdOf to bill the 1h share at the premium, but the seed never declared the rate, so a million Sonnet 5 1h write tokens priced at the 5m $2.50 instead of the documented $4.00: an underpricing a budget ceiling then failed to bound. A usage with no split still folds the whole write count at the 5m rate, byte for byte as before; the stale caps comment claiming the canonical Usage cannot distinguish 1h writes is retired.

    scripts/rates-audit.mjs (the weekly documented-rates drift audit) now compares seed and page in BOTH directions: a billable page rate the seed never declared is a finding, not a silent skip. The old one-directional rule rested on the 1h premium being unbillable; that rationale died with the Usage split, and the audit printing match for Sonnet 5 while the page showed a 1h column the seed lacked is exactly how the underpricing hid. The pinning test is flipped to the fail-closed behavior.

    @rulvar/openai picks up the provider's 2026-07-30 price cut, docs-verified per model page on 2026-07-31 after the live audit caught the drift: Terra to $2 input / $12 output / $0.20 cached input / $2.50 cache write (0.8x across the board) and Luna to $0.20 / $1.20 / $0.02 / $0.25 (0.2x), both keeping the family's long-context tier, under the new pricingVersion openai-2026-07-31. Sol is unchanged and additionally remains billing-confirmed by the 2026-07-30 statement reconciliation; the new Terra and Luna rates are docs-verified only until the next reconciliation over a saved export. Runs recorded under openai-2026-07-18-r2 overstated Terra/Luna spend relative to the cut, never under, and a resumed run surfaces the rotation as explicit pricing drift instead of silently reinterpreting recorded spend. Every re-verified row now stamps ratesVerifiedAt: '2026-07-31'.

Patch Changes

1.124.0

Patch Changes

1.123.0

Patch Changes

1.122.0

Patch Changes

1.121.0

Minor Changes

  • 3d67d41: Rate provenance made checkable (RV807, RV813, RV814). The pricing row grows ratesVerifiedAt (SPI), the ISO date it was last verified against the provider's documented rates or, stronger, its billing categories: the shipped seeds stamp it (the GPT-5.6 family reads 2026-07-30, the day the statement reconciliation confirmed those rates against the provider's own per-component billing categories to the cent; the pre-5.6 OpenAI rows keep their 2026-07-18 docs verification; every Anthropic row was re-verified against the documented table on 2026-07-30). The date is surfaced wherever a dollar is consumed: preflightEstimate copies it onto each spawn report and rulvar preflight renders ratesVerified=<date> with its age on the spawn line; the settle pin journals it with the rest of the applied row so it survives any later table rewrite; and rulvar invoice prints a rates verified: line naming each priced model's date and age, pinned rows first, current table past them; the twelfth run's founder read the invoice doubting the rates and nothing said the seed was 12 days stale. The doctrine ships with the mechanism: seeds bound ceilings conservatively, billing truth is established only by reconcileStatement over saved exports, and a confirmed divergence corrects the seed in its own release with a changeset, never a silent rewrite. Enforcement rides two new gates: a weekly documented-rates audit (scripts/rates-audit.mjs in the live contract workflow) re-fetches exactly the pages the seed comments cite, compares every rate, write premium, and long-context tier, and opens an issue on drift or on a page that stops extracting, and a README release-table gate (scripts/readme-release-shas.mjs, in CI) requires every cited squash SHA to be an ancestor of HEAD, catching the v1.109.0 row that pointed at an object no branch contained for eleven releases (now corrected to the real squash 58afdb5).

Patch Changes

1.120.0

Patch Changes

1.119.0

Patch Changes

1.118.0

Minor Changes

  • f8341a3: Provider statement reconciliation as a machine (RV812, the twelfth experiment's billing lesson). The run's billing question (a dashboard headline of 4.45 then 4.77 USD against the settled 7.304885) was closed by hand with screenshots; nothing in the system could close it. Now @rulvar/openai exports reconcileStatement(invoice, statement, { pricingOf }): it joins the machine-readable invoice against a NORMALIZED provider export, per-request rows by response id or per-model per-component category totals (the Spend categories shape), and refuses a headline aggregate typed, because an eventually consistent dashboard total is not evidence. The report carries response-id coverage (a partially delivered export reads as partial-coverage, never as false divergence: component deltas fold over the covered subset only), per-component deltas per serving model, and the implied actual rate of every component beside our effective rate over the same token base, so a real divergence NAMES the rate-card line that moved with the rate the provider actually applied. Unpriced models and usage-unknown rows are declared apart, never folded or silent; verdicts are match, divergence, partial-coverage, no-overlap. Backing it, @rulvar/core exports priceComponentsOf(pricing, usage): the four billing components (uncached input, output, cached input, cache writes) with token bases and dollars, decomposed with exactly the settled fold's arithmetic; priceUsdOf is now defined as the sum of those four terms in the historical order, byte for byte the same number, so the reconciliation and the settled fold can never disagree about what a usage costs. Validated against the real twelfth-run artifacts offline: the founder's eight dashboard categories reconcile to match with every delta under 0.0005 (3-decimal rounding), response-id coverage reads 120 of 120, a 100-row truncation reads partial coverage with zero divergence, and a synthetically distorted write rate names gpt-5.6-terra cache-write with implied 2.5 USD/MTok against effective 3.125.

Patch Changes

1.117.0

Patch Changes

1.116.0

Patch Changes

1.115.0

Patch Changes

1.114.0

Patch Changes

1.113.0

Patch Changes

1.112.0

Patch Changes

1.111.0

Patch Changes

1.110.0

Patch Changes

1.109.0

Patch Changes

1.108.0

Patch Changes

1.107.0

Patch Changes

1.106.0

Patch Changes

1.105.0

Patch Changes

1.104.0

Patch Changes

1.103.0

Patch Changes

1.102.0

Patch Changes

1.101.0

Patch Changes

1.100.0

Patch Changes

1.99.1

Patch Changes

  • ef08d73: Guarantee matrix and exactly-once claim hygiene (RV508); no runtime behavior changes. The isolated-executor guide now carries the guarantee matrix stating flatly who provides what: the library's layers give at-least-once execution with attempt binding and intent-before-effect, exactly-once effect execution is promised by NO library layer, and what IS exactly-once is pay and replay (the never-pay-twice invariant). The two claims the ninth comparison experiment's judge caught are rewritten to the precise statements ("each ran once" became attempt counting under a stable idempotency key; the approvals guide now says continuation is a run-level guarantee, not an effect-level one, with the at-least-once window named); ctx.step docs state the same window for effectful steps; a ResolutionBy note says the field records a channel, never a verified principal (identity, signatures, and separation of duties are host IAM). The worker header now points at the shipped SqliteQuotaLimiter and PostgresQuotaLimiter instead of denying that cross-process limiters exist. A new docs-lint sentinel forbids "exactly once" claims in the hand-written docs and in package source comments outside a vetted (file, heading anchor) allowlist (the durability pay doctrine and the guarantee matrix), and every remaining occurrence in doc prose and source comments was rewritten to the precise wording; string literals are deliberately out of scope (tool descriptions enter the toolset hash).
  • Updated dependencies [ef08d73]

1.99.0

Patch Changes

1.98.0

Patch Changes

1.97.0

Patch Changes

1.96.0

Patch Changes

1.95.0

Patch Changes

1.94.0

Patch Changes

1.93.0

Patch Changes

1.92.0

Patch Changes

1.91.0

Patch Changes

1.90.0

Patch Changes

1.89.0

Minor Changes

  • f18b671: Provider-id provenance parity across every adapter path (RV401, the eighth comparison experiment). The AI SDK bridge now ships the flat responseId the core reconciliation record reads, beside the nested response object it always emitted, and an error finish carries the accumulated response metadata and warnings on the error event instead of dropping them (retained parts stay deliberately absent there: a failed turn is discarded, never re-injected). The core agent loop captures provider metadata from error events and falls back to the AI SDK's nested response.id shape when a third-party adapter ships only that, with the flat first-class form winning when both are present. The OpenAI adapter attaches the failed response's id to its response.failed error event, so a billed failure reconciles against the provider statement exactly like an ok row. End-to-end tests pin a bridged engine run whose per-call reconciliation records carry ids on the success, retry, and billed-failure paths alike.

Patch Changes

1.88.0

Patch Changes

1.87.0

Patch Changes

1.86.0

Patch Changes

1.85.0

Patch Changes

1.84.0

Patch Changes

1.83.0

Patch Changes

1.82.0

Patch Changes

1.81.2

Patch Changes

1.81.1

Patch Changes

1.81.0

Patch Changes

1.80.0

Patch Changes

1.79.0

Patch Changes

1.78.0

Patch Changes

1.77.0

Patch Changes

1.76.0

Patch Changes

1.75.1

Patch Changes

1.75.0

Minor Changes

  • c486de8: The provider output floor and the finish arguments second chance (the v1.74 comparison review, P0.1 + P1.5). ModelCaps.minOutputTokensPerTurn declares the smallest request output cap the provider accepts (OpenAI Responses: 16; absent means one), and the layer-2b budget clamp never dispatches below it: the last-gasp turn goes out AT the floor instead of one token, a remainder that cannot buy the floor is refused as a typed BudgetExhaustedError with zero wire calls, and a configured per-turn cap below the floor is a ConfigError; preflightEstimate reports that configuration as the error finding output-cap-below-provider-minimum. Tool arguments an adapter delivered as the parse-failure wrapper {__unparsed: raw} now get one deterministic second chance before the schema rejection: a strict re-parse, then one bounded normalization (markdown fence, first balanced object, raw control characters escaped inside string literals); a recovered object that passes the tool schema executes as if it had parsed on the wire, with a warn log naming the pass, and replay or resume recovers identically with nothing journaled. The OpenAI wire re-projects an unparseable call as the ORIGINAL raw arguments string instead of the wrapper JSON, so a model no longer learns to imitate {"__unparsed": ...} from its own rewritten history. Both wires drop unsafe-integer x-ratelimit values instead of normalizing 400 digits into Infinity. FakeAdapter gains capsOverrides so offline tests can drive caps-declared behavior like the floor.

Patch Changes

1.74.0

Minor Changes

  • d94beab: Quota drift telemetry and the honest zero (the v1.71 experiment review, P0.5 resized + P1.4). The experiment declared 12M TPM over a provider-real 1M, the local limiter went quiet, and seven live 429s followed with nothing recording the mismatch. Now: both wire adapters parse the provider's x-ratelimit headers on every real 429 into normalized per-minute limits (WireError.data.reportedLimits; the openai wire also gains the raw bucket capture the anthropic wire already had), the loop remembers them per (provider, model) as live telemetry, and the opt-in quota.declaredRules (the SAME rule array preflight takes) makes the engine journal a quota_drift decision plus a warn log whenever a binding declared cap EXCEEDS the provider-reported one, per invocation and dimension, with anthropic's split input and output windows summed against a combined declared tokensPerMinute. Purely observational, synthetic limiter denials never count, and without declaredRules journals and events stay byte identical. On the invoice, an unconfirmed row that recorded zero usage on every counter now carries usageUnknown: true (export-level usageUnknownRows count, CLI usage-unknown marker): the zeros mean "nothing recorded", never "the provider metered nothing"; derived at export time, no journal shape change.

Patch Changes

1.73.0

Patch Changes

1.72.0

Patch Changes

1.71.0

Patch Changes

1.70.1

Patch Changes

1.70.0

Patch Changes

1.69.0

Patch Changes

1.68.0

Patch Changes

1.67.0

Patch Changes

1.66.0

Patch Changes

1.65.0

Patch Changes

1.64.0

Patch Changes

1.63.0

Patch Changes

1.62.0

Patch Changes

1.61.0

Patch Changes

1.60.0

Patch Changes

1.59.4

Patch Changes

1.59.3

Patch Changes

1.59.2

Patch Changes

1.59.1

Patch Changes

1.59.0

Patch Changes

1.58.0

Patch Changes

1.57.0

Patch Changes

1.56.0

Patch Changes

1.55.0

Patch Changes

1.54.0

Patch Changes

1.53.0

Patch Changes

1.52.0

Patch Changes

1.51.0

Patch Changes

1.50.0

Patch Changes

1.49.0

Patch Changes

1.48.0

Patch Changes

1.47.0

Patch Changes

1.46.0

Patch Changes

1.45.0

Patch Changes

1.44.1

Patch Changes

1.44.0

Patch Changes

1.43.0

Patch Changes

1.42.0

Patch Changes

1.41.0

Patch Changes

1.40.0

Patch Changes

1.39.0

Patch Changes

1.38.0

Patch Changes

1.37.0

Patch Changes

1.36.0

Patch Changes

1.35.0

Patch Changes

1.34.0

Patch Changes

1.33.0

Patch Changes

1.32.0

Patch Changes

1.31.0

Patch Changes

  • df6b8f8: Retry-After accepts HTTP optional whitespace padding only. ECMAScript trim() removed far more than the OWS production (space and horizontal tab), so values padded with newline, carriage return, vertical tab, form feed, or NBSP were honored as delays despite the documented exact delta seconds grammar; a real HTTP transport rejects most of those octets, but an injected SDK client or a mock does not. Both first party adapters now match /^[\t ]*([0-9]+)[\t ]*$/ and fall back to the computed policy backoff for every other form.

1.30.0

Patch Changes

  • 87ce985: Parse Retry-After under the exact RFC delta seconds grammar (v1.29.0 review P3). Published 1.29.0 used Number(header), which accepted far more than the documented delta seconds form: an empty or whitespace header became a 0 ms delay (an instant retry instead of the policy backoff), and hex (0x10), exponent (1e3), decimal (1.5), and signed (+3) forms were honored as delays. The value must now be a nonempty run of decimal digits after optional whitespace; every other form (the HTTP date included) omits retryAfterMs so the engine's computed backoff applies, and a huge digit run still clamps to the Node timer maximum.
  • Updated dependencies [87ce985]

1.29.0

Minor Changes

  • 621d566: Make the retry and failover backoff interruptible and validate every provider supplied retry delay (v1.28.0 review P1 and P2).

    The retry engine now races its backoff wait against the host cancel signal (which the run deadline also drives) and the budget ceiling signal: an abort wakes the wait immediately, settles through the canonical aborted outcome (cancelled or exhausted, with every already recorded usage kept), and forbids every further dispatch, including the one behind a keyed limiter queue, so an adapter that ignores its signal can no longer be re entered after an abort. Previously a provider supplied retryAfterMs armed an uninterruptible sleep: a cancel, a crossed deadline, and a crossed budget ceiling all waited out the full backoff and the adapter was dispatched again. The injected retry.sleep(ms) test hook keeps its signature; a hook that loses the race is abandoned without an unhandled rejection, and the native timer path clears its timer so an abandoned long backoff never pins the event loop.

    retryDelayMs is now the defensive boundary the docs promise: only a finite nonnegative provider retryAfterMs replaces the computed delay, anything else (NaN, Infinity, a negative) is ignored as adapter noise, and every returned delay is a finite nonnegative integer clamped to the Node timer maximum, so a malformed or huge value can never arm an instant or overflowing timer. Both first party adapters stop emitting unvalidated Retry-After parses: an unparsable header (the HTTP date form included) omits retryAfterMs entirely instead of producing NaN (which also broke the WireError.data Json invariant by serializing to null), and a huge but finite value is clamped. The mapAnthropicStream TSDoc now states precisely how a truncated stream is reported (the finished flag on the return value, with the adapter synthesizing the terminal error).

    Four frozen fixture cassettes are refrozen for this release (the hashVersion-bump refreeze ceremony applies; hashVersion itself is unchanged and existing journals replay identically): in three cap freeze scenarios the main orchestrator entry now honestly settles cancelled at the cap instead of paying one more ordinary turn whose result the forced finish machinery discarded anyway, and one scenario loses a post abort wait suspension that can no longer be dispatched. Entry identities, keys, and every other row are byte identical.

Patch Changes

1.28.0

Minor Changes

  • d98eb0b: Enforce the terminal stream contract end to end (v1.27.0 deep E2E review P1 and P2). The runtime now fails closed when an adapter stream drains without a terminal finish or error event: the partial turn becomes a retryable transport fault that feeds the ordinary retry and failover machinery instead of settling as ok with truncated text, and a requested abort (cancel, budget ceiling, idle severance) remains a clean end with no fabricated provider error. Consumption stops at the first terminal event, so events after finish can no longer mutate the value, revise the authoritative bill, or trigger tool execution. The first party adapters enforce the same contract at the wire: the Chat Completions mapper no longer synthesizes finish: stop when the stream is cut before a finish_reason (usage the provider did report is still forwarded, half assembled tool calls are dropped), the Responses mapper fails closed on EOF without a response terminal event, and the Anthropic adapter surfaces a read cut before message_stop as a retryable transport error and no longer converts a caller requested abort during messages.create() into a terminal error. mapResponsesStream and mapChatCompletionsStream accept an optional signal so a requested abort keeps ending the stream without a terminal event. The VCR record wrapper now commits its cassette row even when the consumer stops reading at the terminal event (the engine always does now); adapter middleware must not rely on being drained past the terminal. The committed combined-loop-descent catalog cassette is refrozen because stopping consumption at the terminal shifts the deterministic interleaving of two parallel plan children by one scheduler turn; entry content, keys, and the actual hashVersion are unchanged, journals recorded under earlier versions replay unchanged, and this changeset carries the frozen fixture gate's hashVersion-bump ceremony token only to unlock that refreeze.

Patch Changes

1.27.0

Patch Changes

1.26.0

Patch Changes

1.25.0

Patch Changes

1.24.1

Patch Changes

1.24.0

Patch Changes

1.23.0

Patch Changes

1.22.0

Patch Changes

1.21.0

Minor Changes

  • 7ee42a0: Canonical reasoning effort max now reaches the wire unchanged on every GPT-5.6 sibling: Terra and Luna join Sol with wireMaxEffort: true, each verified live (a max-effort Responses call returns 200 with the effort echoed, and the API's own 400 validator enumerates max among the supported values), closing the silent quality downgrade of the v1.20.0 review P2-3. Pre-5.6 families and unknown models keep the safe, visible downmap to xhigh. The adapter also declares usageSemantics: 'openai-cache-subsets-v2', stamped onto usage-bearing journal entries so the cache-accounting semantics ride the journal alongside the numbers, and new exports undoV1190CacheDoubleCount and auditV1190CacheJournal provide the exact opt-in sidecar inversion for journals recorded by v1.19.0, whose adapter double-counted cache writes (v1.20.0 review P1/P2-2). Numeric hygiene stays with the core boundary validator by design; the normalizer maps wire shape only.

Patch Changes

1.20.0

Patch Changes

  • 9367030: Cache detail tokens are subsets of the full input, never additions. On the OpenAI wire input_tokens/prompt_tokens is already the complete prompt count; cached_tokens and cache_write_tokens classify parts of it. The v1.19.0 normalizer added cache writes on top of the full count, double-billing every written token at the base rate plus the 1.25x premium (a 73.6 percent overreport on the review's live cache scenario) and inflating budget debits, which could prematurely exhaust run, agent, and child ceilings. Both the Responses and the Chat Completions paths now pass the provider's full count through untouched and clamp impossible telemetry conservatively (nonnegative, reads keep priority, reads plus writes never exceed the input) instead of rejecting paid evidence. Verified against the live wire: identical prompts report the same input_tokens whether the details show a write or a read, and total_tokens equals input plus output; a new opt-in live contract test pins exactly that.
  • Updated dependencies [9367030]

1.19.0

Patch Changes

  • 8cc9a9c: Three cost-accounting corrections against the official OpenAI materials. Prompt cache writes are now accounted: GPT-5.6 and later report input_tokens_details.cache_write_tokens (Responses) and prompt_tokens_details.cache_write_tokens (Chat Completions) separately from the base prompt count, billed at 1.25x the uncached input rate; the adapter previously pinned cacheWriteTokens to 0, so the premium never entered cost or the budget guard. Writes now join inputTokens (the canonical Usage invariant: the full prompt), mirroring the Anthropic adapter's mapping, with one usage emission per response. response.failed now emits the failed response's paid usage before the error termination and classifies response.error.code canonically: rate_limit_exceeded retries as a rate limit, server_error and timeout-class codes retry as transport faults, validation/policy/auth and unknown codes stay non-retryable (fail closed); previously the branch dropped usage entirely and pinned retryable: false. The pre-5.6 price rows had gone stale after the provider's price cut and are corrected: gpt-5.5 5/30 (cached 0.5), gpt-5.5-pro 30/180 (no cached-input rate published; the row omits the field rather than fabricating a discount or a zero), gpt-5.4 2.5/15 (cached 0.25), gpt-5.4-mini 0.75/4.5 (cached 0.075). OPENAI_PRICING.pricingVersion bumps to openai-2026-07-18-r2 so a resumed run that priced under the stale rows surfaces the drift instead of silently reinterpreting past spend.
  • Updated dependencies [8cc9a9c]
  • Updated dependencies [8cc9a9c]
  • Updated dependencies [8cc9a9c]

1.18.0

Minor Changes

  • 943962d: Exact GPT-5.6 Terra and Luna capability and pricing rows, and a safe snapshot grammar. The seed table previously carried only Sol and the gpt-5.6 alias, and the general prefix matcher let the alias capture the sibling models: gpt-5.6-luna and gpt-5.6-terra were silently priced as Sol (5x on Luna), which is worse than no price at all. Terra ($2.5/$15 per MTok, cache read $0.25, cache write $3.125) and Luna ($1/$6, cache read $0.1, cache write $1.25) now have their own rows with the family's long-context tier (strictly above 272K input: 2x input, 1.5x output), both exported through OPENAI_PRICING under pricingVersion openai-2026-07-18. Prefix inheritance is restricted to the documented dated-snapshot grammar <exact model>-YYYY-MM-DD; any other unknown sibling or suffix now resolves to conservative unpriced caps so its usage lands in CostReport.unpriced instead of a fabricated total. Canonical reasoning effort max is now sent on the wire unchanged for Sol (OpenAiModelInfo.wireMaxEffort); other models keep the documented lossy downmap to xhigh, still recorded in providerMetadata.openai.effortDownmapped.

Patch Changes

1.17.0

Patch Changes

1.16.2

Patch Changes

1.16.1

Patch Changes

1.16.0

Patch Changes

1.15.0

Minor Changes

  • 4aee1f3: Production auth surface (v1.14 review P2-2). New sdkOptions on OpenAiAdapterOptions forwards official SDK construction options verbatim, maxRetries excluded from the type (OpenAiSdkOptions) and forced to 0: workloadIdentity federation included, plus fetch, timeout, and defaultHeaders. The client option now accepts the official OpenAI instance directly under strict TypeScript, no casts, alongside the structural OpenAiClientLike mock; an injected client with SDK autoretries enabled (maxRetries !== 0) is rejected with a typed ConfigError, as are client combined with construction options, duplicated fields, and the apiKey plus sdkOptions.workloadIdentity conflict, all before any network I/O. A synthetic workload-identity test covers the full path: one token exchange, one Responses API request under the short-lived bearer, canonical finish.

Patch Changes

1.14.0

Patch Changes

1.13.0

Patch Changes

1.12.0

Patch Changes

1.11.0

Patch Changes

1.10.0

Patch Changes

1.9.0

Minor Changes

  • 7577f8e: Correct the Anthropic fallback pricing to the official table and export versioned price tables from both first-party adapters.

    The ANTHROPIC_MODELS seed rows had never been audited against the published price list and overcharged every current Claude model: Fable 5 was seeded at exactly 2x the official rate (20/100 vs 10/50 per MTok, cache rates likewise), Opus 4.8 at 12/60 vs 5/25, Opus 4.7 at 10/50 vs 5/25, and Opus 4.6 at 15/75 vs 5/25. Claude Sonnet 5 now carries its introductory price (2/10, in effect through 2026-08-31); Haiku 4.5 and Sonnet 4.6 were already correct. Cost reports for affected models drop accordingly, and budget ceilings admit roughly twice the work they previously rejected.

    New exports ANTHROPIC_PRICING (anthropic-2026-07-16) and OPENAI_PRICING (openai-2026-07-16) publish the seed rows as versioned PriceTables for createEngine({ pricing }), so runs journal a concrete pricing version instead of unpriced and price revisions become explicit table updates. createTestEngine gained a pricing passthrough for testing against a versioned table.

Patch Changes

1.8.0

Patch Changes

  • Updated dependencies [25724b5]
  • Updated dependencies [57ea1de]
  • Updated dependencies [7884ec5]
  • Updated dependencies [52db30d]

1.7.0

Patch Changes

  • Updated dependencies [45285aa]
  • Updated dependencies [2f20d1d]
  • Updated dependencies [22f65a8]
  • Updated dependencies [2ddfa29]
  • Updated dependencies [2abd9c2]
  • Updated dependencies [1c1175d]

1.6.0

Minor Changes

  • df416fc: Correct and extend model pricing: GPT-5.6 entries, long-context tiers, no fabricated prices, no double-charged cache.

    • Pricing gains optional long-context tiers (PricingTier): the highest threshold strictly below the full prompt re-prices the entire request, input-side rates (cache included) scaling by inputMultiplier and the output rate by outputMultiplier. Existing linear rows are untouched.
    • @rulvar/openai seeds gpt-5.6-sol and its gpt-5.6 alias with the official caps and pricing (1,050,000 context, 128,000 max output, $5/$0.50/$30 per MTok, $6.25 cache write, 2x input and 1.5x output above 272K input tokens). Previously the unknown-model fallback silently priced them as gpt-5.4.
    • Unknown model ids in both first-class adapters keep conservative transport caps but no longer receive a fabricated price row: their usage surfaces in CostReport.unpriced and a USD ceiling warns that it cannot bound them. Provide a versioned createEngine({ pricing }) row for hosted models the tables do not know yet.
    • priceUsdOf no longer double-charges cache tokens: under the Usage invariant inputTokens is the full prompt, so the input rate now bills only the uncached remainder while cache reads and writes bill at their own rates (a row without cache rates bills them at the input rate). Cache-heavy runs previously over-attributed cost by the full input rate on every cached token.
    • Admission reserve estimation routes through the same priceUsdOf, so estimates and settled costs share one formula, tiers included.
    • Model id resolution picks the longest matching table prefix, so a dated gpt-5.5-pro-... snapshot resolves to the pro entry, never the shorter gpt-5.5 sibling.
  • 886d065: Make the first-class adapters genuinely streaming: every canonical event is yielded AS its provider event is consumed.

    Both adapters (and openaiCompatible) buffered the complete canonical event stream in an internal array and yielded it only after the provider response finished. Consequences fixed by this change: agent:stream was never live; the stream-idle watchdog saw zero events during healthy generation, so any turn longer than streamIdleTimeoutMs (default 120s) was falsely severed as idle and retried; a budget or external abort lost ALL partial usage (the journal recorded zero for tokens the provider billed); and every delta of a long response was retained in memory.

    • mapAnthropicStream, mapResponsesStream, and mapChatCompletionsStream are now async generators: they yield each ChatEvent as the corresponding provider event is consumed, with the consumer's pull as the only pacing (natural backpressure, no queue, no detached work). The Anthropic mapper's return value carries the accumulated pause_turn state; TurnMapping no longer has the redundant events array field. Callers of the old callback signatures (emit parameter) must switch to iterating the generator.
    • Adapter behavior is preserved: canonical id mapping, thinking/reasoning retention, pause_turn continuation and its cap (each segment now streams live before the continuation dispatches), tool argument assembly, typed refusals and errors, exactly one canonical terminal event, the degraded Chat Completions path (visible in providerMetadata.openai.degradedPath), abort propagation, usage normalization, and SDK autoretries disabled.
    • New regression tests with gated fake SDK clients prove the first stream().next() resolves before the provider terminal exists, aborts reach the in-flight provider iterable after the first delta, a paused consumer causes zero read-ahead (lock-step pulls), pause_turn segment deltas arrive before the continuation request, and exactly one terminal event survives.

Patch Changes

  • da4dbad: Write the product name as Rulvar in prose: package READMEs, npm descriptions, and the documentation site now capitalize the brand. Identifiers keep their exact casing, so package names, the rulvar binary, rulvar.config.mjs, the .rulvar store directory, the rulvar.* OTel attributes, and every URL are unchanged. Documentation and metadata only; no runtime behaviour changes.
  • Updated dependencies [da4dbad]
  • Updated dependencies [487da86]
  • Updated dependencies [df416fc]
  • Updated dependencies [a737810]
  • Updated dependencies [9eb66b4]

1.5.2

Patch Changes

1.5.1

Patch Changes

1.5.0

Patch Changes

1.4.0

Patch Changes

1.3.2

Patch Changes

  • ddef383: Every published package now ships a README, so its npm page states what the package is, how it installs, and where the documentation lives (npm includes README.md in the tarball regardless of the files allowlist, so no manifest changes are involved; @rulvar/compat gains its README on its own next release). Alongside, the repository-level pages are refreshed to the current project state: the root README is rewritten around the never-pay-twice pitch with a runnable quickstart condensation and the full package table, CONTRIBUTING.md lists the complete PR gate set, the examples README drops retired-spec citations for live docs.rulvar.com links and documents the dogfood journal replay, and the pointer README gets the same treatment.
  • Updated dependencies [ddef383]

1.3.1

Patch Changes

  • 7d1552e: Runtime message strings no longer cite the retired internal specification set: error and warning messages, validation issues, and the CLI help text drop the dangling docs/NN, section ... references, pointing at https://docs.rulvar.com pages where a pointer earns its place (the CLI help header, tool naming, toolset registries, bare resume). The umbrella package description sheds the naming-contingency note: the unscoped alias is published and owned. Three strings embedded in frozen recordings stay byte-identical on purpose (the no-progress abort reason and two testing-internal recorder strings), as does the byte-locked golden-fold fixture. Test-file comments lose their citations too; test titles are unchanged.
  • Updated dependencies [7d1552e]

1.3.0

Patch Changes

1.2.0

Patch Changes

  • 154507b: TSDoc and inline comments no longer cite the retired internal specification set (the pre-docs-site docs/NN, section ... references). The citations either became links to the public documentation at docs.rulvar.com or were dropped where the comment already carried the rule; traceability markers (DEF-n, XF-nn, FR-nnn, OQ-nn, W-nnn) are untouched. Comment-only change: no runtime behavior, no API shapes, and no runtime message strings were modified; the frozen golden-fold fixture is byte-identical.
  • Updated dependencies [3bfaec0]
  • Updated dependencies [890f42c]
  • Updated dependencies [154507b]

1.1.0

Patch Changes

1.0.0

Patch Changes

  • Updated dependencies [0e0b569]
  • Updated dependencies [b28b7a3]
  • Updated dependencies [b53a89e]
  • Updated dependencies [4454175]
  • Updated dependencies [6599ca8]
  • Updated dependencies [6649e5f]
  • Updated dependencies [fd2f83b]
  • Updated dependencies [01d6b2d]
  • Updated dependencies [9a20dbb]
  • Updated dependencies [0fbe7ea]
  • Updated dependencies [ebe0abc]
  • Updated dependencies [a3079d0]
  • Updated dependencies [596a39b]
  • Updated dependencies [464ab6e]

0.9.0

Patch Changes

  • Updated dependencies [84f94d4]
  • Updated dependencies [65c7b2c]
  • Updated dependencies [a2a3243]
  • Updated dependencies [ebc8101]

0.8.0

Patch Changes

  • Updated dependencies [85d55cf]
  • Updated dependencies [b88c9e3]
  • Updated dependencies [f3c4613]
  • Updated dependencies [a41c20f]
  • Updated dependencies [f4e70be]
  • Updated dependencies [75d1646]
  • Updated dependencies [0627413]
  • Updated dependencies [55c0f87]
  • Updated dependencies [fd33871]
  • Updated dependencies [e70e7f4]
  • Updated dependencies [bc9c903]

0.7.0

Patch Changes

  • Updated dependencies [fd1d06c]
  • Updated dependencies [6fcf296]
  • Updated dependencies [dcc97a9]
  • Updated dependencies [434dc83]
  • Updated dependencies [03173c1]
  • Updated dependencies [11c0afc]

0.6.0

Patch Changes

  • Updated dependencies [fa05007]
  • Updated dependencies [9234dc8]
  • Updated dependencies [644512c]
  • Updated dependencies [8a41656]
  • Updated dependencies [02f7f7a]

0.5.0

Minor Changes

  • ac274f4: M4-T01 role protocol completion. The full trigger protocol for the six invocation roles lands in @rulvar/core (model/roles.ts):

    • Extract necessity is completed per docs/04 section 8.3: a separate final structured-output invocation fires when a schema is set AND (routing directs extract to a different model OR the loop model's required tier cannot ride a tools-available turn OR finalize is routed). The required-tier rule is new: a forced-tool tier pins toolChoice to emit_result and cannot ride while the agent's tools must remain available, so such agents now pay one separate extract call instead of silently losing tool access. Agents without tools keep the M1 single-shot behavior byte for byte.
    • The finalize role fires for the first time: only when configured in routing and only for tool-bearing agents, as one synthesis invocation with toolChoice 'none' over the full transcript after tools stop. Its text is the output for schema-less calls; with a schema the separate extract runs over the transcript including the synthesis.
    • A separate extract invocation over a tool-bearing transcript now carries the agent's tool contracts (both providers reject tool-use history without tool definitions) with toolChoice pinned to 'none' or to emit_result per tier.
    • Both adapters map toolChoice: 'none' to the provider's explicit none choice with the tools param present instead of dropping tools from the request.
    • createTestEngine no longer routes finalize by default: the routing key is the firing opt-in, and the old default would have summoned a synthesis call for every tool-bearing test agent. Tests that want finalize route it explicitly.

    Identity is untouched: extract and finalize resolutions never enter the spawn content key, and existing journals replay unchanged.

  • 5735d92: M4-T02 HistoryProjector. Cross-provider history projection lands in @rulvar/core (model/projector.ts) and the retention pipeline that feeds it:

    • projectHistory projects the canonical history into a target provider's view: provider-raw parts ride if and only if the target adapter's provider family matches the part's provider; everything else passes through untouched. The agent loop projects EVERY outgoing request (loop turns, finalize, extract), so per-role provider mixing inside one agent yields a valid wire history on each side.
    • Retention transport: adapters ship a turn's blocks-to-retain in stream order via finish.providerMetadata[<adapter id>].retainedParts; the runtime lifts them into provider-raw parts at the HEAD of the turn's canonical assistant message. @rulvar/anthropic ships thinking and redacted_thinking blocks (signatures intact, pause_turn continuations included); @rulvar/openai ships reasoning items with their encrypted_content. Retained blocks now actually reach the canonical history, survive checkpoints, and echo byte-exact to their own provider on every subsequent turn.
    • ProviderAdapter gains an optional provider field: the provider family for provider-raw matching (default = adapter id). The first-class adapters declare 'anthropic' and 'openai'; openaiCompatible gateways declare 'openai' whatever their custom id, so same-family adapters share retained blocks and projections.

    Identity is untouched: projection state never enters content keys, and adapters that ship no retention payload (FakeAdapter included) produce byte-identical histories.

Patch Changes

  • Updated dependencies [ac274f4]
  • Updated dependencies [5735d92]
  • Updated dependencies [46ca98e]
  • Updated dependencies [8ae129e]
  • Updated dependencies [d1c4525]
  • Updated dependencies [b840aba]

0.4.0

Minor Changes

  • f668890: M3-T05 worktree isolation and M3-T06 openaiCompatible. GitWorktreeProvider implements the IsolationProvider seam: acquire creates a detached worktree from HEAD or a given ref (non-git host is a typed ConfigError), tools receive cwd inside the tree, collect() snapshots changed files and a binary patch, dispose removes the tree with keepOnError retention under the shared maxPinnedWorktrees cap (default 4). ctx.agent resolves isolation call-over-profile into spawn identity, stores the collected patch in TranscriptStore, and surfaces it as a kind 'patch' Artifact on AgentResult.artifacts and the terminal journal entry, so replays reconstruct artifacts with zero live calls; applying the patch stays with the caller. isolation 'readonly' is accepted as a declaration (its compiled deny rule ships with risk presets in M5).

    @rulvar/openai gains openaiCompatible({ id, baseURL, apiKey?, caps? }) for Ollama, vLLM, and gateways: the Chat Completions dialect by construction, explicit ids so several endpoints coexist (duplicate id stays a ConfigError at createEngine), and the most conservative caps when unprobed (prompt-tier structured output, no parallel tools, no pricing; supplied caps merge over the floor).

Patch Changes

  • Updated dependencies [dfe03b5]
  • Updated dependencies [d2089a7]
  • Updated dependencies [3f60234]
  • Updated dependencies [f668890]
  • Updated dependencies [16d7aa6]
  • Updated dependencies [6513ce8]
  • Updated dependencies [7dad493]
  • Updated dependencies [2bbf180]

0.3.0

Patch Changes

  • Updated dependencies [43444f6]
  • Updated dependencies [279881b]
  • Updated dependencies [9fd0966]
  • Updated dependencies [24ebadf]
  • Updated dependencies [a1b35d3]
  • Updated dependencies [18a5821]

0.2.0

Minor Changes

  • 527c9b4: M1-T12/T13: the two first-class adapters on the July 2026 surfaces. @rulvar/anthropic: adaptive thinking, the output_config umbrella (effort passthrough including max, native json_schema format), strict tools, cache_control compilation from cacheHint (deepest-4 kept), thinking-block retention with provider-granularity projection, pause_turn absorption without synthetic user messages, the full stop-reason table with typed refusal stop details, count_tokens, capabilities-bearing refreshCaps, retry-after/x-ratelimit/529 signaling, SDK autoretries disabled, usage normalization under the Usage invariant. @rulvar/openai: Responses API with manual item replay only (store false, encrypted reasoning echoed verbatim; previous_response_id/Conversations rejected as ConfigError), flattened strict function tools, text.format json_schema, the typed SSE catalog mapped to ChatEvent, the Chat Completions degraded path (visible via providerMetadata), effort mapping with the documented lossy max-to-xhigh downmap and provider none via providerOptions only, usage normalization.

Patch Changes

  • Updated dependencies [c24228d]
  • Updated dependencies [c50871e]
  • Updated dependencies [1af8fb9]
  • Updated dependencies [1fe0249]
  • Updated dependencies [5c4fc32]

0.1.0

Minor Changes

  • f4e2be9: M0 repo bootstrap (v0.1.0, docs/10-implementation-plan.md section "M0"): monorepo scaffold on the committed toolchain (pnpm 11 workspaces with catalogs, TypeScript 6.0, tsdown, Vitest 4, ESLint 9 flat config, Turborepo 2, changesets fixed mode, npm trusted publishing), the docs/ canon as single source of truth, the L0 contracts skeleton in @rulvar/core, and the vendored dependencies (StandardSchemaV1/StandardJSONSchemaV1 types, the @cfworker/json-schema lineage validator subset, a first-party monotonic ULID). Placeholder scaffolds only: no public API ships in this release.

Patch Changes

@rulvar/plan

1.252.0

Patch Changes

  • Updated dependencies [3ccb6cf]
  • Updated dependencies [52d807f]
  • Updated dependencies [517ed00]
  • Updated dependencies [a7e589d]
  • Updated dependencies [76e95eb]

1.251.0

Patch Changes

  • Updated dependencies [e7e829c]
  • Updated dependencies [5982be8]
  • Updated dependencies [7c58fb2]
  • Updated dependencies [b3e465a]
  • Updated dependencies [c4e5d6a]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [0ae8b85]
  • Updated dependencies [7932936]
  • Updated dependencies [7932936]
  • Updated dependencies [88da0ed]
  • Updated dependencies [06c0e85]

1.250.0

Patch Changes

  • Updated dependencies [0e240b9]
  • Updated dependencies [6fe585e]
  • Updated dependencies [c5eb19c]
  • Updated dependencies [565c13b]
  • Updated dependencies [c6d197b]
  • Updated dependencies [c9d9729]
  • Updated dependencies [fed9db6]
  • Updated dependencies [df9ed76]
  • Updated dependencies [3020912]
  • Updated dependencies [d8d598d]

1.249.0

Patch Changes

  • Updated dependencies [8862133]
  • Updated dependencies [0d7a717]
  • Updated dependencies [e4428bd]
  • Updated dependencies [d6873c1]
  • Updated dependencies [4092e8d]
  • Updated dependencies [e086590]
  • Updated dependencies [1411938]
  • Updated dependencies [737d1ee]
  • Updated dependencies [634f966]
  • Updated dependencies [052cc26]
  • Updated dependencies [bbae134]

1.248.0

Patch Changes

  • Updated dependencies [8d0cd69]
  • Updated dependencies [81065e4]
  • Updated dependencies [8573f20]
  • Updated dependencies [95f6a5e]

1.247.0

Patch Changes

  • Updated dependencies [1933ecc]
  • Updated dependencies [db0a5f0]
  • Updated dependencies [b698726]
  • Updated dependencies [48348d2]
  • Updated dependencies [4cfa1cc]
  • Updated dependencies [4b7197a]
  • Updated dependencies [5ebc842]
  • Updated dependencies [16ff6b9]
  • Updated dependencies [0c9941d]

1.246.0

Patch Changes

  • Updated dependencies [d165b0c]
  • Updated dependencies [d59f4a0]
  • Updated dependencies [46907ac]
  • Updated dependencies [9929ad3]
  • Updated dependencies [1790a6a]

1.245.0

Patch Changes

  • Updated dependencies [b4d47a8]
  • Updated dependencies [dee6db4]
  • Updated dependencies [b85c113]
  • Updated dependencies [bc556e7]
  • Updated dependencies [9f11d29]
  • Updated dependencies [19bcea0]
  • Updated dependencies [60b461c]
  • Updated dependencies [61e3a1a]
  • Updated dependencies [a156b81]
  • Updated dependencies [0bd7045]

1.244.0

Patch Changes

  • Updated dependencies [38d839a]
  • Updated dependencies [ce13b0f]
  • Updated dependencies [4fa23e3]
  • Updated dependencies [6841c69]
  • Updated dependencies [f56721d]
  • Updated dependencies [c894a43]
  • Updated dependencies [f6944a3]
  • Updated dependencies [23fd0e0]

1.243.0

Patch Changes

  • Updated dependencies [746d1f4]
  • Updated dependencies [009b29c]
  • Updated dependencies [1674cbe]
  • Updated dependencies [bd096bc]

1.242.0

Patch Changes

  • Updated dependencies [6e3438e]
  • Updated dependencies [ba5cf67]
  • Updated dependencies [c2d1531]

1.241.0

Patch Changes

  • Updated dependencies [dbcdd24]
  • Updated dependencies [7ae7243]
  • Updated dependencies [4f832c4]
  • Updated dependencies [7452d3d]
  • Updated dependencies [a4e22bf]
  • Updated dependencies [82df4af]

1.240.0

Patch Changes

1.239.0

Patch Changes

  • Updated dependencies [74ce99a]
  • Updated dependencies [ccd0665]
  • Updated dependencies [0c5ce21]
  • Updated dependencies [0616934]

1.238.0

Patch Changes

  • Updated dependencies [cf00947]
  • Updated dependencies [c7b9382]
  • Updated dependencies [88aea96]
  • Updated dependencies [6da8d05]
  • Updated dependencies [eae5c4c]

1.237.0

Patch Changes

  • Updated dependencies [9d6a279]
  • Updated dependencies [49a98f6]
  • Updated dependencies [a734ca0]
  • Updated dependencies [deb406f]

1.236.0

Patch Changes

1.235.0

Minor Changes

  • e30687f: The resume config identity (RV3203). The profile registry hash frozen in termination.init (profile names mapped to ladder lengths) is now recomputed on every PlanRunner resume: a mismatch refuses the resumed run typed BEFORE any model call, because ladders are live values the journal cannot rebuild and "the journal wins" is not honorable for them; PlanRunnerOptions.profileDrift: 'warn' downgrades the refusal to the termination:config-drift event for a deliberate registry change. The frozen dollar vector (runBudgetUsdCeiling, orchestratorCapUsd, finalizeReserveUsd) rides the same drift report; journals from before v1.8 stored zeros there and skip the comparison, journals from before the registry hash shipped skip the identity check entirely, and a resume under the original profiles is byte identical.
  • 2ecd787: The extension finish gate (RV3202). OrchestratorExtension gains finishGate?(), consulted FIRST on every ordinary coordination finish: a refusal returns as the finish tool's typed error result (nothing journals, no repair spent), so the model resolves the named blockers and finishes again; the forced-finalization and synthesis finishes are never gated. PlanRunner implements it: finish is now refused while any plan node is ready or running, with the stragglers named, because quiescence participation alone gated only wakes and a root could settle a bare ok while the exit barrier cancelled a running node. allowEarlyFinish: true restores the old behavior deliberately. Runs without an extension finish gate are byte identical. journal-shape-revision: the oscillation-freeze cassette re-recorded for the gate's live path (the scripted finish over the still-running frozen-signature node is now refused typed, and the scenario closes the straggler deliberately before finishing); already-journaled entries replay verbatim, so existing journals stay valid.

Patch Changes

  • Updated dependencies [ba4e10d]
  • Updated dependencies [172402b]
  • Updated dependencies [2ecd787]
  • Updated dependencies [e20a5e9]
  • Updated dependencies [98c8691]
  • Updated dependencies [c70def0]

1.234.0

Patch Changes

1.233.0

Patch Changes

  • Updated dependencies [48b5200]
  • Updated dependencies [73bc32b]
  • Updated dependencies [e63b743]
  • Updated dependencies [ef45da7]

1.232.0

Patch Changes

  • Updated dependencies [1440410]
  • Updated dependencies [6e467f4]
  • Updated dependencies [0b14293]
  • Updated dependencies [e3bcab2]
  • Updated dependencies [b55a0f7]

1.231.0

Patch Changes

  • Updated dependencies [4eb4b56]
  • Updated dependencies [bc8f09e]
  • Updated dependencies [ff9b8c2]

1.230.0

Patch Changes

1.229.0

Patch Changes

  • Updated dependencies [3370342]
  • Updated dependencies [2fb6656]
  • Updated dependencies [edce170]

1.228.0

Patch Changes

  • Updated dependencies [4034fac]
  • Updated dependencies [a54b085]
  • Updated dependencies [9d0a9be]
  • Updated dependencies [be9ef28]

1.227.0

Patch Changes

  • Updated dependencies [f262e9f]
  • Updated dependencies [f191ff7]
  • Updated dependencies [fbbfbe8]
  • Updated dependencies [263b5e8]
  • Updated dependencies [db4d56d]
  • Updated dependencies [41f93a9]
  • Updated dependencies [98c8ca9]

1.226.0

Patch Changes

1.225.0

Patch Changes

1.224.0

Patch Changes

1.223.0

Patch Changes

1.222.0

Patch Changes

1.221.0

Patch Changes

1.220.0

Patch Changes

1.219.0

Patch Changes

1.218.0

Patch Changes

1.217.0

Patch Changes

1.216.0

Patch Changes

1.215.0

Patch Changes

1.214.0

Patch Changes

1.213.0

Patch Changes

1.212.0

Patch Changes

1.211.0

Patch Changes

1.210.0

Patch Changes

1.209.0

Patch Changes

1.208.0

Patch Changes

1.207.0

Patch Changes

1.206.0

Patch Changes

1.205.0

Patch Changes

1.204.0

Patch Changes

1.203.0

Patch Changes

1.202.0

Patch Changes

1.201.0

Patch Changes

1.200.0

Patch Changes

1.199.0

Patch Changes

1.198.0

Patch Changes

1.197.0

Patch Changes

1.196.0

Patch Changes

1.195.0

Patch Changes

1.194.0

Patch Changes

1.193.0

Patch Changes

1.192.0

Patch Changes

1.191.0

Patch Changes

1.190.0

Patch Changes

1.189.0

Patch Changes

1.188.0

Patch Changes

1.187.0

Patch Changes

1.186.0

Patch Changes

1.185.0

Patch Changes

1.184.0

Patch Changes

1.183.0

Patch Changes

1.182.0

Patch Changes

1.181.0

Patch Changes

1.180.0

Patch Changes

1.179.0

Patch Changes

1.178.0

Minor Changes

  • e89f377: Package truth is now a gate, not a hope (RV1701). The eighteenth comparison benchmark's strongest documentation-class failure was package identity conflation: a due-diligence dossier described @rulvar/plan with a citation into packages/planner, and nothing mechanical objected. The docs cannot stop a reader's model from confusing two names, but they can refuse to ship a byte that gets the universe wrong themselves. Docs lint check 12 now enforces four layers against build artifacts rather than prose: every @rulvar/<name> token in every page must name a real workspace package; every import, require, export-from, and dynamic-import specifier in a ts/js fence must resolve to a real exports-map subpath of its package; every named root import in a fence must be a symbol the package's committed dts rollup actually exports, which turns import { planRunner } from '@rulvar/planner' into a lint failure instead of a shipped falsehood; and the versioning page's fixed-group list, its spelled-out size, and both package tables stay in set equality with .changeset/config.json and the manifests. The completeness layer had teeth on its first run: the installation guide's "full package list" had silently dropped @rulvar/store-postgres and @rulvar/executor; both rows are restored. The pointer narrative now tells the caret truth: a fresh install of rulvar@X resolves the newest umbrella release of X's major (X or newer, never older), so the bare name is a front door, not a pinning surface; pin @rulvar/rulvar exactly when you need one exact version. The CommonJS consumer path the installation guide documents is now proven on packed artifacts: the install smoke gains a .cjs consumer that require()s the umbrella and the pointer on the packed tarballs and asserts import() serves the same module instance. And the two npm descriptions disambiguate each other in both directions: @rulvar/plan replans during the run and names @rulvar/planner as the package it is not; @rulvar/planner plans before the run and names @rulvar/plan the same way.

Patch Changes

1.177.0

Patch Changes

1.176.0

Patch Changes

1.175.0

Patch Changes

1.174.0

Patch Changes

1.173.0

Patch Changes

1.172.0

Patch Changes

1.171.0

Patch Changes

1.170.0

Patch Changes

1.169.0

Patch Changes

1.168.0

Patch Changes

1.167.0

Patch Changes

1.166.0

Patch Changes

1.165.0

Patch Changes

1.164.0

Patch Changes

1.163.0

Patch Changes

1.162.0

Patch Changes

1.161.0

Patch Changes

1.160.0

Patch Changes

1.159.0

Patch Changes

1.158.0

Patch Changes

1.157.0

Patch Changes

1.156.0

Patch Changes

1.155.0

Patch Changes

1.154.0

Patch Changes

1.153.0

Patch Changes

1.152.0

Patch Changes

1.151.0

Patch Changes

1.150.0

Patch Changes

1.149.0

Patch Changes

1.148.0

Patch Changes

1.147.0

Patch Changes

1.146.0

Patch Changes

1.145.0

Patch Changes

1.144.0

Patch Changes

1.143.0

Patch Changes

1.142.0

Patch Changes

1.141.0

Patch Changes

1.140.0

Patch Changes

1.139.0

Patch Changes

1.138.0

Patch Changes

1.137.0

Patch Changes

1.136.0

Patch Changes

1.135.0

Patch Changes

1.134.0

Patch Changes

1.133.0

Patch Changes

1.132.0

Patch Changes

1.131.0

Patch Changes

1.130.0

Patch Changes

1.129.0

Patch Changes

1.128.0

Patch Changes

1.127.0

Patch Changes

1.126.0

Patch Changes

1.125.0

Patch Changes

1.124.0

Patch Changes

1.123.0

Patch Changes

1.122.0

Patch Changes

1.121.0

Patch Changes

1.120.0

Patch Changes

1.119.0

Patch Changes

1.118.0

Patch Changes

1.117.0

Patch Changes

1.116.0

Patch Changes

1.115.0

Patch Changes

1.114.0

Patch Changes

1.113.0

Patch Changes

1.112.0

Patch Changes

1.111.0

Patch Changes

1.110.0

Patch Changes

1.109.0

Patch Changes

1.108.0

Patch Changes

1.107.0

Patch Changes

1.106.0

Patch Changes

1.105.0

Patch Changes

1.104.0

Patch Changes

1.103.0

Patch Changes

1.102.0

Patch Changes

1.101.0

Patch Changes

1.100.0

Patch Changes

1.99.1

Patch Changes

  • ef08d73: Guarantee matrix and exactly-once claim hygiene (RV508); no runtime behavior changes. The isolated-executor guide now carries the guarantee matrix stating flatly who provides what: the library's layers give at-least-once execution with attempt binding and intent-before-effect, exactly-once effect execution is promised by NO library layer, and what IS exactly-once is pay and replay (the never-pay-twice invariant). The two claims the ninth comparison experiment's judge caught are rewritten to the precise statements ("each ran once" became attempt counting under a stable idempotency key; the approvals guide now says continuation is a run-level guarantee, not an effect-level one, with the at-least-once window named); ctx.step docs state the same window for effectful steps; a ResolutionBy note says the field records a channel, never a verified principal (identity, signatures, and separation of duties are host IAM). The worker header now points at the shipped SqliteQuotaLimiter and PostgresQuotaLimiter instead of denying that cross-process limiters exist. A new docs-lint sentinel forbids "exactly once" claims in the hand-written docs and in package source comments outside a vetted (file, heading anchor) allowlist (the durability pay doctrine and the guarantee matrix), and every remaining occurrence in doc prose and source comments was rewritten to the precise wording; string literals are deliberately out of scope (tool descriptions enter the toolset hash).
  • Updated dependencies [ef08d73]

1.99.0

Patch Changes

1.98.0

Patch Changes

1.97.0

Patch Changes

1.96.0

Patch Changes

1.95.0

Patch Changes

1.94.0

Patch Changes

1.93.0

Patch Changes

1.92.0

Patch Changes

1.91.0

Patch Changes

1.90.0

Patch Changes

1.89.0

Patch Changes

1.88.0

Patch Changes

1.87.0

Patch Changes

1.86.0

Patch Changes

1.85.0

Patch Changes

1.84.0

Patch Changes

1.83.0

Patch Changes

1.82.0

Patch Changes

1.81.2

Patch Changes

1.81.1

Patch Changes

1.81.0

Patch Changes

1.80.0

Patch Changes

1.79.0

Patch Changes

1.78.0

Patch Changes

1.77.0

Patch Changes

1.76.0

Patch Changes

1.75.1

Patch Changes

1.75.0

Patch Changes

1.74.0

Patch Changes

1.73.0

Patch Changes

1.72.0

Patch Changes

1.71.0

Patch Changes

1.70.1

Patch Changes

1.70.0

Patch Changes

1.69.0

Patch Changes

1.68.0

Patch Changes

1.67.0

Patch Changes

1.66.0

Patch Changes

1.65.0

Patch Changes

1.64.0

Patch Changes

1.63.0

Patch Changes

1.62.0

Patch Changes

1.61.0

Patch Changes

1.60.0

Patch Changes

1.59.4

Patch Changes

1.59.3

Patch Changes

1.59.2

Patch Changes

1.59.1

Patch Changes

1.59.0

Patch Changes

1.58.0

Patch Changes

1.57.0

Patch Changes

1.56.0

Patch Changes

1.55.0

Patch Changes

1.54.0

Patch Changes

1.53.0

Patch Changes

1.52.0

Patch Changes

1.51.0

Patch Changes

1.50.0

Patch Changes

1.49.0

Patch Changes

1.48.0

Patch Changes

1.47.0

Patch Changes

1.46.0

Patch Changes

1.45.0

Patch Changes

1.44.1

Patch Changes

1.44.0

Patch Changes

1.43.0

Patch Changes

1.42.0

Patch Changes

1.41.0

Patch Changes

1.40.0

Patch Changes

1.39.0

Patch Changes

1.38.0

Patch Changes

1.37.0

Patch Changes

1.36.0

Minor Changes

  • 101795b: Make the guards fallback 'fail-run' a real failure policy (v1.35.0 review P2). After the journaled guard verdict the PlanRunner terminates the orchestration with FailRunError (data.source: 'plan_guards', data.verdictRef) through the new extension terminate capability: no further model turn is consulted, the run ends with outcome error, and a resume re folds the verdict at boot and rolls the same failure forward with zero model calls. reject-revision and finish-with-partial keep their historical steer to finish behavior.

Patch Changes

1.35.0

Minor Changes

  • d4ac3bf: Validate RevisionGuards limits at construction (v1.34.0 review P2-3). The streak and oscillation limits must be positive integers, the stall replan cap a nonnegative integer, and maxAbandonedNetUsdFraction a fraction in (0, 1]; anything else, NaN included, is a typed ConfigError before any revision is judged. Unvalidated, a NaN limit inverted the machinery: the dropped and oscillation guards tripped immediately while the stall cap never tripped at all.

Patch Changes

1.34.0

Patch Changes

1.33.0

Patch Changes

1.32.0

Patch Changes

1.31.0

Patch Changes

1.30.0

Patch Changes

1.29.0

Patch Changes

1.28.0

Patch Changes

1.27.0

Patch Changes

1.26.0

Patch Changes

1.25.0

Patch Changes

1.24.1

Patch Changes

1.24.0

Patch Changes

1.23.0

Minor Changes

  • 1f9c272: PlanRunner spawn telemetry, the missing evals export, and the conformance kit's new meta field (v1.22.0 review P2-5, P2-6, P1-2).

    • @rulvar/plan: PlanRunner journals every admission INSIDE a carrying entry (decomposition rows in escalation decisions, ladder-verdict respawns, reuse and graft links, revision admissions) and emitted no spawn:admitted/spawn:rejected at all; a live PlanRunner run with admitted roots showed an event count of zero. Every embedded admission row now announces through one formatter, identically on the live path and on replay absorb, with replayed: true on recovered rows, entryRef on the journaled carrying entry, and agentType resolved from the landed specs.
    • @rulvar/evals: agentTypeRuleHolds joins the package root next to rungRuleHolds, exactly as the v1.21.0 changelog had already announced; a public-API test now imports the checkpoint quartet from the root. The evals guide gains a full measured-value checkpoint section (ladder/pool/cell/arm vocabulary, both criteria, the vacuous-pass guard, cost discipline, a runnable example).
    • @rulvar/store-conformance: the meta round-trip case now also pins the new optional RunMeta.segments field, which the engine bumps durably at every resume to keep event seq/spanId unique per run.

Patch Changes

1.22.0

Patch Changes

1.21.0

Patch Changes

1.20.0

Patch Changes

1.19.0

Minor Changes

  • 8cc9a9c: orchestrate(engine, goal, opts?, runOptions?) and orchestratePlanned(engine, goal, opts?, runOptions?) accept the created run's RunOptions as an optional fourth argument, threaded verbatim to engine.run. runOptions.budgetUsd is the ROOT hard ceiling over the whole tree (the orchestrator and every child), immutable after start and frozen into RunMeta, while opts.budget only shapes the orchestrator's own sub-account inside that ceiling; the two layers were previously conflatable, and the canonical shortcuts could not set a root ceiling (or signal, runId, limits, deadline) at all without dropping to engine.run(makeOrchestratorWorkflow(goal, opts), undefined, runOptions). Purely additive; existing calls are unchanged, and a call without runOptions still starts an UNCAPPED run, which the docs now state explicitly.

Patch Changes

  • Updated dependencies [8cc9a9c]
  • Updated dependencies [8cc9a9c]
  • Updated dependencies [8cc9a9c]

1.18.0

Patch Changes

1.17.0

Patch Changes

1.16.2

Patch Changes

1.16.1

Patch Changes

1.16.0

Patch Changes

1.15.0

Patch Changes

1.14.0

Patch Changes

1.13.0

Patch Changes

1.12.0

Patch Changes

1.11.0

Patch Changes

1.10.0

Patch Changes

1.9.0

Patch Changes

1.8.0

Minor Changes

  • 7884ec5: PlanRunner plan admission is now atomic with child dispatch admission (the v1.7.0 follow-up review's P1). Previously a plan_revise op could be journaled as admit (consuming its spawn unit) and only then have scheduleReady's dispatch rejected by the engine budget, stranding the node ready forever, losing the plan:revised event, and burning the orchestrator budget with no worker output.

    • An add_task op whose resolved profile estCost cannot fit the effective child ceiling (rung-resolved maxCostUsd, else budgetUsd) is bounced at rebase time with the new typed reason reserve_exceeds_budget naming the child account, requested and resolved reserve, ceiling, and minimum correction. No plan state changes and no spawn unit is consumed; the plan_revise tool result carries the reason verbatim.
    • The read-only admission branch now projects the SAME reserve the dispatch layer will commit (estimate clamped by the explicit child budget only), plus the pending reserves of earlier ops in the same revision, so every embedded admit of one batch is dispatchable under the snapshot it was decided on. The dynamic spawn_agent path passes the profile estimate into admission for the same reason.
    • Layer 1 (ctx.agent) clamps its committed reserve to the tightest child-allowance account headroom on the chain (a plan node's own sub-account, a ctx.workflow child ceiling): an allowance already bounds the child's lifetime spend, so an estimate above it clamps instead of denying, which is what makes "admit implies dispatchable" hold by construction. The run root and orchestrator cap are never clamped against; their headroom is shared money that projected admission keeps protecting.
    • plan:revised and termination:debit now emit strictly after the durable revision append and before the scheduling effects, so a scheduling fault cannot erase an applied revision from the event stream.
    • The residual class (facts that genuinely changed between admit and dispatch, e.g. the engine lifetime spawn cap) lands the node terminally failed through a journaled plan.decision with the new origin/cause dispatch-rejected; other ready nodes still dispatch and the run proceeds.

    Acceptance tests cover the review's live shape (profile estCost 0.015 against budgetUsd 0.01), the positive control, resume idempotence, the containment path, and an admit-implies-dispatchable property grid over estimates, budgets, ceilings, flat reserves, and prior commitments.

  • 52db30d: termination.init now freezes the ACTUAL orchestrator budget dollars instead of zeros, closing the journal-contract gap the v1.7.0 follow-up review found: the budgets guide documents orchestratorCapUsd and finalizeReserveUsd as frozen in the same limits vector as the counters, but PlanRunner journals stored 0 for both and only the later orchestrator_budget_reserve decision carried the real values.

    • The engine resolves the effective cap and finalize reserve strictly before extension boot and exposes them on OrchestratorExtensionIO (orchestratorCapUsd, finalizeReserveUsd); PlanRunner writes them into termination.init.
    • On resume the cap dollars are now recovered from the frozen orchestrator_budget_reserve decision instead of being re-derived from live options (DEF-2 config-drift-resume: the journal wins). A diverging live capUsd/capFraction/finalizeReserveUsd emits termination:config-drift and is never honored.
    • Journals recorded before this release (zeros in termination.init) replay unchanged: the fold reads the init entry by kind, and the reserve decision remains their authority.
    • The reserve-decision presence guard is now scoped to the orchestrate call, so nested capped orchestrations each journal their own freeze.

    The frozen cassette catalog is re-recorded (the init limits vector and its content key change); hashVersion stays 2, and the fixture lock refresh carries the required hashVersion-bump token.

Patch Changes

  • Updated dependencies [25724b5]
  • Updated dependencies [57ea1de]
  • Updated dependencies [7884ec5]
  • Updated dependencies [52db30d]

1.7.0

Patch Changes

  • Updated dependencies [45285aa]
  • Updated dependencies [2f20d1d]
  • Updated dependencies [22f65a8]
  • Updated dependencies [2ddfa29]
  • Updated dependencies [2abd9c2]
  • Updated dependencies [1c1175d]

1.6.0

Patch Changes

  • da4dbad: Write the product name as Rulvar in prose: package READMEs, npm descriptions, and the documentation site now capitalize the brand. Identifiers keep their exact casing, so package names, the rulvar binary, rulvar.config.mjs, the .rulvar store directory, the rulvar.* OTel attributes, and every URL are unchanged. Documentation and metadata only; no runtime behaviour changes.
  • Updated dependencies [da4dbad]
  • Updated dependencies [487da86]
  • Updated dependencies [df416fc]
  • Updated dependencies [a737810]
  • Updated dependencies [9eb66b4]

1.5.2

Patch Changes

1.5.1

Patch Changes

1.5.0

Patch Changes

1.4.0

Patch Changes

1.3.2

Patch Changes

  • ddef383: Every published package now ships a README, so its npm page states what the package is, how it installs, and where the documentation lives (npm includes README.md in the tarball regardless of the files allowlist, so no manifest changes are involved; @rulvar/compat gains its README on its own next release). Alongside, the repository-level pages are refreshed to the current project state: the root README is rewritten around the never-pay-twice pitch with a runnable quickstart condensation and the full package table, CONTRIBUTING.md lists the complete PR gate set, the examples README drops retired-spec citations for live docs.rulvar.com links and documents the dogfood journal replay, and the pointer README gets the same treatment.
  • Updated dependencies [ddef383]

1.3.1

Patch Changes

  • 7d1552e: Runtime message strings no longer cite the retired internal specification set: error and warning messages, validation issues, and the CLI help text drop the dangling docs/NN, section ... references, pointing at https://docs.rulvar.com pages where a pointer earns its place (the CLI help header, tool naming, toolset registries, bare resume). The umbrella package description sheds the naming-contingency note: the unscoped alias is published and owned. Three strings embedded in frozen recordings stay byte-identical on purpose (the no-progress abort reason and two testing-internal recorder strings), as does the byte-locked golden-fold fixture. Test-file comments lose their citations too; test titles are unchanged.
  • Updated dependencies [7d1552e]

1.3.0

Minor Changes

  • 7d1a287: ModelKnowledge phase 3, first slice (M12-T02, unlocked by the passed measured-value checkpoint): the kb_propose orchestrator tool and the quarantined modelObservations write path. PlanRunner registers kb_propose on explicit opt-in (PlanRunnerOptions.kbPropose, like any opt-in tool); its payload is tier-relative (the orchestrator never names a model) and the engine resolves the tier against the referenced lineage's declared ladder into the concrete KbProposal subject, validates that the tier has a journaled attempt and that evidence refs resolve to this run's decision entries, and journals the proposal as the observation_add ledger.op through the single-writer path. Quarantine is absolute: the ack is entryRef only, ledger_read withholds observation content behind a count (byte-stable for observation-free renders), worker prompts never see it, and nothing can commit during a run (the runtime handle has no write path by API shape); proposals reach the human gate only through the post-run LedgerExport. Core exports KbProposal, KbProposalTrigger and the typed model-free proposalStatement template. The kb-propose-quarantine cassette joins the frozen catalog (61 IDs).

Patch Changes

1.2.0

Patch Changes

  • 154507b: TSDoc and inline comments no longer cite the retired internal specification set (the pre-docs-site docs/NN, section ... references). The citations either became links to the public documentation at docs.rulvar.com or were dropped where the comment already carried the rule; traceability markers (DEF-n, XF-nn, FR-nnn, OQ-nn, W-nnn) are untouched. Comment-only change: no runtime behavior, no API shapes, and no runtime message strings were modified; the frozen golden-fold fixture is byte-identical.
  • Updated dependencies [3bfaec0]
  • Updated dependencies [890f42c]
  • Updated dependencies [154507b]

1.1.0

Patch Changes

1.0.0

Minor Changes

  • 0e0b569: M10 entry: the render budgets of docs/06 Appendix A are committed (the TBD-before-M10 rule) and wired as engine defaults; OQ-04 (the renderBudget measure) closes on the CHARACTER measure.

    • WakeDigest: 400 chars per outputSummary row, one exported constant (WAKE_SUMMARY_RENDER_BUDGET_CHARS) now serving both the distillation cap (adopted unchanged, the value frozen into every cassette since M6) and the digest render default of renderBudgetChars, which stays overridable per orchestration.
    • ledger_read render: 65536 chars over the serialized view via the new pure boundLedgerRender (exported with LEDGER_RENDER_BUDGET_CHARS): over budget, rows drop deterministically oldest-first (auto-derived joins before authored sections, the mission brief slices last) and every drop renders as a FLAGGED discrepancy line. The section caps stay the primary bound, so under default termination limits the belt never engages; all frozen fixtures are byte-identical.
    • KB card: 4096 chars, committed in docs and consumed by the M10-T03 card renderer.
  • 4454175: M10-T03: the ModelKnowledge read path (docs/05, sections "Read path" and "Security"). kb_pinned and kb_repinned land, the card renders, and the whole feature is store-gated: an engine without stores.modelKnowledge writes no kb entries at all, so every existing journal and cassette stays byte-stable (zero added awaits on the off path).

    • createEngine accepts stores.modelKnowledge; the runtime holds ONLY the current() handle (commit is physically absent inside runs).
    • One read at run admission for orchestrate-role runs: the engine filters claims (active, unexpired, reachable through the run's declared ladders after the role-floor filter) and journals kb_pinned { version, hash, cardText } with the card bytes EMBEDDED, strictly before the first orchestrator turn. Resume and replay read the entry bytes and never touch the live store.
    • A fresh kb_repinned lands on every wait_for_events wake under the same filtering rules against a FRESH store read, so expired, stale, and archived claims never steer spawns after pauses; a mid-run store commit affects only subsequent pins.
    • modelKnowledgeCard: deterministic, two-layer, tier-relative, 4096-char budget (oldest notes withhold behind an explicit marker). The verified layer compiles EXCLUSIVELY from eval-measured claims (empty in phase 1) with the one-rung clamp; editorial notes render dated and explicitly marked, never compiled into a tier; the orchestrator never sees model names. The card docks into the spawn tool description beside the profile card.
    • OQ-11 closes: editorial notes render for every taskClass with no self-description suppression (the nameless tier-relative render already blunts the feared bias).
    • Two catalog cassettes (docs/09, new section 6.11): kb-pin-replay and kb-repin-expiry, recorded offline over a deterministic stub store with time-stable dates; the cassette-catalog CI job runs them.
  • 6599ca8: M10-T05: the taskClass binding interim rule becomes the phase-1 resolution (docs/05, section "Phases and placement"; docs/14 OQ-12 CLOSED). The classification source is author declaration: the optional taskClass on AgentProfile, TaskSpec, and spawn_agent params; absence means unclassified and stores no literal string anywhere. Card recommendations never apply to unclassified spawns (in phase 1 no recommendation application exists at all; the M11 compiler inherits the rule as normative).

    • The plan dispatch now forwards the declared TaskSpec.taskClass onto the ExtensionDispatchSpec, completing the substrate: a declared class journals inside the spawn-admission decision (spawn_agent path) and the plan.revision spec of record (PlanRunner path), so M11 matrix sweeps and the recommendation compiler slice attempts by class from journals alone.
    • Byte-neutral: journals without declared classes are unchanged; floors stay profile-driven per docs/04.
  • 0fbe7ea: M9-T04 (part 1): the DEF-2 and DEF-3 catalog rows deferred at M7 (docs/09 sections 6.2 and 6.3; docs/10 M9 row "Complete catalog green in one CI run"), plus the producers and liveness fixes the rows exposed.

    • Nine new frozen cassettes with public runners and byte-for-byte replay tests: combined-loop-descent, config-drift-resume, class-storm-single-turn, oscillation-bounded, race-timeout-vs-live (DEF-2); respawn-preserves-counter, reworded-lessons-collide, stall-streak-classes-and-pinning, legacy-journal-resume (DEF-3). The class and race rows additionally round-trip their frozen bytes through BOTH reference stores (JsonlFileStore and SqliteStore) with identical loads, per the store-independence rule.
    • @rulvar/plan: the class-level escalation decision producer lands (docs/07 6.5): two or more same-kind reports resolved by ONE revision merge into ONE escalation-decision entry with per-lineage debits rows and resolvedBy 'class'; a denied per-lineage debit degrades the group to single-target decisions so denial semantics stay per report. The folds already consumed this form; single-target behavior and all existing cassette bytes are unchanged.
    • @rulvar/plan: termination:config-drift now actually fires on resume when a live termination knob diverges from the journaled termination.init (the journal wins, the divergence is reported per field; docs/07 11.2). Events are never journaled, so frozen cassettes are unaffected.
    • @rulvar/plan: a retry escalation decision re-opens the node AND clears its stale dispatch handle; previously the re-opened node sat ready forever while the scheduler skipped it (the re-dispatch liveness gap behind Flavor B defaultDecision retry).
    • @rulvar/plan: lesson_add keys once (docs/07 9.2): a repeated add with the same content key acks the recorded lesson instead of appending a duplicate; re-executed-turn recovery is unchanged.
    • @rulvar/core: an extension dispatch whose agent dies BEFORE its root entry lands now surfaces the underlying failure loudly to the dispatching caller instead of hanging the dispatch await forever (the pre-root cousin of the stale-writer liveness rule). Healthy paths and replays are byte- and timing-identical.
    • Known residual, unchanged: repeated Flavor B suspensions on ONE re-opened node dedup onto the first suspension's decision key; the recorded cassettes route around it and the at-cap immediate-resolution flavor rows stay with M9-T04's later parts.
  • ebe0abc: M9-T04 (part 2): the six DEF-5 catalog cassettes (docs/09 section 6.5; docs/03 section 9), plus the reuse-producer completions the rows forced.

    • Six new frozen cassettes with public runners and byte-for-byte replay tests: oscillation-full-reuse (escalated-terminal donor, shared full link, by-ref root, reclaimedUsdAtLink carries the donor spend), graft-partial-subtree (a three-rung limit ladder severed mid-top-rung grafts exclusively; the completed rung attempts forward-match through the scope alias and only the interrupted rung reruns live, exactly once), crash-between-link-and-root (cut strictly between the durable node.link and the by-ref root; the resume rolls forward with zero repayment), oscillation-guard-trip (the third re-add at maxOscillationsPerKey 2 rejects osc_guard with the embedded verdict and the run closes non-HITL), worktree-disposed-degrade (an unpinned worktree graft donor degrades to a fresh admit with DedupNote graft_unsafe; reuse_full stays allowed for a worktree donor with a terminal root), claim-exclusivity-and-chain (two identical adds in ONE revision: the first grafts exclusively, the second degrades donor_active; the severed grafted node becomes the chain head and the third add drains the chain transitively; oscillationCount reaches 2).
    • @rulvar/core (docs/03 9.3/9.6 producer completions, folds and bytes of existing journals unchanged): evaluateReuse now skips exclusively-claimed donors (first-wins) and degrades to a fresh admit with the documented donor_active reason when every candidate is captured; a severed grafted node inherits its captured link's chain (ancestry plus chain-tail graft eligibility), so the next add links to the chain head and drains transitively; agent dispatch roots record their resolved isolation (value.isolation, only when not 'none') so the DedupIndex worktree rules can read it from the journal.
    • @rulvar/plan: exclusive captures are first-wins WITHIN one revision too: the second identical add of the same revision degrades to donor_active instead of double-claiming the donor.
    • All fifteen M9 cassettes re-record byte-identically under the double-run agreement; the nine part-1 fixtures are untouched by the producer changes. fixtures.sha256 covers 50 frozen files.
  • a3079d0: M9-T04 (part 3): the six DEF-8 catalog cassettes plus the DEF-7 reserve-survives-run-exhaustion row (docs/09 sections 6.7 and 6.8), with the roll-forward and reserve producers the rows exposed.

    • Seven new frozen cassettes with public runners and byte-for-byte replay tests: revise-racing-defaultDecision (the mandatory stale-wake trio dropping dep_already_resolved with blockingRef, node_escalated, node_already_done in ONE revision), crash-after-append-before-effects (the pre-effects kill point; both children spawn live exactly once on resume and the request-only cancel lands on the redispatched branch), amend-vs-running-then-cancel-add, intra-revision-self-conflict (sequential intra-revision semantics), bad-base-streak-terminates (three fabricated-base all-dropped entries then the non-HITL guards fallback), park-races-child-completion (parkRequested extinguished by the child-result transition, no park retention), and reserve-survives-run-exhaustion (adds that would invade the committed finalize reserve drop admission_denied inside the revision outcomes; the forced finish executes FROM the reserve and closes the run ok).
    • @rulvar/plan: the idempotent plan_revise recovery path now also re-lands request-only cancels and parks by aborting the redispatched mid-flight branch; previously the crash-after-append-before-effects roll-forward left the cancelled branch running forever.
    • @rulvar/plan: an accepted escalation resolution records the node's done reference (doneRefs), so a later waive_dep against the resolved dependency drops dep_already_resolved with the blockingRef pointing at the resolving reference, exactly like a child-result transition.
    • @rulvar/core: the forced finish now RELEASES the finalize reserve as it begins (releaseFinalizeReserve): the reserve stops subtracting from the admission remainder at the moment it is being spent, or the finalize agent could never draw the money reserved for it under a tight run ceiling. Admissions stay frozen past the cap, so nothing else can take it. Cap behavior under unlimited ceilings (all existing cassettes) is byte-identical.
    • All 22 M9 cassettes re-record byte-identically under the double-run agreement; fixtures.sha256 covers 57 frozen files.

Patch Changes

  • Updated dependencies [0e0b569]
  • Updated dependencies [b28b7a3]
  • Updated dependencies [b53a89e]
  • Updated dependencies [4454175]
  • Updated dependencies [6599ca8]
  • Updated dependencies [6649e5f]
  • Updated dependencies [fd2f83b]
  • Updated dependencies [01d6b2d]
  • Updated dependencies [9a20dbb]
  • Updated dependencies [0fbe7ea]
  • Updated dependencies [ebe0abc]
  • Updated dependencies [a3079d0]
  • Updated dependencies [596a39b]
  • Updated dependencies [464ab6e]

0.9.0

Minor Changes

  • f920013: M8-T03: the multi-process seam soak and the queue-failover-during-forced-finish cassette (the DEF-7 final cassette; docs/09 sections 6.9 and 6.10; docs/10 section 3.9 exit criteria).

    • @rulvar/plan: the public runQueueFailoverDuringForcedFinish cassette runner: worker A loses its lease strictly between the cap decision and the final wake; worker B reclaims with a bumped fencing epoch and rolls the forced finish forward. The stale writer's appends are rejected and invisible, exactly one cap decision exists, finalization is paid once. The LeasableStore is injected (QueueFailoverDeps.makeStore) so the package stays core-only; the replay test and the record script supply the reference SqliteStore.
    • @rulvar/cli: the multi-process-fencing-soak harness: two workers over one SqliteStore file with kill/failover across the suspension, plan-revision, and forced-finish boundaries; every round asserts zero split-brain and zero double pay. Worker hardening: a failed renew now frees the concurrency slot immediately (a stale run whose landings all reject may never settle; fencing, not the stale process's cooperation, protects the journal).
    • Repo: cassettes/queue-failover-during-forced-finish.json recorded and frozen (double-run agreement; scripts/record-m8-cassettes.mjs); the queue-mode limitation stays documented (no distributed cross-process rate limiter, EXC-14/OQ-17).

Patch Changes

  • Updated dependencies [84f94d4]
  • Updated dependencies [65c7b2c]
  • Updated dependencies [a2a3243]
  • Updated dependencies [ebc8101]

0.8.0

Minor Changes

  • 85d55cf: The v0.8.0 BREAKING release notes (M7 adaptive orchestration full; the flagged BREAKING minor of the pre-1.0 convention, docs/12 registry).

    BREAKING: the unified AdmitVerdict union is extended with the reuse verdicts (reuse_full, admit_graft) and the new reject codes (termination_exhausted, ladder_exceeds_frozen, lineage_exhausted, lineage_busy, osc_guard) (DEF-5). How it fails: exhaustive switches over the verdict kind or reject code in custom shells and admission SPI extensions stop compiling. Migration: add branches for the new arms; reject-code switches should route unknown codes to their generic-denial path.

    BREAKING: reuse-by-reference is the DEFAULT (DEF-5). A byte-identical add_task after a cancel or abandon no longer re-executes the subtree: the result returns by reference (reuse_full) or continues from the paid prefix (admit_graft). How it fails: changed semantics; runs that relied on re-execution against a changed world observe referenced results instead. This is the only intentional change of visible semantics in the pre-1.0 line. Migration: set reuse.enabled: false on the admission config, or fresh: true on the specific add_task.

    BREAKING: the config key maxEscalationsPerNode is renamed to maxEscalationsPerLogicalTask (XF-10): escalations count per logical task across respawns via the lineage chain. How it fails: a typed ConfigError naming the new key rejects the old one. Migration: rename the key; the default stays 2.

    BREAKING: the plan-size-scaled revision budget option is removed without deprecation (DEF-2). maxRevisionsPerRun is an absolute, non-replenishable counter (default 32) debited by exactly 1 per journaled plan_revise; nothing increments it. How it fails: the removed option is rejected at config validation. Migration: size maxRevisionsPerRun directly.

    BREAKING: plan_revise result and error schemas widen (rebase outcomes, embedded admissions, revisionUnitsRemaining) and WakeDigest gains the MANDATORY termination field beside planHash, budget, and reuse (DEF-2/DEF-8). How it fails: schemaHash and toolsetHash of orchestrator scopes change, so VCR cassettes recorded over orchestrator turns invalidate. Migration: re-record affected cassettes; consumers of the digest type add the new mandatory blocks (all-zero outside PlanRunner).

    BREAKING: B0, the run budget ceiling, is immutable after start (DEF-2): no API, including HITL decisions, can top it up. How it fails: code that mutated the run budget mid-run or expected an HITL top-up hits a typed runtime error; overshoot stays bounded by one turn per in-flight agent. Migration: size the ceiling at start; use the orchestrator cap and the finalize reserve (DEF-7) for graceful degradation instead of top-ups.

    BREAKING: PlanRunner requires a resolvable orchestrator cap (DEF-7). orchestratePlanned with no run USD ceiling and no explicit budget.capUsd, or with effectiveCap < finalizeReserve, refuses to start with a typed OrchestratorCapConfigError before any LLM call. Migration: pass budget: { capUsd } (or run under a USD ceiling and rely on capFraction, default 0.2; up to 1.0 opts out explicitly with a telemetry warning).

  • 712a28e: M7-T01: the plan scope substrate. TaskPlan as engine-owned typed data (docs/07 3.1): PlanNode with the exact canonical field list, the closed PlanNodeStatus machine with immutable terminal statuses (done is immutable by construction) enforced by assertPlanTransition raising the typed PlanInvariantError; pure derivations depsSatisfied, recomputePlanReadiness (dependency satisfaction is derived in the fold, never a record), and wouldCreateDepCycle for the rewire_deps atomicity rule. planHash (docs/07 3.4): sha256 over the RFC 8785 canonical projection of PlanState through the frozen hashVersion 2 deriver, nodes sorted by NodeId, deps sorted in the hash, plus the guard fold counters revisionCount and droppedRevisionStreak; assertPlanHead raises PlanInvariantError on a fold-head mismatch; golden hashes are frozen in tests. PlanWriteLock (docs/07 3.2, XF-07): the in-process FIFO mutex serializing ONLY plan-scope appends, never a substitute for the ResolutionArbiter. The single sequential scope constant PLAN_SCOPE is 'plan'. The temporary M0_SCAFFOLD marker is removed now that the package's first real API has landed.

  • c8d88e7: M7-T04: plan.revision, plan.decision, and the committed rebase algorithm (DEF-8). task-spec.ts: the typed TaskSpec/TaskSpecPatch of docs/07 4.1 with promptSpecHashOf and patch application. plan-entries.ts: the two plan-mutating entry payloads (PlanRevisionValue with base/requestedOps/outcomes/assignedNodeIds/admissions/planHash chain/rationale plus the DEF-2 extensions; PlanDecisionValue with the closed EnginePlanOp set), content keys per docs/07 3.3 (rationale never keys), and THE single applier applyPlanEntry: replay consumes recorded outcomes (the APPLIED diff), never re-runs rebase, verifies the planHash chain under each entry's own hashVersion and raises the typed ReplayPlanHashMismatch at the exact entry; bad_base entries leave the hashed state byte-identical while lengthening the guard-side streak (effectiveDroppedStreak); terminal set_node_status transitions extinguish pending park/cancel flags and record doneRefs for waive blockingRef. rebase.ts: the committed algorithm (base validation against the recorded WakeDigest pair, conflicts evaluated ONLY against the fold head, sequential intra-revision application, per-op applied | transformed | dropped with the complete closed conflict table and reason codes, engine-computed cancel cascades excluding done, embedded add/unpark admissions, lineage-at-head checks, the DEF-5 dedup transform hook, and the DEF-7 plan_frozen row). Every row of the conflict table is exercised by the table-driven test matrix; the revise-racing-defaultDecision cassette shape asserts the exact dropped trio with blockingRef.

  • a41c20f: M7-T05: PlanRunner scheduling and toolset. Core gains the PUBLIC orchestrator extension seam (docs/02 section 4 seam-sufficiency: orchestration packages build exclusively from the public API): OrchestrateOptions.extension hosts an OrchestratorExtension with boot strictly before the orchestrator's first agent entry, extension tools appended to the mode (c) toolset, an activity hook running after every child settlement strictly before wake evaluation, quiescence participation (nothing running AND nothing ready), digest extras, wake observation, prompt lines, and an OrchestratorExtensionIO exposing total-order appends into extension-owned scopes, the journal snapshot, the single admission point, explicit-scope child dispatch through the ordinary ctx.agent path (plan/NodeId sub-accounts open beside the orchestrator account), settled lookups, cancel, ULID minting, and telemetry. outputSchemaRef/toolsetRef now RESOLVE against the new defaults.schemas and defaults.toolsets engine registries (unknown names stay typed tool errors); TerminationAccount.bindDeniedWriter binds I/O onto fold-rebuilt accounts. @rulvar/plan ships planRunner(options) and orchestratePlanned(engine, goal, opts): boot writes termination.init (frozen limits with kMax and the profile-registry snapshot hash) strictly before the first scheduling entry and binds the account into admission; plan_view renders the pinned pure fold (plan state, per-node LineageStats, the TerminationAccount snapshot) at the last delivered WakeDigest, with digestSeq 0 seeded as the empty-plan bootstrap snapshot; plan_revise (normative docs/07 4.7 schema) debits one revisionUnit per journaled revision (underflow writes termination.denied first), evaluates the committed rebase at the fold head, appends ONE plan.revision strictly before effects, schedules newly-ready nodes under plan/NodeId scopes, lands cancel requests, re-issues idempotently on re-executed turns (roll-forward), and emits plan:revised plus termination:debit; the engine (never the model) schedules ready nodes and journals ready-to-running and terminal transitions as plan.decision entries whose terminal transitions extinguish pending flags; quiescence completes (nothing running and nothing ready). The end-to-end revise-mid-run shape and a full crash-resume with zero live calls and no duplicate entries are covered by integration tests against the public engine API.

  • 51b062a: M7-T06: RevisionGuards, the oscillation detector, and hysteresis (docs/07 3.8). New guards.ts: the non-HITL terminating guard state machine whose every verdict is a journaled decision entry (decisionType 'guard-verdict') written strictly BEFORE its effects, with replay rebuilding state from journaled verdicts. The droppedRevisionStreak detector consumes effectiveDroppedStreak (the hashed counter plus trailing bad_base entries) and fires the configured fallback (reject-revision | finish-with-partial | fail-run; default finish-with-partial, droppedRevisionLimit default 3) exactly once: further plan_revise calls are rejected with a typed tool error instructing a finish with the partial result, and the fourth revision after a three-bad-base streak journals nothing and debits nothing. The oscillation detector keys on approachSigCoarse ACROSS LogicalTaskId boundaries: a re-add after a severing cancel counts one oscillation, the per-key limit (2, the Appendix A osc_guard default) freezes the signature with a journaled verdict plus a guard:oscillation event, and frozen re-adds reject at admission with the embedded osc_guard verdict (dropped admission_denied in the revision entry); guard counters are fed from the plan fold itself, identically live and on replay, so freeze thresholds never shift across a resume (fired-but-unjournaled verdicts roll forward at boot, deduplicated by content key). Stall detection emits stall:detected per (lineage, streak) with the hard per-run stall replan cap journaling its own verdict; hysteresis stays structural (park/cancel against running nodes land only as boundary flags, so nearly-done children are never killed mid-turn). plan_view now renders the guards block (engaged fallback, frozen signatures, stall replans used).

  • f4e70be: M7-T07: reuse-by-reference (DEF-5). Core: new journal/reuse.ts with the rich DonorRef (replacing the M6 seq placeholder inside the closed AdmitVerdict union), GraftBoot, DedupNote, ReuseConfig, NodeLinkValue and its content identity (nodeLinkKey over {kind, spawnKey, donorScope, targetNodeId}), the DedupIndex pure fold (severed roots become donor candidates when their pre-abandon effective status is not error, memoized failures excluded, exclusive claims resolve first-wins, plan-node scopes sweep their own branch payments, unpinned worktree donors degrade), evaluateReuse with the four-outcome verdict table (reuse_full | admit_graft | fresh-with-note | reject osc_guard at the link count), and the abandoned-spend ledger fold (abandonedUsd/reclaimedUsd/netLostUsd, per-key oscillation counts). The kernel matcher gains scope-prefix aliasing (docs/03 9.5): registerAlias merges donor-scope candidates into the target scope in journal order at every nested level, and the alias disposition bypasses the abandon overlay so donor entries regain their pre-abandon status ONLY through the alias (the standalone old scope stays skipped); a dangling donor root through the alias IS the graft frontier (rerun-dangling continues from the donor checkpoint). AbandonAttempt carries logicalTaskId (XF-04); the extension IO gains abandonBranch, registerAlias, and priceUsd. Plan: PlanRunner wires the DedupIndex at the fold head under the PlanWriteLock into the rebase dedup hook (transforms embed the verdict, the donor descriptor, and the placement into the revision entry), applies the per-SpawnKey osc_guard rejection, attaches DedupNotes to fresh admits, compiles applied cancel_task (and cancel-landed) into severing abandon entries with lineage attribution, lands node.link entries and by-ref roots in the mandatory write order with idempotent roll-forward, registers aliases (rebuilt by fold at boot), completes full-linked nodes by reference through an engine decision instead of a dispatch, debits a spawnUnit per reuse link, and renders the abandoned-spend view in plan_view (pinned) and the WakeDigest extras; PlanRunnerOptions.reuse carries the docs/03 9.9 config.

  • 75d1646: M7-T08: park and unpark. Core: the internal boot-checkpoint channel lets a FRESH dispatch boot from a retained transcript checkpoint (ExtensionDispatchSpec.bootCheckpointRef; dangling redispatch checkpoints take precedence), serving park/unpark continuation and the DEF-5 graft boot. Plan: new park.ts with the PinLedger fold (live pins counted from abandon entries carrying retainWorktree, park pinning and DEF-5 retention SHARE maxPinnedWorktrees, default 4), parkDispositionOf (checkpoints always retained; worktrees pinned only under capacity, overflow keeps the checkpoint but drops the tree), and unparkPlacementOf (continuation from the retained checkpoint; restart when no checkpoint exists or a worktree-isolated node lost its tree: silent resume against a fresh tree is impossible). PlanRunner lands parks at the turn boundary: a park-requested running child is aborted, the park-landed plan.decision transitions running to parked carrying the checkpoint anchor (set_node_status gains the optional checkpointRef field, applied by the fold), the branch is severed with retainCheckpoint plus retainWorktree per the pin disposition, the dispatch slot frees for the unpark, and node:parked emits. unpark_task applies with the embedded admission: a previously dispatched branch is a lineage rebirth (relation 'unpark-restart' continuing the node's LTID), while a never-started parked node resumes scheduling without consuming an attempt; the unparked dispatch boots from checkpointRefFor(runId, anchor) on the continuation path and restarts otherwise. The park-unpark integration test drives the full shape deterministically (one paid tool turn, park inside the second turn, unpark continuation whose booted history carries the paid turn) plus the pin-cap overflow and placement rows as units.

  • 5ed23d5: M7-T09: RunLedger (docs/07, section 9). New ledger.ts: the CLOSED authored op vocabulary (brief_set once per run, fact_add/fact_supersede, lesson_add keyed by (logicalTaskId, approachSig), observation_add), foldLedger as a pure fold of ledger.op entries joined to the journal task table (auto-derived revisionHistory, taskDigests, worldDelta; journal-vs-ledger contradictions render as flagged discrepancies, never as truth), single-writer discipline (foreign-scope ops ignored and flagged), Appendix A section caps (64 facts, 32 lessons, 16 observations) via ledgerCapViolation, compaction sufficiency via ledgerSufficiency, and the draft-versioned exportLedger (ledgerExportVersion: 'draft-1'). PlanRunner gains ledger_append and ledger_read: appends are journaled effect entries of kind ledger.op in the orchestrator scope with content-derived keys, idempotent on re-execution (a journaled op acks with the recorded ref and skips validation, so re-executed turns never spuriously reject); a lesson_add whose key matches no journaled attempt of that logical task rejects as a typed tool error; ledger_read is pinned to the delivered-wake seq exactly like plan_view, so a re-executed wake turn renders byte-identical ledger bytes and fold-global counters never enter the transcript.

  • 0627413: M7-T10: ModelLadder full (docs/07 section 10; docs/04 section 12; FR-119/FR-313). Core: ladders now RESOLVE through the chain (canonicalizeLadder validates the declaration once, FR-119 undeclared-judge-rung ConfigError included, and resolves every rung's effort explicitly; ladderRungChoice yields the concrete per-rung ModelChoice; a higher concrete layer shadows a lower ladder and vice versa; a ladder that WINS wire resolution stays a typed ConfigError since rung attempts always carry a concrete override). ladderLengthOf reads the normative declaration points (profile model: { ladder } or the loop-role routing entry). foldTermination debits the rung RESPAWN's embedded admission on raising ladder verdicts (docs/07 11.3 b). New per-engine mechanical gate registry defaults.gates (MechanicalGateProfile over AgentResult.artifacts). The extension seam gains io.random (journaled ctx.random for spot-checks), io.gates, and dispatch fields model (the concrete rung resolution entering the attempt's identity hash), memoizeOutcome, and inline schema for the engine-synthesized judge. Plan: new ladder.ts plus the PlanRunner ladder driver: rung attempts are ordinary agent scopes on the concrete rung model with rung caps binding (tier N+1 = new content key = one live attempt, all sharing the LTID via relation rung-retry registered from the raising verdict's nextAttempt); triggers classify typed (error, limit, schema-exhausted, no-progress first-class via the abort class, verify-failed from gates only); acceptance gates run per ok attempt in declaration order with journaled gate-verdict decisions (mechanical registry profiles, judge on a declared rung >= the executing rung or explicit override with a forced verdict schema and derived identity, spot-check selection strictly via the journaled draw); every ladder verdict is a decision entry computed once live and recovered by content key, so folds consume only journaled values; a denied respawn writes termination.denied strictly before the fallback lands; an ok attempt whose acceptance fails with no raise left lands failed, never done. Mid-flight resume redispatches running nodes through forward matching (dangling attempts continue, settled ones replay instantly): the half-escalated-ladder shape resumes without repaying completed rungs, proven by the truncated-journal test.

  • 55c0f87: M7-T11: EscalationProtocol completion (docs/07 section 6; DEF-2/3/4). Core: Flavor B now REQUIRES an explicit deadlineMs (the knob has no engine default per the frozen Appendix A row; a flavor B spawn without it is a typed ConfigError before any LLM call); SpawnRecord captures the dispatch's escalation flavor and the WakeDigest escalations block reports it (a flavor B report reaching the digest is already decided by the DEF-4 winner). Plan: new escalation.ts with the authoritative escalation-decision entry contract (decide-once per report by content key; countsAgainstLimit derived from the report kind, XF-06; the counting debit atomic with the append embedding escalationUnitsAfter; a DENIED debit writes termination.denied strictly before and flips the entry to capExceeded with countsAgainstLimit: false, so the cap yields the flagged decision plus the final report, never a bare limit, and the folds stay replay-strict). PlanRunner completes the decision flow: the cancel_task revision transform on an escalated node lands the verdict cancel decision, the resolve_escalation plan.decision (origin escalation-live), and the severing abandon strictly after the revision append; a settled Flavor B suspension's DEF-4 winner (timeout defaultDecision by timeout, a live decision, or a class fan-out) is absorbed into the authoritative entry (origins escalation-default/escalation-class) and the fate applies through the single applier (retry re-opens the node in place with the journaled amendedPrompt/startTier honored at re-dispatch, accept closes the paid partial result done, cancel closes cancelled, decompose leaves the node escalated while the proposed children enter through spawn_admitted ops with FRESH lineages and embedded admissions debiting spawn units through the decision entry).

  • fd33871: M7-T12: orchestrator cap and finalize reserve (DEF-7; docs/07 section 12). BREAKING for PlanRunner runs (v0.8.0 registry, docs/12): orchestratePlanned now REQUIRES a resolvable orchestrator cap; a run with no USD ceiling and no explicit budget.capUsd, or with effectiveCap < finalizeReserve, refuses to start with a typed OrchestratorCapConfigError BEFORE the first LLM call and before any journal entries (an uncapped orchestrator was precisely the defect; capFraction up to 1.0 opts out explicitly). effectiveCapUsd = min(capUsd, capFraction x runCeiling), default fraction 0.2. The engine writes ONE orchestrator_budget_reserve decision entry strictly after termination.init and strictly before the orchestrator's first agent entry, freezing the cap and the finalize reserve (explicit, or finalizeTurns x the deterministic per-turn estimate) in absolute dollars, recovered by content key on resume and never re-evaluated. The reserve registers on the orchestrator account AND the run root (kept separate from committedReserve; the admission block checks add it), so no spawn ever eats the finalization money. At the pre-wake soft boundary (orchSpent + turnEstimate > effectiveCap - finalizeReserve) the engine writes exactly ONE orchestrator_budget_cap decision strictly before any effects (an in-flight latch closes the wake-ordinal race): the plan freezes for adaptation but not for work (the rebase context frozen flag drops every op plan_frozen while admitted nodes run to completion), all wake triggers except quiescence disarm, and the orchestrator unwinds to the reserved FINAL wake: a fresh agent entry on the restricted single-finish toolset with a finalizeTurns limit, paid from the reserve; success yields outcome ok with forcedFinish marked in the CostReport. If the final finish fails, orchestrator_finalize_fallback journals and the engine SYNTHESIZES a deterministic partial result by pure fold with zero LLM calls; the run ends exhausted with the non-null partial (RunOutcome.value now survives exhaustion). Every digest carries the WakeBudgetBlock (run and orchestrator spend, cap, reserve, the epsilon-floored orchestrator share, softWarning at 0.8) with orchestrator:budget telemetry at each wake boundary and at the cap; CostReport.orchestrator populates spentUsd, wakes, forcedFinish, and reserveUsedUsd for H-OrchShare.

  • e70e7f4: M7-T13: the FINAL normative WakeDigest in ONE coordinated schema change (docs/07 section 5; XF-08/XF-12, inside the frozen hashVersion-2 identity rules). WakeDigest now declares every block first-class: digestSeq, planHash (emission-time plan hash, empty outside PlanRunner), coversToOrdinal, completedDigests ordered by spawn ordinal, escalations (with the Flavor B deadlineAt), the MANDATORY termination snapshot (DEF-2, contributed by the PlanRunner extension as a pure fold), the MANDATORY budget block (WakeBudgetBlock, DEF-7), and the reuse stats (the AbandonedSpendView shape, DEF-5). Runs without the PlanRunner extension ship all-zero blocks (emptyDigestBlocks), mirroring the CostReport convention. The digest render is bounded deterministically: the new renderBudgetChars option clamps each TaskDigest outputSummary by CHARACTERS (the model-independent interim measure; the tokenizer choice stays the docs/14 open question, the numeric default TBD before M10). Pinning semantics are unchanged: the digest is part of the wake snapshot and a re-executed turn reads identical bytes.

  • bc9c903: M7-T14: the M7 gating cassettes and the remaining metric wiring (docs/09 sections "Metrics" and "Mandatory defect cassette catalog"). Thirteen frozen cassettes record the round-2 set (revise-mid-run, crash-during-revision, park-unpark, oscillation-freeze, half-escalated-ladder, budget-denied-rung), the DEF-7 set minus queue-failover (cap-freeze-then-finish, crash-between-cap-and-effects, finalize-fallback-synthesized, escalation-storm-frozen), and representative DEF-2/DEF-3 rows (revision-exhaustion, rung-retry-lineage, decompose-mints-children), each double-run at record time and replayed byte-for-byte in CI through the new public @rulvar/plan cassette runners with deterministic journal normalization (ULIDs, content hashes, wall clock, spans, and refs collapse to first-appearance placeholders). Metric events: orchestrator:woke now carries planHash, coversToOrdinal, and renderSize (the deterministic character measure of the delivered digest, the wake-render-size metric); the escalated landing emits escalation:raised with the report kind, the lineage attribution, agentType (the escalation-rate slice), and costToDateUsd; the abandoned/reclaimed/netLost USD view rides every digest through the T13 reuse block and ledger:op plus spawn:* events already feed ledger-ops-per-spawn.

Patch Changes

  • Updated dependencies [85d55cf]
  • Updated dependencies [b88c9e3]
  • Updated dependencies [f3c4613]
  • Updated dependencies [a41c20f]
  • Updated dependencies [f4e70be]
  • Updated dependencies [75d1646]
  • Updated dependencies [0627413]
  • Updated dependencies [55c0f87]
  • Updated dependencies [fd33871]
  • Updated dependencies [e70e7f4]
  • Updated dependencies [bc9c903]

0.7.0

Patch Changes

  • Updated dependencies [fd1d06c]
  • Updated dependencies [6fcf296]
  • Updated dependencies [dcc97a9]
  • Updated dependencies [434dc83]
  • Updated dependencies [03173c1]
  • Updated dependencies [11c0afc]

0.6.0

Patch Changes

  • Updated dependencies [fa05007]
  • Updated dependencies [9234dc8]
  • Updated dependencies [644512c]
  • Updated dependencies [8a41656]
  • Updated dependencies [02f7f7a]

0.5.0

Patch Changes

  • Updated dependencies [ac274f4]
  • Updated dependencies [5735d92]
  • Updated dependencies [46ca98e]
  • Updated dependencies [8ae129e]
  • Updated dependencies [d1c4525]
  • Updated dependencies [b840aba]

0.4.0

Patch Changes

  • Updated dependencies [dfe03b5]
  • Updated dependencies [d2089a7]
  • Updated dependencies [3f60234]
  • Updated dependencies [f668890]
  • Updated dependencies [16d7aa6]
  • Updated dependencies [6513ce8]
  • Updated dependencies [7dad493]
  • Updated dependencies [2bbf180]

0.3.0

Patch Changes

  • Updated dependencies [43444f6]
  • Updated dependencies [279881b]
  • Updated dependencies [9fd0966]
  • Updated dependencies [24ebadf]
  • Updated dependencies [a1b35d3]
  • Updated dependencies [18a5821]

0.2.0

Patch Changes

  • Updated dependencies [c24228d]
  • Updated dependencies [c50871e]
  • Updated dependencies [1af8fb9]
  • Updated dependencies [1fe0249]
  • Updated dependencies [5c4fc32]

0.1.0

Minor Changes

  • f4e2be9: M0 repo bootstrap (v0.1.0, docs/10-implementation-plan.md section "M0"): monorepo scaffold on the committed toolchain (pnpm 11 workspaces with catalogs, TypeScript 6.0, tsdown, Vitest 4, ESLint 9 flat config, Turborepo 2, changesets fixed mode, npm trusted publishing), the docs/ canon as single source of truth, the L0 contracts skeleton in @rulvar/core, and the vendored dependencies (StandardSchemaV1/StandardJSONSchemaV1 types, the @cfworker/json-schema lineage validator subset, a first-party monotonic ULID). Placeholder scaffolds only: no public API ships in this release.

Patch Changes

@rulvar/planner

1.252.0

Patch Changes

  • Updated dependencies [3ccb6cf]
  • Updated dependencies [52d807f]
  • Updated dependencies [517ed00]
  • Updated dependencies [a7e589d]
  • Updated dependencies [76e95eb]

1.251.0

Patch Changes

  • Updated dependencies [e7e829c]
  • Updated dependencies [5982be8]
  • Updated dependencies [7c58fb2]
  • Updated dependencies [b3e465a]
  • Updated dependencies [c4e5d6a]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [0ae8b85]
  • Updated dependencies [7932936]
  • Updated dependencies [7932936]
  • Updated dependencies [88da0ed]
  • Updated dependencies [06c0e85]

1.250.0

Patch Changes

  • Updated dependencies [0e240b9]
  • Updated dependencies [6fe585e]
  • Updated dependencies [c5eb19c]
  • Updated dependencies [565c13b]
  • Updated dependencies [c6d197b]
  • Updated dependencies [c9d9729]
  • Updated dependencies [fed9db6]
  • Updated dependencies [df9ed76]
  • Updated dependencies [3020912]
  • Updated dependencies [d8d598d]

1.249.0

Patch Changes

  • Updated dependencies [8862133]
  • Updated dependencies [0d7a717]
  • Updated dependencies [e4428bd]
  • Updated dependencies [d6873c1]
  • Updated dependencies [4092e8d]
  • Updated dependencies [e086590]
  • Updated dependencies [1411938]
  • Updated dependencies [737d1ee]
  • Updated dependencies [634f966]
  • Updated dependencies [052cc26]
  • Updated dependencies [bbae134]

1.248.0

Patch Changes

1.247.0

Patch Changes

  • Updated dependencies [1933ecc]
  • Updated dependencies [db0a5f0]
  • Updated dependencies [b698726]
  • Updated dependencies [48348d2]
  • Updated dependencies [4cfa1cc]
  • Updated dependencies [4b7197a]
  • Updated dependencies [5ebc842]
  • Updated dependencies [16ff6b9]
  • Updated dependencies [0c9941d]

1.246.0

Patch Changes

  • Updated dependencies [d165b0c]
  • Updated dependencies [d59f4a0]
  • Updated dependencies [46907ac]
  • Updated dependencies [9929ad3]
  • Updated dependencies [1790a6a]

1.245.0

Patch Changes

  • Updated dependencies [b4d47a8]
  • Updated dependencies [dee6db4]
  • Updated dependencies [b85c113]
  • Updated dependencies [bc556e7]
  • Updated dependencies [9f11d29]
  • Updated dependencies [19bcea0]
  • Updated dependencies [60b461c]
  • Updated dependencies [61e3a1a]
  • Updated dependencies [a156b81]
  • Updated dependencies [0bd7045]

1.244.0

Patch Changes

  • Updated dependencies [38d839a]
  • Updated dependencies [ce13b0f]
  • Updated dependencies [4fa23e3]
  • Updated dependencies [6841c69]
  • Updated dependencies [f56721d]
  • Updated dependencies [c894a43]
  • Updated dependencies [f6944a3]
  • Updated dependencies [23fd0e0]

1.243.0

Patch Changes

1.242.0

Patch Changes

1.241.0

Patch Changes

  • Updated dependencies [dbcdd24]
  • Updated dependencies [7ae7243]
  • Updated dependencies [4f832c4]
  • Updated dependencies [7452d3d]
  • Updated dependencies [a4e22bf]
  • Updated dependencies [82df4af]

1.240.0

Patch Changes

1.239.0

Patch Changes

1.238.0

Patch Changes

  • Updated dependencies [cf00947]
  • Updated dependencies [c7b9382]
  • Updated dependencies [88aea96]
  • Updated dependencies [6da8d05]
  • Updated dependencies [eae5c4c]

1.237.0

Patch Changes

1.236.0

Patch Changes

1.235.0

Patch Changes

  • Updated dependencies [ba4e10d]
  • Updated dependencies [172402b]
  • Updated dependencies [2ecd787]
  • Updated dependencies [e20a5e9]
  • Updated dependencies [98c8691]
  • Updated dependencies [c70def0]

1.234.0

Patch Changes

1.233.0

Patch Changes

1.232.0

Patch Changes

  • Updated dependencies [1440410]
  • Updated dependencies [6e467f4]
  • Updated dependencies [0b14293]
  • Updated dependencies [e3bcab2]
  • Updated dependencies [b55a0f7]

1.231.0

Patch Changes

1.230.0

Patch Changes

1.229.0

Patch Changes

1.228.0

Patch Changes

1.227.0

Patch Changes

  • Updated dependencies [f262e9f]
  • Updated dependencies [f191ff7]
  • Updated dependencies [fbbfbe8]
  • Updated dependencies [263b5e8]
  • Updated dependencies [db4d56d]
  • Updated dependencies [41f93a9]
  • Updated dependencies [98c8ca9]

1.226.0

Patch Changes

1.225.0

Patch Changes

1.224.0

Patch Changes

1.223.0

Patch Changes

1.222.0

Patch Changes

1.221.0

Patch Changes

1.220.0

Patch Changes

1.219.0

Patch Changes

1.218.0

Patch Changes

1.217.0

Patch Changes

1.216.0

Patch Changes

1.215.0

Patch Changes

1.214.0

Patch Changes

1.213.0

Patch Changes

1.212.0

Patch Changes

1.211.0

Patch Changes

1.210.0

Patch Changes

1.209.0

Patch Changes

1.208.0

Patch Changes

1.207.0

Patch Changes

1.206.0

Patch Changes

1.205.0

Patch Changes

1.204.0

Patch Changes

1.203.0

Patch Changes

1.202.0

Patch Changes

1.201.0

Patch Changes

1.200.0

Patch Changes

1.199.0

Patch Changes

1.198.0

Patch Changes

1.197.0

Patch Changes

1.196.0

Patch Changes

1.195.0

Patch Changes

1.194.0

Patch Changes

1.193.0

Patch Changes

1.192.0

Patch Changes

1.191.0

Patch Changes

1.190.0

Patch Changes

1.189.0

Patch Changes

1.188.0

Patch Changes

1.187.0

Patch Changes

1.186.0

Patch Changes

1.185.0

Patch Changes

1.184.0

Patch Changes

1.183.0

Patch Changes

1.182.0

Patch Changes

1.181.0

Patch Changes

1.180.0

Patch Changes

1.179.0

Patch Changes

1.178.0

Minor Changes

  • e89f377: Package truth is now a gate, not a hope (RV1701). The eighteenth comparison benchmark's strongest documentation-class failure was package identity conflation: a due-diligence dossier described @rulvar/plan with a citation into packages/planner, and nothing mechanical objected. The docs cannot stop a reader's model from confusing two names, but they can refuse to ship a byte that gets the universe wrong themselves. Docs lint check 12 now enforces four layers against build artifacts rather than prose: every @rulvar/<name> token in every page must name a real workspace package; every import, require, export-from, and dynamic-import specifier in a ts/js fence must resolve to a real exports-map subpath of its package; every named root import in a fence must be a symbol the package's committed dts rollup actually exports, which turns import { planRunner } from '@rulvar/planner' into a lint failure instead of a shipped falsehood; and the versioning page's fixed-group list, its spelled-out size, and both package tables stay in set equality with .changeset/config.json and the manifests. The completeness layer had teeth on its first run: the installation guide's "full package list" had silently dropped @rulvar/store-postgres and @rulvar/executor; both rows are restored. The pointer narrative now tells the caret truth: a fresh install of rulvar@X resolves the newest umbrella release of X's major (X or newer, never older), so the bare name is a front door, not a pinning surface; pin @rulvar/rulvar exactly when you need one exact version. The CommonJS consumer path the installation guide documents is now proven on packed artifacts: the install smoke gains a .cjs consumer that require()s the umbrella and the pointer on the packed tarballs and asserts import() serves the same module instance. And the two npm descriptions disambiguate each other in both directions: @rulvar/plan replans during the run and names @rulvar/planner as the package it is not; @rulvar/planner plans before the run and names @rulvar/plan the same way.

Patch Changes

1.177.0

Patch Changes

1.176.0

Patch Changes

1.175.0

Patch Changes

1.174.0

Patch Changes

1.173.0

Patch Changes

1.172.0

Patch Changes

1.171.0

Patch Changes

1.170.0

Patch Changes

1.169.0

Patch Changes

1.168.0

Patch Changes

1.167.0

Patch Changes

1.166.0

Patch Changes

1.165.0

Patch Changes

1.164.0

Patch Changes

1.163.0

Patch Changes

1.162.0

Patch Changes

1.161.0

Patch Changes

1.160.0

Patch Changes

1.159.0

Patch Changes

1.158.0

Patch Changes

1.157.0

Patch Changes

1.156.0

Patch Changes

1.155.0

Patch Changes

1.154.0

Patch Changes

1.153.0

Patch Changes

1.152.0

Patch Changes

1.151.0

Patch Changes

1.150.0

Patch Changes

1.149.0

Patch Changes

1.148.0

Patch Changes

1.147.0

Patch Changes

1.146.0

Patch Changes

1.145.0

Patch Changes

1.144.0

Patch Changes

1.143.0

Patch Changes

1.142.0

Patch Changes

1.141.0

Patch Changes

1.140.0

Patch Changes

1.139.0

Patch Changes

1.138.0

Patch Changes

1.137.0

Patch Changes

1.136.0

Patch Changes

1.135.0

Patch Changes

1.134.0

Patch Changes

1.133.0

Patch Changes

1.132.0

Patch Changes

1.131.0

Patch Changes

1.130.0

Patch Changes

1.129.0

Patch Changes

1.128.0

Patch Changes

1.127.0

Patch Changes

1.126.0

Patch Changes

1.125.0

Patch Changes

1.124.0

Patch Changes

1.123.0

Patch Changes

1.122.0

Patch Changes

1.121.0

Patch Changes

1.120.0

Patch Changes

1.119.0

Patch Changes

1.118.0

Patch Changes

1.117.0

Patch Changes

1.116.0

Patch Changes

1.115.0

Patch Changes

1.114.0

Patch Changes

1.113.0

Patch Changes

1.112.0

Patch Changes

1.111.0

Patch Changes

1.110.0

Patch Changes

1.109.0

Patch Changes

1.108.0

Patch Changes

1.107.0

Patch Changes

1.106.0

Patch Changes

1.105.0

Patch Changes

1.104.0

Patch Changes

1.103.0

Patch Changes

1.102.0

Patch Changes

1.101.0

Patch Changes

1.100.0

Patch Changes

1.99.1

Patch Changes

1.99.0

Patch Changes

1.98.0

Patch Changes

1.97.0

Patch Changes

1.96.0

Patch Changes

1.95.0

Patch Changes

1.94.0

Patch Changes

1.93.0

Patch Changes

1.92.0

Patch Changes

1.91.0

Patch Changes

1.90.0

Patch Changes

1.89.0

Patch Changes

1.88.0

Patch Changes

1.87.0

Patch Changes

1.86.0

Patch Changes

1.85.0

Minor Changes

  • 6932a9f: Three fail-closed fixes from the cycle 83 sweep, plus the dependency refresh.

    Engine. A typed error thrown out of ProviderAdapter.stream() now keeps its own class instead of being laundered into a retryable transport fault. A ConfigError (a bridged model id that does not match the wrapped model, an unsupported role, a namespaced option contradicting a canonical field) used to be retried through the whole backoff ladder and then trigger transport failover, so a misconfigured primary silently served the run from a fallback model the caller never asked for while the real fault vanished behind a generic message. Typed errors that ARE retryable by class (a lost lease) keep retrying exactly as before, and an untyped throw is still a retryable transport fault.

    Planner sandbox. The realm scrub replaced Date.now and Math.random, which left three ambient sources open: a bare new Date() never consults Date.now (V8 reads the system clock directly), performance.now() is a second live clock, and WebCrypto (crypto.randomUUID(), crypto.getRandomValues()) is raw entropy. Those are the first idioms a machine-written script reaches for, and each silently produced a run that could not reproduce on replay. All of them now draw from the same seeded stream: zero-argument new Date() and Date() take the logical clock, performance.now() is that clock minus the segment base, crypto.randomUUID() is the journaled uuid shim, and crypto.getRandomValues() fills from the seed. Passing a timestamp or a date string to Date stays a pure conversion.

    Server. A tracked run whose segment REJECTS instead of settling (the genesis ownership boot refusing a run another process owns, a withheld settlement whose durable write failed) was reported as running for the life of the process, its SSE connections never closed, and neither retention nor the settled cap could release it. GET /runs/:id now answers status: "error" with the typed wire error, connected streams close with a comment naming the failure, a late subscriber gets that comment instead of an empty stream, and the tracked run becomes eligible for retention like any other terminal run.

    Dependencies. @anthropic-ai/sdk moves to ^0.115.0 (the only shipped floor its caret was blocking); in-range minors refresh across the workspace. The four majors stay held: eslint 10 and @eslint/js 10, @types/node 26 against the Node 22.12 floor, and TypeScript 7. The tsdown resolution is pinned at 0.22.3 because it generates the frozen .d.ts artifacts, including the published @rulvar/compat tarball that must repack byte identical.

Patch Changes

1.84.0

Patch Changes

1.83.0

Patch Changes

1.82.0

Patch Changes

1.81.2

Patch Changes

1.81.1

Patch Changes

1.81.0

Patch Changes

1.80.0

Patch Changes

1.79.0

Patch Changes

1.78.0

Patch Changes

1.77.0

Patch Changes

1.76.0

Patch Changes

1.75.1

Patch Changes

1.75.0

Patch Changes

1.74.0

Patch Changes

1.73.0

Patch Changes

1.72.0

Patch Changes

1.71.0

Patch Changes

1.70.1

Patch Changes

1.70.0

Patch Changes

1.69.0

Patch Changes

1.68.0

Patch Changes

1.67.0

Patch Changes

1.66.0

Patch Changes

1.65.0

Patch Changes

1.64.0

Patch Changes

1.63.0

Patch Changes

1.62.0

Patch Changes

1.61.0

Patch Changes

1.60.0

Patch Changes

1.59.4

Patch Changes

1.59.3

Patch Changes

1.59.2

Patch Changes

1.59.1

Patch Changes

1.59.0

Patch Changes

1.58.0

Patch Changes

1.57.0

Patch Changes

1.56.0

Patch Changes

1.55.0

Patch Changes

1.54.0

Patch Changes

1.53.0

Patch Changes

1.52.0

Patch Changes

1.51.0

Patch Changes

1.50.0

Patch Changes

1.49.0

Patch Changes

1.48.0

Patch Changes

1.47.0

Patch Changes

1.46.0

Patch Changes

1.45.0

Patch Changes

1.44.1

Patch Changes

1.44.0

Patch Changes

1.43.0

Patch Changes

1.42.0

Patch Changes

1.41.0

Patch Changes

1.40.0

Patch Changes

1.39.0

Minor Changes

  • 0cff035: Close the dynamic code generation parity gap in the planner sandbox dialect (v1.38.0 review P2-CODEGEN-PARITY).

    compileScript and the rulvar/no-code-generation ESLint rule now share one AST policy (scanDialect), so both reach the same decision for every statically visible constructor reconstruction form: .constructor, ["constructor"], a computed key that folds to the constant, { constructor: x } destructuring, and Reflect.get(fn, "constructor"). The previous regex compile gate matched only the dotted form, so a bracket or computed key passed compile while the linter flagged some of them; moving to an AST also drops the regex false positives, where a property merely named eval, Function, or constructor was wrongly rejected.

    A key assembled only at runtime (fn[parts.join("")]) cannot be decided statically without rejecting every dynamic property access, so the worker realm now neutralizes the constructor reconstruction path at runtime by replacing the constructor slot on all four Function family prototypes with a thrower. A script that compiles clean can no longer reach the Function constructor through a dynamic key.

    The planner and orchestration docs are corrected to state the exact boundary: the dialect rejects the statically visible forms and the worker neutralizes the runtime path, but a worker in the same process shares its intrinsics with the code it runs and remains a determinism and blast radius boundary, not a hostile code wall.

Patch Changes

1.38.0

Minor Changes

  • 3e2d591: Reject dynamic code generation in the planner sandbox dialect (v1.37.0 review SEC-P2). compileScript banned import but not eval, the Function constructor, or .constructor access, so a machine script could reach the Function constructor and compile a dynamic import the literal scan never saw, recovering the import allowlist and, through node:child_process, arbitrary host capability at run status ok. compileScript now rejects eval, Function, and .constructor (diagnostic ids no-eval, no-function-constructor, no-constructor-access); a new rulvar/no-code-generation ESLint rule carries the same ban into the workflows preset and the self repair loop; and the worker additionally unbinds eval and Function as defense in depth. This keeps the import allowlist meaningful and the dialect consistent. It is not a hostile code boundary, which the sandbox has never claimed to be: JavaScript intrinsics can still reconstruct the constructors, so the docs continue to call the sandbox a determinism and blast radius boundary, not a security one.

Patch Changes

1.37.0

Patch Changes

1.36.0

Minor Changes

  • 101795b: Validate PlanOptions.repairRounds as a nonnegative integer before the runId derivation, the store lookup, and any provider dispatch (v1.35.0 review P2). Unvalidated, NaN produced zero drafts with an after NaN drafts rejection, a fraction over ran by a draft, and Infinity turned the self repair limiter into an unbounded paid loop.

Patch Changes

1.35.0

Minor Changes

  • d4ac3bf: Validate WorkerSandboxRunner resource ceilings at construction (v1.34.0 review P2-2, P2-3). timeoutMs must be an integer between 1 and 2147483647 ms, the Node timer maximum: a larger value used to clamp to a 1 ms timer and kill a trivial worker immediately with sandbox_limit. memoryMb must be a positive integer. Anything else, NaN included, is a typed ConfigError before any worker exists.

Patch Changes

1.34.0

Patch Changes

1.33.0

Patch Changes

1.32.0

Patch Changes

1.31.0

Patch Changes

1.30.0

Patch Changes

1.29.0

Patch Changes

1.28.0

Patch Changes

1.27.0

Patch Changes

1.26.0

Patch Changes

  • a4fc757: SqliteStore implements the exact lookup capability (getMeta as a primary key query) and narrows status, statuses, and name in SQL over the JSON payload behind new expression indexes (created idempotently, so existing database files gain them on the next open), so a selective listRuns reads only the matching rows instead of decoding the whole catalog; the tags containment check stays in JS over the reduced set with unchanged semantics. The conformance kit checks the genesis round trip, that a statuses filter never drops a matching meta (supersets stay allowed), and that a store exposing getMeta agrees with listRuns and resolves undefined for a missing run. The planner's deterministic plan lookup reads one meta through the capability instead of scanning the catalog.
  • Updated dependencies [a4fc757]

1.25.0

Minor Changes

  • 74851ed: WorkerSandboxRunner now launches its worker with an explicit execArgv (default []) instead of inheriting the host's process.execArgv. Host-only launch flags used to reach the file-entry worker and kill a correct compiled workflow before its first sandbox operation: --input-type=module (present whenever the host itself runs as ESM from stdin or --eval) is rejected for file entries, and an inherited --eval carried the host's whole source text into the worker's options. The same compiled workflow now behaves identically whether the host runs from a file, from stdin, or via --eval. Hosts that need loader, coverage, or instrumentation flags inside the worker opt in through the new WorkerSandboxRunnerOptions.execArgv, which is passed to the worker verbatim.

Patch Changes

1.24.1

Patch Changes

1.24.0

Minor Changes

  • 2b033e8: Fix the API card's semantic contract for tools, model, and routing (the v1.23.0 review P2-1 and P2-2). The card now teaches that string entries of tools are registered TOOLSET names (exactly the set the profile card prints), never agent profile names, matching the runtime resolver that rejects unknown names with a typed ConfigError before any provider call. The model and routing bullets now say to normally omit both: the host's profiles and routing decide models, the profile card never names any (model secrecy is a design invariant), and the escape hatch is explicitly conditioned on the goal text itself supplying allowed refs; the false phrase "a model ref from the profile card" is gone. A ConfigError now also stays typed (code: 'config') across the sandbox worker boundary instead of degrading to a generic error, so a compiled script that misuses a profile name in tools settles with the typed pre-call outcome and zero provider calls.

    The card text is an identity input of plan operations, so the frozen planner cassettes are re-recorded under the hashVersion-bump token ceremony (the derivation itself is unchanged; CURRENT_HASH_VERSION stays 2).

Patch Changes

1.23.0

Minor Changes

  • 1f9c272: The API card now tells the planner the truth about identical calls and the complete sanctioned option set (v1.22.0 review P2-4). The card claimed identical calls "journal as ONE result"; the ordinal semantics have always been the opposite: every call journals as its own operation, identical calls share a content key but take sequential ordinals, and repeats always run. The card now states exactly that, plus why a distinguishing key still matters (it binds each result to its call by identity instead of position across script edits). The agent opts line is now GENERATED from the runtime allowlist (SANDBOX_AGENT_OPT_KEYS, newly exported from @rulvar/core), which also surfaces the three options the hand-maintained list had silently dropped: routing, memoizeOutcome, and replay, each with a one-line explanation the model can act on. A parity test pins the card to the runtime allowlist in both directions.

    Identity note (hashVersion-bump ceremony): the card text is an input of the planner operation's content key, so the frozen planner-self-repair cassette is re-recorded under the new prompt bytes. The key DERIVATION and CURRENT_HASH_VERSION are unchanged; committed journals recorded under the old card replay byte-exact, and only a fresh plan() call sees the new prompt identity.

Patch Changes

1.22.0

Patch Changes

1.21.0

Patch Changes

1.20.0

Patch Changes

1.19.0

Patch Changes

1.18.0

Patch Changes

1.17.0

Patch Changes

1.16.2

Patch Changes

1.16.1

Patch Changes

1.16.0

Patch Changes

1.15.0

Patch Changes

1.14.0

Patch Changes

1.13.0

Patch Changes

  • c28c4c0: Export RunPlannedOptions from the package barrel (v1.12 follow-up review, P2). The interface appears in the public runPlanned signature but was missing from the explicit type export list of index.ts, so a named type import from @rulvar/planner failed with TS2459 and the generated API docs rendered the name as unlinked text with no interface page. Runtime behavior is unchanged. The docs build now escalates any TypeDoc referenced-but-not-included warning outside a frozen baseline of pre-existing internal helper types, so a public type missing from its barrel fails CI instead of shipping.

1.12.0

Minor Changes

  • 46edcc0: Budget-safe planner APIs (v1.11 follow-up review, P2). PlanOptions.run carries run options for the planning conversation itself (budgetUsd, limits, deadlineAt, signal; the runId stays goal-derived and is not overridable): they apply at GENESIS, where budgetUsd freezes as the planning run's immutable ceiling B0, recorded in RunMeta. A later plan() of the same goal resumes the existing journal under its RECORDED ceiling: a differing explicit budgetUsd emits a RULVAR_PLAN_BUDGET_DRIFT warning and never tops up or replaces the frozen value. When the ceiling cannot fit the next draft, plan() throws ScriptRejected whose data carries status: 'exhausted' and the typed budget_exhausted error, with zero over-ceiling provider calls and the planning journal intact. runPlanned(engine, goal, args, options) gains RunPlannedOptions { plan?, run? }: plan bounds (and fully parameterizes) the planning leg, run is passed to engine.run verbatim as the execution leg's own independent RunOptions. Existing calls stay source-compatible; the bare forms without options remain UNBOUNDED and are now documented as such, with the bounded form shown first in the planner and orchestration-modes guides.

Patch Changes

1.11.0

Patch Changes

1.10.0

Patch Changes

1.9.0

Patch Changes

1.8.0

Patch Changes

1.7.0

Patch Changes

  • Updated dependencies [45285aa]
  • Updated dependencies [2f20d1d]
  • Updated dependencies [22f65a8]
  • Updated dependencies [2ddfa29]
  • Updated dependencies [2abd9c2]
  • Updated dependencies [1c1175d]

1.6.0

Patch Changes

  • da4dbad: Write the product name as Rulvar in prose: package READMEs, npm descriptions, and the documentation site now capitalize the brand. Identifiers keep their exact casing, so package names, the rulvar binary, rulvar.config.mjs, the .rulvar store directory, the rulvar.* OTel attributes, and every URL are unchanged. Documentation and metadata only; no runtime behaviour changes.
  • Updated dependencies [da4dbad]
  • Updated dependencies [487da86]
  • Updated dependencies [df416fc]
  • Updated dependencies [a737810]
  • Updated dependencies [9eb66b4]

1.5.2

Patch Changes

1.5.1

Patch Changes

1.5.0

Patch Changes

1.4.0

Patch Changes

1.3.2

Patch Changes

  • ddef383: Every published package now ships a README, so its npm page states what the package is, how it installs, and where the documentation lives (npm includes README.md in the tarball regardless of the files allowlist, so no manifest changes are involved; @rulvar/compat gains its README on its own next release). Alongside, the repository-level pages are refreshed to the current project state: the root README is rewritten around the never-pay-twice pitch with a runnable quickstart condensation and the full package table, CONTRIBUTING.md lists the complete PR gate set, the examples README drops retired-spec citations for live docs.rulvar.com links and documents the dogfood journal replay, and the pointer README gets the same treatment.
  • Updated dependencies [ddef383]

1.3.1

Patch Changes

  • 7d1552e: Runtime message strings no longer cite the retired internal specification set: error and warning messages, validation issues, and the CLI help text drop the dangling docs/NN, section ... references, pointing at https://docs.rulvar.com pages where a pointer earns its place (the CLI help header, tool naming, toolset registries, bare resume). The umbrella package description sheds the naming-contingency note: the unscoped alias is published and owned. Three strings embedded in frozen recordings stay byte-identical on purpose (the no-progress abort reason and two testing-internal recorder strings), as does the byte-locked golden-fold fixture. Test-file comments lose their citations too; test titles are unchanged.
  • Updated dependencies [7d1552e]

1.3.0

Patch Changes

1.2.0

Patch Changes

  • 154507b: TSDoc and inline comments no longer cite the retired internal specification set (the pre-docs-site docs/NN, section ... references). The citations either became links to the public documentation at docs.rulvar.com or were dropped where the comment already carried the rule; traceability markers (DEF-n, XF-nn, FR-nnn, OQ-nn, W-nnn) are untouched. Comment-only change: no runtime behavior, no API shapes, and no runtime message strings were modified; the frozen golden-fold fixture is byte-identical.
  • Updated dependencies [3bfaec0]
  • Updated dependencies [890f42c]
  • Updated dependencies [154507b]

1.1.0

Patch Changes

1.0.0

Patch Changes

  • Updated dependencies [0e0b569]
  • Updated dependencies [b28b7a3]
  • Updated dependencies [b53a89e]
  • Updated dependencies [4454175]
  • Updated dependencies [6599ca8]
  • Updated dependencies [6649e5f]
  • Updated dependencies [fd2f83b]
  • Updated dependencies [01d6b2d]
  • Updated dependencies [9a20dbb]
  • Updated dependencies [0fbe7ea]
  • Updated dependencies [ebe0abc]
  • Updated dependencies [a3079d0]
  • Updated dependencies [596a39b]
  • Updated dependencies [464ab6e]

0.9.0

Patch Changes

0.8.0

Patch Changes

  • Updated dependencies [85d55cf]
  • Updated dependencies [b88c9e3]
  • Updated dependencies [f3c4613]
  • Updated dependencies [a41c20f]
  • Updated dependencies [f4e70be]
  • Updated dependencies [75d1646]
  • Updated dependencies [0627413]
  • Updated dependencies [55c0f87]
  • Updated dependencies [fd33871]
  • Updated dependencies [e70e7f4]
  • Updated dependencies [bc9c903]

0.7.0

Minor Changes

  • dc1c182: M6-T01: compileScript and the CompiledWorkflow surface. compileScript(source, { allowImports }) validates planner-generated source (syntax over the exact sandbox global set; import/require/export scanning with a literal-specifier allowlist defaulting to none) and compiles it into the core CompiledWorkflow data form (errorPolicy 'lenient'); any violation throws the typed ScriptRejected carrying machine-readable ScriptDiagnostic[] for the plan() self-repair loop. Exports SANDBOX_GLOBALS (the docs/06 8.2 curated list) and scriptDiagnosticsOf. The closure Workflow and CompiledWorkflow forms stay mutually unassignable by type.
  • fd1d06c: M6-T02: WorkerSandboxRunner and the sandbox contract. @rulvar/planner gains WorkerSandboxRunner (accepts CompiledWorkflow ONLY; worker_threads with the exact curated 12-global scope; timeoutMs 300000 / memoryMb 512 breaches terminate the worker with the new typed SandboxError, code sandbox_limit). Core gains the public host half, createSandboxBridge: proxied primitives (agent, step, workflow, awaitExternal, parallel, pipeline, phase, budget) served against the canonical run ctx with worker thunks executing under host-allocated scope tokens; the worker's SYNC seeded now/random/uuid (and the Date.now/Math.random replacements) mirror-journal as ordinary kind rand entries with match-first resume semantics; a busy-state protocol keeps suspension and quiescence behavior identical to in-process runs. createEngine gains runners.sandbox; engine.run/engine.resume accept CompiledWorkflow, persist the source blob plus workflowSourceRef/workflowHash at start, and resume(runId) with no workflow rehydrates the hash-pinned source (a differing supplied source is a typed ConfigError). New FileTranscriptStore makes compiled runs resumable across processes. The sandbox dialect exposes async budget.spent()/remaining(); import/fetch/process are absent from the worker scope.
  • 6fcf296: M6-T04: profileCard and the API card. Core gains profileCard(profiles): the one agent vocabulary both orchestration modes speak, feeding the planner prompt (mode b) and spawn_agent agentType guidance (mode c) with IDENTICAL text; pure function of the registry, sorted, byte-stable, rendering only model-agnostic fields (name, description, tool names, taskClass, estCost, escalation opt-in; models are never named). The planner gains apiCard(): the byte-stable card teaching exactly the curated 12-global sandbox dialect (schema literals only, tools by profile name, onError throw|null, async budget, no imports, the opts.key repeat rule) with usage patterns distilled from the examples corpus.
  • dcc97a9: M6-T05: the plan agent and the self-repair loop (mode b). plan(engine, goal, { model?, profiles?, repairRounds? }) asks a planner model under role plan to write a script against the API card plus the engine's profile card, lints it (eslint-plugin-rulvar preset + compileScript), self-repairs up to repairRounds (default 3) from the machine-readable JSON diagnostics, and returns { source, workflow, lint }. The planner conversation is an ordinary journaled run with a goal-derived deterministic runId, so re-planning the same goal replays the unchanged prefix free; exhausting the rounds throws a typed ScriptRejected carrying the last diagnostics. runPlanned(engine, goal, args?) composes plan-then-sandbox-run (async by amendment). Core gains AgentOpts.role ('loop' | 'plan' | 'orchestrate', the primary invocation role threading through resolution, effort defaults, floors, cost buckets, and events) and the narrow Engine.profileCard(names?) accessor rendering the registered profiles through the public API.
  • 10b45f1: M6-T11: the rulvar plan command and the M6 gating cassettes. rulvar plan "<goal>" [--dry-run] (the canonical grammar) loads @rulvar/planner DYNAMICALLY (the CLI's static dependency stays @rulvar/core; a missing install is a clear error), plans against the host-config engine, prints the accepted script plus its advisory diagnostics, and runs it in the worker sandbox unless --dry-run. The three docs/09 6.10 gating cassettes are recorded on the FakeAdapter and committed under the frozen-fixture lock with exported scenario builders shared by the recorder script and the replay tests: sandbox-determinism (two fresh runs of one CompiledWorkflow produce byte-identical normalized journals matching the cassette), planner-self-repair (the failing draft round-trips through the JSON-diagnostics repair, re-planning from the committed journal is free, and the accepted script executes deterministically in the sandbox), and orchestrator-crash-resume (the committed pre-crash journal plus boundary checkpoints resume with zero re-paid spawns, no duplicate spawn decisions, and byte-stable handles).

Patch Changes

  • Updated dependencies [fd1d06c]
  • Updated dependencies [4aaf2d5]
  • Updated dependencies [6fcf296]
  • Updated dependencies [dcc97a9]
  • Updated dependencies [434dc83]
  • Updated dependencies [03173c1]
  • Updated dependencies [11c0afc]

0.6.0

Patch Changes

  • Updated dependencies [fa05007]
  • Updated dependencies [9234dc8]
  • Updated dependencies [644512c]
  • Updated dependencies [8a41656]
  • Updated dependencies [02f7f7a]

0.5.0

Patch Changes

  • Updated dependencies [ac274f4]
  • Updated dependencies [5735d92]
  • Updated dependencies [46ca98e]
  • Updated dependencies [8ae129e]
  • Updated dependencies [d1c4525]
  • Updated dependencies [b840aba]

0.4.0

Patch Changes

  • Updated dependencies [dfe03b5]
  • Updated dependencies [d2089a7]
  • Updated dependencies [3f60234]
  • Updated dependencies [f668890]
  • Updated dependencies [16d7aa6]
  • Updated dependencies [6513ce8]
  • Updated dependencies [7dad493]
  • Updated dependencies [2bbf180]

0.3.0

Patch Changes

  • Updated dependencies [43444f6]
  • Updated dependencies [279881b]
  • Updated dependencies [9fd0966]
  • Updated dependencies [24ebadf]
  • Updated dependencies [a1b35d3]
  • Updated dependencies [18a5821]

0.2.0

Patch Changes

  • Updated dependencies [c24228d]
  • Updated dependencies [c50871e]
  • Updated dependencies [1af8fb9]
  • Updated dependencies [1fe0249]
  • Updated dependencies [5c4fc32]

0.1.0

Minor Changes

  • f4e2be9: M0 repo bootstrap (v0.1.0, docs/10-implementation-plan.md section "M0"): monorepo scaffold on the committed toolchain (pnpm 11 workspaces with catalogs, TypeScript 6.0, tsdown, Vitest 4, ESLint 9 flat config, Turborepo 2, changesets fixed mode, npm trusted publishing), the docs/ canon as single source of truth, the L0 contracts skeleton in @rulvar/core, and the vendored dependencies (StandardSchemaV1/StandardJSONSchemaV1 types, the @cfworker/json-schema lineage validator subset, a first-party monotonic ULID). Placeholder scaffolds only: no public API ships in this release.

Patch Changes

@rulvar/rulvar

1.252.0

Patch Changes

1.251.0

Patch Changes

  • Updated dependencies [e7e829c]
  • Updated dependencies [5982be8]
  • Updated dependencies [7c58fb2]
  • Updated dependencies [b3e465a]
  • Updated dependencies [c4e5d6a]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [0ae8b85]
  • Updated dependencies [7932936]
  • Updated dependencies [7932936]
  • Updated dependencies [88da0ed]
  • Updated dependencies [06c0e85]

1.250.0

Patch Changes

  • Updated dependencies [0e240b9]
  • Updated dependencies [6fe585e]
  • Updated dependencies [c5eb19c]
  • Updated dependencies [565c13b]
  • Updated dependencies [c6d197b]
  • Updated dependencies [c9d9729]
  • Updated dependencies [fed9db6]
  • Updated dependencies [df9ed76]
  • Updated dependencies [3020912]
  • Updated dependencies [d8d598d]

1.249.0

Patch Changes

  • Updated dependencies [8862133]
  • Updated dependencies [0d7a717]
  • Updated dependencies [e4428bd]
  • Updated dependencies [d6873c1]
  • Updated dependencies [4092e8d]
  • Updated dependencies [e086590]
  • Updated dependencies [1411938]
  • Updated dependencies [737d1ee]
  • Updated dependencies [634f966]
  • Updated dependencies [052cc26]
  • Updated dependencies [bbae134]
  • Updated dependencies [67a8d72]

1.248.0

Patch Changes

1.247.0

Patch Changes

  • Updated dependencies [1933ecc]
  • Updated dependencies [db0a5f0]
  • Updated dependencies [b698726]
  • Updated dependencies [48348d2]
  • Updated dependencies [4cfa1cc]
  • Updated dependencies [4b7197a]
  • Updated dependencies [5ebc842]
  • Updated dependencies [16ff6b9]
  • Updated dependencies [0c9941d]

1.246.0

Patch Changes

1.245.0

Patch Changes

  • Updated dependencies [b4d47a8]
  • Updated dependencies [dee6db4]
  • Updated dependencies [b85c113]
  • Updated dependencies [bc556e7]
  • Updated dependencies [9f11d29]
  • Updated dependencies [19bcea0]
  • Updated dependencies [60b461c]
  • Updated dependencies [61e3a1a]
  • Updated dependencies [a156b81]
  • Updated dependencies [0bd7045]

1.244.0

Patch Changes

  • Updated dependencies [38d839a]
  • Updated dependencies [ce13b0f]
  • Updated dependencies [4fa23e3]
  • Updated dependencies [6841c69]
  • Updated dependencies [f56721d]
  • Updated dependencies [c894a43]
  • Updated dependencies [f6944a3]
  • Updated dependencies [23fd0e0]

1.243.0

Patch Changes

1.242.0

Patch Changes

1.241.0

Patch Changes

1.240.0

Patch Changes

1.239.0

Patch Changes

1.238.0

Patch Changes

1.237.0

Patch Changes

1.236.0

Patch Changes

1.235.0

Patch Changes

1.234.0

Patch Changes

1.233.0

Patch Changes

1.232.0

Patch Changes

1.231.0

Patch Changes

1.230.0

Patch Changes

1.229.0

Patch Changes

1.228.0

Patch Changes

1.227.0

Patch Changes

  • Updated dependencies [f262e9f]
  • Updated dependencies [f191ff7]
  • Updated dependencies [fbbfbe8]
  • Updated dependencies [263b5e8]
  • Updated dependencies [db4d56d]
  • Updated dependencies [41f93a9]
  • Updated dependencies [98c8ca9]

1.226.0

Patch Changes

1.225.0

Patch Changes

1.224.0

Patch Changes

1.223.0

Patch Changes

1.222.0

Patch Changes

1.221.0

Patch Changes

1.220.0

Patch Changes

1.219.0

Patch Changes

1.218.0

Patch Changes

1.217.0

Patch Changes

1.216.0

Patch Changes

1.215.0

Patch Changes

1.214.0

Patch Changes

1.213.0

Patch Changes

1.212.0

Patch Changes

1.211.0

Patch Changes

1.210.0

Patch Changes

1.209.0

Patch Changes

1.208.0

Patch Changes

1.207.0

Patch Changes

1.206.0

Patch Changes

1.205.0

Patch Changes

1.204.0

Patch Changes

1.203.0

Patch Changes

1.202.0

Patch Changes

1.201.0

Patch Changes

1.200.0

Patch Changes

1.199.0

Patch Changes

1.198.0

Patch Changes

1.197.0

Patch Changes

1.196.0

Patch Changes

1.195.0

Patch Changes

1.194.0

Patch Changes

1.193.0

Patch Changes

1.192.0

Patch Changes

1.191.0

Patch Changes

1.190.0

Patch Changes

1.189.0

Patch Changes

1.188.0

Patch Changes

1.187.0

Patch Changes

1.186.0

Patch Changes

1.185.0

Patch Changes

1.184.0

Patch Changes

1.183.0

Patch Changes

1.182.0

Patch Changes

1.181.0

Patch Changes

1.180.0

Patch Changes

1.179.0

Patch Changes

1.178.0

Patch Changes

1.177.0

Patch Changes

1.176.0

Patch Changes

1.175.0

Patch Changes

1.174.0

Patch Changes

1.173.0

Patch Changes

1.172.0

Patch Changes

1.171.0

Patch Changes

1.170.0

Patch Changes

1.169.0

Patch Changes

1.168.0

Patch Changes

1.167.0

Patch Changes

1.166.0

Patch Changes

1.165.0

Patch Changes

1.164.0

Patch Changes

1.163.0

Patch Changes

1.162.0

Patch Changes

1.161.0

Patch Changes

1.160.0

Patch Changes

1.159.0

Patch Changes

1.158.0

Patch Changes

1.157.0

Patch Changes

1.156.0

Patch Changes

1.155.0

Patch Changes

1.154.0

Patch Changes

1.153.0

Patch Changes

1.152.0

Patch Changes

1.151.0

Patch Changes

1.150.0

Patch Changes

1.149.0

Patch Changes

1.148.0

Patch Changes

1.147.0

Patch Changes

1.146.0

Patch Changes

1.145.0

Patch Changes

1.144.0

Patch Changes

1.143.0

Patch Changes

1.142.0

Patch Changes

1.141.0

Patch Changes

1.140.0

Patch Changes

1.139.0

Patch Changes

1.138.0

Patch Changes

1.137.0

Patch Changes

1.136.0

Patch Changes

1.135.0

Patch Changes

1.134.0

Patch Changes

1.133.0

Patch Changes

1.132.0

Patch Changes

1.131.0

Patch Changes

1.130.0

Patch Changes

1.129.0

Patch Changes

1.128.0

Patch Changes

1.127.0

Patch Changes

1.126.0

Patch Changes

1.125.0

Patch Changes

1.124.0

Patch Changes

1.123.0

Patch Changes

1.122.0

Patch Changes

1.121.0

Patch Changes

1.120.0

Patch Changes

1.119.0

Patch Changes

1.118.0

Patch Changes

1.117.0

Patch Changes

1.116.0

Patch Changes

1.115.0

Patch Changes

1.114.0

Patch Changes

1.113.0

Patch Changes

1.112.0

Patch Changes

1.111.0

Patch Changes

1.110.0

Patch Changes

1.109.0

Patch Changes

1.108.0

Patch Changes

1.107.0

Patch Changes

1.106.0

Patch Changes

1.105.0

Patch Changes

1.104.0

Patch Changes

1.103.0

Patch Changes

1.102.0

Patch Changes

1.101.0

Patch Changes

1.100.0

Patch Changes

1.99.1

Patch Changes

1.99.0

Patch Changes

1.98.0

Patch Changes

1.97.0

Patch Changes

1.96.0

Patch Changes

1.95.0

Patch Changes

1.94.0

Patch Changes

1.93.0

Patch Changes

1.92.0

Patch Changes

1.91.0

Patch Changes

1.90.0

Patch Changes

1.89.0

Patch Changes

1.88.0

Patch Changes

1.87.0

Patch Changes

1.86.0

Patch Changes

1.85.0

Patch Changes

1.84.0

Patch Changes

1.83.0

Patch Changes

1.82.0

Patch Changes

1.81.2

Patch Changes

1.81.1

Patch Changes

1.81.0

Patch Changes

1.80.0

Patch Changes

1.79.0

Patch Changes

1.78.0

Patch Changes

1.77.0

Patch Changes

1.76.0

Patch Changes

1.75.1

Patch Changes

1.75.0

Patch Changes

1.74.0

Patch Changes

1.73.0

Patch Changes

1.72.0

Patch Changes

1.71.0

Patch Changes

1.70.1

Patch Changes

1.70.0

Patch Changes

1.69.0

Patch Changes

1.68.0

Patch Changes

1.67.0

Patch Changes

1.66.0

Patch Changes

1.65.0

Patch Changes

1.64.0

Patch Changes

1.63.0

Patch Changes

1.62.0

Patch Changes

1.61.0

Patch Changes

1.60.0

Patch Changes

1.59.4

Patch Changes

1.59.3

Patch Changes

1.59.2

Patch Changes

1.59.1

Patch Changes

1.59.0

Patch Changes

1.58.0

Patch Changes

1.57.0

Patch Changes

1.56.0

Patch Changes

1.55.0

Patch Changes

1.54.0

Patch Changes

1.53.0

Patch Changes

1.52.0

Patch Changes

1.51.0

Patch Changes

1.50.0

Patch Changes

1.49.0

Patch Changes

1.48.0

Patch Changes

1.47.0

Patch Changes

1.46.0

Patch Changes

1.45.0

Patch Changes

1.44.1

Patch Changes

1.44.0

Patch Changes

1.43.0

Patch Changes

1.42.0

Patch Changes

1.41.0

Patch Changes

1.40.0

Patch Changes

1.39.0

Patch Changes

1.38.0

Patch Changes

1.37.0

Patch Changes

1.36.0

Patch Changes

1.35.0

Patch Changes

1.34.0

Patch Changes

1.33.0

Patch Changes

1.32.0

Patch Changes

1.31.0

Patch Changes

1.30.0

Patch Changes

1.29.0

Patch Changes

1.28.0

Patch Changes

1.27.0

Patch Changes

1.26.0

Patch Changes

1.25.0

Patch Changes

1.24.1

Patch Changes

1.24.0

Patch Changes

1.23.0

Patch Changes

  • 1f9c272: The renderers' remaining unsanitized paths and the malformed-event gaps (v1.22.0 review P2-2, P2-3).

    • progress(): the error text surfaced when the SOURCE fails (a rejected RunHandle.result, a rejected Promise<RunHandle>, a throwing iterable) went to the sink raw; a crafted rejection could inject ANSI, forge lines, and leak a key-shaped fragment. Every catch path now routes through one helper that secret-masks FIRST (the thrown value never crossed the event masking boundary) and terminal-sanitizes second; lines mode prints the notice as its own sanitized line instead of dropping it.
    • Malformed recognized events from a raw iterable can no longer stop a view: every dynamic field in the progress() reducer, its lines formatter, renderProgress, and the CLI renderEventLine is read through typed guards (a hostile object with a throwing toString included), a backstop catch skips a bad event with a bounded diagnostic carrying no untrusted data, and the stream continues. The v1.22.0 claim of full defensive reads was narrower in reality (agent:stream without delta or phase:start without phase stopped the raw-iterable view); it is true now and pinned by a table-driven test over every consumed type.
    • posIntOption wording: a below-minimum value CLAMPS to the minimum (only non-finite values fall back to the default); the JSDoc said "falls back" for both.
    • @rulvar/cli build config migrates the deprecated tsdown external option to deps.neverBundle; the packed dist keeps the companion specifiers external, byte-for-same behavior.
  • Updated dependencies [1f9c272]

  • Updated dependencies [1f9c272]

1.22.0

Patch Changes

  • 77b554f: Harden the terminal progress renderers (v1.21.0 review). Both progress (its lines mode and the tty state, plus the title option) and the minimal renderProgress now pass every untrusted field through the shared sanitizeTerminalText sanitizer, so control characters and ANSI escape sequences in provider/tool/log strings can no longer clear the screen, recolor to forge text, or inject extra lines (P2-1). progress geometry and timing options are normalized to finite positive integers: a non-finite or below-minimum fps, width, maxRows, sink.columns, or sink.rows falls back instead of breaking the clip or creating a NaN-interval timer, the width clip now holds every rendered line strictly under the terminal width for every width (including 1 to 3), and a NaN or backward clock reading renders a zero timer rather than NaN (P3-2). The clock JSDoc is corrected to performance.now, and every dynamic field is read defensively so a recognized event missing a required field degrades a row instead of stopping the view.
  • Updated dependencies [77b554f]

1.21.0

Minor Changes

  • 7ee42a0: New live terminal progress view: progress(source, options) renders a claude-workflows-style tree over the WorkflowEvent stream with one row per agent (status glyph, running timer, token counts, USD), per-role sub-timings when one call spans several invocation phases, the run header with spend against the ceiling, banners for pending approvals and externals, and a final summary including the per-role dollar split from RunOutcome.cost.byRole. Accepts a RunHandle (subscribes via on(), leaving handle.events free), a promise of one, or a raw event iterable (the gapless resume path). TTY mode repaints in place at a bounded rate; pipes and CI degrade to append-only lines; NO_COLOR, injectable sink and clock, and stderr-only output keep it deterministic and clean. The minimal renderProgress is unchanged.

Patch Changes

1.20.0

Patch Changes

1.19.0

Patch Changes

1.18.0

Minor Changes

  • 943962d: recommendedDefaults.floors now admits openai:gpt-5.6-sol and its published exact alias openai:gpt-5.6 for the orchestrate and plan roles. The allowlists had fallen behind the product recommendation: the rulvar.com quickstart routes the orchestrator at Sol, but a configuration combining that recommendation with the recommended floors was rejected before any provider call with a quality-floor violation. The weaker family siblings Terra and Luna stay deliberately floored out of the control-plane roles; worker roles (loop, extract) remain unfloored.

Patch Changes

1.17.0

Patch Changes

1.16.2

Patch Changes

1.16.1

Patch Changes

1.16.0

Patch Changes

1.15.0

Patch Changes

1.14.0

Patch Changes

1.13.0

Patch Changes

1.12.0

Patch Changes

1.11.0

Patch Changes

1.10.0

Patch Changes

1.9.0

Patch Changes

1.8.0

Patch Changes

1.7.0

Patch Changes

1.6.0

Patch Changes

  • da4dbad: Write the product name as Rulvar in prose: package READMEs, npm descriptions, and the documentation site now capitalize the brand. Identifiers keep their exact casing, so package names, the rulvar binary, rulvar.config.mjs, the .rulvar store directory, the rulvar.* OTel attributes, and every URL are unchanged. Documentation and metadata only; no runtime behaviour changes.
  • Updated dependencies [da4dbad]
  • Updated dependencies [487da86]
  • Updated dependencies [df416fc]
  • Updated dependencies [886d065]
  • Updated dependencies [a737810]
  • Updated dependencies [9eb66b4]

1.5.2

Patch Changes

1.5.1

Patch Changes

1.5.0

Patch Changes

1.4.0

Patch Changes

1.3.2

Patch Changes

  • ddef383: Every published package now ships a README, so its npm page states what the package is, how it installs, and where the documentation lives (npm includes README.md in the tarball regardless of the files allowlist, so no manifest changes are involved; @rulvar/compat gains its README on its own next release). Alongside, the repository-level pages are refreshed to the current project state: the root README is rewritten around the never-pay-twice pitch with a runnable quickstart condensation and the full package table, CONTRIBUTING.md lists the complete PR gate set, the examples README drops retired-spec citations for live docs.rulvar.com links and documents the dogfood journal replay, and the pointer README gets the same treatment.
  • Updated dependencies [ddef383]

1.3.1

Patch Changes

  • 7d1552e: Runtime message strings no longer cite the retired internal specification set: error and warning messages, validation issues, and the CLI help text drop the dangling docs/NN, section ... references, pointing at https://docs.rulvar.com pages where a pointer earns its place (the CLI help header, tool naming, toolset registries, bare resume). The umbrella package description sheds the naming-contingency note: the unscoped alias is published and owned. Three strings embedded in frozen recordings stay byte-identical on purpose (the no-progress abort reason and two testing-internal recorder strings), as does the byte-locked golden-fold fixture. Test-file comments lose their citations too; test titles are unchanged.
  • Updated dependencies [7d1552e]

1.3.0

Patch Changes

1.2.0

Patch Changes

  • 154507b: TSDoc and inline comments no longer cite the retired internal specification set (the pre-docs-site docs/NN, section ... references). The citations either became links to the public documentation at docs.rulvar.com or were dropped where the comment already carried the rule; traceability markers (DEF-n, XF-nn, FR-nnn, OQ-nn, W-nnn) are untouched. Comment-only change: no runtime behavior, no API shapes, and no runtime message strings were modified; the frozen golden-fold fixture is byte-identical.
  • Updated dependencies [3bfaec0]
  • Updated dependencies [890f42c]
  • Updated dependencies [154507b]

1.1.0

Patch Changes

1.0.0

Patch Changes

  • Updated dependencies [0e0b569]
  • Updated dependencies [b28b7a3]
  • Updated dependencies [b53a89e]
  • Updated dependencies [4454175]
  • Updated dependencies [6599ca8]
  • Updated dependencies [6649e5f]
  • Updated dependencies [fd2f83b]
  • Updated dependencies [01d6b2d]
  • Updated dependencies [9a20dbb]
  • Updated dependencies [0fbe7ea]
  • Updated dependencies [ebe0abc]
  • Updated dependencies [a3079d0]
  • Updated dependencies [596a39b]
  • Updated dependencies [464ab6e]

0.9.0

Patch Changes

0.8.0

Patch Changes

  • Updated dependencies [85d55cf]
  • Updated dependencies [b88c9e3]
  • Updated dependencies [f3c4613]
  • Updated dependencies [a41c20f]
  • Updated dependencies [f4e70be]
  • Updated dependencies [75d1646]
  • Updated dependencies [0627413]
  • Updated dependencies [55c0f87]
  • Updated dependencies [fd33871]
  • Updated dependencies [e70e7f4]
  • Updated dependencies [bc9c903]

0.7.0

Patch Changes

0.6.0

Minor Changes

  • fa05007: M5-T01 workflow registry and the @rulvar/cli base.

    • @rulvar/core gains the per-engine WorkflowRegistry type and defaults.workflows on createEngine (docs/06 section 10.4): an explicit first-class value, no module-level registry; shells resolve by-name runs against it (ctx.workflow's string form arrives M6, the queue worker M8).
    • Spec-conformance fix: the M4-T09 quality floors option moves from the createEngine top level to its canonical home defaults.roleFloors (docs/06 section 10.1). Update createEngine({ floors }) call sites to createEngine({ defaults: { roleFloors } }).
    • @rulvar/cli ships its first real surface: the canonical grammar rulvar run <file|name> [--args JSON] [--store PATH] [--budget-usd N], rulvar resume <runId> [--args JSON] [--store PATH], rulvar runs ls [--store PATH], rulvar inspect &lt;runId&gt; [--store PATH] (no aliases), a line-oriented TUI progress renderer over the event stream, and interactive resolution of suspended approvals and externals (EOF leaves the run suspended, never errors). Engine assembly follows the host-config convention: rulvar.config.mjs default-exports { engineOptions?, workflows? }, a workflow module may export workflow/engineOptions/workflows, and --store selects the JsonlFileStore directory (default .rulvar), so the CLI itself depends only on @rulvar/core. The rulvar bin is included; the resume/inspect grammar amendment (--args re-supply, --store symmetry) is recorded in docs/06 section 10.5.

Patch Changes

0.5.0

Minor Changes

  • b840aba: M4-T08 canonical effort completion and M4-T09 role quality floors.

    • Effort semantics are complete: the role effort defaults and the per-adapter mapping tables (Anthropic passthrough including max, OpenAI max downmapped to xhigh and recorded in providerMetadata, provider none only via namespaced providerOptions) shipped earlier milestones; this change completes VISIBLE scrubbing everywhere it was still silent: the summarize invocation surfaces its scrubs at fire time and a failover takeover surfaces the fallback's scrubs the moment it starts serving. Scrubbed effort is never mapped into max_tokens.
    • The effort-defaults-shift cassette is now RECORDED through the live runtime (docs/10 M4 gating row): the frozen v1 prefix, closed offline the way an operator would, resumes live under explicit high effort with the completed semantics; every v1 entry matches and the one new spawn carries canonical effort in v2 identity. The recorder output is pinned byte-for-byte by the frozen-drift suite and the fixture lock now covers 18 files.
    • Quality floors (model/floors.ts, M4-T09): per-role and per-declared-taskClass allow/deny lists supplied via createEngine({ floors }), enforced INSIDE the router at resolution, before any live call and before any journal entry, for every invocation the chain produces (primaries, failover fallbacks, and the summarize fallback alike). AgentProfile.taskClass declares the class; unclassified profiles see only byRole floors. A violation is a typed ConfigError.
    • The umbrella rulvar package now ships floors opinions next to its strong routing defaults: recommendedDefaults.floors pins orchestrate and plan to strong named models. The core itself ships no named model strings, and the umbrella suite enforces that with a source scan.

Patch Changes

0.4.0

Patch Changes

  • Updated dependencies [dfe03b5]
  • Updated dependencies [d2089a7]
  • Updated dependencies [3f60234]
  • Updated dependencies [f668890]
  • Updated dependencies [16d7aa6]
  • Updated dependencies [6513ce8]
  • Updated dependencies [7dad493]
  • Updated dependencies [2bbf180]

0.3.0

Patch Changes

0.2.0

Minor Changes

  • c24228d: M1-T10/T11: the WorkflowEvent envelope and M1 catalog (per-run telemetry seq distinct from JournalEntry.seq, span hierarchy run > phase > agent), the per-run EventBus feeding RunHandle.events and on(), RunOutcome with exhausted-overrides-error precedence and the normative CostReport (byModel/byPhase/byAgentType/byRole, the all-zero orchestrator block, unpriced evidence); createEngine with per-engine registries and engine.run over the ScriptRunner seam; InProcessRunner with the dev-mode bare-Date.now/Math.random warnings; run cancellation (host signal, handle.cancel, run deadline) and RunMeta run-to-definition binding fields. The umbrella ships the minimal terminal progress renderer (renderProgress) and re-exports the core surface.
  • 5c4fc32: M1-T14/T15: @rulvar/testing tier 1 (FakeAdapter matching on agentType/label/prompt regex with a '*' fallback, honoring the selected structured-output tier, zero USD by construction; createTestEngine over the full real engine with recorded event streams; toHaveCalledAgent and toStayUnderBudget matchers at '@rulvar/testing/matchers') and the completed umbrella (re-exports of @rulvar/core and both first-class adapters, renderProgress, the umbrella-only recommendedDefaults strong model slots, the M1 exit-criteria example workflow, and the CI install smoke on packed tarballs). The core now populates the reserved providerOptions 'rulvar' telemetry namespace on every request (docs/04 section 1.8 as amended) and AgentResult carries errorMessage detail for journaled WireError fidelity.

Patch Changes

0.1.0

Minor Changes

  • f4e2be9: M0 repo bootstrap (v0.1.0, docs/10-implementation-plan.md section "M0"): monorepo scaffold on the committed toolchain (pnpm 11 workspaces with catalogs, TypeScript 6.0, tsdown, Vitest 4, ESLint 9 flat config, Turborepo 2, changesets fixed mode, npm trusted publishing), the docs/ canon as single source of truth, the L0 contracts skeleton in @rulvar/core, and the vendored dependencies (StandardSchemaV1/StandardJSONSchemaV1 types, the @cfworker/json-schema lineage validator subset, a first-party monotonic ULID). Placeholder scaffolds only: no public API ships in this release.

Patch Changes

@rulvar/store-conformance

1.252.0

Patch Changes

  • Updated dependencies [3ccb6cf]
  • Updated dependencies [52d807f]
  • Updated dependencies [517ed00]
  • Updated dependencies [a7e589d]
  • Updated dependencies [76e95eb]

1.251.0

Patch Changes

  • Updated dependencies [e7e829c]
  • Updated dependencies [5982be8]
  • Updated dependencies [7c58fb2]
  • Updated dependencies [b3e465a]
  • Updated dependencies [c4e5d6a]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [0ae8b85]
  • Updated dependencies [7932936]
  • Updated dependencies [7932936]
  • Updated dependencies [88da0ed]
  • Updated dependencies [06c0e85]

1.250.0

Minor Changes

  • c5eb19c: The restoration generation (RV4503, plan 45, rfcs/effects.md section 4.5, item 3): SqliteStore and PostgresStore implement the EffectLaneStore capability, carrying a restoration generation OUTSIDE the journal bytes (a one-row table beside the leases). The restore runbook is one rule: after a point-in-time restore, call bumpRestorationGeneration() BEFORE the restored database becomes reachable to any worker, so the effect lane comes up with dispatch disabled by construction until an operator appends a fresh effect_epoch citing the bumped generation. The new effectLaneStoreConformance suite in @rulvar/store-conformance is the executable definition: generation starts at 0 and bumps monotonically (ELS1, ELS2), a bumped generation refuses every lane append until the fresh epoch (ELS3, the kill point 25 window, driven through the real writer over the real store), and a lane append under a non-current lease dies on the store's fence with nothing consumed (ELS4, the kill point 16 shape).
  • c6d197b: The reconciler, the trust envelope, and the whole kill point kit (RV4505, plan 45, rfcs/effects.md sections 3.1, 7, 8, 9). The sweep makes "every intent deterministically reaches confirmed, compensated, or quarantined" true: crossing reconcileBy quarantines whatever state with the state recorded, receipt waits and attempt budgets quarantine on exhaustion, lookups are bounded SEPARATELY through journaled effect_probe rows (countable from the journal alone, crash-proof), pre-terminal conflicting receipts quarantine, and effect authorizations past their deadline refuse durably instead of waiting forever. Receipt verification runs a declared trust envelope: issuer identity, per-class content bindings, key validity windows, revocation from its time forward, and the host's signature check; every failure classifies unverified, which routes to unknown. The post-restore reconciliation (kill 25) quarantines provider effects the journal cannot reconstruct by name (or the whole range without authoritative enumeration), and a restoration epoch stays undispatchable until the new effect_reconciliation_complete decision cites it. Section 9 telemetry folds effective dispositions (the compensated overlay included), pressure, duplicate classification, and open incidents. The kit exports all thirty effects.kill.* rows as named conformance checks parameterized by a store factory (ambiguous acks and restoration generations injected through delegating proxies, so any store qualifies), registered over the in-memory reference store in single-process posture and over the REAL sqlite and postgres stores in their own packages.
  • df9ed76: The admission conformance matrix, all twelve rows (RV4509, plan 45, rfcs/admission.md section 7): admissionConformance runs the RFC's named acceptance surface over any scheduler factory, registered over the in-memory reference (snapshot/hydrate plays the crash reopen), the sqlite document, and the postgres document. The fairness rows measure GRANTED RAW SERVICE, the property itself: sixty equal tenants each receive their exact share with every consecutive sixty-grant window containing every tenant, and weights 1/2/4 grant exactly 1:2:4 in the first virtual-time cycle with weight 1 never starving (the tenant plugs that assemble the queue first carry weight equal to their cost, a uniform one-unit tag shift that preserves the burst's relative order bit for bit). The remaining rows: the minute-boundary burst bound, queued-ticket crash survival with arrival identity intact, the conservative fenced-cover expiry settlement with late debt, the denied-versus-queued state distinction, region loss without double grants, hundred-percent repair amplification held inside caps through debt, fail-closed foreign scope, multi-level all-or-nothing, the atomic failover rebind (new rebind on the SPI: the target slot acquires before the source releases, and a failed transfer changes nothing), and tenant resolution parity. The reference pump's scan is now bucket-blocking: a refused ticket blocks ITS bucket for the pass, so no later ticket of the same bucket overtakes it (the no-starvation guarantee), while independent buckets proceed; release no longer grants implicitly, making every grant an observable pump event.

Patch Changes

  • Updated dependencies [0e240b9]
  • Updated dependencies [6fe585e]
  • Updated dependencies [c5eb19c]
  • Updated dependencies [565c13b]
  • Updated dependencies [c6d197b]
  • Updated dependencies [c9d9729]
  • Updated dependencies [fed9db6]
  • Updated dependencies [df9ed76]
  • Updated dependencies [3020912]
  • Updated dependencies [d8d598d]

1.249.0

Patch Changes

  • Updated dependencies [8862133]
  • Updated dependencies [0d7a717]
  • Updated dependencies [e4428bd]
  • Updated dependencies [d6873c1]
  • Updated dependencies [4092e8d]
  • Updated dependencies [e086590]
  • Updated dependencies [1411938]
  • Updated dependencies [737d1ee]
  • Updated dependencies [634f966]
  • Updated dependencies [052cc26]
  • Updated dependencies [bbae134]

1.248.0

Minor Changes

  • 95f6a5e: The scope identity gains a declarative value normalization table, and the journal is its authority (RV4302, plan 43). scopePolicy.normalize is a versioned table over a closed vocabulary (trim, lowercase, nfc, applied per dimension in declared order), deliberately data and not a callback: a host function is not replay stable, not journalable, and free to read locale or time. The table applies in normalizeExecutionScope strictly AFTER the existing input validation, the result re-validates by the same rule (an all-whitespace value that trims to empty refuses typed instead of recording an identity that asserts nothing), and the canonical values exist BEFORE any digest does, so ' EU-West ' and 'eu-west' stop splitting one tenant's quota buckets and FinOps joins across two identities. The table is journaled in the genesis execution_scope decision beside the scope and digest it shaped, mirrored in RunMeta.scopeNormalize (stores must round-trip it; the conformance kit checks), and on resume the RECORDED table is what normalizes the supplied scope before any comparison, so a host that re-supplies the raw values it started with asserts true; a conflicting re-supplied table refuses typed (the args-binding rule), and a table supplied over a run that recorded none warns and is never applied. compileRegulatedProfile preserves a declared table under its pinned unknown: 'reject' and hashes it into the posture, so two compiles over the same canonical values with different declared tables carry different profileHashes; absence keeps every undeclared config byte for byte, hash included. Beside the code, rfcs/admission.md records the accepted design for the durable fairness and admission SPI (P1.4): the split from the live-only QuotaLimiter, hierarchical buckets over the resolved effective tenant, start time fair queuing with reserved wires as the one scheduler unit, conditional-create tickets with lease-fenced consumption covers, and the conformance matrix, hardened by adversarial review to the final verdict closed.

Patch Changes

  • Updated dependencies [8d0cd69]
  • Updated dependencies [81065e4]
  • Updated dependencies [8573f20]
  • Updated dependencies [95f6a5e]

1.247.0

Patch Changes

  • Updated dependencies [1933ecc]
  • Updated dependencies [db0a5f0]
  • Updated dependencies [b698726]
  • Updated dependencies [48348d2]
  • Updated dependencies [4cfa1cc]
  • Updated dependencies [4b7197a]
  • Updated dependencies [5ebc842]
  • Updated dependencies [16ff6b9]
  • Updated dependencies [0c9941d]

1.246.0

Patch Changes

  • Updated dependencies [d165b0c]
  • Updated dependencies [d59f4a0]
  • Updated dependencies [46907ac]
  • Updated dependencies [9929ad3]
  • Updated dependencies [1790a6a]

1.245.0

Minor Changes

  • 60b461c: The bounded execution scope (RV4007, the fifth comparison experiment's P0.4). Who a run executes for, as the host names it, carried WITHOUT LOSS and never interpreted: RunOptions.scope ({ tenant?, account?, project? }, own properties, non-empty strings, at least one field, copied at intake so later mutation moves nothing) records at genesis into RunMeta and a journaled execution_scope decision, is immutable for the run's life (no resume door), rides the invoice header as executionScope (a pure fold from the entries, so a FinOps pipeline reads the owner off the money document), travels in the export bundle via its meta, and ResumeOptions.scope asserts it back (mismatch refuses typed before ownership; a supplied scope over a run that recorded none warns). On the provider side, ProviderAdapter.scopeKey names the ACCOUNT within a family: the retention transport then keys provider-raw blocks by (family, scopeKey) instead of family alone, so cache handles and thinking blocks minted under one account never ride a request served by another; undeclared adapters keep the family-wide sharing byte for byte, and routing, pricing, and quota keys are untouched. The store conformance kit pins the RunMeta round-trip; probes pin the genesis decision and the retention separation. Attribution envelope, not IAM: tenancy semantics stay host decisions.

Patch Changes

  • Updated dependencies [b4d47a8]
  • Updated dependencies [dee6db4]
  • Updated dependencies [b85c113]
  • Updated dependencies [bc556e7]
  • Updated dependencies [9f11d29]
  • Updated dependencies [19bcea0]
  • Updated dependencies [60b461c]
  • Updated dependencies [61e3a1a]
  • Updated dependencies [a156b81]
  • Updated dependencies [0bd7045]

1.244.0

Minor Changes

  • 38d839a: RunOptions.budgetPolicy: 'segment' | 'immutable-lifetime' (RV3902, the fourth comparison experiment): the regulated posture the docs used to promise by accident is now a real, opt-in invariant. Default 'segment' is today's behavior byte for byte. Under 'immutable-lifetime' the posture is recorded in RunMeta at genesis (only the non-default is written; the store conformance kit holds stores to the round-trip) and restored on every resume, and a resume carrying ANY applying ResumeOptions.run override refuses with a typed ConfigError before ownership, meta writes, or any append, raising and lowering alike; the empty run: {} object stays the documented no-op, a bare resume stays a pure replay, and a store that drops the field degrades to 'segment' (the door works again), never to an invented refusal. The fault kit gains the budget-policy-immutable scenario (typed refusal, zero wires, zero durable mutations, bare replay intact); two mutation probes pin the refusal gate and the genesis recording. The source TSDoc sweep retires the last immutable after start comments (engine, budget, termination, orchestrate, plan), and the docs doctrine pins now scan docs/api too.

Patch Changes

  • Updated dependencies [38d839a]
  • Updated dependencies [ce13b0f]
  • Updated dependencies [4fa23e3]
  • Updated dependencies [6841c69]
  • Updated dependencies [f56721d]
  • Updated dependencies [c894a43]
  • Updated dependencies [f6944a3]
  • Updated dependencies [23fd0e0]

1.243.0

Patch Changes

  • Updated dependencies [746d1f4]
  • Updated dependencies [009b29c]
  • Updated dependencies [1674cbe]
  • Updated dependencies [bd096bc]

1.242.0

Patch Changes

  • Updated dependencies [6e3438e]
  • Updated dependencies [ba5cf67]
  • Updated dependencies [c2d1531]

1.241.0

Patch Changes

  • Updated dependencies [dbcdd24]
  • Updated dependencies [7ae7243]
  • Updated dependencies [4f832c4]
  • Updated dependencies [7452d3d]
  • Updated dependencies [a4e22bf]
  • Updated dependencies [82df4af]

1.240.0

Patch Changes

1.239.0

Patch Changes

  • Updated dependencies [74ce99a]
  • Updated dependencies [ccd0665]
  • Updated dependencies [0c5ce21]
  • Updated dependencies [0616934]

1.238.0

Patch Changes

  • Updated dependencies [cf00947]
  • Updated dependencies [c7b9382]
  • Updated dependencies [88aea96]
  • Updated dependencies [6da8d05]
  • Updated dependencies [eae5c4c]

1.237.0

Patch Changes

  • Updated dependencies [9d6a279]
  • Updated dependencies [49a98f6]
  • Updated dependencies [a734ca0]
  • Updated dependencies [deb406f]

1.236.0

Patch Changes

1.235.0

Patch Changes

  • Updated dependencies [ba4e10d]
  • Updated dependencies [172402b]
  • Updated dependencies [2ecd787]
  • Updated dependencies [e20a5e9]
  • Updated dependencies [98c8691]
  • Updated dependencies [c70def0]

1.234.0

Patch Changes

1.233.0

Patch Changes

  • Updated dependencies [48b5200]
  • Updated dependencies [73bc32b]
  • Updated dependencies [e63b743]
  • Updated dependencies [ef45da7]

1.232.0

Patch Changes

  • Updated dependencies [1440410]
  • Updated dependencies [6e467f4]
  • Updated dependencies [0b14293]
  • Updated dependencies [e3bcab2]
  • Updated dependencies [b55a0f7]

1.231.0

Patch Changes

  • Updated dependencies [4eb4b56]
  • Updated dependencies [bc8f09e]
  • Updated dependencies [ff9b8c2]

1.230.0

Patch Changes

1.229.0

Patch Changes

  • Updated dependencies [3370342]
  • Updated dependencies [2fb6656]
  • Updated dependencies [edce170]

1.228.0

Patch Changes

  • Updated dependencies [4034fac]
  • Updated dependencies [a54b085]
  • Updated dependencies [9d0a9be]
  • Updated dependencies [be9ef28]

1.227.0

Patch Changes

  • Updated dependencies [f262e9f]
  • Updated dependencies [f191ff7]
  • Updated dependencies [fbbfbe8]
  • Updated dependencies [263b5e8]
  • Updated dependencies [db4d56d]
  • Updated dependencies [41f93a9]
  • Updated dependencies [98c8ca9]

1.226.0

Patch Changes

1.225.0

Patch Changes

1.224.0

Patch Changes

1.223.0

Patch Changes

1.222.0

Patch Changes

1.221.0

Patch Changes

1.220.0

Patch Changes

1.219.0

Patch Changes

1.218.0

Patch Changes

1.217.0

Patch Changes

1.216.0

Patch Changes

1.215.0

Patch Changes

1.214.0

Patch Changes

1.213.0

Patch Changes

1.212.0

Patch Changes

1.211.0

Patch Changes

1.210.0

Patch Changes

1.209.0

Patch Changes

1.208.0

Patch Changes

1.207.0

Patch Changes

1.206.0

Patch Changes

1.205.0

Patch Changes

1.204.0

Patch Changes

1.203.0

Patch Changes

1.202.0

Patch Changes

1.201.0

Patch Changes

1.200.0

Patch Changes

1.199.0

Patch Changes

1.198.0

Patch Changes

1.197.0

Patch Changes

1.196.0

Patch Changes

1.195.0

Patch Changes

1.194.0

Patch Changes

1.193.0

Patch Changes

1.192.0

Patch Changes

1.191.0

Patch Changes

1.190.0

Patch Changes

1.189.0

Patch Changes

1.188.0

Patch Changes

1.187.0

Patch Changes

1.186.0

Patch Changes

1.185.0

Patch Changes

1.184.0

Patch Changes

1.183.0

Patch Changes

1.182.0

Patch Changes

1.181.0

Patch Changes

1.180.0

Patch Changes

1.179.0

Patch Changes

1.178.0

Patch Changes

1.177.0

Patch Changes

1.176.0

Patch Changes

1.175.0

Patch Changes

1.174.0

Patch Changes

1.173.0

Patch Changes

1.172.0

Patch Changes

1.171.0

Patch Changes

1.170.0

Patch Changes

1.169.0

Patch Changes

1.168.0

Patch Changes

1.167.0

Patch Changes

1.166.0

Patch Changes

1.165.0

Patch Changes

1.164.0

Patch Changes

1.163.0

Patch Changes

1.162.0

Patch Changes

1.161.0

Minor Changes

  • d4547b7: Refuse unpriced, malformed, and stale-priced dispatches before the wire under the opt-in strict pricing gate (RV1508). The fourth PR of the eighteenth plan.

    Dollars come from the price table, and a model absent from it debits NOTHING, so every USD ceiling silently fails to bound it; the docs called that hole honest, and the seventeenth comparison benchmark asked for a mode that closes it. RunOptions.strictPricing arms the gate: every paid dispatch must resolve a well-formed price row for its serving model BEFORE the wire call, at the same dispatch chokepoint the exposure admission holds, or the dispatch refuses with a typed ConfigError naming the model and the defect (no row, a non-finite or negative rate, a malformed long-context tier). maxRatesAgeDays additionally demands a fresh ratesVerifiedAt on the row, binding only when declared; allowUnpriced lists the exact model refs the host KNOWS are free, the one explicit exception. Each model vets once per run, since the price table is fixed for the run's life.

    The posture follows the exposure cap's durability rule (RV1504): canonicalized and recorded in RunMeta at genesis, restored by every resume with no ResumeOptions override, absence stays absent, and the store conformance kit holds stores to the round-trip, because a FinOps gate a resumed segment silently drops is not a gate.

Patch Changes

1.160.0

Patch Changes

1.159.0

Minor Changes

  • e881c8b: Record the in-flight exposure cap in RunMeta and restore it on every resume, and fold each budget account's settled spend for audits (RV1504, RV1505 first half). The second PR of the eighteenth plan.

    The durable exposure cap (RV1504). RunOptions.maxInFlightExposureUsd was operational and per-invocation, so a resumed segment silently ran WITHOUT the exposure bound the original invocation declared, the seventeenth comparison benchmark's top FinOps gap. The cap now follows the ceiling's exact rule: recorded in RunMeta at genesis, restored by every resume, no ResumeOptions field to override it, absence stays absent (a run started uncapped stays uncapped, a pre-field journal resumes exactly as before), and the store conformance kit holds stores to the round-trip. One honest asymmetry is documented rather than papered over: limits stay per-invocation, so a resumed segment that does not re-supply them prices turn estimates from the model's full output allowance, and a tight restored cap then refuses dispatches the original clamped estimates admitted; that direction is fail closed, never silent uncapping.

    The per-account audit fold (RV1505, the audit half). accountSpendFromJournal, exported from @rulvar/core, folds the same settled entries the cost report folds into each budget account's INCLUSIVE spend, with the account tree read from the journaled spawn-admission decisions, so a host can hold any orchestrator cap or child allowance against what its subtree actually spent on a plain stored journal. Abandoned subtrees and unpriced slices contribute zero, exactly like the net total. Seeding the fold into re-opened accounts on resume is deliberately NOT wired yet: a rerun of a journaled invocation re-admits with exact-fill arithmetic today, so spend-at-reopen would refuse the continuation of the very work the money was spent on; the reopen seeding lands together with a seed-aware rerun re-admission, and the docs name the remaining amnesia instead of hiding it.

Patch Changes

1.158.0

Patch Changes

1.157.0

Patch Changes

1.156.0

Patch Changes

1.155.0

Patch Changes

1.154.0

Patch Changes

1.153.0

Patch Changes

1.152.0

Patch Changes

1.151.0

Patch Changes

1.150.0

Patch Changes

1.149.0

Patch Changes

1.148.0

Patch Changes

1.147.0

Patch Changes

1.146.0

Patch Changes

1.145.0

Patch Changes

1.144.0

Patch Changes

1.143.0

Patch Changes

1.142.0

Patch Changes

1.141.0

Patch Changes

1.140.0

Patch Changes

1.139.0

Patch Changes

1.138.0

Patch Changes

1.137.0

Patch Changes

1.136.0

Patch Changes

1.135.0

Patch Changes

1.134.0

Patch Changes

1.133.0

Patch Changes

1.132.0

Patch Changes

1.131.0

Patch Changes

1.130.0

Patch Changes

1.129.0

Patch Changes

1.128.0

Patch Changes

1.127.0

Patch Changes

1.126.0

Patch Changes

1.125.0

Patch Changes

1.124.0

Patch Changes

1.123.0

Patch Changes

1.122.0

Patch Changes

1.121.0

Patch Changes

1.120.0

Patch Changes

1.119.0

Patch Changes

1.118.0

Patch Changes

1.117.0

Patch Changes

1.116.0

Patch Changes

1.115.0

Patch Changes

1.114.0

Patch Changes

1.113.0

Patch Changes

1.112.0

Minor Changes

  • 00ae55b: Duplicate quota rules are refused at construction in every reference limiter (RV704). snapshotQuotaRules, the shared construction chokepoint of memoryQuotaLimiter, SqliteQuotaLimiter, and PostgresQuotaLimiter, now throws a typed ConfigError naming both indexes and the canonical quotaRuleKey when a rule set contains two identical rules. Before the refusal, the same duplicated configuration admitted differently per storage: the memory reference buckets by rule index, so each copy counted independently and the full cap admitted, while the store references bucket by rule key, so one shared bucket was debited once per matching copy and half the cap admitted (a cap-4 set granted 4 in memory and 2 on sqlite), breaking storage parity with a configuration nothing had refused. @rulvar/store-conformance gains quotaRulesConformance, the executable construction contract any limiter implementation can register.

Patch Changes

1.111.0

Patch Changes

1.110.0

Patch Changes

1.109.0

Patch Changes

1.108.0

Patch Changes

1.107.0

Patch Changes

1.106.0

Patch Changes

1.105.0

Patch Changes

1.104.0

Patch Changes

1.103.0

Patch Changes

1.102.0

Patch Changes

1.101.0

Patch Changes

1.100.0

Patch Changes

1.99.1

Patch Changes

  • ef08d73: Guarantee matrix and exactly-once claim hygiene (RV508); no runtime behavior changes. The isolated-executor guide now carries the guarantee matrix stating flatly who provides what: the library's layers give at-least-once execution with attempt binding and intent-before-effect, exactly-once effect execution is promised by NO library layer, and what IS exactly-once is pay and replay (the never-pay-twice invariant). The two claims the ninth comparison experiment's judge caught are rewritten to the precise statements ("each ran once" became attempt counting under a stable idempotency key; the approvals guide now says continuation is a run-level guarantee, not an effect-level one, with the at-least-once window named); ctx.step docs state the same window for effectful steps; a ResolutionBy note says the field records a channel, never a verified principal (identity, signatures, and separation of duties are host IAM). The worker header now points at the shipped SqliteQuotaLimiter and PostgresQuotaLimiter instead of denying that cross-process limiters exist. A new docs-lint sentinel forbids "exactly once" claims in the hand-written docs and in package source comments outside a vetted (file, heading anchor) allowlist (the durability pay doctrine and the guarantee matrix), and every remaining occurrence in doc prose and source comments was rewritten to the precise wording; string literals are deliberately out of scope (tool descriptions enter the toolset hash).
  • Updated dependencies [ef08d73]

1.99.0

Patch Changes

1.98.0

Patch Changes

1.97.0

Patch Changes

1.96.0

Patch Changes

1.95.0

Patch Changes

1.94.0

Patch Changes

1.93.0

Patch Changes

1.92.0

Patch Changes

1.91.0

Patch Changes

1.90.0

Minor Changes

  • 9603940: Scope the isolated-executor idempotency key to the run incarnation (RV403, the eighth-experiment review). A fresh run stamps the additive optional RunMeta.execKeyDerivation field (version 2) at genesis and every resume segment carries it verbatim; version 2 keys bind the run's generation token, so a deleteRun-then-recreate of the same explicit runId never reuses the deleted incarnation's keys against a long-lived external dedup store, while a crash-and-resume redispatch inside one incarnation keeps its key exactly as before. Runs recorded without the stamp derive the original genesis-free version 1 keys for their whole life, across resume and upgrade, so external dedup state accumulated for them stays valid; a recorded derivation the engine does not know, or a version 2 stamp whose store dropped the genesis token, is a typed resume refusal when executors are configured, never a silent fallback. The store conformance kit now checks the field's round trip alongside genesis.

Patch Changes

1.89.0

Patch Changes

1.88.0

Patch Changes

1.87.0

Patch Changes

1.86.0

Patch Changes

1.85.0

Patch Changes

1.84.0

Patch Changes

1.83.0

Patch Changes

1.82.0

Minor Changes

  • 9cc5d66: The free-cleanup harvest (cycle 80). leasableStoreConformance gains the expiry option: the mandatory lease checks follow the suite's no-wall-clock convention, so the harness now hands them a store whose ttl no scheduler stall can cross, and only the wall-clock expiry check keeps a short-ttl store of its own; the legacy single-ttlMs pairing let one CI stall past 150 ms expire a just-acquired lease inside a fencing check (the flake observed on Node 22). All three shipped harnesses move to the split pairing, and the store-authors guide stops recommending the flaky shape. In @rulvar/cli, worker retention is no longer slot-bound: a worker whose every concurrency slot is busy still applies retention over settled runs during its sweeps instead of starving until idle. In @rulvar/core, concurrent cold tools() calls on an MCP source share one in-flight tools/list fetch instead of each sweeping the list, and AdmissionController's maxTotalSpawns TSDoc now tells the truth: it is the controller-lifetime cap on admitted spawns for hosts driving the controller directly (pinned by a test), while engine runs cap totals through budgetDefaults.lifetimeSpawnCap; the old comment claimed it was the per-orchestrate maxSpawns.

Patch Changes

1.81.2

Patch Changes

1.81.1

Patch Changes

1.81.0

Patch Changes

1.80.0

Patch Changes

1.79.0

Patch Changes

1.78.0

Patch Changes

1.77.0

Patch Changes

1.76.0

Patch Changes

1.75.1

Patch Changes

1.75.0

Patch Changes

1.74.0

Patch Changes

1.73.0

Patch Changes

1.72.0

Patch Changes

1.71.0

Patch Changes

1.70.1

Patch Changes

  • ac57099: Kill-point suite hardening against loaded test runners: the worker's default lease ttl rises from 300 ms to 2000 ms, because a scheduler stall past the ttl between the worker's own renewals cancels the run by contract BEFORE the kill point is reached (the worker then exits zero as ran-to-completion and the scenario reads a self-inflicted takeover as a violation); the referee's post-kill wait is now the resume retry loop itself (each attempt against a live lease rejects typed with zero writes, so polling is free) instead of a fixed sleep; and the ran-to-completion violation names the worker's settled status for diagnosability. Only the killed owner is short-leased; referees and successor instances belong on their store's generous default ttl.

1.70.0

Minor Changes

  • 29141ed: The engine-level kill-point suite (the 1.65.0 experiment review, P1.10): killPointConformance spawns a child process that drives a scripted engine run over the consumer's store and SIGKILLs itself around each durable write, both brackets of all five points (the running entry, the ok terminal, the limit terminal, the run settle decision, the meta projection), then resumes the run from the referee process after the dead owner's lease lapses and asserts the documented recovery semantics with exact provider re-pay counts: the lost ok terminal is the only bracket that pays a step twice (the at-least-once window), a lost limit terminal re-pays only the turns since the last transcript boundary (the checkpoint restore), a durable limit terminal in a never-settled run re-runs the agent live in full (the second chance), and the settle and meta brackets recover as pure replays with exactly one ok run settle, a healed meta, and a contiguous journal. A worker that runs to completion is a violation, never a pass. runKillPointWorker plus killPointWorkerConfigFromEnv keep the consumer's writer script to a few lines, runKillPointScenario runs one scenario standalone, and KILL_POINT_SCENARIOS is the pinned table. SqliteStore and PostgresStore run the whole table in their own test suites (postgres gated on RULVAR_POSTGRES_URL).

Patch Changes

1.69.0

Patch Changes

1.68.0

Patch Changes

1.67.0

Patch Changes

1.66.0

Patch Changes

1.65.0

Patch Changes

1.64.0

Patch Changes

1.63.0

Minor Changes

  • 8a28aed: Durable settlement acknowledgement and the fencing-epoch tombstone (the 1.62.0 experiment review, P0.1 and P0.2).

    Settlement acknowledgement: a NON-fencing failure of either settlement write now rejects handle.result with the new typed SettlementError (code settlement, retryable; stage names the write, data carries the runId and the computed run status) instead of resolving as if nothing happened. Only a superseded segment's LeaseHeldError stays swallowed, on both writes, because the successor owns settlement. A failed run_settle append also skips the terminal meta write, so the projection can never run ahead of the journal (published 1.62.0 wrote meta ok over a journal with no settle record when the append failed). Recovery is deterministic and free: the run's work entries are already durable, engine.resume replays to the same outcome without one paid provider call and re-attempts the settlement writes (a non-empty journal with no recorded settle now re-settles on pure replay), and rulvar runs audit [--repair] reconciles offline.

    Fencing-epoch tombstone: SqliteStore and PostgresStore no longer erase the per-run epoch high-water mark on delete, so a recreate of the same explicit runId always acquires a strictly higher epoch and a zombie lease from the deleted incarnation (same runId, same stable owner identity) is rejected on every fenced surface instead of fencing green. The LeasableStore contract now states the rule, and the conformance kit enforces it with two new mandatory checks (fencing-epoch-tombstone in leasableStoreConformance, fenced-tombstone-zombie-rejected in fencedWritesConformance). The tombstone holds only the runId and a counter, never run content; the data-protection guide documents the erasure boundary.

Patch Changes

1.62.0

Patch Changes

1.61.0

Patch Changes

1.60.0

Patch Changes

1.59.4

Patch Changes

1.59.3

Patch Changes

1.59.2

Patch Changes

1.59.1

Patch Changes

1.59.0

Patch Changes

1.58.0

Patch Changes

1.57.0

Patch Changes

1.56.0

Patch Changes

1.55.0

Patch Changes

1.54.0

Patch Changes

1.53.0

Patch Changes

1.52.0

Patch Changes

1.51.0

Patch Changes

1.50.0

Patch Changes

1.49.0

Patch Changes

1.48.0

Minor Changes

  • 96093ea: Ship the adversarial multi-process soak and fix the SqliteStore concurrent-boot race it found (the fenced run state RFC, phase 3's last open item).

    The conformance kit gains the soak harness: runMultiProcessSoak spawns real OS processes that storm one store location through EVERY fenced write surface (journal append, meta write, transcript blob put and delete, fenced run deletion, renew, release) with stalls injected past the lease ttl, then rebuilds the one serial history the fencing epochs promise (accepted mutations ordered by epoch and per-tenure counter) and diffs it against the actual journal, meta row, and blobs. Any stale acceptance, lost accepted write, epoch inversion, or divergent final byte is a violation. The stale probe sweep re-reads the journal tail before each stale append attempt, so the monotonic-seq guard cannot mask a fencing hole; a live lease is also probed against a foreign run, and side runs get full create-and-fenced-delete cycles. The storm runs until an activity quorum is met (takeovers, per-surface accepted writes, typed stale rejections), so a slow machine storms longer instead of asserting on thin coverage. The child side is runSoakWriter plus soakWriterConfigFromEnv (the consumer's writer script constructs its store bare and passes a retryable hook for backend contention errors); the pure referee verifySoakHistory, the report tools parseSoakReport and countSoakActivity, and the quorum types are exported alongside.

    The soak's first storm against the published 1.47.0 never reached the fencing: N processes constructing SqliteStore over one SAME fresh file (an ordinary fleet start) collided in the constructor's schema bootstrap and the losers died with a raw SQLITE_BUSY (a 60 percent crash rate at six concurrent boots). A driver busy_timeout is not enough because the journal-mode conversion skips the busy handler on some lock transitions, so the constructor now retries the idempotent bootstrap as a unit through the SQLITE_BUSY family (extended result codes included, e.g. SQLITE_BUSY_RECOVERY while a sibling recovers the fresh WAL) under a wall-clock bound, exported as BOOT_BUSY_TIMEOUT_MS. Every runtime contention path keeps the documented fail-fast semantics. With the fix, 480 of 480 concurrent boots succeed, and the full storm (five writers, hundreds of takeovers, thousands of stale probes) holds every fenced surface with zero violations; SqliteStore now runs the soak and a concurrent-boot regression in its test suite.

Patch Changes

1.47.0

Patch Changes

1.46.0

Minor Changes

  • 865e7bf: Close finding F2 of the fenced run state RFC with the sqlite transcript twin. SqliteStore.transcripts() returns a TranscriptStore that declares fencedWrites because its blobs live in the store's own database, beside the lease rows: a lease-carrying put or delete verifies the current holder of the run the ref's leading path segment names atomically with the blob mutation, in the same one-immediate-transaction shape as the journal side, and rejects stale or cross-run holders with the typed LeaseHeldError leaving the prior blob byte intact. Demonstrated against the published 1.45.0 first: the engine threaded the superseded segment's lease into its late checkpoint save, both shipped transcript stores ignored it, and the blob at the deterministic ref both segments share regressed to older turn state (the state a later boot decodes, replaying turns the successor already paid for) while the same holder's journal append bounced typed. Over the { journal: store, transcripts: store.transcripts() } pair, assertFencedWrites now passes and every durable run mutation is fenced. The conformance kit gains fencedTranscriptsConformance, the executable definition of the transcript-side promise, taking a factory for the pair that shares the fencing domain; staleness is produced with release plus reacquire, so the suite needs no wall sleeps.

Patch Changes

1.45.0

Minor Changes

  • b96305d: The fenced writes capability (the fenced run state RFC, phase 2). JournalStore.putMeta and delete and TranscriptStore.put and delete accept the same optional trailing lease that append always took, and a store declares enforcement with the fencedWrites: true marker: a mutation carrying a lease that is not the current holder for the mutated run rejects with the typed LeaseHeldError, atomically and leaving nothing changed, including a live lease for a different run. The engine threads the segment's lease into every durable mutation of a leased resume (meta writes, checkpoints, compaction summaries, worktree patches, workflow sources), so over a declaring store a superseded worker can no longer overwrite the successor's meta at its late settle and strand the run from worker sweeps, and its very first refused meta write now fails the stale segment typed at boot with zero paid calls. SqliteStore declares the marker and enforces it on putMeta, delete, and append (with the run-match rule as defense in depth); the conformance kit gains fencedWritesConformance as the capability's executable definition; the queue worker's retention sweep passes its brief lease through the new optional second argument of engine.deleteRun (pruneRun takes the same); and hasFencedWrites plus assertFencedWrites let a host assert the full fence at deployment time. Stores written before the capability are untouched: without the marker the extra argument is ignored and the journal-append fence works exactly as before.

Patch Changes

1.44.1

Patch Changes

1.44.0

Patch Changes

1.43.0

Patch Changes

1.42.0

Patch Changes

1.41.0

Patch Changes

1.40.0

Patch Changes

1.39.0

Patch Changes

1.38.0

Patch Changes

1.37.0

Patch Changes

1.36.0

Patch Changes

1.35.0

Patch Changes

1.34.0

Patch Changes

1.33.0

Patch Changes

1.32.0

Patch Changes

1.31.0

Patch Changes

1.30.0

Patch Changes

1.29.0

Patch Changes

1.28.0

Patch Changes

1.27.0

Patch Changes

1.26.0

Minor Changes

  • a4fc757: SqliteStore implements the exact lookup capability (getMeta as a primary key query) and narrows status, statuses, and name in SQL over the JSON payload behind new expression indexes (created idempotently, so existing database files gain them on the next open), so a selective listRuns reads only the matching rows instead of decoding the whole catalog; the tags containment check stays in JS over the reduced set with unchanged semantics. The conformance kit checks the genesis round trip, that a statuses filter never drops a matching meta (supersets stay allowed), and that a store exposing getMeta agrees with listRuns and resolves undefined for a missing run. The planner's deterministic plan lookup reads one meta through the capability instead of scanning the catalog.

Patch Changes

1.25.0

Patch Changes

1.24.1

Patch Changes

1.24.0

Minor Changes

  • 2b033e8: Record the genesis args binding in RunMeta and make the dry-run preview mutation-free (the v1.23.0 review). RunMeta gains argsProvided (whether the run started with defined args) and argsHash (sha256 over the JCS canonical serialization of the genesis args, never the raw value), written by the engine at genesis and preserved verbatim by every resume segment, so hosts can refuse a resume whose re-supplied args silently diverge from the original invocation; the new public hashRunArgs() derives the same hash host-side. Legacy metas never gain the marker retroactively, and unserializable args record presence without a hash. A dryRun resume now performs ZERO store mutations by invariant: putMeta is skipped entirely (no status flip, no segments bump), the compiled-source blob is not re-put, and the Replayer's single append site refuses any journal append under replay-strict with a typed JournalMissError. The store conformance kit checks the round-trip of both new fields.

Patch Changes

1.23.0

Minor Changes

  • 1f9c272: PlanRunner spawn telemetry, the missing evals export, and the conformance kit's new meta field (v1.22.0 review P2-5, P2-6, P1-2).

    • @rulvar/plan: PlanRunner journals every admission INSIDE a carrying entry (decomposition rows in escalation decisions, ladder-verdict respawns, reuse and graft links, revision admissions) and emitted no spawn:admitted/spawn:rejected at all; a live PlanRunner run with admitted roots showed an event count of zero. Every embedded admission row now announces through one formatter, identically on the live path and on replay absorb, with replayed: true on recovered rows, entryRef on the journaled carrying entry, and agentType resolved from the landed specs.
    • @rulvar/evals: agentTypeRuleHolds joins the package root next to rungRuleHolds, exactly as the v1.21.0 changelog had already announced; a public-API test now imports the checkpoint quartet from the root. The evals guide gains a full measured-value checkpoint section (ladder/pool/cell/arm vocabulary, both criteria, the vacuous-pass guard, cost discipline, a runnable example).
    • @rulvar/store-conformance: the meta round-trip case now also pins the new optional RunMeta.segments field, which the engine bumps durably at every resume to keep event seq/spanId unique per run.

Patch Changes

1.22.0

Patch Changes

1.21.0

Patch Changes

1.20.0

Patch Changes

1.19.0

Patch Changes

  • Updated dependencies [8cc9a9c]
  • Updated dependencies [8cc9a9c]
  • Updated dependencies [8cc9a9c]

1.18.0

Patch Changes

1.17.0

Patch Changes

1.16.2

Patch Changes

1.16.1

Patch Changes

1.16.0

Patch Changes

1.15.0

Patch Changes

1.14.0

Patch Changes

1.13.0

Patch Changes

1.12.0

Patch Changes

1.11.0

Minor Changes

  • 0c70c5e: New mandatory obligation A5, monotonic seq: three new checks reject stores that persist duplicate or stale seqs. a5-monotonic-seq (a duplicate or stale append rejects with code journal_order_violation and never becomes visible, while the true next seq still lands), a5-stale-tail-race (two writers appending the same next seq: exactly one persists, the loser observes the typed conflict, reload shows a strictly increasing order), and a5-stale-replayer-fencing (the same race driven through two kernel Replayers from one loaded tail). The CommunityMemoryStore walkthrough listing gains the guard in step with docs/guide/store-authors.md. Third-party stores that pass the previous kit but accept duplicate seqs will fail the new checks until they add the guard; the obligation is documented in guide/stores and guide/store-authors.

Patch Changes

1.10.0

Patch Changes

1.9.0

Patch Changes

1.8.0

Patch Changes

  • Updated dependencies [25724b5]
  • Updated dependencies [57ea1de]
  • Updated dependencies [7884ec5]
  • Updated dependencies [52db30d]

1.7.0

Patch Changes

  • Updated dependencies [45285aa]
  • Updated dependencies [2f20d1d]
  • Updated dependencies [22f65a8]
  • Updated dependencies [2ddfa29]
  • Updated dependencies [2abd9c2]
  • Updated dependencies [1c1175d]

1.6.0

Patch Changes

  • da4dbad: Write the product name as Rulvar in prose: package READMEs, npm descriptions, and the documentation site now capitalize the brand. Identifiers keep their exact casing, so package names, the rulvar binary, rulvar.config.mjs, the .rulvar store directory, the rulvar.* OTel attributes, and every URL are unchanged. Documentation and metadata only; no runtime behaviour changes.
  • Updated dependencies [da4dbad]
  • Updated dependencies [487da86]
  • Updated dependencies [df416fc]
  • Updated dependencies [a737810]
  • Updated dependencies [9eb66b4]

1.5.2

Patch Changes

1.5.1

Patch Changes

1.5.0

Patch Changes

1.4.0

Minor Changes

  • c4f563d: Production readiness fixes from the July 2026 full audit.

    • The budgetUsd ceiling now survives resume: the engine records it in RunMeta.budgetUsd and restores it on every resume, so the replayed spend counts against the original invocation's bound and ResumeOptions still exposes no way to raise it. Journals written before the field existed (or read through a store that drops optional RunMeta fields) resume uncapped, exactly as before; the conformance kit gains a round-trip check so custom stores cannot drop the field silently.

    • spawn:rejected and resolution:applied / resolution:superseded are now emitted: live admission rejections carry the rejection code, agentType, and the journaled decision entryRef (absent only for pre-admission config gates), and live resolution attempts report winning or losing the first-closing-wins fold. spawn:admitted now carries the decision entryRef and the admitting verdict arm. The orchestrator:budget union member now types the two payload shapes actually emitted; journal:compat stays declared but unemitted (the scan runs before a run's event stream exists) and its TSDoc says so.

    • toOtel implements real parent-child span nesting when contextApi and setSpan are passed; without them spans stay flat but attributed.

    • 'readonly' isolation now compiles a deny rule for tools declaring risk write or destructive into the spawn's permission chain, exactly as the tools guide documents; read tools and other isolation modes are unaffected.

    • VCR replay() refuses a cassette recorded outside the engine's hashVersion support window ([CURRENT-1, CURRENT]) with a typed ConfigError instead of silently drifting; in-window cassettes replay as before.

    • InMemoryStore accepts { quiet: true } to opt out of the durability warning, and the warning text now states the precise truth: nothing survives a process exit and cross-process resume is impossible (same-process resume of a kept instance works). createTestEngine constructs its store quietly, so the blessed offline tier no longer prints a misleading warning.

    • The bare Date.now() / Math.random() development warnings no longer blame workflow code for calls that originate in library internals (the engine's own retry jitter, provider SDKs): the retry jitter uses a natively captured Math.random, and the in-process guard skips callers that live under node_modules.

    • rulvar run --profile now applies the profile's per-role effort hints: entries in defaults.routing that carry no effort are seeded from RunProfile.effortByRole (an explicit host effort always wins; ladder entries and unrouted roles stay untouched).

    • rulvar --help documents the shipped kb inbox and kb gate subcommands.

    • The unscoped rulvar pointer package ships TypeScript declarations (index.d.ts with a types export condition), so strict TypeScript projects can import the bare name; the install smoke gate now packs and checks the pointer alongside the umbrella.

Patch Changes

1.3.2

Patch Changes

  • ddef383: Every published package now ships a README, so its npm page states what the package is, how it installs, and where the documentation lives (npm includes README.md in the tarball regardless of the files allowlist, so no manifest changes are involved; @rulvar/compat gains its README on its own next release). Alongside, the repository-level pages are refreshed to the current project state: the root README is rewritten around the never-pay-twice pitch with a runnable quickstart condensation and the full package table, CONTRIBUTING.md lists the complete PR gate set, the examples README drops retired-spec citations for live docs.rulvar.com links and documents the dogfood journal replay, and the pointer README gets the same treatment.
  • Updated dependencies [ddef383]

1.3.1

Patch Changes

  • 7d1552e: Runtime message strings no longer cite the retired internal specification set: error and warning messages, validation issues, and the CLI help text drop the dangling docs/NN, section ... references, pointing at https://docs.rulvar.com pages where a pointer earns its place (the CLI help header, tool naming, toolset registries, bare resume). The umbrella package description sheds the naming-contingency note: the unscoped alias is published and owned. Three strings embedded in frozen recordings stay byte-identical on purpose (the no-progress abort reason and two testing-internal recorder strings), as does the byte-locked golden-fold fixture. Test-file comments lose their citations too; test titles are unchanged.
  • Updated dependencies [7d1552e]

1.3.0

Patch Changes

1.2.0

Patch Changes

  • 154507b: TSDoc and inline comments no longer cite the retired internal specification set (the pre-docs-site docs/NN, section ... references). The citations either became links to the public documentation at docs.rulvar.com or were dropped where the comment already carried the rule; traceability markers (DEF-n, XF-nn, FR-nnn, OQ-nn, W-nnn) are untouched. Comment-only change: no runtime behavior, no API shapes, and no runtime message strings were modified; the frozen golden-fold fixture is byte-identical.
  • Updated dependencies [3bfaec0]
  • Updated dependencies [890f42c]
  • Updated dependencies [154507b]

1.1.0

Patch Changes

1.0.0

Minor Changes

  • 5f0fdcd: M9-T03: community adapter and store guides (docs/10 section 3.10; docs/11 M9 exit row "conformance kits published as community guides").

    • New informative docs: docs/guide-adapter-authors.md (wire mapping requirements, the Usage invariant checklist, caps posture, an adapter skeleton template, and the VCR-based contract-test pattern with record and hermetic replay legs) and docs/guide-store-authors.md (the storage contracts A1-A4 plus leasing and fencing, a complete minimal LeasableStore walkthrough with an injectable clock and release-surviving epochs, conformance kit wiring, common failure modes, and publishing checklists). Both are indexed in the docs README inventory.
    • @rulvar/store-conformance gains the dogfood suite: the guide's CommunityMemoryStore walkthrough listing runs VERBATIM through journalStoreConformance and leasableStoreConformance in CI, so the acceptance ("a third-party mock store built only from the guide passes conformance") holds permanently and the guide's code cannot rot.

Patch Changes

  • Updated dependencies [0e0b569]
  • Updated dependencies [b28b7a3]
  • Updated dependencies [b53a89e]
  • Updated dependencies [4454175]
  • Updated dependencies [6599ca8]
  • Updated dependencies [6649e5f]
  • Updated dependencies [fd2f83b]
  • Updated dependencies [01d6b2d]
  • Updated dependencies [9a20dbb]
  • Updated dependencies [0fbe7ea]
  • Updated dependencies [ebe0abc]
  • Updated dependencies [a3079d0]
  • Updated dependencies [596a39b]
  • Updated dependencies [464ab6e]

0.9.0

Patch Changes

  • Updated dependencies [84f94d4]
  • Updated dependencies [65c7b2c]
  • Updated dependencies [a2a3243]
  • Updated dependencies [ebc8101]

0.8.0

Patch Changes

  • Updated dependencies [85d55cf]
  • Updated dependencies [b88c9e3]
  • Updated dependencies [f3c4613]
  • Updated dependencies [a41c20f]
  • Updated dependencies [f4e70be]
  • Updated dependencies [75d1646]
  • Updated dependencies [0627413]
  • Updated dependencies [55c0f87]
  • Updated dependencies [fd33871]
  • Updated dependencies [e70e7f4]
  • Updated dependencies [bc9c903]

0.7.0

Patch Changes

  • Updated dependencies [fd1d06c]
  • Updated dependencies [6fcf296]
  • Updated dependencies [dcc97a9]
  • Updated dependencies [434dc83]
  • Updated dependencies [03173c1]
  • Updated dependencies [11c0afc]

0.6.0

Patch Changes

  • Updated dependencies [fa05007]
  • Updated dependencies [9234dc8]
  • Updated dependencies [644512c]
  • Updated dependencies [8a41656]
  • Updated dependencies [02f7f7a]

0.5.0

Patch Changes

  • Updated dependencies [ac274f4]
  • Updated dependencies [5735d92]
  • Updated dependencies [46ca98e]
  • Updated dependencies [8ae129e]
  • Updated dependencies [d1c4525]
  • Updated dependencies [b840aba]

0.4.0

Patch Changes

  • Updated dependencies [dfe03b5]
  • Updated dependencies [d2089a7]
  • Updated dependencies [3f60234]
  • Updated dependencies [f668890]
  • Updated dependencies [16d7aa6]
  • Updated dependencies [6513ce8]
  • Updated dependencies [7dad493]
  • Updated dependencies [2bbf180]

0.3.0

Minor Changes

  • 43444f6: M2-T11/T12: the executable store conformance kit and the M2 gating cassettes with frozen fixtures.

    @rulvar/store-conformance ships its first real API: journalStoreConformance (A1 append atomicity, A2 total per-run order, A3 read-your-writes, A4 opaque payload with read-side-only normalization, meta separation, the golden fold-state fixture with a frozen reference hash, the decide-once oracle, and the abandon-derived-skip fixture) and leasableStoreConformance (typed LeaseHeldError on held acquire, monotonic fencing epochs, stale-epoch appends rejected and invisible, released leases fenced from renew and append, optional ttl/renew-cadence timing checks), plus registerConformance for Vitest/Jest and the stableStringify fold-state hasher. InMemoryStore and JsonlFileStore pass; deliberately broken stores (reordering, normalizing, tearing, fencing-less) fail loudly.

    @rulvar/core kernel closes three DEF-1/DEF-4 gaps the cassettes gate: an abandon-covered hanging dispatch derives skipped instead of redispatching, abandon-covered operations contribute a zero ledger increment, the resume report lists covered entries as skipped (never orphaned), and an abandon over an already-resolved suspension folds to a noop with already_resolved (first-closing-wins per target, both closer kinds).

    @rulvar/testing ships the M2 cassette suite over committed frozen fixtures: the DEF-1 synthetic subset (abandon-subtree, memoize-classifier, v1-journal-on-v2), the DEF-4 set (timeout-vs-live-race, class-decision-fanout, abandon-then-crash-then-resume, abandon-vs-resolution-race, offline-invalid-then-valid, double-abandon-idempotent), the DEF-6 six IDs (resume-v1-on-engine-v2, resume-v1-with-inserted-call, suspended-v1-resolves-on-v2, reject-version-too-old via deriverV0Synthetic, reject-version-from-future, effort-defaults-shift), the mandatory mixed-version scenarios (ordinal-space split, forward-cursor preference, cross-version resolution, the compatibility and never-pay-twice-through-upgrade lemmas), and KeyDeriver contract tests against the frozen v2 golden identities including the docs/03 worked example. Fixture regeneration is deliberate: scripts/record-m2-cassettes.mjs rebuilds, and CI write protection (scripts/check-frozen-fixtures.mjs plus fixtures.sha256) fails any fixture diff shipped without the explicit bump token (the hyphenated compound of hashVersion and bump) in a changeset.

Patch Changes

  • Updated dependencies [43444f6]
  • Updated dependencies [279881b]
  • Updated dependencies [9fd0966]
  • Updated dependencies [24ebadf]
  • Updated dependencies [a1b35d3]
  • Updated dependencies [18a5821]

0.2.0

Patch Changes

  • Updated dependencies [c24228d]
  • Updated dependencies [c50871e]
  • Updated dependencies [1af8fb9]
  • Updated dependencies [1fe0249]
  • Updated dependencies [5c4fc32]

0.1.0

Minor Changes

  • f4e2be9: M0 repo bootstrap (v0.1.0, docs/10-implementation-plan.md section "M0"): monorepo scaffold on the committed toolchain (pnpm 11 workspaces with catalogs, TypeScript 6.0, tsdown, Vitest 4, ESLint 9 flat config, Turborepo 2, changesets fixed mode, npm trusted publishing), the docs/ canon as single source of truth, the L0 contracts skeleton in @rulvar/core, and the vendored dependencies (StandardSchemaV1/StandardJSONSchemaV1 types, the @cfworker/json-schema lineage validator subset, a first-party monotonic ULID). Placeholder scaffolds only: no public API ships in this release.

Patch Changes

@rulvar/store-postgres

1.252.0

Minor Changes

  • a7e589d: The durable admission bracket hardens on every seam the ninth experiment named (RV4804). The queued wait honors a verdict's retryAfterMs verbatim for its next sleep (pollMs stays the fallback cadence) and ends with the RUN: the run's cancel signal rides into the wait, so host abort and the deadline stop the polling, cancel the ticket best effort, and hand the run to its own cancellation machinery, where before a cancelled run camped in the queue forever. Renew failures are announced, never fatal: the first failure warns, a verify recover that no longer answers granted emits the new admission:lease-lost event once (the scheduler expired the grant and may re-admit the capacity while the holder is alive), and the run continues, because the wire quota still gates every dispatch and the settle release is idempotent. The postgres scheduler takes its schema-scoped advisory lock under a lock_timeout bound (lockTimeoutMs, default 10 seconds, validated typed): a holder that hangs mid-transaction used to block every lifecycle call of the whole fleet forever; past the bound the call refuses with the typed retryable LeaseHeldError instead of camping.

Patch Changes

  • Updated dependencies [3ccb6cf]
  • Updated dependencies [52d807f]
  • Updated dependencies [517ed00]
  • Updated dependencies [a7e589d]
  • Updated dependencies [76e95eb]

1.251.0

Patch Changes

  • Updated dependencies [e7e829c]
  • Updated dependencies [5982be8]
  • Updated dependencies [7c58fb2]
  • Updated dependencies [b3e465a]
  • Updated dependencies [c4e5d6a]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [0ae8b85]
  • Updated dependencies [7932936]
  • Updated dependencies [7932936]
  • Updated dependencies [88da0ed]
  • Updated dependencies [06c0e85]

1.250.0

Minor Changes

  • c5eb19c: The restoration generation (RV4503, plan 45, rfcs/effects.md section 4.5, item 3): SqliteStore and PostgresStore implement the EffectLaneStore capability, carrying a restoration generation OUTSIDE the journal bytes (a one-row table beside the leases). The restore runbook is one rule: after a point-in-time restore, call bumpRestorationGeneration() BEFORE the restored database becomes reachable to any worker, so the effect lane comes up with dispatch disabled by construction until an operator appends a fresh effect_epoch citing the bumped generation. The new effectLaneStoreConformance suite in @rulvar/store-conformance is the executable definition: generation starts at 0 and bumps monotonically (ELS1, ELS2), a bumped generation refuses every lane append until the fresh epoch (ELS3, the kill point 25 window, driven through the real writer over the real store), and a lane append under a non-current lease dies on the store's fence with nothing consumed (ELS4, the kill point 16 shape).
  • c6d197b: The reconciler, the trust envelope, and the whole kill point kit (RV4505, plan 45, rfcs/effects.md sections 3.1, 7, 8, 9). The sweep makes "every intent deterministically reaches confirmed, compensated, or quarantined" true: crossing reconcileBy quarantines whatever state with the state recorded, receipt waits and attempt budgets quarantine on exhaustion, lookups are bounded SEPARATELY through journaled effect_probe rows (countable from the journal alone, crash-proof), pre-terminal conflicting receipts quarantine, and effect authorizations past their deadline refuse durably instead of waiting forever. Receipt verification runs a declared trust envelope: issuer identity, per-class content bindings, key validity windows, revocation from its time forward, and the host's signature check; every failure classifies unverified, which routes to unknown. The post-restore reconciliation (kill 25) quarantines provider effects the journal cannot reconstruct by name (or the whole range without authoritative enumeration), and a restoration epoch stays undispatchable until the new effect_reconciliation_complete decision cites it. Section 9 telemetry folds effective dispositions (the compensated overlay included), pressure, duplicate classification, and open incidents. The kit exports all thirty effects.kill.* rows as named conformance checks parameterized by a store factory (ambiguous acks and restoration generations injected through delegating proxies, so any store qualifies), registered over the in-memory reference store in single-process posture and over the REAL sqlite and postgres stores in their own packages.
  • fed9db6: Durable admission over sqlite and postgres (RV4508, plan 45, rfcs/admission.md section 9): SqliteAdmissionScheduler and PostgresAdmissionScheduler persist the scheduler's WHOLE state as one plain-JSON document (AdmissionState, now exported with snapshot() and hydration on the reference core), committed atomically per lifecycle call inside a BEGIN IMMEDIATE transaction (sqlite) or an advisory-lock-serialized transaction (postgres). This is the RFC's first shipped durable shape, recorded as a deliberate decision: a single scheduler over durable state with deterministic ordering, where "state moved AND buckets moved" holds trivially because the whole document commits or none of it does; per-row schemas are an optimization the SPI does not require. A queued ticket survives its holder with position and arrival identity intact, re-enqueueing the same (unitId, generation) returns the SAME ticket, and settlement operation ids replay as durable no-ops across holders (a late-settlement debt entry lands exactly once).

Patch Changes

  • Updated dependencies [0e240b9]
  • Updated dependencies [6fe585e]
  • Updated dependencies [c5eb19c]
  • Updated dependencies [565c13b]
  • Updated dependencies [c6d197b]
  • Updated dependencies [c9d9729]
  • Updated dependencies [fed9db6]
  • Updated dependencies [df9ed76]
  • Updated dependencies [3020912]
  • Updated dependencies [d8d598d]

1.249.0

Patch Changes

  • Updated dependencies [8862133]
  • Updated dependencies [0d7a717]
  • Updated dependencies [e4428bd]
  • Updated dependencies [d6873c1]
  • Updated dependencies [4092e8d]
  • Updated dependencies [e086590]
  • Updated dependencies [1411938]
  • Updated dependencies [737d1ee]
  • Updated dependencies [634f966]
  • Updated dependencies [052cc26]
  • Updated dependencies [bbae134]

1.248.0

Patch Changes

  • Updated dependencies [8d0cd69]
  • Updated dependencies [81065e4]
  • Updated dependencies [8573f20]
  • Updated dependencies [95f6a5e]

1.247.0

Patch Changes

  • Updated dependencies [1933ecc]
  • Updated dependencies [db0a5f0]
  • Updated dependencies [b698726]
  • Updated dependencies [48348d2]
  • Updated dependencies [4cfa1cc]
  • Updated dependencies [4b7197a]
  • Updated dependencies [5ebc842]
  • Updated dependencies [16ff6b9]
  • Updated dependencies [0c9941d]

1.246.0

Patch Changes

  • Updated dependencies [d165b0c]
  • Updated dependencies [d59f4a0]
  • Updated dependencies [46907ac]
  • Updated dependencies [9929ad3]
  • Updated dependencies [1790a6a]

1.245.0

Patch Changes

  • Updated dependencies [b4d47a8]
  • Updated dependencies [dee6db4]
  • Updated dependencies [b85c113]
  • Updated dependencies [bc556e7]
  • Updated dependencies [9f11d29]
  • Updated dependencies [19bcea0]
  • Updated dependencies [60b461c]
  • Updated dependencies [61e3a1a]
  • Updated dependencies [a156b81]
  • Updated dependencies [0bd7045]

1.244.0

Patch Changes

  • Updated dependencies [38d839a]
  • Updated dependencies [ce13b0f]
  • Updated dependencies [4fa23e3]
  • Updated dependencies [6841c69]
  • Updated dependencies [f56721d]
  • Updated dependencies [c894a43]
  • Updated dependencies [f6944a3]
  • Updated dependencies [23fd0e0]

1.243.0

Patch Changes

  • Updated dependencies [746d1f4]
  • Updated dependencies [009b29c]
  • Updated dependencies [1674cbe]
  • Updated dependencies [bd096bc]

1.242.0

Patch Changes

  • Updated dependencies [6e3438e]
  • Updated dependencies [ba5cf67]
  • Updated dependencies [c2d1531]

1.241.0

Patch Changes

  • Updated dependencies [dbcdd24]
  • Updated dependencies [7ae7243]
  • Updated dependencies [4f832c4]
  • Updated dependencies [7452d3d]
  • Updated dependencies [a4e22bf]
  • Updated dependencies [82df4af]

1.240.0

Patch Changes

1.239.0

Patch Changes

  • Updated dependencies [74ce99a]
  • Updated dependencies [ccd0665]
  • Updated dependencies [0c5ce21]
  • Updated dependencies [0616934]

1.238.0

Patch Changes

  • Updated dependencies [cf00947]
  • Updated dependencies [c7b9382]
  • Updated dependencies [88aea96]
  • Updated dependencies [6da8d05]
  • Updated dependencies [eae5c4c]

1.237.0

Patch Changes

  • Updated dependencies [9d6a279]
  • Updated dependencies [49a98f6]
  • Updated dependencies [a734ca0]
  • Updated dependencies [deb406f]

1.236.0

Patch Changes

1.235.0

Patch Changes

  • Updated dependencies [ba4e10d]
  • Updated dependencies [172402b]
  • Updated dependencies [2ecd787]
  • Updated dependencies [e20a5e9]
  • Updated dependencies [98c8691]
  • Updated dependencies [c70def0]

1.234.0

Patch Changes

1.233.0

Patch Changes

  • Updated dependencies [48b5200]
  • Updated dependencies [73bc32b]
  • Updated dependencies [e63b743]
  • Updated dependencies [ef45da7]

1.232.0

Patch Changes

  • Updated dependencies [1440410]
  • Updated dependencies [6e467f4]
  • Updated dependencies [0b14293]
  • Updated dependencies [e3bcab2]
  • Updated dependencies [b55a0f7]

1.231.0

Patch Changes

  • Updated dependencies [4eb4b56]
  • Updated dependencies [bc8f09e]
  • Updated dependencies [ff9b8c2]

1.230.0

Patch Changes

1.229.0

Patch Changes

  • Updated dependencies [3370342]
  • Updated dependencies [2fb6656]
  • Updated dependencies [edce170]

1.228.0

Patch Changes

  • Updated dependencies [4034fac]
  • Updated dependencies [a54b085]
  • Updated dependencies [9d0a9be]
  • Updated dependencies [be9ef28]

1.227.0

Patch Changes

  • Updated dependencies [f262e9f]
  • Updated dependencies [f191ff7]
  • Updated dependencies [fbbfbe8]
  • Updated dependencies [263b5e8]
  • Updated dependencies [db4d56d]
  • Updated dependencies [41f93a9]
  • Updated dependencies [98c8ca9]

1.226.0

Patch Changes

1.225.0

Patch Changes

1.224.0

Patch Changes

1.223.0

Patch Changes

1.222.0

Patch Changes

1.221.0

Patch Changes

1.220.0

Patch Changes

1.219.0

Patch Changes

1.218.0

Patch Changes

1.217.0

Patch Changes

1.216.0

Patch Changes

1.215.0

Patch Changes

1.214.0

Patch Changes

1.213.0

Patch Changes

1.212.0

Patch Changes

1.211.0

Patch Changes

1.210.0

Patch Changes

1.209.0

Patch Changes

1.208.0

Patch Changes

1.207.0

Patch Changes

1.206.0

Patch Changes

1.205.0

Patch Changes

1.204.0

Patch Changes

1.203.0

Patch Changes

1.202.0

Patch Changes

1.201.0

Patch Changes

1.200.0

Patch Changes

1.199.0

Patch Changes

1.198.0

Patch Changes

1.197.0

Patch Changes

1.196.0

Patch Changes

1.195.0

Patch Changes

1.194.0

Patch Changes

1.193.0

Patch Changes

1.192.0

Patch Changes

1.191.0

Patch Changes

1.190.0

Patch Changes

1.189.0

Patch Changes

1.188.0

Patch Changes

1.187.0

Patch Changes

1.186.0

Patch Changes

1.185.0

Patch Changes

1.184.0

Patch Changes

1.183.0

Patch Changes

1.182.0

Patch Changes

1.181.0

Patch Changes

1.180.0

Patch Changes

1.179.0

Patch Changes

1.178.0

Patch Changes

1.177.0

Patch Changes

1.176.0

Patch Changes

1.175.0

Patch Changes

1.174.0

Patch Changes

1.173.0

Patch Changes

1.172.0

Patch Changes

1.171.0

Patch Changes

1.170.0

Patch Changes

1.169.0

Patch Changes

1.168.0

Patch Changes

1.167.0

Patch Changes

1.166.0

Patch Changes

1.165.0

Patch Changes

1.164.0

Patch Changes

1.163.0

Patch Changes

1.162.0

Patch Changes

1.161.0

Patch Changes

1.160.0

Patch Changes

1.159.0

Patch Changes

1.158.0

Patch Changes

1.157.0

Patch Changes

1.156.0

Patch Changes

1.155.0

Patch Changes

1.154.0

Patch Changes

1.153.0

Patch Changes

1.152.0

Patch Changes

1.151.0

Patch Changes

1.150.0

Patch Changes

1.149.0

Patch Changes

1.148.0

Patch Changes

1.147.0

Patch Changes

1.146.0

Patch Changes

1.145.0

Patch Changes

1.144.0

Patch Changes

1.143.0

Patch Changes

1.142.0

Patch Changes

1.141.0

Patch Changes

1.140.0

Minor Changes

  • 3044838: Both store limiters implement the optional QuotaLimiter.release (RV1103 + RV1104, the SPI method from RV1013): a cancelled admission returns exactly what it consumed, the admitted requests and the token estimate, to the window, from any process sharing the file (SqliteQuotaLimiter) or any host sharing the schema (PostgresQuotaLimiter, under the same advisory lock and generation fence as every admission). Unknown, expired, and repeated ids are no-ops; a rolled-over window already aged the estimate out; a released id settles nothing afterwards; verdicts mirror memoryQuotaLimiter exactly. Both reservation tables grew a requests column, migrated in place on boot (sqlite: a serialized ALTER under BEGIN IMMEDIATE; postgres: ADD COLUMN IF NOT EXISTS under the boot lock) defaulting to 1, the single request every engine admission reserves, so pre-release reservations release exactly what their admission consumed.

Patch Changes

1.139.0

Patch Changes

1.138.0

Patch Changes

1.137.0

Patch Changes

1.136.0

Patch Changes

1.135.0

Patch Changes

1.134.0

Patch Changes

1.133.0

Patch Changes

1.132.0

Patch Changes

1.131.0

Patch Changes

1.130.0

Patch Changes

1.129.0

Patch Changes

1.128.0

Minor Changes

  • 27c4e38: pause_turn continuations become accounted wire units (RV905, the thirteenth experiment's fifth release risk). The Anthropic adapter absorbs server-side turn pauses by re-sending, making up to six wire requests inside ONE core dispatch; until now the request quota window, the provider call record, and the invoice row all saw one, and a per-request provider statement matched one segment while the rest read statement-only.

    The adapter's finish metadata now names the whole segment set (providerMetadata.anthropic.wireRequests = { count, responseIds }); the provider call record and the invoice row carry wireResponseIds; and the quota reconciliation settles the reservation against the TRUE wire request count. The QuotaLimiter.reconcile SPI gains an optional actual.requests argument, honored by all three reference limiters through one shared arithmetic (quotaActualRequestsDelta), so a window that admitted one request per reservation now reflects what the provider's own RPM meter saw; a settlement only ever adds, never denies retroactively, and implementations written against the two-argument form remain valid. reconcileStatement joins a multi-wire invoice row by ANY id of its segment set, all-or-nothing: a partially delivered segment set reads partial-coverage with its delivered segments never counted as statement-only (and never no-overlap when segments touched our data), and provider-reported token counts compare as the SUM over the segments against the dispatch's recorded usage. Single-wire dispatches carry none of the new fields and stay byte-identical, journals and events included.

Patch Changes

1.127.0

Patch Changes

1.126.0

Patch Changes

1.125.0

Patch Changes

1.124.0

Patch Changes

1.123.0

Patch Changes

1.122.0

Patch Changes

1.121.0

Patch Changes

1.120.0

Patch Changes

1.119.0

Patch Changes

1.118.0

Patch Changes

1.117.0

Patch Changes

1.116.0

Patch Changes

1.115.0

Patch Changes

1.114.0

Patch Changes

1.113.0

Patch Changes

1.112.0

Patch Changes

1.111.0

Patch Changes

1.110.0

Patch Changes

1.109.0

Patch Changes

1.108.0

Patch Changes

1.107.0

Patch Changes

1.106.0

Patch Changes

1.105.0

Minor Changes

  • 531dc88: Make quota rules an immutable snapshot with a canonical denial order in all three limiters, and give the postgres limiter rotation generations, a fenced stale host, a bounded bootstrap, and strict intake (RV608).

    Immutable snapshot (all three limiters): memoryQuotaLimiter, SqliteQuotaLimiter, and PostgresQuotaLimiter now admit under the new exported snapshotQuotaRules(rules): a validated, frozen copy carrying only the known rule fields, taken at construction. Mutating the caller's array or rule objects afterwards (a pushed rule, a reassigned cap) can no longer change a decision, a bucket key, telemetry, or the fingerprint the postgres schema records; previously the caller's live graph was read on every admission and the fingerprint was computed lazily from it at first boot. The canonical per-rule content key is also exported as quotaRuleKey, and every limiter folds a denial over matching rules in that canonical order, so permuted but identical rule sets now produce the byte-identical refusal object (reason and retryAfterMs), not just the same fingerprint.

    Rotation generations (postgres): rulvar_quota_meta now records a rules generation beside the fingerprint. Every admission re-reads both inside its own locked transaction and, on a mismatch, is refused with the new typed QuotaGenerationError instead of admitting under retired bucket keys, so a host that booted before a rotation is fenced rather than silently splitting the budget; its next call re-boots into the honest boot-time ConfigError, and its outstanding reservations age out with their window. Rotation (acceptRulesUpdate: true) now serializes with in-flight admissions on the same advisory lock, bumps the generation, and carries current-window consumption conservatively: a new bucket inherits the retired bucket's counters for the same (provider, model, tenant) dimension triple (the maximum when several retired rules share it), so a raised cap grants only the difference, a lowered cap counts what was already consumed, and a genuinely new dimension starts empty. The carry decision is conservative by design: estimates held by fenced hosts settle nowhere and age out, which errs toward under-admission inside the rotation window, never over.

    Bounded bootstrap and honest deadline phases (postgres): the bootstrap transaction now runs under the same SET LOCAL lock_timeout as admissions (a held boot lock used to wait unboundedly), and its connection is registered with the full-path deadline, which destroys it on expiry so an abandoned bootstrap can never commit DDL or a rotation after the caller was already refused. QuotaDeadlineError.phase gains 'bootstrap', and each phase's message now narrates only what actually happened: an 'acquire' refusal held no connection and no longer claims one was destroyed.

    Strict intake (postgres): acceptRulesUpdate is runtime-checked as a real boolean (the string "false" used to enable rotation by truthiness), and admissionDeadlineMs is refused above the Node timer maximum (2147483647 ms, now exported from @rulvar/core as MAX_TIMER_DELAY_MS) before the pool is constructed; above it, the deadline timer used to clamp and refuse every admission after about a millisecond.

    Migration note: hosts running mixed rule sets over one schema now fail loud during a rotation instead of silently splitting the budget: old booted hosts receive QuotaGenerationError on their next admission the moment a new deployment boots with acceptRulesUpdate: true. That refusal is the designed rollout signal, not a regression; roll the refused hosts to the new rule set and remove the flag. Existing recorded fingerprints keep matching (the key encoding is unchanged), and pre-generation schemas are backfilled to generation 1 on the first matching boot.

Patch Changes

1.104.0

Patch Changes

1.103.0

Patch Changes

1.102.0

Patch Changes

1.101.0

Patch Changes

1.100.0

Patch Changes

1.99.1

Patch Changes

1.99.0

Patch Changes

1.98.0

Minor Changes

  • 6c7fbd8: PostgresQuotaLimiter bounds each WHOLE admission path and fingerprints the shared rules (RV506). New admissionDeadlineMs (default the exported QUOTA_ADMISSION_DEADLINE_MS, 5000 ms; refused at construction unless it exceeds the internal QUOTA_LOCK_TIMEOUT_MS stage bound) races lazy bootstrap, pool checkout, and the admission transaction together: before, the 2000 ms lock bound covered only the lock-wait stage, so a call could spend it once at checkout and again at the lock without ever being refused. Expiry throws a typed QuotaDeadlineError into the engine's onLimiterError policy and destroys the held connection via release(err) instead of returning it dirty to the pool. Boot now records quotaRulesFingerprint(rules) (exported; sha256 over the canonical rule keys, insensitive to array order) in a new rulvar_quota_meta table under the boot lock, and refuses an instance whose rule set drifted with a typed ConfigError naming both hashes and the schema, so mismatched hosts can no longer silently split one budget across different bucket keys; rotation is the explicit acceptRulesUpdate: true opt-in (enable on the new deployment, roll every host, remove the flag).

Patch Changes

1.97.0

Patch Changes

1.96.0

Patch Changes

1.95.0

Minor Changes

  • 2bda821: PostgresQuotaLimiter (RV410): the multi-host reference implementation of the core QuotaLimiter SPI. Engine processes on any number of hosts pointing instances at one database and schema enforce one global provider quota: admission consumes the window counters inside a single transaction serialized on a schema-wide advisory lock, so two hosts can never both take the last slot; reservations are rows, so reconciliation settles a grant from any host; both tables are lazily pruned to two accounting windows. The rule model, the fixed epoch-aligned one-minute windows, and the admission decision are the core's own exported functions, so this limiter, memoryQuotaLimiter, and SqliteQuotaLimiter agree byte for byte on every verdict. A call still waiting for the admission lock past the exported QUOTA_LOCK_TIMEOUT_MS (2000 ms) throws into the engine's onLimiterError policy instead of hanging. The durable admission queue stays the host's documented boundary: a denial carries the honest window remainder, and what to do while waiting is host policy.

Patch Changes

1.94.0

Patch Changes

1.93.0

Patch Changes

1.92.0

Patch Changes

1.91.0

Patch Changes

1.90.0

Patch Changes

1.89.0

Patch Changes

1.88.0

Patch Changes

1.87.0

Patch Changes

1.86.0

Patch Changes

1.85.0

Patch Changes

1.84.0

Patch Changes

1.83.0

Patch Changes

1.82.0

Patch Changes

1.81.2

Patch Changes

1.81.1

Patch Changes

1.81.0

Patch Changes

1.80.0

Patch Changes

1.79.0

Patch Changes

1.78.0

Patch Changes

1.77.0

Patch Changes

1.76.0

Patch Changes

1.75.1

Patch Changes

1.75.0

Patch Changes

1.74.0

Patch Changes

1.73.0

Patch Changes

1.72.0

Patch Changes

1.71.0

Patch Changes

1.70.1

Patch Changes

1.70.0

Patch Changes

1.69.0

Patch Changes

1.68.0

Patch Changes

1.67.0

Patch Changes

1.66.0

Patch Changes

1.65.0

Patch Changes

1.64.0

Patch Changes

1.63.0

Minor Changes

  • 8a28aed: Durable settlement acknowledgement and the fencing-epoch tombstone (the 1.62.0 experiment review, P0.1 and P0.2).

    Settlement acknowledgement: a NON-fencing failure of either settlement write now rejects handle.result with the new typed SettlementError (code settlement, retryable; stage names the write, data carries the runId and the computed run status) instead of resolving as if nothing happened. Only a superseded segment's LeaseHeldError stays swallowed, on both writes, because the successor owns settlement. A failed run_settle append also skips the terminal meta write, so the projection can never run ahead of the journal (published 1.62.0 wrote meta ok over a journal with no settle record when the append failed). Recovery is deterministic and free: the run's work entries are already durable, engine.resume replays to the same outcome without one paid provider call and re-attempts the settlement writes (a non-empty journal with no recorded settle now re-settles on pure replay), and rulvar runs audit [--repair] reconciles offline.

    Fencing-epoch tombstone: SqliteStore and PostgresStore no longer erase the per-run epoch high-water mark on delete, so a recreate of the same explicit runId always acquires a strictly higher epoch and a zombie lease from the deleted incarnation (same runId, same stable owner identity) is rejected on every fenced surface instead of fencing green. The LeasableStore contract now states the rule, and the conformance kit enforces it with two new mandatory checks (fencing-epoch-tombstone in leasableStoreConformance, fenced-tombstone-zombie-rejected in fencedWritesConformance). The tombstone holds only the runId and a counter, never run content; the data-protection guide documents the erasure boundary.

Patch Changes

1.62.0

Patch Changes

1.61.0

Patch Changes

1.60.0

Patch Changes

1.59.4

Patch Changes

1.59.3

Patch Changes

1.59.2

Patch Changes

1.59.1

Patch Changes

1.59.0

Patch Changes

1.58.0

Patch Changes

1.57.0

Minor Changes

  • dc6ef2c: RV-214: the official PostgreSQL store. The new @rulvar/store-postgres package ships PostgresStore, implementing the full storage contract over node-postgres for multi-process AND multi-host deployments: JournalStore plus LeasableStore with fencing epochs, fencedWrites on both the journal side and the transcripts() twin, and the getMeta/leaseTtlMs capabilities. Payloads stay opaque TEXT (obligation A4 forbids jsonb normalization; jsonb appears only in query-side casts and expression indexes). Every run-scoped mutation runs inside one transaction that first takes a per-run advisory transaction lock, this store's translation of the sqlite BEGIN IMMEDIATE lesson: the fence check and the guarded mutation commit as ONE serialized unit across processes and hosts, at per-run granularity so unrelated runs never queue behind each other. The A5 monotonic-seq guard is one conditional INSERT under that lock, with per-instance appends chained in submission order (a genuinely async pool would otherwise let a later-submitted seq reach the server first). The lazy idempotent schema bootstrap serializes on a schema-scoped advisory lock so a fleet start over one fresh database boots clean; the schema option namespaces the five tables and doubles as cheap isolation. Lease expiry uses the client clock with an injectable now (NTP-synced hosts; the 60 s default ttl dwarfs sane drift), and one write region per run is the documented boundary. The package's own suite runs the full conformance kit, cross-instance fencing over one schema, an engine-level e2e (run on one store instance, resume from another with zero adapter calls), the adversarial multi-process soak, and the fleet boot race, all against a real postgres (gated on RULVAR_POSTGRES_URL; CI provides a service container). The stores guide documents options, pooling and backpressure sizing, the clock and single-write-region boundaries, and a backup/PITR runbook.

Patch Changes

@rulvar/store-sqlite

1.252.0

Patch Changes

  • Updated dependencies [3ccb6cf]
  • Updated dependencies [52d807f]
  • Updated dependencies [517ed00]
  • Updated dependencies [a7e589d]
  • Updated dependencies [76e95eb]

1.251.0

Patch Changes

  • Updated dependencies [e7e829c]
  • Updated dependencies [5982be8]
  • Updated dependencies [7c58fb2]
  • Updated dependencies [b3e465a]
  • Updated dependencies [c4e5d6a]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [0ae8b85]
  • Updated dependencies [7932936]
  • Updated dependencies [7932936]
  • Updated dependencies [88da0ed]
  • Updated dependencies [06c0e85]

1.250.0

Minor Changes

  • c5eb19c: The restoration generation (RV4503, plan 45, rfcs/effects.md section 4.5, item 3): SqliteStore and PostgresStore implement the EffectLaneStore capability, carrying a restoration generation OUTSIDE the journal bytes (a one-row table beside the leases). The restore runbook is one rule: after a point-in-time restore, call bumpRestorationGeneration() BEFORE the restored database becomes reachable to any worker, so the effect lane comes up with dispatch disabled by construction until an operator appends a fresh effect_epoch citing the bumped generation. The new effectLaneStoreConformance suite in @rulvar/store-conformance is the executable definition: generation starts at 0 and bumps monotonically (ELS1, ELS2), a bumped generation refuses every lane append until the fresh epoch (ELS3, the kill point 25 window, driven through the real writer over the real store), and a lane append under a non-current lease dies on the store's fence with nothing consumed (ELS4, the kill point 16 shape).
  • c6d197b: The reconciler, the trust envelope, and the whole kill point kit (RV4505, plan 45, rfcs/effects.md sections 3.1, 7, 8, 9). The sweep makes "every intent deterministically reaches confirmed, compensated, or quarantined" true: crossing reconcileBy quarantines whatever state with the state recorded, receipt waits and attempt budgets quarantine on exhaustion, lookups are bounded SEPARATELY through journaled effect_probe rows (countable from the journal alone, crash-proof), pre-terminal conflicting receipts quarantine, and effect authorizations past their deadline refuse durably instead of waiting forever. Receipt verification runs a declared trust envelope: issuer identity, per-class content bindings, key validity windows, revocation from its time forward, and the host's signature check; every failure classifies unverified, which routes to unknown. The post-restore reconciliation (kill 25) quarantines provider effects the journal cannot reconstruct by name (or the whole range without authoritative enumeration), and a restoration epoch stays undispatchable until the new effect_reconciliation_complete decision cites it. Section 9 telemetry folds effective dispositions (the compensated overlay included), pressure, duplicate classification, and open incidents. The kit exports all thirty effects.kill.* rows as named conformance checks parameterized by a store factory (ambiguous acks and restoration generations injected through delegating proxies, so any store qualifies), registered over the in-memory reference store in single-process posture and over the REAL sqlite and postgres stores in their own packages.
  • fed9db6: Durable admission over sqlite and postgres (RV4508, plan 45, rfcs/admission.md section 9): SqliteAdmissionScheduler and PostgresAdmissionScheduler persist the scheduler's WHOLE state as one plain-JSON document (AdmissionState, now exported with snapshot() and hydration on the reference core), committed atomically per lifecycle call inside a BEGIN IMMEDIATE transaction (sqlite) or an advisory-lock-serialized transaction (postgres). This is the RFC's first shipped durable shape, recorded as a deliberate decision: a single scheduler over durable state with deterministic ordering, where "state moved AND buckets moved" holds trivially because the whole document commits or none of it does; per-row schemas are an optimization the SPI does not require. A queued ticket survives its holder with position and arrival identity intact, re-enqueueing the same (unitId, generation) returns the SAME ticket, and settlement operation ids replay as durable no-ops across holders (a late-settlement debt entry lands exactly once).

Patch Changes

  • Updated dependencies [0e240b9]
  • Updated dependencies [6fe585e]
  • Updated dependencies [c5eb19c]
  • Updated dependencies [565c13b]
  • Updated dependencies [c6d197b]
  • Updated dependencies [c9d9729]
  • Updated dependencies [fed9db6]
  • Updated dependencies [df9ed76]
  • Updated dependencies [3020912]
  • Updated dependencies [d8d598d]

1.249.0

Patch Changes

  • Updated dependencies [8862133]
  • Updated dependencies [0d7a717]
  • Updated dependencies [e4428bd]
  • Updated dependencies [d6873c1]
  • Updated dependencies [4092e8d]
  • Updated dependencies [e086590]
  • Updated dependencies [1411938]
  • Updated dependencies [737d1ee]
  • Updated dependencies [634f966]
  • Updated dependencies [052cc26]
  • Updated dependencies [bbae134]

1.248.0

Patch Changes

  • Updated dependencies [8d0cd69]
  • Updated dependencies [81065e4]
  • Updated dependencies [8573f20]
  • Updated dependencies [95f6a5e]

1.247.0

Patch Changes

  • Updated dependencies [1933ecc]
  • Updated dependencies [db0a5f0]
  • Updated dependencies [b698726]
  • Updated dependencies [48348d2]
  • Updated dependencies [4cfa1cc]
  • Updated dependencies [4b7197a]
  • Updated dependencies [5ebc842]
  • Updated dependencies [16ff6b9]
  • Updated dependencies [0c9941d]

1.246.0

Patch Changes

  • Updated dependencies [d165b0c]
  • Updated dependencies [d59f4a0]
  • Updated dependencies [46907ac]
  • Updated dependencies [9929ad3]
  • Updated dependencies [1790a6a]

1.245.0

Patch Changes

  • Updated dependencies [b4d47a8]
  • Updated dependencies [dee6db4]
  • Updated dependencies [b85c113]
  • Updated dependencies [bc556e7]
  • Updated dependencies [9f11d29]
  • Updated dependencies [19bcea0]
  • Updated dependencies [60b461c]
  • Updated dependencies [61e3a1a]
  • Updated dependencies [a156b81]
  • Updated dependencies [0bd7045]

1.244.0

Patch Changes

  • Updated dependencies [38d839a]
  • Updated dependencies [ce13b0f]
  • Updated dependencies [4fa23e3]
  • Updated dependencies [6841c69]
  • Updated dependencies [f56721d]
  • Updated dependencies [c894a43]
  • Updated dependencies [f6944a3]
  • Updated dependencies [23fd0e0]

1.243.0

Patch Changes

  • Updated dependencies [746d1f4]
  • Updated dependencies [009b29c]
  • Updated dependencies [1674cbe]
  • Updated dependencies [bd096bc]

1.242.0

Patch Changes

  • Updated dependencies [6e3438e]
  • Updated dependencies [ba5cf67]
  • Updated dependencies [c2d1531]

1.241.0

Patch Changes

  • Updated dependencies [dbcdd24]
  • Updated dependencies [7ae7243]
  • Updated dependencies [4f832c4]
  • Updated dependencies [7452d3d]
  • Updated dependencies [a4e22bf]
  • Updated dependencies [82df4af]

1.240.0

Patch Changes

1.239.0

Patch Changes

  • Updated dependencies [74ce99a]
  • Updated dependencies [ccd0665]
  • Updated dependencies [0c5ce21]
  • Updated dependencies [0616934]

1.238.0

Patch Changes

  • Updated dependencies [cf00947]
  • Updated dependencies [c7b9382]
  • Updated dependencies [88aea96]
  • Updated dependencies [6da8d05]
  • Updated dependencies [eae5c4c]

1.237.0

Patch Changes

  • Updated dependencies [9d6a279]
  • Updated dependencies [49a98f6]
  • Updated dependencies [a734ca0]
  • Updated dependencies [deb406f]

1.236.0

Patch Changes

1.235.0

Patch Changes

  • Updated dependencies [ba4e10d]
  • Updated dependencies [172402b]
  • Updated dependencies [2ecd787]
  • Updated dependencies [e20a5e9]
  • Updated dependencies [98c8691]
  • Updated dependencies [c70def0]

1.234.0

Patch Changes

1.233.0

Patch Changes

  • Updated dependencies [48b5200]
  • Updated dependencies [73bc32b]
  • Updated dependencies [e63b743]
  • Updated dependencies [ef45da7]

1.232.0

Patch Changes

  • Updated dependencies [1440410]
  • Updated dependencies [6e467f4]
  • Updated dependencies [0b14293]
  • Updated dependencies [e3bcab2]
  • Updated dependencies [b55a0f7]

1.231.0

Patch Changes

  • Updated dependencies [4eb4b56]
  • Updated dependencies [bc8f09e]
  • Updated dependencies [ff9b8c2]

1.230.0

Patch Changes

1.229.0

Patch Changes

  • Updated dependencies [3370342]
  • Updated dependencies [2fb6656]
  • Updated dependencies [edce170]

1.228.0

Patch Changes

  • Updated dependencies [4034fac]
  • Updated dependencies [a54b085]
  • Updated dependencies [9d0a9be]
  • Updated dependencies [be9ef28]

1.227.0

Patch Changes

  • Updated dependencies [f262e9f]
  • Updated dependencies [f191ff7]
  • Updated dependencies [fbbfbe8]
  • Updated dependencies [263b5e8]
  • Updated dependencies [db4d56d]
  • Updated dependencies [41f93a9]
  • Updated dependencies [98c8ca9]

1.226.0

Patch Changes

1.225.0

Patch Changes

1.224.0

Patch Changes

1.223.0

Patch Changes

1.222.0

Patch Changes

1.221.0

Patch Changes

1.220.0

Patch Changes

1.219.0

Patch Changes

1.218.0

Patch Changes

1.217.0

Patch Changes

1.216.0

Patch Changes

1.215.0

Patch Changes

1.214.0

Patch Changes

1.213.0

Patch Changes

1.212.0

Patch Changes

1.211.0

Patch Changes

1.210.0

Patch Changes

1.209.0

Patch Changes

1.208.0

Patch Changes

1.207.0

Patch Changes

1.206.0

Patch Changes

1.205.0

Patch Changes

1.204.0

Patch Changes

1.203.0

Patch Changes

1.202.0

Patch Changes

1.201.0

Patch Changes

1.200.0

Patch Changes

1.199.0

Patch Changes

1.198.0

Patch Changes

1.197.0

Patch Changes

1.196.0

Patch Changes

1.195.0

Patch Changes

1.194.0

Patch Changes

1.193.0

Patch Changes

1.192.0

Patch Changes

1.191.0

Patch Changes

1.190.0

Patch Changes

1.189.0

Patch Changes

1.188.0

Patch Changes

1.187.0

Patch Changes

1.186.0

Patch Changes

1.185.0

Patch Changes

1.184.0

Patch Changes

1.183.0

Patch Changes

1.182.0

Patch Changes

1.181.0

Patch Changes

1.180.0

Patch Changes

1.179.0

Patch Changes

1.178.0

Patch Changes

1.177.0

Patch Changes

1.176.0

Patch Changes

1.175.0

Patch Changes

1.174.0

Patch Changes

1.173.0

Patch Changes

1.172.0

Patch Changes

1.171.0

Patch Changes

1.170.0

Patch Changes

1.169.0

Patch Changes

1.168.0

Patch Changes

1.167.0

Patch Changes

1.166.0

Patch Changes

1.165.0

Patch Changes

1.164.0

Patch Changes

1.163.0

Patch Changes

1.162.0

Patch Changes

1.161.0

Patch Changes

1.160.0

Patch Changes

1.159.0

Patch Changes

1.158.0

Patch Changes

1.157.0

Patch Changes

1.156.0

Patch Changes

1.155.0

Patch Changes

1.154.0

Patch Changes

1.153.0

Patch Changes

1.152.0

Patch Changes

1.151.0

Patch Changes

1.150.0

Patch Changes

1.149.0

Patch Changes

1.148.0

Patch Changes

1.147.0

Patch Changes

1.146.0

Patch Changes

1.145.0

Patch Changes

1.144.0

Patch Changes

1.143.0

Patch Changes

1.142.0

Patch Changes

1.141.0

Patch Changes

1.140.0

Minor Changes

  • 3044838: Both store limiters implement the optional QuotaLimiter.release (RV1103 + RV1104, the SPI method from RV1013): a cancelled admission returns exactly what it consumed, the admitted requests and the token estimate, to the window, from any process sharing the file (SqliteQuotaLimiter) or any host sharing the schema (PostgresQuotaLimiter, under the same advisory lock and generation fence as every admission). Unknown, expired, and repeated ids are no-ops; a rolled-over window already aged the estimate out; a released id settles nothing afterwards; verdicts mirror memoryQuotaLimiter exactly. Both reservation tables grew a requests column, migrated in place on boot (sqlite: a serialized ALTER under BEGIN IMMEDIATE; postgres: ADD COLUMN IF NOT EXISTS under the boot lock) defaulting to 1, the single request every engine admission reserves, so pre-release reservations release exactly what their admission consumed.

Patch Changes

1.139.0

Patch Changes

1.138.0

Patch Changes

1.137.0

Patch Changes

1.136.0

Patch Changes

1.135.0

Patch Changes

1.134.0

Patch Changes

1.133.0

Patch Changes

1.132.0

Patch Changes

1.131.0

Patch Changes

1.130.0

Patch Changes

1.129.0

Patch Changes

1.128.0

Minor Changes

  • 27c4e38: pause_turn continuations become accounted wire units (RV905, the thirteenth experiment's fifth release risk). The Anthropic adapter absorbs server-side turn pauses by re-sending, making up to six wire requests inside ONE core dispatch; until now the request quota window, the provider call record, and the invoice row all saw one, and a per-request provider statement matched one segment while the rest read statement-only.

    The adapter's finish metadata now names the whole segment set (providerMetadata.anthropic.wireRequests = { count, responseIds }); the provider call record and the invoice row carry wireResponseIds; and the quota reconciliation settles the reservation against the TRUE wire request count. The QuotaLimiter.reconcile SPI gains an optional actual.requests argument, honored by all three reference limiters through one shared arithmetic (quotaActualRequestsDelta), so a window that admitted one request per reservation now reflects what the provider's own RPM meter saw; a settlement only ever adds, never denies retroactively, and implementations written against the two-argument form remain valid. reconcileStatement joins a multi-wire invoice row by ANY id of its segment set, all-or-nothing: a partially delivered segment set reads partial-coverage with its delivered segments never counted as statement-only (and never no-overlap when segments touched our data), and provider-reported token counts compare as the SUM over the segments against the dispatch's recorded usage. Single-wire dispatches carry none of the new fields and stay byte-identical, journals and events included.

Patch Changes

1.127.0

Patch Changes

1.126.0

Patch Changes

1.125.0

Patch Changes

1.124.0

Patch Changes

1.123.0

Patch Changes

1.122.0

Patch Changes

1.121.0

Patch Changes

1.120.0

Patch Changes

1.119.0

Patch Changes

1.118.0

Patch Changes

1.117.0

Patch Changes

1.116.0

Patch Changes

1.115.0

Patch Changes

1.114.0

Patch Changes

1.113.0

Patch Changes

1.112.0

Patch Changes

1.111.0

Patch Changes

1.110.0

Patch Changes

1.109.0

Patch Changes

1.108.0

Patch Changes

1.107.0

Patch Changes

1.106.0

Patch Changes

1.105.0

Minor Changes

  • 531dc88: Make quota rules an immutable snapshot with a canonical denial order in all three limiters, and give the postgres limiter rotation generations, a fenced stale host, a bounded bootstrap, and strict intake (RV608).

    Immutable snapshot (all three limiters): memoryQuotaLimiter, SqliteQuotaLimiter, and PostgresQuotaLimiter now admit under the new exported snapshotQuotaRules(rules): a validated, frozen copy carrying only the known rule fields, taken at construction. Mutating the caller's array or rule objects afterwards (a pushed rule, a reassigned cap) can no longer change a decision, a bucket key, telemetry, or the fingerprint the postgres schema records; previously the caller's live graph was read on every admission and the fingerprint was computed lazily from it at first boot. The canonical per-rule content key is also exported as quotaRuleKey, and every limiter folds a denial over matching rules in that canonical order, so permuted but identical rule sets now produce the byte-identical refusal object (reason and retryAfterMs), not just the same fingerprint.

    Rotation generations (postgres): rulvar_quota_meta now records a rules generation beside the fingerprint. Every admission re-reads both inside its own locked transaction and, on a mismatch, is refused with the new typed QuotaGenerationError instead of admitting under retired bucket keys, so a host that booted before a rotation is fenced rather than silently splitting the budget; its next call re-boots into the honest boot-time ConfigError, and its outstanding reservations age out with their window. Rotation (acceptRulesUpdate: true) now serializes with in-flight admissions on the same advisory lock, bumps the generation, and carries current-window consumption conservatively: a new bucket inherits the retired bucket's counters for the same (provider, model, tenant) dimension triple (the maximum when several retired rules share it), so a raised cap grants only the difference, a lowered cap counts what was already consumed, and a genuinely new dimension starts empty. The carry decision is conservative by design: estimates held by fenced hosts settle nowhere and age out, which errs toward under-admission inside the rotation window, never over.

    Bounded bootstrap and honest deadline phases (postgres): the bootstrap transaction now runs under the same SET LOCAL lock_timeout as admissions (a held boot lock used to wait unboundedly), and its connection is registered with the full-path deadline, which destroys it on expiry so an abandoned bootstrap can never commit DDL or a rotation after the caller was already refused. QuotaDeadlineError.phase gains 'bootstrap', and each phase's message now narrates only what actually happened: an 'acquire' refusal held no connection and no longer claims one was destroyed.

    Strict intake (postgres): acceptRulesUpdate is runtime-checked as a real boolean (the string "false" used to enable rotation by truthiness), and admissionDeadlineMs is refused above the Node timer maximum (2147483647 ms, now exported from @rulvar/core as MAX_TIMER_DELAY_MS) before the pool is constructed; above it, the deadline timer used to clamp and refuse every admission after about a millisecond.

    Migration note: hosts running mixed rule sets over one schema now fail loud during a rotation instead of silently splitting the budget: old booted hosts receive QuotaGenerationError on their next admission the moment a new deployment boots with acceptRulesUpdate: true. That refusal is the designed rollout signal, not a regression; roll the refused hosts to the new rule set and remove the flag. Existing recorded fingerprints keep matching (the key encoding is unchanged), and pre-generation schemas are backfilled to generation 1 on the first matching boot.

Patch Changes

1.104.0

Patch Changes

1.103.0

Patch Changes

1.102.0

Patch Changes

1.101.0

Patch Changes

1.100.0

Patch Changes

1.99.1

Patch Changes

1.99.0

Patch Changes

1.98.0

Patch Changes

1.97.0

Patch Changes

1.96.0

Patch Changes

1.95.0

Patch Changes

1.94.0

Patch Changes

1.93.0

Patch Changes

1.92.0

Patch Changes

1.91.0

Patch Changes

1.90.0

Patch Changes

1.89.0

Patch Changes

1.88.0

Patch Changes

1.87.0

Patch Changes

1.86.0

Patch Changes

1.85.0

Patch Changes

1.84.0

Patch Changes

1.83.0

Patch Changes

1.82.0

Patch Changes

1.81.2

Patch Changes

1.81.1

Patch Changes

1.81.0

Patch Changes

1.80.0

Patch Changes

1.79.0

Patch Changes

1.78.0

Patch Changes

1.77.0

Patch Changes

1.76.0

Patch Changes

1.75.1

Patch Changes

1.75.0

Patch Changes

1.74.0

Patch Changes

1.73.0

Patch Changes

1.72.0

Patch Changes

1.71.0

Patch Changes

1.70.1

Patch Changes

1.70.0

Patch Changes

1.69.0

Patch Changes

1.68.0

Patch Changes

1.67.0

Patch Changes

1.66.0

Patch Changes

1.65.0

Patch Changes

1.64.0

Patch Changes

1.63.0

Minor Changes

  • 8a28aed: Durable settlement acknowledgement and the fencing-epoch tombstone (the 1.62.0 experiment review, P0.1 and P0.2).

    Settlement acknowledgement: a NON-fencing failure of either settlement write now rejects handle.result with the new typed SettlementError (code settlement, retryable; stage names the write, data carries the runId and the computed run status) instead of resolving as if nothing happened. Only a superseded segment's LeaseHeldError stays swallowed, on both writes, because the successor owns settlement. A failed run_settle append also skips the terminal meta write, so the projection can never run ahead of the journal (published 1.62.0 wrote meta ok over a journal with no settle record when the append failed). Recovery is deterministic and free: the run's work entries are already durable, engine.resume replays to the same outcome without one paid provider call and re-attempts the settlement writes (a non-empty journal with no recorded settle now re-settles on pure replay), and rulvar runs audit [--repair] reconciles offline.

    Fencing-epoch tombstone: SqliteStore and PostgresStore no longer erase the per-run epoch high-water mark on delete, so a recreate of the same explicit runId always acquires a strictly higher epoch and a zombie lease from the deleted incarnation (same runId, same stable owner identity) is rejected on every fenced surface instead of fencing green. The LeasableStore contract now states the rule, and the conformance kit enforces it with two new mandatory checks (fencing-epoch-tombstone in leasableStoreConformance, fenced-tombstone-zombie-rejected in fencedWritesConformance). The tombstone holds only the runId and a counter, never run content; the data-protection guide documents the erasure boundary.

Patch Changes

1.62.0

Patch Changes

1.61.0

Patch Changes

1.60.0

Patch Changes

1.59.4

Patch Changes

1.59.3

Patch Changes

1.59.2

Patch Changes

1.59.1

Patch Changes

1.59.0

Patch Changes

1.58.0

Patch Changes

1.57.0

Patch Changes

  • 5897232: Two follow-ups from the RV-210 and RV-215 cycles. (1) Resume of a run that already SETTLED ok no longer re-dispatches plain cap-expiry limit children live: the canonical replay predicate now takes a runSettledOk input (computed by the engine from the loaded journal's run settle entry), and the memoize-limit rule replays unstamped limit entries when the run is finished history, so resuming a completed run makes ZERO adapter calls and replay --assert-no-live style verification holds. Non-ok settles and never-settled journals keep the rerun retry semantics (a crashed segment still resumes into a second chance), and an explicit invalidate still forces a rerun. (2) SqliteQuotaLimiter carries its own class TSDoc (the api page previously inherited the bare SPI interface line), documenting the single-transaction admission, cross-process reconciliation, identical-rules requirement, pruning, and the busy_timeout contract.
  • Updated dependencies [5897232]

1.56.0

Minor Changes

  • f26dba0: RV-215: distributed provider limiting. The new QuotaLimiter SPI is the extension seam for SHARED rate/quota limiting across engine instances and OS processes: createEngine({quota: {limiter, tenant?, onLimiterError?}}) makes the engine reserve capacity before EVERY live wire dispatch (initial attempts, transport retries, and failover takeovers alike, in every phase), dimensioned by provider/model/tenant with a heuristic token estimate, and reconcile each granted reservation with the attempt's actual usage after the outcome settles. A denial becomes a synthetic rate-limit-class WireError that rides the existing provider-429 retry and failover machinery verbatim, except no wire call is paid: the limiter's retryAfterMs (the honest window remainder) drives the interruptible backoff, attempts stay bounded by RetryPolicy, exhaustion fails over (the takeover reserves under its own model), and the terminal is the typed error of kind rate-limit. onLimiterError decides what a limiter INFRASTRUCTURE failure means: 'deny' (default) fails closed as a retryable transport-class denial, 'allow' logs a warning and dispatches without a reservation. Quota admission is live-only by construction (nothing journaled; replay and resume of memoized work never touch the limiter), and an unconfigured engine takes the exact pre-quota dispatch path down to promise-tick identity. Two reference implementations share one rule model (QuotaRule: optional provider/model/tenant dimensions; requestsPerMinute exact and hard, tokensPerMinute estimated at admission and settled to actual; every matching rule must admit; fixed epoch-aligned one-minute windows; validateQuotaRules at intake): memoryQuotaLimiter in @rulvar/core coordinates engines inside one process, and SqliteQuotaLimiter in @rulvar/store-sqlite coordinates PROCESSES over one database file, with admission inside a single BEGIN IMMEDIATE transaction, cross-process reconciliation via reservation rows, lazy two-window pruning, and the store's boot-scoped busy retry; a multi-process test fleet of real engines proves the global cap holds (dispatched wire calls exactly equal recorded window consumption, no window over cap). createTestEngine in @rulvar/testing passes a quota option through to the engine.

Patch Changes

1.55.0

Patch Changes

1.54.0

Patch Changes

1.53.0

Patch Changes

1.52.0

Patch Changes

1.51.0

Patch Changes

1.50.0

Patch Changes

1.49.0

Patch Changes

1.48.0

Patch Changes

  • 96093ea: Ship the adversarial multi-process soak and fix the SqliteStore concurrent-boot race it found (the fenced run state RFC, phase 3's last open item).

    The conformance kit gains the soak harness: runMultiProcessSoak spawns real OS processes that storm one store location through EVERY fenced write surface (journal append, meta write, transcript blob put and delete, fenced run deletion, renew, release) with stalls injected past the lease ttl, then rebuilds the one serial history the fencing epochs promise (accepted mutations ordered by epoch and per-tenure counter) and diffs it against the actual journal, meta row, and blobs. Any stale acceptance, lost accepted write, epoch inversion, or divergent final byte is a violation. The stale probe sweep re-reads the journal tail before each stale append attempt, so the monotonic-seq guard cannot mask a fencing hole; a live lease is also probed against a foreign run, and side runs get full create-and-fenced-delete cycles. The storm runs until an activity quorum is met (takeovers, per-surface accepted writes, typed stale rejections), so a slow machine storms longer instead of asserting on thin coverage. The child side is runSoakWriter plus soakWriterConfigFromEnv (the consumer's writer script constructs its store bare and passes a retryable hook for backend contention errors); the pure referee verifySoakHistory, the report tools parseSoakReport and countSoakActivity, and the quorum types are exported alongside.

    The soak's first storm against the published 1.47.0 never reached the fencing: N processes constructing SqliteStore over one SAME fresh file (an ordinary fleet start) collided in the constructor's schema bootstrap and the losers died with a raw SQLITE_BUSY (a 60 percent crash rate at six concurrent boots). A driver busy_timeout is not enough because the journal-mode conversion skips the busy handler on some lock transitions, so the constructor now retries the idempotent bootstrap as a unit through the SQLITE_BUSY family (extended result codes included, e.g. SQLITE_BUSY_RECOVERY while a sibling recovers the fresh WAL) under a wall-clock bound, exported as BOOT_BUSY_TIMEOUT_MS. Every runtime contention path keeps the documented fail-fast semantics. With the fix, 480 of 480 concurrent boots succeed, and the full storm (five writers, hundreds of takeovers, thousands of stale probes) holds every fenced surface with zero violations; SqliteStore now runs the soak and a concurrent-boot regression in its test suite.

1.47.0

Patch Changes

1.46.0

Minor Changes

  • 865e7bf: Close finding F2 of the fenced run state RFC with the sqlite transcript twin. SqliteStore.transcripts() returns a TranscriptStore that declares fencedWrites because its blobs live in the store's own database, beside the lease rows: a lease-carrying put or delete verifies the current holder of the run the ref's leading path segment names atomically with the blob mutation, in the same one-immediate-transaction shape as the journal side, and rejects stale or cross-run holders with the typed LeaseHeldError leaving the prior blob byte intact. Demonstrated against the published 1.45.0 first: the engine threaded the superseded segment's lease into its late checkpoint save, both shipped transcript stores ignored it, and the blob at the deterministic ref both segments share regressed to older turn state (the state a later boot decodes, replaying turns the successor already paid for) while the same holder's journal append bounced typed. Over the { journal: store, transcripts: store.transcripts() } pair, assertFencedWrites now passes and every durable run mutation is fenced. The conformance kit gains fencedTranscriptsConformance, the executable definition of the transcript-side promise, taking a factory for the pair that shares the fencing domain; staleness is produced with release plus reacquire, so the suite needs no wall sleeps.

Patch Changes

1.45.0

Minor Changes

  • b96305d: The fenced writes capability (the fenced run state RFC, phase 2). JournalStore.putMeta and delete and TranscriptStore.put and delete accept the same optional trailing lease that append always took, and a store declares enforcement with the fencedWrites: true marker: a mutation carrying a lease that is not the current holder for the mutated run rejects with the typed LeaseHeldError, atomically and leaving nothing changed, including a live lease for a different run. The engine threads the segment's lease into every durable mutation of a leased resume (meta writes, checkpoints, compaction summaries, worktree patches, workflow sources), so over a declaring store a superseded worker can no longer overwrite the successor's meta at its late settle and strand the run from worker sweeps, and its very first refused meta write now fails the stale segment typed at boot with zero paid calls. SqliteStore declares the marker and enforces it on putMeta, delete, and append (with the run-match rule as defense in depth); the conformance kit gains fencedWritesConformance as the capability's executable definition; the queue worker's retention sweep passes its brief lease through the new optional second argument of engine.deleteRun (pruneRun takes the same); and hasFencedWrites plus assertFencedWrites let a host assert the full fence at deployment time. Stores written before the capability are untouched: without the marker the extra argument is ignored and the journal-append fence works exactly as before.

Patch Changes

1.44.1

Patch Changes

  • 248a19f: Commit the fence check and the mutation it guards as one immediate transaction. The store checked the lease row in one autocommit statement and mutated in the next, so a takeover landing between them (reachable across two processes) let a superseded holder append a visible journal entry despite the moved epoch, extend the successor's lease with its own ttl, or delete the successor's live lease outright. All three were demonstrated against the published 1.44.0. The check and the insert, extension, or deletion now share one BEGIN IMMEDIATE transaction (the shape acquire always had), and the renew and release mutations additionally pin owner and epoch in their WHERE clauses as defense in depth. The cross-instance tests shim the interleave and prove a takeover can no longer land mid-call. The fenced run state RFC on the docs site records the full audit this fix came out of.

1.44.0

Patch Changes

1.43.0

Patch Changes

1.42.0

Patch Changes

1.41.0

Patch Changes

1.40.0

Patch Changes

1.39.0

Patch Changes

1.38.0

Patch Changes

1.37.0

Patch Changes

1.36.0

Minor Changes

  • 101795b: Validate SqliteStoreOptions.ttlMs as an integer between 1 and 2147483647 ms BEFORE the database opens, and expose the configured value as the readonly leaseTtlMs capability (v1.35.0 review P2). Unvalidated, zero or a negative made every lease born expired so a second owner could take over immediately, NaN failed the first acquire with a raw sqlite error, and Infinity never expired.

Patch Changes

1.35.0

Patch Changes

1.34.0

Patch Changes

1.33.0

Patch Changes

1.32.0

Patch Changes

1.31.0

Patch Changes

1.30.0

Patch Changes

1.29.0

Patch Changes

1.28.0

Patch Changes

1.27.0

Patch Changes

1.26.0

Minor Changes

  • a4fc757: SqliteStore implements the exact lookup capability (getMeta as a primary key query) and narrows status, statuses, and name in SQL over the JSON payload behind new expression indexes (created idempotently, so existing database files gain them on the next open), so a selective listRuns reads only the matching rows instead of decoding the whole catalog; the tags containment check stays in JS over the reduced set with unchanged semantics. The conformance kit checks the genesis round trip, that a statuses filter never drops a matching meta (supersets stay allowed), and that a store exposing getMeta agrees with listRuns and resolves undefined for a missing run. The planner's deterministic plan lookup reads one meta through the capability instead of scanning the catalog.

Patch Changes

1.25.0

Patch Changes

1.24.1

Patch Changes

1.24.0

Patch Changes

1.23.0

Patch Changes

1.22.0

Patch Changes

1.21.0

Patch Changes

1.20.0

Patch Changes

1.19.0

Patch Changes

  • 8cc9a9c: Internal real-time reads bind the wall clock at module load, never the live global, eliminating false RULVAR_BARE_DATE_NOW warnings for consumers whose rulvar frames live outside node_modules (workspace dists, monorepo checkouts). Two composing defects: createEngine captured Date.now per call, so an engine created after a previous run had installed the dev-mode patch bound the PATCHED wrapper as its real clock (its EventBus then warned from the engine's own frames), and the ULID factory read the live global at every mint, so ids minted mid-run (the orchestrator extension IO, PlanRunner revisions, adapter id maps) routed through the patch too. The engine now uses a module-load realNow binding (module load always precedes the first patch install), the vendored ULID factory defaults to its own module-load clock, and @rulvar/store-sqlite follows the same convention. The dev-mode guard itself is untouched and stays exactly as sharp for workflow code, which keeps reading the live global.
  • Updated dependencies [8cc9a9c]
  • Updated dependencies [8cc9a9c]
  • Updated dependencies [8cc9a9c]

1.18.0

Patch Changes

1.17.0

Patch Changes

1.16.2

Patch Changes

1.16.1

Patch Changes

1.16.0

Patch Changes

1.15.0

Patch Changes

1.14.0

Patch Changes

1.13.0

Patch Changes

1.12.0

Patch Changes

1.11.0

Minor Changes

  • 0c70c5e: Enforce the monotonic-seq store obligation: append commits through one atomic conditional INSERT that rejects an entry whose seq is not strictly greater than the run's stored tail with the typed JournalOrderViolation, so two writers racing the same journal from a stale tail can never both persist (the second writer of a split-brain resume gets a typed conflict instead of silently corrupting replay). Entries without a finite seq (legacy or exotic shapes) pass through unguarded, preserving payload opacity. A non-unique expression index over (run_id, seq) keeps the tail check cheap on long journals; existing database files need no migration.

Patch Changes

1.10.0

Patch Changes

1.9.0

Patch Changes

1.8.0

Patch Changes

  • Updated dependencies [25724b5]
  • Updated dependencies [57ea1de]
  • Updated dependencies [7884ec5]
  • Updated dependencies [52db30d]

1.7.0

Patch Changes

  • Updated dependencies [45285aa]
  • Updated dependencies [2f20d1d]
  • Updated dependencies [22f65a8]
  • Updated dependencies [2ddfa29]
  • Updated dependencies [2abd9c2]
  • Updated dependencies [1c1175d]

1.6.0

Patch Changes

  • da4dbad: Write the product name as Rulvar in prose: package READMEs, npm descriptions, and the documentation site now capitalize the brand. Identifiers keep their exact casing, so package names, the rulvar binary, rulvar.config.mjs, the .rulvar store directory, the rulvar.* OTel attributes, and every URL are unchanged. Documentation and metadata only; no runtime behaviour changes.
  • Updated dependencies [da4dbad]
  • Updated dependencies [487da86]
  • Updated dependencies [df416fc]
  • Updated dependencies [a737810]
  • Updated dependencies [9eb66b4]

1.5.2

Patch Changes

1.5.1

Patch Changes

1.5.0

Patch Changes

1.4.0

Patch Changes

1.3.2

Patch Changes

  • ddef383: Every published package now ships a README, so its npm page states what the package is, how it installs, and where the documentation lives (npm includes README.md in the tarball regardless of the files allowlist, so no manifest changes are involved; @rulvar/compat gains its README on its own next release). Alongside, the repository-level pages are refreshed to the current project state: the root README is rewritten around the never-pay-twice pitch with a runnable quickstart condensation and the full package table, CONTRIBUTING.md lists the complete PR gate set, the examples README drops retired-spec citations for live docs.rulvar.com links and documents the dogfood journal replay, and the pointer README gets the same treatment.
  • Updated dependencies [ddef383]

1.3.1

Patch Changes

  • 7d1552e: Runtime message strings no longer cite the retired internal specification set: error and warning messages, validation issues, and the CLI help text drop the dangling docs/NN, section ... references, pointing at https://docs.rulvar.com pages where a pointer earns its place (the CLI help header, tool naming, toolset registries, bare resume). The umbrella package description sheds the naming-contingency note: the unscoped alias is published and owned. Three strings embedded in frozen recordings stay byte-identical on purpose (the no-progress abort reason and two testing-internal recorder strings), as does the byte-locked golden-fold fixture. Test-file comments lose their citations too; test titles are unchanged.
  • Updated dependencies [7d1552e]

1.3.0

Patch Changes

1.2.0

Patch Changes

  • 154507b: TSDoc and inline comments no longer cite the retired internal specification set (the pre-docs-site docs/NN, section ... references). The citations either became links to the public documentation at docs.rulvar.com or were dropped where the comment already carried the rule; traceability markers (DEF-n, XF-nn, FR-nnn, OQ-nn, W-nnn) are untouched. Comment-only change: no runtime behavior, no API shapes, and no runtime message strings were modified; the frozen golden-fold fixture is byte-identical.
  • Updated dependencies [3bfaec0]
  • Updated dependencies [890f42c]
  • Updated dependencies [154507b]

1.1.0

Patch Changes

1.0.0

Patch Changes

  • Updated dependencies [0e0b569]
  • Updated dependencies [b28b7a3]
  • Updated dependencies [b53a89e]
  • Updated dependencies [4454175]
  • Updated dependencies [6599ca8]
  • Updated dependencies [6649e5f]
  • Updated dependencies [fd2f83b]
  • Updated dependencies [01d6b2d]
  • Updated dependencies [9a20dbb]
  • Updated dependencies [0fbe7ea]
  • Updated dependencies [ebe0abc]
  • Updated dependencies [a3079d0]
  • Updated dependencies [596a39b]
  • Updated dependencies [464ab6e]

0.9.0

Patch Changes

  • Updated dependencies [84f94d4]
  • Updated dependencies [65c7b2c]
  • Updated dependencies [a2a3243]
  • Updated dependencies [ebc8101]

0.8.0

Patch Changes

  • Updated dependencies [85d55cf]
  • Updated dependencies [b88c9e3]
  • Updated dependencies [f3c4613]
  • Updated dependencies [a41c20f]
  • Updated dependencies [f4e70be]
  • Updated dependencies [75d1646]
  • Updated dependencies [0627413]
  • Updated dependencies [55c0f87]
  • Updated dependencies [fd33871]
  • Updated dependencies [e70e7f4]
  • Updated dependencies [bc9c903]

0.7.0

Patch Changes

  • Updated dependencies [fd1d06c]
  • Updated dependencies [6fcf296]
  • Updated dependencies [dcc97a9]
  • Updated dependencies [434dc83]
  • Updated dependencies [03173c1]
  • Updated dependencies [11c0afc]

0.6.0

Minor Changes

  • 8f7e61f: M5-T02 SqliteStore: the first real surface of @rulvar/store-sqlite. SqliteStore implements JournalStore AND LeasableStore with fencing epochs over the builtin node:sqlite driver (zero native dependencies; requires a Node.js line with node:sqlite unflagged, 22.13+/23.4+). It passes the full @rulvar/store-conformance suites: A1-A4 store obligations, meta separation, the golden fold fixture, the decide-once oracle, the abandon-derived skip, lease exclusivity (typed LeaseHeldError), monotonic fencing epochs with stale-append rejection and invisibility, release fencing, and wall-clock ttl expiry with the renew-at-ttl/3 cadence. The lease ttl defaults to the Appendix A interim reference for this store (60000 ms; the committed value is an M8 decision), and an injectable clock supports expiry tests. This is the reference implementation for community stores (docs/03, section 12.6).

Patch Changes

  • Updated dependencies [fa05007]
  • Updated dependencies [9234dc8]
  • Updated dependencies [644512c]
  • Updated dependencies [8a41656]
  • Updated dependencies [02f7f7a]

0.5.0

Patch Changes

  • Updated dependencies [ac274f4]
  • Updated dependencies [5735d92]
  • Updated dependencies [46ca98e]
  • Updated dependencies [8ae129e]
  • Updated dependencies [d1c4525]
  • Updated dependencies [b840aba]

0.4.0

Patch Changes

  • Updated dependencies [dfe03b5]
  • Updated dependencies [d2089a7]
  • Updated dependencies [3f60234]
  • Updated dependencies [f668890]
  • Updated dependencies [16d7aa6]
  • Updated dependencies [6513ce8]
  • Updated dependencies [7dad493]
  • Updated dependencies [2bbf180]

0.3.0

Patch Changes

  • Updated dependencies [43444f6]
  • Updated dependencies [279881b]
  • Updated dependencies [9fd0966]
  • Updated dependencies [24ebadf]
  • Updated dependencies [a1b35d3]
  • Updated dependencies [18a5821]

0.2.0

Patch Changes

  • Updated dependencies [c24228d]
  • Updated dependencies [c50871e]
  • Updated dependencies [1af8fb9]
  • Updated dependencies [1fe0249]
  • Updated dependencies [5c4fc32]

0.1.0

Minor Changes

  • f4e2be9: M0 repo bootstrap (v0.1.0, docs/10-implementation-plan.md section "M0"): monorepo scaffold on the committed toolchain (pnpm 11 workspaces with catalogs, TypeScript 6.0, tsdown, Vitest 4, ESLint 9 flat config, Turborepo 2, changesets fixed mode, npm trusted publishing), the docs/ canon as single source of truth, the L0 contracts skeleton in @rulvar/core, and the vendored dependencies (StandardSchemaV1/StandardJSONSchemaV1 types, the @cfworker/json-schema lineage validator subset, a first-party monotonic ULID). Placeholder scaffolds only: no public API ships in this release.

Patch Changes

@rulvar/testing

1.252.0

Patch Changes

  • Updated dependencies [3ccb6cf]
  • Updated dependencies [52d807f]
  • Updated dependencies [517ed00]
  • Updated dependencies [a7e589d]
  • Updated dependencies [76e95eb]

1.251.0

Patch Changes

  • Updated dependencies [e7e829c]
  • Updated dependencies [5982be8]
  • Updated dependencies [7c58fb2]
  • Updated dependencies [b3e465a]
  • Updated dependencies [c4e5d6a]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [c6fc3da]
  • Updated dependencies [0ae8b85]
  • Updated dependencies [7932936]
  • Updated dependencies [7932936]
  • Updated dependencies [88da0ed]
  • Updated dependencies [06c0e85]

1.250.0

Patch Changes

  • Updated dependencies [0e240b9]
  • Updated dependencies [6fe585e]
  • Updated dependencies [c5eb19c]
  • Updated dependencies [565c13b]
  • Updated dependencies [c6d197b]
  • Updated dependencies [c9d9729]
  • Updated dependencies [fed9db6]
  • Updated dependencies [df9ed76]
  • Updated dependencies [3020912]
  • Updated dependencies [d8d598d]

1.249.0

Patch Changes

  • Updated dependencies [8862133]
  • Updated dependencies [0d7a717]
  • Updated dependencies [e4428bd]
  • Updated dependencies [d6873c1]
  • Updated dependencies [4092e8d]
  • Updated dependencies [e086590]
  • Updated dependencies [1411938]
  • Updated dependencies [737d1ee]
  • Updated dependencies [634f966]
  • Updated dependencies [052cc26]
  • Updated dependencies [bbae134]

1.248.0

Patch Changes

  • Updated dependencies [8d0cd69]
  • Updated dependencies [81065e4]
  • Updated dependencies [8573f20]
  • Updated dependencies [95f6a5e]

1.247.0

Patch Changes

  • Updated dependencies [1933ecc]
  • Updated dependencies [db0a5f0]
  • Updated dependencies [b698726]
  • Updated dependencies [48348d2]
  • Updated dependencies [4cfa1cc]
  • Updated dependencies [4b7197a]
  • Updated dependencies [5ebc842]
  • Updated dependencies [16ff6b9]
  • Updated dependencies [0c9941d]

1.246.0

Patch Changes

  • Updated dependencies [d165b0c]
  • Updated dependencies [d59f4a0]
  • Updated dependencies [46907ac]
  • Updated dependencies [9929ad3]
  • Updated dependencies [1790a6a]

1.245.0

Patch Changes

  • Updated dependencies [b4d47a8]
  • Updated dependencies [dee6db4]
  • Updated dependencies [b85c113]
  • Updated dependencies [bc556e7]
  • Updated dependencies [9f11d29]
  • Updated dependencies [19bcea0]
  • Updated dependencies [60b461c]
  • Updated dependencies [61e3a1a]
  • Updated dependencies [a156b81]
  • Updated dependencies [0bd7045]

1.244.0

Patch Changes

  • Updated dependencies [38d839a]
  • Updated dependencies [ce13b0f]
  • Updated dependencies [4fa23e3]
  • Updated dependencies [6841c69]
  • Updated dependencies [f56721d]
  • Updated dependencies [c894a43]
  • Updated dependencies [f6944a3]
  • Updated dependencies [23fd0e0]

1.243.0

Patch Changes

  • Updated dependencies [746d1f4]
  • Updated dependencies [009b29c]
  • Updated dependencies [1674cbe]
  • Updated dependencies [bd096bc]

1.242.0

Patch Changes

  • Updated dependencies [6e3438e]
  • Updated dependencies [ba5cf67]
  • Updated dependencies [c2d1531]

1.241.0

Patch Changes

  • Updated dependencies [dbcdd24]
  • Updated dependencies [7ae7243]
  • Updated dependencies [4f832c4]
  • Updated dependencies [7452d3d]
  • Updated dependencies [a4e22bf]
  • Updated dependencies [82df4af]

1.240.0

Patch Changes

1.239.0

Patch Changes

  • Updated dependencies [74ce99a]
  • Updated dependencies [ccd0665]
  • Updated dependencies [0c5ce21]
  • Updated dependencies [0616934]

1.238.0

Patch Changes

  • Updated dependencies [cf00947]
  • Updated dependencies [c7b9382]
  • Updated dependencies [88aea96]
  • Updated dependencies [6da8d05]
  • Updated dependencies [eae5c4c]

1.237.0

Patch Changes

  • Updated dependencies [9d6a279]
  • Updated dependencies [49a98f6]
  • Updated dependencies [a734ca0]
  • Updated dependencies [deb406f]

1.236.0

Patch Changes

1.235.0

Patch Changes

  • Updated dependencies [ba4e10d]
  • Updated dependencies [172402b]
  • Updated dependencies [2ecd787]
  • Updated dependencies [e20a5e9]
  • Updated dependencies [98c8691]
  • Updated dependencies [c70def0]

1.234.0

Patch Changes

1.233.0

Patch Changes

  • Updated dependencies [48b5200]
  • Updated dependencies [73bc32b]
  • Updated dependencies [e63b743]
  • Updated dependencies [ef45da7]

1.232.0

Patch Changes

  • Updated dependencies [1440410]
  • Updated dependencies [6e467f4]
  • Updated dependencies [0b14293]
  • Updated dependencies [e3bcab2]
  • Updated dependencies [b55a0f7]

1.231.0

Patch Changes

  • Updated dependencies [4eb4b56]
  • Updated dependencies [bc8f09e]
  • Updated dependencies [ff9b8c2]

1.230.0

Patch Changes

1.229.0

Patch Changes

  • Updated dependencies [3370342]
  • Updated dependencies [2fb6656]
  • Updated dependencies [edce170]

1.228.0

Patch Changes

  • Updated dependencies [4034fac]
  • Updated dependencies [a54b085]
  • Updated dependencies [9d0a9be]
  • Updated dependencies [be9ef28]

1.227.0

Patch Changes

  • Updated dependencies [f262e9f]
  • Updated dependencies [f191ff7]
  • Updated dependencies [fbbfbe8]
  • Updated dependencies [263b5e8]
  • Updated dependencies [db4d56d]
  • Updated dependencies [41f93a9]
  • Updated dependencies [98c8ca9]

1.226.0

Patch Changes

1.225.0

Patch Changes

1.224.0

Patch Changes

1.223.0

Patch Changes

1.222.0

Patch Changes

1.221.0

Patch Changes

1.220.0

Patch Changes

1.219.0

Patch Changes

1.218.0

Patch Changes

1.217.0

Patch Changes

1.216.0

Patch Changes

1.215.0

Patch Changes

1.214.0

Patch Changes

1.213.0

Patch Changes

1.212.0

Patch Changes

1.211.0

Patch Changes

1.210.0

Patch Changes

1.209.0

Patch Changes

1.208.0

Minor Changes

  • e7d426f: First-class prompt-cache policy (RV2006). ChatRequest.cacheHint existed and the Anthropic adapter compiled it into cache_control, but nothing in the core ever populated it: the third parity rerun's workers re-paid the full input rate on every turn of their ~550k-token contexts (cacheReadTokens 0 across the run), and the $6 envelope sized on OpenAI's implicit server cache was incomparable on Anthropic. The agent loop now compiles the hint on every tool-cycle turn: breakpoints after tools, after system, and after the deepest message, sliding with the history. Default ON exactly where the adapter declares the new ModelCaps.promptCaching: 'explicit' (the Anthropic adapter does); OpenAI declares 'implicit' and undeclared adapters get byte-identical requests. Configure with defaults.cache, AgentProfile.cache, or per-call opts.cache (CachePolicy { mode?: 'auto' | 'off'; ttl?: '5m' | '1h' }), call over profile over engine. Billing note: on cache-capable Anthropic models this changes the wire requests of every loop turn to carry cache breakpoints, typically cutting long-cycle input cost several-fold (cached reads bill at a tenth of the input rate); CostReport cache accounting is unchanged, the hint never enters identity or journals, and @rulvar/testing's requestHash strips it so existing cassettes replay byte for byte.

Patch Changes

1.207.0

Patch Changes

1.206.0

Patch Changes

1.205.0

Patch Changes

1.204.0

Patch Changes

1.203.0

Patch Changes

1.202.0

Patch Changes

1.201.0

Patch Changes

1.200.0

Patch Changes

1.199.0

Patch Changes

1.198.0

Patch Changes

1.197.0

Patch Changes

1.196.0

Patch Changes

1.195.0

Patch Changes

1.194.0

Patch Changes

1.193.0

Patch Changes

1.192.0

Patch Changes

1.191.0

Patch Changes

1.190.0

Patch Changes

1.189.0

Patch Changes

1.188.0

Patch Changes

1.187.0

Patch Changes

1.186.0

Patch Changes

1.185.0

Patch Changes

1.184.0

Patch Changes

1.183.0

Patch Changes

1.182.0

Patch Changes

1.181.0

Patch Changes

1.180.0

Patch Changes

1.179.0

Patch Changes

1.178.0

Patch Changes

1.177.0

Patch Changes

1.176.0

Patch Changes

1.175.0

Patch Changes

1.174.0

Patch Changes

1.173.0

Patch Changes

1.172.0

Patch Changes

1.171.0

Patch Changes

1.170.0

Patch Changes

1.169.0

Patch Changes

1.168.0

Patch Changes

1.167.0

Patch Changes

1.166.0

Patch Changes

1.165.0

Patch Changes

1.164.0

Patch Changes

1.163.0

Patch Changes

1.162.0

Patch Changes

1.161.0

Patch Changes

1.160.0

Patch Changes

1.159.0

Patch Changes

1.158.0

Patch Changes

1.157.0

Patch Changes

1.156.0

Patch Changes

1.155.0

Patch Changes

1.154.0

Patch Changes

1.153.0

Patch Changes

1.152.0

Patch Changes

1.151.0

Patch Changes

1.150.0

Patch Changes

1.149.0

Patch Changes

1.148.0

Patch Changes

1.147.0

Patch Changes

1.146.0

Patch Changes

1.145.0

Patch Changes

1.144.0

Patch Changes

1.143.0

Patch Changes

1.142.0

Patch Changes

1.141.0

Patch Changes

1.140.0

Patch Changes

1.139.0

Patch Changes

1.138.0

Patch Changes

1.137.0

Patch Changes

1.136.0

Patch Changes

1.135.0

Patch Changes

1.134.0

Patch Changes

1.133.0

Patch Changes

1.132.0

Patch Changes

1.131.0

Patch Changes

1.130.0

Patch Changes

1.129.0

Patch Changes

1.128.0

Patch Changes

1.127.0

Patch Changes

1.126.0

Patch Changes

1.125.0

Patch Changes

1.124.0

Patch Changes

1.123.0

Patch Changes

1.122.0

Patch Changes

1.121.0

Patch Changes

1.120.0

Patch Changes

1.119.0

Patch Changes

1.118.0

Patch Changes

1.117.0

Patch Changes

1.116.0

Patch Changes

1.115.0

Patch Changes

1.114.0

Patch Changes

1.113.0

Patch Changes

1.112.0

Patch Changes

1.111.0

Patch Changes

1.110.0

Patch Changes

1.109.0

Patch Changes

1.108.0

Patch Changes

1.107.0

Patch Changes

1.106.0

Patch Changes

1.105.0

Patch Changes

1.104.0

Patch Changes

1.103.0

Patch Changes

1.102.0

Patch Changes

1.101.0

Patch Changes

1.100.0

Patch Changes

1.99.1

Patch Changes

  • ef08d73: Guarantee matrix and exactly-once claim hygiene (RV508); no runtime behavior changes. The isolated-executor guide now carries the guarantee matrix stating flatly who provides what: the library's layers give at-least-once execution with attempt binding and intent-before-effect, exactly-once effect execution is promised by NO library layer, and what IS exactly-once is pay and replay (the never-pay-twice invariant). The two claims the ninth comparison experiment's judge caught are rewritten to the precise statements ("each ran once" became attempt counting under a stable idempotency key; the approvals guide now says continuation is a run-level guarantee, not an effect-level one, with the at-least-once window named); ctx.step docs state the same window for effectful steps; a ResolutionBy note says the field records a channel, never a verified principal (identity, signatures, and separation of duties are host IAM). The worker header now points at the shipped SqliteQuotaLimiter and PostgresQuotaLimiter instead of denying that cross-process limiters exist. A new docs-lint sentinel forbids "exactly once" claims in the hand-written docs and in package source comments outside a vetted (file, heading anchor) allowlist (the durability pay doctrine and the guarantee matrix), and every remaining occurrence in doc prose and source comments was rewritten to the precise wording; string literals are deliberately out of scope (tool descriptions enter the toolset hash).
  • Updated dependencies [ef08d73]

1.99.0

Patch Changes

1.98.0

Patch Changes

1.97.0

Patch Changes

1.96.0

Patch Changes

1.95.0

Patch Changes

1.94.0

Patch Changes

1.93.0

Patch Changes

1.92.0

Patch Changes

1.91.0

Patch Changes

1.90.0

Patch Changes

1.89.0

Patch Changes

1.88.0

Patch Changes

1.87.0

Patch Changes

1.86.0

Patch Changes

1.85.0

Patch Changes

1.84.0

Patch Changes

1.83.0

Patch Changes

1.82.0

Patch Changes

1.81.2

Patch Changes

1.81.1

Patch Changes

1.81.0

Patch Changes

1.80.0

Patch Changes

1.79.0

Patch Changes

1.78.0

Patch Changes

1.77.0

Patch Changes

1.76.0

Patch Changes

1.75.1

Patch Changes

1.75.0

Minor Changes

  • c486de8: The provider output floor and the finish arguments second chance (the v1.74 comparison review, P0.1 + P1.5). ModelCaps.minOutputTokensPerTurn declares the smallest request output cap the provider accepts (OpenAI Responses: 16; absent means one), and the layer-2b budget clamp never dispatches below it: the last-gasp turn goes out AT the floor instead of one token, a remainder that cannot buy the floor is refused as a typed BudgetExhaustedError with zero wire calls, and a configured per-turn cap below the floor is a ConfigError; preflightEstimate reports that configuration as the error finding output-cap-below-provider-minimum. Tool arguments an adapter delivered as the parse-failure wrapper {__unparsed: raw} now get one deterministic second chance before the schema rejection: a strict re-parse, then one bounded normalization (markdown fence, first balanced object, raw control characters escaped inside string literals); a recovered object that passes the tool schema executes as if it had parsed on the wire, with a warn log naming the pass, and replay or resume recovers identically with nothing journaled. The OpenAI wire re-projects an unparseable call as the ORIGINAL raw arguments string instead of the wrapper JSON, so a model no longer learns to imitate {"__unparsed": ...} from its own rewritten history. Both wires drop unsafe-integer x-ratelimit values instead of normalizing 400 digits into Infinity. FakeAdapter gains capsOverrides so offline tests can drive caps-declared behavior like the floor.

Patch Changes

1.74.0

Patch Changes

1.73.0

Patch Changes

1.72.0

Patch Changes

1.71.0

Patch Changes

1.70.1

Patch Changes

1.70.0

Patch Changes

1.69.0

Patch Changes

1.68.0

Patch Changes

1.67.0

Patch Changes

1.66.0

Patch Changes

1.65.0

Patch Changes

1.64.0

Patch Changes

1.63.0

Patch Changes

1.62.0

Patch Changes

1.61.0

Patch Changes

1.60.0

Patch Changes

1.59.4

Patch Changes

1.59.3

Patch Changes

1.59.2

Patch Changes

1.59.1

Patch Changes

1.59.0

Minor Changes

  • 615dc90: RV-216: the isolated tool executor, the last open item in the improvement plan. In-process tools are ordinary function calls with full host capabilities (an execution convenience, never a sandbox for hostile or model-generated code); this release adds an official out-of-process executor contract so a tool whose input is untrusted cannot reach host capabilities. (1) THE SEAM in @rulvar/core: a ToolExecutorProvider SPI, registered on the engine as createEngine({ executors: { subprocess, container } }). A tool declaring executor: 'subprocess' or 'container' (previously a hard "only inprocess in v1" rejection) dispatches through the matching provider instead of running its execute closure; an unregistered tag is a typed ConfigError at spawn time, before any provider or model call. The dispatch mints the tool span exactly like an inprocess call and derives a stable idempotency key (a pure function of runId, tool name, and canonical args) so a side-effecting tool can fold an at-least-once retry into effectively-once; the tag never enters toolsetHash, so opting a tool into isolation does not change run identity, and inprocess dispatch stays byte-identical. (2) THE REFERENCE ADAPTERS in the new @rulvar/executor package: subprocessExecutor runs the tool in a child process with a REPLACED environment (host credentials scrubbed; the usual exfiltration path removed), a fresh ephemeral working directory per call, per-call short-lived credentials, a hard timeout that escalates SIGTERM to SIGKILL, and a bounded output capture, plus a sandbox launcher hook where bwrap/firejail/sandbox-exec plug in for filesystem and network isolation; containerExecutor runs it in a one-shot container with the network dropped (--network none), the root filesystem read-only, memory/CPU/pid caps, and all Linux capabilities dropped, which is where the strong isolation the subprocess adapter cannot promise on its own actually holds (a microVM adapter implements the same seam). subprocessTool defines a tool that dispatches through them; a ToolEffectLedger records every dispatch (idempotency key, tool, argsHash, workdir, outcome) so a host can bind an approval to the effect it authorized. (3) THE CONFORMANCE KIT: executorConformance is the executable shared-contract battery any command-based executor must pass, foremost the gate the epic exists for, a hostile tool cannot read the host's ambient credentials; the subprocess reference passes all of it, and the container reference additionally proves the network and filesystem isolation against a real runtime. New guide page: https://docs.rulvar.com/guide/isolated-executor.

Patch Changes

1.58.0

Minor Changes

  • 4fa35ce: RV-217: data protection hooks, the full close. The plan's gate ("PII never persists or emits in plaintext under policy") now holds end to end. (1) ENVELOPE ENCRYPTION on the serialization seam: createEnvelopeEncryption({provider, historicalWrappedKeys?, plaintextReads?}) returns a SerializationHook that AES-256-GCM encrypts every persisted byte (journal payloads, transcript blobs, checkpoints) with entry identity as associated data (a ciphertext moved between entries or refs fails authentication), keeping only the kernel-pinned ordering/identity fields plus spanId and timestamps plaintext; DataKeyProvider is the KMS seam (the exact shape of GenerateDataKey/Decrypt, called only in the async factory so the sync hooks run on in-memory data keys, and every envelope carries its wrapped key so reads need no live KMS); the shipped localKeyProvider derives KEKs via HKDF-SHA256 with an info partition for tenant-scoped keys (a different tenant's provider cannot unwrap, pinned by tests); reads of non-enveloped data fail closed by default with plaintextReads: 'passthrough' as the explicit migration mode; fromStored(toStored(e)) reproduces entries exactly, so replay, resume, and recovery are untouched and a run over real files greps to ZERO plaintext PII while Engine.stores reads plaintext through the one policy point. (2) REDACTION POLICY: redaction.patterns adds host-defined patterns (RegExp or strings, compiled once, typed ConfigError on an invalid one) on top of the default credential set for every emitted event, via the new exported compileSecretMasker; the OTel exporter accepts the same patterns for trace parity. (3) EXPORT/IMPORT: engine.exportRun(runId) produces the portable bundle (meta, entries, blobs) read through the policy point, so encrypted deployments export plaintext for subject-access requests; engine.importRun(bundle) writes through the target's stores (re-encrypting under its policy), keeps the original runId, and refuses an existing run typed; together with the existing deleteRun/pruneRun this completes the retention/deletion/export surface. (4) SALTED METADATA DIGESTS: security.argsHashSalt switches RunMeta.argsHash to HMAC-SHA256 under a deployment salt (equal args stop correlating across deployments; low-entropy args stop being recoverable from the digest), hashRunArgs gains the optional salt, and the CLI resume args gate picks the salt up from engineOptions.security automatically. (5) AUDIT TRAIL: reduceAuditTrail(entries) folds a journal into the typed, ordered sequence of authority events (suspensions with deadlines, resolutions with who and what, abandons with reasons, engine decisions, termination denials, run settles), tolerant across journal vintages. New guide page: https://docs.rulvar.com/guide/data-protection.

Patch Changes

1.57.0

Patch Changes

1.56.0

Minor Changes

  • f26dba0: RV-215: distributed provider limiting. The new QuotaLimiter SPI is the extension seam for SHARED rate/quota limiting across engine instances and OS processes: createEngine({quota: {limiter, tenant?, onLimiterError?}}) makes the engine reserve capacity before EVERY live wire dispatch (initial attempts, transport retries, and failover takeovers alike, in every phase), dimensioned by provider/model/tenant with a heuristic token estimate, and reconcile each granted reservation with the attempt's actual usage after the outcome settles. A denial becomes a synthetic rate-limit-class WireError that rides the existing provider-429 retry and failover machinery verbatim, except no wire call is paid: the limiter's retryAfterMs (the honest window remainder) drives the interruptible backoff, attempts stay bounded by RetryPolicy, exhaustion fails over (the takeover reserves under its own model), and the terminal is the typed error of kind rate-limit. onLimiterError decides what a limiter INFRASTRUCTURE failure means: 'deny' (default) fails closed as a retryable transport-class denial, 'allow' logs a warning and dispatches without a reservation. Quota admission is live-only by construction (nothing journaled; replay and resume of memoized work never touch the limiter), and an unconfigured engine takes the exact pre-quota dispatch path down to promise-tick identity. Two reference implementations share one rule model (QuotaRule: optional provider/model/tenant dimensions; requestsPerMinute exact and hard, tokensPerMinute estimated at admission and settled to actual; every matching rule must admit; fixed epoch-aligned one-minute windows; validateQuotaRules at intake): memoryQuotaLimiter in @rulvar/core coordinates engines inside one process, and SqliteQuotaLimiter in @rulvar/store-sqlite coordinates PROCESSES over one database file, with admission inside a single BEGIN IMMEDIATE transaction, cross-process reconciliation via reservation rows, lazy two-window pruning, and the store's boot-scoped busy retry; a multi-process test fleet of real engines proves the global cap holds (dispatched wire calls exactly equal recorded window consumption, no window over cap). createTestEngine in @rulvar/testing passes a quota option through to the engine.

Patch Changes

1.55.0

Patch Changes

1.54.0

Patch Changes

1.53.0

Minor Changes

  • b821bd1: Ship the RV-211 synthesis role and critical-path metrics. InvocationRole gains 'synthesize': the dynamic orchestrator's opt-in post-fan-in synthesis invocation (OrchestrateOptions.synthesis { model?, effort?, limits?, instructions?, estCost? }). With it configured, the coordination loop's finish({ result }) becomes a draft and one fresh finish-only invocation with role synthesize composes the final run result from the goal, the draft, and the settled child digest, routable independently of coordination through the ordinary chain (the routing key picks its model and never summons it; no role effort default, like loop and finalize). Ordering and failure posture are strict: synthesis runs only after an accepted acceptance verdict; finishValidation validators bind the synthesis finish instead of the draft (same repair loop, same journaled verdicts); a dead synthesis falls back to the draft under a journaled orchestrator_synthesis_fallback decision and a warn log without validators, or fails the run typed (data.source 'orchestrator_synthesis') with them. The invocation is an ordinary journaled agent entry, so a resume replays it with zero paid calls (the prompt derives from journaled state, and the replayed root now awaits recovery before the digest fold). Telemetry: full synthesize span and phase pairs (CostReport.byRole.synthesize), a debug log event with the actual draft/digest/prompt sizes, and the new pure reducer reduceCriticalPath(events) (CriticalPath), which computes run wall, the post-fan-in interval, the synthesis wall, and their shares, so the improvement plan's post-fan-in gate (at most 40% of wall time) is a field read; the benchmark kit can expose any of them as metric extractors. createTestEngine routes synthesize to the fake model like every other model-picking key. Demonstrated against published 1.52.0 first: the whole orchestration emitted only orchestrate/loop roles, the final synthesis request ran on the coordination model, byRole had no synthesize bucket, the post-fan-in share was hand-rolled or nothing, and the synthesis vocabulary was silently ignored words.

Patch Changes

1.52.0

Patch Changes

1.51.0

Patch Changes

1.50.0

Patch Changes

1.49.0

Patch Changes

1.48.0

Patch Changes

1.47.0

Patch Changes

1.46.0

Patch Changes

1.45.0

Patch Changes

1.44.1

Patch Changes

1.44.0

Patch Changes

1.43.0

Patch Changes

1.42.0

Patch Changes

1.41.0

Patch Changes

1.40.0

Patch Changes

1.39.0

Patch Changes

1.38.0

Patch Changes

1.37.0

Patch Changes

1.36.0

Patch Changes

1.35.0

Patch Changes

1.34.0

Minor Changes

  • f1505ec: The VCR occurrence numbering is now bounded and the appending seed scales (v1.33.0 review P3). An appending record() session seeds each hash counter in one pass instead of spreading the whole group into Math.max, which overflowed the call stack with an untyped RangeError once a group held enough rows (150000 in the review's reproducer). A group that already numbers Number.MAX_SAFE_INTEGER refuses the appending session at construction, and a session whose counter would pass the ceiling refuses that call, both with a typed ConfigError naming the cassette, adapter, and hash, before dispatching the provider and before touching the file. Previously the recorder paid the provider, appended an unsafe number that a later readCassette refuses, and the stalled float counter then duplicated that same unsafe number on every following append, so the library itself turned a valid cassette invalid. The cassette format stays v1 with no new fields, hashVersion is untouched, and existing valid cassettes replay unchanged.

Patch Changes

1.33.0

Minor Changes

  • 3f0f5e8: Appending record sessions continue the occurrence numbering, and ambiguous numbering refuses (v1.32.0 review P2). Each record() call created a fresh occurrence counter, so a second session appending to an existing cassette restarted the numbering at zero for hashes the file already held: the file order stayed honest, but replay, which sorts a fully numbered group by its occurrence numbers, served the appended exchange before earlier ones (rows numbered 0, 1, 0 replayed as first, third, second). The error was silent, the cassette validated, and onMiss: 'passthrough' exists precisely to complete a cassette across sessions. record() now reads and validates an existing target before wrapping anything and seeds every (adapterId, requestHash) counter one past the highest number already on disk, so sequential sessions continue the numbering; a gap left by an aborted call stays a gap rather than being filled. Groups recorded before v1.32.0 keep their documented file order mode, including rows a later session appends to them. A duplicate occurrence inside a fully numbered group now refuses with a typed ConfigError naming the cassette, adapter, and hash, in replay and in an appending record() alike, because a duplicate means two recorder sessions wrote the file concurrently and either order would hand a caller the wrong exchange; the documented contract is one active recorder per cassette at a time, and a violation is now caught instead of silently misordering. Reading the target up front also closes two adjacent holes: record() no longer appends rows to a file that was never a cassette (or is empty), and it refuses a header recorded under a different hashVersion, which would have mixed two identity profiles under one header. The cassette format stays v1 and existing valid cassettes replay unchanged.

Patch Changes

1.32.0

Minor Changes

  • e366d64: Concurrent identical calls replay to the callers that made them (v1.31.0 review P2). record appends rows when each stream completes, so two identical live requests that finished out of order were stored in completion order, and replay, which hands occurrences out in caller order, served each caller the other one's response; a parallel workflow could branch differently on replay even though every hash and every row was valid. Every recorded stream() call now claims a zero based per (adapterId, requestHash) occurrence number synchronously in the call itself and persists it on the completed row, and replay serves same hash rows sorted by that number when every row of the group carries one. An aborted or failed call claims a number but appends no row, and such gaps are valid. The cassette format stays v1: readers before this release tolerate the new optional field and keep file order, and groups recorded before this release (no numbers) keep file order too. readCassette checks the field is a nonnegative safe integer when present.
  • e366d64: Cassette event validation now covers every constrained nested field of the canonical vocabulary (v1.31.0 review P3). Three shapes the documentation already promised to refuse were accepted by readCassette and replay: a tool-call-end without its args (required payload; any JSON value including null is valid, absence is not, because the replayed event would differ from what the live adapter emitted), a refusal stopDetails that is not a plain object or whose present type, category, or explanation is not a string, and a finish providerMetadata that is not a plain object. All three now refuse with a typed ConfigError naming the JSONL line and the exact field path.
  • e366d64: VCR passthrough now preserves truthful adapter provenance (v1.31.0 review P2). The engine journals every response served through a replay wrapper under the wrapper's own provider and usageSemantics declarations, and under onMiss: 'passthrough' that includes live served misses: before this release a miss served by the live adapter was journaled under the declarations of the recorded rows (a stale stamp asserting a semantics the serving adapter did not use), and a live adapter with no recorded rows lost both declarations entirely, so its journals went unstamped. replay now refuses at construction with a typed ConfigError when the cassette rows and the live passthrough adapter disagree on either declaration, absent versus present included, and an adapter with no recorded rows keeps the live adapter's own declarations, so wrapping stays metadata preserving. Under onMiss: 'throw' the live adapter only backs caps lookups and never serves, so no agreement is demanded there.

Patch Changes

1.31.0

Minor Changes

  • df6b8f8: readCassette now validates the nested structures of every row, not only field presence: the request must be a plain object (an array was accepted), every event must be a member of the canonical ChatEvent vocabulary with its required payload and the numeric Usage invariants (a null element used to crash replay with a raw TypeError, and a bare { type: 'finish' } reached the engine and died there on the missing usage), and caps must carry every ModelCaps field, with the optional pricing table checked when present (an empty object passed as a snapshot). Failures throw a typed ConfigError naming the cassette path, the JSONL line, and the field path. Unknown extra fields stay tolerated for forward compatibility; an unknown event type is refused. Event stream semantics (exactly one trailing terminal per row) and adapter consistency across rows stay replay build concerns, so reading never blocks inspecting a well formed file.
  • df6b8f8: VCR cassettes now carry the recording adapter's declared usageSemantics, and replay restores it. record snapshots the field into every row, readCassette requires a nonempty string when the field is present, and the adapter that replay rebuilds declares the recorded value, so the fresh journal of a replayed run gets the same provenance stamp the recorded run got. Before this, a replayed run's usage bearing entries were unstamped, which reads exactly like an entry recorded before the stamp existed; for an OpenAI journal with cache writes that unstamped shape is what the v1.19 cache audit treats as affected, so an honest replayed total could be "corrected" into a wrong number. All rows of one adapter must agree on provider and on usageSemantics; a conflict refuses with a typed ConfigError before anything is served. Cassettes recorded before this release store no usageSemantics and keep replaying, with nothing stamped (the documented legacy reading).

Patch Changes

1.30.0

Minor Changes

  • 87ce985: Replay repeated request hashes as ordered occurrences and validate the full cassette shape (v1.29.0 review P2 and P3). Published 1.29.0 built replay on a Map<requestHash, row>, so a cassette holding two exchanges under one hash (a recorded retry: error then success) served only the later row, on the first call and forever: the recorded error branch never replayed, usage and cost silently shrank, and no occurrence was ever exhausted. Rows sharing one (adapterId, requestHash) key now form an ordered occurrence list; every stream() call consumes exactly one occurrence in file order, claimed synchronously inside the call itself so concurrent identical requests each get their own exchange. A call past the last occurrence is a typed miss: VcrMissError gains a recordedOccurrences field saying the hash was recorded but is exhausted, and onMiss: 'passthrough' forwards exhausted hashes to the live adapter. replay also refuses a cassette whose row does not end with exactly one terminal event or whose caps snapshots conflict for one (adapterId, model), and readCassette now validates the full documented header and row shape (integer hashVersion, date string recordedAt, nonempty model, a request object, a caps object, a string provider when present) with errors naming the cassette path and line; unknown extra fields stay tolerated for forward compatibility. Hand written cassettes missing documented fields, previously accepted and failed late with misleading errors, are now refused at read time; cassettes written by record always carried the full shape.

Patch Changes

1.29.0

Minor Changes

  • 621d566: A VCR cassette row is now always the record of one completed exchange, and readCassette validates the cassette format version (v1.28.0 review P2 and P3).

    record appends a row only when the wrapped stream delivered exactly one terminal event: a requested abort and a naturally truncated stream (no terminal), a thrown wire failure, and a contract violating stream (a second terminal or data after the terminal) append nothing. The v1.28.0 behavior that made the append unconditional on a clean generator exit could commit a partial, finish less exchange, which the fail closed core would then replay as a transport error; the intent of that fix is preserved, because a consumer that stops consuming right after the terminal (the engine shape) still commits its row.

    readCassette now refuses a cassette whose header does not declare format v: 1 with a typed ConfigError, instead of silently interpreting an unknown future format as v1 (hashVersion, checked by replay, gates request identity and never substitutes for the format version). Corrupt JSON lines and rows missing their required fields also throw a typed ConfigError naming the cassette path and line, so a torn cassette fails loudly before any partial replay.

Patch Changes

1.28.0

Minor Changes

  • d98eb0b: Enforce the terminal stream contract end to end (v1.27.0 deep E2E review P1 and P2). The runtime now fails closed when an adapter stream drains without a terminal finish or error event: the partial turn becomes a retryable transport fault that feeds the ordinary retry and failover machinery instead of settling as ok with truncated text, and a requested abort (cancel, budget ceiling, idle severance) remains a clean end with no fabricated provider error. Consumption stops at the first terminal event, so events after finish can no longer mutate the value, revise the authoritative bill, or trigger tool execution. The first party adapters enforce the same contract at the wire: the Chat Completions mapper no longer synthesizes finish: stop when the stream is cut before a finish_reason (usage the provider did report is still forwarded, half assembled tool calls are dropped), the Responses mapper fails closed on EOF without a response terminal event, and the Anthropic adapter surfaces a read cut before message_stop as a retryable transport error and no longer converts a caller requested abort during messages.create() into a terminal error. mapResponsesStream and mapChatCompletionsStream accept an optional signal so a requested abort keeps ending the stream without a terminal event. The VCR record wrapper now commits its cassette row even when the consumer stops reading at the terminal event (the engine always does now); adapter middleware must not rely on being drained past the terminal. The committed combined-loop-descent catalog cassette is refrozen because stopping consumption at the terminal shifts the deterministic interleaving of two parallel plan children by one scheduler turn; entry content, keys, and the actual hashVersion are unchanged, journals recorded under earlier versions replay unchanged, and this changeset carries the frozen fixture gate's hashVersion-bump ceremony token only to unlock that refreeze.

Patch Changes

1.27.0

Minor Changes

  • 884a433: Types referenced by public signatures are now exported from their package barrels, so the API docs resolve them instead of carrying known incomplete references (v1.26.0 deep E2E review): BaseAppend from @rulvar/core (the fields common to every Replayer append), Block and MappedStop from @rulvar/anthropic (the wire level content block alias and the stop reason mapping), and VcrHeader from @rulvar/testing (the first line of every cassette file). The frozen TypeDoc baseline shrinks from eleven entries to the four vendored Standard Schema notices.

Patch Changes

1.26.0

Patch Changes

1.25.0

Patch Changes

1.24.1

Patch Changes

1.24.0

Minor Changes

  • 2b033e8: Remove the repository-only cassette recording plumbing from the public root barrel (the v1.23.0 review): buildFrozenV1JournalRaw, buildM2CassetteFixtures, buildV2GoldenIdentity, recordLiveCassettes, and the M6 recording constants/helpers no longer appear in dist/index.js or dist/index.d.ts. They were @internal and absent from the API reference, yet importable and visible to every consumer's autocomplete, which read as public semver surface. They now live on an internal dist entry that the exports map never exposes; the monorepo's recorder scripts import it by file path. Per the documented versioning policy, @internal exports are outside the contract, so this rides a minor release. The supported tiers (FakeAdapter, createTestEngine, VCR, replay-strict, live smoke, matchers) are unchanged.

Patch Changes

1.23.0

Patch Changes

1.22.0

Patch Changes

1.21.0

Patch Changes

1.20.0

Patch Changes

1.19.0

Patch Changes

  • Updated dependencies [8cc9a9c]
  • Updated dependencies [8cc9a9c]
  • Updated dependencies [8cc9a9c]

1.18.0

Patch Changes

1.17.0

Patch Changes

1.16.2

Patch Changes

1.16.1

Patch Changes

1.16.0

Minor Changes

  • 5f76cf2: Cap runLiveSmoke backoffs at Node's timer maximum (v1.15 review P2-1). Both baseDelayMs and the largest scheduled backoff, baseDelayMs * (attempts - 1), are now validated against the new exported MAX_LIVE_SMOKE_DELAY_MS (2^31 - 1 ms) before any stream opens; past that bound Node would not sleep longer, it would clamp the timer to 1 ms with a TimeoutOverflowWarning and retry almost immediately. Every option rejection now carries field, value, and max in the ConfigError data. Previously baseDelayMs: 2_147_483_648 was accepted and silently turned the backoff into an immediate retry.

Patch Changes

1.15.0

Minor Changes

  • 4aee1f3: Harden runLiveSmoke (v1.14 review P2-1 and P3-1). Options are validated before any stream opens: attempts must be an integer from 1 to the new exported MAX_LIVE_SMOKE_ATTEMPTS (10) and baseDelayMs a non-negative integer; anything else, NaN, Infinity, and fractions included, rejects with a typed ConfigError instead of being clamped, defaulted, or (for Infinity) allowed to spend without bound. The provider SPI's terminal contract is now enforced per attempt: a stream with multiple terminal events, or whose single terminal is not the final event, classifies as the new 'contract-violation' outcome (reason: 'multiple-terminals' | 'terminal-not-final') and is never retried; 'no-terminal' keeps meaning exactly zero terminals. Previously an error followed by a finish classified as 'ok', and explicit attempts: 0 or fractional values were silently coerced. DEFAULT_LIVE_SMOKE_ATTEMPTS is also exported.

Patch Changes

1.14.0

Minor Changes

  • 6073226: Add the live-test opt-in gate and the bounded live smoke. liveTestEnabled(...keys) is true only when RULVAR_LIVE_TESTS=1 AND every named environment key is present, so a provider key alone never triggers a paid call from an ordinary test run. runLiveSmoke(adapter, req, options?) drains one adapter stream per attempt and classifies the terminal event: finish passes, a typed retryable error (429 rate limit, 529 overload, transport) retries with linear backoff up to the attempt bound, a non-retryable error fails immediately with the typed WireError intact, a stream without any terminal event is reported as the adapter-contract violation it is, and a thrown stream propagates unchanged. Rulvar's own key-gated live suites (Anthropic, OpenAI, ai-sdk bridge, the umbrella example) now require the explicit opt-in and run via the documented pnpm test:live command, which reports which suites will fire and never prints key values.

Patch Changes

1.13.0

Minor Changes

  • c28c4c0: FakeAdapter honors the caller's AbortSignal under the same contract as live adapters (v1.12 follow-up review, P2). stream now accepts the optional signal every ProviderAdapter receives and obeys the adapter-authors abort rule: an abort ends the stream promptly with no terminal event and is never converted into a fake provider error. A request whose signal is already aborted on arrival is never served: no responder runs, nothing is recorded in fake.calls, no events are emitted. An abort while an async responder is pending detaches the responder (its late value is discarded and a late rejection cannot become an unhandled rejection) and ends the iterator without waiting it out; an abort during event emission stops at the next synchronous boundary. Cancellation, deadline, and budget tests over createTestEngine therefore observe the same journal shapes as production adapters: a cancelled run journals the agent as cancelled, never as a false agent: ok terminal. Non-aborted behavior (output events, usage, tool calls, structured-output tiers, call recording, deterministic ids) is unchanged.

Patch Changes

1.12.0

Patch Changes

1.11.0

Patch Changes

  • 0c70c5e: Repair the committed class-decision-fanout cassette: the M9 live re-record was itself corrupted by the suspension split-brain fixed in this release (its recorder resolved report-1 on the settled handle, waking the closed body while a resume appended concurrently), so the committed journal held two byte-identical report-2 suspended entries with the same seq. Re-recording through the fixed engine drops exactly the duplicate twin; every other live cassette is byte-identical. This is NOT a hashVersion-bump and no identity profile changed; the literal ceremony token appears here only because the frozen-fixture lock refresh requires a changeset carrying it, and a corrupt-fixture repair is precisely the deliberate, reviewable diff the ceremony exists to force.
  • Updated dependencies [0c70c5e]

1.10.0

Patch Changes

1.9.0

Minor Changes

  • 7577f8e: Correct the Anthropic fallback pricing to the official table and export versioned price tables from both first-party adapters.

    The ANTHROPIC_MODELS seed rows had never been audited against the published price list and overcharged every current Claude model: Fable 5 was seeded at exactly 2x the official rate (20/100 vs 10/50 per MTok, cache rates likewise), Opus 4.8 at 12/60 vs 5/25, Opus 4.7 at 10/50 vs 5/25, and Opus 4.6 at 15/75 vs 5/25. Claude Sonnet 5 now carries its introductory price (2/10, in effect through 2026-08-31); Haiku 4.5 and Sonnet 4.6 were already correct. Cost reports for affected models drop accordingly, and budget ceilings admit roughly twice the work they previously rejected.

    New exports ANTHROPIC_PRICING (anthropic-2026-07-16) and OPENAI_PRICING (openai-2026-07-16) publish the seed rows as versioned PriceTables for createEngine({ pricing }), so runs journal a concrete pricing version instead of unpriced and price revisions become explicit table updates. createTestEngine gained a pricing passthrough for testing against a versioned table.

Patch Changes

1.8.0

Patch Changes

  • Updated dependencies [25724b5]
  • Updated dependencies [57ea1de]
  • Updated dependencies [7884ec5]
  • Updated dependencies [52db30d]

1.7.0

Patch Changes

  • Updated dependencies [45285aa]
  • Updated dependencies [2f20d1d]
  • Updated dependencies [22f65a8]
  • Updated dependencies [2ddfa29]
  • Updated dependencies [2abd9c2]
  • Updated dependencies [1c1175d]

1.6.0

Patch Changes

  • da4dbad: Write the product name as Rulvar in prose: package READMEs, npm descriptions, and the documentation site now capitalize the brand. Identifiers keep their exact casing, so package names, the rulvar binary, rulvar.config.mjs, the .rulvar store directory, the rulvar.* OTel attributes, and every URL are unchanged. Documentation and metadata only; no runtime behaviour changes.
  • Updated dependencies [da4dbad]
  • Updated dependencies [487da86]
  • Updated dependencies [df416fc]
  • Updated dependencies [a737810]
  • Updated dependencies [9eb66b4]

1.5.2

Patch Changes

1.5.1

Patch Changes

1.5.0

Patch Changes

1.4.0

Minor Changes

  • c4f563d: Production readiness fixes from the July 2026 full audit.

    • The budgetUsd ceiling now survives resume: the engine records it in RunMeta.budgetUsd and restores it on every resume, so the replayed spend counts against the original invocation's bound and ResumeOptions still exposes no way to raise it. Journals written before the field existed (or read through a store that drops optional RunMeta fields) resume uncapped, exactly as before; the conformance kit gains a round-trip check so custom stores cannot drop the field silently.

    • spawn:rejected and resolution:applied / resolution:superseded are now emitted: live admission rejections carry the rejection code, agentType, and the journaled decision entryRef (absent only for pre-admission config gates), and live resolution attempts report winning or losing the first-closing-wins fold. spawn:admitted now carries the decision entryRef and the admitting verdict arm. The orchestrator:budget union member now types the two payload shapes actually emitted; journal:compat stays declared but unemitted (the scan runs before a run's event stream exists) and its TSDoc says so.

    • toOtel implements real parent-child span nesting when contextApi and setSpan are passed; without them spans stay flat but attributed.

    • 'readonly' isolation now compiles a deny rule for tools declaring risk write or destructive into the spawn's permission chain, exactly as the tools guide documents; read tools and other isolation modes are unaffected.

    • VCR replay() refuses a cassette recorded outside the engine's hashVersion support window ([CURRENT-1, CURRENT]) with a typed ConfigError instead of silently drifting; in-window cassettes replay as before.

    • InMemoryStore accepts { quiet: true } to opt out of the durability warning, and the warning text now states the precise truth: nothing survives a process exit and cross-process resume is impossible (same-process resume of a kept instance works). createTestEngine constructs its store quietly, so the blessed offline tier no longer prints a misleading warning.

    • The bare Date.now() / Math.random() development warnings no longer blame workflow code for calls that originate in library internals (the engine's own retry jitter, provider SDKs): the retry jitter uses a natively captured Math.random, and the in-process guard skips callers that live under node_modules.

    • rulvar run --profile now applies the profile's per-role effort hints: entries in defaults.routing that carry no effort are seeded from RunProfile.effortByRole (an explicit host effort always wins; ladder entries and unrouted roles stay untouched).

    • rulvar --help documents the shipped kb inbox and kb gate subcommands.

    • The unscoped rulvar pointer package ships TypeScript declarations (index.d.ts with a types export condition), so strict TypeScript projects can import the bare name; the install smoke gate now packs and checks the pointer alongside the umbrella.

Patch Changes

1.3.2

Patch Changes

  • ddef383: Every published package now ships a README, so its npm page states what the package is, how it installs, and where the documentation lives (npm includes README.md in the tarball regardless of the files allowlist, so no manifest changes are involved; @rulvar/compat gains its README on its own next release). Alongside, the repository-level pages are refreshed to the current project state: the root README is rewritten around the never-pay-twice pitch with a runnable quickstart condensation and the full package table, CONTRIBUTING.md lists the complete PR gate set, the examples README drops retired-spec citations for live docs.rulvar.com links and documents the dogfood journal replay, and the pointer README gets the same treatment.
  • Updated dependencies [ddef383]

1.3.1

Patch Changes

  • 7d1552e: Runtime message strings no longer cite the retired internal specification set: error and warning messages, validation issues, and the CLI help text drop the dangling docs/NN, section ... references, pointing at https://docs.rulvar.com pages where a pointer earns its place (the CLI help header, tool naming, toolset registries, bare resume). The umbrella package description sheds the naming-contingency note: the unscoped alias is published and owned. Three strings embedded in frozen recordings stay byte-identical on purpose (the no-progress abort reason and two testing-internal recorder strings), as does the byte-locked golden-fold fixture. Test-file comments lose their citations too; test titles are unchanged.
  • Updated dependencies [7d1552e]

1.3.0

Patch Changes

1.2.0

Patch Changes

  • 154507b: TSDoc and inline comments no longer cite the retired internal specification set (the pre-docs-site docs/NN, section ... references). The citations either became links to the public documentation at docs.rulvar.com or were dropped where the comment already carried the rule; traceability markers (DEF-n, XF-nn, FR-nnn, OQ-nn, W-nnn) are untouched. Comment-only change: no runtime behavior, no API shapes, and no runtime message strings were modified; the frozen golden-fold fixture is byte-identical.
  • Updated dependencies [3bfaec0]
  • Updated dependencies [890f42c]
  • Updated dependencies [154507b]

1.1.0

Patch Changes

1.0.0

Minor Changes

  • 807d1f9: M9-T04 (final part): the DEF-4 live re-record, production-journal replay, and the one-CI-job catalog gate (docs/09 section 6; docs/10 M9 row "Complete catalog green in one CI run"; the 1.0 gate of docs/12 section 5).

    • The six DEF-4 cassettes are re-recorded through the LIVE producers per the synthetic-fixture rule: engine runs, RunHandle.resolveExternal, and the offline kernel writer (the M8 machinery) produce the committed journals; recordLiveCassettes gains the six recorders and scripts/record-m3-cassettes.mjs regenerates them. The synthetic builders stay in the suite as the kernel regression (def1-def4.test.ts now replays the builder output for DEF-4), and the new def4-live.test.ts replays the committed live forms end-to-end, seq-agnostic.
    • Production-journal replay is wired: dogfood journals live under the frozen journals/ directory and every one replays STRICT with zero live calls against its shipped workflow (examples/src/journals.test.ts; RECORD_DOGFOOD=1 re-records). Seeded with judge-panel-fake, a full run of the shipped judge-panel example.
    • The catalog gates as ONE CI job: cassette-catalog runs scripts/catalog-audit.mjs (every docs/09 section 6 ID must resolve to a cassettes/ fixture or a named suite; 58 IDs today, parser-drift guarded) and then a single vitest invocation over every cassette suite (the M2/M3/M9 fixture suites, the M7/M8/M9 plan cassettes, the M8 multi-process soak, and the dogfood journals), replay-strict with zero live calls.

Patch Changes

  • Updated dependencies [0e0b569]
  • Updated dependencies [b28b7a3]
  • Updated dependencies [b53a89e]
  • Updated dependencies [4454175]
  • Updated dependencies [6599ca8]
  • Updated dependencies [6649e5f]
  • Updated dependencies [fd2f83b]
  • Updated dependencies [01d6b2d]
  • Updated dependencies [9a20dbb]
  • Updated dependencies [0fbe7ea]
  • Updated dependencies [ebe0abc]
  • Updated dependencies [a3079d0]
  • Updated dependencies [596a39b]
  • Updated dependencies [464ab6e]

0.9.0

Minor Changes

  • 65c7b2c: M8-T01: createServer, the HTTP shell (docs/02 section 8.2; FR-702), plus the Engine.stores seam it stands on (docs/06 10.2, M8 entry amendment).

    • @rulvar/cli: createServer({ engine, workflows }) returns { fetch(req: Request): Promise<Response> } with the five canonical routes: POST /runs (start a registered workflow), GET /runs/:id (status and outcome), GET /runs/:id/events (SSE; Last-Event-ID maps to the event seq, replay is at-least-once and consumers deduplicate on replayed), POST /runs/:id/external/:key (programmatic resolution, by: 'external'; a run that settled suspended in-process auto-resumes; a run not live in this process gets the documented offline append under a lease where the store is leasable, and resumes on a worker), GET /runs/:id/cost (the settled in-process CostReport, or the pure journal fold priced by the optional priceUsd). Authentication stays host middleware (docs/14, OQ-16).
    • @rulvar/core: the Engine interface gains the readonly stores accessor exposing the configured journal and transcript stores; exactly the instances createEngine received (or defaulted), no store contract widens.
    • @rulvar/testing: createTestEngine forwards the new stores accessor.
  • ebc8101: M8-T04: the redaction and retention interim rules executed (docs/14 OQ-20 and OQ-22; docs/09 section 8 rewritten to the executed state; docs/03 12.4 and 12.8; docs/06 10.1 and 10.2 amendments).

    • @rulvar/core: the L0 SerializationHook (createEngine({ serialization })): redact/encrypt at the append/put boundaries, symmetric on load/get, applied by wrapping the stores so Engine.stores exposes the one policy point; kernel ordering fields are drift-checked with a loud ConfigError. Default key masking at the telemetry boundary: every emitted WorkflowEvent passes maskSecrets (provider keys, PATs, bearer tokens, JWTs, private-key blocks become [masked-secret]); opt out via redaction: { maskEvents: false }; never touches the journal. Retention: TranscriptStore.delete(ref) joins the SPI (missing ref is a no-op; InMemory and File stores implement it), Engine.deleteRun(runId) cascades blob deletion before the journal (no orphan transcripts), and Engine.pruneRun(runId) deletes checkpoint blobs of ok-terminal attempts that nothing else references (parked, cancelled, escalated, and hanging attempts keep theirs).
    • @rulvar/cli: createServer and createWorker take the opt-in retention predicate over RunMeta (the server applies it at terminal settles, the worker during sweeps under a brief lease); the OTel exporter masks string span attributes with the same policy, defense in depth over the already conservative attribute content policy.
    • @rulvar/testing: createTestEngine forwards deleteRun/pruneRun.

Patch Changes

  • Updated dependencies [84f94d4]
  • Updated dependencies [65c7b2c]
  • Updated dependencies [a2a3243]
  • Updated dependencies [ebc8101]

0.8.0

Patch Changes

  • Updated dependencies [85d55cf]
  • Updated dependencies [b88c9e3]
  • Updated dependencies [f3c4613]
  • Updated dependencies [a41c20f]
  • Updated dependencies [f4e70be]
  • Updated dependencies [75d1646]
  • Updated dependencies [0627413]
  • Updated dependencies [55c0f87]
  • Updated dependencies [fd33871]
  • Updated dependencies [e70e7f4]
  • Updated dependencies [bc9c903]

0.7.0

Minor Changes

  • 10b45f1: M6-T11: the rulvar plan command and the M6 gating cassettes. rulvar plan "<goal>" [--dry-run] (the canonical grammar) loads @rulvar/planner DYNAMICALLY (the CLI's static dependency stays @rulvar/core; a missing install is a clear error), plans against the host-config engine, prints the accepted script plus its advisory diagnostics, and runs it in the worker sandbox unless --dry-run. The three docs/09 6.10 gating cassettes are recorded on the FakeAdapter and committed under the frozen-fixture lock with exported scenario builders shared by the recorder script and the replay tests: sandbox-determinism (two fresh runs of one CompiledWorkflow produce byte-identical normalized journals matching the cassette), planner-self-repair (the failing draft round-trips through the JSON-diagnostics repair, re-planning from the committed journal is free, and the accepted script executes deterministically in the sandbox), and orchestrator-crash-resume (the committed pre-crash journal plus boundary checkpoints resume with zero re-paid spawns, no duplicate spawn decisions, and byte-stable handles).

Patch Changes

  • Updated dependencies [fd1d06c]
  • Updated dependencies [6fcf296]
  • Updated dependencies [dcc97a9]
  • Updated dependencies [434dc83]
  • Updated dependencies [03173c1]
  • Updated dependencies [11c0afc]

0.6.0

Minor Changes

  • 638d9a1: M5-T04 VCR cassettes and cron contract tests. @rulvar/testing gains the tier-2 VCR at the adapter boundary: record({ adapters, cassette, redact? }) wraps live adapters and appends redacted JSONL rows keyed by a hash of the canonical wire-contract request (the engine-populated providerOptions.rulvar telemetry namespace is excluded from the key); replay({ cassette, onMiss }) serves recorded streams back with the typed VcrMissError under 'throw' (hermetic CI) or live forwarding under 'passthrough'. Redaction happens at record time: the built-in policy masks authorization material (key-shaped strings, bearer tokens, api-key assignments) in every stored string and a custom hook composes on top, so secrets never reach cassette bytes. Cassette headers record the hashVersion they were produced under (DEF-6), and replay adapters expose the recorded caps snapshots. The live contract-test cron workflow is now real: weekly, non-blocking, gated on the CONTRACT_TESTS_ENABLED variable and provider keys, validating the wire contract (one terminal event, Usage invariant, finish vocabulary) against committed provider cassettes and opening a contract-drift issue on failure instead of rerecording.

Patch Changes

  • Updated dependencies [fa05007]
  • Updated dependencies [9234dc8]
  • Updated dependencies [644512c]
  • Updated dependencies [8a41656]
  • Updated dependencies [02f7f7a]

0.5.0

Minor Changes

  • ac274f4: M4-T01 role protocol completion. The full trigger protocol for the six invocation roles lands in @rulvar/core (model/roles.ts):

    • Extract necessity is completed per docs/04 section 8.3: a separate final structured-output invocation fires when a schema is set AND (routing directs extract to a different model OR the loop model's required tier cannot ride a tools-available turn OR finalize is routed). The required-tier rule is new: a forced-tool tier pins toolChoice to emit_result and cannot ride while the agent's tools must remain available, so such agents now pay one separate extract call instead of silently losing tool access. Agents without tools keep the M1 single-shot behavior byte for byte.
    • The finalize role fires for the first time: only when configured in routing and only for tool-bearing agents, as one synthesis invocation with toolChoice 'none' over the full transcript after tools stop. Its text is the output for schema-less calls; with a schema the separate extract runs over the transcript including the synthesis.
    • A separate extract invocation over a tool-bearing transcript now carries the agent's tool contracts (both providers reject tool-use history without tool definitions) with toolChoice pinned to 'none' or to emit_result per tier.
    • Both adapters map toolChoice: 'none' to the provider's explicit none choice with the tools param present instead of dropping tools from the request.
    • createTestEngine no longer routes finalize by default: the routing key is the firing opt-in, and the old default would have summoned a synthesis call for every tool-bearing test agent. Tests that want finalize route it explicitly.

    Identity is untouched: extract and finalize resolutions never enter the spawn content key, and existing journals replay unchanged.

  • b840aba: M4-T08 canonical effort completion and M4-T09 role quality floors.

    • Effort semantics are complete: the role effort defaults and the per-adapter mapping tables (Anthropic passthrough including max, OpenAI max downmapped to xhigh and recorded in providerMetadata, provider none only via namespaced providerOptions) shipped earlier milestones; this change completes VISIBLE scrubbing everywhere it was still silent: the summarize invocation surfaces its scrubs at fire time and a failover takeover surfaces the fallback's scrubs the moment it starts serving. Scrubbed effort is never mapped into max_tokens.
    • The effort-defaults-shift cassette is now RECORDED through the live runtime (docs/10 M4 gating row): the frozen v1 prefix, closed offline the way an operator would, resumes live under explicit high effort with the completed semantics; every v1 entry matches and the one new spawn carries canonical effort in v2 identity. The recorder output is pinned byte-for-byte by the frozen-drift suite and the fixture lock now covers 18 files.
    • Quality floors (model/floors.ts, M4-T09): per-role and per-declared-taskClass allow/deny lists supplied via createEngine({ floors }), enforced INSIDE the router at resolution, before any live call and before any journal entry, for every invocation the chain produces (primaries, failover fallbacks, and the summarize fallback alike). AgentProfile.taskClass declares the class; unclassified profiles see only byRole floors. A violation is a typed ConfigError.
    • The umbrella rulvar package now ships floors opinions next to its strong routing defaults: recommendedDefaults.floors pins orchestrate and plan to strong named models. The core itself ships no named model strings, and the umbrella suite enforces that with a source scan.

Patch Changes

  • Updated dependencies [ac274f4]
  • Updated dependencies [5735d92]
  • Updated dependencies [46ca98e]
  • Updated dependencies [8ae129e]
  • Updated dependencies [d1c4525]
  • Updated dependencies [b840aba]

0.4.0

Minor Changes

  • dfe03b5: M3-T11 gating cassettes and the v0.4.0 BREAKING release notes.

    BREAKING (pre-1.0 convention, docs/12): AgentStatus now produces 'escalated' at runtime and AgentResult carries the optional escalation: EscalationReport field (present if and only if the status is escalated). This is the third kernel amendment of the replay predicate (escalated-replays-as-ok, DEF-1) whose table row shipped frozen in M2; the producers ship here. Migration: add an escalated branch to every switch over AgentStatus; consumers not adopting the protocol are advised to map escalated to limit (paid partial work, output null, the report stays available for logs). isEscalated and EscalatedResult are exported for narrowing. Status production stays gated by opt-in: workflows that never pass escalation options cannot observe the new status at runtime.

    Cassettes: the DEF-1 live set (escalate-replay, crash-between-report-and-decision, flavor-b-timeout) is recorded through the live runtime and replayed strict; the M2 synthetic DEF-1 subset is re-recorded (memoize-classifier fully live; abandon-subtree through the kernel write APIs with a realistic escalated child report and an authorizing owner cancel decision; both re-record again with the orchestrator producers in M7). FakeAdapter gains fakeToolCalls and fakeWireError responder markers; replayRun gains the onEscalation pass-through so replay tests can prove the hook stays cold. The deliberate fixture regeneration updates fixtures.sha256 in the same change (the identity profile is UNCHANGED; this is the docs/10 M3-T11 ordered re-record, not an identity-pipeline revision).

Patch Changes

  • Updated dependencies [dfe03b5]
  • Updated dependencies [d2089a7]
  • Updated dependencies [3f60234]
  • Updated dependencies [f668890]
  • Updated dependencies [16d7aa6]
  • Updated dependencies [6513ce8]
  • Updated dependencies [7dad493]
  • Updated dependencies [2bbf180]

0.3.0

Minor Changes

  • 43444f6: M2-T11/T12: the executable store conformance kit and the M2 gating cassettes with frozen fixtures.

    @rulvar/store-conformance ships its first real API: journalStoreConformance (A1 append atomicity, A2 total per-run order, A3 read-your-writes, A4 opaque payload with read-side-only normalization, meta separation, the golden fold-state fixture with a frozen reference hash, the decide-once oracle, and the abandon-derived-skip fixture) and leasableStoreConformance (typed LeaseHeldError on held acquire, monotonic fencing epochs, stale-epoch appends rejected and invisible, released leases fenced from renew and append, optional ttl/renew-cadence timing checks), plus registerConformance for Vitest/Jest and the stableStringify fold-state hasher. InMemoryStore and JsonlFileStore pass; deliberately broken stores (reordering, normalizing, tearing, fencing-less) fail loudly.

    @rulvar/core kernel closes three DEF-1/DEF-4 gaps the cassettes gate: an abandon-covered hanging dispatch derives skipped instead of redispatching, abandon-covered operations contribute a zero ledger increment, the resume report lists covered entries as skipped (never orphaned), and an abandon over an already-resolved suspension folds to a noop with already_resolved (first-closing-wins per target, both closer kinds).

    @rulvar/testing ships the M2 cassette suite over committed frozen fixtures: the DEF-1 synthetic subset (abandon-subtree, memoize-classifier, v1-journal-on-v2), the DEF-4 set (timeout-vs-live-race, class-decision-fanout, abandon-then-crash-then-resume, abandon-vs-resolution-race, offline-invalid-then-valid, double-abandon-idempotent), the DEF-6 six IDs (resume-v1-on-engine-v2, resume-v1-with-inserted-call, suspended-v1-resolves-on-v2, reject-version-too-old via deriverV0Synthetic, reject-version-from-future, effort-defaults-shift), the mandatory mixed-version scenarios (ordinal-space split, forward-cursor preference, cross-version resolution, the compatibility and never-pay-twice-through-upgrade lemmas), and KeyDeriver contract tests against the frozen v2 golden identities including the docs/03 worked example. Fixture regeneration is deliberate: scripts/record-m2-cassettes.mjs rebuilds, and CI write protection (scripts/check-frozen-fixtures.mjs plus fixtures.sha256) fails any fixture diff shipped without the explicit bump token (the hyphenated compound of hashVersion and bump) in a changeset.

  • a1b35d3: M2-T09/T10: engine.resume under the run-to-definition binding contract (wf required for in-process runs, name mismatch is a typed ConfigError, body-hash mismatch warns loudly and proceeds; the compatibility scan runs strictly before any side effect; the resumed run seeds the budget from the ledger fold, re-emits open suspensions, and reports ResumePreview hits/misses/reruns/orphans plus invalid offline resolutions), the dryRun option (replay-strict matching: the first would-be-live call settles the run with the typed journal_miss error and zero live calls), and @rulvar/testing replayRun (tier 3: strict replay of any journal with JournalMissError on ANY live call; suspended journals finish suspended with zero live calls).

Patch Changes

  • Updated dependencies [43444f6]
  • Updated dependencies [279881b]
  • Updated dependencies [9fd0966]
  • Updated dependencies [24ebadf]
  • Updated dependencies [a1b35d3]
  • Updated dependencies [18a5821]

0.2.0

Minor Changes

  • 5c4fc32: M1-T14/T15: @rulvar/testing tier 1 (FakeAdapter matching on agentType/label/prompt regex with a '*' fallback, honoring the selected structured-output tier, zero USD by construction; createTestEngine over the full real engine with recorded event streams; toHaveCalledAgent and toStayUnderBudget matchers at '@rulvar/testing/matchers') and the completed umbrella (re-exports of @rulvar/core and both first-class adapters, renderProgress, the umbrella-only recommendedDefaults strong model slots, the M1 exit-criteria example workflow, and the CI install smoke on packed tarballs). The core now populates the reserved providerOptions 'rulvar' telemetry namespace on every request (docs/04 section 1.8 as amended) and AgentResult carries errorMessage detail for journaled WireError fidelity.

Patch Changes

  • Updated dependencies [c24228d]
  • Updated dependencies [c50871e]
  • Updated dependencies [1af8fb9]
  • Updated dependencies [1fe0249]
  • Updated dependencies [5c4fc32]

0.1.0

Minor Changes

  • f4e2be9: M0 repo bootstrap (v0.1.0, docs/10-implementation-plan.md section "M0"): monorepo scaffold on the committed toolchain (pnpm 11 workspaces with catalogs, TypeScript 6.0, tsdown, Vitest 4, ESLint 9 flat config, Turborepo 2, changesets fixed mode, npm trusted publishing), the docs/ canon as single source of truth, the L0 contracts skeleton in @rulvar/core, and the vendored dependencies (StandardSchemaV1/StandardJSONSchemaV1 types, the @cfworker/json-schema lineage validator subset, a first-party monotonic ULID). Placeholder scaffolds only: no public API ships in this release.

Patch Changes