Branch: feat/per-project-sync-consent | Date: 2026-08-09 | Spec: spec.md Input: Feature specification from /kitty-specs/per-project-sync-consent-ledgers-01KZKMQZ/spec.md

Summary

Replace live machine-global sync storage and multi-source granting with one sync.db per canonical UUID, opened only through a connection-owning ProjectSyncStore unit of work, and one versioned consent writer. ProjectSyncStore also owns the machine layout-generation/write-permit API before payload repositories migrate, so every current-version writer participates in later cutover. Local capture assigns a monotonic sequence in explicit epochs; opt-in records the inclusive tail, while sealed history requires an immutable preview/confirmation capability. Egress requires current consent/epoch, exact binding audience, per-write admission generation, and a durable transport attempt. Admit/revoke uses a durable operation outbox. Partition legacy state through daemon and current-writer layout participation, read-only logical snapshots, exact verification, and atomic cutover. Opt-out cancels pre-start work and reconciles or irrevocably terminalizes orphaned old-generation attempts before acknowledgement. Compatibility consumes a pinned SaaS candidate commit and contract digest; coordinated closure emits a checksum manifest with one owner per evidence claim.

Technical Context

Language/Version: Python 3.11+ Primary Dependencies: Typer, SQLite, toml, requests/httpx/WebSocket stack, filelock, existing Spec Kitty runtime/paths abstractions; no new dependency Storage: .../projects/<uuid>/sync/sync.db containing control/epoch/sequence/journal/attempt/result/outbox/target/admission-operation/history-action/migration tables, plus egress.lock; one machine layout-generation authority/write-permit API; atomic denial hints at projects/.deny-hints/<uuid>.json; checksum-addressed acceptance manifests outside payload stores Testing: pytest unit/integration/CLI, connection/commit instrumentation, cross-process and hard-kill transport barriers, migration writer-race/WAL injection, old-daemon protocol, architecture/writer census, mutation controls, reproducible process-warm/cold benchmarks, cross-platform CI, real CLI↔SaaS contract/E2E Target Platform: Linux, macOS, Windows 10+; local/offline operation plus the canonical SaaS contract, with hosted mutation limited to a dynamically discovered Upsun branch environment Project Type: Python CLI/runtime with background daemon and multiple transport adapters Performance Goals: 200 warm scans of the specified 100-store fixture complete within 500 ms p95 and 30 fresh-process scans within 1 s p95, without opening payload tables for 80 valid denied hints Constraints: one UUID authority/grant writer/live connection owner; one layout-generation writer authority; deterministic ASCII paths; no implicit grant or dual-read; monotonic capture sequence; immutable history capability; exact binding audience; durable operation/attempt identity; pinned SaaS candidate commit/contract digest; strict typing; no production mutation Scale/Scope: all live event/body/history/daemon/WebSocket/tracker-hosted sender classes, mixed legacy state, and six-project cross-repository acceptance

Charter Check

GATE: PASS before Phase 0; PASS again after Phase 1 design.

  • Single canonical authority — PASS: sync/consent.py is reduced to one project-store record and writer. Path, repo-default, UUID index/backfill, checkout-only flags, local config, login, environment, and target cannot grant.
  • Architectural alignment — PASS: project identity owns store resolution; target remains a separate value; SaaS admission remains an external authority consumed through the canonical contract.
  • ATDD-first — PASS: implementation starts with A/B store-open traps, implicit-grant/writer negative matrices, capture-epoch tests, two-ordering revocation barriers, and hard-kill migration tests through public CLI/runtime entry points.
  • Structural closure — PASS: live store constructors require ProjectSyncContext; no-argument/global resolvers are removed from live paths. Shrink-only call-site censuses and mutants prove the boundary is non-vacuous.
  • Campsite rule — PASS WITH REQUIRED FIRST SLICE: scout the large consent/routing/sync-command/emitter surfaces and fix only domain-matched blockers before functional edits.
  • Cross-platform identity — PASS: canonical UUID tokens are ASCII by construction and use get_runtime_root; Windows/macOS/Linux tests include non-ASCII display names and path variants.
  • Transactional coherence — PASS: one ProjectSyncStore unit of work owns the live connection/outer transaction; component repositories cannot independently connect/commit. Fault tests cover atomic control/epoch/journal/outbox/attempt/result changes.
  • Layout authority — PASS: ProjectSyncStore introduces the machine layout-generation/write-permit API before repository conversion; every WP04 writer consumes it and migration only advances it after exact verification.
  • Contract authority — PASS: SaaS owns contracts/cli-saas-current-api.yaml; core accepts an explicit SaaS candidate checkout/ref/digest after SaaS WP04 and refuses ambient relative sibling resolution.
  • Issue/tracker hygiene — PASS: #3262 and companion #585 are claimed/commented; predecessor and adjacent issues have matrix rows.
  • Reviewer separation — PASS: each work package uses different implementer/reviewer profiles in Spec Kitty worktrees.
  • Supply chain — PASS: no package added; existing authenticity, lockfile, lifecycle-script, and Node LTS gates remain unchanged.
  • Deployment/release safety — PASS: local mission work only; hosted mutation may use only a dynamically discovered Upsun branch environment. Production, publishing, PR creation, mainline integration, and deployment require explicit operator authorization.

