24 KiB
Independent Review — Native Kanban/SOT Canon
Reviewer: enhance-sol (independent of author planner-sol)
Date: 2026-07-13
Review mode: design/contract only; read-only against the staged canon
Source plan: /home/hermes/agent-work/planning/mosaic-native-kanban-sot-plan.md (sha256:96ea4fb91436ec9a53f371d27276e27f62ecf817662599ff9152df0db55296e5)
Canon reviewed: every listed artifact under /home/hermes/agent-work/planning/kanban-canon/, including the four TypeScript contracts; the author scratchpad was also read as validation context.
Executive verdict
NO-GO
The canon is not freeze-ready. I found 8 BLOCKERs, 7 MAJORs, and 1 MINOR. The prose preserves the ratified authority model well, but the frozen types/schema leave concrete fail-closed, approval, fencing, tenant, outage-proposal, migration, and parallelization gaps. Those gaps would force implementation lanes either to invent contract semantics or to ship paths that violate fixed invariants.
Blocking findings
- Health/write authorization can be represented as contradictory, stale, or caller-asserted state.
- Coordinator failures collapse authoritative denial, unknown transport outcome, and version conflict into one permissive shape.
- Assignment proposals and approval proofs have no authoritative relational binding; lease acquisition accepts a forgeable proof DTO.
- Fencing uniqueness is present, but monotonic fencing and same-task lease/checkpoint binding are not.
- Workspace-safe accountable-owner, assignment-principal, and evidence/artifact relationships are not frozen.
- Attributable post-recovery proposals have neither a canonical table nor command contract.
- The slice graph starts schema/UI work before prerequisite threat and exact API/DTO freezes and contradicts coder4 lane order.
- P0 claims a migration map while publishing only generic rules; the concrete N-1 transition from current
origin/mainis absent.
Findings
KCR-001 — BLOCKER — “Healthy” is not a proof and can be contradictory or stale
Location
contracts/health-state.v1.ts:21-31—KanbanHealthResponseV1permits every combination ofstate,readHealthProven, andwriteHealthProven.contracts/mechanical-coordinator.v1.ts:40-49—CoordinatorContextV1accepts a caller-suppliedhealthStateenum only.contracts/mechanical-coordinator.v1.ts:255-293— every Coordinator operation, including mutating operations, accepts that context.SHARED-CONTRACT.md:171-184— mutations are allowed only after live PostgreSQL read/write probes.
Violation
Fixed invariant 2 / REQ-SOT-002: mutations must fail closed unless write health is positively proven. The current type permits { state: 'healthy', writeHealthProven: false }, and the Coordinator mutation boundary can be invoked with a stale or fabricated { healthState: 'healthy' }. A Valkey/client-derived enum could therefore be mistaken for write authorization.
Minimal fix
- Make
KanbanHealthResponseV1a discriminated union with only these legal combinations:healthy => read=true/write=true,read-only-degraded => read=true/write=false, andwrite-unavailable => read=false/write=false. - Do not accept write authority from a public DTO. Require Gateway/domain code to obtain and revalidate a fresh internal PostgreSQL write-health proof at mutation time (including
checkedAt, bounded validity/policy revision, and transaction-local enforcement). - Split pure evaluation context from mutation context; mutation methods must accept only an unforgeable/internal healthy context or perform the probe themselves.
- Add negative contract tests for contradictory state, expired proof, Valkey-only liveness, and caller-forged
healthy.
KCR-002 — BLOCKER — Coordinator error shape can conflate denial, unknown outcome, and conflict
Location
contracts/mechanical-coordinator.v1.ts:184-216— oneCoordinatorFailureV1allows every code to pair with arbitraryretryableand eitherrequestOutcomevalue.contracts/health-state.v1.ts:51-106— the Gateway health contract correctly distinguishes deliberate denial, transport uncertainty, and version conflict.SHARED-CONTRACT.md:177-216— frozen client semantics require those cases not to be conflated.
Violation
Charter E and REQ-SOT-002. The current Coordinator result can legally encode WRITE_HEALTH_UNPROVEN as retryable: true, requestOutcome: 'unknown', or VERSION_CONFLICT as retryable. That permits blind retry or a false “unknown” outcome after an authoritative fail-closed denial.
Minimal fix
Replace CoordinatorFailureV1 with a discriminated union keyed by code/kind:
- deliberate health denial:
not_applied,retryable:false; - version conflict:
not_applied,retryable:false, current version; - stale fence/session/eligibility/approval failures: exact non-retry semantics;
- transport failure: a separate
retryable_transport_error,unknown, same idempotency key.
Reuse or map explicitly to KanbanMutationFailureV1, and add exhaustive client tests proving 503 authoritative bodies, 502/504/timeouts, and 409 cannot cross-map.
KCR-003 — BLOCKER — Approval proof is forgeable and is not linked to the persisted proposal
Location
contracts/mechanical-coordinator.v1.ts:107-137— proposal and approval DTOs.contracts/mechanical-coordinator.v1.ts:265-270—acquireApprovedLeaseaccepts the entireApprovalProofV1by value.contracts/kanban-schema.v1.ts:650-688—task_assignmentshas no proposal expiry, task version, session binding, or proposal/approval FK.contracts/kanban-schema.v1.ts:823-863—approval_decisionscan target only a task or mission and has no proposal/assignment relation.SHARED-CONTRACT.md:128-137— lease acquisition requires authoritative approval under the exact policy revision.
Violation
Fixed invariant 5 and REQ-COORD-002/003. A caller can construct an ApprovalProofV1; the schema cannot prove that it belongs to the proposal, workspace, task version, agent/session, unexpired policy revision, or still-current approval. The DTO state vocabulary (awaiting_approval | policy_pre_authorized) also does not map directly to the persisted assignment states (proposed | approved | ...).
Minimal fix
Persist one authoritative proposal/assignment identity with task version, target agent/session, expiry, state, and policy revision. Add a workspace-aware approval relation to that identity. Change lease acquisition to accept IDs, then reload and lock proposal + approval + task inside PostgreSQL and verify workspace, current version, target session, state, expiry, and policy revision before creating the lease. Freeze one state vocabulary across schema and DTOs.
KCR-004 — BLOCKER — Fencing is unique but not monotonically increasing; relational binding is incomplete
Location
contracts/kanban-schema.v1.ts:694-743—task_leaseshas positive/unique fencing tokens but no monotonic per-task counter.contracts/kanban-schema.v1.ts:748-784— checkpoints independently carry task, lease, and fencing token.contracts/kanban-schema.v1.ts:905-910— token equality is deferred to prose; same-task lease binding is not stated.contracts/mechanical-coordinator.v1.ts:140-175— worker commands depend on fencing safety.
Violation
Fixed invariant 12 / REQ-COORD-003. Uniqueness permits token 10 followed by token 9. A lease can reference assignment A while naming task B in the same workspace, and a checkpoint can reference lease A while naming task B. bigint(..., { mode: 'number' }) also eventually loses integer precision in JavaScript.
Minimal fix
Add a durable per-task fencing counter (or equivalent PostgreSQL sequence row) incremented atomically under task lock and use the returned value for every new lease. Add workspace-aware composite constraints tying lease to its exact task+assignment and checkpoint to exact task+lease+fence. Use bigint-safe representation (bigint/serialized decimal), and test monotonicity, concurrent claims, stale lower tokens, and mismatched same-workspace IDs.
KCR-005 — BLOCKER — Hard tenant boundary is not frozen for several polymorphic relationships
Location
contracts/kanban-schema.v1.ts:315-318and475-478— project/task accountable owners are unvalidated(kind, text id)pairs.contracts/kanban-schema.v1.ts:659-663— assignment principals are unvalidated(kind, text id)pairs.contracts/kanban-schema.v1.ts:758and841— checkpoint/evidence artifact relationships are JSON arrays without workspace-aware FKs.SHARED-CONTRACT.md:89-96— only selected polymorphic checks are delegated to domain transactions; owner/principal/evidence checks are not included.REQUIREMENTS.md:101-108— every relationship must reject cross-workspace IDs.
Violation
Fixed invariant 7 / REQ-TEN-001 and REQ-ID-001. The frozen schema can name a team or agent from another workspace as owner/assignee, and can embed foreign-workspace artifact IDs in checkpoint or approval evidence arrays. A global user ID is also insufficient without active workspace membership validation.
Minimal fix
Use workspace-aware owner/assignment join tables or separate nullable user/team/agent columns with exactly-one checks and composite FKs where possible. Model checkpoint/evidence artifact links as workspace-scoped join rows, or freeze explicit transaction checks for every ID. Require active workspace membership for user principals and workspace-agent/session consistency for agent principals. Add DB/repository/API/Coordinator cross-workspace negative tests without existence oracles.
KCR-006 — BLOCKER — Post-recovery outage proposals have no canonical persistence or command surface
Location
REQUIREMENTS.md:93-99— proposal submission and authorized accept/reject are required.SHARED-CONTRACT.md:26-29— outage notes may return only as authenticated proposals.SHARED-CONTRACT.md:243-267— the thin command/query contract contains no proposal submit/get/accept/reject operations.contracts/kanban-schema.v1.ts:1-916— no proposal table captures proposed command, target/version, attribution, lifecycle, or decision.TASKS.md:99-108— KBN-110 does not own an outage-proposal command path.
Violation
Fixed invariant 4 / REQ-SOT-004. An implementation lane would have to invent storage or misuse artifacts/approval gates. Either path risks silently applying an outage note or creating shadow state.
Minimal fix
Add a workspace-scoped change_proposals/outage_proposals contract with authenticated proposer, source note digest, target aggregate, expected version, proposed typed command/payload, pending/accepted/rejected state, decision actor/reason/time, idempotency key, and audit linkage. Add explicit submit/query/accept/reject Gateway commands. Acceptance must execute the normal command in a healthy transaction; a proposal itself can never claim, order, satisfy a gate, or mutate the target.
KCR-007 — BLOCKER — Parallel slice ordering is not freeze-safe and contains a direct lane-order contradiction
Location
TASKS.md:43-60— dependency graph makes KBN-010 and KBN-100 siblings.TASKS.md:88-97— KBN-100 nevertheless depends on KBN-010 threat findings that alter constraints.SHARED-CONTRACT.md:243andINDEX.md:44-50— exact route names/DTO placement remain unresolved.TASKS.md:110-130— KBN-120/130 depend on a frozen endpoint/DTO contract, while mocks may begin before KBN-110 lands.TASKS.md:145-153— KBN-200 says lane-serial after KBN-120.TASKS.md:248-254— wave table runs KBN-200 before KBN-120.
Violation
Charter C and the mandatory freeze-before-parallelize gate. Schema can begin before tenant/threat findings are complete; web/CLI consumers have only semantic operations, not exact DTO/endpoint contracts; coder4 has two opposite legal orders. This does not create same-file edits immediately, but it guarantees contract invention or rework across active lanes.
Minimal fix
- Make KBN-010 (or an explicit constraint-impact gate from it) a completed prerequisite of KBN-100.
- Add a small serialized KBN-105 endpoint/DTO/endpoint-registry freeze, with exact request/response/error DTOs, before KBN-120 and KBN-130 implementation.
- Choose one coder4 lane order and use it consistently in slice text, graph, and wave table.
- Name the exact MCP-owned files or assign their Gateway changes to coder3 before coder4 starts.
KCR-008 — BLOCKER — Claimed P0 migration map is absent; concrete N-1 hazards remain unresolved
Location
MISSION-MANIFEST.md:153-157— P0 says to publish a migration map and states the build hold is lifted at line 3.SHARED-CONTRACT.md:101-121— only generic expand/backfill/contract rules are supplied.contracts/kanban-schema.v1.ts:1-916— target-state declarations reuse live table names and make target fields required.- Current foundation evidence:
origin/main:packages/db/src/schema.ts:120-301has no workspace keys, nullable project/mission links, legacy text status vocabularies,tasks.tags,tasks.assignee,tasks.due_date, mission JSON milestones/config,mission_tasks.status, and legacy agent fields.
Violation
Charter D / REQ-MIG-001 and the P0 exit claim. The generic rule is correct, but coder2 lacks the required field-by-field transition map. A direct Drizzle reconciliation could attempt type narrowing/status conversion, add required workspace/project/owner columns too early, or drop legacy columns before N-1 readers and writers are retired.
Minimal fix
Publish a concrete current-main delta map before lifting the hold. For each existing table/column, specify expand, backfill, compatibility read/write, switch, and contract release. At minimum cover:
- nullable-first
workspace_id, required project/owner fields, and workspace backfill; - legacy task/project/mission status aliases or shadow columns before v1 emission;
mission_tasks.statusread retirement and write-source prohibition;- mapping/retention for tags, assignee, due date, mission description/config/milestones, and agent fields;
- current milestone circular FK ordering;
- empty, production-shape, partial-resume, and rollback/downgrade tests already named in §4.
Explicitly require legacy columns to remain in the unified Drizzle declaration during the expand/N-1 window.
KCR-009 — MAJOR — Dependency uniqueness permits parallel duplicate edges
Location
contracts/kanban-schema.v1.ts:561-567— unique key includesdependencyType.SHARED-CONTRACT.md:47-49— calls for a unique directed edge.REQUIREMENTS.md:142-149— duplicate edge attempts must fail.
Violation
REQ-DEP-001. The same predecessor/successor pair can be inserted three times, once per dependency type. That is not a unique directed edge and complicates readiness semantics.
Minimal fix
Make (workspace_id, predecessor_task_id, successor_task_id) unique independent of type, or explicitly redefine the requirement as one edge per type and freeze deterministic multi-edge completion semantics. The source plan says unique directed edge, so the former is the minimal faithful fix.
KCR-010 — MAJOR — Same-workspace planning relationships can contradict the project hierarchy
Location
contracts/kanban-schema.v1.ts:325—projects.currentMilestoneIdhas no FK in the declaration.contracts/kanban-schema.v1.ts:427-448— mission/milestone association checks workspace but not common project.contracts/kanban-schema.v1.ts:490-516— a task’s project, mission, milestone, and parent only need share a workspace, not a project.contracts/kanban-schema.v1.ts:905-908— only current milestone is mentioned as a deferred invariant.
Violation
REQ-PLAN-001 and schema correctness. A task in project A can point to a mission/milestone/parent task from project B in the same workspace. A mission can associate a milestone from another project despite having one required project.
Minimal fix
Add project-congruent composite keys/FKs (or freeze mandatory transaction checks) for task→mission, task→milestone, task→parent, mission→milestone, and project→current milestone. Add same-workspace/same-project negative tests.
KCR-011 — MAJOR — Immutable/append-only records can be erased by parent cascades
Location
contracts/kanban-schema.v1.ts:798-818—task_eventsis described as append-only but remains under a workspace cascade.contracts/kanban-schema.v1.ts:911— only application-role UPDATE/DELETE privilege removal is stated.- Numerous canonical relationships use
onDelete('cascade'), including workspace roots and artifact/checkpoint/event owners. REQUIREMENTS.md:41-43and154-170— audit must be append-only, attributable, and reconstructable.
Violation
REQ-AUD-001. Revoking direct DELETE on task_events does not prevent a parent delete from cascading into the audit log. Checkpoints and immutable artifacts also lack explicit append-only privilege/retention semantics.
Minimal fix
Use lifecycle/archive states and RESTRICT for canonical parent deletion during normal operation. Freeze a separate, audited retention/break-glass purge procedure. Apply INSERT/SELECT-only or equivalent immutability controls to task events, checkpoints, and immutable artifacts, and test that parent deletion cannot silently erase them.
KCR-012 — MAJOR — Coordinator persistence lacks durable quarantine/retry state and DTO/schema alignment
Location
contracts/mechanical-coordinator.v1.ts:239-244— expiry returnsquarantinedIDs.contracts/kanban-schema.v1.ts:457-490— task has only untypedretryPolicymetadata and no quarantine/execution disposition.contracts/mechanical-coordinator.v1.ts:173—evidenceIdshas no corresponding evidence table/type; schema has artifacts.contracts/kanban-schema.v1.ts:479,663, and agent role JSON — specialist roles are free text despite the frozen role vocabulary inmechanical-coordinator.v1.ts:19-29.
Violation
REQ-COORD-004 and internal consistency. PostgreSQL cannot deterministically reconstruct why/when a task was quarantined, its bounded retry state, or which typed evidence was submitted. Free-text roles allow the schema and engine to disagree.
Minimal fix
Freeze a durable execution/retry/quarantine record (attempt count, next eligibility, terminal reason, actor/policy, timestamps, version) or typed task columns with events. Align evidenceIds to artifact IDs or add a real evidence entity. Use one specialist-role enum/check across tasks, assignments, agents/sessions, DTOs, and Coordinator.
KCR-013 — MAJOR — Thin MVP promises task archive and tag filtering without target-state semantics
Location
REQUIREMENTS.md:182-193— users must archive tasks and filter by tags.SHARED-CONTRACT.md:252-267— mutations include cancel but not archive task.contracts/kanban-schema.v1.ts:457-490— no task archive field and no typed tags field/table.- Current
origin/mainalready hastasks.tags, making omission from the target declaration a migration-loss hazard.
Violation
REQ-UI-001/002 and internal acceptance consistency. “Archive” cannot be implemented without inventing whether it means cancelled, hidden, or soft-deleted; tag filtering has no frozen storage/query contract.
Minimal fix
Either remove task archive/tag acceptance from P1, or add explicit non-lifecycle archival semantics (archived_at/by/reason) and a workspace-safe tags model/query contract. Preserve/migrate the current tags column until the selected model is live.
KCR-014 — MAJOR — Recovery contract states critical rules only in comments and has no owning implementation slice
Location
contracts/recovery-posture.v1.ts:97-147— exported JSON Schema validates only local field shapes.contracts/recovery-posture.v1.ts:150-156— PITR/WAL, effective RPO, off-cluster, high-assurance minima, and audit rules are comments only.REQUIREMENTS.md:270-277— parser rejection of impossible combinations is acceptance-critical.TASKS.md:75-244— no bounded slice owns recovery config parsing, override audit, backup/WAL setup, or restore/break-glass evidence.
Violation
REQ-REC-001. A consumer using the advertised JSON Schema can accept weakened high-assurance values, PITR without WAL, or an impossible RPO. The task plan has no lane accountable for closing that acceptance criterion.
Minimal fix
Export a normative validateRecoveryPostureV1/schema refinement with machine-testable cross-field checks and add a bounded Infra/recovery slice (serialized if it touches shared config) owning config parsing, override audit, mechanism verification, restore test, and break-glass evidence. Recovery config must continue to expose no authority/gate knobs.
KCR-015 — MAJOR — Pure Coordinator slice cannot implement two frozen methods without persistence access
Location
contracts/mechanical-coordinator.v1.ts:259-263—explainEligibilityreceives onlytaskId, not a structured snapshot.contracts/mechanical-coordinator.v1.ts:289-293—recoverFromPostgresexplicitly reads PostgreSQL.TASKS.md:145-153— KBN-200 is a pure engine with no SQL, Drizzle, Gateway, or Valkey.TASKS.md:157-164— persistence belongs to coder3/KBN-210.
Violation
Charter C and internal consistency. coder4 cannot implement the frozen port in a pure package without crossing coder3’s persistence boundary. If coder3 implements the port instead, KBN-200’s acceptance and ownership are misassigned.
Minimal fix
Split the contract into a pure decision engine that receives complete immutable snapshots and a persistence/orchestration service port implemented by KBN-210. Move recoverFromPostgres and ID-based loading to the adapter/service; make pure explanation accept a snapshot.
KCR-016 — MINOR — Health denial code/state pairs are not correlated by type
Location
contracts/health-state.v1.ts:35-61— either denial code can pair with either degraded state.SHARED-CONTRACT.md:188-190— prose definesKANBAN_WRITE_UNAVAILABLEspecifically forwrite-unavailable.
Violation
Health contract precision. A client can receive a semantically inconsistent authoritative body even after KCR-001’s broader state fix.
Minimal fix
Make deliberate denial a two-variant union with exact code/state pairing.
Clean checks / invariants that do hold
The review did not find a gap in these areas:
- The canon consistently selects current
mosaicstack/stack+ Drizzle/PostgreSQL and rejects greenfield/Prisma revival. - Every artifact states PostgreSQL is the sole writable SOT and Valkey/files are non-authoritative.
- Generated
TASKS.md,mission.json, and exports are consistently declared read-only and never import sources. KBN-300’s importer is scoped to immutable legacy JSON/Vikunja snapshots, not generated projections. - Recovery config exposes recovery fields only; it contains no direct fail-open, SOT, Coordinator-authority, or gate-waiver knob.
- The Coordinator interface contains no
createTask, acceptance-edit, gate-waive, certify, merge, release, or provider-close method.submitForReviewis type-limited toin_review, notdoneorcertified. - Certifier is consistently final independent gate with no merge authority.
- The seven canonical task status values match across requirements, shared prose, schema, and Coordinator’s ready/in-review surfaces.
- One-active-lease partial uniqueness, no-self-edge, outbox aggregate-revision/event-type uniqueness, optimistic task/project/mission/milestone versions, and N-1 test categories are explicitly present.
- The file-tree partition is mostly well separated once the ordering/freeze defects in KCR-007 are corrected.
Required re-review scope
After remediation, re-review at minimum:
- health/coordinator discriminated unions and mutation-time health proof;
- proposal/approval/assignment/lease relational model;
- monotonic fencing and composite bindings;
- tenant-safe polymorphic relationships;
- outage-proposal persistence and commands;
- concrete current-main migration map;
- corrected dependency graph and exact API/DTO freeze;
- recovery validator/owner slice;
- all schema and DTO vocabulary alignment.
Overall verdict
NO-GO — 8 BLOCKERs must be resolved before the v1 contract is frozen or parallel implementation begins.