diff --git a/docs/PRD.md b/docs/PRD.md index 7585ecc..15f73fd 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -125,6 +125,33 @@ are defined in [docs/TASKS.md](./TASKS.md) and must remain one card/one PR. --- +## KBN-101 Database Runtime/Migration Role Split (#771) + +### Problem and objective + +PostgreSQL Gateway/storage currently uses one `DATABASE_URL` for runtime queries and migrations. That makes the deployed application identity an owner and prevents certification that KBN immutable event, artifact, checkpoint, and evidence relations reject runtime `UPDATE`/`DELETE`. KBN-101 freezes a least-privilege runtime/migration split before KBN-100 schema work. + +### Normative requirements + +1. `K101-REQ-01`: `DATABASE_URL` SHALL be the non-owner PostgreSQL runtime connection and `DATABASE_MIGRATION_URL` SHALL be the migration-only owner/migrator connection. They are required respectively for runtime and explicit migration phases in `standalone`/`federated`; local PGlite is the explicit exception. A missing migration URL SHALL never fall back to the runtime URL/default/configuration URL. +2. `K101-REQ-02`: Gateway runtime/replicas SHALL not execute migrations or DDL. A serialized, advisory-locked migration phase SHALL verify the exact shipped-versus-applied schema ledger before replicas become ready; mismatch SHALL fail closed. +3. `K101-REQ-03`: PostgreSQL SHALL separate non-login platform database owner, non-login schema owner, login migrator, non-login runtime capability, and login runtime roles; runtime ownership, superuser/role-creation/schema-creation/TEMPORARY, unsafe membership, untrusted search path, missing grants, unauthenticated TLS, and immutable privilege drift SHALL fail startup/readiness closed. +4. `K101-REQ-04`: KBN immutable relations SHALL permit the real runtime role INSERT/SELECT only and deny UPDATE/DELETE; parent retention remains RESTRICT/no-cascade. Role/password/Vault creation is external platform control, never application migration/source. +5. `K101-REQ-05`: N-1 single-URL compatibility, rollout/rollback, Vault ownership/rotation/redaction, CI, installer, compose/Portainer, observability, and deployment handoffs SHALL be separately bounded one-card/one-PR work. KBN-101 planning itself SHALL not mutate production. +6. `K101-REQ-06`: KBN-100 SHALL begin only after the KBN-101 foundation role/schema-boundary certificate; it SHALL rebase on that main head, restore generated Drizzle declaration/snapshot/journal consistency, and bound procedural immutable-table grant/trigger/backfill additions to its schema slice. KBN-101 real deployed-role immutable-operation certification SHALL complete after KBN-100 creates those relations and before KBN-105. + +### Acceptance criteria + +1. `AC-K101-01`: Runtime/migration DTO and environment contract tests prove required modes, PGlite exception, and no migration-to-runtime fallback. +2. `AC-K101-02`: Explicit migration lock/schema-readiness tests prove no replica race or runtime auto-migration and fail closed on ledger mismatch. +3. `AC-K101-03`: Catalog tests and a real deployed-role certificate prove platform/schema-owner/migrator/runtime separation, ownership/membership/search-path/ledger-read/TEMP/default grants, authenticated TLS, and unsafe privilege denial. +4. `AC-K101-04`: Real runtime-role evidence proves INSERT/SELECT succeeds and UPDATE/DELETE fails for every frozen immutable KBN relation. +5. `AC-K101-05`: N-1 rollout/rollback, Vault/rotation/redaction, health/operator behavior, CI/deployment handoff, independent security review, and terminal-green CI evidence the foundation before KBN-100; after KBN-100, the real deployed-role immutable-operation certificate and Ultron approval release KBN-105. + +**Normative implementation contract:** [`docs/native-kanban-sot/KBN-101-DB-ROLE-SPLIT.md`](./native-kanban-sot/KBN-101-DB-ROLE-SPLIT.md). `ASSUMPTION:` existing `standalone` and `federated` are all PostgreSQL production-like modes; any new PostgreSQL tier inherits these requirements until an explicit versioned amendment. + +--- + ## Tess Interaction Agent Workstream (TESS) ### Problem and Objective diff --git a/docs/SITEMAP.md b/docs/SITEMAP.md index 1ac19d5..1e43791 100644 --- a/docs/SITEMAP.md +++ b/docs/SITEMAP.md @@ -21,6 +21,7 @@ - [Workstream index](native-kanban-sot/INDEX.md) — artifact map, lane partition, and delivery order. - [Mission manifest](native-kanban-sot/MISSION-MANIFEST.md) — scope, authority, invariants, and gate model. - [Task decomposition](native-kanban-sot/TASKS.md) — dependency-ordered implementation slices and ownership boundaries. +- [KBN-101 database role split](native-kanban-sot/KBN-101-DB-ROLE-SPLIT.md) — rc.5 PostgreSQL runtime/migration connection, privilege, rollout, and certification prerequisite. - [Frozen shared contract](native-kanban-sot/SHARED-CONTRACT.md) — schema, API, Coordinator, health, recovery, and migration contracts. - [Initial independent review](reports/native-kanban-sot/canon-initial-review-no-go.md) — KCR-001–016 findings that blocked the first draft. - [Final independent re-review](reports/native-kanban-sot/canon-final-rereview-go.md) — closure evidence and GO verdict. diff --git a/docs/native-kanban-sot/INDEX.md b/docs/native-kanban-sot/INDEX.md index 2f12599..8f5cbed 100644 --- a/docs/native-kanban-sot/INDEX.md +++ b/docs/native-kanban-sot/INDEX.md @@ -11,6 +11,7 @@ | [Canonical requirements](../requirements/native-kanban-sot.md) | Canonical P0–P3 requirements, all seven ratified decisions, fixed invariants, thin MVP, recovery tiers, non-goals, and per-requirement acceptance criteria | | [`MISSION-MANIFEST.md`](./MISSION-MANIFEST.md) | Mission/authority boundaries, exact role chain, gate model, mandatory SecReview triggers, Certifier final/no-merge rule, and collision-free slice ownership | | [`TASKS.md`](./TASKS.md) | Dependency-ordered, bounded P0–P3 slices with IN/OUT scope, dependencies, shared contracts, file ownership, evidence, and USC coder2/3/4/5 parallelization | +| [`KBN-101-DB-ROLE-SPLIT.md`](./KBN-101-DB-ROLE-SPLIT.md) | rc.5 frozen PostgreSQL runtime/migration connection, role, readiness, rollout, evidence, and one-card/one-PR contract; foundation prerequisite of KBN-100 and real-role gate before KBN-105 | | [`SHARED-CONTRACT.md`](./SHARED-CONTRACT.md) | Remediated v1 integration contract: proof authority, exact failures/routes/DTOs/MCP ownership, concrete current-main field migration map, relational invariants, Coordinator split, recovery delivery | | [`contracts/kanban-schema.v1.ts`](./contracts/kanban-schema.v1.ts) | Drizzle target declarations including exact owner/principal membership, project congruence, tags/archive, proposals, persisted assignments, monotonic fences, durable retry, immutable evidence/audit | | [`contracts/mechanical-coordinator.v1.ts`](./contracts/mechanical-coordinator.v1.ts) | Pure snapshot decision engine separated from persistence/service adapter; ID-bound approvals, bigint-safe fences, durable retry/quarantine, artifact-backed checkpoints, exact failures | @@ -32,7 +33,7 @@ | **coder5** | Web | Tasks/Projects Kanban/List/detail and later Coordinator/migration-review UI | | **Mos** | Serialized integration | Canon publication, frozen-contract changes, shared-root/exports, integration gates, merge authority | -The safe order is KBN-010 → KBN-100 → KBN-105, then coder3 Gateway/MCP server, coder4 CLI/projection, coder5 web, and coder2 recovery can proceed on disjoint files. coder4 then runs pure Coordinator → importer → cutover tooling serially. No two active slices edit the same files. +The safe order is KBN-010 → KBN-101 foundation → KBN-100 → KBN-101 deployed-role immutable-operation certificate → KBN-105, then coder3 Gateway/MCP server, coder4 CLI/projection, coder5 web, and coder2 recovery can proceed on disjoint files. KBN-100 is blocked on the KBN-101 foundation; real deployed-role certification—not synthetic test roles—is required before KBN-105. coder4 then runs pure Coordinator → importer → cutover tooling serially. No two active slices edit the same files. ## Recovery defaults diff --git a/docs/native-kanban-sot/KBN-101-DB-ROLE-SPLIT.md b/docs/native-kanban-sot/KBN-101-DB-ROLE-SPLIT.md new file mode 100644 index 0000000..f8978b5 --- /dev/null +++ b/docs/native-kanban-sot/KBN-101-DB-ROLE-SPLIT.md @@ -0,0 +1,145 @@ +# KBN-101 — Database Runtime/Migration Role Split + +**Status:** frozen implementation contract; documentation-only planning for [#771](https://git.mosaicstack.dev/mosaicstack/stack/issues/771) + +**Version:** 1.0.0-rc.5 + +**Dependency:** KBN-010 → **KBN-101 foundation** → KBN-100 → **KBN-101 deployed-role certification** → KBN-105 +**Scope:** PostgreSQL standalone/federated runtime identity, migration identity, readiness, deployment handoff, and evidence. No database, secret, deployment, CI, or runtime mutation is authorized by this card. + +## 1. Decision and current-state evidence + +Current `main` inspection found one `DATABASE_URL` used by `packages/db/src/client.ts`, `packages/db/src/migrate.ts`, Drizzle Kit, storage `PostgresAdapter.migrate()`, Gateway startup, installer output, Portainer federated stack, and CI. `DatabaseModule.onModuleInit()` invokes `storageAdapter.migrate()` for PostgreSQL, so each Gateway starts migrations under its application identity. The current config path also supplies PostgreSQL defaults. This prevents a production proof that the deployed runtime identity cannot update/delete KBN immutable tables. + +This contract separates those duties. It does not claim that existing synthetic roles, current defaults, or current single-URL deployments satisfy the target model. + +### Frozen names and modes + +| Item | Exact name / shape | Rule | +| ---------------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Runtime URL variable | `DATABASE_URL` | PostgreSQL **runtime non-owner** connection. Required for `standalone` and `federated`; runtime-only containers/services receive this variable and never `DATABASE_MIGRATION_URL`. | +| Migration URL variable | `DATABASE_MIGRATION_URL` | PostgreSQL migrator connection. Required only by an explicit migration Job/one-shot operator phase in `standalone` and `federated`; it is forbidden in Gateway runtime service environments. | +| Runtime config DTO | `DatabaseRuntimeConnectionConfigDto` | Internal configuration DTO: `tier`, `databaseUrl`, `runtimeMode`. It maps only `DATABASE_URL`; URL values are never returned, logged, traced, or placed in public DTOs. | +| Migration config DTO | `DatabaseMigrationConnectionConfigDto` | Internal migration-command DTO: `tier`, `migrationDatabaseUrl`, `migrationMode`. It maps only `DATABASE_MIGRATION_URL`; it cannot be constructed by the Gateway runtime bootstrap. | +| Readiness DTO | `DatabaseSchemaReadinessDto` | Sanitized internal/admin health shape: `state`, `expectedSchemaVersion`, `observedSchemaVersion`, `migrationRequired`, `roleCheck`, `checkedAt`. It contains neither URL nor password nor host. | +| TLS variable / DTO | `DATABASE_TLS_CA_CERT_PATH`; `DatabaseTlsConfigDto` | Required in PostgreSQL production-like modes. Internal DTO is `caCertificatePath`, `rejectUnauthorized: true`, `serverName`; it derives server name from the URL host and validates the mounted CA file without serializing its content. | +| Modes | `local`, `standalone`, `federated` | `local` is the PGlite exception. `standalone` and `federated` are PostgreSQL production-like modes and must enforce this contract. | + +**No fallback:** in `standalone` or `federated`, a missing `DATABASE_MIGRATION_URL` is a migration-phase configuration error. The migration command must not fall back to `DATABASE_URL`, a config-file URL, a default URL, or an inferred URL. A missing `DATABASE_URL` or `DATABASE_TLS_CA_CERT_PATH` is a runtime startup error. Both PostgreSQL URLs must be parsed with authenticated TLS configured from `DatabaseTlsConfigDto`; reject `sslmode=disable`, `allow`, `prefer`, `require`, `no-verify`, certificate-unverified configuration, and URL/host mismatch. PGlite uses its configured data directory and its explicit local migration routine; it neither requires nor interprets PostgreSQL connection/TLS variables. + +**ASSUMPTION K101-A1:** existing production-like tiers are `standalone` and `federated`, as defined by `MosaicConfig`. If a new PostgreSQL tier is introduced, it inherits the production-like contract until a versioned amendment says otherwise. + +## 2. Explicit migration phase and fail-closed readiness + +1. Deployment supplies `DATABASE_MIGRATION_URL` only to a dedicated migration command/Job. That command opens one migration session, uses `pg_try_advisory_lock(hashtext('mosaic-schema-migration-v1'))`, returns `DATABASE_MIGRATION_LOCKED` immediately if unavailable, runs the ordered migration set as the migrator role, verifies the final schema version, releases the lock on that same session, and exits. +2. The application Gateway never calls `runMigrations()`, `drizzle-kit migrate`, adapter migration, extension creation, or any schema DDL on PostgreSQL startup. Replica startup can only perform runtime identity and schema readiness checks. +3. KBN-101-03 repairs the current PostgreSQL migration-runner/journal ordering defect before foundation certification: it must use a hash-complete ordered runner, repair the `_journal.json` order, and prove on a clean PostgreSQL database that every shipped migration hash (including `0009`) is applied exactly once. `expectedSchemaVersion` is the deterministic SHA-256 of the resulting ordered shipped Drizzle migration journal entries and migration hashes. `observedSchemaVersion` is calculated from `drizzle.__drizzle_migrations`; equality requires the same ordered complete set—no missing, duplicate, unknown, or out-of-order ledger entry. This is the readiness contract, not a best-effort count. +4. The Gateway starts/advertises readiness only when `DatabaseSchemaReadinessDto.state === 'ready'`, observed equals expected, and the role/grant checks in §4 pass. Any pending, partial, unknown, corrupt, or mismatched schema is `migration_required` or `schema_mismatch`, emits a sanitized operator code, and fails closed before serving requests. +5. Replicas do not wait for or race to migrate. They remain unready until the deployment controller observes a successful migration phase and each replica independently verifies the exact version/role checks. Migration concurrency is prevented by the non-blocking advisory lock; deployment sequencing permits no application replicas before its successful exit. +6. PostgreSQL extension provisioning requiring elevated database privilege (including any provider-specific extension prerequisite) is a separately audited platform/bootstrap step. It is not performed by Gateway runtime or accepted as a reason to grant the migration role superuser. + +## 3. PostgreSQL role and privilege model + +Role creation, passwords, membership, database ownership, and Vault values are platform/IaC/operator work—not Drizzle/application migrations. Application SQL must not issue `CREATE ROLE`, `ALTER ROLE ... PASSWORD`, `GRANT ... TO LOGIN` credential creation, 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. It owns the database but is never granted to application roles. | Outside application runtime/migration paths and separately audited. | +| external platform bootstrap actor | Provider/operator/IaC-controlled privileged identity, outside the Mosaic role graph and never placed in Vault/app configuration. | Creates a fresh database and roles, then transfers database ownership to `mosaic_platform_database_owner`; for an existing database it performs the audited ownership/grant transition. It is not used after bootstrap. | +| `mosaic_schema_owner` | `NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS`; owns only the Mosaic application schema and `drizzle` migration-ledger schema plus their objects. It does **not** own the database and lacks database `CREATE`, `ALTER DATABASE`, and extension-install authority. | Never used as an application login. | +| `mosaic_migrator` | `LOGIN NOINHERIT NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS`; has a separately stored migration credential. | Is granted only `mosaic_schema_owner`. The migration runner verifies `session_user = mosaic_migrator`, executes `SET ROLE mosaic_schema_owner`, and verifies `current_user = mosaic_schema_owner` before DDL. | +| `mosaic_runtime_capability` | `NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS`; owns no schema/object. | Holds the application table/sequence/schema privileges below. | +| `mosaic_runtime` | `LOGIN INHERIT NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS`; owns no schema/object. | Is granted only `mosaic_runtime_capability WITH INHERIT TRUE, SET FALSE, ADMIN FALSE`; it is never member of either owner/migrator role and cannot `SET ROLE` to them. Its inherited capability grants are effective without changing `current_user`. | + +The external platform bootstrap actor creates the database and role graph, transfers database ownership to `mosaic_platform_database_owner`, creates the application schema and `drizzle` ledger schema, makes `mosaic_schema_owner` their owner, revokes `CREATE` and function `EXECUTE` on both from `PUBLIC`, revokes database-level create/connect/TEMPORARY privileges not explicitly needed (including `REVOKE TEMPORARY ON DATABASE FROM PUBLIC`), and grants `CONNECT` only to the named login roles. The runtime receives `USAGE` on the application schema, `USAGE, SELECT` on required sequences, and table rights only through `mosaic_runtime_capability`. It receives only `USAGE` on `drizzle` plus `SELECT` on `drizzle.__drizzle_migrations`; `INSERT`, `UPDATE`, `DELETE`, and `TRUNCATE` on that ledger are explicitly revoked. The migration role/owner retains schema DDL authority; ordinary runtime has no `CREATE`, `TEMP`, schema ownership, role-administration, extension, or database-administration privilege. Production `search_path` is explicitly `pg_catalog, `; current-main `public` is the N-1 schema name until a separately versioned schema relocation is complete. Runtime startup verifies it and rejects untrusted writable schemas before application queries. + +For ordinary mutable application tables, grant `SELECT, INSERT, UPDATE, DELETE` through `mosaic_runtime_capability`. `ALTER DEFAULT PRIVILEGES FOR ROLE mosaic_schema_owner IN SCHEMA ` grants that baseline to future ordinary tables and sequence rights to future sequences; it also revokes `EXECUTE ON FUNCTIONS FROM PUBLIC`. The KBN-101 N-1 implementation uses current-main `public` as that schema name until the separately versioned relocation. `SECURITY DEFINER` functions are forbidden by default; a separately versioned reviewed exception must pin search path, revoke PUBLIC execute, grant only the named capability role, and add a negative privilege test. Each migration creating an immutable table must then apply the immutable override in the same migration and verify it: + +| KBN immutable relation | Runtime rights | Explicitly absent | +| ----------------------------- | ---------------- | ----------------------------------------------- | +| `task_events` | `SELECT, INSERT` | `UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER` | +| `artifacts` | `SELECT, INSERT` | `UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER` | +| `task_checkpoints` | `SELECT, INSERT` | `UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER` | +| `task_checkpoint_artifacts` | `SELECT, INSERT` | `UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER` | +| `approval_decision_artifacts` | `SELECT, INSERT` | `UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER` | + +The named list matches the KBN v1 contract’s event/checkpoint/artifact/evidence relations. KBN-100 must retain `RESTRICT` parent deletion semantics; grants alone do not prevent a privileged owner or a cascading FK from erasing evidence. Any future immutable relation must be added to this table in a versioned amendment before code ships. + +## 4. Effective-identity verification and operator errors + +Before accepting traffic, the runtime uses its already-open `DATABASE_URL` connection to query sanitized identity/privilege metadata (`session_user`, `current_user`, `current_database`, `current_schema`, `pg_has_role`, `has_schema_privilege`, `has_table_privilege`, `rolsuper`, `rolcreaterole`, `rolcreatedb`, `rolbypassrls`) and the migration ledger. It must fail closed when any condition holds: + +- `session_user` and `current_user` resolve to the same effective identity as `mosaic_platform_database_owner`, `mosaic_schema_owner`, or `mosaic_migrator`, or runtime can assume any of them; +- the runtime role is a superuser, `CREATEROLE`, `CREATEDB`, `BYPASSRLS`, database/table/schema owner, has database `TEMPORARY`, or has schema/DDL/extension/role-management authority; +- required runtime inherited-capability membership, grants, sequence rights, read-only Drizzle-ledger access, no unexpected PUBLIC/runtime function `EXECUTE`, `search_path`, authenticated-TLS configuration, database/schema identity, or schema readiness are missing/mismatched; +- **after KBN-100 creates the immutable relations**, immutable INSERT/SELECT rights or immutable UPDATE/DELETE denials are missing/mismatched, or an expected immutable relation is absent. The KBN-101 foundation verifies only the role/schema boundary; its post-KBN-100 certification verifies the immutable relation matrix. + +The migration command performs the reciprocal preflight: it requires the dedicated migration URL, the named migrator session user, schema owner after `SET ROLE`, no superuser/CREATEROLE/database-owner/database-CREATE requirement, the approved database/schema target, and an acquired advisory lock. It fails before DDL otherwise. + +Errors have stable sanitized codes: `DATABASE_RUNTIME_URL_REQUIRED`, `DATABASE_MIGRATION_URL_REQUIRED`, `DATABASE_ROLE_UNSAFE`, `DATABASE_ROLE_GRANT_MISMATCH`, `DATABASE_SEARCH_PATH_UNSAFE`, `DATABASE_SCHEMA_MISMATCH`, `DATABASE_MIGRATION_LOCKED`, and `DATABASE_MIGRATION_IDENTITY_UNSAFE`. Operator logs/metrics may contain code, tier, schema-version fingerprints, role **class** (`runtime`/`migrator`/`owner`), and correlation ID—not a DSN, username, host, database name, SQL parameter, secret, or raw catalog result. External health returns unavailable/not-ready without role detail. + +## 5. N-1 rollout, compatibility, and rollback + +1. **Prepare (N-1, no production mutation in KBN-101):** publish this contract, enumerate all consumers of `DATABASE_URL`, identify installer/compose/Portainer/CI/Vault handoff owners, and add implementation tests. Existing deployments remain one-URL only until the implementation card’s compatibility release. +2. **Compatibility release:** N-1 compatibility preserves the `DATABASE_URL` variable/config shape, not an owner-runtime exception. A current single-URL deployment remains on its prior release until roles are provisioned. The KBN-101 runtime release starts only after `DATABASE_URL` has been changed to the non-owner runtime role; it refuses owner/migrator identity, does not auto-migrate, and never treats that URL as `DATABASE_MIGRATION_URL`. +3. **Provision and migrate:** platform operators create the roles and Vault records, issue distinct URLs, run the explicit migration phase using `DATABASE_MIGRATION_URL`, verify exact schema and grants, then deploy runtime with only `DATABASE_URL`. +4. **Enforce:** after deployed-role certification is green, require non-owner `DATABASE_URL` for production-like runtime. Remove the N-1 compatibility path only in a separately versioned contract/release after fleet inventory proves no single-URL deployment remains. +5. **Rollback:** before any incompatible KBN-100 schema switch, stop/scale runtime replicas, restore the previous runtime release and its known schema-compatible backup/manifest, and re-run only the approved prior migration/restore procedure under the migration identity. Never put the migration URL into the application to make rollback work. After KBN-100 canonical mutation/switch, its existing freeze/reconciliation authority governs data rollback; role separation remains in force. + +**ASSUMPTION K101-A2:** a controlled migration Job/one-shot command exists or will be added by the deployment slice. If a target cannot schedule one, its deployment is not eligible for federated/production-like KBN certification. + +## 6. Secrets, observability, and deployment handoff + +Vault owns three distinct values, with least-privilege policies and no plaintext copies: `secret-{env}/mosaic-stack/database/runtime` (`url`) for the runtime role, `secret-{env}/mosaic-stack/database/migrator` (`url`) for the migration role, and `secret-{env}/mosaic-stack/database/tls-ca` (`certificate`) mounted at `DATABASE_TLS_CA_CERT_PATH` for both authorized PostgreSQL phases. Exact existing mount/service naming must be verified by the deployment owner before creation; this naming is a planned canonical target, not a claim that those paths already exist. CI uses isolated disposable non-production roles/URLs and must model both variables without promoting test credentials. + +Rotation is independent: rotate the runtime credential first with overlapping database validity and rolling runtime restart; rotate the migration credential only while no migration is active; rotate the CA only with verified trust overlap and rolling consumers; revoke old material after connection/pipeline evidence. Rotation never downgrades a runtime role or causes a Gateway to receive the migration secret. Secret injection maps runtime secret only to Gateway and migration secret only to migration Job; both are redacted by config/error/logging/OTEL processors. Secret names may be logged; values, DSNs, URI components, and decoded credentials may not. + +Readiness/metrics distinguish database connectivity, exact schema readiness, identity safety, immutable-grant verification, and migration-phase outcome. They expose codes and versions only. Runbooks must tell operators to inspect secret binding/role grants/schema phase through privileged deployment tooling—not to paste URLs into shell history, issues, logs, or chat. + +Deployment-specific ownership is deliberately handed off: installer/wizard, local compose, federated compose, Portainer stack, CI, Vault policy/provisioning, and runtime deployment manifests each require a separate one-card/one-PR implementation slice below. KBN-101 performs none of those mutations. + +## 7. Implementation DAG and file manifest + +Each card is one PR and has exclusive ownership. No card absorbs another card’s files. + +| Card | Depends on | Exact implementation ownership | Required result | +| --------------------------------------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `KBN-101-00` platform bootstrap / IaC | KBN-101 contract | `infra/pg-init/01-extensions.sql`, new `infra/pg-init/02-kbn101-role-bootstrap.sql`, platform deployment/IaC change set, and KBN-101-07 transition runbook/evidence | External bootstrap actor creates fresh DB/roles then transfers database ownership; existing DB transition inventories owners, creates/rotates roles, transfers schema/table/ledger ownership, applies grants/default privileges, provisions Vault paths, and records reversible evidence. It is not an application migration and no login credential is hardcoded. | +| `KBN-101-01` runtime config/DB boundary | KBN-101-00 | `packages/config/src/mosaic-config.ts`, `.spec.ts`; `packages/db/src/client.ts`, `defaults.ts`, relevant DB tests; `apps/gateway/src/database/database.module.ts` and tests | Typed split DTO/config, authenticated TLS/CA validation, no production defaults/fallback, no PostgreSQL startup migration, readiness/identity verifier seam. | +| `KBN-101-02` storage migration separation | KBN-101-01 | `packages/db/drizzle.config.ts`; `packages/storage/src/adapters/postgres.ts`, `.spec.ts`; `packages/storage/src/{factory,types,cli,migrate-tier,tier-detection}.ts` and focused tests; `apps/gateway/src/main.ts` and startup tests; `packages/mosaic/src/commands/fleet-backlog.ts` and tests | Storage adapter/runtime cannot migrate on Gateway startup; Drizzle Kit/migration-capable commands use only migration DTO/URL and fail if absent; no runtime URL fallback. Replace all runtime `CREATE EXTENSION` probes with read-only extension/catalog checks; prohibit PostgreSQL fleet-backlog auto-migration or route it to the explicit migration command. | +| `KBN-101-03` role foundation + declarative snapshot | KBN-101-00, KBN-101-01 | `packages/db/src/{migrate,index}.ts`, `schema.ts`, `packages/db/drizzle/**`, migration tests, generated Drizzle snapshot/journal | Platform/schema ownership boundary, baseline grants/default privileges/function-execute revocation, ordered hash-complete PostgreSQL migration runner/journal repair, lock/version behavior; clean-PG proof every shipped hash applies exactly once; generated declarations and snapshot exact consistency. It creates no future KBN immutable relation. | +| `KBN-101-04` installer/wizard/config migration | KBN-101-01 | `packages/mosaic/src/stages/gateway-config.ts`, `.spec.ts`, relevant wizard/integration tests; `tools/install.sh` only if it owns emitted variables | Production-like installer/config output stores no PostgreSQL DSN in generated `.env` or `mosaic.config.json`; it records only non-secret deployment/Vault references and accepts injected runtime variables. Tests prove neither runtime nor migration URL is written, echoed, or logged; production-like config loading rejects persisted/config-file DSN fallback. | +| `KBN-101-05` compose/Portainer deployment handoff | KBN-101-00, KBN-101-01, KBN-101-03 | `docker-compose.yml`, `docker-compose.federated.yml`, `deploy/portainer/federated-test.stack.yml`, corresponding deployment docs/runbooks | Dedicated migration phase and secret binding boundary; runtime receives non-owner only. | +| `KBN-101-06` CI/test topology | KBN-101-02, KBN-101-03 | `.woodpecker/ci.yml`, CI-only test helpers, targeted integration tests | Ephemeral owner/migrator/runtime identities; migration then runtime grant/denial evidence; no production credential. | +| `KBN-101-07` Vault/runbook/observability | KBN-101-00, KBN-101-04, KBN-101-05, KBN-101-06 | deployment/Vault/runbook/operations docs and approved observability configuration only | Provision/rotation/incident/rollback instructions, redaction and handoff evidence. | +| `KBN-101-08` foundation certification gate | KBN-101-00…07 | integration evidence only; no opportunistic source scope | Independent security review and deployed runtime role/schema-boundary certificate, including platform-bootstrap fresh/existing transition evidence; no immutable KBN table claim before KBN-100. | +| `KBN-101-09` post-KBN-100 certification gate | KBN-100, KBN-101-08 | integration evidence only; no opportunistic source scope | Real deployed-role INSERT/SELECT and UPDATE/DELETE-denial evidence for every KBN-100 immutable relation; independent security/Ultron approval. | + +`KBN-100` starts only after `KBN-101-08` passes. It must rebase on the resulting main head, restore Drizzle generated declaration/snapshot/journal consistency, and keep its procedural immutable-table grant/trigger/backfill additions bounded to KBN-100’s schema migration ownership. It may not create roles, passwords, Vault entries, deployment secrets, or mutate environment wiring. `KBN-101-09` performs real deployed-role certification after those relations exist and must pass before KBN-105. + +## 8. Threat, tests, and acceptance traceability + +| Threat / failure | Frozen control | Required evidence | +| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Gateway role owns tables or runs DDL | Separate runtime/migration URLs; no runtime DDL path, including extension probes and fleet-backlog; owner/migrator checks | Runtime startup tests prove migration/extension calls impossible; deployed runtime catalog certificate. | +| Missing migration secret falls back to runtime URL | Explicit migration DTO requires `DATABASE_MIGRATION_URL` | Unit/integration negative proving absent migration URL fails before connection/DDL. | +| Replica migration race | Dedicated phase plus advisory lock; replicas readiness-only | Concurrent migration invocation: one lock holder, other `DATABASE_MIGRATION_LOCKED`; replicas do no DDL. | +| Role escalation / unsafe grant | Inherited runtime capability with SET/ADMIN denied; no owner membership; catalog privilege preflight fails closed | Superuser/CREATEROLE/owner/SET ROLE/TEMP/ledger/function-EXECUTE mismatch negatives. | +| Evidence rewrite/deletion | KBN-100 immutable grants plus `RESTRICT` FKs | After KBN-100, real `mosaic_runtime` can INSERT/SELECT and receives permission denial for UPDATE/DELETE on every listed relation; owner-only administrative path separately audited. | +| Schema drift / partial migration | Hash-complete ordered ledger fingerprint readiness | Clean-PG every-hash-once, missing/unknown/out-of-order/partial ledger, and stale replica tests fail unready. | +| Dsn/credential leakage / on-path attack | DTO boundary, redaction rules, secret separation, authenticated TLS | Error/log/OTEL snapshots contain no URL/user/host/password; runtime/migrator reject missing/unverified CA, insecure sslmode, and host mismatch; rotation test preserves redaction. | +| Rollback reintroduces owner runtime | Ordered rollback and secret binding controls | Rollback rehearsal verifies restored runtime gets only runtime URL and remains non-owner. | + +| Acceptance criterion | Evidence needed before KBN-101 closes | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| AC-K101-01 Split connection contract | Exact variable/DTO tests for local, standalone, federated; no production fallback/default. | +| AC-K101-02 Explicit migration/readiness | Clean PostgreSQL every-hash-once runner/journal test; dedicated migration command/lock/version tests; Gateway/replica non-migration test. | +| AC-K101-03 Least-privilege PostgreSQL roles | Catalog assertions for ownership, inherited-only capability with SET/ADMIN denial, search path, ledger read-only grant, TEMP/function-EXECUTE denial, grants/default privileges, unsafe attributes. | +| AC-K101-04 Immutable KBN privilege proof | `KBN-101-09`, after KBN-100, proves deployed `mosaic_runtime` INSERT/SELECT allowed and UPDATE/DELETE denied for every immutable relation. | +| AC-K101-05 N-1 and recovery | Single-URL compatibility diagnostic; staged rollout and rollback rehearsal with no production mutation in this planning card. | +| AC-K101-06 Secrets/operations | Vault/injection/rotation/redaction/runbook review, health/metrics codes, operator failure paths. | +| AC-K101-07 Delivery integrity | One-card/one-PR DAG; strict contract/docs/link checks; independent author≠reviewer security review; terminal-green CI. | + +## 9. Non-goals and residual authority + +KBN-101 planning does not execute a production role change, create Vault paths, change compose/CI/installer files, alter database objects, or certify any deployed identity. It does not replace KBN-100’s data migration, immutable retention design, tenant constraints, or KBN-105 endpoint freeze. A database superuser or break-glass operator remains outside application containment and requires separately audited platform controls, backup/audit evidence, and drills. diff --git a/docs/native-kanban-sot/SHARED-CONTRACT.md b/docs/native-kanban-sot/SHARED-CONTRACT.md index f1c1b76..341c112 100644 --- a/docs/native-kanban-sot/SHARED-CONTRACT.md +++ b/docs/native-kanban-sot/SHARED-CONTRACT.md @@ -1,14 +1,22 @@ # Native Kanban/SOT — Remediated Shared Contract v1 -**Status:** CONTROL-PLANE SI-001 AMENDMENT AUTHORIZED; prior KCR-001–016 independent-review GO retained; rc.4 requires independent schema/SecReview before KBN-100 -**Version:** 1.0.0-rc.4 -**Date:** 2026-07-14 +**Status:** CONTROL-PLANE rc.5 DB role/connection amendment authorized; prior KCR-001–016 and rc.4 SI-001 independent-review decisions retained; KBN-101 foundation certification precedes KBN-100 and real immutable-operation certification precedes KBN-105 +**Version:** 1.0.0-rc.5 +**Date:** 2026-07-15 **Change authority:** Mosaic control plane/Jason only **SI-001 amendment authority:** `web1:mosaic-100` control-plane decision under issue #753 ## Amendment record -### 1.0.0-rc.4 — KBN010-SI-001 +### 1.0.0-rc.5 — KBN-101 role/connection split + +- **Choice:** PostgreSQL `standalone` and `federated` runtime uses `DATABASE_URL` only as a non-owner `mosaic_runtime` login; an explicit migration phase uses `DATABASE_MIGRATION_URL` only as `mosaic_migrator`, which `SET ROLE`s to non-login `mosaic_schema_owner` for DDL. Local PGlite remains an explicit embedded exception. +- **No fallback / no startup DDL:** missing migration URL fails the migration phase; it never falls back to runtime URL/default/config. Gateway replicas do not run migrations. An advisory-locked migration phase verifies the exact ordered Drizzle ledger fingerprint before replicas may become ready. +- **Privilege model:** non-login `mosaic_platform_database_owner` is outside application paths; `mosaic_schema_owner` owns only application/ledger schemas. `mosaic_runtime` has only `mosaic_runtime_capability`, owns no object/schema, cannot assume owner/migrator, has no TEMPORARY privilege, has only read access to the Drizzle ledger, and must fail startup if effective identity, unsafe attributes, authenticated TLS, search path, schema version, grants, or immutable relation privileges differ from the frozen contract. `task_events`, `artifacts`, `task_checkpoints`, `task_checkpoint_artifacts`, and `approval_decision_artifacts` grant runtime only INSERT/SELECT; KBN-100 retains RESTRICT/no-cascade semantics. +- **Non-effect:** rc.4 SI-001 candidate-key/FK order and all KCR-001–016 tenancy, SOT, proposal-audit, approval, fence, recovery, no-cascade, endpoint, and wire invariants are unchanged. This amendment neither creates roles/secrets nor changes production deployment. +- **Gate:** KBN-101’s role/schema-boundary foundation certificate, Vault/redaction/rotation, N-1/rollback, and independent security GO are mandatory before KBN-100. After KBN-100 creates the immutable relations, KBN-101 real deployed-role immutable-operation certification plus Ultron GO is mandatory before KBN-105; synthetic test-role success alone is insufficient. Exact implementation detail is normative in [`KBN-101-DB-ROLE-SPLIT.md`](./KBN-101-DB-ROLE-SPLIT.md). + +### 1.0.0-rc.4 — KBN010-SI-001 (preserved) - **Choice:** add the explicitly named, non-partial unique candidate key `missions_workspace_id_uidx` on `missions(workspace_id, id)` and retain `missions_workspace_project_id_uidx` on `(workspace_id, project_id, id)`. - **Rationale:** mission `id` remains globally unique, while the composite candidate key makes the frozen tenant-safe generic mission relations valid. `artifacts` and `approval_decisions` are polymorphic exactly-one-target records and do not consistently carry `project_id`; widening both children would unnecessarily broaden v1 and its target semantics. @@ -19,7 +27,7 @@ ## 1. Authority -Concrete contracts are the four `contracts/*.v1.ts` files. PostgreSQL/current-main Drizzle is the sole writable SOT. Public health, Valkey, files, exports, providers, browser state, and outage notes cannot authorize/reconstruct writes. Mechanical Coordinator is non-LLM with no scope/gate/certification/merge authority. Certifier is final independent gate with no merge authority. No feature lane starts until this canon merges and the KBN-010/KBN-105 prerequisites are satisfied. +Concrete contracts are the four `contracts/*.v1.ts` files. PostgreSQL/current-main Drizzle is the sole writable SOT. In PostgreSQL standalone/federated deployments, KBN-101 rc.5 role/connection separation is a precondition to schema implementation and certification. Public health, Valkey, files, exports, providers, browser state, and outage notes cannot authorize/reconstruct writes. Mechanical Coordinator is non-LLM with no scope/gate/certification/merge authority. Certifier is final independent gate with no merge authority. No feature lane starts until this canon merges and the KBN-010/KBN-105 prerequisites are satisfied. ## 2. Health proof and exact failures @@ -104,7 +112,7 @@ The candidate key is intentionally redundant with globally unique `missions.id`, ### 5.3 New audit/proposal DDL order -KBN-100 migration DDL must execute in this order: +KBN-100 migration DDL may begin only after KBN-101 foundation role/schema-boundary certification. It runs in the explicit migrator/owner phase—not Gateway startup—and its generated Drizzle declaration/snapshot/journal must be mutually consistent. It must execute in this order: 1. create `task_events` and its unique `(workspace_id, id)` key; 2. create `change_proposals` with nullable acceptance-event ID and required submission-event ID; @@ -243,7 +251,7 @@ KBN-115/coder2 owns `packages/config/src/recovery-posture.ts`, tests, and recove ## 9. Integration, security, and hold -Required release evidence includes empty/prod/partial/rollback/N-1 migration tests; cross-workspace and same-workspace wrong-project negatives; active-membership owners/principals; proposal inertness/normal acceptance; exact failure mapping; concurrent monotonic bigint fences; relational lease/checkpoint/evidence mismatch; immutability privileges/RESTRICT; recovery validation/mechanism evidence; endpoint registry alignment; accessible web journeys; author≠reviewer; mandatory SecReview; final Certifier pass/no merge authority. +Required release evidence includes KBN-101 foundation role/schema-boundary and post-KBN-100 real immutable-operation deployed-role certificates (not synthetic roles), empty/prod/partial/rollback/N-1 migration tests; cross-workspace and same-workspace wrong-project negatives; active-membership owners/principals; proposal inertness/normal acceptance; exact failure mapping; concurrent monotonic bigint fences; relational lease/checkpoint/evidence mismatch; immutability privileges/RESTRICT; recovery validation/mechanism evidence; endpoint registry alignment; accessible web journeys; author≠reviewer; mandatory SecReview; final Certifier pass/no merge authority. ### 9.1 SI-001 amendment gate and #757 boundary diff --git a/docs/native-kanban-sot/TASKS.md b/docs/native-kanban-sot/TASKS.md index d868545..3d37b85 100644 --- a/docs/native-kanban-sot/TASKS.md +++ b/docs/native-kanban-sot/TASKS.md @@ -43,12 +43,14 @@ Shared roots, package exports/manifests, lockfiles, and generated artifacts are ```text KBN-000 canon remediation -> KBN-010 threat/auth/constraint-impact gate (MUST COMPLETE) - -> KBN-100 schema + concrete N-1 migration implementation - ├─ KBN-105 exact endpoint/DTO/error/registry freeze (SERIAL) - │ ├─ KBN-110 domain + Gateway + MCP server implementation - │ ├─ KBN-120 CLI/projection implementation [coder4 first] - │ └─ KBN-130 web MVP implementation - └─ KBN-115 recovery parser/mechanism slice [coder2 lane-serial] + -> KBN-101 foundation role/schema-boundary certificate (SERIAL) + -> KBN-100 schema + concrete N-1 migration implementation + ├─ KBN-101 post-KBN-100 deployed-role immutable-operation certificate (SERIAL) + │ -> KBN-105 exact endpoint/DTO/error/registry freeze (SERIAL) + │ ├─ KBN-110 domain + Gateway + MCP server implementation + │ ├─ KBN-120 CLI/projection implementation [coder4 first] + │ └─ KBN-130 web MVP implementation + └─ KBN-115 recovery parser/mechanism slice [coder2 lane-serial] KBN-110 + KBN-120 + KBN-130 + KBN-115 -> KBN-140 P1 integration/SIT -> KBN-200 pure decision engine [coder4 after KBN-120] @@ -64,7 +66,7 @@ KBN-310 + KBN-320 -> KBN-340 owner-gated cutover/stabilization ``` -No consumer implementation begins before KBN-105. No schema work begins before KBN-010 completes. The coder4 order is always KBN-120 → KBN-200 → KBN-300 → KBN-320. +No consumer implementation begins before KBN-105. No schema work begins before KBN-010 completes and the KBN-101 foundation role/schema-boundary certificate passes; the real immutable-operation certificate follows KBN-100 and blocks KBN-105. The coder4 order is always KBN-120 → KBN-200 → KBN-300 → KBN-320. ## 4. P0 — Canon, threat gate, schema, and exact API freeze @@ -91,6 +93,17 @@ No consumer implementation begins before KBN-105. No schema work begins before K - **Contract surfaces:** schema constraints, health proof, exact errors, command-family authorization. - **Evidence:** signed constraint-impact matrix; no unresolved schema-impact finding; SecReview pass. +### KBN-101 — PostgreSQL runtime/migration role split and deployed-role certification + +- **Status:** IN PROGRESS — issue [#771](https://git.mosaicstack.dev/mosaicstack/stack/issues/771); this documentation contract is frozen, implementation remains held. +- **Owner:** Mos integration control plane; independently reviewed by security/Ultron. +- **Mode:** SERIAL foundation certificate blocks KBN-100; its post-KBN-100 real immutable-operation certificate blocks KBN-105. +- **IN:** Exact `DATABASE_URL` non-owner runtime versus `DATABASE_MIGRATION_URL` owner/migrator connection contract; `DATABASE_TLS_CA_CERT_PATH` authenticated-TLS contract; PGlite exception; no production/federated migration fallback; explicit migration phase/advisory lock/exact ledger readiness; platform/schema-owner/migrator/runtime roles; ownership, membership, schema/search-path/TEMP/ledger-read/default privilege and immutable grant proof; N-1 rollout/rollback; Vault/rotation/redaction/observability/operator runbooks; deployment-specific handoff; one-card/one-PR implementation DAG. +- **OUT:** Production mutation in this planning card; KBN-100 tables/data backfill; application API behavior; KBN-105 route/DTO freeze. +- **Depends on:** KBN-010 completed. +- **Contract surfaces:** [`KBN-101-DB-ROLE-SPLIT.md`](./KBN-101-DB-ROLE-SPLIT.md); `SHARED-CONTRACT.md` rc.5 amendment. +- **Evidence:** foundation: exact variable/DTO/TLS and no-fallback tests; clean-PostgreSQL every-hash-once migration-runner/journal proof; runtime cannot invoke migrations; single non-blocking migration lock/replica readiness; catalog ownership/inherited-capability-with-SET/ADMIN-denied/unsafe-attribute/TEMP/ledger-read/function-EXECUTE/default-grant/search-path checks; N-1 rollout/rollback rehearsal; Vault/redaction/rotation/operator evidence; independent author≠reviewer security GO. Post-KBN-100: real deployed non-owner INSERT/SELECT and UPDATE/DELETE denial for immutable event/artifact/evidence relations plus Ultron GO. + ### KBN-100 — Unified Drizzle schema and concrete N-1 migration - **Owner:** **coder2**. @@ -98,7 +111,7 @@ No consumer implementation begins before KBN-105. No schema work begins before K - **Exclusive files:** `packages/db/src/schema.ts`, `packages/db/drizzle/**`, DB tests. - **IN:** All frozen tables/joins/enums; workspace/project-congruent constraints; owners/principals; tags/archive; change proposals with both workspace-aware task-event composite FKs and frozen event-before-proposal DDL order; assignment approvals; durable execution/quarantine; monotonic bigint fence; exact checkpoint/evidence joins; RESTRICT/immutability; concrete current-main expand/backfill/switch/contract map. - **OUT:** Repositories, Gateway, Coordinator behavior, UI, importer. -- **Depends on:** **KBN-010 completed**. +- **Depends on:** **KBN-010 completed and KBN-101 foundation role/schema-boundary certificate PASS**. KBN-100 is blocked until both are terminal; it rebases on KBN-101 main, restores generated Drizzle declaration/snapshot/journal consistency, and confines procedural immutable-table grant/trigger/backfill work to its schema ownership. Its new relations are then subject to KBN-101 post-KBN-100 deployed-role certification. - **Contract surfaces:** `kanban-schema.v1.ts`; SHARED-CONTRACT current-main delta map. - **Evidence:** reviewed SQL; empty/prod-shape/partial-resume/rollback tests; N-1 app safety; legacy columns remain declared; workspace/project mismatch negatives; proposal event-FK missing/foreign-workspace tests; one active lease; monotonic fence; parent-delete RESTRICT; immutability privileges; SecReview. @@ -109,7 +122,7 @@ No consumer implementation begins before KBN-105. No schema work begins before K - **Exclusive files:** canonical endpoint-registry/DTO contract docs; no implementation. - **IN:** Exact routes and methods from SHARED-CONTRACT §8; request/success/error fields; status codes; pagination/filter/revision envelopes; idempotency/expected-version headers/fields; proposal commands; health proof exclusion from public DTOs; MCP tool-to-route map. - **OUT:** Controller/service/client implementation. -- **Depends on:** KBN-100. +- **Depends on:** KBN-100 and KBN-101 post-KBN-100 deployed-role immutable-operation certification PASS. - **Contract surfaces:** health/error unions; schema IDs/statuses; Gateway DTO freeze. - **Evidence:** every FE/CLI/MCP call maps 1:1 to a route; 503/502-504/409 non-cross-map fixtures; contract digest published. @@ -251,13 +264,15 @@ No consumer implementation begins before KBN-105. No schema work begins before K ## 8. Consistent USC wave schedule -| Wave | coder2 | coder3 | coder4 | coder5 | -| ---- | ------------------------- | -------------------------------------- | ------------------------------ | ------------------------------ | -| 0 | Wait | **KBN-010** | Wait | Wait | -| 1 | **KBN-100** | Review constraint implementation | Wait | Wait | -| 2 | **KBN-115** after KBN-100 | **KBN-105** exact freeze, then KBN-110 | **KBN-120** only after KBN-105 | **KBN-130** only after KBN-105 | -| 3 | Review support | Finish KBN-110 | **KBN-200 after KBN-120** | Finish KBN-130 | -| 4 | — | **KBN-210 after KBN-200** | Review/support | **KBN-220 after KBN-210 DTOs** | -| 5 | — | P2 remediation | **KBN-300 then KBN-320** | **KBN-310** | +| Wave | coder2 | coder3 | coder4 | coder5 | +| ---- | ----------------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------ | ------------------------------ | +| 0 | Wait | **KBN-010** | Wait | Wait | +| 0.5 | Wait | **KBN-101 foundation** Mos-controlled role/connection contract and certificate | Wait | Wait | +| 1 | **KBN-100** after KBN-101 foundation PASS | Review bounded schema/grant implementation | Wait | Wait | +| 1.5 | Certification support | **KBN-101 post-KBN-100 deployed-role immutable-operation certificate**, then KBN-105 | Wait | Wait | +| 2 | **KBN-115** after KBN-100 | **KBN-105** exact freeze, then KBN-110 | **KBN-120** only after KBN-105 | **KBN-130** only after KBN-105 | +| 3 | Review support | Finish KBN-110 | **KBN-200 after KBN-120** | Finish KBN-130 | +| 4 | — | **KBN-210 after KBN-200** | Review/support | **KBN-220 after KBN-210 DTOs** | +| 5 | — | P2 remediation | **KBN-300 then KBN-320** | **KBN-310** | Mos alone releases slices and lifts the build hold after independent re-review GO. diff --git a/docs/scratchpads/771-kbn101-db-role-split.md b/docs/scratchpads/771-kbn101-db-role-split.md new file mode 100644 index 0000000..b1ba9b0 --- /dev/null +++ b/docs/scratchpads/771-kbn101-db-role-split.md @@ -0,0 +1,74 @@ +# Scratchpad — KBN-101 DB runtime/migration role split (#771) + +- **Branch:** `docs/771-kbn101-db-role-split` +- **Base:** `main` `e9c4aa3` +- **Scope:** planning/documentation only; authorized files are PRD, Native Kanban task/shared/index docs, sitemap, this scratchpad, and the new KBN-101 contract. +- **Explicit exclusions:** source/runtime/config/deployment/secret/migration/compose/CI/lock/package/KBN-100 branch edits; no production mutation. + +## Objective + +Freeze an implementation-ready PostgreSQL role/connection split so the Gateway uses a non-owner runtime identity and only a dedicated migration phase uses an owner/migrator identity. Make real deployed-role certification—not synthetic role tests—a serial prerequisite of KBN-100 and KBN-105. + +## Intake and current-state evidence + +- Mission MVP is active; W3 Native Kanban/SOT is planning-complete. The task state shows KBN-010 as the predecessor and KBN-100 as the current schema slice. +- Current branch started at `e9c4aa3`; `.mosaic/orchestrator/{mission.json,session.lock}` were already runtime-modified and remain untouched. +- `packages/db/src/client.ts`, `migrate.ts`, and `drizzle.config.ts` resolve one `DATABASE_URL` (with default fallback). `packages/storage/src/adapters/postgres.ts` calls `runMigrations(this.url)`. +- `apps/gateway/src/database/database.module.ts` calls `storageAdapter.migrate()` at startup for PostgreSQL; this is the owner-runtime defect to remove in KBN-101 implementation. +- `packages/config/src/mosaic-config.ts`, installer wizard, local/federated compose, Portainer test stack, and `.woodpecker/ci.yml` currently expose one URL. PGlite has an existing explicit local migration path. +- Current KBN contract requires immutable events/checkpoints/artifacts/evidence, `RESTRICT`, and KBN-100 generated Drizzle consistency. It did not establish a deployable runtime identity split. + +## Frozen decisions + +1. `DATABASE_URL` is the non-owner runtime URL; `DATABASE_MIGRATION_URL` is migration-only. Both are required in their respective PostgreSQL phases; PGlite is the explicit local exception; migration never falls back to runtime/default/config URL. +2. PostgreSQL Gateway runtime never auto-runs migration/DDL. Dedicated migrator uses `pg_try_advisory_lock(hashtext('mosaic-schema-migration-v1'))`; replicas only check exact ordered Drizzle-ledger readiness and fail closed. +3. Roles are non-login `mosaic_platform_database_owner`, non-login `mosaic_schema_owner`, login/noinherit `mosaic_migrator`, non-login `mosaic_runtime_capability`, and login/inherit `mosaic_runtime`. Runtime inherits only its capability role with SET/ADMIN denied, has no owner/migrator membership, no unsafe attributes/ownership/DDL authority, and an explicit trusted search path. +4. Runtime gets mutable DML only as needed, but INSERT/SELECT only on `task_events`, `artifacts`, `task_checkpoints`, `task_checkpoint_artifacts`, and `approval_decision_artifacts`. KBN-100 still enforces RESTRICT/no-cascade. +5. Startup verifies effective role/ownership/attributes/inherited capability/TEMP/function-execute/ledger grants/search path/immutable denials/schema fingerprint without DSN exposure. It also requires authenticated CA/hostname-verified TLS. Stable sanitized errors and redaction rules are required. +6. N-1 retains single runtime URL only as a non-certified compatibility release; staged role provisioning/migration/runtime deployment then enforces the split. Rollback never injects migration URL into Gateway. +7. Vault target paths, rotation, deployment injection, CI, installer, compose, Portainer, and observability are separate one-card/one-PR handoffs. The migration-only file manifest includes `packages/db/drizzle.config.ts`; KBN-101 repairs the known PostgreSQL runner/journal ordering defect and proves a clean database applies every hash once before its foundation certificate. No application migration creates roles/passwords or hardcodes credentials. +8. KBN-101 foundation merges/certifies first. KBN-100 then rebases, restores Drizzle declaration/snapshot/journal consistency, and bounds procedural immutable-table grant/trigger/backfill work to its own slice. Because those immutable relations do not exist until KBN-100, KBN-101’s real deployed-role immutable-operation certificate follows KBN-100 and is the serial gate before KBN-105. + +## Assumptions + +- `standalone` and `federated` are all current PostgreSQL production-like modes; a future PostgreSQL tier inherits this contract unless versioned otherwise. +- Deployment will support a dedicated migration Job/one-shot command. A target that cannot run it cannot receive production/federated KBN certification. +- Canonical Vault target paths require deployment-owner verification before provisioning; the planning document does not claim they already exist. + +## Documentation produced + +- `docs/PRD.md`: bounded KBN-101 requirements and acceptance criteria. +- `docs/native-kanban-sot/KBN-101-DB-ROLE-SPLIT.md`: normative rc.5 implementation, threat, migration/rollback, evidence, and exact file DAG contract. +- `docs/native-kanban-sot/SHARED-CONTRACT.md`: rc.5 amendment preserving rc.4. +- `docs/native-kanban-sot/TASKS.md`: KBN-101 inserted before and blocks KBN-100; KBN-105 held. +- Native Kanban index and root sitemap links. + +## Validation plan + +1. Prettier only for changed Markdown. +2. Markdown link target/check checks scoped to modified docs. +3. Strict contract check with `pnpm exec tsc --noEmit -p docs/native-kanban-sot/tsconfig.json` (the frozen TypeScript contracts remain unchanged). +4. Diff allowlist proves only authorized documentation files changed, apart from pre-existing Mosaic runtime state. +5. Independent documentation/security self-review: role escalation, fallback, startup DDL, schema readiness, grants/default privileges, immutable tables, secret leakage, deployment and KBN-100 boundaries. + +## Review corrections + +Independent Codex review found two blockers and security review found two medium defects; all were remediated in the frozen contract: + +1. Split the KBN-101 certificate into a foundation role/schema-boundary certificate (before KBN-100) and real immutable-operation certificate (after KBN-100, before KBN-105). This preserves the requested KBN-100 block without requiring evidence for tables not yet created. +2. Removed the legacy owner-runtime exception. N-1 compatibility preserves variable/config shape only; the KBN-101 runtime refuses owner/migrator identity and current single-URL installs remain on their previous release until role cutover. +3. Introduced `mosaic_platform_database_owner` as a separate non-login platform role. `mosaic_schema_owner` owns application/ledger schemas only, not the database and has no database CREATE/ALTER/extension authority. +4. Replaced blocking `pg_advisory_lock` with `pg_try_advisory_lock` and the deterministic `DATABASE_MIGRATION_LOCKED` failure. +5. Review also flagged active `.mosaic/orchestrator` state. It was pre-existing launcher state and remains unstaged/uncommitted. +6. Second review added `packages/db/drizzle.config.ts` to the migration-only slice, mandates `DATABASE_MIGRATION_URL` with a missing-variable negative, grants runtime only `USAGE` plus `SELECT` on `drizzle.__drizzle_migrations`, and verifies/revokes its ledger writes. +7. Security review added `DATABASE_TLS_CA_CERT_PATH` / `DatabaseTlsConfigDto` with authenticated TLS and hostname/CA verification in production-like modes, explicit database `TEMPORARY` revocation/catalog denial testing, and default-PUBLIC function EXECUTE revocation with SECURITY DEFINER prohibited by default. +8. Final review corrected the runtime login to inherit only its capability role with SET/ADMIN denied, and moved the known hash-complete migration-runner/journal repair into KBN-101-03 before the foundation certificate. +9. Final manifest review added all live runtime DDL paths (`packages/storage/src/tier-detection.ts`, Gateway startup, and `fleet-backlog`) to KBN-101-02, requiring read-only extension probes and no PostgreSQL runtime auto-migration. It also requires KBN-101-04 to stop persisting either DSN into generated `.env`/`mosaic.config.json`, using only Vault/deployment references and injected variables. +10. Provisioning review separated the external privileged platform bootstrap actor from the NOCREATEDB database-owner role and added KBN-101-00. That IaC/bootstrap card owns fresh/existing database role/ownership/grant/Vault transition evidence and is a foundation-certificate dependency. + +## Results + +- `pnpm exec prettier --check` on every authorized Markdown file: PASS. +- Markdown link and whitespace checker on all seven authorized Markdown files: PASS. +- `pnpm exec tsc --noEmit -p docs/native-kanban-sot/tsconfig.json`: PASS (frozen strict contracts unchanged). +- Codex code/security review rerun is required after the above remediation; no source code or unauthorized files were changed.