"""Per-invocation identity for pipeline runs. A pipeline invocation stamps a unique run id into the task context. The scan it runs records that id alongside its completion markers, and the shadow book requires an *exact* match before acting on the scan's batch. This is what timestamp comparison cannot provide. A manually triggered scan and the scheduled near-close pipeline are separate APScheduler jobs, and ``max_instances=1`` only serialises a job against itself — not two different jobs. So a manual scan can start just before the pipeline and finish just after it began, leaving a completion timestamp later than the pipeline's start even though its batch is unrelated. Matching on a run id generated by the pipeline, and stamped only by the scan running inside that pipeline, removes the ambiguity. Lives in its own module so the scheduler (which sets the id), the scanner (which stamps it), and the shadow book (which checks it) can all import it without an import cycle. """ from __future__ import annotations import contextvars import uuid _run_id: contextvars.ContextVar[str | None] = contextvars.ContextVar( "pipeline_run_id", default=None ) def new_run_id() -> str: """A fresh, collision-free run id.""" return uuid.uuid4().hex def current() -> str | None: """Run id of the pipeline invocation on the current task, if any.""" return _run_id.get() def bind(run_id: str) -> contextvars.Token: """Set the current run id; pass the returned token to ``release``.""" return _run_id.set(run_id) def release(token: contextvars.Token) -> None: """Restore the previous run id (call in a finally).""" _run_id.reset(token)