Skip to content

test: guard the DB schema composer against unwired DDL modules (#5682) - #5810

Merged
atomantic merged 3 commits into
mainfrom
claim/issue-5682
Sep 2, 2026
Merged

test: guard the DB schema composer against unwired DDL modules (#5682)#5810
atomantic merged 3 commits into
mainfrom
claim/issue-5682

Conversation

@atomantic

Copy link
Copy Markdown
Owner

Summary

server/lib/db/schema/ splits the boot DDL into per-domain modules that index.js composes by hand into buildUpgradeDdl() / buildCatalogDdl(), the two lists ensureSchemaImpl() runs on every boot. Nothing verified that a module in that directory actually reaches a composed list. Adding server/lib/db/schema/foo.js and wiring only the import + export {} block — the obvious half of the change — shipped a CREATE TABLE that never executes, so a fresh install (and every peer install upgrading) is missing the table and the feature fails at first query with a Postgres relation does not exist. The existing schema.test.js checks a hand-written module list, and the directory-level guard in server/lib/index.test.js uses a non-recursive readdirSync, so nothing under server/lib/db/ was covered.

Adds a directory-driven guard modeled on server/lib/editorial/checkInfraBarrel.test.js.

Changes

  • server/lib/db/schema/index.test.js (new) — derives its expectations from readdirSync rather than a maintained list. For every non-test module it asserts: index.js imports it; it contributes DDL; every statement of that contribution appears in the composed lists; and it has a backtick-wrapped README.md row.
    • Two contribution shapes are recognized: a plain <domain>Ddl statement array, and a zero-arg build*Ddl / build*Triggers() generator (the shape audit.js uses). Both are held to the same membership check, so generated statements can't be dropped either.
    • Membership is checked statement-by-statement rather than by array identity, so catalog.js (two separately-positioned arrays) and audit.js (auditDdl plus generated triggers) pass without encoding any ordering. Statement order stays out of scopeschema.test.js and db.catalogDdlParity.test.js own it, and no DDL was touched.
  • server/lib/README.md — new db/ row so the documented discovery grep can find the schema modules at all; refreshed the guard-contract line now that .jsx counts.
  • server/lib/index.test.js, client/src/lib/index.test.js — filters now accept .jsx (matching the hooks/services guards), so a future .jsx in either directory can't bypass the barrel/README parity check.

Test plan

Verified the new guard is actually red for each failure mode by temporarily adding a throwaway server/lib/db/schema/zzz.js and deleting it afterwards:

  1. Module present but not imported and not composed → fails on both the import and the statement-membership assertions.
  2. Module imported and re-exported but only its first statement spread into a composer → fails, naming the uncomposed statement.
  3. Module exporting only a generated build*Triggers() that is never composed → fails, naming the uncomposed generated statement.

Suites:

  • cd server && npm test → 1835 files passed, 37302 tests passed, 1 skipped.
  • cd client && npm test → 855 files, 10719 tests passed.
  • No DB-backed suite was run (no npm run test:db); the real portos database was never touched.

Closes #5682

https://claude.ai/code/session_01GMxEz43s3YCLaVZV9KmVwE

Adding a module under server/lib/db/schema/ and wiring only its import and
re-export shipped a CREATE TABLE that never ran: ensureSchemaImpl() executes
only what buildUpgradeDdl()/buildCatalogDdl() enumerate by hand, so a fresh
install (and every peer install upgrading) silently missed the table and the
feature failed at first query with a Postgres "relation does not exist".
Nothing caught it — schema.test.js checks a hand-written module list, and the
server/lib barrel guard's readdirSync is non-recursive.

- New server/lib/db/schema/index.test.js derives its expectations from the
  directory listing: every non-test module must be imported by index.js, must
  export at least one *Ddl array, each such array's first statement must appear
  in the composed lists, and each module must have a README row. Membership is
  checked on the first statement rather than array identity so catalog.js (two
  separately-positioned arrays) and audit.js (auditDdl plus generated triggers)
  both pass without encoding any ordering — statement order stays the job of
  schema.test.js and db.catalogDdlParity.test.js.
- server/lib/README.md gains a db/ row so the documented discovery grep can
  find the schema modules at all.
- server/lib/index.test.js and client/src/lib/index.test.js now accept .jsx
  alongside .js (matching the hooks/services guards), so a future .jsx cannot
  bypass the barrel/README parity check.

Claude-Session: https://claude.ai/code/session_01GMxEz43s3YCLaVZV9KmVwE
Local review round 1: matching only an array's first statement let a composer
spread part of a module's DDL and still pass, so the remaining CREATE TABLE /
CREATE INDEX statements would silently never run. Check every statement (still
membership-only, so no ordering is encoded) and report the first uncomposed one
in the failure message. Also refresh the stale "non-test .js file" wording in
server/lib/README.md now that the guard covers .jsx too.

Claude-Session: https://claude.ai/code/session_01GMxEz43s3YCLaVZV9KmVwE
Local review round 2: the guard only inspected `*Ddl` arrays, so a module that
emits DDL from a generator — the shape audit.js already uses with
buildAuditTriggers() — could be dropped from the composer and still pass,
leaving its statements unrun. Also accept a zero-arg build*Ddl/build*Triggers()
export, call it, and hold its statements to the same membership check.

Claude-Session: https://claude.ai/code/session_01GMxEz43s3YCLaVZV9KmVwE
@atomantic
atomantic merged commit ce39a30 into main Sep 2, 2026
7 checks passed
@atomantic
atomantic deleted the claim/issue-5682 branch September 2, 2026 05:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A new per-domain DDL module can be added without ever running, because nothing checks the schema composer

1 participant