feat: move Signage to the Ekklesia 0.2 module contract (manifest v8, database ABI v1) #10

Merged
Zayin_Admin merged 1 commits from feat/platform-0.2-compat into main 2026-10-09 01:41:36 +00:00
Member

Moves Signage from the legacy module contract to Ekklesia 0.2.0's: manifest v8, Module Database ABI v1, the @ekklesia/module-database Drizzle adapter, plain schema metadata, and the curated module SDK. This is the first of the slices. Signage still builds inside the pinned platform workspace (ci/assemble.sh). No behaviour, schema or data change.

What changes

  • Manifest v8. manifestVersion: 8 and database: { abi: 1 }. Compatibility is now ^0.2.0 for both platform and module SDK, which is what CI tests against. Identity, permissions, configuration, storage, entrypoints and the display declaration are unchanged. The runtime declares the same ABI, because Core refuses a mismatch.
  • Database ABI v1.
    • createSignageStore(executor) now takes the platform's ABI v1 executor and binds moduleDrizzle to it once, so none of the 12 call sites change.
    • The four drizzle*.ts stores are now typed ModuleDrizzleDatabase / ModuleDrizzleTransaction instead of the removed legacy ModuleDatabase.
    • Query code is unchanged.
  • Schema metadata. src/schema/index.ts now exports only moduleDrizzleSchemaMetadata("signage", …): plain table names and foreign-key targets. The Drizzle table re-exports and defineModuleSchema are gone, so no Drizzle object reaches Core. The declarations in tables.ts are untouched (only its header comment changed).
  • Duplicate-image bug fix. isUniqueViolation used to check error.code === "23505", but Drizzle wraps every executor failure in DrizzleQueryError, with the real error on .cause. So the check never matched, and a duplicate-upload race returned a 500 instead of the content-hash conflict. It now reads findEkklesiaDatabaseError(error)?.sqlState === "23505".
  • Migrations. No new migration. 0000_initial.sql and its pinned checksum (fbbc448f…) are unchanged.
  • Version. Stays 0.1.2. By the repository's convention, versions move only in a chore: release signage X PR.

Temporary CI changes (deleted in the next PR)

  • ci/platform.env now pins v0.2.0 (1788d17), and ci/pnpm-lock.yaml is regenerated with ci/update-lock.sh.
  • The integration job is disabled (if: false). From 0.2.0 on, the platform image is generic (ADR 0048): the Dockerfile refuses a build context containing modules/, and the smoke installs modules only as Module Packages. Signage can't be packaged until its sharp dependency goes through the native linux-x64-glibc flow, which is out of scope here. Run against 0.2.0, the job would build an image without Signage and drive the template module's display instead. The job and ci/integration.sh stay as they are. The job comes back in the change that installs Signage as a Module Package.

Tests

  • New tests/drizzle-store.test.ts, driven by an ABI v1 executor double (tests/support/abi-executor.ts). The double enforces Core's rules: ABI-only parameters (a Date is refused), callback scopes, forgotten-await detection, revocation, and aborted transactions. The new tests cover:
    • all 13 store transactions: each is one BEGIN … COMMIT, with every statement inside the transaction and no savepoints (Signage has no nested transactions);
    • rollback when a removal or reorder is refused, and when a later statement fails;
    • encoding: instants go out as ISO text and JSON as text;
    • decoding of the raw forms Core sends: int8 text becomes a number, timestamptz text a Date (including a -05 offset), jsonb text a value (a JSON string stays a string);
    • the 23505 regression: the error arrives wrapped by Drizzle, is recognized as a conflict and rolled back, while 23503, a failure with no SQLSTATE, a refused parameter and a raw pg-shaped { code: "23505" } are not treated as duplicates.
  • I checked that the tests can fail: they catch both the old 23505 check and a forgotten await when each is reintroduced.
  • manifest.test.ts, schema.test.ts and acceptance.test.ts now cover manifest v8, database: { abi: 1 } in both manifest and runtime, and plain JSON-safe metadata. The schema entrypoint's only export is default, and the metadata pins every table and foreign-key target.

