fix(task-logs): a collaborator who may write a task may log on it (rule 78)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 11s
CI & Build / integration (push) Successful in 52s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / Python tests (push) Successful in 1m37s
CI & Build / Build & push image (push) Failing after 24s

create_log filtered on Note.user_id == user_id, a bare owner check, so a
collaborator with write access to a shared task was told it did not exist
— and, since a log now stamps the claim, could never be seen working it.
It now asks can_write_note. Editing and deleting a log still require its
author, which is authorship rather than access.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 06:48:01 -04:00
co-authored by Claude Opus 5.5
parent 22b7a928da
commit 4a93c8b262
3 changed files with 26 additions and 6 deletions
+7 -5
View File
@@ -7,7 +7,7 @@ from sqlalchemy import func, select
from scribe.models import async_session
from scribe.models.task_log import TaskLog
from scribe.models.note import Note, TaskStatus
from scribe.services.access import can_read_note, readable_notes_clause
from scribe.services.access import can_read_note, can_write_note, readable_notes_clause
from scribe.services.task_claims import stamp_claim
logger = logging.getLogger(__name__)
@@ -51,11 +51,13 @@ async def create_log(
content: str,
duration_minutes: int | None = None,
) -> TaskLog:
# Whoever may WRITE the task may log on it (rule #78) — a collaborator on a
# shared project included. This used to be a bare owner filter, which
# refused exactly the person a shared task exists for.
if not await can_write_note(user_id, task_id):
raise ValueError(f"Task {task_id} not found")
async with async_session() as session:
# Verify task exists and belongs to user
result = await session.execute(
select(Note).where(Note.id == task_id, Note.user_id == user_id)
)
result = await session.execute(select(Note).where(Note.id == task_id))
task = result.scalars().first()
if task is None:
raise ValueError(f"Task {task_id} not found")