diff --git a/docs/native-kanban-sot/SHARED-CONTRACT.md b/docs/native-kanban-sot/SHARED-CONTRACT.md index 43e44c3..f1c1b76 100644 --- a/docs/native-kanban-sot/SHARED-CONTRACT.md +++ b/docs/native-kanban-sot/SHARED-CONTRACT.md @@ -1,9 +1,21 @@ # 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 +**Status:** CONTROL-PLANE SI-001 AMENDMENT AUTHORIZED; prior KCR-001–016 independent-review GO retained; rc.4 requires independent schema/SecReview before KBN-100 +**Version:** 1.0.0-rc.4 **Date:** 2026-07-14 **Change authority:** Mosaic control plane/Jason only +**SI-001 amendment authority:** `web1:mosaic-100` control-plane decision under issue #753 + +## Amendment record + +### 1.0.0-rc.4 — KBN010-SI-001 + +- **Choice:** add the explicitly named, non-partial unique candidate key `missions_workspace_id_uidx` on `missions(workspace_id, id)` and retain `missions_workspace_project_id_uidx` on `(workspace_id, project_id, id)`. +- **Rationale:** mission `id` remains globally unique, while the composite candidate key makes the frozen tenant-safe generic mission relations valid. `artifacts` and `approval_decisions` are polymorphic exactly-one-target records and do not consistently carry `project_id`; widening both children would unnecessarily broaden v1 and its target semantics. +- **Exact effect:** `artifacts_workspace_mission_fk` and `approval_decisions_workspace_mission_fk` continue to reference the exact ordered columns `missions(workspace_id, id)` with RESTRICT deletion, now backed by a matching candidate key. +- **Non-effect:** no SOT, tenancy, project-congruence, proposal-audit, approval, fencing, immutability, no-cascade, API, or wire-version invariant changes. The `SuccessEnvelopeV1.contractVersion` remains `1.0.0`. +- **Historical evidence boundary:** `KBN-010-THREAT-AUTH-CONSTRAINT-GATE.md` intentionally remains the immutable rc.3 blocker verdict that detected SI-001; this rc.4 record and the #753 scratchpad append are the authorized disposition. Rewriting the gate verdict is outside this amendment's exclusive scope. +- **Gate:** this amendment resolves the DDL defect identified by KBN010-SI-001 but does not itself lift KBN-100; independent schema/SecReview remains required. ## 1. Authority @@ -43,6 +55,7 @@ Complete declaration: `contracts/kanban-schema.v1.ts`. - Specialist roles everywhere: `planning | enhance | coder | review | security-review | pr-monitor | certifier`. - Owner uses exactly-one user/team; assignment principal exactly-one user/team/agent; users require active membership; agent/session and all evidence are workspace-bound. - Task→mission/milestone/parent, mission→milestone, and project→current-milestone are project-congruent composite relations. +- Mission `id` remains globally unique. The additional non-partial `missions_workspace_id_uidx` candidate key on `(workspace_id, id)` exists only to support the frozen workspace-safe polymorphic artifact and approval-decision mission relations; the project-congruent `(workspace_id, project_id, id)` key remains authoritative wherever `project_id` is present. - Dependency identity is workspace+predecessor+successor independent of type. - Approval evidence and checkpoint evidence are workspace-scoped joins to immutable artifacts, never JSON ID arrays. - Proposal audit links are composite relations: `(workspace_id, submitted_audit_event_id)` and `(workspace_id, accepted_command_audit_event_id)` reference `task_events(workspace_id, id)` with RESTRICT deletion. @@ -76,7 +89,20 @@ Legacy columns remain declared in unified `schema.ts` for expand + full N-1/roll 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 +### 5.2 Mission candidate-key and dependent-FK DDL order + +KBN-100 migration DDL must execute the SI-001 portion in this order: + +1. expand/backfill `missions.workspace_id` and `missions.project_id` while preserving the global `missions.id` primary key and the project-congruent `missions_workspace_project_id_uidx` key; +2. prove duplicate-key feasibility on the production-shape dataset: `(workspace_id, id)` has no duplicate groups and global `id` uniqueness remains intact; +3. create the non-partial unique index `missions_workspace_id_uidx` on exact ordered columns `(workspace_id, id)`; +4. only after step 3, create/alter `artifacts` and add `artifacts_workspace_mission_fk` from `(workspace_id, mission_id)` to exact `missions(workspace_id, id)` with `ON DELETE RESTRICT`; +5. only after step 3, create/alter `approval_decisions` and add `approval_decisions_workspace_mission_fk` from `(workspace_id, mission_id)` to exact `missions(workspace_id, id)` with `ON DELETE RESTRICT`; +6. validate both constraints and prove a mission ID paired with a foreign workspace is rejected for each child. + +The candidate key is intentionally redundant with globally unique `missions.id`, but PostgreSQL requires a matching unique candidate key for the exact two-column FK target. It is additive and N-1-safe. Pre-switch rollback drops the two dependent FKs/tables before dropping this candidate key, preserves the global primary key and project-congruent key, and follows the existing freeze/reconciliation rule after the first canonical mutation. + +### 5.3 New audit/proposal DDL order KBN-100 migration DDL must execute in this order: @@ -88,36 +114,39 @@ KBN-100 migration DDL must execute in this order: 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 +### 5.4 Field map -| Current | Expand/backfill | N-1 compatibility | Switch/contract | -| ---------------------------------------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------- | -| global `teams`, `team_members` | add workspace nullable; bootstrap; validate active owners | retain global slug/FKs | workspace composites; global unique contracts later | -| `projects.status` | add `canonical_status`; map active/paused/completed/archived | mirror representable values; no `planning` | canonical authority; legacy contracts later | -| project `owner_id/team_id/owner_type` | add exact accountable user/team; deterministic map or quarantine | preserve old reads and compare drift | canonical exact-one; remove legacy after parity | -| current milestone | create milestones then join table (no circular DDL) | absent to N-1 | join is authority | -| nullable `missions.project_id` | derive workspace/project; null/orphan exception, never guess | keep nullable legacy read | canonical required; validate/set NOT NULL later | -| mission `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 | +| Current | Expand/backfill | N-1 compatibility | Switch/contract | +| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------- | +| global `teams`, `team_members` | add workspace nullable; bootstrap; validate active owners | retain global slug/FKs | workspace composites; global unique contracts later | +| `projects.status` | add `canonical_status`; map active/paused/completed/archived | mirror representable values; no `planning` | canonical authority; legacy contracts later | +| project `owner_id/team_id/owner_type` | add exact accountable user/team; deterministic map or quarantine | preserve old reads and compare drift | canonical exact-one; remove legacy after parity | +| current milestone | create milestones then join table (no circular DDL) | absent to N-1 | join is authority | +| nullable `missions.project_id` | derive workspace/project; null/orphan exception, never guess | keep nullable legacy read | canonical required; validate/set NOT NULL later | +| mission relational candidate keys | retain global `id` PK and project-congruent key; add non-partial `(workspace_id,id)` key before artifact/approval FKs | additive key is ignored safely by N-1 readers/writers | retain both composite keys; generic mission children use exact workspace+ID target | +| mission `description` | add objective; preserve description; reviewed nonblank mapping | N-1 description | objective authority; retain until signed review | +| `missions.status` | add canonical; planning→draft, active/paused/completed/failed same | no new-only statuses emitted | canonical authority | +| mission `milestones` JSON | normalize with source digest; preserve malformed/original | N-1 reads JSON; no reverse sync | normalized authority; JSON removed after checksum sign-off | +| mission config/metadata/phase/user | retain all; map known typed policy only | all remain declared | remove only by signed consumer inventory | +| nullable `tasks.project_id` | derive explicit/mission project; orphan quarantine | retain nullable read/write during compatibility | canonical required; NOT NULL later | +| `tasks.mission_id` | add project-congruent composite | old relation readable | composite authority | +| `tasks.status` | canonical: not-started→backlog, in-progress→in_progress, others same | no ready/in_review emission | canonical authority | +| `tasks.assignee` | deterministic active user/team/agent assignment; raw value preserved if ambiguous | mirror text only if unambiguous | canonical owner/assignment; remove after no-loss sign-off | +| `tasks.tags` JSON | normalize trim/case/dedupe with original digest | transactionally mirror normalized rows | normalized authority; JSON later removed | +| `tasks.due_date` | copy exactly to `due_at` | mirror | due_at authority; legacy later | +| task common fields | preserve metadata byte-for-byte; add criteria/rank/retry/archive/version/fence | old reads valid | new fields canonical | +| `mission_tasks.status` | keep; prohibit as write source; linked status ignored; unlinked becomes task or reject | read-only compatibility value | membership uses task mission; status dropped after no readers | +| mission-task notes/PR/user | map to metadata/artifact/event/link/attribution; preserve | read-only | remove after parity | +| `agents.status` | add workspace/lifecycle/runtime/roles; status remains presence | retain all legacy fields | lifecycle/roles authority; status may remain telemetry | +| agent project/owner/prompt/tools/skills/config | preserve; validate tenant; derive typed capabilities without loss | N-1 reads | removal only by separate inventory | +| fleet `backlog` | map to designated-project tasks; edges; claimed rows quarantine | freeze claims before switch; read-only compare | task/lease authority; retire after stabilization | -### 5.4 Required migration tests +### 5.5 Required migration tests Empty DB; exact production-shape snapshot; crash/resume; rollback before switch; N-1 startup/read/write; workspace/member negatives; status-shadow/no premature new status; `mission_tasks.status` write prohibition; tags/assignee/date/mission JSON/config/description/agent checksum; project congruence/current-milestone order; backlog freeze/no dispatch; and proof legacy declarations persist until contract release. +SI-001 adds frozen future executable evidence: empty and production-shape migrations create `missions_workspace_id_uidx` before either dependent FK; duplicate-key feasibility preflight returns no `(workspace_id,id)` duplicate groups without weakening global `id` uniqueness; N-1 startup/read/write behavior is unchanged; pre-switch rollback removes dependents before the candidate key; both exact FK column lists reconcile to the candidate key; and foreign-workspace mission references fail for both artifacts and approval decisions. TDD is not applicable to this design-only amendment; KBN-100 must implement these negative migration tests before runtime schema release. + Proposal-specific negatives must attempt: missing submission event, foreign-workspace submission event, foreign-workspace acceptance event, same-workspace event for another proposal, event for another target aggregate, and unrelated normal-command event. Every attempt must fail atomically with no accepted proposal and no target mutation. ## 6. Ownership and Coordinator split @@ -216,4 +245,10 @@ KBN-115/coder2 owns `packages/config/src/recovery-posture.ts`, tests, and recove 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. +### 9.1 SI-001 amendment gate and #757 boundary + +- KBN-100 must provide the §5.2 candidate-key ordering, duplicate-feasibility, exact-FK reconciliation, empty/prod/N-1/rollback, and two-child foreign-workspace evidence before SI-001 can be certified closed. +- All prior KCR-001–016 decisions and fixed SOT/tenant/authority, proposal-audit, approval, task-fencing, immutability, and no-cascade invariants remain unchanged. +- Read-only PR #757 cross-check: its logical-agent connector lease/CAS fencing uses separate runtime tables/contracts and `lease_epoch`; rc.4 changes only the frozen `missions` candidate key. There is no shared table, index, FK, identity, fence, or authority semantic to consume or reconcile, and #757 remains owned by its existing lane. + +The build hold remains active until independent re-review reports GO for KCR-001–016 and the rc.4 SI-001 amendment. Mos alone releases waves and serializes integration roots. diff --git a/docs/native-kanban-sot/contracts/kanban-schema.v1.ts b/docs/native-kanban-sot/contracts/kanban-schema.v1.ts index 594a556..5d0c3aa 100644 --- a/docs/native-kanban-sot/contracts/kanban-schema.v1.ts +++ b/docs/native-kanban-sot/contracts/kanban-schema.v1.ts @@ -485,6 +485,7 @@ export const missionsV1 = pgTable( foreignColumns: [projectsV1.workspaceId, projectsV1.id], }).onDelete('restrict'), uniqueIndex('missions_workspace_project_id_uidx').on(t.workspaceId, t.projectId, t.id), + uniqueIndex('missions_workspace_id_uidx').on(t.workspaceId, t.id), index('missions_workspace_project_status_idx').on( t.workspaceId, t.projectId, diff --git a/docs/scratchpads/753-kbn010-threat-gate.md b/docs/scratchpads/753-kbn010-threat-gate.md index a4bd9a2..eaa5f2f 100644 --- a/docs/scratchpads/753-kbn010-threat-gate.md +++ b/docs/scratchpads/753-kbn010-threat-gate.md @@ -53,3 +53,23 @@ Pending worker validation, independent SecReview, Ultron gate, PR merge, post-me - Independent SecReview remains pending and cannot return PASS until contract authority resolves `KBN010-SI-001`. - Final formatting/diff/test-ID completeness review — PASS (all catalog prefixes contiguous with no duplicate IDs). - Remaining: scoped commit, queue guard, and push. + +## 2026-07-14 KBN010-SI-001 contract-authority amendment + +- Authority: `web1:mosaic-100` directed the minimal rc.4 amendment under issue #753: add a non-partial unique candidate key on `missions(workspace_id, id)` while retaining global `missions.id` uniqueness and the project-congruent `(workspace_id, project_id, id)` key. +- Rationale: artifacts and approval decisions are polymorphic exactly-one-target records and do not consistently carry `project_id`; widening both children would broaden frozen v1 semantics without improving tenant safety. +- Plan: amend only the frozen schema contract and shared contract, freeze exact DDL ordering and future migration negatives, run scoped/full validation and independent review, then commit and push without opening/merging a PR or closing #753. +- TDD: not applicable because this is a design-contract amendment with no runtime schema or migration implementation; rc.4 freezes future executable empty/prod/N-1/rollback/foreign-workspace and duplicate-key-feasibility tests. +- Budget: no explicit cap supplied; use a 20K-equivalent soft working cap and one bounded worker lane. +- Read-only #757 boundary: PR #757 adds separate logical-agent connector lease/CAS fencing (`logical_agent_connector_leases.lease_epoch`) in runtime schema and connector contracts. SI-001 changes only the frozen mission candidate key; it does not consume, alter, or reinterpret connector fencing, task fencing, lease authority, or #757 ownership. + +### Amendment verification evidence + +- Frozen schema: added exactly one non-partial `missions_workspace_id_uidx` on `(workspace_id, id)`; retained the global `id` primary key and `missions_workspace_project_id_uidx` on `(workspace_id, project_id, id)`. +- FK reconciliation: targeted static checks prove the candidate key precedes both `artifacts_workspace_mission_fk` and `approval_decisions_workspace_mission_fk`; each continues to reference exact ordered columns `(workspace_id, mission_id)` → `missions(workspace_id, id)` with RESTRICT deletion. +- Shared contract: candidate version is `1.0.0-rc.4`; authority, rationale, exact effect/non-effect, candidate-before-FK DDL order, duplicate feasibility, empty/prod/N-1/rollback/foreign-workspace future tests, and unchanged KCR/SOT/tenant/proposal/fencing/no-cascade invariants are explicit. +- Changed-file Prettier, contract ESLint, `pnpm exec tsc -p docs/native-kanban-sot/tsconfig.json`, and targeted SI-001 static checks — PASS. +- Full `pnpm format:check && pnpm lint && pnpm typecheck` — PASS (23 lint tasks and 42 typecheck/build tasks). +- Independent Codex security review — PASS, zero critical/high/medium/low findings; tenant isolation is preserved. +- Independent Codex code review confirmed the candidate key repairs both FK targets and reported no blocker on the authorized delta. Its initial request to rewrite the historical KBN-010 verdict conflicts with the exclusive scope and was resolved by documenting the immutable-evidence boundary in rc.4; its remaining finding concerns pre-existing live `.mosaic` session files, which are untouched and excluded from this commit. +- Scoped diff: only the two frozen contract files and this append-only scratchpad amendment are staged for delivery; no runtime schema, migration, task plan, gate verdict, provider artifact, or #757-owned file is included.