Changelog
Notable changes to the j17 platform.
0.25.23
October 13, 2026Improved
- Faster data processing. Reading and saving data, and importing saved history, now spend less time preparing your application's rules.
Fixed
- Consistent results during application updates. Requests already in progress keep using the rules they started with, and delayed responses cannot restore rules from an older deployment.
0.25.22
October 13, 2026Fixed
- Import saved history without repeating past actions. Importing previously exported data no longer creates duplicate events, triggers automated actions again, or cancels scheduled work.
0.25.21
October 5, 2026Added
- Tombstone consistency warnings appear in health and metrics. Confirmed discrepancies are reported without automatic repair; operator notification delivery remains a separate follow-up.
Fixed
- Backups capture authoritative environment definitions. Concurrent definition changes reject capture rather than publishing mismatched files. Checkpoints and internal event exports reject incomplete reads, and exports honor the environment in their URL.
- Erasure cannot publish obsolete aggregate caches. Overlapping erasures retain verifiable progress, scheduled cancellation stays within its environment, and recoverable fold failures remain retryable.
- Blob versions remain immutable across retries and repairs. Conflicting uploads and retired-version reuse are rejected, deletion preserves bytes if its authoritative write fails, and ambiguous storage paths are refused.
- Browser access and key changes enforce current ownership. Revoked access cannot keep using instance pages or mutate another owner's keys.
- Public CLI 0.1.4 protects credentials and exact JSON numbers. Redirects are rejected and spec uploads preserve large integer constants. Compiler output limits and input ownership checks also fail safely.
- Deployment and backup recovery fail safely. Interrupted Caddy activation retains its rollback state, production backup settings are checked before deployment, and service backup restart and retention handling are corrected.
- Restored projections and maintenance report incomplete work. SQL reconstruction errors and failed ID-index scans cannot be reported as successful completion.
0.25.20
October 2, 2026Changed
- Service backups use encrypted S3-compatible storage. An isolated offsite rehearsal verified clean-host recovery, rebuilt projections and resumed application operations.
Fixed
- Backup activation cannot reuse another repository's success. Exact snapshot and command identities keep the first-backup gate safe across configuration changes and retries.
- Cold recovery recreates derived stores reliably. Restored directory ownership and explicit projection rebuilding preserve definitions and events while restoring usable query outputs.
- Real worker restores preserve exact images and safe ports. Dual-stack source bindings and Docker image-store differences no longer prevent cold recovery; restored Redis ports bind only to loopback.
- Production deploys retain activated backup settings. Regular deployments load the separate encrypted backup vault and fail closed if it is missing.
0.25.19
September 29, 2026Fixed
- Tombstone retries preserve one progress fact per completed stream. A later stream failure no longer causes bounded retries to duplicate audit progress for streams already erased during the same execution.
0.25.18
September 29, 2026Fixed
- Tombstone erasure now fences scheduled event delivery. Work selected before an erasure can no longer fire afterward and recreate erased data, including for empty and already-erased targets.
- Restored nodes recover registration secrets safely. Provisioning resolves the existing node exactly, regenerates its secret, and stops rather than deploying an unauthenticated worker if recovery is ambiguous.
- Combined control and worker hosts deploy as one verified transaction. The final merged proxy configuration is activated only after both restarted services are healthy, with the previous configuration and binary restored together on failure.
0.25.17
September 29, 2026Fixed
- Redis write failures can no longer masquerade as successful event IDs. Ordinary appends now commit the event, audit-chain tip, and aggregate index together, while concurrent writes preserve one valid hash chain.
0.25.16
September 29, 2026Changed
- Large tombstone cascades now resume without rereading completed streams. Compact progress events make retries proportional to the remaining work and keep execution details out of target reservations.
- Tombstone audit streams use explicit lifecycle events. Claims, plans, root anchors, rewritten streams, failures, and completions no longer share a generic status-update envelope.
- Worker backups fail faster and spend less time stopped. Snapshot scheduling stops after failure, and authoritative tree enumeration happens after service restart.
Fixed
- Tier changes remain authorized through instance and organization access changes. The commit now guards the exact instance, user, plan, and applicable organization states used for authorization.
- Failed tombstone requests retain their reason in list responses. Rebuilt projections now expose the same authoritative failure reason as individual request reads.
- Backup format upgrades cannot skip a failed role-specific snapshot on retry. Provisioning requires a successful backup for the same role and current bundle format.
0.25.15
September 29, 2026Fixed
- Later data can be erased without destroying earlier tombstone audit lineage. A completed target reservation can begin another erasure generation while preserving all existing marker bytes and hashes.
- Tombstones no longer depend on projection completeness when cancelling scheduled work. Cleanup discovers authoritative scheduled-command state and uses optimistic concurrency, so a missing SQLite row cannot allow work to reappear after erasure.
0.25.14
September 29, 2026Changed
- Service backups now keep application pauses focused on coherent data capture. Images and at-rest volumes are prepared while the worker remains available, live Redis snapshots run with bounded parallelism, pause duration is recorded separately, and full repository checks run on a weekly schedule.
Fixed
- Backups and cold restores fail closed if authoritative data is incomplete. A shared role-specific schema requires every event, signing-key, blob, customer-backup, checkpoint, Redis persistence, and image artifact before a restore can mutate its target.
- Tier changes remain authorized through the committing write. Concurrent user, grant, or plan changes now force a complete authorization reread instead of allowing an instance update based on stale approval.
0.25.13
September 29, 2026Changed
- Tombstone cascades now have an explicit direct, point-in-time contract. A cascade erases the root and streams directly referencing it when execution begins; it does not recursively traverse a relationship graph.
Fixed
- Tombstone cascades now resume safely after partial execution. A durable execution plan and authoritative concurrency guards prevent lost work, duplicate requests, and cancel/execute races.
- CLI credentials are bound to their issuing control plane. The public CLI refuses to disclose stored tokens to another endpoint, fails closed on malformed configuration, and uses immutable instance IDs for saved selections.
- The internal test runner no longer kills unknown port listeners. An unhealthy process occupying the test headnode port is reported and left untouched.
0.25.12
September 29, 2026Fixed
- Documented tombstone cascade rules can be deployed again. Aggregate-level onTombstone actor and target rules now pass spec validation, while unknown roles and values remain rejected.
0.25.11
September 29, 2026Added
- Encrypted offsite disaster-recovery backups now have a complete cold-restore path. Authoritative control and worker data can be checksummed, retained, monitored for freshness, and restored without treating rebuildable projections as source data.
Fixed
- Logical backup exports and their verification records now describe the same coherent stream snapshot. A read, signing, or captured-tip failure aborts the export instead of allowing an incomplete or unverifiable backup to appear successful.
0.25.10
September 25, 2026Fixed
- Audit root anchors now describe one coherent cross-stream snapshot. Concurrent atomic writes are included in full or excluded in full, rather than allowing an anchor to mix stream states from different moments.
0.25.9
September 25, 2026Changed
- Stream listing no longer runs a legacy index migration during ordinary reads. A fleet-wide audit confirmed that every environment's derived aggregate-ID index exactly matches its event streams; guarded reconstruction remains available as an explicit recovery operation.
0.25.8
September 25, 2026Changed
- Promo rewards now use only the active reward vocabulary. Retired aliases were removed after the production store and its projection confirmed that no remaining promo depends on them.
Fixed
- Projection SQL preserves duplicate output-column names. Structurally capped queries now report the submitted SELECT's column names instead of exposing SQLite's wrapper-generated suffixes.
0.25.7
September 24, 2026Fixed
- Organization-owned instances no longer produce false control-health warnings. The consistency checker now resolves the public organization owner type to the correct authoritative aggregate.
- Operator status checks distinguish degraded services from healthy ones. A successful HTTP response carrying health warnings is now shown as degraded instead of being treated as healthy based on its status code alone.
0.25.6
September 24, 2026Changed
- Production build tools and Caddy plugins are now pinned and verified. Release downloads are checksum-checked, every custom-build input participates in rebuild detection, and the build cannot silently select a different Go toolchain.
Fixed
- Custom Caddy upgrades now install atomically and roll back on failure. A candidate is validated before installation and must pass an end-to-end health check before deployment commits it. Failed activation restores the previous executable and configuration together.
0.25.5
September 24, 2026Changed
- Feature usage reporting now takes one write per instance. Each worker batches a flush's feature counts together, reducing control-plane work without changing the usage data recorded.
Fixed
- Last-event reads use a consistent stream snapshot. Stream length and edge events now come from one Redis operation, removing a narrow race with concurrent writes.
0.25.4
September 24, 2026Fixed
- Malformed checkpoint data is refused before restore clears the destination. Stream keys that cannot be indexed correctly are rejected consistently by every storage backend. A checkpoint containing one now fails its safety check before any existing environment data is removed.
0.25.3
September 23, 2026Changed
- Repeated cache-rebuild failures no longer consume a worker continuously. When the same aggregate cannot fold under an unchanged spec and event count, the background sweeper waits for new events or a new spec before trying again. Temporary storage failures continue retrying normally.
- Worker diagnostics are quieter and actionable. Operational failures now go to bounded log summaries or the durable audit trail, while instrumentation with no consumer has been removed.
Fixed
- Engine errors appear in worker metrics. The exported engine-error counter is now connected to the engine error signal instead of always reporting zero.
0.25.2
September 23, 2026Added
- Change instance names and tiers from the CLI. User CLI 0.1.2 adds
j17 instance updatewith name and tier changes, including private granted tiers such as alpha. Human and JSON output use the same instance shape as create, show, and list.
Fixed
- Projection SQL catches up after a materialization failure. A failed SQL materialization no longer gets hidden behind a fresh Redis marker. The next background sweep retries it, so projection queries converge without waiting for another source event.
0.25.1
September 21, 2026Fixed
- CLI authorization completes cleanly. Approving or denying a device no longer crashes on an unsupported button style, and the success page now shows the actual client and granted scope instead of template expressions.
- Instance and API-key lists work in the user CLI. User CLI 0.1.1 now accepts the numeric creation timestamps returned by the public API.
0.25.0
September 21, 2026Added
- Manage j17 from your terminal. The new
j17CLI signs in through browser approval, remembers the instance and environment you are working in, manages instances and API keys, and deploys specs. It also provides structured JSON output and stable exit codes for automation. The CLI is available during the private alpha by request; see the CLI documentation to get started. - CLI login is passwordless and renewable. Your password and browser session never pass through the CLI. Access tokens are short-lived, refresh sessions rotate on use and can be revoked with
j17 logout, and simultaneous commands coordinate credential refresh instead of invalidating one another.
0.24.1
August 17, 2026Added
- Cold start — provision a fresh environment in one call. Seed an empty environment from an exported event stream, from the console (Data tab), the management API, or a
deploykey in CI. It writes only into an environment that has no data yet and then latches it, so it can never overwrite live data; with a deploy key it is limited to staging and test. Use it to stand an environment up from an export; use import to change data that already exists. See cold start.
0.24.0
August 14, 2026Changed
- The log records which writes were conditioned. A guarded write's first event now carries
metadata.previous_length— the value its concurrency check ran against — and an unguarded write's events omit the field entirely. Absent and zero are deliberately distinct: zero means "checked against an empty stream", absence means "no precondition was stated". Events written before this release carry a defaulted 0 that says nothing; from this release on, the field is trustworthy evidence of arbitration. See what the log records. - Atomic writes validate harder and answer more precisely. A negative or malformed
previous_lengthis refused rather than silently disabling the check it names; a request is capped at 100 events and 1MB of event data; refusals carry the target (and event) position you can act on; and a conflict on a precondition the server itself resolved retries briefly instead of surfacing, while a precondition you stated still fails fast — only you can remake that decision.
Fixed
- Idempotent replays no longer bill. A retry caught by the write ledger returned the original response but metered the write again. Replays now count zero, as the metering docs always said.
- Batch writes run their side effects. Identity mappings and geo-index entries emitted by an event were applied on single writes but silently skipped when the same event arrived in a batch or an atomic write. All write shapes now behave identically.
- Concurrent writes to one stream can no longer fork its hash chain. The conditional append now verifies the chain tip it extends from inside the same atomic step as the write, so racing writers serialize instead of one of them chaining from a stale tip.
- written in the atomic-write response counts events, as documented; it briefly counted targets.
0.23.0
August 13, 2026Added
- One write across several aggregates. Atomic writes commit events to several aggregates at once, each with its own concurrency check, and either all of them land or none do. That makes a decision spanning aggregates guardable: create this booking only if both calendars are still where you read them. A conflict names the aggregate that moved and writes nothing, so you re-read and retry the whole set. Most applications never need this — an implication is usually what you want, because it expands after its parent is written and so cannot veto it. Reach for an atomic write only when the second write can mean the first should never have happened.
Changed
- Batch writes are now metered per event. A batch to one aggregate previously counted as a single event no matter how many it wrote. It now counts every event, so grouping writes together no longer changes what you are charged. Writes that were rejected or replayed from an idempotency key now count nothing, having previously counted one.
- Two aggregate type names are now reserved.
healthandmetricscollided with platform routes and were unreachable over HTTP; names shaped likev1orv2are held for a future versioned API. No deployed spec uses any of them.
0.22.0
August 11, 2026Changed
- Confirming a breaking change no longer signs for unrelated ones. The acknowledgement exists for changes that alter what your already-stored events mean — removing a type, or narrowing a property's type. Tightening a schema does neither: validation runs when an event is written and never again, so nothing already stored is at risk. Those are advisories now and no longer block a deploy. Read more →
- The spec check catches three more ways a spec can break an aggregate. A field it could not prove no longer hides one it can; keyed counters like
increment_atnow account for the value they write; and an operation written beside anifis no longer skipped. A spec that deploys today may be refused — the message names the fields and the two events involved. - And it stops refusing three things that were always fine. Dynamic keys, optional values on
mergeandset_at, and two operations written in one object.
Fixed
- Deploy advisories appear in the console again. A deploy that succeeded with advisories rendered a blank screen where a clean one showed a confirmation — the single case the advisories were added for.
- Upgrade warnings describe what actually happens. Three of them claimed events already stored would fail validation. They cannot: validation runs on write and the fold never re-checks. They now say what does change — that a client still sending the old shape starts getting 422s.
0.21.0
August 10, 2026Fixed
- Usage accounting no longer loses counts when a node cannot be reached. Usage is measured where your events are handled and reported onward for billing. A report that failed to arrive — during a deploy, say — was previously dropped, so the totals behind your dashboard could sit slightly below what you actually used. Reports are now retried, and carry an identifier that keeps a retry from counting twice.
0.20.2
August 10, 2026Fixed
- The spec check no longer misses specs that would break. A review found five ways a spec could pass the check and still fail permanently when aggregates are computed — among them a write to a nested path quietly changing the container above it, and counters and watermarks being invisible to the check altogether. All five now report.
- A literal value referencing the event is no longer misread. Writing
{"tags": "$.data.tags"}was treated as assigning that text rather than the payload's list, so a correct spec was refused. - The undeclared-keys advisory was wrong in both directions. It judged an event by its whole schema even when the handler assigns only part of the payload, so it warned about payloads that were closed and stayed quiet about open ones. It also blamed other events for fields the same handler sets.
- A check that cannot finish now refuses rather than passing. Running out of memory previously reported no findings, which reads exactly like a clean spec.
- Deploy advisories appear in the dashboard. Deploying from the console reported plain success while the same spec deployed through the API returned advisories. Read more →
0.20.1
August 10, 2026Fixed
- Spec deploys are no longer refused for shapes we merely could not prove. The check treated "I cannot verify this" the same as "this is provably wrong", so correct specs using a state path, an actor id, or a
letbinding as an operation value were turned away. Those now come back as advisories on the deploy response. Only a demonstrable mismatch still refuses. Read more →
0.20.0
August 9, 2026Added
- Deploying a spec can now tell you something without refusing it. A deploy returns a
warningslist alongside success when there is something worth knowing but nothing worth blocking. The field is left out entirely when there is nothing to say, and the pre-flight check returns the same answer, so a pipeline sees it before anything is written. - First advisory: an event that accepts undeclared keys. A handler that assigns the whole event payload to the aggregate, with a schema that does not close, lets a caller write any field it likes into your state — including fields another event owns. This is allowed: an open payload is sometimes exactly what you want, and a log-style aggregate with no named fields is never flagged. Close it with
"additionalProperties": false, or declare the keys that event may carry. Read more → - The same advice whichever endpoint you use. Deploying through your instance host and deploying through the management API now return identical warnings.
0.19.0
August 9, 2026Added
- Spec deploys now check that collections have a provable shape. Appending to a field that holds an object could never work, and because aggregates are computed by replaying every event, that failure was permanent — the replay stopped at the same event every time, so no later event could correct it. Deploying a spec now requires each collection to be provably the right kind of value, which catches the mistake while it is still a text edit.
- Declare the container, not the contents. A field is proven if no handler assigns it, if it is seeded with an empty
[]or{}, or if the event schema gives it a type.{"type": "array"}with noitemsplaces no constraint on the elements. Read more → - Any event that writes a whole payload counts, not just creation. A
mergeat the root writes every key the payload carries, so an update event can land a wrong-shaped value in a collection some other event builds. It is easy to miss by eye — the calling code reads as "update these three fields", and nothing there suggests an extra key reaches the aggregate root. - Keyed writes reach one level down.
set_atandmerge_atwrite below their target, so aset_aton the root whose key comes from the payload can land on any top-level field. Both are checked the same way. - A payload can stay open. Use
"additionalProperties": falseto prove a collection can never arrive through an event, or declare only the keys that are not free-form and leave the rest unconstrained. An aggregate with no creation event needs nothing at all. - Running specs are unaffected. The check applies when a spec is deployed. Upgrading j17 never stops a spec that is already serving.
0.18.2
August 9, 2026Fixed
- Resolved listings are as current as single reads. A listing with
could serve aggregate state a moment behind a direct read of the same aggregate. Both now answer from the same freshness rules, so a listing and aGETone request apart never disagree. - Large reads are priced before they are paid for. Recomputing an aggregate from a long tail of events is now measured as the events arrive and declined the moment it exceeds the budget, rather than after the whole tail has been read. The refusal is the same 422 as before; what changed is that it no longer costs the read it refuses.
- An aggregate that cannot be read says so. In a resolved listing, an entry the platform could not compute now carries an
errorfield naming the reason instead of appearing as an id with no data. Read more →
Changed
- Faster keyed writes and faster listings. Writes carrying an idempotency key use one fewer round trip, and reading an aggregate whose every event replaces its state is a single storage call rather than three.
0.18.1
August 8, 2026Fixed
- Idempotent writes are safe under truly simultaneous duplicates. Two concurrent deliveries of the same keyed request could previously both append. Keyed writes now take an atomic claim: one request executes, a duplicate arriving mid-flight gets 409 with
Retry-After, and a duplicate arriving after completion gets the original response replayed. At most one event per key, under any interleaving. - Storage metrics measure only event streams. A one-time index migration could count cache entries toward an instance's logical storage size, inflating the figure. Indexes rebuild themselves correctly on this release; no action needed.
0.18.0
August 8, 2026Changed
- Write metadata is a closed contract. Only
actor,target,previous_length,skip_occandtimestampmay appear in write metadata. Platform-stamped fields and unknown fields are refused with 422 instead of silently dropped — a misspelled OCC guard is now a loud error rather than a guard that never engages. Read more → - Idempotency keys are recorded and layered. A successful write carrying
X-Idempotency-Keystamps the key into the stored event as provenance, and the idempotency ledger is evaluated before concurrency control — a keyed create retry gets its original response back, never a conflict it has to interpret. A second engine-level check refuses duplicates even when the response cache misses one.
Fixed
- Contradictory concurrency options return 422. Sending both
skip_occandprevious_lengthwas refused correctly but reported as an internal error; it now returns a clear client error.
0.17.0
August 8, 2026Changed
- Reads are always current. The default
GET /:type/:idnever serves a stale cached state: the platform checks the cache against the stream on every read and folds in anything the background refresh hasn't caught up to yet, so a read after a write sees the write.?synchronous=truenow means "re-derive everything from the event log" — an audit posture, not a freshness requirement. Read more → - Replaying append-heavy streams is ~30x faster. A 10,000-event append-only replay dropped from ~1.8s to ~57ms. Types whose every event replaces the whole state are served in constant time regardless of history.
- One pagination vocabulary everywhere. Listings speak the same dialect as
/events: the applied limit echoed back,truncatedalways present,next_startpresent exactly when there is more, clamped limits reported instead of silent, and malformed sizes refused instead of defaulted. Truncated SQL query results now carry a hint saying how to continue. Read more → - Specs that could never run are refused at deploy. A literal
nullvalue on a merge-family handler deployed cleanly and then failed every fold; it is now rejected at validation, where you can fix it.
Added
- Specs can document themselves. The documentation subset of JSON Schema 2020-12's meta-data annotations —
title,description,examples— is accepted at the spec root, on aggregate types, and on event types, alongside$schema.versionis reserved for future platform use. Read more →
0.16.4
August 7, 2026Added
- Per-event attestation bundles.
GET /:type/:id/audit/attestation/:indexreturns everything needed to verify one event against a signed anchor — the event's stored bytes and chain hash, its proof into the stream root, the stream root's proof into the instance anchor, and the anchor itself — in one round trip, a few kilobytes, verifiable offline, and disclosing nothing about other streams. Pin an anchor with?anchor=to attest a batch of documents against one anchor. Errors distinguish "this anchor doesn't cover that yet" (wait for the next anchor) from a malformed request, so automated pipelines never guess. Read the spec →
Changed
- Anchor minting scales an order of magnitude further. Creating a signed anchor no longer scans the keyspace — streams are enumerated from an index maintained on every write — and the per-anchor stream ceiling rises from 100,000 to 1,000,000.
0.16.3
August 7, 2026Added
- Events come with their chain hash, and
?raw=truereturns the stored bytes. Every/eventsentry now carrieshash, andraw=truereplaces the decoded event with the bytes as written — exactly what recomputing the hash chain needs, since a re-encoded object doesn't preserve the stored key order. A page of events is now verifiable on its own. Read more →
Changed
- Audit format v2. Merkle hashing is now domain-separated (RFC 6962 leaf/node prefixes), and anchor signatures cover a canonical payload binding the anchor's instance, environment, counts, and timestamp — previously only the root hash was signed, so those fields could be edited in an artifact that still verified. Artifacts carry
"format": 2; existing format-1 artifacts remain verifiable. The full verification rules are now a standalone spec, implementable in any language with SHA-256 and Ed25519. Read the spec →
0.16.2
August 6, 2026Added
- Spec deploys warn about shapes that will perform badly. A successful
POST /_specor/_spec/validatecan now return awarningsarray. The first warning covers aggregate types whose every event appends to state: their cached state grows with the stream and costs about twice its size in traffic on every write, so the warning names the type and suggestsmodules.never_cache. Advisory only — nothing is blocked, and the field is absent when there is nothing to say. Read more → - Bare
@now resolves to the whole aggregate state in saga templates, matching handlers and implications. It was documented everywhere; the saga resolver only implemented@.-prefixed paths.
Fixed
- State paths now work as handler operation values. A value like
"@.status"in asetorappendwas stored as the literal string instead of reading aggregate state — while the same path in a conditional worked as documented. State paths now resolve identically in both positions, the optional suffix (@.field?) works as it does for event paths, and a string that merely begins with@— a mention like"@alice"— stays a literal everywhere rather than being read as a state reference.
0.16.1
August 6, 2026Fixed
- Reading events backwards could skip some of them. A page of events is capped by size as well as by count. Reading with
direction=backwards, a page that hit the size cap returned the oldest events of its window instead of the newest ones you asked for — and the cursor it handed back continued from before the ones it had dropped, so those events could not be reached by paging. Only requests large enough to hit the cap were affected; smaller pages were always correct. Read more → - A projection could report itself up to date before it was. Reading a single projection row wrote the record that tells the background refresher "nothing has changed" before it had finished writing the row to the SQL query store. If that second write failed, the refresher agreed there was nothing left to do and the projection stayed missing from
POST /_projections/query. It now retries instead.
0.16.0
August 6, 2026Fixed
- Aggregate reads are faster on long streams. Cached aggregates were being recomputed from scratch on every pass rather than folding in only what had changed. Answers were always correct — what changed is what it costs to produce them.
- An erasure could leave a stale cached aggregate. Rewriting a stream to fulfil a deletion request could, with concurrent writes, leave an aggregate's cached state double-counting the events it already held. The rewrite now clears that state as part of the same atomic operation, and the cache independently refuses to apply events it may already have applied.
- Caching keeps up under load on busy nodes. A single large instance could consume a node's entire refresh budget, leaving other instances on that node serving uncached reads indefinitely.
Changed
forceon tombstone execution accepts onlytrueorfalse. Any other value now returns 400 instead of being read as "don't force", so a misspelled flag says so rather than failing later with an unexpected grace-period error. Read more →
Added
- Validate a spec without deploying it:
POST /_spec/validate. Runs the same validation and backward-compatibility checks a deploy runs, returns the same errors, and writes nothing — so a spec that cannot ship fails in your precommit hook rather than partway through a release. Read more → - Projection files accept
$schema. Editors read it to find a schema for the file you are editing. Spec files have accepted it since 0.15.1; projections are separate files, so they do too.
0.15.1
August 5, 2026Added
- New tick operation:
merge_add. Adds a whole object of numbers into the target, key by key — the additive counterpart tomerge_max.increment_atadds to one key named by the operation, so a compound entry split across several accounts needed one op per leg and a handler that knew how many legs to expect. This applies the entire delta map in one op, whatever its size. Read more →
0.15.0
August 5, 2026Changed
- The events endpoint is paginated by default. A request to
GET /:type/:id/eventswithout acountnow returns 500 events rather than the entire stream, withnext_startto continue. Responses also carrytruncatedandcount_limit, and truncation is exact — a page that ends on the last event of a stream says so, instead of leaving you to fetch an empty page to find out. If you have a client relying on one call returning everything, it now needs to follow the cursor. Read more →
Fixed
- Event paging limits the work, not just the response. Paging parameters were applied after the whole stream had already been read, so asking for 50 events from a 200,000-event aggregate read all 200,000 and threw away the rest — 79ms of database time and 37MB of transfer to return 10KB. The range is now pushed into the read itself. Same results, bounded cost.
0.14.2
August 5, 2026Added
- Specs accept a top-level
$schema. Point your editor at a JSON Schema and get completion and inline validation while writing a spec. j17 ignores the value — it exists so editor tooling has somewhere to look. Other unrecognised top-level keys are still rejected. Read more →
0.14.1
August 5, 2026Changed
- Projection definitions reject unrecognised fields. An unknown key — a misspelled
fan_out, or anindexeskey that was never a feature — used to deploy successfully and be ignored, while still rebuilding the table. Deploys now fail with the offending key named and the allowed set listed. Read more →
Fixed
- A spec deploy that failed to reach a worker could stay unapplied. Deploys propagate by notifying the worker holding the instance, and a cached spec was trusted until that notification arrived. If it was lost — a network blip, a timeout — the worker kept handling events under the previous spec indefinitely, while the deploy reported success. Cached specs are now re-checked against the control plane every five minutes, so a lost notification costs minutes rather than persisting. Deploys still apply immediately in the normal case.
0.14.0
August 4, 2026Changed
- Aggregate caches update incrementally. Keeping an aggregate cached used to mean replaying its entire history every time an event was appended, so the cost grew with the aggregate's lifetime. An entry now records how far it has been folded and folds only what arrived since. Reads and writes no longer slow down as an aggregate accumulates history. Read more →
- Projection SQL results report truncation. Responses now include
truncatedandrow_limit. A result of exactly the row limit was indistinguishable from a complete one, so a clipped result read as a total — and a sum computed over it was simply wrong, with nothing in the response to say so. Checktruncatedbefore treating a result as complete. Read more →
Removed
- The 10,000-event ceiling on aggregates is gone. It counted events while the memory it protected scales with bytes, it ran after those events were already in memory, and crossing it was permanent — an aggregate past the ceiling could never be read again. In its place is a budget on what a single request may spend rebuilding an aggregate from its log, 32 MB by default. It refuses an operation, not an aggregate: cached reads, writes and the events endpoint work at any size, and only a forced fresh read can exceed it — now a
422naming the cheaper alternative, where the ceiling returned a500. Read more →
0.13.1
August 4, 2026Fixed
- Aggregate caching was not running. A configuration lookup pointed at a field that is always empty, so the background cache builder swept nothing and every aggregate read recomputed from the full event stream. Answers were always correct — the cost was not. Reads that should have been served from cache now are. Read more →
- Projections read with
?synchronous=truenow appear in SQL queries. A synchronous single-row read resolved the projection and cached it, but never wrote it to the store thatPOST /_projections/queryreads — and the cache entry it wrote made the background refresher consider the projection up to date, so it never wrote it either. A projection whose first read was synchronous answered single-row reads correctly while being absent from SQL queries entirely. Read more → - Idle instances are no longer rescanned continuously. The check that decides whether an instance has changed recently enough to be worth rescanning treated "no activity on record" as "assume it changed", and never recorded having looked. An instance that is not being written to therefore stayed in the rescan set indefinitely, which after a restart meant all of them.
0.13.0
August 3, 2026Removed
- Removed the deprecated alternate method of configuring aggregate caching.
modules.never_cacheis the way to exclude a type; everything else is cached by default. Read more →
0.12.3
August 2, 2026Changed
- Aggregate identity in projection
selectpaths.$keynow resolves to the full aggregate key (org:a1b2c3…) and$idto the bare ID — the same value as the automatic_key_idcolumn. Previously$keyduplicated_key_idand$idwas rejected outright. This matches the path vocabulary handlers and sagas already use. No deployed projection used either path, so nothing changes for existing tables. Read more →
Documentation
- Corrected the fan-out example. The guide showed
"path": "$m.key"where the correct form is"$.m.key"— binding references are ordinary JSONPath against the bindings, and the dotless form is rejected at deploy. The example as printed would not have deployed.
0.12.2
August 2, 2026Maintenance
- Internal dependency bump. No API or behaviour changes.
0.12.1
August 2, 2026Added
- Read the most recent events with
?direction=backwards. There was no way to ask for the latest N events —countcounts forward from the cursor, so "the last five" meant paging all the way to the end of the stream.directionselects which window you get:?direction=backwards&count=5is the newest five.startstays exclusive in both directions — page forwards from the last id of a window, backwards from the first.
The array is always oldest-first , whichever direction you ask for. An event stream is a fold, so a page that arrived reversed could not be replayed; direction moves the window, order stays replay order. Read more →
Changed
- An unusable
countis now rejected.count=0, a negative, or a non-numeric value used to be ignored — and ignoring it returned the entire stream to a caller who had asked for a handful. That is the dangerous direction to fail in, so it is now a400. An unrecogniseddirectionis rejected for the same reason: quietly reading forwards would return the oldest events to someone who asked for the newest.
0.12.0
August 2, 2026Added
- Fan-out projections — one row per collection element. A projection emitted exactly one row per aggregate, so the reverse edge of a many-to-many relationship had no indexable read model: if an
orgholds its members as a map, "which orgs is this user in?" meant scanning every org. Addfan_outand the projection emits a row per map key or array index instead, addressable in SQL byuser_id. Membership rules stay on the aggregate that can enforce them — only the query shape changes. Read more → - Monotonic handler operations. For values that advance and never regress — instrument readings, high-water marks, best times. A reading logged late, below one already recorded, now leaves the field untouched instead of rolling it backwards, so the event still writes and the field still tells the truth. These operations are order-independent, so there is no optimistic-concurrency retry loop to write and the event can carry the reading actually observed rather than a value computed from whatever the state happened to be. Available on a fixed target (
max,min), at a dynamic key (max_at,min_at), and key-by-key across an object (merge_max,merge_min). Comparison is numeric by default;"compare": "string"opts into lexicographic ordering for ISO 8601 timestamps. Read more →
Changed
- Projection paths are checked at deploy time. A projection
selectpath is JSONPath, not the template-path language handlers use — the two look identical and sit in the same spec file. A path JSONPath cannot parse used to resolve to null, producing a column that read empty for every row, silently, from the day it shipped. The common way in is the optional-path suffix$.order.memo?, which is a handler feature and not valid JSONPath. Such a path is now rejected with a message naming the problem. If a projection has one today its column is already empty, and the next deploy will fail until the path is fixed. Read more →
Documentation
- Corrected the event stream reference.
GET /:type/:id/eventswas documented with a default page size of 100 — there is none, and omittingcountreturns the whole stream — and with a response shape that did not match what the API returns. It now also states plainly thatcounttakes the oldest N counting forward from the cursor, not the most recent N. - Projections: consistency, path rules, and what they are not. Projections are asynchronous, and a row that has not materialised yet looks exactly like one that was dropped — worth knowing before concluding a projection is broken. Changing a definition rebuilds its table, so it is briefly empty. An unresolvable path yields null and keeps the row, which makes adding a column for a field only newer events set safe and backfill-free. And built-in projections are derived from current aggregate state: anything path-dependent, historical, or analytical belongs in a read model you maintain yourself, fed by listeners or by paging the event stream into whatever store suits it.
0.11.7
July 29, 2026Fixed
- Deployed projections are queryable immediately. A projection that had never received a write answered SQL queries with an error instead of zero rows. Deploying a projection now creates its table right away, so read-before-first-write patterns — sequence numbering, existence checks — work on a fresh environment.
- Quieter platform internals. A background rebuild task could log crash noise during shutdowns and test runs. It now exits cleanly.
0.11.6
July 29, 2026Added
- Reset test and staging environments with an API key. A deploy-scoped key can now
POST /_resetto wipe an environment's events and projections while keeping the deployed spec — so a deploy pipeline can reset and reseed test data as one scripted step, no console login required. Production environments can never be reset this way.
Fixed
- Environment reset internals. Resets now delete the instance's own keys directly instead of clearing the backing database. No user-facing change.
0.11.5
July 28, 2026Fixed
- Projection queries: bound limits now work, and runaway queries stop. Using a parameter for
LIMITin projection SQL used to fail; it now works. Result caps are enforced on every query shape, a query that overstays the 5-second budget is now genuinely cancelled, and parameter mistakes return a clear error naming the mismatch.
0.11.4
July 28, 2026Fixed
- Internal tooling fixes. No user-facing changes.
0.11.3
July 28, 2026Fixed
- Smoother platform maintenance. Some platform upgrades could briefly interrupt service several times in a row. Upgrades no longer interrupt anything.
- Signed root anchors work on every instance. Creating a root anchor could fail on some instances. It now works on all of them.
0.11.2
July 27, 2026Fixed
- Console accuracy. Health and instance counts in the console could display as unknown or zero while everything was fine. The numbers now match reality.
0.11.1
July 27, 2026Added
- Pin our keys without asking us. Your instance now publishes its signing keys at
/.well-known/j17-attestation— no API key, no login. A third party verifying your evidence can fetch the public keys straight from your instance's own address, so the keys they pin never depend on the bundle they're checking. - Timestamp proofs finish the job. OpenTimestamps submissions now upgrade automatically once the Bitcoin confirmation lands: the stored proof becomes a complete, standalone file the standard OpenTimestamps client verifies — no extra steps for whoever holds the evidence.
Fixed
- Anchoring reliability. Fixed two faults in daily platform anchoring, one of which could make a platform inclusion proof fail to verify.
0.11.0
July 27, 2026Added
- Signed root anchors — verifiable attestation, now in your hands. Your instance's cryptographic audit trail was always there; now you can hold it. New
/_auditendpoints on your instance return Ed25519-signed root anchors — a signed snapshot of every stream's Merkle root — in exactly the format thej17-verifytool checks offline. Chain any single event to a signed root without trusting j17 at read time. - Anchor on demand. POST to
/_audit/root-anchorsto mint a fresh signed anchor whenever it matters — right after a critical batch, before an export, on your own compliance schedule — then submit its root to OpenTimestamps or any RFC 3161 timestamping authority yourself. Your evidence, your cadence. - A pinnable signing key. Fetch your instance's public key from
/_audit/signing-keybefore you ever create an anchor, pin it, and verify everything that follows. The key's history rides the same tamper-evident event log it protects, so a key that changes is a key that left a record. - Platform inclusion proofs. Ask
/_audit/platform-prooffor the Merkle proof linking your instance's root into the fleet-wide platform anchor — with the platform anchor's external timestamp evidence attached, and nothing about any other tenant in the response.
Changed
- Signing keys never silently change. A damaged key file now stops anchor creation cold instead of quietly generating a replacement identity — because a signature that can be silently re-keyed isn't worth verifying. Recovery is a deliberate operation that leaves a record, and every key's lifecycle is recorded from the moment it's created.
0.10.0
July 26, 2026Added
- Deploy keys — ship your schema from CI. A new deploy-scoped API key pushes your spec and projections with just the API key, on the same host you already read and write to — no separate login or admin token. Deploy scope is isolated from data access (it can't read or write events) and locked to its environment, so a test key can only ever deploy test. Deploying a bad spec returns clear validation errors.
0.9.13
July 3, 2026Changed
- Internal metering changes. Behind-the-scenes accuracy improvements to per-instance usage accounting.
0.9.12
July 3, 2026Improved
- Usage metering. Refinements to how per-instance usage — operations, bandwidth, and storage — is tracked and surfaced across the platform dashboards.
0.9.11
July 2, 2026Added
- New decrement_at tick operation. Subtract a value at a dynamic key — the counterpart to increment_at, and the dynamic-key twin of decrement. Use it when the amount is a template value (like $.data.amount) you need to subtract from a keyed bucket; previously that required carrying a pre-negated field in the event. Same { target, key, by } shape as increment_at.
0.9.10
July 2, 2026Fixed
- Map implications now fan out correctly. A map implication emits one event per array item — whether they target the same aggregate or different ones — and elements referenced as $item.field now resolve as documented. Previously, a fan-out to a single aggregate could emit only the first item, and $item.field returned nothing.
0.9.9
June 30, 2026Fixed
- Clearer error when an environment has no spec. Reading or writing before a spec is deployed (or right after a reset) now returns a clear message telling you to deploy a spec first — with an error id you can look up — instead of a generic failure.
- Resetting an environment clears the spec view. After a reset, the Spec tab immediately shows the spec was cleared, with a reminder to re-deploy it, so you're never looking at a spec that's no longer there.
0.9.8
June 29, 2026Changed
- List your instances over the API. GET /api/instances now returns the instances you own for any signed-in account — platform admins still see all of them. Previously it was admin-only, so listing your instances meant using the dashboard.
0.9.7
June 12, 2026Changed
- Faster dashboards. Aggregate reads and writes got quicker — we removed redundant recomputation when you write events, so your data views load and update faster.
Fixed
- More reliable startup. Hardened how a node creates its signing key the first time it boots, removing a rare startup race.
0.9.6
June 11, 2026Fixed
- Faster spec uploads. Uploading your first spec is now instant — it previously stalled for several seconds on large specs.
- Foldable spec diffs. When you upload a changed spec, the unchanged sections collapse so your changes are easy to spot.
0.9.5
June 11, 2026Changed
- Internal idempotency key handling. Tidied up how idempotency keys are stored internally. No change to behavior — your keys work exactly as before.
0.9.4
June 11, 2026Changed
- Projection queries report their columns even when empty. A projection SQL query that matches no rows now returns the column names instead of an empty list, so you can still read the result's shape. Rows are unchanged.
0.9.3
June 11, 2026Fixed
- Invalid projections are rejected before they break things. Deploying a projection now validates it against your spec and rejects a malformed one (missing for_each, unknown aggregate type, …) with a clear reason, instead of accepting it and failing later.
0.9.2
June 10, 2026Internal hardening and code-review fixes — no user-facing changes.
0.9.1
June 10, 2026Security
- Tightened our dependency supply chain. Removed an unused payment integration (and 8 transitive dependencies with it), and pinned our build-time and pre-1.0 dependencies to exact, audited versions.
0.9.0
June 10, 2026Fixed
- Invalid specs are rejected before they break things. Deploying a spec now validates it and rejects a malformed one with a clear reason, instead of accepting it and failing later at runtime.
0.8.7
June 9, 2026Added
- Spec change preview. Uploading a new spec now shows a diff against the deployed spec, and flags an identical upload instead of deploying a no-op.
Changed
- Overlay scrollbars in the dashboard. Scrollbars hide by default and fade in on hover, so wide content no longer reserves a permanent gutter.
Fixed
- Projection details no longer crash the data explorer. Clicking "Details" on a projection row with nothing selected raised an error; fixed.
0.8.6
June 4, 2026Fixed
- Instance names ending in -staging, -test, or -prod are now rejected. These suffixes are reserved for environment routing and would cause subdomain conflicts.
0.8.5
May 27, 2026Changed
- Environment reset clears the deployed spec. Resetting an environment now also removes its deployed spec so a new — potentially incompatible — spec can be deployed immediately after without errors.
Fixed
- Singleton actor IDs now accepted. Actor IDs declared in the spec's `singletons` list (e.g. `"platform"`, `"scheduler"`) were incorrectly rejected by the event writer. The spec's singleton list is now correctly passed through to validation.
0.8.4
May 27, 2026Added
- Error lookup API. `GET /_errors/:error_id` is now available via API key. Error IDs appear in the `x-error-id` response header when writes fail. Errors are retained for 24 hours.
Fixed
- Error lookup UI showed "unknown" for all errors. Dashboard was reading fields from the wrong level of nesting in stored error data.
0.8.3
May 26, 2026Fixed
- Spec viewer no longer stretches the dashboard layout. Long lines in the spec JSON now scroll horizontally inside the viewer box instead of expanding the page width.
0.8.2
May 22, 2026Added
- Typed projection columns. Select entries now accept an object form with a `type` annotation: `{"path": "$.user.age", "type": "integer"}`. Valid types: `text` (default), `integer`, `real`, `boolean`, `json`. Typed columns use native SQLite affinities so numeric and range comparisons work correctly in SQL queries. `boolean` stores as `0`/`1`. `json` stays TEXT in SQLite but is signaled to clients via the `types` field in the query response.
- `types` field in SQL query response. `POST /_projections/query` now returns a `types` array parallel to `columns`. Clients can use it to know which columns contain JSON-encoded values that need `JSON.parse()`.
0.8.1
May 22, 2026Breaking change
- The `system*` actor type prefix is now fully reserved. Previously only `system_` (with underscore) was blocked at spec deploy time — bare `system` without an underscore slipped through. Any agent type starting with `system` is now rejected on deploy. Existing event data is unaffected and fully readable — nothing breaks unless and until you deploy a spec change. If your spec declares a type like `"systemadmin"` or `"system"`, rename it and redeploy. If historical events carry the old actor type in their metadata, any code that inspects `metadata.actor.type` will need to handle both names until those events are no longer relevant.
0.8.0
May 22, 2026Breaking change
- Projections no longer include tagged UUID substreams by default. A projection with `for_each: "ledger"` now matches bare `ledger:{uuid}` aggregates only. Tagged variants (`ledger:uuid:2025`, `ledger:uuid:2026`, etc.) are excluded unless you add `"include_substreams": true` to the projection definition. If you rely on the old behaviour, add that flag — everything else stays the same.
Added
- New `include_substreams` field on projection definitions (boolean, default `false`). When `true`, all tagged UUID variants of the `for_each` type are included — one row per variant — with `_key_id` holding the full id including the tag. Filter by period in SQL: `WHERE _key_id LIKE '%:2025'`. See the projections guide for the two use cases and when to reach for a v5 UUID instead.
0.7.12
May 18, 2026Changed
- Internal changes to UUID and humane code handling.
0.7.11
May 18, 2026Changed
- `metadata.actor.id` rule loosened from "v4/v5 UUID only" to the full identifier rule: v4/v5 UUID, humane code, declared singleton, or tagged UUID — the same rule that applies to aggregate keys and `metadata.target.id` (since 0.7.9). The asymmetry blocked named non-user actors from having a clean expression: a webhook source like `twilio_webhook` previously had to be encoded as a UUIDv5 derived from a name, which is exactly the "Don't fake singletons with UUIDs" antipattern the docs warn against. Declaring `"singletons": ["twilio_webhook"]` and using it directly as `actor.id` now works. System-agent actor ids (`actor.type == "system_*"`) continue to use the hardcoded allowlist — the general identifier rule does not apply there.
0.7.10
May 17, 2026Added
- `metadata.timestamp` on the write endpoints (`POST /:type/:id/:event_type` and batch `POST /:type/:id`) is now honored in non-production environments, letting staging/test workloads backdate events without going through the admin import path. Format: non-negative integer Unix seconds; invalid shapes return 422. In a batch write, the top-level `metadata.timestamp` applies to every event in the batch — for per-event timestamps continue to use the admin import endpoints.
- Loud-fail guard on `metadata.timestamp` in `:prod`: requests carrying `metadata.timestamp` against a prod environment are rejected with 422 instead of being silently accepted or silently dropped. Staging→prod fixture leaks now surface immediately instead of quietly drifting the recorded timeline.
0.7.9
May 9, 2026Changed
- `metadata.actor.id` now accepts v4 and v5 UUIDs (previously v4-only). The v4-only check was leftover from when the engine first required v4 everywhere; aggregate keys were broadened to v4-or-v5 shortly after but the actor check wasn't updated. Auth lives at the API key boundary, not at `actor.id`, so there's no security gain from rejecting v5 — and the asymmetry blocked integrators deriving stable actor IDs (e.g., UUIDv5 from external identifiers).
- `metadata.target.id` is now validated and must be a v4/v5 UUID, humane code, singleton, or tagged UUID (`uuid:tag`) — same rule as aggregate keys. Previously any string was accepted. Non-conforming target IDs will now be rejected at write time with a 422 and `path: metadata.target.id`.
0.7.8
May 9, 2026Fixed
- `GET /:type/:id/events?start=
` now correctly applies the documented exclusive cursor. The `start` parameter was accepted but silently ignored — every page returned the head of the stream regardless of cursor, breaking client pagination loops (they would re-receive the same events forever and eventually trip the read rate limit). - `GET /:type/:id` returned 200 instead of 404 Not Found for non-existent aggregates after the inner-map atom-key cleanup in 0.7.6. A controller pattern-match on atom `metadata: %{length: 0}` silently stopped matching when metadata switched to string keys, and a catch-all clause absorbed the response. Now matches the actual shape. Singleton `global` reads still return 200 with empty data.
0.7.7
May 3, 2026Changed
- `GET /:type/:id` now returns 404 Not Found when the aggregate has never had an event written (previously returned 200 with empty `data` and `metadata.length: 0`). RFC 9110 §15.5.5 — no current representation exists. The `/length` and `/events` endpoints continue to return 200 with `length: 0` / `[]` respectively, since their literal contracts are "tell me the length / events, even if zero / empty."
- Non-GET requests on `/:type/:id/length` and `/:type/:id/events` now return 405 Method Not Allowed with an `Allow: GET` response header (previously caught by the event-write catch-all route and rejected as "Unknown event type" with 404). RFC 9110 §15.5.6. The URL paths `/:type/:id/length` and `/:type/:id/events` are now reserved for read access — event types named `length` or `events` cannot be POSTed via the per-event URL.
0.7.6
May 3, 2026Changed
- API status codes across both worker and headnode now follow a single rule: 400 Bad Request for unparseable requests, 422 Unprocessable Entity for "parsed fine but contents rejected", 403 Forbidden for policy violations. Aligns with RFC 9110 and the GitHub / Rails / Phoenix-Ecto idiom.
- 422 now covers: event-write schema/actor validation, missing/empty/wrong-type `events` array on batch writes / cold-start / test-data injection, "No spec deployed" instance-state rejections, SQL queries that violate the SELECT-only / no-semicolons policy, idempotency-key format violations, blob uploads with missing fields or invalid base64, Merkle-proof verify field validation, and the worker's centralized validation mappings (invalid UUID, scope, credit type, amount, artifact type, WASM magic, backup ID).
- 422 also now covers headnode missing-required-field cases: login, backup-settings update, instance-access grant/transfer, org member add, several internal-API endpoints, and webhook body.
- 403 now covers: `skip_occ` rejection in production, environment-reset attempts on production, `environment_mismatch` between API key scope and request.
- 400 still covers genuinely unparseable cases: malformed JSON, JSONL line-parse errors, empty request body, third-party connection failures. State-conflict cases (saga not retriable, event not dead-lettered) remain 400 pending a separate decision on whether to use 409 Conflict.
- Clients matching `status >= 400` are unaffected. Clients matching literal status codes for these cases need to update.
0.7.5
April 30, 2026Fixed
- `$.id` resolved to `null` inside an implication's `emit.data` — the synthetic id (key after the colon) wasn't injected into the event JSON used for template resolution, even though the canonical path contract promises every base prefix works in implication context. Symptom: implied events failed schema with `expected string, got null`, sometimes rolling the parent back and sometimes failing silently. `$.id` now resolves correctly in both tick and map implications.
0.7.4
April 30, 2026Fixed
- Spec docs showed a `{"$": "$.path"}` "spread" form for `$merge` that was never implemented — silently produced `{"$": }` instead of merging fields. Examples now use the working forms (bare-string `"$.data"` for spread; literal-object `{ key: "$.path" }` for explicit fields), with a "common pitfall" callout on the reference page.
Added
- Spec validator rejects the broken `{"$": "..."}` shape inside any `$merge` array at spec-load time. Error message includes the JSON path of the offending field (e.g. `handler[0].append.value.$merge[0]`) so the bad item is found immediately, even when nested inside conditionals or maps.
0.7.3
April 29, 2026Fixed
- Billing usage display on instances showing zeros instead of actual values
- Invoice display on instance billing tab not rendering data
- Several dashboard displays using wrong key access pattern after 0.7.2 data key unification
Improved
- Test suite now verifies consumer-facing data contracts, not just internal implementation shapes
0.7.2
April 27, 2026Changed
- Internal data key handling unified to prevent silent failures in projection management and billing metrics
- Projection deploy and delete operations now reliably modify the correct entries
0.7.1
April 25, 2026Changed
- Dashboard instance page reorganized — environment-specific tabs (Data, Spec, Keys, Operations, Listeners) are separate from instance-wide tabs (Overview, Settings, Backups, Billing)
- API Keys tab now shows only keys for the selected environment
- Environment reset now clears materialized projection data while preserving projection definitions
Removed
- Instance health check endpoint (unreliable, no actionable information)
0.7.0
April 21, 2026Changed
- All aggregate types are now cached by default — no configuration needed
- Use
modules.never_cacheto exclude specific types from caching
Added
-
?nocache=truequery parameter — alias for?synchronous=trueon read endpoints
Deprecated
-
modules.cache(explicit opt-in list) — will be removed before 1.0
0.6.1
April 20, 2026Internal changes only.
0.6.0
April 20, 2026Changed
- Rate limits are now per-instance, tiered by billing plan — creating more API keys no longer increases throughput
- Write limits: Free 10/s, Hobby 100/s, Pro 200/s, Scale 1000/s. Read limits are 5x higher.
- 429 responses include tier context so you know exactly what limit you hit and what to upgrade to
- Successful responses include code.text-ruby.text-sm<> X-RateLimit-Remaining | header for client-side throttling
Added
- Read routes now have per-instance rate limiting (previously unlimited)
- Event naming convention guide in docs
0.5.7
April 15, 2026Fixed
- Improved resilience of projection materialization under adverse conditions
0.5.6
April 14, 2026Changed
- Spec and projection deployment is now upload-only — inline text editors removed from dashboard
- Uploads expect the same JSON format as the API
- Spec diff buttons now toggle on/off
Fixed
- Projection deploy docs corrected to show actual headnode endpoint and wrapper format
- Admin API reference now includes all projection endpoints (deploy, list, delete)
0.5.5
April 14, 2026Added
-
$.idtemplate path now available everywhere — extracts aggregate ID from key -
$.metadata.*now available in saga templates (actor, target, timestamp) - Upload JSON file for spec deployment on the dashboard
- Scope descriptions on the API key creation form
Improved
- API key environment now defaults to the currently selected environment
- Template paths unified across handlers, implications, and sagas — single reference in JSONPath docs
- Removed non-functional Admin scope from API key form (admin actions use JWT, not API keys)
0.5.4
April 13, 2026Fixed
- Saga race condition: response events arriving before a step transitions to "awaiting" are no longer lost
- Projection cross-source resolution no longer breaks when source names sort alphabetically before their dependencies
- Projections now resolve data for all aggregate types, not just those with caching enabled
- Projection definitions are now visible on workers immediately after deploy (was up to 5 minutes)
- Projection JSON-by-ID endpoint now computes on demand instead of returning null on cache miss
- ProjectionUpdater no longer crashes on empty Redis pipeline for new projections
- JSON parse errors from the engine are now human-readable
Improved
- Saga timeout checks guard against overwriting concurrent inline advances
- Projection materialization failures are now visible in logs
0.5.3
April 13, 2026Fixed
- Saga
@.*and$.state.*templates now work — trigger aggregate state was captured but never persisted - Optional template suffix
?is now supported in saga emit data (e.g.,$.data.field?omits the key when absent instead of emitting null) - Saga await resolution is now near-instant instead of taking 2+ seconds per step
- Schema validation errors now show expected vs. actual types
- Saga step errors are now structured with type, detail, and path fields
Improved
- Saga troubleshooting guide added to documentation
0.5.2
April 9, 2026Fixed
- Dashboard data browser no longer errors when selecting back to the default option
- Quick Start curl hint now shows the correct environment-specific URL
0.5.1
April 4, 2026Fixed
-
@.paths now work correctly in implication emit targets and data templates - Implication conditions now have access to source aggregate state
0.5.0
April 4, 2026Added
-
@.prefix for reading current aggregate state in tick handlers, implications, and sagas - Tick handler conditionals can now guard on aggregate state
Deprecated
-
$.state.*in implications and sagas — use@.*instead (removal at or before 1.0)
0.4.0
April 4, 2026Changed
-
GET /:typenow returns IDs by default; use?resolveto hydrate to full aggregate objects - Resolved aggregates use batch cache reads with async fallback for cache misses
- Projection auto-detection removed from listing endpoint — use dedicated projection endpoints
Added
- Saga
$.state.*templates: reference the trigger aggregate's state at saga creation time - Predicate shorthand for array matching — single-key objects desugar to equality checks
-
?synchronous=truesupport onGET /:type?resolveto skip cache
Fixed
- Saga state snapshots used wrong key format (always returned empty state)
- Docker container conflicts during test instance provisioning
- Internal rate limiting no longer applies to headnode-to-worker requests
0.3.1
April 3, 2026Fixed
- Scheduled event cancellation now works correctly for specs that define schedule implications inline on events
- Removed unused internal code path in spec caching
0.3.0
March 28, 2026Added
- Structured audit log with admin viewer
- Webhook notifications with HMAC signing (Stripe-style) and replay protection
- Health counters and engine performance metrics on Prometheus endpoint
Improved
- 66x throughput improvement for authenticated requests under load
- Metrics endpoint now requires internal authentication
Fixed
- Saga depth tracking, stuck state detection, compensation, and race conditions
- Saga emit deduplication
- Billing settlement and concurrency bugs
- GDPR tombstones no longer interfere with optimistic concurrency control
- Numeric bounds checking in the engine for integer and float values
- Instance names can now be reused after deletion
- Dashboard write operations properly gated by user role
- Consistent error responses across all API endpoints
Security
- SQLite ATTACH/DETACH blocked at the database driver level (defense in depth for customer SQL queries)
0.2.0
March 22, 2026Added
- Organization-owned instances with billing, access control, and transfer
- Push-based spec cache invalidation (specs update on workers immediately after deploy)
- Aggregate listing endpoint:
GET /:aggregate_typereturns full data with cursor pagination - Projection CSV export:
GET /_projections/:name/export.csv - Customer SQL queries against projections:
POST /_projections/query
Changed
- All dashboard UI consolidated to the control plane; worker nodes are now pure data APIs
- Sagas validated at deploy time (OCC settings, template paths)
- Checkpoints renamed to anchors
Fixed
- API key scope enforcement
- Health polling reliability
- Usage accounting and display
0.1.0
March 11, 2026Initial alpha release.
Core Engine
- Event validation, optimistic concurrency control, and aggregate computation
- JSON Schema Draft 2020-12 validation (99.9% official test suite)
- Declarative tick handlers: set, merge, append, remove, increment, decrement
- Conditional operations, array operations, variable bindings
- Implications: reactive cross-aggregate event generation with cycle detection
Platform
- Multi-tenant event sourcing with per-instance isolation
- JWT auth for operators, API key auth for data operations
- Instance environments (prod / staging / test) with scoped API keys
- Scheduled events with cancel conditions and dead-letter handling
- Sagas with compensation and rollback
- JSON projections with background refresh
- WASM escape hatch for custom handlers
- Idempotency keys with 24h TTL
- Cryptographic audit trail: hash chains, Merkle proofs, signed anchors
- GDPR tombstoning with transitive cascade
Control Plane
- Identity, billing, node registry, and health monitoring
- Tiered plans with daily accrual / monthly settlement billing