Part of the OpenCrane agent guidance. See
AGENTS.mdfor the index.
The OpenCrane server database schema is owned per domain, mirroring the
libs/backend/<domain>/main package layout (#153). One physical PostgreSQL database and one
clean target baseline remain, and every model/enum has exactly one owning domain.
- The schema is a multi-file folder:
apps/opencrane/prisma/schema/(Prisma β₯ 6.7 folder mode; wired via"prisma": { "schema": "prisma/schema" }in the operatorpackage.json). base.prismaholds thegeneratoranddatasourceblocks β nothing else.<domain>.prismaholds the models and enums owned bylibs/backend/<domain>/main(e.g.grants.prisma,model-routing.prisma). Cross-file relations are fine β Prisma merges the folder into one schema.
- New model/enum β the owning domain's file. If the owning domain package doesn't
exist yet, create the lib first (see
libs/backend/README.md); a model with no owning domain is a design smell. - Never edit a model from a non-owning domain. If domain B needs a field on domain A's model, that is an API conversation with A's contract, not a schema edit from B.
- Schema changes edit the target baseline; there is no upgrade path pre-1.0. Regenerate and
review
apps/opencrane/prisma/bootstrap/target-baseline.sql, then prove it against a new empty database. Prisma's generated diff does not contain the hand-written triggers, partial/NULL-safe indexes, and authority constraints in the reviewed baseline. Regeneration must preserve and revalidate those blocks explicitly. Updatedatabase.baselineSha256in the currentreleases/<version>.jsonand rebuild any live dev silo that needs the new schema. Runnpm run test:authority-baseline -w @opencrane/serveras well: it fails closed when a Prisma-only rewrite has discarded the reviewed functions, triggers, constraints, or seeds. - CNPG
initdbis the only application-schema setup boundary. The deployment publisher prependsSET ROLEfor the configured application owner and exposes the canonical SQL through one immutable, content-addressed ConfigMap. Its superuser envelope records the full baseline digest in a protected database schema. Physical recovery restores that marker with the existing schema, never attaches fresh setup SQL, and must pass the digest-checking Postgres hook. Existing databases do not advance in place pre-1.0 β a silo that needs a newer schema is rebuilt (seeversioning.md); the protected baseline digest remains immutable origin proof.
Production TypeScript reaches Prisma through reviewed capability boundaries, enforced by
npm run check:prisma-boundaries -- --diff <base-ref>:
- Domain services, materializers, and use cases do not import Prisma or call model delegates.
- Only an exact repository adapter declared in
prisma-boundary-policy.jsonmay call model delegates. A declaration binds the repository contract import, adapter class, and source path; renaming or moving any of them requires policy review.$queryRaw,$queryRawUnsafe,$executeRaw, and$executeRawUnsafeare forbidden in production TypeScript, including declared repositories. The permanent exceptions are the typedWorkflowTaskAdmissionandWorkflowTaskEventAdmissionadapters. They call the fixed, parameterizedabsurd.spawn_taskandabsurd.emit_eventtemplates, each bound by exact path, class, contract, method, SQL template, and canonical-LF source SHA-256 registered in the checker and mirrored in the checked policy. Recompute and update the enforcement-owned source pin only after reviewing the complete adapter; any import, helper, or statement change deliberately invalidates the raw-procedure exception. - Only an exact declared UnitOfWork adapter may call
$transaction. New unit-of-work adapters do not call$transactionthemselves: they pass their work callback to the reviewed___RunInPrismaUnitOfWorkhelper (@opencrane/backend/server/infra/prisma-unit-of-work), which owns transaction opening, the explicit isolation level, and bounded retry of proven rollbacks. The checker grants that callback's first parameter transaction-client authority, exactly as it does for___RunSerializableAuthorizationTransaction. - Passing a transaction client into another repository is also policy-owned. Every declared
repository constructor accepts
Prisma.TransactionClient, and each declared construction must receive the exact$transactioncallback binding (or an owning repository's typed transaction property), never the rootPrismaClient. Stale declarations and substituted bindings fail. - Composition roots may import
PrismaClientonly at exact listed paths. That permits dependency wiring, never delegate or transaction ownership.
The checker compares new findings with the base revision, so inherited violations remain visible
through --all without blocking unrelated slices. Exact temporary exemptions require an owner,
reason, an allowed delegate or transaction operation, and a real UTC calendar expiry; malformed or
stale policy fails closed. Raw Prisma methods cannot be authorized by an owner declaration or a
temporary exemption; apart from the fixed durable-admission procedure above, database-specific
invariants belong in the reviewed target baseline while repositories access them through typed delegates.
Per-domain schema files keep model ownership attributable while one reviewed target SQL describes the product OpenCrane creates today. Git history records older shapes; the runtime does not carry them.