Per IMPLEMENTATION-ORDER.md 1.5: a nullable actor_child_id column
records which panel profile was tapped; admin events stay NULL.
- schema.py: completion_events table body extracted to
COMPLETION_EVENTS_TABLE_SQL; the new column carries a FK to
children(id) and the actor-pair coherence is extended to a second
CHECK: 'user' events require actor_user_id and forbid
actor_child_id, 'panel' events require actor_child_id and forbid
actor_user_id.
- migrations.py: migration 6 rebuilds legacy-shaped completion_events
via the canonical procedure (CHECKs cannot be added to an existing
table), backfilling each legacy panel row to its own child_id
(pre-D-008 panel completions were always the tapped child) via
INSERT..SELECT — no UPDATE or DELETE path exists, so the append-only
guarantee is untouched; ids preserved; PRAGMA foreign_key_check
verified in-transaction.
- dao_instances.py: append() takes actor_child_id, validates both
actor shapes (panel requires it, user forbids it) and the tapped
child's existence under the connection lock; the record and column
list grow accordingly.
- tests: coherence CHECK matrix (5 bad shapes), unknown
actor_child_id FK, panel/user round-trips at DDL and DAO layers,
migration-6 backfill test on a frozen v2-shape seed; the
completion_events leak guard gains justified INSERT exemptions for
the migration layer (UPDATE/DELETE stay forbidden everywhere).
Per IMPLEMENTATION-ORDER.md 1.2: quest_instances gains a NOT NULL
window column (CHECK-constrained to the const window spellings) and
the table-level UNIQUE constraint widens to the full fan-out tuple.
- schema.py: instance table body extracted to QUEST_INSTANCES_TABLE_SQL
shared with the rebuild migration; uniqueness docstring rewritten for
the widened key.
- migrations.py: migration 5 rebuilds a pre-window quest_instances via
the canonical procedure; migrated rows are pinned to the documented
default 'morning' window (pre-window rows were whole-day obligations;
future dates are regenerated with real windows by materialization),
preserving ids so completion_events keep pointing at the same
instances; PRAGMA foreign_key_check verified inside the transaction.
- dao_instances.py: upsert takes required keyword window and is
idempotent on the widened tuple; the immutable-once-completed check
and the read-back get() are keyed on the full tuple; window spelling
validated via dao_rules._validate_window.
- tests: schema shape (window column, widened autoindex columns),
twice-daily and shared-assignee fan-out cases at both the DDL and DAO
layers (incl. idempotent re-runs), cross-child same-key allowance,
window CHECK/NOT NULL constraints, v2 seed frozen to the true
version-2 shapes and its migration assertions widened.
Per IMPLEMENTATION-ORDER.md 1.3: a quest_definition_assignees table
(definition_id, child_id) with a composite primary key and FKs to
quest_definitions and children replaces the single child_id column on
quest_definitions.
- schema.py: definitions DDL loses child_id; new assignees table joins
SCHEMA_V1_STATEMENTS; the definitions table body is extracted to
QUEST_DEFINITIONS_TABLE_SQL so the rebuild migration cannot drift
from the canonical shape.
- migrations.py: callable migrations now own their transaction and
stamp via stamp_schema_version(database, version), because the
rebuild must bracket itself with PRAGMA foreign_keys OFF/ON. New
migration 3 creates the assignees table and, when a pre-D-008
quest_definitions still carries child_id, rebuilds the table via
SQLite's canonical procedure (create, copy, move assignees, drop,
rename), verifies PRAGMA foreign_key_check inside the transaction,
and stamps. Migration 2 converted to the new callable contract.
- dao_rules.py: QuestDefinitionsDao.create drops the child_id argument
and applies assignee_child_ids atomically; set_assignee is replaced
by add_assignee (idempotent, active-child validated under the
connection lock), remove_assignee and list_assignees; list_by_child
reads through the assignees table.
- dao_instances.py: upsert validates the child against
quest_definition_assignees instead of the definitions column.
- tests: schema shape tests for the assignees table (columns, PK, FKs,
duplicate/unknown constraints); DAO tests rewritten for the new
assignment API; migration tests for the v2 -> v3 rebuild (rows and
instances survive, child_id gone, foreign_key_check clean).
Per design/IMPLEMENTATION-ORDER.md 1.1 and decision D-007: the domain
model takes the quest vocabulary pre-release. SCHEMA_V1 now creates
quest_definitions/quest_instances directly; DAO classes, records and
error messages follow (QuestDefinitionsDao, QuestInstancesDao). The
migration runner gains migration 2, an async-callable step that detects
surviving task_* tables on dev machines and ALTER TABLE RENAMES them
inside the migration transaction, with FK clauses rewritten by SQLite.
Migration 1's content rewrite is legal because nothing was ever
shipped; dev databases stamped version 1 ride migration 2 up to 2.