<!-- SPDX-FileCopyrightText: 2026 FocusFlow contributors -->
<!-- SPDX-License-Identifier: AGPL-3.0-only -->

# V1 ADR 002: SQLite Document Schema V2

Status: **Accepted; updated to match current app state**

Date: 2026-06-26

Last reviewed: 2026-07-07

## Context

The project is SQLite-first. The local app persists scheduler state in a Drift
database and keeps storage details behind repository and application-unit-of-work
boundaries.

The original V1 schema was implemented and then migrated to the current Drift
schema version 2 when application-layer records became durable state.

## Decision

* **Database file**: `scheduler.sqlite` under `~/ADHD_Scheduler/` by default.
  Runtime composition may override this with `SCHEDULER_SQLITE_PATH`, and dev
  tooling may pass `--sqlite`.
* **Schema version**: `2`, managed by Drift. Version 1 files are supported as
  migration inputs; downgrades are not supported.
* **Tables**

| Table | Purpose | Key fields |
|-------|---------|------------|
| `tasks` | Authoritative task rows | `id TEXT PRIMARY KEY`, `owner_id`, `title`, `project_id`, `parent_id`, `type`, `status`, `priority`, `reward`, `difficulty`, `duration_minutes`, scheduled/actual/completed UTC timestamps, `backlog_tags_json`, `reminder_override`, `stats_json`, `backlog_entered_at_utc`, `backlog_entered_provenance`, `revision`, `created_at_utc`, `updated_at_utc` |
| `task_activities` | Append-only task activity facts | `id TEXT PRIMARY KEY`, `owner_id`, `task_id`, `project_id`, `operation_id`, `code`, `occurred_at_utc`, `metadata_json` |
| `projects` | Project configuration | `id TEXT PRIMARY KEY`, `owner_id`, `name`, `color_key`, configured defaults, `archived_at_utc`, `revision`, `created_at_utc`, `updated_at_utc` |
| `project_statistics` | Project-level aggregate statistics | `project_id PRIMARY KEY`, `owner_id`, completion and suggestion-count fields, `revision`, `created_at_utc`, `updated_at_utc` |
| `locked_blocks` | Recurring / one-off locked time | `id TEXT PRIMARY KEY`, `owner_id`, `name`, `date`, `start_time`, `end_time`, `recurrence_json`, `hidden_by_default`, `project_id`, `archived_at_utc`, `revision`, timestamps |
| `locked_overrides` | Date-scoped locked-block overrides | `id TEXT PRIMARY KEY`, `owner_id`, `locked_block_id`, `date`, override type and payload fields |
| `settings` | One row per owner | `owner_id TEXT PRIMARY KEY`, `timezone_id`, `day_start_minutes`, `day_end_minutes`, `compact_mode`, `backlog_staleness_json`, `revision`, timestamps |
| `snapshots` | Bounded diagnostic schedule snapshots | `id TEXT PRIMARY KEY`, `owner_id`, `captured_at_utc`, `operation_name`, `source_date`, `target_date`, JSON payload columns, `retention_expires_utc`, `truncated`, `revision`, timestamps |
| `application_operations` | Committed operation records for idempotency | `operation_id TEXT PRIMARY KEY`, `owner_id`, `operation_name`, `committed_at_utc` |
| `notice_acknowledgements` | Owner-scoped acknowledged notice records | composite key `owner_id, notice_id`, `acknowledged_at_utc`, `revision`, timestamps |

* UTC timestamps are stored in Drift `DateTime` columns. Civil dates and wall
  times remain text fields where the domain model requires calendar-local values.
* Mutable domain/application records carry optimistic `revision` values. Updates
  must compare the expected revision and increment on success. Append-only
  activity and operation records are immutable facts and do not use mutable-save
  revision checks.
* Schema version 2 migrations create the application-layer tables and indexes
  needed for task activity, project statistics, idempotent operations, and notice
  acknowledgements.

## Consequences

* Domain objects remain unchanged; adapters translate between domain objects and
  SQLite rows.
* The backup library copies and encrypts this single SQLite file.
* Repository conformance tests cover adapter behavior, while Drift schema tests
  verify version 2 tables and version 1 upgrade behavior.
