Skip to content

LumiBase v0.22.0

Latest

Choose a tag to compare

@github-actions github-actions released this 12 Jul 03:26

Version

  • v0.22.0

Date

  • 2026-07-12

Highlights

  • CDC Change Feed (Phases A–H). Outbox capture, pull API, dispatcher,
    extension integration, retention, and Studio surface — plus skills/MCP
    coverage. The change-data-capture pipeline lands end to end (#244).
  • Realtime studio co-editing, hardened. A read-gated, filterable realtime
    plane: subscribe is permission-scoped and fail-closed, broadcasts are
    signal-only (no row data on the wire), and the item editor shows a live
    co-editing warning (#249).
  • Security hardening pass. Settings writes admin-gated with secret reads
    redacted, external-JWT DoS guards + denial/issuer auditing, scheduled-release
    publishes now audited, and an out-of-scope findings backlog wired into the
    Definition of Done (#251).
  • v1 readiness groundwork. Golden-path E2E gate + dependency-audit in CI,
    "Upgrading to 1.0" runbook, SECURITY.md, and the versioning policy — staging
    the remaining work before the v1.0.0 tag (#243, #239).

Security

  • Realtime subscribe is read-gated and filterable. A studio session could
    previously subscribe to any collection name and receive change signals for
    collections it had no read grant on (metadata leak: which collections
    change, when, and which item ids). The studio realtime ticket now embeds the
    collections the principal can read (computed by PermissionService at ticket
    issuance — admin bypass gets *), and the hub rejects any other subscribe
    with SUBSCRIBE_FORBIDDEN, fail-closed on a missing/empty allowlist. The
    subscribe message also accepts an optional Directus-style filter,
    evaluated server-side per subscription over the event envelope
    (collection/action/itemId — the wire is signal-only, so row data is
    never filterable or leakable).

  • Scheduled release publishes are now audited. The scheduler sweep
    published due releases without writing any audit row — only manual publishes
    were recorded. The sweep now writes the same release_published /
    release_partially_published / release_publish_failed vocabulary
    (shared helpers, counts-only metadata) with trigger: 'scheduled'.

  • Realtime studio broadcasts are signal-only. An item mutation used to
    fan out the full row.data to every studio session subscribed to the
    collection, without re-checking that session's read grant or field mask —
    a client could read row content (including masked fields) straight off the
    WebSocket. The broadcast now carries only the change signal
    (collection/action/itemId, payload: null); the Studio client
    re-fetches through the permission-enforced /items API, so field masking and
    row RBAC apply by construction and no row content crosses the wire.

  • External JWT auth: DoS guards + denial/issuer auditing. The verifier now
    rejects an oversized bearer (> 8192 chars) before parsing it and caps the
    role-claim list it resolves (≤ 50), bounding attacker-controlled parse/query
    work. Denied external authentications now write an external_auth_denied
    audit row (classification code only — never the token, claims, or reason), and
    issuer create/update/delete write external_issuer_* audit rows.

  • FK dependent-records now enforce the caller's RBAC. The
    POST /api/v1/items/:collection/:id/resolve-dependents and
    GET …/dependents endpoints previously ran the batch set_null / reassign
    / delete and the preflight report without the caller's permission context —
    any authenticated tenant member could clear, reassign, or delete records in a
    collection they had no update/delete grant on, and read dependent ids they
    could not otherwise see. The resolve path now gates each action against
    update/delete on the dependent collection (403 FORBIDDEN), scopes batch
    writes to the caller's row-level grant, delegates deletes through a
    permission-carrying ItemService, and the preflight requires read on the
    target and only samples rows the caller may read. A source-independent
    tripwire (dependents-service-rbac.test.ts) locks the gate, and Definition of
    Done §2c gains a rule for request-path services that delegate to ItemService
    or write content tables directly. No schema or setup change.

  • /api/v1/settings writes are now admin-only. POST /api/v1/settings and
    DELETE /api/v1/settings/:key were open to any authenticated site member,
    letting a non-admin overwrite arbitrary settings keys (including
    upload_policy and media.signedTransform) and bypass the admin gates on
    dedicated config endpoints. Both now require requireSiteAdmin; reads stay
    open because non-admin editors legitimately read keys like locales.

  • Settings reads redact secret-bearing fields. GET /api/v1/settings and
    /:key previously returned raw values including secrets such as
    media.signedTransform.secret. Secret-named fields (secret/token/password/
    apiKey/…) are now redacted ([redacted]) on read for every caller; code that
    needs the real value reads it directly from the DB, not this HTTP endpoint.

Performance

  • Trusted external-JWT issuers are cached. getTrustedIssuers queried the
    DB on every bearer-token request; it now reads through runtime.cache
    (auth:issuers:<siteId>, TTL 60s) and issuer create/update/delete drop the
    key, so config changes apply within the TTL bound.

Added

  • Change Feed (CDC Extension Integration). First-party transactional
    outbox + relay over content mutations: lumibase_cdc_change_events /
    _subscriptions / _deliveries (migration 0007_cdc_change_feed, RLS
    site-isolated), cursor-paginated GET /api/v1/cdc/events, HMAC-signed
    webhook dispatcher with retry/dead handling, sandboxed extension
    subscribers (defineCdcSubscriber, manifest capability
    cdc:subscribe:<collection>), retention + replay, Studio → Settings →
    Change Feed panel, five governed AI skills and MCP tools.
    Upgrade note: two new capability strings exist — cdc:subscribe
    (read the feed / ack) and cdc:manage (AI-skill subscription
    management). Admin roles satisfy them implicitly (adminAccess
    wildcard); grant them explicitly only for narrow integration tokens.
    deleteCdcSubscription is control-plane → HITL below autopilot.
    Feed is off-by-default per site (cdc_feed.enabled or first active
    subscription turns it on). No backfill: three new empty tables.
  • Registry-numbering tripwire (pnpm registry:check). A CI check
    (scripts/check-registry-numbering.mjs, wired into the CI checks job) fails
    the build when the Setup Impact Registry # column contains a duplicate —
    mechanizing the Definition of Done §2 uniqueness rule per §6 ("cơ giới hóa"),
    replacing the manual grep.
  • Out-of-scope findings backlog + Definition of Done §7. A single place
    (.kiro/steering/out-of-scope-backlog.md) to log vulnerabilities, bugs, and
    follow-up tasks discovered while working a PR but outside its scope, so they
    are not lost after merge. DoD §7 makes logging them a required review step.

Fixed

  • build-release-manifest.mjs is now idempotent. Regenerating
    apps/docs/public/releases.json on a plain docs:build no longer dirties the
    working tree: editorial fields (migrationWarning, minimumSafeUpgradeVersion)
    and releaseDate are preserved from the committed manifest unless explicitly
    overridden (env var, or a matching CHANGELOG heading for the date). This also
    fixes a latent bug where a deploy build could clobber a hand-set editorial
    value back to its default.

Changed

  • Setup Impact Registry # column deduplicated. Parallel branches had kept
    picking "the next number" independently, leaving many collisions (#16/#20/#21
    through #38). Colliding rows were renumbered to fresh ids (45–68), keeping the
    occurrence that other rows cite by number so cross-references stay valid.

Migrations

  • 0007_cdc_change_feed — Change Feed outbox/capture tables (additive,
    idempotent). No destructive changes.