Skip to content

v1.3.1

Choose a tag to compare

@iloveautomation iloveautomation released this 13 Jul 18:12
757fece

Added

  • Opt-in soft delete for a store. A store may now declare softDelete: true.
    When it does, a delete stamps the injected deleted_at tombstone (through the
    tenant-scoped update chokepoint) instead of physically removing the row, and every
    read/write hides tombstoned rows — so a soft-deleted row is uniformly invisible:
    get404, list omits it, a second delete404, update/PATCH
    404. Tombstone-hiding is enforced on the richer read/write surface too
    (declarative views, workflow store_read/store_write, and tool/route/trigger
    handlers), not just the CRUD routes. Without the field the default is unchanged —
    a delete is a hard physical delete with no deleted_at filtering. Documented
    caveat: because a tombstoned row physically persists (holding its column values),
    a unique value from a soft-deleted row still occupies the tenant-scoped unique
    index, so re-creating that same value returns 409 CONFLICT rather than reusing it.
  • Server-enforced enum whitelists on a text column. A text column may declare
    an enum list of allowed values, and the platform now enforces it server-side: an
    out-of-whitelist value on a create/update store route is a 400 VALIDATION_ERROR
    (a z.enum derived at the write chokepoint), and the same whitelist is enforced on
    the workflow store.write value path. enum is valid only on a text column and
    its members must be distinct (rejected at validation otherwise). Honest residual: a
    custom escape-hatch handler that writes directly through the HandlerDb facade is
    not enum-checked — a handler author owns its own value discipline.
  • Foreign keys to a unique parent column (referencesColumn). A store
    foreign key may set referencesColumn to target a unique: true column of the
    parent store instead of its injected id. It materializes as a tenant-scoped
    compound
    foreign key — (tenant_id, <col>) REFERENCES parent(tenant_id, <refcol>)
    — which structurally forbids a cross-tenant reference. A create/update naming a
    non-existent parent value returns 400; a restrict-blocked parent delete returns
    409 (both tenant-safe — the 400 names only the local column, the 409 names no
    relationship at all, never a foreign value). The
    local column's type must match the referenced column's, the referenced column must
    be unique: true, and onDelete: 'set null' is rejected (a compound FK cannot null
    tenant_id). The id-target FK path is unchanged.
  • A set (IN) filter on the declarative list op. A list route now accepts a
    per-column set filter ?<col>__in=v1,v2,… that maps to SQL IN, so a "status is
    open OR in_progress" read is expressible in one query. The distinct __in suffix
    keeps plain ?<col>=v equality byte-identical and unambiguous on a comma-bearing
    value (a real column literally named <x>__in still routes as plain equality). It
    folds into the same AND-chain as equality filters, keyset pagination, and the tenant
    predicate. Fail-closed: an empty/blank element, an oversized set (> 100 values), a
    non-filterable (jsonb) column, or an unknown prefix column each return 400.
  • rayspec deploy --apply-migration <delta.sql>. deploy can now apply a reviewed
    forward migration in place, reaching the existing gated migration engine — an
    operator with a brownfield schema change no longer has to drop to the dev harness.
    --allowlist <file.json> supplies the reviewed cover for a destructive statement (a
    destructive statement without a covering entry is still blocked by the deploy gate);
    both paths are jailed through the same path check as the spec. It is reboot-safe:
    the boot classifies the live schema first and mounts a present-matching schema
    instead of re-applying a non-idempotent delta, so leaving the flag in a
    process-managed unit applies once and mounts thereafter. --dry-run rejects the flag
    (it touches no database), and a bare --allowlist (without --apply-migration) is
    refused. Reachable from both profiles.

Fixed

  • An agent-free spec boots and updates with no provider key. The local dev-boot
    wrapper hard-required OPENAI_API_KEY up front (an unconditional check before the
    spec was parsed, plus an always-on OpenAI factory), so applying an additive delta to
    an agent-free spec failed closed on a credential it never uses. The wrapper now
    routes through the shipped assembleOptsFromEnv, which returns an agent-backends
    factory only when the spec declares at least one agent — so a stores/api-only backend
    (or a product-profile document) boots and updates with no provider key, while an
    agent-bearing spec still fail-closes naming the missing per-agent credential.

Documentation

  • Corrected the "deploy applies the migration" overstatement to the mount-only
    truth.
    rayspec deploy/rayspec-serve materializes a store on a clean database
    and mounts a present-matching one, but against an existing deployment it is
    mount-only: it fail-closes on a drifted schema rather than altering it on its
    own. A schema change is applied by the explicit rayspec deploy --apply-migration <delta.sql> (with --allowlist for a reviewed destructive statement). The
    diff/gate and from-clean-database guarantees are unchanged. Corrected across
    getting-started, the CLI reference, concepts, ARCHITECTURE, and the README.
  • New "Restore and key rotation" operational note (ARCHITECTURE → security model):
    a restored database dump survives whole at the row level — orgs, users, the argon2id
    password hashes, and all tenant data come back reachable. The only thing a
    freshly-minted RAYSPEC_API_KEY_PEPPER breaks is the set of copied API keys:
    their stored HMACs no longer match, so they return 401 — mint new ones. User
    passwords are argon2id (pepper-independent), so an org owner just logs in again and a
    fresh JWT under the current signing key reaches the data. (The JWT signing key is the
    same class and self-heals on that same re-login; an org whose sole credential was an
    API key needs a fresh key established out of band.)
  • Documented the new store features in the spec reference (enum, softDelete,
    referencesColumn, and the <col>__in set filter), and pinned Range and HEAD on
    a static frontend mount
    as a supported feature with tests (byte-range 206 /
    HEAD 200). Honest edge: an unsatisfiable range currently returns 500 — the
    underlying static server (@hono/node-server serveStatic) has no RFC-7233 416
    path.