From 8a3bba4eb87c31d2d9db5b101de2bc2b5686e8d4 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 13 May 2026 12:09:45 -0400 Subject: [PATCH] feat(schema): add note.description and note.consolidated_at Spec: docs/superpowers/specs/2026-05-13-task-as-durable-record-design.md - description: user-stated goal / initial context for tasks (NULL for knowledge notes). - consolidated_at: timestamp of the most recent auto-summary pass (NULL until first consolidation). - Migration 0044 backfills description from body for existing rows where status IS NOT NULL (i.e. tasks). Body left in place; first consolidation pass will overwrite it. --- ...dd_note_description_and_consolidated_at.py | 48 +++++++++++++++++++ src/fabledassistant/models/note.py | 8 ++++ 2 files changed, 56 insertions(+) create mode 100644 alembic/versions/0044_add_note_description_and_consolidated_at.py diff --git a/alembic/versions/0044_add_note_description_and_consolidated_at.py b/alembic/versions/0044_add_note_description_and_consolidated_at.py new file mode 100644 index 0000000..2fe7957 --- /dev/null +++ b/alembic/versions/0044_add_note_description_and_consolidated_at.py @@ -0,0 +1,48 @@ +"""add note.description and note.consolidated_at + +Revision ID: 0044 +Revises: 0043 +Create Date: 2026-05-13 + +Adds two columns to the ``notes`` table to support the task-as-durable-record +design (spec 2026-05-13): + +- ``description``: user-stated goal / initial context. Meaningful when + ``is_task=true``; left NULL on knowledge notes. +- ``consolidated_at``: timestamp of the most recent auto-summary pass. NULL + until the first consolidation runs. + +Backfill: for existing tasks we copy ``body`` into ``description`` so the +user's original goal text is preserved when ``body`` is later overwritten by +the auto-summary pipeline. The brief duplication window between +``body`` and ``description`` is harmless and resolves on the first +consolidation pass. +""" +from alembic import op +import sqlalchemy as sa + + +revision = "0044" +down_revision = "0043" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.add_column("notes", sa.Column("description", sa.Text(), nullable=True)) + op.add_column( + "notes", + sa.Column("consolidated_at", sa.DateTime(timezone=True), nullable=True), + ) + # is_task is a Python property (status IS NOT NULL); there's no DB column + # of that name. Backfill description from body for everything that + # qualifies as a task at the model layer. + op.execute( + "UPDATE notes SET description = body " + "WHERE status IS NOT NULL AND body IS NOT NULL" + ) + + +def downgrade() -> None: + op.drop_column("notes", "consolidated_at") + op.drop_column("notes", "description") diff --git a/src/fabledassistant/models/note.py b/src/fabledassistant/models/note.py index ca12310..dfce1b3 100644 --- a/src/fabledassistant/models/note.py +++ b/src/fabledassistant/models/note.py @@ -32,6 +32,10 @@ class Note(Base, TimestampMixin): ) title: Mapped[str] = mapped_column(Text, default="") body: Mapped[str] = mapped_column(Text, default="") + description: Mapped[str | None] = mapped_column(Text, nullable=True) + consolidated_at: Mapped[datetime | None] = mapped_column( + DateTime(timezone=True), nullable=True + ) tags: Mapped[list[str]] = mapped_column(ARRAY(Text), default=list) parent_id: Mapped[int | None] = mapped_column( Integer, ForeignKey("notes.id", ondelete="SET NULL"), nullable=True @@ -81,6 +85,10 @@ class Note(Base, TimestampMixin): "id": self.id, "title": self.title, "body": self.body, + "description": self.description, + "consolidated_at": ( + self.consolidated_at.isoformat() if self.consolidated_at else None + ), "tags": self.tags or [], "parent_id": self.parent_id, "project_id": self.project_id,