Skip to content

Restore true-recency resume via annotation_job_asset.touched_at #558

Description

@JArmandoAnaya

Home's "Continue where you left off" card was specified as the most recently active batch and
did not ship that way. Building the workspace dashboard (#552) turned up the reason: there is no
timestamp column on batch, on annotation, or on annotation_job_asset.
Nothing in the
storage format records when a batch was created, when it changed state, when a label was drawn, or
when an asset's progress last moved. Recency had no source, so the card shipped ranked by
progress instead — the batch furthest through that still has an unannotated frame — and
DESIGN.md, docs/api.md and the kernel model all state that substitution rather than hiding it.

The accepted limit was written down at the time: two batches part-way through resolve to the
further-along one rather than to the one somebody actually touched last. That is the defect this
issue closes.

The fix is one nullable column

annotation_job_asset gains touched_at, nullable, holding an ISO-8601 UTC string like every
other timestamp in the schema.

It is cheap because the write already exists and there is exactly one of it.
UnitOfWork.set_asset_progress is the narrow compare-and-set introduced when concurrent progress
writes were being lost (#302); every progress transition in the product goes through it, and
_job_write_children writes a progress value only on insert, so no other path can move one. The
stamp rides inside that same guarded UPDATE, which makes it as atomic as the transition it
records and unable to drift from it.

Nothing else changes shape. GET /home keeps its fields, the resume card keeps its layout, and no
surface gains a control.

What a NULL means, and the fallback

No backfill. A row that predates the column stays NULL, which reads as nobody has worked this
frame since the column existed
— the same posture annotation_schema.provenance and
inference_connection.model_family already take for values that could not honestly be
reconstructed.

So the ranking becomes two populations rather than one:

  • Any batch with a touched_at outranks every batch without one, and among those the latest touch
    wins.
  • Batches whose rows are all NULL are ranked among themselves by the progress rule that ships
    today, ties going to the later-created batch.

A workspace opened straight after upgrading therefore behaves exactly as it does now, and
converges to true recency as soon as somebody works in it.

Out of scope, and needs a decision

Whether batch itself gains created_at and state_changed_at is not decided here. With
touched_at in place the untouched population is still ordered by insertion, and a batch that
nobody has annotated in still has no age. The project header's Annotate control hit the same wall
from the other side — BatchOut carries no timestamp, so "the most recent batch" had no wire
source and that work asked the operator which batch instead (#544, shipped in #545). Adding
lifecycle timestamps to a batch is a domain-model decision with more consequences than a progress
stamp has, and it wants its own issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions