This commit was merged in pull request #752.
This commit is contained in:
36
docs/native-kanban-sot/DOCUMENTATION-CHECKLIST.md
Normal file
36
docs/native-kanban-sot/DOCUMENTATION-CHECKLIST.md
Normal 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-001–016 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.
|
||||
64
docs/native-kanban-sot/INDEX.md
Normal file
64
docs/native-kanban-sot/INDEX.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Native Kanban/SOT Canon
|
||||
|
||||
**Status:** KCR-001–016 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 P0–P3 requirements, all seven ratified decisions, fixed invariants, thin MVP, recovery tiers, non-goals, and per-requirement acceptance criteria |
|
||||
| [`MISSION-MANIFEST.md`](./MISSION-MANIFEST.md) | Mission/authority boundaries, exact role chain, gate model, mandatory SecReview triggers, Certifier final/no-merge rule, and collision-free slice ownership |
|
||||
| [`TASKS.md`](./TASKS.md) | Dependency-ordered, bounded P0–P3 slices with IN/OUT scope, dependencies, shared contracts, file ownership, evidence, and USC coder2/3/4/5 parallelization |
|
||||
| [`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-001–016 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-001–016 traceability are recorded in the issue scratchpad and linked review reports.
|
||||
- Independent re-review returned GO with KCR-001–016 closed; implementation remains held until canon merge and the dependency-ordered KBN prerequisites complete.
|
||||
195
docs/native-kanban-sot/MISSION-MANIFEST.md
Normal file
195
docs/native-kanban-sot/MISSION-MANIFEST.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# Mission Manifest — Mosaic Native Kanban and Canonical Task SOT P0–P3
|
||||
|
||||
**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.
|
||||
219
docs/native-kanban-sot/SHARED-CONTRACT.md
Normal file
219
docs/native-kanban-sot/SHARED-CONTRACT.md
Normal 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` (1–128 chars). Existing-aggregate mutations also require `If-Match-Version` (positive integer); create and privileged assignment-cycle requests are the only exceptions, while proposal submission carries `expectedTargetVersion` in its body. Body workspace fields are forbidden. Tenant denial follows one 404/403 policy without foreign existence detail.
|
||||
|
||||
```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-001–016. Mos alone releases waves and serializes integration roots.
|
||||
261
docs/native-kanban-sot/TASKS.md
Normal file
261
docs/native-kanban-sot/TASKS.md
Normal file
@@ -0,0 +1,261 @@
|
||||
# Native Kanban/SOT P0–P3 — 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-001–016 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.
|
||||
206
docs/native-kanban-sot/contracts/health-state.v1.ts
Normal file
206
docs/native-kanban-sot/contracts/health-state.v1.ts
Normal 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.
|
||||
*/
|
||||
1294
docs/native-kanban-sot/contracts/kanban-schema.v1.ts
Normal file
1294
docs/native-kanban-sot/contracts/kanban-schema.v1.ts
Normal file
File diff suppressed because it is too large
Load Diff
419
docs/native-kanban-sot/contracts/mechanical-coordinator.v1.ts
Normal file
419
docs/native-kanban-sot/contracts/mechanical-coordinator.v1.ts
Normal 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'
|
||||
);
|
||||
}
|
||||
369
docs/native-kanban-sot/contracts/recovery-posture.v1.ts
Normal file
369
docs/native-kanban-sot/contracts/recovery-posture.v1.ts
Normal 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;
|
||||
}
|
||||
16
docs/native-kanban-sot/tsconfig.json
Normal file
16
docs/native-kanban-sot/tsconfig.json
Normal 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"]
|
||||
}
|
||||
Reference in New Issue
Block a user