Files
stack/docs/native-kanban-sot/SHARED-CONTRACT.md
2026-07-15 07:04:39 -05:00

55 KiB
Raw Blame History

Native Kanban/SOT — Remediated Shared Contract v1

Status: CONTROL-PLANE rc.11 KBN-101 target-bound importer-attestation and complete operator-inventory remediation complete; awaiting independent exact-head re-review. Prior KCR-001016 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.11 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.11 — Target-bound importer attestation and exhaustive operator-route closure

  • Target-bound proof: trusted mosaic-db-migrator --verify now produces the atomic, credential-free migrate-target.v1.json JCS/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-tier requires both --target-url-file /run/secrets/mosaic_migrate_target_url and --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.md and status-only docs/federation/TASKS.md; the latter is historical and cannot authorize DDL. The legacy storage migrate tier-copy syntax is unavailable. storage migrate is schema-wrapper delegation only; secure tier data copy is migrate-tier. Exact KBN PRD/contract/shared/task paths may be normative-contract scan class but are still scanned and cannot mask executable instructions. The normative detail remains KBN-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 vector is untrusted (trusted absent; relocatable=true), so mosaic_extension_owner is exactly NOLOGIN SUPERUSER, not NOSUPERUSER. It is dedicated solely to mosaic_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 alone SET ROLEs for extension CREATE/UPDATE/SET SCHEMA, then RESET ROLE; fresh and shadow paths do so, while in-place existing work requires exact pre-existing extowner.
  • Explicit superuser exception: GRANT/REVOKE cannot 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.md is 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_URL fallback, 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, mosaic application schema, TLS, activation, and KBN-100/KBN-105 serial gates remain unchanged. The normative detail remains KBN-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, not mosaic_schema_owner, creates and owns mosaic_extensions, vector, and extension-member objects. The external bootstrap actor SET ROLEs for fresh creation or approved-owner relocation, then RESET ROLEs; rc.10 replaces the earlier membership wording with the PostgreSQL-valid zero-member superuser exception. Schema owner has only USAGE for 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 image ENTRYPOINT ["mosaic-db-migrator"]; packages/storage/src/{cli,migrate-tier}.ts belongs 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:migrate is replaced by sole-runner guidance rather than hidden under a historical category.
  • Non-effect: manifest v1, lock, mosaic application-schema ownership, TLS, activation, KBN-100/KBN-105 serial gates, and all earlier canon decisions remain unchanged. The normative detail remains KBN-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.ts loses its public runMigrations export with a direct-import/compile negative; docs/fleet/backlog-conventions.md and docs/PERFORMANCE.md lose 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-migrator from packages/db/package.json/src/cli.ts, owns docker/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 owns infra/pg-bootstrap/roles.sql, infra/pg-bootstrap/extensions.sql, infra/pg-bootstrap/README.md, plus bootstrap tests. KBN-101-05 alone owns tools/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_owner is a dedicated NOLOGIN role whose membership/credentials never reach services; the external bootstrap actor alone may SET ROLE during bootstrap. Fresh vector and member objects retain that owner. PostgreSQL has no supported extension-owner alteration: approved-owner existing extension relocation validates pg_extension.extowner, members/schema/version and uses tested ALTER 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, or DROP CASCADE is 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.sql retirement, migrate-tier.ts runner/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 exported pgSchema('mosaic'); immutable historical SQL runs only in trusted legacy public. vector is fixed in non-writable mosaic_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/stack KBN-101-00/05 owns current Compose, Portainer, two-gateway, bootstrap renderer/templates, UID/GID declarations, and rendered validation. Gateway is fixed to 10001: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-migrator is the sole application/CI/test PostgreSQL DDL control plane. Every legacy entrypoint is routed or denied, rejects DATABASE_URL-only before connection/DDL, and db:push is unavailable outside an allowlisted disposable developer target. The runner holds one max:1 session with fixed pg_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 0009 bytes remain unchanged; a missing/effects-absent 0009 runs 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 only pg_catalog,mosaic; no URL/config-derived identifier reaches SQL.
  • Safe release: cards 0007 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.5s preserved rc.4 SI-001 invariants, and all KCR-001016 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 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 ROLEs 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-001016 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-101s 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_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.
  • Exact effect: artifacts_workspace_mission_fk and approval_decisions_workspace_mission_fk continue to reference the exact ordered columns missions(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.contractVersion remains 1.0.0.
  • Historical evidence boundary: KBN-010-THREAT-AUTH-CONSTRAINT-GATE.md intentionally 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.11 DDL/ledger/TLS/role/attestation 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 id remains globally unique. The additional non-partial missions_workspace_id_uidx candidate 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 wherever project_id is 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) reference task_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_counter is bigint; locked atomic increment/RETURNING creates a decimal-string lease token. Lease/checkpoint composites bind exact workspace+task+assignment/session+fence.
  • task_execution_states durably records retry/quarantine/exhaustion.
  • Tags are normalized; legacy tasks.tags remains 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

  1. Pre-expand: N-1 patch stops mission_tasks.status as write source; inventory writers; backup/checksum.
  2. Expand: add enums/tables and nullable-first columns; retain legacy declarations/uniques; emit no v1-only status.
  3. Backfill: bootstrap workspace; bounded idempotent cursor/checksum batches; quarantine ambiguous rows.
  4. Validate: no null tenant, cross-project link, ambiguous owner; status/tag/date/config retention; then constraints/NOT NULL.
  5. Compatibility: N-1 reads legacy; same-DB transaction mirrors only unavoidable fields; never file/Valkey dual write.
  6. Switch: stop N-1 writers; Gateway sole command boundary; enable canonical statuses.
  7. 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:

  1. expand/backfill missions.workspace_id and missions.project_id while preserving the global missions.id primary key and the project-congruent missions_workspace_project_id_uidx key;
  2. prove duplicate-key feasibility on the production-shape dataset: (workspace_id, id) has no duplicate groups and global id uniqueness remains intact;
  3. create the non-partial unique index missions_workspace_id_uidx on exact ordered columns (workspace_id, id);
  4. only after step 3, create/alter artifacts and add artifacts_workspace_mission_fk from (workspace_id, mission_id) to exact missions(workspace_id, id) with ON DELETE RESTRICT;
  5. only after step 3, create/alter approval_decisions and add approval_decisions_workspace_mission_fk from (workspace_id, mission_id) to exact missions(workspace_id, id) with ON DELETE RESTRICT;
  6. 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:

  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;
  3. add change_proposals_workspace_submitted_event_fk from (workspace_id, submitted_audit_event_id) to task_events(workspace_id, id) with ON DELETE RESTRICT;
  4. add change_proposals_workspace_accepted_command_event_fk from (workspace_id, accepted_command_audit_event_id) to the same composite key with ON DELETE RESTRICT;
  5. 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 (1128 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.ts
  • mcp.controller.ts
  • mcp.service.ts
  • mcp.module.ts
  • mcp.tokens.ts
  • mcp.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-001016 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 frozen missions candidate 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-001016 and the rc.4 SI-001 amendment. Mos alone releases waves and serializes integration roots.