Verification

  • CI job commands, from a fresh ci/assemble.sh against v0.2.0: frozen install, lint, check-cleanup, typecheck, test (22 files, 261 tests), pnpm boundaries, pnpm modules:validate (1 module, 11 tables), pnpm migrations:validate and build all pass.
  • Live run, not committed (a module can't import Core): Signage's built store ran through Core 0.2.0's real createModuleDatabaseExecutor against disposable PostgreSQL 18.4. The server was set to America/New_York and the host to TZ=America/Chicago. All 22 checks passed:
    • every transaction flow and its rollback;
    • a real duplicate content hash, which arrives as 23505 through Drizzle and becomes SignageStoreConflict with nothing persisted;
    • 23514 and 23503 not treated as duplicates;
    • int8 above 2³¹, exact instants, and the jsonb round trip.
    • Afterwards the persisted values were still jsonb objects, bigint, and exact timestamptz.
  • pnpm test:migrations:integration doesn't apply here: it uses the platform's own fixture modules, not workspace modules.

Not in this PR

  • Standalone dependencies: registry .npmrc, replacing workspace:*, a standalone lockfile, and deleting ci/assemble.sh, ci/platform.env and ci/pnpm-lock.yaml.
  • Native linux-x64-glibc packaging of sharp, and Module Package builds.

🤖 Generated with Claude Code

Moves Signage from the legacy module contract to Ekklesia 0.2.0's: manifest v8, Module Database ABI v1, the `@ekklesia/module-database` Drizzle adapter, plain schema metadata, and the curated module SDK. This is the first of the slices. Signage still builds inside the pinned platform workspace (`ci/assemble.sh`). No behaviour, schema or data change. ## What changes - **Manifest v8.** `manifestVersion: 8` and `database: { abi: 1 }`. Compatibility is now `^0.2.0` for both platform and module SDK, which is what CI tests against. Identity, permissions, configuration, storage, entrypoints and the display declaration are unchanged. The runtime declares the same ABI, because Core refuses a mismatch. - **Database ABI v1.** - `createSignageStore(executor)` now takes the platform's ABI v1 executor and binds `moduleDrizzle` to it once, so none of the 12 call sites change. - The four `drizzle*.ts` stores are now typed `ModuleDrizzleDatabase` / `ModuleDrizzleTransaction` instead of the removed legacy `ModuleDatabase`. - Query code is unchanged. - **Schema metadata.** `src/schema/index.ts` now exports only `moduleDrizzleSchemaMetadata("signage", …)`: plain table names and foreign-key targets. The Drizzle table re-exports and `defineModuleSchema` are gone, so no Drizzle object reaches Core. The declarations in `tables.ts` are untouched (only its header comment changed). - **Duplicate-image bug fix.** `isUniqueViolation` used to check `error.code === "23505"`, but Drizzle wraps every executor failure in `DrizzleQueryError`, with the real error on `.cause`. So the check never matched, and a duplicate-upload race returned a 500 instead of the content-hash conflict. It now reads `findEkklesiaDatabaseError(error)?.sqlState === "23505"`. - **Migrations.** No new migration. `0000_initial.sql` and its pinned checksum (`fbbc448f…`) are unchanged. - **Version.** Stays 0.1.2. By the repository's convention, versions move only in a `chore: release signage X` PR. ## Temporary CI changes (deleted in the next PR) - `ci/platform.env` now pins v0.2.0 (`1788d17`), and `ci/pnpm-lock.yaml` is regenerated with `ci/update-lock.sh`. - **The `integration` job is disabled (`if: false`).** From 0.2.0 on, the platform image is generic (ADR 0048): the Dockerfile refuses a build context containing `modules/`, and the smoke installs modules only as Module Packages. Signage can't be packaged until its sharp dependency goes through the native linux-x64-glibc flow, which is out of scope here. Run against 0.2.0, the job would build an image without Signage and drive the template module's display instead. The job and `ci/integration.sh` stay as they are. The job comes back in the change that installs Signage as a Module Package. ## Tests - New `tests/drizzle-store.test.ts`, driven by an ABI v1 executor double (`tests/support/abi-executor.ts`). The double enforces Core's rules: ABI-only parameters (a `Date` is refused), callback scopes, forgotten-`await` detection, revocation, and aborted transactions. The new tests cover: - all **13** store transactions: each is one `BEGIN … COMMIT`, with every statement inside the transaction and no savepoints (Signage has no nested transactions); - rollback when a removal or reorder is refused, and when a later statement fails; - encoding: instants go out as ISO text and JSON as text; - decoding of the raw forms Core sends: `int8` text becomes a number, `timestamptz` text a `Date` (including a `-05` offset), `jsonb` text a value (a JSON string stays a string); - the 23505 regression: the error arrives wrapped by Drizzle, is recognized as a conflict and rolled back, while 23503, a failure with no SQLSTATE, a refused parameter and a raw pg-shaped `{ code: "23505" }` are **not** treated as duplicates. - I checked that the tests can fail: they catch both the old 23505 check and a forgotten `await` when each is reintroduced. - `manifest.test.ts`, `schema.test.ts` and `acceptance.test.ts` now cover manifest v8, `database: { abi: 1 }` in both manifest and runtime, and plain JSON-safe metadata. The schema entrypoint's only export is `default`, and the metadata pins every table and foreign-key target. ## Verification - **CI job commands, from a fresh `ci/assemble.sh` against v0.2.0:** frozen install, lint, check-cleanup, typecheck, test (22 files, 261 tests), `pnpm boundaries`, `pnpm modules:validate` (1 module, 11 tables), `pnpm migrations:validate` and build all pass. - **Live run, not committed (a module can't import Core):** Signage's built store ran through Core 0.2.0's real `createModuleDatabaseExecutor` against disposable PostgreSQL 18.4. The server was set to `America/New_York` and the host to `TZ=America/Chicago`. All 22 checks passed: - every transaction flow and its rollback; - a real duplicate content hash, which arrives as 23505 through Drizzle and becomes `SignageStoreConflict` with nothing persisted; - 23514 and 23503 not treated as duplicates; - `int8` above 2³¹, exact instants, and the jsonb round trip. - Afterwards the persisted values were still `jsonb` objects, `bigint`, and exact `timestamptz`. - `pnpm test:migrations:integration` doesn't apply here: it uses the platform's own fixture modules, not workspace modules. ## Not in this PR - Standalone dependencies: registry `.npmrc`, replacing `workspace:*`, a standalone lockfile, and deleting `ci/assemble.sh`, `ci/platform.env` and `ci/pnpm-lock.yaml`. - Native linux-x64-glibc packaging of sharp, and Module Package builds. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
ekklesia-bot added 1 commit 2026-10-09 01:36:45 +00:00
feat: move Signage to the Ekklesia 0.2 module contract (manifest v8, database ABI v1)
CI / lint (pull_request) Successful in 14s
CI / typecheck (pull_request) Successful in 16s
CI / test (pull_request) Successful in 21s
CI / build (pull_request) Successful in 20s
CI / integration (pull_request) Skipped
a6d553efbb
Signage moves from the legacy module contract to Ekklesia 0.2.0's, with no behaviour or schema
change:

