7The work queue's contract: one work item, one operation set, two backends.
11Plan 258 P1, from the factory-decomposition design note §5. The unit of
12work is a work item WITH A KIND -- ``plan`` is the first kind and the
13only one this release accepts -- and the queue has one set of
14operations that two backends implement: a file backend that is the
15default and the mode the package is tested in, and a registry backend
16that inherits signing, audit and checkpoints from the registry because
17that is where those already live. Neither backend is a special case,
18so the operations, the item, the state and the operation record are
19declared exactly once, here, and neither backend is the place where
20the shape gets decided.
22This module is deliberately a LEAF: it imports nothing from any
23consumer -- no registry, no chat client -- and nothing outside the
24standard library besides the diagnostics runtime. A witness measures
25that with the committed import-graph extractor rather than asserting
26it by hand. Plan 271 moved it here from ``oc_chat/core/work_queue.py``,
27which is now a rebinding shim over this module.
31- :class:`WorkItem` -- the unit of work: a kind, an id, and three
32 optional free-text fields.
33- :func:`check_kind` -- the ONE place a kind is checked, so a second
34 kind is one edit to :data:`SUPPORTED_KINDS`.
35- :class:`QueueBackend` -- the six operations, as a runtime-checkable
36 :class:`typing.Protocol`, so a backend is a plain class.
37- :class:`QueueState` -- three ordered tuples: pending, in progress,
39- :class:`QueueOp` -- one operation, as the log records it.
40- :class:`QueueError` and its named subclasses -- every refusal.
46 L2 - lifecycle events (module load)
47 L3 - semantic details (a refusal, with what it refused)
52- ``kind`` is a plain string checked by name against
53 :data:`SUPPORTED_KINDS`, read at call time. Widening that set is the
54 whole edit a second kind needs in this module. The design note names
55 the registry's plan-shaped ``related_plan_id`` column as the seam a
56 second kind will also find; that is named, not built.
57- ``item_id`` for a ``plan`` is the plan id, ``NNN-stem``. ``lane`` is
58 a free-text preference such as a model family or a harness name and
59 is NEVER a roster letter -- the letter model was retired by plan 223
60 and is not revived. ``instance`` is the ``agent-NNN`` identity
61 allocated at dispatch; identity is plan-AND-instance, so an item may
62 be re-dispatched and the log then shows both instances.
63- Positions are 1-BASED, matching the registry's ``queue_position``.
64- :class:`QueueOp.at` is ISO 8601 with an explicit offset.
65 :class:`QueueOp.actor` is free text and is NEVER signed here: a
66 backend that signs (the registry one) inherits signing from the
67 registry, and a backend that does not (the file one) must not
69- Every refusal is a named exception under :class:`QueueError`.
70 :class:`QueueBackendError` is the separate arm a caller maps to its
71 own "the backend said no" exit code; the other three are semantic.
75- oc_diagnostics (structured debug logging via _dbg)
78from __future__
import annotations
80from collections.abc
import Mapping
81from dataclasses
import dataclass, field
82from typing
import Protocol, runtime_checkable
84from oc_diagnostics
import _dbg
86_dbg(
"OC_CHAT.MAIN",
"module",
"work_queue loaded", 4)
98SUPPORTED_KINDS: frozenset[str] = frozenset({KIND_PLAN})
104OP_ENQUEUE =
"enqueue"
106OP_DEQUEUE =
"dequeue"
107OP_COMPLETE =
"complete"
112OP_NAMES: tuple[str, ...] = (OP_ENQUEUE, OP_MOVE, OP_DEQUEUE, OP_COMPLETE)
119 """Base class for every refusal the queue makes."""
123 """A work item named a kind this release does not accept."""
127 """An operation named an item the queue does not hold."""
131 """An enqueue named an item the queue already holds."""
135 """A position is not a 1-based index into the pending list."""
139 """The BACKEND refused, rather than the operation being meaningless.
141 A corrupt log line, a storage constraint, a missing argument the
142 storage demands: things a different backend would not have refused.
143 Callers that map refusals onto exit codes give this its own code.
148 """Refuse *kind* by name unless it is in :data:`SUPPORTED_KINDS`.
150 The single check site. :data:`SUPPORTED_KINDS` is read here, at call
151 time, so widening it is the whole edit a second kind needs.
154 UnknownKindError: VALIDATION_ERROR (L3) -- *kind* is not
155 accepted; the message names it and the accepted set.
158 if kind
not in SUPPORTED_KINDS:
159 accepted =
", ".join(sorted(SUPPORTED_KINDS))
160 _dbg(
"OC_CHAT.MAIN", func, f
"VALIDATION_ERROR: kind {kind!r}", 3)
161 raise UnknownKindError(
162 f
"unknown work-item kind {kind!r}; this release accepts: {accepted}"
169@dataclass(frozen=True)
171 """One unit of work: a kind, an id, and three free-text fields."""
179 lane: str |
None =
None
183 instance: str |
None =
None
185 note: str |
None =
None
188 """Refuse an unaccepted kind at construction, by name."""
192@dataclass(frozen=True)
194 """The queue's three buckets, each in its own order."""
196 pending: tuple[WorkItem, ...] = ()
197 in_progress: tuple[WorkItem, ...] = ()
198 completed: tuple[WorkItem, ...] = ()
201@dataclass(frozen=True)
203 """One recorded operation, in the shape the log stores."""
214 payload: Mapping[str, object] = field(default_factory=dict)
224 """The six operations, defined once, implemented by both backends."""
226 def enqueue(self, item: WorkItem, position: int |
None =
None) -> QueueState:
227 """Put *item* into pending at *position*, or at the end."""
229 def move(self, item_id: str, position: int, *, reason: str) -> QueueState:
230 """Move a pending item to *position*, for a stated *reason*."""
232 def dequeue(self, item_id: str, *, instance: str, reason: str) -> QueueState:
233 """Take an item out of pending into progress, naming *instance*."""
235 def complete(self, item_id: str, *, reason: str) -> QueueState:
236 """Finish an item, for a stated *reason*."""
239 """The three buckets as they stand."""
241 def history(self, item_id: str) -> tuple[QueueOp, ...]:
242 """Every recorded operation about *item_id*, oldest first."""
254 "DuplicateItemError",
QueueState enqueue(self, WorkItem item, int|None position=None)
QueueState dequeue(self, str item_id, *, str instance, str reason)
QueueState move(self, str item_id, int position, *, str reason)
tuple[QueueOp,...] history(self, str item_id)
QueueState complete(self, str item_id, *, str reason)
None check_kind(str kind)