feat(content): add doc_notes column for the documenter's own slot

This commit is contained in:
Renn F
2026-06-21 01:58:43 +02:00
parent a6e09f6d75
commit 31c2d5adc6
5 changed files with 21 additions and 3 deletions
@@ -1,9 +1,11 @@
"""Structured content columns + quick_context machine-marker split. """Structured content columns + quick_context machine-marker split.
Adds three nullable columns to ``tasks``: Adds four nullable columns to ``tasks``:
- ``pr_reviewer_notes`` (TEXT) — the PR reviewer's own rendered note slot, so a - ``pr_reviewer_notes`` (TEXT) — the PR reviewer's own rendered note slot, so a
review no longer overwrites ``qa_notes`` / ``dev_notes``. review no longer overwrites ``qa_notes`` / ``dev_notes``.
- ``doc_notes`` (TEXT) — the documenter's own rendered note slot (doc content
used to leak into the human ``quick_context`` blob).
- ``notes_structured`` (JSON) — the typed source of truth for every role's - ``notes_structured`` (JSON) — the typed source of truth for every role's
structured note (the TEXT note columns become a derived mirror). structured note (the TEXT note columns become a derived mirror).
- ``orchestration_markers`` (JSON) — the machine markers that used to be packed - ``orchestration_markers`` (JSON) — the machine markers that used to be packed
@@ -102,6 +104,7 @@ def _parse_quick_context(blob: str | None) -> tuple[dict, str | None]:
def upgrade() -> None: def upgrade() -> None:
op.add_column("tasks", sa.Column("pr_reviewer_notes", sa.Text(), nullable=True)) op.add_column("tasks", sa.Column("pr_reviewer_notes", sa.Text(), nullable=True))
op.add_column("tasks", sa.Column("doc_notes", sa.Text(), nullable=True))
op.add_column( op.add_column(
"tasks", sa.Column("notes_structured", postgresql.JSON(), nullable=True) "tasks", sa.Column("notes_structured", postgresql.JSON(), nullable=True)
) )
@@ -141,4 +144,5 @@ def downgrade() -> None:
# Lossy by design: markers are not re-merged back into quick_context. # Lossy by design: markers are not re-merged back into quick_context.
op.drop_column("tasks", "orchestration_markers") op.drop_column("tasks", "orchestration_markers")
op.drop_column("tasks", "notes_structured") op.drop_column("tasks", "notes_structured")
op.drop_column("tasks", "doc_notes")
op.drop_column("tasks", "pr_reviewer_notes") op.drop_column("tasks", "pr_reviewer_notes")
+2
View File
@@ -236,6 +236,7 @@ class TaskUpdate(BaseModel):
qa_notes: str | None = None qa_notes: str | None = None
auditor_notes: str | None = None auditor_notes: str | None = None
pr_reviewer_notes: str | None = None pr_reviewer_notes: str | None = None
doc_notes: str | None = None
quick_context: str | None = None quick_context: str | None = None
# Lifecycle override — privileged/admin only. Applied by the route as an # Lifecycle override — privileged/admin only. Applied by the route as an
@@ -335,6 +336,7 @@ class TaskResponse(BaseModel):
qa_notes: str | None qa_notes: str | None
auditor_notes: str | None = None auditor_notes: str | None = None
pr_reviewer_notes: str | None = None pr_reviewer_notes: str | None = None
doc_notes: str | None = None
quick_context: str | None quick_context: str | None
notes_structured: dict | None = None notes_structured: dict | None = None
orchestration_markers: dict | None = None orchestration_markers: dict | None = None
+1
View File
@@ -345,6 +345,7 @@ class TaskTable(Base):
# quick_context (never human-facing). pr_reviewer_notes is the reviewer's # quick_context (never human-facing). pr_reviewer_notes is the reviewer's
# own slot so a review no longer overwrites qa_notes / dev_notes. # own slot so a review no longer overwrites qa_notes / dev_notes.
pr_reviewer_notes: Mapped[str | None] = mapped_column(Text, nullable=True) pr_reviewer_notes: Mapped[str | None] = mapped_column(Text, nullable=True)
doc_notes: Mapped[str | None] = mapped_column(Text, nullable=True)
notes_structured: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True) notes_structured: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True)
orchestration_markers: Mapped[dict[str, Any] | None] = mapped_column( orchestration_markers: Mapped[dict[str, Any] | None] = mapped_column(
JSON, nullable=True JSON, nullable=True
+4
View File
@@ -251,6 +251,9 @@ class Task(TimestampMixin):
pr_reviewer_notes: str | None = Field( pr_reviewer_notes: str | None = Field(
default=None, description="PR reviewer's rendered verdict (own slot)" default=None, description="PR reviewer's rendered verdict (own slot)"
) )
doc_notes: str | None = Field(
default=None, description="Documenter's rendered note (own slot)"
)
notes_structured: dict | None = Field( notes_structured: dict | None = Field(
default=None, default=None,
description="Typed structured note payloads — the source of truth", description="Typed structured note payloads — the source of truth",
@@ -390,6 +393,7 @@ class TaskUpdate(RobocoBase):
auditor_notes: str | None = None auditor_notes: str | None = None
quick_context: str | None = None quick_context: str | None = None
pr_reviewer_notes: str | None = None pr_reviewer_notes: str | None = None
doc_notes: str | None = None
# Git fields # Git fields
task_type: TaskType | None = None task_type: TaskType | None = None
@@ -8,21 +8,28 @@ from roboco.models.task import TaskUpdate as DomainTaskUpdate
def test_api_task_update_accepts_auditor_and_pr_reviewer_notes() -> None: def test_api_task_update_accepts_auditor_and_pr_reviewer_notes() -> None:
u = ApiTaskUpdate(auditor_notes="audit", pr_reviewer_notes="review") u = ApiTaskUpdate(
auditor_notes="audit", pr_reviewer_notes="review", doc_notes="doc"
)
assert u.auditor_notes == "audit" assert u.auditor_notes == "audit"
assert u.pr_reviewer_notes == "review" assert u.pr_reviewer_notes == "review"
assert u.doc_notes == "doc"
def test_domain_task_update_accepts_new_notes() -> None: def test_domain_task_update_accepts_new_notes() -> None:
u = DomainTaskUpdate(auditor_notes="audit", pr_reviewer_notes="review") u = DomainTaskUpdate(
auditor_notes="audit", pr_reviewer_notes="review", doc_notes="doc"
)
assert u.auditor_notes == "audit" assert u.auditor_notes == "audit"
assert u.pr_reviewer_notes == "review" assert u.pr_reviewer_notes == "review"
assert u.doc_notes == "doc"
def test_task_response_exposes_structured_fields() -> None: def test_task_response_exposes_structured_fields() -> None:
fields = set(TaskResponse.model_fields) fields = set(TaskResponse.model_fields)
assert { assert {
"pr_reviewer_notes", "pr_reviewer_notes",
"doc_notes",
"notes_structured", "notes_structured",
"orchestration_markers", "orchestration_markers",
} <= fields } <= fields