60 KiB
Native Kanban/SOT — Remediated Shared Contract v1
Status: CONTROL-PLANE rc.15 KBN-101 current-document runner/legacy-CI authority remediation complete; awaiting independent exact-head re-review. Prior KCR-001–016 and rc.4 SI-001 decisions retained; KBN-101 foundation certification precedes KBN-100 and real immutable-operation certification precedes KBN-105
Version: 1.0.0-rc.15
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.15 — Held runner and legacy-CI authority closure
- Held runner only: Current operator documents cannot advertise
mosaic-db-migrator --run|--verifyas executable. The sole passing future form is oneHeld future procedureMarkdown section, bounded through its next equal-or-higher heading, that explicitly says non-operative/no-current-command-authority, names KBN-101-00/-03/-05, and preserves external bootstrap → TLS/roles →mosaic-db-migrator --run→mosaic-db-migrator --verify→ Gateway/Compose readiness. Any runner hit outside that section fails before inventory/ownership/status masking. - PGlite/current-CI boundary: Fleet backlog current behavior is PGlite-only; PostgreSQL CLI/runner authority remains held until activation. README classifies the checked-in direct
db:migrateCI job as active legacy N-1, uncertified, non-authorizing as an operator route, and pending KBN-101-06 removal; it is a known direct-DDL exception against an isolated disposable CI database, not approved ordinary behavior. The -06 fixture asserts every required status term and rejects ordinary-authority presentation. - Non-effect: prior Compose, production-secret, attestation, pgvector, manifest, lock, TLS, activation, and serial-gate closures remain unchanged.
1.0.0-rc.14 — Current Compose and production-secret route closure
- Current developer boundary:
README.mdanddocs/guides/dev-guide.mdpermit only in-process PGlite data-layer work and explicitly selected non-PostgreSQL Compose services. Gateway/Web local start is held because the current unguarded loader can inherit a daemon/project PostgreSQL DSN and reach runtime DDL; KBN-101-02 must reject it before connection. The current PostgreSQL Compose mount is legacy/unqualified; PostgreSQL and federated activation are held until KBN-101-00/-03/-05 and then follow external bootstrap → TLS/roles → runner--run→--verify→ Gateway/Compose readiness. - Production boundary:
docs/guides/deployment.mdis non-operative until the KBN-101-05 renderer-backed process-exec orLoadCredentialinterface exists. It contains no active production environment-file, monorepo auto-load, credential export/argv, or secret-activation lifecycle route; future units must preserve generation-pinned Vault consumer isolation. - Unmaskable semantic negatives: -06 fails the exact former README/dev/deployment Compose-first sequences and every production
.env,EnvironmentFile=, credential export/argv, or restart-as-secret-activation fixture before owned/status/normative classification. The held PGlite/non-PostgreSQL route and future ordered activation are the only passing fixtures.
1.0.0-rc.13 — Federation-MILESTONES indirect-startup closure
- Complete operator inventory:
docs/federation/MILESTONES.mdis exclusively KBN-101-07 and an exact KBN-101-06operator-documentstatus-onlyrecord. Its formerpgvector extension installed + verified on startupwording is superseded and forbidden; it authorizes no current DDL, Compose/init, or runtime/startup path. - Unmaskable semantic negative: before inventory disposition, the scanner fixture proves that exact former wording fails. The only passing status-only sequence is external bootstrap → TLS/roles →
mosaic-db-migrator --run→mosaic-db-migrator --verify→ Gateway readiness.
1.0.0-rc.12 — Deployable importer generation and indirect-DDL-route closure
- Authenticated generation: KBN-101-05 owns one canonical Vault KV-v2 importer record,
secret-{env}/mosaic-stack/database/importerkeyurl, with its version taken only from the same successfuldata.metadata.versionresponse. Value plus provider version are one generation, never inferred from DSN bytes. The renderer creates separate immutable0400URL/version copies for migrator10003:10003binding-only access and importer10002:10002access; it uses fsync/atomic generation replacement for Compose and distinct versioned secret/config references for Swarm, so deployment cannot mix generations. - Bounded consumers: importer alone receives its URL/version, CA at
DATABASE_TLS_CA_CERT_PATH, pinned public key, and read-only attestation; migrator receives its own migration URL/CA, the URL/version only for no-connect/no-export binding, attestation output, and the root-wrapper-only private key. Safe fd open/fstat/digest/zeroize/close semantics, a privileged producer-only-to-importer-only attestation handoff controller (verify, exact-byte copy, fsync/atomic rename,10002:100020400seal, then importer start), no shared writable file, no logging/oracle, provider rotation/revocation, CA/mount, consumer-isolation, and symlink/hardlink/owner/mode/TOCTOU negatives are mandatory. - Indirect-DDL closure:
docs/federation/SETUP.mdis non-operative until KBN-101 activation and documents only external bootstrap → TLS/roles → runner--run→--verify→ Gateway readiness. The -06 scanner performs unsuppressible semantic checks for automatic first-boot/startup extension/schema/migration language, Compose-up-before-runner, and init-script authority; the former SETUP wording fails and the remediated sequence passes.
1.0.0-rc.11 — Target-bound importer attestation and exhaustive operator-route closure
- Target-bound proof: trusted
mosaic-db-migrator --verifynow produces the atomic, credential-freemigrate-target.v1.jsonJCS/Ed25519 artifact from a runner-only root-owned signing-key reference; the importer receives only pinned public verification keys and the artifact. Its signed v1 fields bind issued/expiry/nonce, exact secret version and SHA-256 of high-entropy target-file bytes, canonical TLS host/port/database, CA/SPKI, PostgreSQL system identifier/database OID, expected importer role, manifest/schema fingerprints, and producer invocation/build/image/correlation. No DSN, username, password, credential bytes, or signing key enters the artifact, importer, runtime, logs, or output. - Fail-closed importer:
mosaic storage migrate-tierrequires both--target-url-file /run/secrets/mosaic-migrate-target-urland--target-attestation-file /run/mosaic-attestations/migrate-target.v1.json. Before target connection it validates files, signature/key/expiry/replay, secret version/digest, TLS/CA/role/manifest bindings and opens/digests/connects from the same in-memory URL bytes. After verified TLS but before transaction/DML it matches server ID, database OID,current_user, CA/SPKI, and manifest/schema; failure distinguishes zero connection from connection/zero-DML and DDL remains impossible. Rotation overlap/revocation, atomic rename, replay cache, secret rotation invalidation, and wrong/substituted/stale/tampered/file-change tests are mandatory. - Closed documentation surface: KBN-101-06 inventories every current non-normative scanner hit, including
docs/guides/user-guide.mdand status-onlydocs/federation/TASKS.md; the latter is historical and cannot authorize DDL. The legacystorage migratetier-copy syntax is unavailable.storage migrateis schema-wrapper delegation only; secure tier data copy ismigrate-tier. Exact KBN PRD/contract/shared/task paths may benormative-contractscan class but are still scanned and cannot mask executable instructions. The normative detail remainsKBN-101-DB-ROLE-SPLIT.md. - Non-effect: pgvector closure, manifest, lock, role graph, TLS, activation, and KBN-100/KBN-105 serial gates are unchanged.
1.0.0-rc.10 — PostgreSQL-valid untrusted pgvector owner and active migrate-tier closure
- Valid extension authority: PostgreSQL 17 + pgvector 0.8.2
vectoris untrusted (trustedabsent;relocatable=true), somosaic_extension_owneris exactlyNOLOGIN SUPERUSER, notNOSUPERUSER. It is dedicated solely tomosaic_extensions,vector, and owner-bearing extension members;rolcanlogin=false,rolsuper=true, zero members, no runtime credential/Vault secret, and no app-container delivery are catalog and deployment proof. An externally controlled audited bootstrap-superuser session aloneSET ROLEs for extension CREATE/UPDATE/SET SCHEMA, thenRESET ROLE; fresh and shadow paths do so, while in-place existing work requires exact pre-existingextowner. - Explicit superuser exception:
GRANT/REVOKEcannot privilege-limit a superuser. The containment is dedicated identity, no login, no membership, external control plane, audit, independent review, backup/rollback, and maintenance window—not a false least-privilege claim. Runtime, migrator, schema owner, importer, and every service role cannot assume the role or alter/update/drop/change extension membership. Managed targets without this exact role are ineligible unless a versioned provider-owned extension-owner profile is independently approved. - Active secure data-migration route:
docs/guides/migrate-tier.mdis exclusively KBN-101-07, is active rather than historical, and specifies runner-prepared/verified PostgreSQL destination plus a dedicated non-DDL importer. KBN-101-02 freezes--target-url-file /run/secrets/mosaic-migrate-target-url, never credential argv; raw--target-url,DATABASE_URLfallback, runtime owner, missing/unsafe file, wrong mode, and DDL all fail before target connection/DDL. KBN-101-06 inventory/matrix records the route and exact secure fields, then tests its finite operator-document closure. - Non-effect: manifest, lock,
mosaicapplication schema, TLS, activation, and KBN-100/KBN-105 serial gates remain unchanged. The normative detail remainsKBN-101-DB-ROLE-SPLIT.md.
1.0.0-rc.9 — KBN-101 extension-schema boundary, disjoint manifests, and scanner mechanics
- Extension schema owner:
mosaic_extension_owner, notmosaic_schema_owner, creates and ownsmosaic_extensions,vector, and extension-member objects. The external bootstrap actorSET ROLEs for fresh creation or approved-owner relocation, thenRESET ROLEs; rc.10 replaces the earlier membership wording with the PostgreSQL-valid zero-member superuser exception. Schema owner has onlyUSAGEfor legacy type resolution—never ownership,CREATE,ALTER,DROP, member change, or default-privilege authority. Runtime, migrator, and schema owner must fail catalog and direct DDL denials; shadow/resume/rollback repeat the owner/default-privilege proof. - Exclusive delivery DAG: KBN-101-00…09 now has a complete, nonoverlapping exact file/glob manifest with named tests/evidence. The runner mapping is exactly
"mosaic-db-migrator": "./dist/cli.js"and imageENTRYPOINT ["mosaic-db-migrator"];packages/storage/src/{cli,migrate-tier}.tsbelongs only to -02, and -07 is documentation only. -08/-09 own evidence paths only. -00…07 are prepared artifacts; the immutable N-1 image remains live until -08 atomic activation, so no independently deployed intermediate can bypass runtime controls. - Mechanical classifier: -06 owns the exact scanner, inventory fixture, command-matrix harness, and CI wiring. Inventory records pin path/class/owner/disposition/allowed tokens/rationale/expiry/review revision; unknown, duplicate-owner, ownerless, missing-path, invalid allowlist, and historical-category masking fail. The architecture plan's operative direct
db:migrateis replaced by sole-runner guidance rather than hidden under a historical category. - Non-effect: manifest v1, lock,
mosaicapplication-schema ownership, TLS, activation, KBN-100/KBN-105 serial gates, and all earlier canon decisions remain unchanged. The normative detail remainsKBN-101-DB-ROLE-SPLIT.md.
1.0.0-rc.8 — KBN-101 finite authority, executable runner, and pgvector-owner remediation
- Finite authority closure: KBN-101-06 classifies every current executable source/script/package bin, operator document, and deploy manifest by exact path; unclassified current hits fail. Byte-immutable historical SQL, PGlite-only routines, negative-test literals, vendored/generated artifacts, and clearly labeled historical reports are exact-path/category reviewed allowlists only.
packages/db/src/index.tsloses its publicrunMigrationsexport with a direct-import/compile negative;docs/fleet/backlog-conventions.mdanddocs/PERFORMANCE.mdlose first-use/direct-Drizzle/Gateway-startup migration instructions and carry runner/readiness route negatives. A token scan is only input to the classifier, never proof of authority. - Executable exclusive cards: KBN-101-03 alone publishes
mosaic-db-migratorfrompackages/db/package.json/src/cli.ts, ownsdocker/db-migrator.Dockerfile, and keeps{runner,config.dto,manifest,identity,tls}private, with exact--run|--verify|--help, env-only input, stable exits, and command tests. KBN-101-00 alone ownsinfra/pg-bootstrap/roles.sql,infra/pg-bootstrap/extensions.sql,infra/pg-bootstrap/README.md, plus bootstrap tests. KBN-101-05 alone ownstools/db/render-postgres-secrets.ts, renderer tests, and Compose/Portainer/Swarm/two-gateway declarations, consuming the versioned bootstrap interface. No card overlaps renderer/bootstrap/deployment ownership. - Extension-owner transition:
mosaic_extension_owneris a dedicated NOLOGIN role whose membership/credentials never reach services; the external bootstrap actor alone maySET ROLEduring bootstrap. Fresh vector and member objects retain that owner. PostgreSQL has no supported extension-owner alteration: approved-owner existing extension relocation validatespg_extension.extowner, members/schema/version and uses testedALTER EXTENSION ... SET SCHEMA; legacy runtime-owned extension fails closed to a controlled shadow database migration with backup, evidence, quiesce/final delta, atomic switch, and read-only rollback window. No catalog mutation, ownership adoption, orDROP CASCADEis permitted. Runtime/migrator/schema-owner extension ALTER/DROP/member-update denial is mandatory. - Non-effect: manifest v1, lock namespace, role/search-path, relocation/TLS/activation, KBN-100/KBN-105 serial gates, and all retained canon decisions are strengthened, not weakened. The normative detail remains
KBN-101-DB-ROLE-SPLIT.md.
1.0.0-rc.7 — KBN-101 complete current-path, relocation, and two-gateway remediation
- Finite current-path closure: static inventory and the
DATABASE_URL-only-before-connect/DDL denial matrix now explicitly include Gateway's former temporary-table pgvector test (runner-prepared persistent read/query-only fixture),docker/init-db.sqlretirement,migrate-tier.tsrunner/bootstrap-only guidance, and the active two-gateway harness. The harness is migrated, not retired:postgres-a/b → mosaic-db-migrator-a/b → gateway-a/b, each with isolated URL/CA material, verified readiness, SANs, and positive/negative TLS evidence. - Executable relocation: KBN-101-03 exclusively owns
schema.ts, Drizzle snapshots/journal/generated relocation and exact tests. All future application declarations use exportedpgSchema('mosaic'); immutable historical SQL runs only in trusted legacypublic.vectoris fixed in non-writablemosaic_extensions, with exact catalog relocatability/version eligibility, explicit type/operator qualification, catalog-class ordering, unknown-object fail-closed behavior, clean/current-public/partial/reverse rollback tests, and an N-1 release order. - Bound deployment ownership:
mosaicstack/stackKBN-101-00/05 owns current Compose, Portainer, two-gateway, bootstrap renderer/templates, UID/GID declarations, and rendered validation. Gateway is fixed to10001:10001; PostgreSQL UID/GID is image-inspected and frozen only after digest pinning. Exact secret paths, atomic renderer behavior, Compose/Swarm targets/modes, Gateway/PostgreSQL leaf separation, and two-pair TLS failure evidence are required. Mosaic deployment control plane/Jason is the named activation authority; environment IaC/Vault supplies versioned input only. - Correct traceability: REQ-03 maps to role/schema/search-path, REQ-04 to TLS, REQ-05 to post-KBN-100 immutability, REQ-06 to rollout/rollback, and REQ-07 to the KBN-101 → KBN-100 → KBN-101 → KBN-105 sequence. No prior manifest/lock/role/DAG/activation decision is weakened.
1.0.0-rc.6 — KBN-101 closed DDL/TLS/ledger activation remediation
- Choice:
mosaic-db-migratoris the sole application/CI/test PostgreSQL DDL control plane. Every legacy entrypoint is routed or denied, rejectsDATABASE_URL-only before connection/DDL, anddb:pushis unavailable outside an allowlisted disposable developer target. The runner holds onemax:1session with fixedpg_try_advisory_lock(1297044289,1262636593)across preflight through release. - Exact ledger: manifest v1 canonically serializes journal logical index/tag and SHA-256 of exact shipped migration bytes. It maps each observed ledger hash to one tuple; physical insertion order is non-normative, while missing/unknown/duplicate/ambiguous/corrupt/stale states fail closed. Shipped
0009bytes remain unchanged; a missing/effects-absent0009runs normally, an applied-late hash maps normally, and partial/full effects with missing hash require backup restoration or separately reviewed repair—not manual adoption. - TLS/search path: operator/IaC owns CA and server leaf lifecycle, exact compose/Swarm secret mounts, server TLS activation, service-DNS SANs, verified-TLS readiness, transition, CA overlap rotation, and rollback. Runtime/migrator use
verify-full; PGlite is not PostgreSQL TLS evidence. Application sessions use onlypg_catalog,mosaic; no URL/config-derived identifier reaches SQL. - Safe release: cards 00–07 land prepared but inactive; owner-runtime deployments remain N-1. Mosaic control plane/Jason alone authorizes one atomic TLS/roles → runner → readiness → runtime activation or rollback. No runtime-operator compatibility switch, bypass, plaintext interval, or force-on-red exists; all temporary support is removed before KBN-101-08.
- Non-effect: role graph, immutable certification after KBN-100, KBN-105 gate, rc.5’s preserved rc.4 SI-001 invariants, and all KCR-001–016 decisions remain unchanged. Exact detail is normative in
KBN-101-DB-ROLE-SPLIT.md.
1.0.0-rc.5 — KBN-101 role/connection split
- Choice: PostgreSQL
standaloneandfederatedruntime usesDATABASE_URLonly as a non-ownermosaic_runtimelogin; an explicit migration phase usesDATABASE_MIGRATION_URLonly asmosaic_migrator, whichSET ROLEs to non-loginmosaic_schema_ownerfor 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_owneris outside application paths;mosaic_schema_ownerowns only application/ledger schemas.mosaic_runtimehas onlymosaic_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, andapproval_decision_artifactsgrant 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.
1.0.0-rc.4 — KBN010-SI-001 (preserved)
- Choice: add the explicitly named, non-partial unique candidate key
missions_workspace_id_uidxonmissions(workspace_id, id)and retainmissions_workspace_project_id_uidxon(workspace_id, project_id, id). - Rationale: mission
idremains globally unique, while the composite candidate key makes the frozen tenant-safe generic mission relations valid.artifactsandapproval_decisionsare polymorphic exactly-one-target records and do not consistently carryproject_id; widening both children would unnecessarily broaden v1 and its target semantics. - Exact effect:
artifacts_workspace_mission_fkandapproval_decisions_workspace_mission_fkcontinue to reference the exact ordered columnsmissions(workspace_id, id)with RESTRICT deletion, now backed by a matching candidate key. - Non-effect: no SOT, tenancy, project-congruence, proposal-audit, approval, fencing, immutability, no-cascade, API, or wire-version invariant changes. The
SuccessEnvelopeV1.contractVersionremains1.0.0. - Historical evidence boundary:
KBN-010-THREAT-AUTH-CONSTRAINT-GATE.mdintentionally remains the immutable rc.3 blocker verdict that detected SI-001; this rc.4 record and the #753 scratchpad append are the authorized disposition. Rewriting the gate verdict is outside this amendment's exclusive scope. - Gate: this amendment resolves the DDL defect identified by KBN010-SI-001 but does not itself lift KBN-100; independent schema/SecReview remains required.
1. Authority
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.13 DDL/ledger/TLS/role/attestation/generation 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
KanbanHealthResponseV1 is a discriminated union:
| State | read | write | Capability |
|---|---|---|---|
healthy |
true | true | reads; public state still cannot authorize mutation |
read-only-degraded |
true | false | reads only |
write-unavailable |
false | false | diagnostics only |
Every response has checkedAt, validUntil, policyRevision; contradictory booleans fail validation.
For a mutation, Gateway opens the PostgreSQL transaction, executes the live write probe on that transaction/connection, mints the internal branded PostgresWriteHealthProofV1, and revalidates time/policy/transaction identity immediately before mutation. Public REST/MCP/CLI DTOs never accept health/proof fields. Valkey/caller assertions cannot mint proof. Pure Coordinator takes KanbanEvaluationContextV1; persistence takes InternalKanbanMutationContextV1 or probes internally.
| Case | HTTP | Frozen result | Retry |
|---|---|---|---|
| degraded write | 503 | KANBAN_WRITE_HEALTH_UNPROVEN, read-only-degraded, not_applied |
false |
| write unavailable | 503 | KANBAN_WRITE_UNAVAILABLE, write-unavailable, not_applied |
false |
| version conflict | 409 | AGGREGATE_VERSION_CONFLICT, actual version, not_applied |
false |
| timeout/unreachable | timeout/502/504 | retryable_transport_error, unknown |
same idempotency key |
| stale fence/session/approval | coordinator rejection union | not_applied |
false |
Required negatives: contradictory state, expired/policy-mismatched/wrong-transaction proof, Valkey-only health, forged healthy, and exhaustive non-cross-mapping of 503 vs 502/504/timeout vs 409.
3. Canonical schema invariants
Complete declaration: contracts/kanban-schema.v1.ts.
- Tables: tenant/identity (
workspaces, members, teams/members, agents/sessions); planning (projects,milestones, current-milestone join,missions, mission-milestones,tasks, normalized tags, dependencies); orchestration (task_assignments, durable execution state, leases, checkpoints/evidence); governance (change_proposals, immutable artifacts/evidence, events, approvals, outbox, external links). - Task statuses:
backlog | ready | in_progress | blocked | in_review | done | cancelled. - Assignment states everywhere:
awaiting_approval | policy_pre_authorized | approved | rejected | leased | released | expired | superseded. - Specialist roles everywhere:
planning | enhance | coder | review | security-review | pr-monitor | certifier. - Owner uses exactly-one user/team; assignment principal exactly-one user/team/agent; users require active membership; agent/session and all evidence are workspace-bound.
- Task→mission/milestone/parent, mission→milestone, and project→current-milestone are project-congruent composite relations.
- Mission
idremains globally unique. The additional non-partialmissions_workspace_id_uidxcandidate key on(workspace_id, id)exists only to support the frozen workspace-safe polymorphic artifact and approval-decision mission relations; the project-congruent(workspace_id, project_id, id)key remains authoritative whereverproject_idis present. - Dependency identity is workspace+predecessor+successor independent of type.
- Approval evidence and checkpoint evidence are workspace-scoped joins to immutable artifacts, never JSON ID arrays.
- Proposal audit links are composite relations:
(workspace_id, submitted_audit_event_id)and(workspace_id, accepted_command_audit_event_id)referencetask_events(workspace_id, id)with RESTRICT deletion. - Assignment is persisted with task/version, exact target/session, expiry/state/policy/proposer/reason. Approval relates to assignment. Lease acquisition accepts IDs, then reloads/locks and validates every relation.
tasks.fencing_counteris bigint; locked atomic increment/RETURNING creates a decimal-string lease token. Lease/checkpoint composites bind exact workspace+task+assignment/session+fence.task_execution_statesdurably records retry/quarantine/exhaustion.- Tags are normalized; legacy
tasks.tagsremains through N-1. Archive is explicit actor/reason/time and does not change lifecycle. - Canonical parents use RESTRICT. Events/checkpoints/artifacts/evidence are INSERT/SELECT-only for application roles. Normal flow archives/cancels; purge is audited break-glass retention work.
4. Outage proposal contract
change_proposals stores workspace, active-member proposer, source-note digest, target/version, typed command/payload, idempotency, lifecycle, decision actor/reason/time, proposal version, and submit/accepted event IDs. Both event IDs are workspace-aware composite foreign keys to task_events(workspace_id, id); a bare UUID is never sufficient.
Submission preallocates the proposal ID. One transaction inserts change_proposal.submitted with the proposal workspace, aggregate_type='change_proposal', aggregate_id=<new proposal ID>, previous_version=NULL, and new_version=1, then inserts the proposal referencing that event. Missing, foreign-workspace, wrong-type, or unrelated-proposal events abort the transaction.
Submit/list/get/accept/reject are explicit Gateway commands. Pending/rejected proposals are inert: no scheduling, dependency/gate satisfaction, or direct target mutation. Acceptance locks proposal+target, obtains fresh transaction-local proof, verifies pending/expected version, invokes the normal command handler, and atomically stores the emitted normal-command event ID. That event must share the proposal workspace, match target_aggregate_type and target_aggregate_id, use causation_id=submitted_audit_event_id, and carry payload.changeProposalId=<locked proposal ID>. Missing, foreign-workspace, unrelated-target, unrelated-proposal, or unrelated-command events abort acceptance.
5. Concrete current-main N-1 migration delta
Inspected: origin/main:packages/db/src/schema.ts at e72388b2cbfe400842fe940fa6cabf984ed43711 (2026-07-13). It has global teams/no workspace keys, legacy project/mission/task statuses, nullable task project/mission, tasks.assignee/tags/due_date, mission JSON/config, duplicated mission_tasks.status, legacy agent fields, and separate fleet backlog claims.
Legacy columns remain declared in unified schema.ts for expand + full N-1/rollback window. Generation must not infer early drops.
5.1 Ordered phases
- Pre-expand: N-1 patch stops
mission_tasks.statusas write source; inventory writers; backup/checksum. - Expand: add enums/tables and nullable-first columns; retain legacy declarations/uniques; emit no v1-only status.
- Backfill: bootstrap workspace; bounded idempotent cursor/checksum batches; quarantine ambiguous rows.
- Validate: no null tenant, cross-project link, ambiguous owner; status/tag/date/config retention; then constraints/NOT NULL.
- Compatibility: N-1 reads legacy; same-DB transaction mirrors only unavoidable fields; never file/Valkey dual write.
- Switch: stop N-1 writers; Gateway sole command boundary; enable canonical statuses.
- Contract release: later release after rollback/N-1; remove compatibility/global uniques/legacy fields.
5.2 Mission candidate-key and dependent-FK DDL order
KBN-100 migration DDL must execute the SI-001 portion in this order:
- expand/backfill
missions.workspace_idandmissions.project_idwhile preserving the globalmissions.idprimary key and the project-congruentmissions_workspace_project_id_uidxkey; - prove duplicate-key feasibility on the production-shape dataset:
(workspace_id, id)has no duplicate groups and globaliduniqueness remains intact; - create the non-partial unique index
missions_workspace_id_uidxon exact ordered columns(workspace_id, id); - only after step 3, create/alter
artifactsand addartifacts_workspace_mission_fkfrom(workspace_id, mission_id)to exactmissions(workspace_id, id)withON DELETE RESTRICT; - only after step 3, create/alter
approval_decisionsand addapproval_decisions_workspace_mission_fkfrom(workspace_id, mission_id)to exactmissions(workspace_id, id)withON DELETE RESTRICT; - validate both constraints and prove a mission ID paired with a foreign workspace is rejected for each child.
The candidate key is intentionally redundant with globally unique missions.id, but PostgreSQL requires a matching unique candidate key for the exact two-column FK target. It is additive and N-1-safe. Pre-switch rollback drops the two dependent FKs/tables before dropping this candidate key, preserves the global primary key and project-congruent key, and follows the existing freeze/reconciliation rule after the first canonical mutation.
5.3 New audit/proposal DDL 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:
- create
task_eventsand its unique(workspace_id, id)key; - create
change_proposalswith nullable acceptance-event ID and required submission-event ID; - add
change_proposals_workspace_submitted_event_fkfrom(workspace_id, submitted_audit_event_id)totask_events(workspace_id, id)withON DELETE RESTRICT; - add
change_proposals_workspace_accepted_command_event_fkfrom(workspace_id, accepted_command_audit_event_id)to the same composite key withON DELETE RESTRICT; - install application-role immutability privileges and same-transaction semantic validation before enabling proposal commands.
The submission transaction inserts the event first using a preallocated proposal UUID, then the proposal. Acceptance inserts the normal command event before updating the locked proposal. Neither FK is omitted or replaced by a bare UUID/index check.
5.4 Field map
| Current | Expand/backfill | N-1 compatibility | Switch/contract |
|---|---|---|---|
global teams, team_members |
add workspace nullable; bootstrap; validate active owners | retain global slug/FKs | workspace composites; global unique contracts later |
projects.status |
add canonical_status; map active/paused/completed/archived |
mirror representable values; no planning |
canonical authority; legacy contracts later |
project owner_id/team_id/owner_type |
add exact accountable user/team; deterministic map or quarantine | preserve old reads and compare drift | canonical exact-one; remove legacy after parity |
| current milestone | create milestones then join table (no circular DDL) | absent to N-1 | join is authority |
nullable missions.project_id |
derive workspace/project; null/orphan exception, never guess | keep nullable legacy read | canonical required; validate/set NOT NULL later |
| mission relational candidate keys | retain global id PK and project-congruent key; add non-partial (workspace_id,id) key before artifact/approval FKs |
additive key is ignored safely by N-1 readers/writers | retain both composite keys; generic mission children use exact workspace+ID target |
mission description |
add objective; preserve description; reviewed nonblank mapping | N-1 description | objective authority; retain until signed review |
missions.status |
add canonical; planning→draft, active/paused/completed/failed same | no new-only statuses emitted | canonical authority |
mission milestones JSON |
normalize with source digest; preserve malformed/original | N-1 reads JSON; no reverse sync | normalized authority; JSON removed after checksum sign-off |
| mission config/metadata/phase/user | retain all; map known typed policy only | all remain declared | remove only by signed consumer inventory |
nullable tasks.project_id |
derive explicit/mission project; orphan quarantine | retain nullable read/write during compatibility | canonical required; NOT NULL later |
tasks.mission_id |
add project-congruent composite | old relation readable | composite authority |
tasks.status |
canonical: not-started→backlog, in-progress→in_progress, others same | no ready/in_review emission | canonical authority |
tasks.assignee |
deterministic active user/team/agent assignment; raw value preserved if ambiguous | mirror text only if unambiguous | canonical owner/assignment; remove after no-loss sign-off |
tasks.tags JSON |
normalize trim/case/dedupe with original digest | transactionally mirror normalized rows | normalized authority; JSON later removed |
tasks.due_date |
copy exactly to due_at |
mirror | due_at authority; legacy later |
| task common fields | preserve metadata byte-for-byte; add criteria/rank/retry/archive/version/fence | old reads valid | new fields canonical |
mission_tasks.status |
keep; prohibit as write source; linked status ignored; unlinked becomes task or reject | read-only compatibility value | membership uses task mission; status dropped after no readers |
| mission-task notes/PR/user | map to metadata/artifact/event/link/attribution; preserve | read-only | remove after parity |
agents.status |
add workspace/lifecycle/runtime/roles; status remains presence | retain all legacy fields | lifecycle/roles authority; status may remain telemetry |
| agent project/owner/prompt/tools/skills/config | preserve; validate tenant; derive typed capabilities without loss | N-1 reads | removal only by separate inventory |
fleet backlog |
map to designated-project tasks; edges; claimed rows quarantine | freeze claims before switch; read-only compare | task/lease authority; retire after stabilization |
5.5 Required migration tests
Empty DB; exact production-shape snapshot; crash/resume; rollback before switch; N-1 startup/read/write; workspace/member negatives; status-shadow/no premature new status; mission_tasks.status write prohibition; tags/assignee/date/mission JSON/config/description/agent checksum; project congruence/current-milestone order; backlog freeze/no dispatch; and proof legacy declarations persist until contract release.
SI-001 adds frozen future executable evidence: empty and production-shape migrations create missions_workspace_id_uidx before either dependent FK; duplicate-key feasibility preflight returns no (workspace_id,id) duplicate groups without weakening global id uniqueness; N-1 startup/read/write behavior is unchanged; pre-switch rollback removes dependents before the candidate key; both exact FK column lists reconcile to the candidate key; and foreign-workspace mission references fail for both artifacts and approval decisions. TDD is not applicable to this design-only amendment; KBN-100 must implement these negative migration tests before runtime schema release.
Proposal-specific negatives must attempt: missing submission event, foreign-workspace submission event, foreign-workspace acceptance event, same-workspace event for another proposal, event for another target aggregate, and unrelated normal-command event. Every attempt must fail atomically with no accepted proposal and no target mutation.
6. Ownership and Coordinator split
coder2 solely owns packages/db/src/schema.ts, packages/db/drizzle/**, journal/metadata, and migration tests. No other lane generates migrations. Expand is additive; no drop/rename/narrow; constraints validate before NOT NULL; compatibility is same-DB only; contract is later.
KBN-200/coder4 owns pure MechanicalCoordinatorDecisionEngineV1: complete immutable snapshots in, deterministic eligibility/proposal/retry decisions out; no ID loading, SQL, Gateway, Valkey, proof, persistence, restart I/O, or LLM.
KBN-210/coder3 owns MechanicalCoordinatorServicePortV1: ID loading, locks, fresh proof, assignment/approval persistence, atomic fencing, lease/checkpoint/outbox, Valkey wakes, durable retry/quarantine, and recoverFromPostgres. Cycle: load snapshots → pure decision → persist assignment → authoritative approval/policy → acquire by IDs/locks → increment fence → lease → ack/heartbeat/checkpoint → submit to review or durable retry/quarantine. No completion/certification/merge method exists.
7. Exact Gateway/DTO freeze for KBN-105
7.1 Common wire rules
Base is /api/v1/workspaces/:workspaceId. Mutations require header Idempotency-Key (1–128 chars). Existing-aggregate mutations also require If-Match-Version (positive integer); create and privileged assignment-cycle requests are the only exceptions, while proposal submission carries expectedTargetVersion in its body. Body workspace fields are forbidden. Tenant denial follows one 404/403 policy without foreign existence detail.
interface SuccessEnvelopeV1<T> {
contractVersion: '1.0.0';
data: T;
aggregateRevision: string;
correlationId: string;
}
interface ListEnvelopeV1<T> extends SuccessEnvelopeV1<T[]> {
page: { cursor: string | null; nextCursor: string | null; limit: number };
}
Errors are the exact health/transport/version unions in §2 plus validation/auth/not-found. Public DTOs never expose/accept internal write proof.
7.2 Exact route registry
| Method/path | Request body/query | Success data |
|---|---|---|
GET /kanban-health |
none | KanbanHealthResponseV1 |
GET /projects |
status,ownerUserId,ownerTeamId,cursor,limit |
project list |
POST /projects |
name,key,description,status,priority,ownerUserId XOR ownerTeamId,metadata |
project |
GET /projects/:projectId |
none | project |
PATCH /projects/:projectId |
editable create fields + expected header | project |
POST /projects/:projectId/archive |
reason |
project |
GET /tasks |
projectId,missionId,milestoneId,status,priority,ownerUserId,ownerTeamId,specialistRole,tag,dueState,archived,cursor,limit |
task summary list |
POST /tasks |
projectId,missionId?,milestoneId?,parentTaskId?,title,description?,acceptanceCriteria[],status,priority,rank,ownerUserId XOR ownerTeamId,specialistRole?,dueAt?,notBeforeAt?,estimateMinutes?,retryPolicy?,tagIds[],metadata |
task detail |
GET /tasks/:taskId |
none | task detail including readiness/dependencies/assignment/lease/events |
PATCH /tasks/:taskId |
editable non-transition fields | task detail |
POST /tasks/:taskId/transition |
toStatus,reason? |
task detail |
POST /tasks/:taskId/move |
toStatus?,beforeTaskId?,afterTaskId? |
task detail with persisted rank |
POST /tasks/:taskId/archive |
reason |
task detail |
PUT /tasks/:taskId/tags |
tagIds[] |
task detail |
POST /tasks/:taskId/dependencies |
predecessorTaskId,type |
dependency |
DELETE /tasks/:taskId/dependencies/:predecessorTaskId |
no body | deleted dependency ID |
GET /tasks/:taskId/events |
cursor,limit |
event list |
GET /tags |
query,cursor,limit |
tag list |
POST /tags |
name,color? |
tag |
GET /change-proposals |
state,targetType,targetId,cursor,limit |
proposal list |
POST /change-proposals |
sourceNoteDigest,targetType,targetId,expectedTargetVersion,commandType,commandPayload |
inert proposal |
GET /change-proposals/:proposalId |
none | proposal |
POST /change-proposals/:proposalId/accept |
reason |
proposal + normal command result |
POST /change-proposals/:proposalId/reject |
reason |
proposal |
GET /coordinator/eligibility |
projectId?,missionId?,cursor,limit |
EligibilityDecisionV1[] |
POST /coordinator/assignment-cycles |
limit |
assignment proposals; privileged internal |
POST /coordinator/assignments/:assignmentId/approve |
decision,reason,policyRevision,artifactIds[] |
approval decision |
POST /coordinator/leases/acquire |
taskId,assignmentId,approvalDecisionId,targetSessionId,leaseTtlSeconds |
lease with decimal-string fence |
POST /coordinator/leases/:leaseId/ack |
taskId,sessionId,fencingToken |
lease |
POST /coordinator/leases/:leaseId/heartbeat |
taskId,sessionId,fencingToken,extendSeconds |
lease |
POST /coordinator/leases/:leaseId/checkpoints |
taskId,sessionId,fencingToken,sequence,resumableSummary,artifactIds[],contextUsagePercent |
checkpoint |
POST /coordinator/leases/:leaseId/submit-review |
taskId,sessionId,fencingToken,artifactIds[],summary |
task in in_review |
All Coordinator mutations except human approval are service-identity-only. Generic task PATCH cannot perform claim/heartbeat/checkpoint/review/certification/completion shortcuts. Completion after certification uses a separately gated lifecycle command owned by the Portfolio/Sub-Orchestrator flow, not the Coordinator.
7.3 DTO invariants
Task summary/detail use exact schema vocabularies, owner union, version: number, fencingCounter: string, explicit archivedAt/by/reason, normalized tags, computed readiness, and separate assignment/lease. Assignment DTO includes one persisted ID, task/version, exact principal/agent/session, role, state, expiry, policy, proposer/reason. Lease/checkpoint DTOs serialize every fence as decimal string. Proposal DTO exposes no hidden write authority.
7.4 MCP ownership and mapping
coder3 exclusively owns:
apps/gateway/src/mcp/mcp.dto.tsmcp.controller.tsmcp.service.tsmcp.module.tsmcp.tokens.tsmcp.service.spec.ts
MCP tools are thin maps: mosaic_projects_{list,get,create,update,archive}, mosaic_tasks_{list,get,create,update,transition,move,archive,set_tags,add_dependency,remove_dependency}, and mosaic_change_proposals_{list,get,submit,accept,reject} to the exact routes above. coder4 owns CLI/projection clients only and must not edit Gateway MCP files.
KBN-105 publishes route+DTO fixture digest before KBN-110/120/130. Every web/CLI/MCP call must match this registry and the generated client.
8. Recovery contract and bounded delivery slice
Runtime must invoke normative validateRecoveryPostureV1; JSON Schema alone is insufficient. It rejects unknown fields, PITR/WAL mismatch, RPO better than mechanism, unsafe storage, and weakened High-assurance. High-assurance is RPO 15m/RTO 4h, WAL ≤5m, PITR ≥35d, base ≤24h, restore test ≤30d, break-glass ≤90d, encrypted separate-failure-domain storage.
KBN-115/coder2 owns packages/config/src/recovery-posture.ts, tests, and recovery runbook. It wires parser/refinement, override audit, mechanism assertions, restore test, and break-glass evidence. Any deployment manifest is separately enumerated and Mos-serialized. Recovery config has no SOT/gate/Coordinator authority fields.
9. Integration, security, and hold
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
- KBN-100 must provide the §5.2 candidate-key ordering, duplicate-feasibility, exact-FK reconciliation, empty/prod/N-1/rollback, and two-child foreign-workspace evidence before SI-001 can be certified closed.
- All prior KCR-001–016 decisions and fixed SOT/tenant/authority, proposal-audit, approval, task-fencing, immutability, and no-cascade invariants remain unchanged.
- Read-only PR #757 cross-check: its logical-agent connector lease/CAS fencing uses separate runtime tables/contracts and
lease_epoch; rc.4 changes only the frozenmissionscandidate key. There is no shared table, index, FK, identity, fence, or authority semantic to consume or reconcile, and #757 remains owned by its existing lane.
The build hold remains active until independent re-review reports GO for KCR-001–016 and the rc.4 SI-001 amendment. Mos alone releases waves and serializes integration roots.