feat(tasks): give task rows a canonical record identifier
Every work-record type carried a stable identifier except tasks, whose rows held only id, workplan_id, title, status and priority — nothing connecting a row to CUST-WP-0067-T01 in the file it came from. Matching was therefore by title, so a renamed heading looked like one task vanishing and another appearing, and the forge-derived reset had to refuse to touch tasks at all. Adds tasks.record_id (nullable: no migration can invent an identity for an existing row) and a backfill that reads the pairing from the repository files, where a task declares both its canonical id and its projection UUID. 5516 pairs across 121 repositories with zero conflicts; 4456 of 6073 cache task rows identified. Diff and reset now key on record_id where present, falling back to a title-prefixed key so an unidentified row stays visibly unidentified. Unknown stays unknown: a row the files do not claim keeps no identity and the reset keeps refusing to act on it, and an existing identity is never overwritten — a mismatch is recorded as a conflict rather than resolved. Refs STATE-WP-0083-T06 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 2583210@bnt-lap001 Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
This commit is contained in:
parent
43ffe883c3
commit
8b207a991a
6 changed files with 325 additions and 6 deletions
36
migrations/versions/e2b3c4d5f6a7_task_canonical_record_id.py
Normal file
36
migrations/versions/e2b3c4d5f6a7_task_canonical_record_id.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
"""task canonical record id (STATE-WP-0083-T06)
|
||||
|
||||
Every work-record type carries a stable canonical identifier except tasks. A
|
||||
task row holds `id, workplan_id, title, status, priority` — nothing connecting it
|
||||
to `CUST-WP-0067-T01` in the file it came from.
|
||||
|
||||
The consequence is that a file task and a hub task can only be matched by title,
|
||||
so a renamed heading looks like one task disappearing and another appearing. That
|
||||
is why the forge-derived reset deliberately refuses to touch tasks of existing
|
||||
workplans: destroying and recreating a record because someone edited a heading is
|
||||
not an acceptable failure mode.
|
||||
|
||||
Nullable on purpose. Existing rows have no canonical id and cannot be given one
|
||||
by this migration — only the repository files know the mapping, and backfilling
|
||||
from them is a separate, reversible step.
|
||||
|
||||
Revision ID: e2b3c4d5f6a7
|
||||
Revises: d1a2b3c4e5f6
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = "e2b3c4d5f6a7"
|
||||
down_revision = "d1a2b3c4e5f6"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("tasks", sa.Column("record_id", sa.String(length=120), nullable=True))
|
||||
op.create_index("ix_tasks_record_id", "tasks", ["record_id"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_tasks_record_id", table_name="tasks")
|
||||
op.drop_column("tasks", "record_id")
|
||||
Loading…
Add table
Add a link
Reference in a new issue