docs(#751): Publish native Kanban/SOT canon (#752)
All checks were successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful

This commit was merged in pull request #752.
This commit is contained in:
2026-07-14 17:08:09 +00:00
parent d077183554
commit 49e8a54105
18 changed files with 4077 additions and 11 deletions

View File

@@ -0,0 +1,36 @@
# Documentation Completion Checklist — Native Kanban/SOT Canon
**Tracking:** Mosaic Stack issue #751
**Scope:** Requirements and contract publication only; runtime implementation follows in separate slices.
## Required artifacts
- [x] Project `docs/PRD.md` exists; the workstream requirements refine its task/project-management scope.
- [x] Canonical workstream requirements published at `docs/requirements/native-kanban-sot.md`.
- [x] Mission manifest, task decomposition, frozen shared contract, and typed contract declarations included.
- [x] `docs/SITEMAP.md` updated.
- [x] Independent initial review and final GO report stored under `docs/reports/native-kanban-sot/`.
- [x] Task scratchpad stored under `docs/scratchpads/`.
- [ ] User/Admin/Developer guides — N/A for canon-only publication; required in implementation slices that change behavior or operations.
- [ ] OpenAPI and endpoint index — N/A until KBN-105 freezes implementation-ready endpoint contracts.
## Structural and root hygiene
- [x] Canonical requirements are under `docs/requirements/`.
- [x] Workstream artifacts are under `docs/native-kanban-sot/`.
- [x] Review reports are under `docs/reports/native-kanban-sot/`.
- [x] No new unscoped document was added to the `docs/` root.
- [x] Root mission/task rollups link to the workstream.
## Review gate
- [x] Author and independent reviewer are different agents.
- [x] KCR-001016 closure was independently verified.
- [x] Ultron final gate returned GO with zero BLOCKER/HIGH findings.
- [x] Formatter, lint, typecheck, strict contract TypeScript, link, scope, and invariant publication validation passed in the current Stack toolchain.
- [ ] PR review, CI, squash merge, and issue closure remain required before publication completion.
## Publishing
- [x] Canonical source remains in-repository.
- [x] No external publishing platform is required for this internal architecture contract.

View File

@@ -0,0 +1,64 @@
# Native Kanban/SOT Canon
**Status:** KCR-001016 independently cleared; canonical publication is in progress under issue [#751](https://git.mosaicstack.dev/mosaicstack/stack/issues/751)
**Date:** 2026-07-14
**Implementation hold:** no feature implementation starts until this canon is squash-merged to `main` with terminal-green CI; after merge, every slice remains held until its KBN prerequisite graph is satisfied.
## Artifacts
| Artifact | Purpose |
| ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Canonical requirements](../requirements/native-kanban-sot.md) | Canonical P0P3 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 P0P3 slices with IN/OUT scope, dependencies, shared contracts, file ownership, evidence, and USC coder2/3/4/5 parallelization |
| [`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 |
| [`contracts/health-state.v1.ts`](./contracts/health-state.v1.ts) | Discriminated public health, separate branded transaction-local write proof, and non-overlapping denial/transport/version-conflict mappings |
| [`contracts/recovery-posture.v1.ts`](./contracts/recovery-posture.v1.ts) | Provider-neutral shape schema plus normative runtime refinement, cross-field constraints, and Lite/Standard/High-assurance defaults |
| [`tsconfig.json`](./tsconfig.json) | Strict no-emit project scope for linting and compiling the four frozen TypeScript contracts against the current Stack Drizzle declarations |
| [`DOCUMENTATION-CHECKLIST.md`](./DOCUMENTATION-CHECKLIST.md) | Publication documentation gate and implementation-slice deferrals |
| [Initial independent review](../reports/native-kanban-sot/canon-initial-review-no-go.md) | KCR-001016 findings that blocked the first draft |
| [Final independent re-review](../reports/native-kanban-sot/canon-final-rereview-go.md) | Closure matrix, reproducible validation evidence, and GO verdict |
| [Ultron final gate](../reports/native-kanban-sot/ultron-final-go.md) | Final requirements, authority, schema, migration, recovery, decomposition, and evidence review GO |
## Recommended USC lane partition
| Lane | Natural seam | Exclusive ownership |
| ---------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **coder2** | Schema + migrations + recovery slice | Unified Drizzle schema, migration SQL/meta/journal/tests, then recovery parser/mechanism/runbook files |
| **coder3** | Domain + Gateway + MCP server | Workspace-safe repositories, DTOs/controllers/services, exact `apps/gateway/src/mcp/**` files, health proof, proposals, Coordinator persistence adapter |
| **coder4** | Pure Coordinator + tooling | `packages/coord` mechanical engine, CLI/MCP consumers, generated projection, one-way importer and cutover tooling; lane-serialized internally |
| **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.
## Recovery defaults
| Tier | RPO / RTO | WAL / PITR | Base backup | Restore / break-glass | Off-cluster |
| -------------- | ------------ | ------------------- | ----------- | ----------------------- | ----------------------------------------------- |
| Lite | 24h / 24h | disabled / disabled | daily | quarterly / annual | encrypted separate target |
| Standard | 1h / 8h | q15m / 14d | daily | quarterly / semiannual | encrypted separate object storage |
| High-assurance | **15m / 4h** | **q5m / 35d** | **daily** | **monthly / quarterly** | **encrypted base+WAL, separate failure domain** |
These knobs affect recovery posture only. PostgreSQL remains the sole writable SOT in every tier. Fail-closed writes, generated-file non-authority, attributable post-recovery proposals, non-LLM Coordinator limits, and Certifier final-gate/no-merge authority are fixed for every tier.
## Non-blocking implementation sub-decisions for Mos
The source plan and ratified seven decisions resolve all build-blocking product choices. The following implementation-local selections remain for the owning slices/Mos and must not weaken v1:
1. Exact PostgreSQL write-health probe SQL and bounded proof lifetime; authority and failures are frozen.
2. Dependency-cycle serialization mechanism (recursive CTE plus transaction/advisory lock or equivalent); required behavior is frozen.
3. Whether RLS lands in the first migration or immediately after the tested session-context pattern; workspace constraints/repository authorization are required from migration one.
4. Concrete off-cluster backup provider/bucket and selected production recovery tier; High-assurance minima are frozen if selected.
5. Cutover reconciliation thresholds and stabilization duration, to be owner-approved before P3 execution.
None authorizes a second writer, dual sync, LLM scheduling, Coordinator gate waiver/merge, or Certifier merge authority.
## Publication validation evidence
- Concrete TypeScript contracts are formatted with repository Prettier.
- All four contracts pass strict TypeScript no-emit checking against the current Stack Drizzle toolchain.
- Contract remediation and KCR-001016 traceability are recorded in the issue scratchpad and linked review reports.
- Independent re-review returned GO with KCR-001016 closed; implementation remains held until canon merge and the dependency-ordered KBN prerequisites complete.

View File

@@ -0,0 +1,195 @@
# Mission Manifest — Mosaic Native Kanban and Canonical Task SOT P0P3
**Mission status:** CANON INDEPENDENTLY APPROVED; publication in progress under issue [#751](https://git.mosaicstack.dev/mosaicstack/stack/issues/751)
**Date:** 2026-07-14
**Human decision owner:** Jason
**Orchestrator/publication owner:** web1 control plane (`mos-claude`; `mosaic-100` acting during Claude quota outage)
**Execution topology:** USC web1, partitioned across collision-free GPT coder2/3/4/5 lanes
**Canonical requirements:** [`../requirements/native-kanban-sot.md`](../requirements/native-kanban-sot.md)
**Frozen integration contract:** `SHARED-CONTRACT.md` and `contracts/*.v1.ts`
## 1. Mission statement
Extend current `mosaicstack/stack` main into the sole native control plane for workspace-scoped project, mission, milestone, task, dependency, assignment, lease, approval, evidence, and audit state. First deliver a thin writable Kanban/List vertical slice; then add deterministic mechanical coordination and execute a one-way migration/cutover from jarvis-brain/Vikunja project/task stores.
Success means every user, agent, orchestrator, specialist, and UI sees and mutates the same PostgreSQL aggregate revisions through typed Gateway commands, with no writable fallback and no hidden second authority.
## 2. Scope boundaries
### In scope
- Current Drizzle/PostgreSQL schema extension and migrations.
- Workspace tenancy and authorization from the first migration.
- Projects, missions, milestones, tasks, normalized tags, dependencies, assignments, durable execution/quarantine state, links, immutable artifacts/evidence joins, outage change proposals, events, approvals, leases, checkpoints, and transactional outbox.
- NestJS Gateway queries and explicit lifecycle commands.
- MCP/CLI agent surfaces and generated read-only projections.
- Thin writable Next.js Tasks Kanban/List, task detail, minimal Projects CRUD, filters, dependency readiness, ownership/lease separation, and audit timeline.
- Non-LLM Mechanical Coordinator eligibility, proposal, approval-policy, lease/fence, heartbeat, retry, expiry, quarantine, and restart recovery.
- Planning, Enhance, Coder, Review, SecReview, PR-Monitor, and Certifier role/gate representation.
- One-way shadow importer, reconciliation, write freeze, final delta, cutover, rollback package, and legacy read-only stabilization.
- Recovery-posture configuration and health-state/fail-closed contract.
### Out of scope
- Greenfield services, Prisma runtime revival, or jarvis-brain flat files as runtime storage.
- Writable Markdown/JSON/Valkey/browser/provider fallback.
- Gitea issue/PR replacement or generic bidirectional provider sync.
- Calendar, email, GLPI cache, CRM, billing, time tracking, personal-brain migration.
- LLM scheduling or scope interpretation by the Coordinator.
- Autonomous gate waiver, certification, merge, release, deployment, or issue closure by Coordinator.
- Merge authority for Certifier.
- P4 full portfolio/mission designer and P5 fleet-scale policy unless separately released.
## 3. Fixed invariants
Every deployment MUST preserve all of the following:
1. PostgreSQL is the sole writable SOT.
2. Drizzle on current stack main is the only persistence foundation.
3. Mutations fail closed when DB write-health cannot be proven `healthy`.
4. No file, Valkey, browser, queue, provider, or human note becomes a fallback writer.
5. `TASKS.md`, `mission.json`, and every file export are generated, read-only, non-authoritative, and never import sources.
6. Human outage notes become attributable post-recovery proposals only.
7. Workspace is the hard tenant; Team is intra-workspace authorization.
8. Valkey is expendable; PostgreSQL owns state, leases, fencing, audit, and outbox.
9. Mechanical Coordinator is deterministic/non-LLM and cannot invent scope, waive gates, certify, or merge.
10. Certifier is the final independent quality gate and has no merge authority.
11. Mutations use idempotency and optimistic aggregate versions; worker commands also require a current fencing token.
12. Recovery tier changes only backup/recovery posture, never authority or gate semantics.
## 4. Configurable recovery posture
Deployments select Lite, Standard, or High-assurance defaults from [`../requirements/native-kanban-sot.md`](../requirements/native-kanban-sot.md) and `contracts/recovery-posture.v1.ts`. Configurable fields are limited to:
- backup/base-backup cadence;
- RPO and RTO targets;
- PITR retention;
- WAL archive cadence;
- restore-test frequency;
- break-glass drill frequency;
- encrypted off-cluster storage.
High-assurance defaults are fixed reference values: RPO 15 minutes, RTO 4 hours, encrypted off-cluster WAL every 5 minutes with 35-day PITR, daily base backup, monthly restore test, and quarterly break-glass drill.
## 5. Canonical role map
```text
User
↓ objectives, constraints, ratified decisions
Interaction Layer
↓ workspace/project context; no scheduling authority
Portfolio Orchestrator
↓ approved mission, cross-project priority/capacity
Project Sub-Orchestrator
↓ decomposition, DAG, acceptance, release, routing policy, overrides
Gateway
↓ authenticated/authorized typed commands
Project/Task Domain Services
↓ transactional state + semantic event + outbox
Mechanical Coordinator
↓ deterministic eligibility/proposal/lease/fence/retry/quarantine
Specialists
Planning → Enhance → Coder → Review → conditional SecReview → remediation
↓ complete evidence bundle
Certifier
↓ final pass/reject/escalate; NO merge authority
Project Sub-Orchestrator / control plane
↓ merge authority after all gates
Post-merge validation
```
### Authority table
| Role/layer | Owns | Explicitly cannot do |
| ------------------------ | ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------- |
| User | Objectives, constraints, Jason-owned decisions | Direct DB/file authority bypass |
| Interaction | Conversation and context resolution | Schedule, approve, lease, certify |
| Portfolio Orchestrator | Mission approval, cross-project priority/capacity/global holds | Implement or self-certify specialist work |
| Project Sub-Orchestrator | Task decomposition/DAG/acceptance, release to ready, routing policy, overrides, remediation, merge go-ahead | Bypass required independent gates |
| Gateway | Identity, tenancy, DTO validation, commands, state-machine enforcement | Accept file edits or client SQL as mutations |
| Domain services | Transactional business invariants, semantic events/outbox | Depend on Valkey/files for committed truth |
| Mechanical Coordinator | Eligibility, dependencies, proposal, approved routing, lease/fence, heartbeat, retry/quarantine | Invent/alter scope, waive gates, certify, merge |
| Specialists | Bounded planning/implementation/review artifacts under a task lease | Modify another lane's owned files or self-approve |
| Certifier | Final independent evidence/traceability/gate decision | Merge, close provider issue, release, waive policy |
## 6. Gate model
### Mandatory gates
1. Requirements/contract freeze before parallel implementation.
2. P0 schema/authority threat model and tenant isolation review.
3. Author and reviewer MUST be different principals/sessions.
4. Functional review validates requirements, endpoint registry, concurrency, and negative paths.
5. **Mandatory SecReview (`secrev`)** for any auth, authorization, tenant, service-token, secret, database schema/migration, data-integrity, import/cutover, audit, lease/fencing, recovery, or destructive-retirement surface.
6. Review findings enter bounded remediation owned by the implementation lane.
7. Raising reviewer re-verifies remediation.
8. Certifier performs the final independent evidence and traceability gate.
9. Merge authority remains with `mos-claude`/Project Sub-Orchestrator control plane after gates pass.
10. Post-merge CI and situational validation must be terminal green before closure.
### Gate outcomes
- **PASS:** evidence complete; next authority may proceed.
- **REJECT:** findings are explicit and route to remediation.
- **ESCALATE:** policy/owner decision required; no implicit waiver.
No role can transform a missing gate into a warning by changing status, editing a projection, or writing Valkey.
## 7. Slice ownership rules
1. USC web1 is the sole execution environment; coder2/3/4/5 are independent bounded lanes under Mos.
2. Every slice has one named file-tree owner and an explicit IN/OUT boundary in `TASKS.md`.
3. Two active slices MUST NOT edit the same source file, migration file, generated snapshot, lockfile, or API contract.
4. coder2 exclusively owns `packages/db/src/schema.ts`, `packages/db/drizzle/**`, migration journal/meta/tests, then its disjoint recovery-parser/runbook slice. All schema requests serialize through coder2.
5. Frozen `contracts/*.v1.ts` are read-only inputs during implementation. Contract changes require Mos approval, a version bump/amendment, and coordinated rebase before work resumes.
6. coder3 exclusively owns Gateway DTO/controllers/services and the enumerated `apps/gateway/src/mcp/**` server files. coder4 owns CLI/projection clients and never edits MCP server files. Web consumers use the exact KBN-105 endpoint/DTO freeze.
7. coder4 executes one lane order: CLI/projection → pure Coordinator → importer → cutover. The pure Coordinator under `packages/coord` does not load IDs or access DB, Gateway, Valkey, recovery I/O, or web files; coder3 owns the persistence/service adapter.
8. Migration/import tooling calls Gateway/migration-only approved ports and does not add a second database model.
9. Each lane commits only its owned files and reports any needed cross-slice change as a contract-change request instead of editing another lane's tree.
10. Cross-review is mandatory: no lane reviews its own changes. Recommended ring is coder2 ← coder5, coder3 ← coder2, coder4 ← coder3, coder5 ← coder4, followed by independent SecReview where triggered and Certifier final.
11. Integration-only edits are a separate serialized slice after component lanes are green; no opportunistic merge-conflict resolution may alter semantics.
## 8. Delivery phases and exit gates
### P0 — Canon and authority foundation
- Publish this canon, frozen schema/ports/health/recovery contracts, threat model, authorization matrix, exact endpoint/DTO registry, concrete current-main field-by-field migration map, and standards amendment.
- Build hold remains active until independent author≠reviewer re-review returns GO on health proof/failures, approval binding, fencing, tenant relationships, proposals, migration map, slice ordering/API freeze, recovery validation, and vocabulary alignment.
- Exit: no unresolved second writer or contract blocker, tenant boundary frozen, all seven decisions traceable, and independent re-review GO recorded.
### P1 — Thin native MVP
- Schema/migration, tenant-safe Gateway, CLI/MCP/projection, writable Kanban/List/Projects, dependencies/readiness/audit.
- Exit: same revision across web/CLI/MCP/projection; cross-workspace tests fail closed; generated files cannot mutate state.
### P2 — Mechanical coordination
- Agent/session registry, deterministic engine, approval queue, PostgreSQL leases/fencing/checkpoints/outbox, retry/quarantine, operations UI.
- Exit: one lease winner, stale tokens rejected, dependencies/approvals enforced, DB/Valkey fault semantics proven, Certifier gate has no merge authority.
### P3 — Shadow migration and cutover
- Importer, lineage, reconciliation, reviewer UI, write freeze, final delta, Gateway switch, legacy read-only, stabilization and rollback package.
- Exit: signed reconciliation, zero active legacy writers, scoped Gateway identities, imported backlog cannot dispatch accidentally.
## 9. Evidence required for mission closure
- Requirement-to-test/evidence matrix.
- Schema/migration and N-1 rolling-deploy proof.
- Cross-workspace API/repository/import/Coordinator negative tests.
- Health-state and fail-closed fault injection.
- Valkey-loss/outbox replay and Coordinator restart tests.
- Concurrent lease and stale fencing tests.
- Endpoint-registry alignment across web/CLI/MCP/Gateway.
- Accessible real-Gateway Kanban journeys.
- Generated projection tamper/no-import proof.
- One-way migration dry-run/apply/verify and field reconciliation.
- Author-independent functional review and required SecReview.
- Certifier final decision and evidence bundle.
- Merged main SHA, terminal green CI, closed linked task/issue, and post-merge situational validation under orchestrator ownership.
## 10. Change control
This manifest is derived from the ratified source plan. Any change to SOT authority, workspace tenancy, fixed statuses, Coordinator/Certifier authority, health-state semantics, schema v1, migration direction, or recovery-tier field set is a contract change. Contract changes require Jason/Mos authorization and cannot be inferred by an implementation lane.
No coder lane may start while the build hold is active. KBN-010 must complete before KBN-100; KBN-105 exact endpoint/DTO freeze must complete before any API consumer implementation.

View File

@@ -0,0 +1,219 @@
# Native Kanban/SOT — Remediated Shared Contract v1
**Status:** INDEPENDENT REVIEW GO; freezes as v1 when issue #751 canon merges to `main`
**Version:** 1.0.0-rc.3
**Date:** 2026-07-14
**Change authority:** Mosaic control plane/Jason only
## 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.
## 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.
- 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 New audit/proposal DDL order
KBN-100 migration DDL 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.3 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 `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.4 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.
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.
```ts
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 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.
The build hold remains active until independent re-review reports GO for KCR-001016. Mos alone releases waves and serializes integration roots.

View File

@@ -0,0 +1,261 @@
# Native Kanban/SOT P0P3 — Dependency-Ordered Build Slices
**Status:** CANON INDEPENDENTLY APPROVED; PUBLICATION IN PROGRESS
**Tracking:** [Mosaic Stack issue #751](https://git.mosaicstack.dev/mosaicstack/stack/issues/751)
**Execution:** USC web1 only; collision-free GPT coder2/3/4/5 lanes
**Contract:** `SHARED-CONTRACT.md` + four `contracts/*.v1.ts` files
**Implementation hold:** no feature slice starts until the canon PR is merged to `main` with terminal-green CI; after merge, each slice remains held until every declared KBN prerequisite is complete.
> This publication file is not a runtime task authority. After cutover, repository `TASKS.md` is generated read-only and never imported.
## Execution invariants
- PostgreSQL is the sole writable SOT; current-main Drizzle is the persistence foundation.
- Mutations require fresh internal PostgreSQL transaction-local write proof and fail closed otherwise.
- Public health DTOs, Valkey, files, browser state, providers, and outage notes cannot authorize writes.
- Outage notes return only through attributable `change_proposals`; proposal acceptance executes the normal command.
- Mechanical Coordinator is non-LLM and cannot invent scope, waive gates, certify, or merge.
- Certifier is final independent gate with no merge authority.
- Workspace is the hard tenant. Project hierarchy is project-congruent. Assignment, approval, lease, fence, checkpoint, and evidence are relationally bound.
- Recovery tiers change recovery posture only.
## 1. Collision-free ownership
| USC lane | Exclusive ownership | Must not edit |
| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| **coder2 — schema/recovery** | `packages/db/src/schema.ts`; `packages/db/drizzle/**`; DB tests; `packages/config/src/recovery-posture.ts`; `packages/config/src/recovery-posture.spec.ts`; `docs/runbooks/kanban-postgres-recovery.md` | Gateway, Brain repositories, Coordinator, web, CLI/importer |
| **coder3 — domain/Gateway/MCP server** | Kanban repositories under `packages/brain/src/`; Gateway workspace/project/mission/milestone/task/kanban/health/coord modules; **exact MCP files:** `apps/gateway/src/mcp/mcp.dto.ts`, `mcp.controller.ts`, `mcp.service.ts`, `mcp.module.ts`, `mcp.tokens.ts`, `mcp.service.spec.ts`; Gateway root wiring/tests | DB schema/migrations, `packages/coord`, web, CLI/importer |
| **coder4 — CLI → pure Coordinator → migration tooling** | In this one fixed lane order: KBN-120 (`packages/mosaic` CLI/projection) → KBN-200 (`packages/coord/src/mechanical/**`) → KBN-300/320 (`scripts/kanban-migration/**`) | DB, Gateway/MCP server, web |
| **coder5 — web** | `apps/web/src/app/(dashboard)/{tasks,projects}/**`; `apps/web/src/components/{tasks,projects}/**`; Kanban web API/types; later Coordinator/migration-review routes | DB, Gateway, Coordinator, CLI/importer |
| **Mos — publication/integration** | Contract amendments, exact endpoint registry publication, serialized root exports/manifests/lockfiles, integration gates | Active lane feature files |
Shared roots, package exports/manifests, lockfiles, and generated artifacts are integration-serialized. Contract changes stop affected lanes and require Mos approval.
## 2. Parallelization legend
- **SERIAL:** prerequisite must be complete and reviewed.
- **PARALLEL-GROUP:** disjoint files and exact frozen contract permit concurrent work.
- **LANE-SERIAL:** one lane's stated order cannot change.
- **INTEGRATION-SERIAL:** component heads green first; semantic findings return to owner.
## 3. Corrected dependency graph
```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-110 + KBN-120 + KBN-130 + KBN-115
-> KBN-140 P1 integration/SIT
-> KBN-200 pure decision engine [coder4 after KBN-120]
-> KBN-210 persistence/service adapter + approval/lease binding
-> KBN-220 Coordinator operations UI
-> KBN-230 P2 concurrency/fault/gate integration
KBN-230
-> KBN-300 importer dry-run/apply/verify [coder4 after KBN-200]
├─ KBN-310 migration reviewer UI
└─ KBN-320 cutover/rollback tooling [coder4 after KBN-300]
KBN-310 + KBN-320
-> KBN-330 rehearsal/reconciliation
-> 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.
## 4. P0 — Canon, threat gate, schema, and exact API freeze
### KBN-000 — Remediate and publish canon
- **Owner:** Mos / publication control plane.
- **Mode:** SERIAL; publication gate in progress.
- **IN:** Resolve KCR-001016 in requirements, schema, health, Coordinator, recovery, migration map, and slices; independent re-review.
- **OUT:** Feature implementation.
- **Depends on:** none.
- **Contract surfaces:** all canon.
- **Evidence:** strict TS; Prettier; per-finding traceability; independent author≠reviewer GO.
### KBN-010 — Threat, authorization, and constraint-impact gate
- **Owner:** coder3; independent `secrev`.
- **Mode:** SERIAL prerequisite of KBN-100.
- **Exclusive files:** Mos-selected threat/auth docs only.
- **IN:** Cross-workspace owners/principals/evidence; active membership; stale/forged health; approval forgery; fence monotonicity; audit retention; proposal target/audit-event forgery; service tokens; DB/Valkey outage.
- **OUT:** Runtime/schema edits.
- **Depends on:** KBN-000 independent re-review GO.
- **Contract surfaces:** schema constraints, health proof, exact errors, command-family authorization.
- **Evidence:** signed constraint-impact matrix; no unresolved schema-impact finding; SecReview pass.
### KBN-100 — Unified Drizzle schema and concrete N-1 migration
- **Owner:** **coder2**.
- **Mode:** SERIAL.
- **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**.
- **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.
### KBN-105 — Exact Gateway/MCP endpoint, DTO, and error freeze
- **Owner:** Mos + coder3 contract author; independent endpoint-alignment reviewer.
- **Mode:** SERIAL after KBN-100; prerequisite for KBN-110/120/130.
- **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.
- **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.
### KBN-115 — Recovery posture parser, mechanisms, and evidence
- **Owner:** **coder2**, lane-serial after KBN-100.
- **Mode:** PARALLEL with KBN-110/120/130 after KBN-105.
- **Exclusive files:** `packages/config/src/recovery-posture.ts`, `.spec.ts`, `docs/runbooks/kanban-postgres-recovery.md`; deployment-specific backup manifest changes are a separately enumerated Mos integration patch.
- **IN:** Wire normative `validateRecoveryPostureV1`; override audit; backup/WAL/PITR mechanism assertions; off-cluster encryption/failure-domain checks; restore and break-glass evidence procedure.
- **OUT:** SOT/gate/Coordinator policy knobs; DB business schema.
- **Depends on:** KBN-100, KBN-105.
- **Contract surfaces:** `recovery-posture.v1.ts` only.
- **Evidence:** impossible-combination tests; High-assurance weakening tests; selected-tier mechanism verification; restore and break-glass evidence; SecReview.
## 5. P1 — Thin native MVP
### KBN-110 — Workspace-safe domain, Gateway, MCP server, and proposal commands
- **Owner:** **coder3**.
- **Mode:** PARALLEL-GROUP P1-A after KBN-105.
- **Exclusive files:** ownership map, including all exact MCP server files listed there.
- **IN:** Workspace-safe repositories; project/task/dependency/tag/archive CRUD; transitions; exact owners; assignment/approval/link/artifact queries; submit/query/accept/reject change proposals; health endpoint; internal write-proof mint/revalidation; event/outbox atomicity; frozen DTOs/routes.
- **OUT:** Scheduling algorithm, web, CLI, DB schema.
- **Depends on:** KBN-100, KBN-105.
- **Contract surfaces:** all four TypeScript contracts and exact registry.
- **Evidence:** DTO/service/controller/integration tests; active-membership and no-oracle negatives; proposal cannot mutate directly; submission event is the new proposal's exact `change_proposal.submitted` event; acceptance links the executed normal command for the locked proposal and same workspace/target; missing, foreign-workspace, unrelated-proposal/target/command event negatives; exact failure mapping; endpoint registry; SecReview.
### KBN-120 — CLI, MCP client mapping, and generated projection
- **Owner:** **coder4**; first coder4 slice.
- **Mode:** PARALLEL-GROUP P1-A after KBN-105.
- **Exclusive files:** `packages/mosaic/src/commands/{kanban,tasks,projects}.ts`; `packages/mosaic/src/projections/**`; tests. **No `apps/gateway/src/mcp/**` edits.\*\*
- **IN:** Frozen query/mutation routes; proposal commands; compact context; generated `TASKS.md`; deliberate denial/transport/conflict handling.
- **OUT:** Gateway/MCP server, file importer, raw SQL/Valkey, Coordinator.
- **Depends on:** KBN-105; runtime integration later requires KBN-110.
- **Evidence:** contract fixtures; same revision; no import parser; same idempotency key on transport retry; 503 never auto-retried.
### KBN-130 — Writable Kanban/List and minimal Projects UI
- **Owner:** **coder5**.
- **Mode:** PARALLEL-GROUP P1-A after KBN-105.
- **Exclusive files:** web ownership map.
- **IN:** Workspace context; projects; tasks; tags; explicit archive; detail; accessible move/reorder; filters; dependency/readiness; owner/assignment/lease; audit; proposal visibility; conflict/loading/error/reconnect.
- **OUT:** Gateway/schema, Coordinator operations UI, migration UI.
- **Depends on:** KBN-105; runtime integration later requires KBN-110.
- **Evidence:** frozen contract mocks; real-Gateway journeys; keyboard/non-drag; tags/archive semantics; no-oracle tenant negatives; 503/transport/409 distinct UI.
### KBN-140 — P1 integration and situational gate
- **Owner:** Mos integration; independent reviewer/SecReview/Certifier.
- **Mode:** INTEGRATION-SERIAL.
- **IN:** KBN-110/120/130/115; unavoidable root exports only.
- **OUT:** P2 behavior.
- **Depends on:** KBN-110, KBN-120, KBN-130, KBN-115.
- **Evidence:** clean migration; web/CLI/MCP/projection revision parity; forged/expired health negatives; change-proposal event-chain success plus missing/foreign/unrelated-event negatives; tag/archive; tenant negatives; endpoint registry; author-independent review; Certifier pass.
## 6. P2 — Mechanical Coordinator
### KBN-200 — Pure deterministic decision engine
- **Owner:** **coder4**; second coder4 slice, strictly after KBN-120.
- **Mode:** SERIAL in coder4 lane.
- **Exclusive files:** `packages/coord/src/mechanical/**` and pure tests.
- **IN:** `MechanicalCoordinatorDecisionEngineV1`; complete immutable snapshots; eligibility/explanation; fairness/order; capability matching; expiry/retry/quarantine decisions.
- **OUT:** ID loading, PostgreSQL, Drizzle, Gateway, Valkey, health-proof minting, persistence, `recoverFromPostgres`, LLM calls.
- **Depends on:** KBN-140 (or Mos may release after KBN-120 + frozen types if no P1 semantic risk remains).
- **Evidence:** deterministic/property tests; snapshot completeness; no I/O/model imports; no authority methods.
### KBN-210 — Coordinator persistence/service adapter and approval-bound leases
- **Owner:** **coder3**.
- **Mode:** SERIAL after KBN-200.
- **Exclusive files:** Gateway `coord` and repositories.
- **IN:** `MechanicalCoordinatorServicePortV1`; snapshot loading; proposal persistence; manual/versioned policy approval; acquire by IDs; reload+lock task/assignment/approval/session; fresh txn-local write proof; atomic task fence increment; lease/ack/heartbeat/checkpoint/submit; durable retry/quarantine; outbox/Valkey wake; restart recovery.
- **OUT:** Pure algorithm, UI, DB schema.
- **Depends on:** KBN-110, KBN-200.
- **Evidence:** forged/stale approval rejection; target/session/version/expiry/policy checks; concurrent monotonic fences; same-workspace mismatch negatives; bigint precision; stale worker rejection; DB/Valkey faults; SecReview.
### KBN-220 — Coordinator operations UI
- **Owner:** **coder5**.
- **Mode:** after KBN-210 exact DTO freeze.
- **IN:** Roster; eligibility; persisted assignment state; approvals/overrides; exact lease/fence; durable retry/quarantine; role/gate/Certifier visibility.
- **OUT:** Scheduling decisions, schema, merge control for Certifier.
- **Depends on:** KBN-210.
- **Evidence:** authorized journeys; reason required; stale refresh; no Certifier merge; endpoint alignment/accessibility.
### KBN-230 — P2 concurrency/fault/gate integration
- **Owner:** Mos integration; independent reviewer/SecReview/Certifier.
- **Mode:** INTEGRATION-SERIAL.
- **Depends on:** KBN-200, KBN-210, KBN-220.
- **Evidence:** one lease; monotonic fences; exact relational mismatches rejected; expired proof; forged healthy; approval binding; restart; durable quarantine; outbox recovery; author≠reviewer; Certifier final/no merge.
## 7. P3 — Shadow migration and cutover
### KBN-300 — One-way importer dry-run/apply/verify
- **Owner:** **coder4**; third coder4 slice.
- **Mode:** after KBN-230.
- **Exclusive files:** `scripts/kanban-migration/import/**`.
- **IN:** Immutable jarvis-brain/Vikunja snapshots; deterministic mapping; source digest/lineage; Gateway writes; rejects; no dispatch.
- **OUT:** Bidirectional sync, direct DB/file canonical writes, unrelated brain data.
- **Depends on:** KBN-230.
- **Evidence:** idempotency; counts/fields; malformed/foreign rejects; no dispatch; SecReview.
### KBN-310 — Shadow reviewer UI
- **Owner:** **coder5**.
- **Mode:** PARALLEL-GROUP P3-A after KBN-300 report freeze.
- **IN:** Read-only counts/diffs/rejects/lineage/sign-off.
- **OUT:** Apply/cutover mutations.
- **Depends on:** KBN-300.
- **Evidence:** read-only and tenant tests; pagination/accessibility.
### KBN-320 — Cutover/rollback tooling
- **Owner:** **coder4**; fourth coder4 slice, after KBN-300.
- **Mode:** PARALLEL-GROUP P3-A with KBN-310.
- **Exclusive files:** `scripts/kanban-migration/cutover/**`.
- **IN:** Freeze assertion; backup/checksum; final delta; client switch; legacy writer/credential shutdown; rollback delta; stabilization.
- **OUT:** Destructive deletion, reverse sync, ungated production execution.
- **Depends on:** KBN-300.
- **Evidence:** fail-safe rehearsal; no dual writer; rollback authority; SecReview.
### KBN-330 — Migration rehearsal/reconciliation
- **Owner:** Mos + coder4 support + independent data reviewer.
- **Mode:** INTEGRATION-SERIAL.
- **Depends on:** KBN-310, KBN-320.
- **Evidence:** signed exceptions; selected-tier restore; backlog hold; no legacy changes; Certifier readiness.
### KBN-340 — Final cutover/stabilization
- **Owner:** Mos/control plane; owner-gated operation.
- **Mode:** SERIAL.
- **Depends on:** KBN-330 PASS and Jason authorization.
- **Evidence:** no legacy writer; scoped Gateway identities; no accidental dispatch; terminal green health/CI; Certifier evidence; owner retirement approval.
## 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** |
Mos alone releases slices and lifts the build hold after independent re-review GO.

View File

@@ -0,0 +1,206 @@
/**
* Mosaic Native Kanban — frozen health/error contract v1.
* Publication contract only; no runtime implementation is included here.
*
* PostgreSQL is the sole writable SOT. Public health DTOs are observations,
* never write authority. Only an internal transaction-local proof produced by
* the PostgreSQL adapter may authorize a mutation.
*/
export const KANBAN_CONTRACT_VERSION = '1.0.0' as const;
export const kanbanHealthStates = ['healthy', 'read-only-degraded', 'write-unavailable'] as const;
export type KanbanHealthState = (typeof kanbanHealthStates)[number];
interface KanbanHealthBaseV1 {
contractVersion: typeof KANBAN_CONTRACT_VERSION;
checkedAt: string;
/** Observation expires at this RFC 3339 instant; it still never authorizes writes. */
validUntil: string;
policyRevision: string;
reasons: string[];
}
export interface HealthyKanbanHealthResponseV1 extends KanbanHealthBaseV1 {
state: 'healthy';
readHealthProven: true;
writeHealthProven: true;
}
export interface ReadOnlyDegradedKanbanHealthResponseV1 extends KanbanHealthBaseV1 {
state: 'read-only-degraded';
readHealthProven: true;
writeHealthProven: false;
}
export interface WriteUnavailableKanbanHealthResponseV1 extends KanbanHealthBaseV1 {
state: 'write-unavailable';
readHealthProven: false;
writeHealthProven: false;
}
/** Public, discriminated observation. Contradictory combinations are unrepresentable. */
export type KanbanHealthResponseV1 =
| HealthyKanbanHealthResponseV1
| ReadOnlyDegradedKanbanHealthResponseV1
| WriteUnavailableKanbanHealthResponseV1;
/** Pure evaluation context. It cannot authorize a mutation. */
export interface KanbanEvaluationContextV1 {
contractVersion: typeof KANBAN_CONTRACT_VERSION;
workspaceId: string;
correlationId: string;
now: string;
policyRevision: string;
observedHealth: KanbanHealthResponseV1;
}
/**
* Non-exported brand: public DTO deserialization cannot construct this type.
* The PostgreSQL adapter mints it only after a fresh write probe inside the same
* transaction and validates checkedAt <= now < validUntil and policy revision.
*/
declare const postgresWriteHealthProofBrand: unique symbol;
export interface PostgresWriteHealthProofV1 {
readonly [postgresWriteHealthProofBrand]: true;
readonly source: 'postgres-transaction-local-write-probe';
readonly transactionId: string;
readonly checkedAt: string;
readonly validUntil: string;
readonly policyRevision: string;
}
/** Internal mutation context; MUST NOT appear in REST/MCP/CLI request DTOs. */
export interface InternalKanbanMutationContextV1 {
contractVersion: typeof KANBAN_CONTRACT_VERSION;
workspaceId: string;
correlationId: string;
causationId?: string;
idempotencyKey: string;
now: string;
expectedPolicyRevision: string;
writeProof: PostgresWriteHealthProofV1;
}
interface MutationFailureBaseV1 {
contractVersion: typeof KANBAN_CONTRACT_VERSION;
retryable: false;
requestOutcome: 'not_applied';
idempotencyKey: string;
correlationId: string;
message: string;
}
/** KCR-016: code/state pairing is exact and cannot cross-map. */
export interface ReadOnlyWriteHealthDenialV1 extends MutationFailureBaseV1 {
kind: 'deliberate_fail_closed_denial';
code: 'KANBAN_WRITE_HEALTH_UNPROVEN';
healthState: 'read-only-degraded';
checkedAt: string;
}
export interface WriteUnavailableDenialV1 extends MutationFailureBaseV1 {
kind: 'deliberate_fail_closed_denial';
code: 'KANBAN_WRITE_UNAVAILABLE';
healthState: 'write-unavailable';
checkedAt: string;
}
export type DeliberateWriteDenialV1 = ReadOnlyWriteHealthDenialV1 | WriteUnavailableDenialV1;
export const transportErrorCodes = [
'GATEWAY_UNREACHABLE',
'GATEWAY_TIMEOUT',
'UPSTREAM_BAD_GATEWAY',
] as const;
export type TransportErrorCode = (typeof transportErrorCodes)[number];
/** Client-normalized transport uncertainty; never an authoritative 503 body. */
export interface RetryableTransportErrorV1 {
contractVersion: typeof KANBAN_CONTRACT_VERSION;
kind: 'retryable_transport_error';
code: TransportErrorCode;
retryable: true;
requestOutcome: 'unknown';
/** Retry MUST reuse this exact key. */
idempotencyKey: string;
correlationId: string;
message: string;
}
export interface VersionConflictV1 {
contractVersion: typeof KANBAN_CONTRACT_VERSION;
kind: 'version_conflict';
code: 'AGGREGATE_VERSION_CONFLICT';
retryable: false;
requestOutcome: 'not_applied';
aggregateType: 'project' | 'mission' | 'milestone' | 'task' | 'change_proposal';
aggregateId: string;
expectedVersion: number;
actualVersion: number;
idempotencyKey: string;
correlationId: string;
message: string;
}
export type KanbanMutationFailureV1 =
| DeliberateWriteDenialV1
| RetryableTransportErrorV1
| VersionConflictV1;
export const kanbanHealthCapabilities: Readonly<
Record<KanbanHealthState, { canonicalReads: boolean; mutations: boolean }>
> = {
healthy: { canonicalReads: true, mutations: true },
'read-only-degraded': { canonicalReads: true, mutations: false },
'write-unavailable': { canonicalReads: false, mutations: false },
};
/** Exact HTTP/error normalization freeze; 503, transport, and 409 cannot cross-map. */
export const kanbanFailureHttpMapV1 = {
KANBAN_WRITE_HEALTH_UNPROVEN: {
httpStatus: 503,
kind: 'deliberate_fail_closed_denial',
requestOutcome: 'not_applied',
retryable: false,
},
KANBAN_WRITE_UNAVAILABLE: {
httpStatus: 503,
kind: 'deliberate_fail_closed_denial',
requestOutcome: 'not_applied',
retryable: false,
},
AGGREGATE_VERSION_CONFLICT: {
httpStatus: 409,
kind: 'version_conflict',
requestOutcome: 'not_applied',
retryable: false,
},
GATEWAY_UNREACHABLE: {
httpStatus: 502,
kind: 'retryable_transport_error',
requestOutcome: 'unknown',
retryable: true,
},
GATEWAY_TIMEOUT: {
httpStatus: 504,
kind: 'retryable_transport_error',
requestOutcome: 'unknown',
retryable: true,
},
UPSTREAM_BAD_GATEWAY: {
httpStatus: 502,
kind: 'retryable_transport_error',
requestOutcome: 'unknown',
retryable: true,
},
} as const;
/**
* Required negative contract tests:
* - contradictory state/proof booleans fail type/schema validation;
* - expired internal proof and policy mismatch deny before mutation;
* - Valkey-only liveness cannot mint PostgresWriteHealthProofV1;
* - public/caller-forged `healthy` cannot enter InternalKanbanMutationContextV1;
* - authoritative 503, transport 502/504/timeout, and 409 mappings are exhaustive.
*/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,419 @@
/**
* Mosaic Native Kanban — frozen Mechanical Coordinator contracts v1.
*
* The pure decision engine and persistence/orchestration service are separate.
* Neither surface can create scope, edit acceptance, waive gates, certify,
* merge, release a deployment, or close a provider issue.
*/
import type {
DeliberateWriteDenialV1,
InternalKanbanMutationContextV1,
KanbanEvaluationContextV1,
KanbanMutationFailureV1,
RetryableTransportErrorV1,
VersionConflictV1,
} from './health-state.v1.js';
export const COORDINATOR_CONTRACT_VERSION = '1.0.0' as const;
export type Uuid = string;
export type IsoTimestamp = string;
/** PostgreSQL bigint-safe decimal string; never a JavaScript number. */
export type FencingTokenV1 = string;
export const specialistRoles = [
'planning',
'enhance',
'coder',
'review',
'security-review',
'pr-monitor',
'certifier',
] as const;
export type SpecialistRole = (typeof specialistRoles)[number];
/** One vocabulary shared with task_assignment_state_v1 in the Drizzle schema. */
export const assignmentStates = [
'awaiting_approval',
'policy_pre_authorized',
'approved',
'rejected',
'leased',
'released',
'expired',
'superseded',
] as const;
export type AssignmentStateV1 = (typeof assignmentStates)[number];
export const readinessStates = [
'dependency-gated',
'schedule-gated',
'policy-gated',
'lease-available',
'leased',
'retry-delayed',
'exhausted',
'quarantined',
] as const;
export type ReadinessState = (typeof readinessStates)[number];
export interface RetryStateSnapshotV1 {
disposition: 'available' | 'retry_delayed' | 'quarantined' | 'exhausted';
attemptCount: number;
maxAttempts: number;
nextEligibleAt: IsoTimestamp | null;
idempotent: boolean;
terminalReason: string | null;
version: number;
}
export interface TaskEligibilitySnapshotV1 {
workspaceId: Uuid;
taskId: Uuid;
taskVersion: number;
projectId: Uuid;
projectActive: boolean;
missionId: Uuid | null;
missionActive: boolean;
status: 'ready';
priority: 'critical' | 'high' | 'medium' | 'low';
boardRank: string;
dueAt: IsoTimestamp | null;
notBeforeAt: IsoTimestamp | null;
createdAt: IsoTimestamp;
requiredRole: SpecialistRole;
requiredCapabilities: readonly string[];
blockingDependencies: readonly {
taskId: Uuid;
done: boolean;
completionConditionSatisfied: boolean;
}[];
releaseApproval: {
decisionId: Uuid;
approved: boolean;
policyRevision: string;
} | null;
activeLeaseId: Uuid | null;
retry: RetryStateSnapshotV1;
}
export interface AgentSessionSnapshotV1 {
workspaceId: Uuid;
agentId: Uuid;
sessionId: Uuid;
state: 'available' | 'busy';
roles: readonly SpecialistRole[];
capabilities: readonly string[];
capacity: number;
activeLeaseCount: number;
heartbeatAt: IsoTimestamp;
}
export interface EligibilityExplanationV1 {
taskId: Uuid;
eligible: boolean;
readiness: ReadinessState;
reasons: readonly {
gate:
| 'status'
| 'project'
| 'mission'
| 'dependency'
| 'schedule'
| 'retry'
| 'approval'
| 'lease'
| 'capability'
| 'capacity'
| 'health';
satisfied: boolean;
code: string;
detail: string;
}[];
policyRevision: string;
evaluatedAt: IsoTimestamp;
}
export interface AssignmentProposalDecisionV1 {
workspaceId: Uuid;
taskId: Uuid;
taskVersion: number;
targetAgentId: Uuid;
targetSessionId: Uuid;
specialistRole: SpecialistRole;
initialState: 'awaiting_approval' | 'policy_pre_authorized';
policyRevision: string;
explanation: EligibilityExplanationV1;
expiresAt: IsoTimestamp;
}
export interface AssignmentCycleSnapshotV1 {
context: KanbanEvaluationContextV1;
tasks: readonly TaskEligibilitySnapshotV1[];
sessions: readonly AgentSessionSnapshotV1[];
workspaceFairness: Readonly<Record<Uuid, number>>;
limit: number;
}
export interface AssignmentCycleDecisionV1 {
evaluatedTaskCount: number;
proposals: readonly AssignmentProposalDecisionV1[];
explanations: readonly EligibilityExplanationV1[];
}
export interface LeaseExpirySnapshotV1 {
workspaceId: Uuid;
taskId: Uuid;
taskVersion: number;
leaseId: Uuid;
assignmentId: Uuid;
sessionId: Uuid;
fencingToken: FencingTokenV1;
state: 'pending_ack' | 'active';
acknowledgeBy: IsoTimestamp;
expiresAt: IsoTimestamp;
lastHeartbeatAt: IsoTimestamp | null;
retry: RetryStateSnapshotV1;
}
export interface LeaseExpiryDecisionV1 {
leaseId: Uuid;
action: 'retain' | 'release' | 'retry' | 'quarantine' | 'exhaust';
reason: string;
nextEligibleAt: IsoTimestamp | null;
}
/** Pure package owned by KBN-200. It receives complete immutable snapshots. */
export interface MechanicalCoordinatorDecisionEngineV1 {
evaluateAssignmentCycle(snapshot: AssignmentCycleSnapshotV1): AssignmentCycleDecisionV1;
explainEligibility(
context: KanbanEvaluationContextV1,
task: TaskEligibilitySnapshotV1,
sessions: readonly AgentSessionSnapshotV1[],
): EligibilityExplanationV1;
decideLeaseExpiry(
context: KanbanEvaluationContextV1,
lease: LeaseExpirySnapshotV1,
): LeaseExpiryDecisionV1;
}
export interface PersistedAssignmentV1 {
assignmentId: Uuid;
workspaceId: Uuid;
taskId: Uuid;
taskVersion: number;
targetAgentId: Uuid;
targetSessionId: Uuid;
specialistRole: SpecialistRole;
state: AssignmentStateV1;
policyRevision: string;
proposedBy: { kind: 'user' | 'agent'; id: Uuid };
reason: string;
createdAt: IsoTimestamp;
expiresAt: IsoTimestamp;
}
export interface TaskLeaseV1 {
leaseId: Uuid;
workspaceId: Uuid;
taskId: Uuid;
taskVersion: number;
assignmentId: Uuid;
agentId: Uuid;
sessionId: Uuid;
state: 'pending_ack' | 'active';
fencingToken: FencingTokenV1;
attempt: number;
acquiredAt: IsoTimestamp;
acknowledgeBy: IsoTimestamp;
lastHeartbeatAt: IsoTimestamp | null;
expiresAt: IsoTimestamp;
}
interface ServiceCommandBaseV1 {
context: InternalKanbanMutationContextV1;
taskId: Uuid;
expectedTaskVersion: number;
}
export interface AcquireApprovedLeaseCommandV1 extends ServiceCommandBaseV1 {
assignmentId: Uuid;
approvalDecisionId: Uuid;
targetSessionId: Uuid;
leaseTtlSeconds: number;
}
export interface LeaseCommandV1 extends ServiceCommandBaseV1 {
leaseId: Uuid;
sessionId: Uuid;
fencingToken: FencingTokenV1;
}
export interface HeartbeatLeaseCommandV1 extends LeaseCommandV1 {
extendSeconds: number;
}
export interface CheckpointCommandV1 extends LeaseCommandV1 {
sequence: number;
resumableSummary: string;
artifactIds: readonly Uuid[];
contextUsagePercent: number;
}
export interface SubmitForReviewCommandV1 extends LeaseCommandV1 {
artifactIds: readonly Uuid[];
summary: string;
}
export interface ReleaseLeaseCommandV1 extends LeaseCommandV1 {
reason:
| 'worker_requested'
| 'ack_timeout'
| 'heartbeat_timeout'
| 'task_submitted'
| 'policy_revoked'
| 'shutdown';
}
export interface AssignmentCycleCommandV1 {
context: InternalKanbanMutationContextV1;
limit: number;
}
export interface ExpirySweepCommandV1 {
context: InternalKanbanMutationContextV1;
limit: number;
}
export interface RecoverCoordinatorCommandV1 {
context: InternalKanbanMutationContextV1;
}
interface CoordinatorRejectionBaseV1 {
kind: 'coordinator_rejection';
retryable: false;
requestOutcome: 'not_applied';
correlationId: Uuid;
idempotencyKey: string;
message: string;
}
export type CoordinatorPolicyRejectionV1 =
| (CoordinatorRejectionBaseV1 & { code: 'WORKSPACE_MISMATCH' })
| (CoordinatorRejectionBaseV1 & {
code: 'TASK_NOT_ELIGIBLE';
explanation: EligibilityExplanationV1;
})
| (CoordinatorRejectionBaseV1 & { code: 'APPROVAL_REQUIRED' })
| (CoordinatorRejectionBaseV1 & { code: 'APPROVAL_STALE' })
| (CoordinatorRejectionBaseV1 & { code: 'ASSIGNMENT_STALE' })
| (CoordinatorRejectionBaseV1 & { code: 'ASSIGNMENT_TARGET_MISMATCH' })
| (CoordinatorRejectionBaseV1 & { code: 'POLICY_REVISION_MISMATCH' })
| (CoordinatorRejectionBaseV1 & { code: 'ARTIFACT_WORKSPACE_MISMATCH' })
| (CoordinatorRejectionBaseV1 & { code: 'LEASE_ALREADY_ACTIVE' })
| (CoordinatorRejectionBaseV1 & { code: 'LEASE_NOT_FOUND' })
| (CoordinatorRejectionBaseV1 & { code: 'LEASE_NOT_ACTIVE' })
| (CoordinatorRejectionBaseV1 & {
code: 'ACK_DEADLINE_EXPIRED';
expiredAt: IsoTimestamp;
})
| (CoordinatorRejectionBaseV1 & {
code: 'FENCING_TOKEN_STALE';
currentFencingToken: FencingTokenV1;
})
| (CoordinatorRejectionBaseV1 & { code: 'SESSION_MISMATCH' })
| (CoordinatorRejectionBaseV1 & {
code: 'HEARTBEAT_EXPIRED';
expiredAt: IsoTimestamp;
})
| (CoordinatorRejectionBaseV1 & {
code: 'CHECKPOINT_SEQUENCE_CONFLICT';
currentSequence: number;
})
| (CoordinatorRejectionBaseV1 & { code: 'RETRY_EXHAUSTED' })
| (CoordinatorRejectionBaseV1 & {
code: 'NON_IDEMPOTENT_RETRY_REQUIRES_ORCHESTRATOR';
});
/** Explicit mapping to the Gateway mutation failure union; no arbitrary booleans. */
export type CoordinatorFailureV1 =
| DeliberateWriteDenialV1
| VersionConflictV1
| RetryableTransportErrorV1
| CoordinatorPolicyRejectionV1;
export interface CoordinatorSuccessV1<T> {
ok: true;
value: T;
correlationId: Uuid;
}
export interface CoordinatorFailureResultV1 {
ok: false;
failure: CoordinatorFailureV1;
}
export type CoordinatorResultV1<T> = CoordinatorSuccessV1<T> | CoordinatorFailureResultV1;
export interface ExpirySweepResultV1 {
examined: number;
released: readonly Uuid[];
retryScheduled: readonly Uuid[];
quarantined: readonly Uuid[];
exhausted: readonly Uuid[];
}
export interface RestartRecoveryResultV1 {
activeLeaseIds: readonly Uuid[];
expiredLeaseIds: readonly Uuid[];
pendingAssignmentIds: readonly Uuid[];
pendingOutboxEventIds: readonly Uuid[];
}
/** Persistence/Gateway adapter owned by KBN-210. */
export interface MechanicalCoordinatorServicePortV1 {
/** Loads immutable snapshots, invokes pure engine, and persists proposals atomically. */
runAssignmentCycle(
command: AssignmentCycleCommandV1,
): Promise<CoordinatorResultV1<{ assignments: readonly PersistedAssignmentV1[] }>>;
/** Query path loads by ID; public health observation cannot authorize mutation. */
getEligibilityExplanation(
context: KanbanEvaluationContextV1,
taskId: Uuid,
): Promise<CoordinatorResultV1<EligibilityExplanationV1>>;
/**
* Accepts IDs only. Implementation reloads and locks assignment + approval +
* task + target session in PostgreSQL, then verifies workspace, task version,
* target agent/session, state, expiry, policy revision, and current approval.
*/
acquireApprovedLease(
command: AcquireApprovedLeaseCommandV1,
): Promise<CoordinatorResultV1<TaskLeaseV1>>;
acknowledgeLease(command: LeaseCommandV1): Promise<CoordinatorResultV1<TaskLeaseV1>>;
heartbeatLease(command: HeartbeatLeaseCommandV1): Promise<CoordinatorResultV1<TaskLeaseV1>>;
appendCheckpoint(
command: CheckpointCommandV1,
): Promise<CoordinatorResultV1<{ checkpointId: Uuid }>>;
submitForReview(
command: SubmitForReviewCommandV1,
): Promise<CoordinatorResultV1<{ taskVersion: number; status: 'in_review' }>>;
releaseLease(command: ReleaseLeaseCommandV1): Promise<CoordinatorResultV1<{ released: true }>>;
expireAndRecover(
command: ExpirySweepCommandV1,
): Promise<CoordinatorResultV1<ExpirySweepResultV1>>;
recoverFromPostgres(
command: RecoverCoordinatorCommandV1,
): Promise<CoordinatorResultV1<RestartRecoveryResultV1>>;
}
/** Compile-time mapping guarantee: Coordinator Gateway failures are Kanban failures or exact policy rejections. */
export function isKanbanMutationFailureV1(
failure: CoordinatorFailureV1,
): failure is KanbanMutationFailureV1 {
return (
failure.kind === 'deliberate_fail_closed_denial' ||
failure.kind === 'retryable_transport_error' ||
failure.kind === 'version_conflict'
);
}

View File

@@ -0,0 +1,369 @@
/**
* Mosaic Native Kanban — frozen recovery-posture contract v1.
* Recovery posture is configurable; SOT, write-health, Coordinator authority,
* and gate semantics are not fields and cannot be overridden.
*/
export const RECOVERY_POSTURE_CONTRACT_VERSION = '1.0.0' as const;
export const recoveryTiers = ['lite', 'standard', 'high-assurance'] as const;
export type RecoveryTier = (typeof recoveryTiers)[number];
export interface OffClusterStorageV1 {
required: true;
encrypted: true;
separateFailureDomain: true;
minimumCopies: number;
storageClass: 'encrypted-object-storage' | 'encrypted-backup-target';
}
export interface RecoveryPostureV1 {
contractVersion: typeof RECOVERY_POSTURE_CONTRACT_VERSION;
tier: RecoveryTier;
targetRpoMinutes: number;
targetRtoMinutes: number;
baseBackupIntervalHours: number;
/** null means WAL archival/PITR is disabled. */
walArchiveIntervalMinutes: number | null;
/** 0 means PITR is disabled. */
pitrRetentionDays: number;
restoreTestIntervalDays: number;
breakGlassDrillIntervalDays: number;
offClusterStorage: OffClusterStorageV1;
}
export const recoveryPostureDefaults: Readonly<Record<RecoveryTier, RecoveryPostureV1>> = {
lite: {
contractVersion: RECOVERY_POSTURE_CONTRACT_VERSION,
tier: 'lite',
targetRpoMinutes: 24 * 60,
targetRtoMinutes: 24 * 60,
baseBackupIntervalHours: 24,
walArchiveIntervalMinutes: null,
pitrRetentionDays: 0,
restoreTestIntervalDays: 90,
breakGlassDrillIntervalDays: 365,
offClusterStorage: {
required: true,
encrypted: true,
separateFailureDomain: true,
minimumCopies: 1,
storageClass: 'encrypted-backup-target',
},
},
standard: {
contractVersion: RECOVERY_POSTURE_CONTRACT_VERSION,
tier: 'standard',
targetRpoMinutes: 60,
targetRtoMinutes: 8 * 60,
baseBackupIntervalHours: 24,
walArchiveIntervalMinutes: 15,
pitrRetentionDays: 14,
restoreTestIntervalDays: 90,
breakGlassDrillIntervalDays: 180,
offClusterStorage: {
required: true,
encrypted: true,
separateFailureDomain: true,
minimumCopies: 1,
storageClass: 'encrypted-object-storage',
},
},
'high-assurance': {
contractVersion: RECOVERY_POSTURE_CONTRACT_VERSION,
tier: 'high-assurance',
targetRpoMinutes: 15,
targetRtoMinutes: 4 * 60,
baseBackupIntervalHours: 24,
walArchiveIntervalMinutes: 5,
pitrRetentionDays: 35,
restoreTestIntervalDays: 30,
breakGlassDrillIntervalDays: 90,
offClusterStorage: {
required: true,
encrypted: true,
separateFailureDomain: true,
minimumCopies: 1,
storageClass: 'encrypted-object-storage',
},
},
};
/** Shape schema. Normative cross-field semantics are enforced by validateRecoveryPostureV1. */
export const recoveryPostureJsonSchemaV1 = {
$id: 'https://mosaicstack.dev/contracts/recovery-posture.v1.schema.json',
$schema: 'https://json-schema.org/draft/2020-12/schema',
type: 'object',
additionalProperties: false,
required: [
'contractVersion',
'tier',
'targetRpoMinutes',
'targetRtoMinutes',
'baseBackupIntervalHours',
'walArchiveIntervalMinutes',
'pitrRetentionDays',
'restoreTestIntervalDays',
'breakGlassDrillIntervalDays',
'offClusterStorage',
],
properties: {
contractVersion: { const: RECOVERY_POSTURE_CONTRACT_VERSION },
tier: { enum: recoveryTiers },
targetRpoMinutes: { type: 'integer', minimum: 1 },
targetRtoMinutes: { type: 'integer', minimum: 1 },
baseBackupIntervalHours: { type: 'integer', minimum: 1 },
walArchiveIntervalMinutes: {
anyOf: [{ type: 'integer', minimum: 1 }, { type: 'null' }],
},
pitrRetentionDays: { type: 'integer', minimum: 0 },
restoreTestIntervalDays: { type: 'integer', minimum: 1 },
breakGlassDrillIntervalDays: { type: 'integer', minimum: 1 },
offClusterStorage: {
type: 'object',
additionalProperties: false,
required: ['required', 'encrypted', 'separateFailureDomain', 'minimumCopies', 'storageClass'],
properties: {
required: { const: true },
encrypted: { const: true },
separateFailureDomain: { const: true },
minimumCopies: { type: 'integer', minimum: 1 },
storageClass: {
enum: ['encrypted-object-storage', 'encrypted-backup-target'],
},
},
},
},
} as const;
export const recoveryValidationCodes = [
'INVALID_SHAPE',
'UNKNOWN_FIELD',
'PITR_REQUIRES_WAL',
'WAL_REQUIRES_PITR',
'RPO_BETTER_THAN_MECHANISM',
'OFF_CLUSTER_REQUIRED',
'HIGH_ASSURANCE_WEAKENED',
] as const;
export type RecoveryValidationCode = (typeof recoveryValidationCodes)[number];
export interface RecoveryValidationIssueV1 {
code: RecoveryValidationCode;
path: string;
message: string;
}
export type RecoveryValidationResultV1 =
| { ok: true; value: RecoveryPostureV1 }
| { ok: false; issues: RecoveryValidationIssueV1[] };
const topLevelFields = new Set([
'contractVersion',
'tier',
'targetRpoMinutes',
'targetRtoMinutes',
'baseBackupIntervalHours',
'walArchiveIntervalMinutes',
'pitrRetentionDays',
'restoreTestIntervalDays',
'breakGlassDrillIntervalDays',
'offClusterStorage',
]);
const storageFields = new Set([
'required',
'encrypted',
'separateFailureDomain',
'minimumCopies',
'storageClass',
]);
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function isPositiveInteger(value: unknown): value is number {
return Number.isInteger(value) && Number(value) > 0;
}
function isNonnegativeInteger(value: unknown): value is number {
return Number.isInteger(value) && Number(value) >= 0;
}
/**
* Normative parser/refinement. Deployment code MUST call this function (or a
* byte-for-byte behaviorally equivalent generated validator), not JSON Schema
* shape validation alone.
*/
export function validateRecoveryPostureV1(input: unknown): RecoveryValidationResultV1 {
const issues: RecoveryValidationIssueV1[] = [];
if (!isRecord(input)) {
return {
ok: false,
issues: [{ code: 'INVALID_SHAPE', path: '$', message: 'posture must be an object' }],
};
}
for (const key of Object.keys(input)) {
if (!topLevelFields.has(key)) {
issues.push({ code: 'UNKNOWN_FIELD', path: `$.${key}`, message: 'unknown field' });
}
}
const tier = input['tier'];
const storage = input['offClusterStorage'];
const integerFields = [
'targetRpoMinutes',
'targetRtoMinutes',
'baseBackupIntervalHours',
'restoreTestIntervalDays',
'breakGlassDrillIntervalDays',
] as const;
if (input['contractVersion'] !== RECOVERY_POSTURE_CONTRACT_VERSION) {
issues.push({
code: 'INVALID_SHAPE',
path: '$.contractVersion',
message: `must equal ${RECOVERY_POSTURE_CONTRACT_VERSION}`,
});
}
if (!recoveryTiers.includes(tier as RecoveryTier)) {
issues.push({ code: 'INVALID_SHAPE', path: '$.tier', message: 'unknown recovery tier' });
}
for (const field of integerFields) {
if (!isPositiveInteger(input[field])) {
issues.push({
code: 'INVALID_SHAPE',
path: `$.${field}`,
message: 'must be a positive integer',
});
}
}
if (!isNonnegativeInteger(input['pitrRetentionDays'])) {
issues.push({
code: 'INVALID_SHAPE',
path: '$.pitrRetentionDays',
message: 'must be a nonnegative integer',
});
}
if (
input['walArchiveIntervalMinutes'] !== null &&
!isPositiveInteger(input['walArchiveIntervalMinutes'])
) {
issues.push({
code: 'INVALID_SHAPE',
path: '$.walArchiveIntervalMinutes',
message: 'must be null or a positive integer',
});
}
if (!isRecord(storage)) {
issues.push({
code: 'INVALID_SHAPE',
path: '$.offClusterStorage',
message: 'must be an object',
});
} else {
for (const key of Object.keys(storage)) {
if (!storageFields.has(key)) {
issues.push({
code: 'UNKNOWN_FIELD',
path: `$.offClusterStorage.${key}`,
message: 'unknown field',
});
}
}
if (
storage['required'] !== true ||
storage['encrypted'] !== true ||
storage['separateFailureDomain'] !== true
) {
issues.push({
code: 'OFF_CLUSTER_REQUIRED',
path: '$.offClusterStorage',
message: 'storage must be required, encrypted, and in a separate failure domain',
});
}
if (!isPositiveInteger(storage['minimumCopies'])) {
issues.push({
code: 'INVALID_SHAPE',
path: '$.offClusterStorage.minimumCopies',
message: 'must be a positive integer',
});
}
if (
storage['storageClass'] !== 'encrypted-object-storage' &&
storage['storageClass'] !== 'encrypted-backup-target'
) {
issues.push({
code: 'INVALID_SHAPE',
path: '$.offClusterStorage.storageClass',
message: 'unsupported storage class',
});
}
}
const wal = input['walArchiveIntervalMinutes'];
const pitr = input['pitrRetentionDays'];
if (pitr !== 0 && wal === null) {
issues.push({
code: 'PITR_REQUIRES_WAL',
path: '$.pitrRetentionDays',
message: 'PITR retention requires WAL archival',
});
}
if (wal !== null && pitr === 0) {
issues.push({
code: 'WAL_REQUIRES_PITR',
path: '$.walArchiveIntervalMinutes',
message: 'WAL archival requires positive PITR retention',
});
}
if (
isPositiveInteger(input['targetRpoMinutes']) &&
isPositiveInteger(input['baseBackupIntervalHours']) &&
(wal === null || isPositiveInteger(wal))
) {
const mechanismMinutes = wal === null ? input['baseBackupIntervalHours'] * 60 : wal;
if (mechanismMinutes > input['targetRpoMinutes']) {
issues.push({
code: 'RPO_BETTER_THAN_MECHANISM',
path: '$.targetRpoMinutes',
message: `configured mechanism can only support ${mechanismMinutes} minutes`,
});
}
}
if (tier === 'high-assurance') {
const weakened =
!isPositiveInteger(input['targetRpoMinutes']) ||
input['targetRpoMinutes'] > 15 ||
!isPositiveInteger(input['targetRtoMinutes']) ||
input['targetRtoMinutes'] > 4 * 60 ||
!isPositiveInteger(input['baseBackupIntervalHours']) ||
input['baseBackupIntervalHours'] > 24 ||
!isPositiveInteger(wal) ||
wal > 5 ||
!isNonnegativeInteger(pitr) ||
pitr < 35 ||
!isPositiveInteger(input['restoreTestIntervalDays']) ||
input['restoreTestIntervalDays'] > 30 ||
!isPositiveInteger(input['breakGlassDrillIntervalDays']) ||
input['breakGlassDrillIntervalDays'] > 90;
if (weakened) {
issues.push({
code: 'HIGH_ASSURANCE_WEAKENED',
path: '$',
message: 'high-assurance posture may be strengthened but not weakened',
});
}
}
if (issues.length > 0) return { ok: false, issues };
return { ok: true, value: input as unknown as RecoveryPostureV1 };
}
export interface RecoveryPostureOverrideAuditV1 {
actorId: string;
reason: string;
effectiveAt: string;
policyRevision: string;
previous: RecoveryPostureV1;
next: RecoveryPostureV1;
}

View File

@@ -0,0 +1,16 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"noEmit": true,
"incremental": false,
"declaration": false,
"declarationMap": false,
"sourceMap": false,
"baseUrl": ".",
"paths": {
"drizzle-orm": ["../../packages/db/node_modules/drizzle-orm/index.d.ts"],
"drizzle-orm/pg-core": ["../../packages/db/node_modules/drizzle-orm/pg-core/index.d.ts"]
}
},
"include": ["contracts/*.ts"]
}