# KBN-101 — Database Runtime/Migration Role Split **Status:** frozen implementation contract; remediation pending independent exact-head re-review for [#771](https://git.mosaicstack.dev/mosaicstack/stack/issues/771) **Version:** 1.0.0-rc.6 **Dependency:** KBN-010 → **KBN-101 foundation** → KBN-100 → **KBN-101 deployed-role certification** → KBN-105 **Scope:** PostgreSQL standalone/federated runtime identity, the sole application DDL runner, TLS bootstrap, readiness, deployment handoff, and evidence. This documentation card changes no database, secret, deployment, CI, runtime, migration, or compose artifact. ## 1. Decision, modes, and non-negotiable boundaries Current `main` has one `DATABASE_URL` path in `packages/db/src/client.ts`, `migrate.ts`, `drizzle.config.ts`, storage adapters/CLI, Gateway startup, fleet-backlog, CI, installer output, compose, and Portainer. It also has PostgreSQL DDL outside a controlled migration phase: direct Drizzle scripts, `CREATE EXTENSION` probes, a direct-DDL federated integration test, and platform init SQL. None of those current paths is certified by this contract; every implementation card below must close its listed path. | Item | Exact name / shape | Rule | | ------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Runtime URL | `DATABASE_URL` | PostgreSQL **non-owner runtime** connection. Required only by runtime services in `standalone`/`federated`; those services never receive `DATABASE_MIGRATION_URL`. | | Migration URL | `DATABASE_MIGRATION_URL` | Required only by `mosaic-db-migrator`, the dedicated one-shot runner/Job. It is forbidden in Gateway, storage runtime, fleet, and ordinary CLI environments. | | Runtime DTO | `DatabaseRuntimeConnectionConfigDto` | `tier`, `databaseUrl`, `runtimeMode`; maps only `DATABASE_URL`. It is not constructible from migration configuration. | | Migration DTO | `DatabaseMigrationConnectionConfigDto` | `tier`, `migrationDatabaseUrl`, `migrationMode`; maps only `DATABASE_MIGRATION_URL`, is accepted only by `mosaic-db-migrator`, and has no runtime-bootstrap import path. | | TLS DTO | `DatabaseTlsConfigDto` | `caCertificatePath`, `rejectUnauthorized: true`, `serverName`; validates the mounted CA without serializing its bytes. `serverName` is derived only from the validated connection host. | | Readiness DTO | `DatabaseSchemaReadinessDto` | `state`, `expectedSchemaVersion`, `observedSchemaVersion`, `migrationRequired`, `roleCheck`, `checkedAt`; no URL, user, host, database name, or secret. | | Modes | `local`, `standalone`, `federated` | `local` is the explicit PGlite-only exception. `standalone` and `federated` are production-like PostgreSQL modes. | **No fallback or plaintext:** in production-like modes, missing `DATABASE_URL`, `DATABASE_MIGRATION_URL`, or `DATABASE_TLS_CA_CERT_PATH` is a phase-specific error. No command may substitute a runtime URL, config-file URL, default URL, inferred URL, or hard-coded URL. PostgreSQL URLs must use `sslmode=verify-full` with `rejectUnauthorized: true`; `disable`, `allow`, `prefer`, `require`, `no-verify`, an absent CA, a wrong CA, an unverified certificate, or host/SAN mismatch fails before readiness. PGlite uses only its configured local data directory and explicit PGlite routine; it neither reads nor interprets PostgreSQL URL/TLS variables. **ASSUMPTION K101-A1:** `standalone` and `federated` are the complete current PostgreSQL production-like modes. A future PostgreSQL tier inherits this contract until a versioned amendment names its DNS, secrets, bootstrap, and evidence. ## 2. The sole PostgreSQL DDL control plane `mosaic-db-migrator` is the **only application/CI/test command that may connect with DDL authority to PostgreSQL**. It owns, in one `max: 1` PostgreSQL session: migration-DTO parse, TLS/identity/search-path preflight, advisory-lock acquisition, manifest/ledger reconciliation, migration execution, postflight/readiness verification, lock release, and session close. It rejects `DATABASE_URL`-only invocation **before opening a connection or emitting DDL**. The only exception is an external privileged platform/IaC bootstrap actor (§6), which is not an application command, never receives either application URL, and has its separately audited fixed bootstrap artifact. The implementation must inventory and close every present and future entrypoint as follows: | Current entrypoint | Required rc.6 disposition | | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `packages/db/src/migrate.ts:runMigrations()` | Remove its optional URL, `DATABASE_URL`, and default fallback API from public/runtime exports. Its PostgreSQL behavior moves behind `mosaic-db-migrator`; callers cannot invoke it with an arbitrary URL. | | `packages/db/drizzle.config.ts` and direct `drizzle-kit migrate` | A database-connecting Drizzle configuration reads only `DATABASE_MIGRATION_URL` through the migration DTO and rejects its absence before connection. Replace direct `drizzle-kit migrate` exposure with `mosaic-db-migrator`. `db:generate` is an offline schema artifact command and must neither resolve nor connect to a URL. | | `pnpm --filter @mosaicstack/db db:migrate` and CI invocation | Make it a thin `mosaic-db-migrator` wrapper; no direct Drizzle migrator invocation remains. CI supplies an isolated disposable migration URL only to that job. | | `db:push` / direct `drizzle-kit push` | Forbidden for standalone, federated, CI production-like, Portainer, and any URL outside a disposable developer database. If retained for local experimentation, a wrapper requires `MOSAIC_DISPOSABLE_DEVELOPER_DB=1`, a locally allowlisted disposable target, `DATABASE_MIGRATION_URL`, verified TLS when PostgreSQL is used, and rejects `DATABASE_URL`, any production-like tier, and every non-allowlisted host/database before connection. It is never a release, repair, or migration procedure. | | `mosaic storage migrate --run` | Delegates only to `mosaic-db-migrator`; it rejects `DATABASE_URL`-only execution before spawning or connecting. Its PGlite form remains explicitly local-only. | | `PostgresAdapter.migrate()`, Gateway `DatabaseModule`/startup, and PostgreSQL adapter factories | Runtime PostgreSQL migration is removed: no `runMigrations`, DDL, `CREATE EXTENSION`, or migration-compatible handle is reachable from startup. Gateway performs read-only identity, TLS, `search_path`, and manifest-ledger readiness checks only. | | `packages/mosaic/src/commands/fleet-backlog.ts` | PostgreSQL fleet backlog never migrates or creates tables. It consumes a ready runtime connection; PGlite may use only its explicit local migration routine. | | `packages/storage/src/{adapters/postgres,tier-detection}.ts` extension work and `infra/pg-init/01-extensions.sql` | Runtime probes become read-only catalog/extension-presence checks. Extension provisioning is a fixed external bootstrap prerequisite or a reviewed runner migration where the migrator has the required scoped authority; it is never a probe side effect or a reason to grant runtime database CREATE. | | `packages/db/src/federation.integration.test.ts` direct type/table/index DDL | Replace with a pre-migrated disposable database created by `mosaic-db-migrator`, or make the test invoke that runner. The test itself has runtime credentials and no direct DDL. | | `README.md` and `docs/guides/{dev-guide,deployment}.md` operator instructions | `KBN-101-07` replaces direct `db:migrate`, `db:push`, and `CREATE EXTENSION` production-like instructions with the runner/bootstrap procedure, and labels any remaining disposable-local command as non-production with the §2 guard. Documentation is a DDL entrypoint and cannot advertise a bypass. | | New package scripts, test helpers, setup hooks, CLI commands, installers, CI steps, adapters, or operator docs | A repository check rejects any new PostgreSQL DDL-capable entrypoint or instruction unless it is the dedicated runner or the named external bootstrap artifact. No future script may accept a URL parameter or `DATABASE_URL` as a DDL escape hatch. | The runner must run the original migration bytes, including shipped `0009`; no migration command may repair a ledger by manual insertion, adoption, `db:push`, or schema diff. Tests requiring PostgreSQL schema consume a pre-migrated disposable database or invoke this exact runner. Every row above has a negative test proving `DATABASE_URL`-only execution fails before connection/DDL; the `db:push` negatives also cover production-like tier and production-like URL rejection. ## 3. Exact migration manifest, ledger, and lock ### 3.1 Manifest v1 The runner generates and verifies a source-controlled **migration manifest v1** from the shipped Drizzle journal and SQL files. A record has exactly: ```text logicalIndex: non-negative integer from journal array position journalTag: exact journal `tag` string migrationSha256: lowercase SHA-256 of the exact migration `.sql` file bytes ``` The canonical SQL bytes are the raw Git blob bytes at the signed source-release commit, not workstation checkout bytes. KBN-101-03 adds/validates an LF-pinning `.gitattributes` rule for `packages/db/drizzle/**/*.sql`, generates the source-controlled manifest from those canonical blobs in CI, and makes the runner verify deployed file bytes against the manifest before DDL. No newline, Unicode, whitespace, SQL, or line-ending normalization is applied. Manifest canonical serialization is UTF-8 bytes of: ```text mosaic-drizzle-manifest-v1\n \t\t\n ... in ascending logicalIndex with no omitted index ``` `manifestSha256` is SHA-256 of those canonical bytes. The manifest has no timestamp-derived ordering; `folderMillis`, legacy ledger `id`, and `created_at` are diagnostic only. KBN-101-03 corrects journal **logical** order metadata (including the current `0008`/`0009` anomaly) without changing shipped `0009` bytes. The runner stores certification in `drizzle.__mosaic_migration_manifest` with exactly one active v1 row (`manifest_version=1`, `manifest_sha256`, `certified_at`). It is owned by `mosaic_schema_owner`; only the migrator after `SET ROLE mosaic_schema_owner` may insert/update it; runtime gets `SELECT` only, and `PUBLIC` gets no schema/table privilege. Existing databases receive this table through the runner after backup/preflight; it is not created by Gateway, a test, or manual SQL. ### 3.2 Reconciliation and 0009 transition The expected ledger is the ordered list of v1 manifest tuples. The runner reads every observed `drizzle.__drizzle_migrations.hash`, maps **each observed hash to exactly one** manifest tuple, and rejects a missing, unknown, duplicate, ambiguous, corrupt, or stale-replica mapping. Equality is tuple-complete: every expected tuple occurs once, no additional tuple occurs, and the manifest digest matches. A count comparison or hash-set comparison is forbidden. Physical legacy insertion `id`, insertion timestamp, and order are expressly non-normative. For an existing database: 1. take the KBN-101-07 approved backup and capture a read-only inventory before changing anything; 2. if `0009` is missing **and** its effects are absent, run the original shipped `0009` through `mosaic-db-migrator` and record it normally; 3. if the `0009` hash is present but physically late, accept it when its one-to-one tuple mapping is exact; 4. if an expected hash is missing while its effects are partial or complete, or catalog/ledger evidence conflicts, fail closed as `DATABASE_MIGRATION_RECONCILIATION_AMBIGUOUS`. Recovery is backup restoration or an explicit separately reviewed repair artifact with its own owner, tests, backup, rollback, and approval—not manual ledger insertion/adoption; and 5. write/update the v1 certification row only after exact reconciliation and final catalog/readiness verification. Required runner tests cover clean, pre-0009, skipped-0009/effects-absent, applied-late, duplicate, unknown, missing, corrupt tuple-pair, partial/full-effect ambiguity, stale replica, backup/restore, and rerun idempotence. The tests prove the `0009` SQL bytes are unchanged and logical journal ordering—not physical ledger order—controls reconciliation. ### 3.3 Fixed advisory-lock namespace Before any preflight that can decide migration state, `mosaic-db-migrator` acquires `pg_try_advisory_lock(1297044289, 1262636593)`. The constants are signed-int4-safe fixed namespace values: class `1297044289` (`MOSA`) and object `1262636593` (`KBN1`). The same `max: 1` session retains it for preflight, reconcile, migrate, verify, release, and close. Failure returns `DATABASE_MIGRATION_LOCKED` immediately; a process crash releases it when the PostgreSQL connection closes. Runtime readiness remains unready while a holder is active and never waits by running migrations. Tests prove concurrent contention, crash/connection-loss release, readiness while held, and non-interference from an unrelated advisory key. ## 4. PostgreSQL roles, identifiers, and trusted sessions Role creation, passwords, membership, database ownership, certificates, and Vault values are platform/IaC/operator work—not Drizzle/application migrations. Application SQL must not issue credential/role management statements or embed credentials. | Role | Attributes and ownership | Membership / session use | | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `mosaic_platform_database_owner` | `NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS`; platform-only database owner after bootstrap. | Never granted to application roles. | | external platform bootstrap actor | Provider/operator/IaC-controlled privileged identity outside the Mosaic role graph and Vault/application configuration. | Creates/transitions the database and roles, then retires from application use. | | `mosaic_schema_owner` | `NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS`; owns only `mosaic` and `drizzle` schemas/objects. | Never an application login; no database/extension authority except reviewed bootstrap scope. | | `mosaic_migrator` | `LOGIN NOINHERIT NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS`. | Only `mosaic_schema_owner`; runner verifies `session_user=mosaic_migrator`, `SET ROLE mosaic_schema_owner`, then `current_user=mosaic_schema_owner`. | | `mosaic_runtime_capability` | `NOLOGIN` and no ownership/administrative attributes. | Holds only named runtime grants. | | `mosaic_runtime` | `LOGIN INHERIT` with no ownership/administrative attributes. | Only `mosaic_runtime_capability WITH INHERIT TRUE, SET FALSE, ADMIN FALSE`; never owner/migrator member. | The fixed application/runtime schema is **`mosaic`**. Every application/runtime pooled connection executes and verifies exactly `SET search_path TO pg_catalog, mosaic` before its first application query; connection checkout repeats this after reset/reconnect. Transactional application operations use `SET LOCAL search_path TO pg_catalog, mosaic` and verify it before query execution. `public` and `$user` are forbidden in all runtime paths. The sole runner has one narrowly bounded **legacy-history bootstrap subphase** for the immutable current journal: before it runs, external bootstrap revokes `CREATE` on `public` from `PUBLIC`, assigns its ownership to `mosaic_schema_owner`, and denies all runtime connections. In its locked `max:1` migration session only, after `SET ROLE mosaic_schema_owner`, it uses `SET LOCAL search_path TO pg_catalog, public` solely to execute the shipped historical bytes that contain unqualified objects and explicit `public.*` references. It then, in that same runner/session before manifest certification or any runtime readiness, inventories and relocates every application object to `mosaic`, reconciles extension/type dependencies, regenerates/verifies Drizzle declarations/snapshots, and restores the exact `pg_catalog,mosaic` runner path. This compatibility subphase is not a runtime/operator option, accepts no configuration identifier, has no fallback, and is removed/disabled before KBN-101-08. A clean-PostgreSQL test must prove original historical bytes succeed in this subphase and runtime reaches readiness only with `pg_catalog,mosaic`; existing-database transition follows the same inventory/relocation or fails closed on an unknown dependency. `vector` moves only if relocatable; otherwise every type/function reference is explicitly qualified and its non-writable extension schema is documented. The pre-activation rollback restores the approved backup or reverses only the reviewed relocation artifact before activation; no runtime `search_path` bypass exists. No SQL identifier may come from URL/config/environment/operator input. Catalog comparisons use parameter values. The fixed identifiers above are constants; the external bootstrap artifact alone may use server-side `format('%I', fixed_allowlisted_identifier)` after allowlist validation. Raw client-side interpolation for identifiers, `SET search_path`, database, schema, role, table, or extension names is forbidden. Tests include injection-shaped values, a poisoned pooled-session reset, and transaction `SET LOCAL` restoration negatives. External bootstrap executes `REVOKE CONNECT, TEMPORARY ON DATABASE FROM PUBLIC`, then grants `CONNECT` only to `mosaic_runtime`, `mosaic_migrator`, and the time-bounded external bootstrap actor while it is required. Certification fails if an unrelated login retains `CONNECT` or either application login retains `TEMPORARY`. Runtime receives `USAGE` on `mosaic`, named table/sequence grants through `mosaic_runtime_capability`, and `USAGE` on `drizzle` plus `SELECT` only on `drizzle.__drizzle_migrations` and `drizzle.__mosaic_migration_manifest`. `INSERT`, `UPDATE`, `DELETE`, `TRUNCATE`, and DDL rights on ledger/manifest are revoked. Revoke public CREATE and function EXECUTE; `SECURITY DEFINER` is forbidden unless a separately reviewed exception pins trusted path and grants only the capability role. Database TEMPORARY, role management, extension, schema, and object ownership are denied. Immutable KBN relations, after KBN-100 creates them, grant runtime only `SELECT, INSERT` and explicitly deny `UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER`: `task_events`, `artifacts`, `task_checkpoints`, `task_checkpoint_artifacts`, and `approval_decision_artifacts`. KBN-100 retains RESTRICT/no-cascade semantics. Foundation certification verifies the role/schema boundary only; post-KBN-100 certification verifies this real deployed-role matrix. ## 5. Deployable verified-TLS bootstrap The **operator/IaC owner** provisions the CA and PostgreSQL leaf key/certificate. Mosaic applications never generate, self-sign, copy, or persist production certificates. The external bootstrap actor receives those secrets only through the deployment secret mechanism; no plaintext development exception exists for production-like modes. | Material | Vault target / deployment secret | Mount, injection, and authorized consumer | | --------------------------- | ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Runtime URL | `secret-{env}/mosaic-stack/database/runtime` (`url`) → `mosaic-db-runtime-url-v1` | `/run/secrets/mosaic-db-runtime-url`, `0600`, Gateway service UID only; entrypoint maps it to `DATABASE_URL` only at process exec. It is denied to migrator, storage runtime, fleet, ordinary CLI, and every test except an explicit runtime-negative fixture. | | Migration URL | `secret-{env}/mosaic-stack/database/migrator` (`url`) → `mosaic-db-migrator-url-v1` | `/run/secrets/mosaic-db-migrator-url`, `0600`, one-shot `mosaic-db-migrator` service UID only; entrypoint maps it to `DATABASE_MIGRATION_URL` only at process exec. It is denied to Gateway, storage runtime, fleet, and ordinary CLI. | | CA bundle | `secret-{env}/mosaic-stack/database/tls-ca` (`certificate`) → `mosaic-db-ca-v1` | Runtime and migrator: `/run/secrets/mosaic-db-ca.crt`, `0600`, service UID; PostgreSQL gets a read-only CA copy only when client-cert validation is enabled. | | PostgreSQL leaf certificate | `secret-{env}/mosaic-stack/database/postgres-server-tls` (`certificate`) → `mosaic-postgres-server-cert-v1` | PostgreSQL: `/run/secrets/mosaic-postgres-server.crt`, `0600`, postgres UID. | | PostgreSQL private key | same Vault record (`private_key`) → `mosaic-postgres-server-key-v1` | PostgreSQL: `/run/secrets/mosaic-postgres-server.key`, `0400`, postgres UID, never mounted to Gateway or migrator. | Compose uses identically named local **secret references**, rendered by an operator-controlled secret provider into a non-repository tmpfs; Swarm declares the named secrets with the same target paths, UID/GID, and modes. KBN-101-05 rejects bind-mounted committed cert/key files, environment-encoded PEM, and a missing secret. The existing target Vault names are planned canonical paths and must be verified/provisioned by the deployment owner; the planning card does not claim they exist. The server leaf SANs are frozen to actual connection DNS names, not a configurable alias: | topology | PostgreSQL service DNS names that must be SANs | | ------------------------------------ | -------------------------------------------------------------------------------------------------- | | standalone compose | `DNS:postgres`, `DNS:localhost` (only for the documented host-port disposable test path) | | federated compose | `DNS:postgres-federated`, `DNS:localhost` (only for the documented host-port disposable test path) | | Portainer/Swarm federated test stack | `DNS:postgres` (the in-stack service endpoint used by Gateway and migrator) | A new topology requires a versioned amendment before issuance. The PostgreSQL container activation artifact sets `ssl=on`, `ssl_cert_file`, and `ssl_key_file` to those paths, uses a locked-down `postgresql.conf` include, and verifies file ownership/mode before start. The server health/readiness gate makes a `verify-full` CA/SAN-validated connection as the approved runtime/migrator identity; `pg_isready` alone is insufficient. Migration Job starts only after server TLS readiness. Gateway replicas start only after a successful runner result and independently pass verified-TLS, identity, search-path, and ledger readiness. **Fresh DB:** provision CA/leaf/secrets and server TLS configuration before initial database bootstrap; bootstrap/extension prerequisites run, then the runner migrates over verified TLS, then runtime deploys. **Existing DB:** take the approved backup, provision/mount TLS material, and **drain/scale to zero every N-1 runtime, worker, CLI maintenance process, and replica before TLS enforcement**. Enable server TLS, terminate any residual non-TLS PostgreSQL backend sessions, set `pg_hba.conf` to `hostssl` for all application/migrator CIDRs with no matching `host` rule, reload/restart as required, and prove the non-TLS session count is zero. Only then prove `verify-full` through the existing endpoint, run reconciliation/migration, and roll the non-owner TLS runtime. There is no plaintext transition interval. **Rotation/rollback:** stage a CA bundle containing old+new trust roots to runtime/migrator, validate a new server leaf with exact SANs, restart PostgreSQL and validate it, roll consumers, then remove the old root only after evidence. Credential rotation remains independent and never mounts migration material into Gateway. Before expiry, rollback restores the prior known-valid leaf/key and overlapping CA bundle, restarts PostgreSQL, enforces `hostssl`, terminates residual non-TLS sessions, and verifies `verify-full`; it never downgrades `sslmode`, restores a plaintext-only N-1 runtime after enforcement, or accepts plaintext. A pre-enforcement abort may restore the backed-up N-1 state only before `hostssl` is enabled and is recorded as an aborted—not activated—release. The runbook records expiry windows, secret versions, backup ID, drained-service/session evidence, activation actor, and validation result—not secret values. Required disposable tests cover standalone compose and federated/Swarm positives using verified TLS, plus missing CA, wrong CA, wrong SAN, `sslmode` downgrade, missing/mispermissioned server key, runtime-with-migrator-secret, rendered secret-consumer separation, legacy plaintext drain/termination/`hostssl` enforcement, and readiness-before-migration negatives. PGlite local tests are explicitly classified as non-PostgreSQL and do not satisfy a PostgreSQL TLS test. ## 6. Runtime verification and sanitized failures Before accepting traffic, runtime queries only parameterized/sanitized identity and privilege metadata on its already-open verified-TLS connection. It fails closed for owner/migrator identity or assumability; superuser/CREATEROLE/CREATEDB/BYPASSRLS; object/schema/database ownership; TEMPORARY; unexpected function execute; missing inherited capability/sequence/ledger grants; any non-exact search path; wrong schema/database target; bad TLS; or manifest/ledger mismatch. After KBN-100 it also checks every immutable grant/denial and relation presence. The runner performs reciprocal preflight under the same session/lock: dedicated migration DTO only, verified TLS, exact allowlisted target, migrator `session_user`, schema-owner `current_user`, exact trusted search path, and no unsafe attributes. It fails before DDL otherwise. Stable sanitized codes are `DATABASE_RUNTIME_URL_REQUIRED`, `DATABASE_MIGRATION_URL_REQUIRED`, `DATABASE_TLS_REQUIRED`, `DATABASE_TLS_VERIFICATION_FAILED`, `DATABASE_ROLE_UNSAFE`, `DATABASE_ROLE_GRANT_MISMATCH`, `DATABASE_SEARCH_PATH_UNSAFE`, `DATABASE_SCHEMA_MISMATCH`, `DATABASE_MIGRATION_RECONCILIATION_AMBIGUOUS`, `DATABASE_MIGRATION_LOCKED`, and `DATABASE_MIGRATION_IDENTITY_UNSAFE`. Logs/metrics may contain code, tier, manifest fingerprint, role class, and correlation ID only; never DSN, username, host, database name, SQL parameter, secret, or raw catalog result. External health exposes only unavailable/not-ready. ## 7. Safe DAG, activation, and rollback authority Every KBN-101 card remains one PR with exclusive ownership. Cards `00`–`07` may merge only as **prepared, inactive capability**: no current owner-runtime deployment consumes their image/config, and no compatibility switch is exposed to a runtime operator. They must not retain `ALLOW_LEGACY_*`, runtime DDL, `DATABASE_URL` migration fallback, plaintext TLS, direct Drizzle, or test-only bypass flags. Current owner-runtime deployments remain on their known N-1 release until final activation. | Card | Depends on | Exact ownership and required result | | ----------------------------------------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `KBN-101-00` platform bootstrap / IaC | contract | Fixed external bootstrap artifact creates/transitions roles, `mosaic`/`drizzle` ownership, extension prerequisite, Vault secret bindings, server TLS config, fresh/existing inventory, and reversible evidence. It is not an app migration or a runtime command. | | `KBN-101-01` runtime config/DB boundary | 00 | Typed split DTOs, verified TLS config, fixed schema/session verifier, no PostgreSQL Gateway startup migration. | | `KBN-101-02` entrypoint closure | 01 | Owns all paths in §2 except runner/journal: storage, CLI, adapters, Gateway startup, tier probes, fleet backlog, and direct-DDL test closure. | | `KBN-101-03` runner/manifest/schema foundation | 00,01 | Owns `mosaic-db-migrator`, canonical Git-blob/LF manifest v1, fixed lock, journal logical-order repair, runner-only legacy-public bootstrap then `mosaic` relocation, ledger/manifest grants, and PostgreSQL runner tests. Shipped `0009` bytes remain unchanged. | | `KBN-101-04` installer/wizard/config | 01 | Production-like config persists no DSN and has no fallback; only non-secret references/injected variables. | | `KBN-101-05` compose/Portainer deployment | 00,01,03 | Owns secret references, TLS server/client mounts, server configuration, Job ordering, readiness, standalone/federated/Swarm TLS tests, and no plaintext exception. | | `KBN-101-06` CI/test topology | 02,03,05 | Disposable identities and pre-migrated/runner test topology; repository checks reject DDL bypasses and insecure URLs. | | `KBN-101-07` Vault/runbook/observability | 00,04,05,06 | Activation/rotation/incident/backup/rollback runbook, sanitized observability evidence, and replacement of direct-DDL operator documentation (`README.md`, `docs/guides/dev-guide.md`, `docs/guides/deployment.md`). | | `KBN-101-08` foundation certification and **atomic activation release** | 00…07 | Independent review verifies all prepared cards and terminal-green CI; Mosaic control plane/Jason authorizes one ordered activation: backup → drain/scale zero all N-1 runtime clients → TLS server start → terminate non-TLS sessions and enforce `hostssl`/no `host` rule → verify no plaintext session → roles → runner → verified readiness → rolling non-owner runtime. Any red test/readiness/CI result aborts; no force-on-red or bypass. Remove/disable all temporary compatibility support before this gate. | | `KBN-101-09` post-KBN-100 certification | KBN-100,08 | Real deployed-role immutable INSERT/SELECT and UPDATE/DELETE-denial evidence plus independent security/Ultron approval. | **Authority:** Mosaic control plane/Jason is the sole activation and rollback authority. CI, Gateway, migrator, Coordinator, and Certifier cannot activate, waive a red result, or force release. Before an incompatible KBN-100 switch, the authority stops/scales runtime, uses the approved backup/restore or separately reviewed runner artifact, restores only a known TLS-compatible runtime with its runtime secret after `hostssl` enforcement, and verifies no plaintext sessions plus TLS/readiness. Migration URL is never injected into Gateway to enable rollback. KBN-100 starts only after KBN-101-08; KBN-105 starts only after KBN-101-09. **ASSUMPTION K101-A2:** every eligible production-like deployment can schedule a dedicated migration Job/one-shot command and an operator/IaC-controlled TLS bootstrap. A target that cannot do both is ineligible for KBN certification. ## 8. Acceptance traceability | Requirement / acceptance criterion | Required implementation evidence | | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | K101-REQ-01 / AC-K101-01 | DTO and command matrix covers local/PGlite, standalone, federated; every §2 entrypoint rejects `DATABASE_URL`-only before connection/DDL; no runtime fallback/default. | | K101-REQ-02 / AC-K101-02 | One-session fixed two-int lock, contention/crash/readiness/unrelated-key tests; manifest v1 canonical bytes/digest and all reconciliation states; Gateway/replica DDL impossibility. | | K101-REQ-03 / AC-K101-03 | Fresh/existing TLS bootstrap and compose/Swarm verified-TLS positives; CA/SAN/downgrade/key-permission negatives; exact runtime/migrator secret-consumer rendering/CI negatives; catalog role/grant/search-path/identifier/pool-reset tests. | | K101-REQ-04 / AC-K101-04 | After KBN-100, real deployed runtime INSERT/SELECT success and UPDATE/DELETE denial for each frozen relation, with RESTRICT retention evidence. | | K101-REQ-05 / AC-K101-05 | Prepared-card/no-intermediate-deploy evidence; one final activation authority record; N-1 drain/zero-plaintext-session/`hostssl`, backup/restore, CA overlap rotation, TLS-only rollback, Vault/redaction, and no-force-on-red evidence. | | K101-REQ-06 / delivery integrity | One-card/one-PR DAG, exact file ownership, docs/link/contract checks, independent author≠reviewer re-review on the pushed exact head, and terminal-green CI for implementation cards. | ## 9. Non-goals and residual authority KBN-101 planning does not create roles, certificates, Vault paths, migrations, deployment artifacts, or a deployed certificate. It does not replace KBN-100’s data migration, immutable retention, tenant constraints, or KBN-105 endpoint freeze. A PostgreSQL superuser/break-glass operator remains outside application containment and requires separate audited platform controls, backup evidence, and drills.