ADR-0023: Async Monitor-URL Polling — Backend-Local in _graph¶
Status¶
| Field | Value |
|---|---|
| Status | Accepted |
| Supersedes | — |
| Superseded by | — |
| Amends | — |
Revised 2026-06-03 in place rather than superseded — by the time the rewrite landed the ADR was unimplemented against, so there was no caller state to preserve and a superseding ADR would have added a level of indirection without aiding any reader. The rewrite is material (it dropped the shared-helper design); a future audit reading this file should note that the supersession discipline was traded for in-place clarity, scoped to the four Graph ADRs (0021..0024).
Context¶
Microsoft Graph answers long-running operations with 202 Accepted
plus a Location header pointing to a monitor URL the client polls
until the operation finishes. The Graph backend (ID-127, RFC-0010)
needs this for copy (always async) and move (may go async on
large or cross-folder items).
An earlier draft of this ADR proposed hoisting the polling logic into
a shared src/remote_store/backends/_async_monitor.py on the premise
that Azure cross-account copy and similar 202-monitor patterns
would reuse it. Reality check before implementation: no second
consumer exists today. AsyncAzureBackend.copy ships in v0.27.0 by
calling start_copy_from_url and returning — same-account Azure
copies complete server-side without polling, and cross-account copy
is not implemented. S3 multipart-copy completion uses a different
shape (UploadId + complete-multipart-upload, not a monitor URL).
"Shared helper" was speculative reuse for a single consumer.
Decision¶
- Ship the poller backend-local. Put the polling logic in
src/remote_store/aio/backends/_graph/monitor.py(inline inbackend.pyif it stays small). It lives underaio/backends/because the Graph backend is async-native (matchingaio/backends/_azure.py). - Not a shared facility (YAGNI). No second
202-monitor consumer exists today: same-account Azure copy completes server-side without polling, and S3 multipart completion uses a different shape (not a monitor URL). Reverse only when a second backend genuinely needs the same shape, measured in a follow-up rather than predicted here; a hoisting ADR then supersedes this one. - Parser-driven shape. The poller takes a
status_parsermapping each poll response topending/succeeded/failed, so the loop is already shaped for a second consumer without being a generic helper today. Cadence and timeout defaults are the spec's (GR-026). - No Store capability. A capability such as
ASYNC_COPYwould leak an implementation detail into the public API and invite callers to branch on "is this copy asynchronous?", the wrong question.Store.copy()is synchronous from the caller's view (ADR-0012); the backend presents that result regardless of how it gets there. - Not in
ext/. Extensions use only the public Store/Backend API (ADR-0008); the poller operates on raw HTTP, takes anhttpx.AsyncClient, and serves only the backend implementer.
Consequences¶
- One file, one consumer. No premature abstraction; the polling
code lives next to the only thing that calls it, and reviewers
reading
aio/backends/_graph/find the loop where they expect it (monitor.pynext tobackend.py). - No public API surface growth. The poller is private and un-exported. Changing its signature affects only Graph backend code.
- Store API remains sync-from-the-caller's-view. Async posture is owned by the backend (ADR-0012). The poller is an implementation technique, not a contract.
- Testing shape. The poller is tested with
respxfixtures as part of the Graph backend test surface. - No new capability.
CapabilitySetis unchanged. Callers do not observe whether an operation polled internally. - Future hoist is cheap. The migration is mechanical: lift the
function to a shared location (
aio/backends/_async_monitor.py, orbackends/_async_monitor.pyif a sync consumer also wants it), add a re-export from_graph/monitor.pyfor one release, and supersede this ADR. The parser-driven contract means no redesign at hoist time.
References¶
- RFC-0010: Microsoft Graph Backend (async monitor section)
sdd/specs/044-graph-backend.md(GR-025 through GR-027)- ADR-0012: Async Store / Backend API
- ADR-0008: Extension Architecture