Project Structure

Documentation (this mission)

kitty-specs/per-project-sync-consent-ledgers-01KZKMQZ/
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── issue-matrix.json
├── contracts/
│   ├── project-sync-store-layout.md
│   └── sender-and-migration-matrix.md
└── traces/
    ├── tooling-friction.md
    ├── approach.md
    └── design-decisions.md

Source Code (repository root)

src/specify_cli/
├── event_journal/
│   └── journal.py                    # explicit project-owned path only
├── delivery/
│   ├── interfaces.py                # target/delivery capability interfaces
│   ├── targets.py                   # project-store-backed exact target registry
│   ├── dispatcher.py                 # immutable context and final barrier
│   ├── ledger.py                     # project-owned ledger
│   ├── retention.py                  # project-scoped purge/diagnostics
│   ├── selection.py                  # preserved consent-bearing selection
│   └── status_report.py
├── sync/
│   ├── project_store.py              # canonical UUID path/store aggregate
│   ├── layout_generation.py          # sole current-writer layout authority/API
│   ├── project_context.py            # store+epoch+audience/admission binding
│   ├── consent.py                    # sole project-control authority/writer
│   ├── admission_operations.py        # durable admit/revoke operation outbox
│   ├── history_disclosure.py          # preview/confirm sealed cohort capability
│   ├── transport_attempts.py          # crash-aware attempt/result protocol
│   ├── transport_lease.py             # cross-process final-gate/result barrier
│   ├── routing.py                    # opt-in/opt-out orchestration
│   ├── project_store_migration.py    # preview/copy/verify/cutover/quarantine
│   ├── daemon_protocol.py             # quiesce/restart layout handshake
│   ├── queue.py                      # project outbox; no shared live sender
│   ├── body_queue.py
│   ├── daemon.py
│   ├── runtime.py
│   ├── emitter.py
│   ├── client.py
│   ├── local_commit.py
│   └── history_import/
├── cli/commands/sync.py              # explicit commands and diagnostics
└── state/contract.py                 # declared project-store ownership

tests/
├── sync/
│   ├── test_project_store.py
│   ├── test_project_consent_authority.py
│   ├── test_project_store_migration.py
│   ├── test_consent_epochs.py
│   ├── test_history_disclosure.py
│   ├── test_admission_operations.py
│   ├── test_transport_attempt_recovery.py
│   ├── test_transport_result_lease.py
│   ├── test_transport_orphan_settlement.py
│   ├── test_interactive_transport_convergence.py
│   ├── test_background_authority_convergence.py
│   ├── test_transport_revocation_matrix.py
│   ├── test_transport_crash_matrix.py
│   ├── test_legacy_grant_writers.py
│   ├── test_daemon_project_isolation.py
│   ├── test_daemon_cutover_protocol.py
│   └── test_saas_admission_compatibility.py
├── architectural/
│   ├── test_project_store_boundary.py
│   └── test_egress_consent_boundary.py
└── contract/

<explicit-saas-candidate>/contracts/cli-saas-current-api.yaml
<explicit-e2e-checkout>/

Structure Decision: project_uuid owns one SQLite database, and ProjectSyncStore.unit_of_work() owns every live connection/outer transaction. ProjectSyncStore also exposes the sole layout-generation/write-permit API. Component repositories are connection-free adapters and every current writer uses the permit immediately before insert. The aggregate exposes one typed context containing capture sequence/epoch and exact target audience rather than loose parameters. Existing consent.py becomes the sole grant writer. Delivery target interfaces/exports and the concrete target registry move with the target/admission slice. Control operations, history disclosure, and transport attempts are durable store entities. Selection and final egress remain defense in depth and consume only the context/capabilities.