- manifest version 8, `database: { abi: 1 }`, compatibility ^0.2.0 for platform and module SDK;
  the runtime declares the same ABI, as Core requires;
- every store reaches PostgreSQL through the platform's Module Database ABI v1 executor, with
  Signage's own Drizzle tables and the module-side adapter (`moduleDrizzle`) from
  @ekklesia/module-database. createSignageStore takes the executor, so no call site changes;
- the schema entrypoint exports only plain ABI v1 metadata (table names and foreign-key targets),
  derived from tables.ts with moduleDrizzleSchemaMetadata; no Drizzle object reaches Core;
- duplicate-image detection reads SQLSTATE 23505 from the stable Ekklesia database error, found
  with findEkklesiaDatabaseError through Drizzle's DrizzleQueryError wrapping. The old
  `error.code === "23505"` check never matched a wrapped error, so a duplicate-upload race
  surfaced as a 500 instead of the content-hash conflict.

tables.ts declarations, the migration, and its pinned checksum are unchanged.

Tests: an ABI v1 executor double that enforces Core's scope, parameter, and abort rules drives all
13 store transactions, their rollbacks, raw int8/timestamptz/jsonb decoding, and the 23505
regression, including generic and driver-shaped errors that must not count as duplicates.

CI, temporarily: the platform pin moves to v0.2.0 (1788d17) with a regenerated ci/pnpm-lock.yaml.
The integration job is disabled: from 0.2.0 the platform image is generic (ADR 0048) and installs
modules only as Module Packages, which Signage cannot be until its sharp dependency goes through
the native package flow. The job and ci/integration.sh stay, and the job returns in that change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Zayin_Admin merged commit fefe34be03 into main 2026-10-09 01:41:36 +00:00
Zayin_Admin deleted branch feat/platform-0.2-compat 2026-10-09 01:41:36 +00:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Zayin/Signage#10