You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Added
A server-stamped created_by actor column on every store row. Each row now
records the principal that created it — user:<userId> for a JWT request, key:<apiKeyId> for an API-key request. It is stamped on create only (never
re-stamped on update), returned in responses, and is not client-settable: it is a
reserved column, so a created_by/createdBy field in a request body is rejected
(400 VALIDATION_ERROR). It is filterable on a list route, so a caller can list
only the rows it created. (A row created before this column existed carries a null created_by.)
Query power on the declarative storelist op. A list route now accepts
equality filters (?<column>=<value> on any declared column plus created_by,
AND-combined — equality only, no ranges / OR / LIKE), single-column ordering
(?order=<column>.asc|desc over non-nullable columns and the injected id/created_at; default id asc), and keyset pagination (?limit= in 1–200,
default 200, plus ?after=<opaque cursor>). A full page sets X-Result-Truncated: true and returns an X-Next-Cursor. Every filter, order, and
cursor is folded through the tenant predicate, and an unknown query parameter is
rejected (400). An offset-paged read or a filtered total count still drops to a handler route.
Idempotency-Key replay on storecreate. A create request carrying an Idempotency-Key header is deduplicated per tenant and per store: a repeat with
the same key value replays the original row (200 with Idempotency-Replay: true,
no duplicate row and no 409), regardless of the request body. A request without
the header is never deduplicated. This is distinct from a unique: true column,
whose duplicate value is a 409 CONFLICT rather than a replay.
Owner-gated org membership management.POST /v1/orgs/{orgId}/members adds a
member by {email} — owner-only, via a live-membership permission check (a
non-owner, or an API-key principal, is refused). An existing user is added
idempotently as a member; a new email provisions an account and returns a oneTimePassword once in the owner's response (the core sends no mail — the owner
conveys it out of band). GET /v1/orgs/{orgId}/members lists the org's members and
is readable by any member. Accepted limitation: because the one-time password
appears only for a newly provisioned account, the response reveals whether an email
already has a platform account — accepted for the trusted single-node posture and
closed by the out-of-band invite flow in the hardening layer (see SECURITY.md).
A shipped authoring skill, rayspec-author, guiding an assistant from a
plain-language product brief to a validated spec and a deployed, curl-testable
local backend, plus a gate:skill-drift build guard (in the deterministic CI lane)
that fails if the skill drifts from the shipped grammar version, the CLI
entrypoints, or the example specs it cites.
rayspec dev db --reset --yes. An opt-in, destructive local-dev reset that
DROPs and re-CREATEs a clean, empty dev database (and drops the sibling <name>_dbos_sys durable-worker system database). It is gated on an explicit --yes; --reset without it refuses and touches nothing. The default dev db
remains create-if-absent and never destructive.
Changed
storecreate/update bodies accept snake_case or camelCase column keys. A
request may key each declared column by its snake_case declared name or its
camelCase twin (the form the generated OpenAPI documents); both are accepted.
Sending both variants of the same column in one body is rejected
(400 VALIDATION_ERROR). Responses are always snake_case.
Newly minted API keys use an rk_ prefix (previously mk_); the key shape is rk_<public-prefix>.<secret>. Existing mk_ keys stay valid indefinitely — both
prefixes are accepted.
Quieter boot. The benign NOTICE frames Postgres emits for each idempotent DDL
guard in the migration chain (… already exists, skipping) are no longer printed,
so a clean boot no longer prints a wall of messages that read like errors. A WARNING (or any higher severity) is still logged, and query error handling is
unchanged.