Complexity Tracking

ViolationWhy NeededSimpler Alternative Rejected Because
Supersede #3030 shared-store non-goal#3262 explicitly requires physical project isolation and local capture must no longer depend on a shared hosted-consent gate.Retaining a shared database with stronger predicates reproduces containment, not the requested structural boundary.

This is an intentional predecessor decision supersession, not a charter exception; it must be recorded in an ADR/Decision Moment and validated through migration/compatibility tests.

Implementation Concern Map

IC-01 — Baseline, campsite, and structural test harness

  • Purpose: Freeze all live store resolvers and sender call sites and provide green census, instrumentation, positive-control, and mutation-harness foundations. Each later behavior package owns its own first red acceptance commit.
  • Relevant requirements: FR-001, FR-002, FR-003, FR-010, FR-018, FR-023, FR-026; NFR-001, NFR-004, NFR-007.
  • Affected surfaces: current journal/ledger/queue resolvers, consent/routing, dispatcher, emitter, runtime, daemon, CLI tests and architecture census.
  • Sequencing/depends-on: none.
  • Risks: broad fixtures currently grant consent by monkeypatch; tests must prove patch targets are live and include positive/mutation controls.
  • Purpose: Build the connection-owning unit of work and machine layout-generation API, then the sole grant writer, monotonic capture sequences/epochs, immutable history capability, and exact audience-bound admission operation state.
  • Relevant requirements: FR-001, FR-002, FR-003, FR-004, FR-005, FR-006, FR-007, FR-017, FR-021, FR-022, FR-023, FR-026, FR-027, FR-028, FR-031, FR-032.
  • Affected surfaces: sync/project_store.py, sync/project_context.py, sync/consent.py, sync/routing.py, sync/config.py, sync/target_authority.py, state contract and diagnostics.
  • Sequencing/depends-on: IC-01.
  • Risks: hidden component commits/connections, privately inferred writer layout, promoting legacy inputs, treating target as grant, capture/opt-in off-by-one, or ordinary selection minting history authority.

IC-03 — Project-owned journals, delivery, and outboxes

capture and payload state. Move target interfaces/registry/exports into the project boundary; WP05 and IC-04 own control-operation/transport uncertainty.

offline/body queue, delivery target interfaces/registry/exports.

  • Purpose: Require the context/epoch and a fresh layout write permit for
  • Relevant requirements: FR-002, FR-006, FR-007, FR-010, FR-016, FR-017, FR-018, FR-021, FR-022, FR-025, FR-027, FR-028, FR-030, FR-031, FR-032.
  • Affected surfaces: event journal, delivery ledger/selection/retention/status,
  • Sequencing/depends-on: IC-02.
  • Risks: cross-pairing, crash after remote acceptance/before local result, unsafe automatic resend, and breaking native Event/body/LocalCommit correlation.

IC-04 — Durable attempt and transport/result lease protocol

  • Purpose: Persist attempts before I/O, define one cross-process lease, cancel pre-start work, and settle or irrevocably terminalize orphaned old-generation attempts before opt-out acknowledgement.
  • Relevant requirements: FR-008, FR-009, FR-010, FR-025, FR-027, FR-030, FR-031; NFR-003, NFR-004.
  • Affected surfaces: transport-attempt repository, consent gate, egress lock/lease, recovery service, protocol-level tests.
  • Sequencing/depends-on: IC-02, IC-03.
  • Risks: recheck/send race, OS lock release after process death, indefinite wait, unknown remote outcome, duplicate disclosure, and falsely complete revoke.

IC-05 — Interactive transport convergence

  • Purpose: Carry the immutable context/attempt/lease through dispatcher HTTP, WebSocket/Event relay, LocalCommit, body/dossier, history, tracker-hosted, and generic SaaS adapters with native correlation.
  • Relevant requirements: FR-005, FR-007, FR-010, FR-016, FR-018, FR-025, FR-030, FR-031; NFR-003, NFR-004, NFR-007.
  • Affected surfaces: dispatcher, emitter/relay, body/dossier, LocalCommit, history import, SaaS/tracker client adapters.
  • Sequencing/depends-on: IC-04 and the explicit SaaS WP04 candidate contract.
  • Risks: request-wide proof, transport-specific bypass, incorrect terminal classification, and fresh identity after uncertainty.

