Changelog

Notable changes to the j17 platform.

0.7.12

May 18, 2026

Changed

  • Internal changes to UUID and humane code handling.

0.7.11

May 18, 2026

Changed

  • `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, 2026

Added

  • `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, 2026

Changed

  • `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, 2026

Fixed

  • `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, 2026

Changed

  • `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, 2026

Changed

  • 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, 2026

Fixed

  • `$.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, 2026

Fixed

  • 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, 2026

Fixed

  • 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, 2026

Changed

  • 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, 2026

Changed

  • 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, 2026

Changed

  • All aggregate types are now cached by default — no configuration needed
  • Use modules.never_cache to exclude specific types from caching

Added

  • query parameter — alias for on read endpoints

Deprecated

  • modules.cache (explicit opt-in list) — will be removed before 1.0

0.6.1

April 20, 2026

Internal changes only.

0.6.0

April 20, 2026

Changed

  • 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, 2026

Fixed

  • Improved resilience of projection materialization under adverse conditions

0.5.6

April 14, 2026

Changed

  • 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, 2026

Added

  • $.id template 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, 2026

Fixed

  • 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, 2026

Fixed

  • 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, 2026

Fixed

  • 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, 2026

Fixed

  • @. 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, 2026

Added

  • @. 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, 2026

Changed

  • GET /:type now returns IDs by default; use ?resolve to 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
  • support on GET /:type?resolve to 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, 2026

Fixed

  • 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, 2026

Added

  • 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, 2026

Added

  • 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_type returns 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, 2026

Initial 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