IC-06 — Daemon and background convergence

  • Purpose: Apply project authority and deny-only discovery to daemon/background writers while preserving another project's liveness.
  • Relevant requirements: FR-005, FR-010, FR-011, FR-029; NFR-001, NFR-006, NFR-007.
  • Affected surfaces: daemon, runtime, background discovery, denial hints, focused daemon tests.
  • Sequencing/depends-on: IC-04.
  • Risks: long-lived grant caches, directory enumeration creating stores, or a current writer bypassing layout generation.

IC-07 — Aggregate revocation and crash matrix

  • Purpose: Prove both revoke orderings and every kill window across all transport families, including kill-during-response followed immediately by opt-out and late recovery.
  • Relevant requirements: FR-008, FR-010, FR-025, FR-030; NFR-003, NFR-004; SC-004, SC-012.
  • Affected surfaces: aggregate acceptance/race tests only; underlying sender source remains owned by IC-04 through IC-06.
  • Sequencing/depends-on: IC-05, IC-06.
  • Risks: representative sampling, late success after acknowledgement, hidden automatic resend, and cross-project starvation.

IC-08 — Legacy partition, quarantine, and exclusive cutover

  • Purpose: Consume the existing layout authority to quiesce recognized daemons/current writers, take read-only logical snapshots with WAL semantics, copy/verify, atomically cut over, redirect, and diagnose unrecognized late residue.
  • Relevant requirements: FR-012, FR-013, FR-014, FR-015, FR-024, FR-029; NFR-002, NFR-005, NFR-007.
  • Affected surfaces: migration/cutover services, daemon protocol, CLI migration/status commands, migration tests. Current writer source files remain owned by IC-03 and must already participate.
  • Sequencing/depends-on: IC-02, IC-03.
  • Risks: constructor-mutated sources, WAL/SHM omission, hard-kill partial phases, foreground write loss, ghost results, unrecognized binaries, and protocol skew.

IC-09 — Pinned SaaS compatibility and coordinated evidence

  • Purpose: Establish/revoke exact-target server admission against a pinned candidate contract, prove the core-owned six-project bytes/parking boundary, and emit the checksum manifest that references separately owned SaaS refusal/tombstone/hosted evidence.
  • Relevant requirements: FR-004, FR-007, FR-009, FR-016, FR-019, FR-020, FR-022, FR-033, FR-034; all SCs.
  • Affected surfaces: contract selection/attestation, core integration and cross-platform tests, discovery benchmark, evidence manifest/docs.
  • Sequencing/depends-on: SaaS WP04 before core admission compatibility; IC-04 through IC-08 plus reviewed SaaS WP02 anti-rematerialization and WP08 server/effect boundaries before coordinated acceptance.
  • Risks: landing core against an unstable contract or confusing local opt-out with acknowledged server revocation.

Delivery sequence

1. Establish green census/instrumentation/positive-control/mutation-harness foundations; each behavior package then commits its own failing ATDD test before source changes. 2. Introduce the connection-owning project unit of work and layout-generation/write-permit authority, then the sole consent writer, sequence/epochs, denial hints, and immutable history capability. 3. Move every current journal/delivery/event-outbox/body writer behind the unit of work and layout permit; add exact target/audience binding and durable admit/revoke operation records against the SaaS WP04 pinned contract. 4. Implement durable attempt and cross-process lease/recovery, then converge interactive transports and daemon/background paths in separate reviewable packages. 5. Run the aggregate revoke/crash matrix, including kill-during-response -> immediate opt-out -> late recovery. 6. Consume the existing layout authority in read-only logical snapshot migration, verification, atomic cutover, redirect, quarantine, and late-residue diagnostics. 7. After core transport and reviewed SaaS WP02/WP08 readiness, run core-owned conforming omission/stale parking/local isolation evidence and reference SaaS-owned refusal/zero-effect/hosted evidence by exact commit/digest. 8. Emit the immutable evidence manifest, run process-warm/cold benchmark and full architecture/mutation/cross-platform gates, and complete tracker evidence without production mutation or unauthorized publication.

Post-design Charter Re-check

PASS. Phase 1 selects one UUID-owned SQLite database, one connection/transaction owner, and one grant writer; makes every live writer context/sequence/epoch-bound; persists control and transport uncertainty before I/O; scopes admission to a verifiable audience; and gives migration writers a layout-generation barrier with read-only WAL-aware snapshots. Opt-out recovery, history capability, denial hints, benchmarks, and proof splitting now have executable protocols. The #3030 supersession remains explicit and scoped.