7Domain-level registry mutation API.
11Expose typed queue and plan-lifecycle functions that emit canonical
12operation-log rows through :class:`oc_chat.registry.Registry`.
16- Build versioned payloads for the queue and plan operation catalogs.
17- Enforce domain-level preconditions that belong above the seam.
18- Keep callers state-version and operation-uuid unaware.
30- Every public mutating function calls ``Registry.execute_domain_command``.
31- This module performs no SQL, git, subprocess, filesystem, or clock work.
32- Payload version is always 1 for the v0.2.3 operation catalog.
36- typing (literal operation arguments)
37- collections.abc (the hub message payload mapping)
38- oc_diagnostics (structured debug logging via _dbg)
39- oc_chat.registry.api (Registry facade)
40- oc_chat.registry.backends.base (OperationLogRow return type)
42from __future__
import annotations
44from collections.abc
import Mapping
45from typing
import Literal
47from oc_diagnostics
import _dbg
53_dbg(
"OC_CHAT.MAIN",
"module",
"loaded", 4)
66ActorType = Literal[
"human_operator",
"orchestrator_agent",
"worker_agent"]
67QueueEnqueueReason = Literal[
69 "promoted_from_partial",
70 "restored_from_aborted_round",
72QueueDequeueReason = Literal[
73 "transition_to_failed",
74 "transition_to_integrating",
82PlanStatusSource = Literal[
"worker_self",
"orchestrator",
"round_lifecycle"]
84QUEUE_ENQUEUE_REASONS = {
86 "promoted_from_partial",
87 "restored_from_aborted_round",
89QUEUE_DEQUEUE_REASONS = {
90 "transition_to_failed",
91 "transition_to_integrating",
95PLAN_STATUS_SOURCES = {
"worker_self",
"orchestrator",
"round_lifecycle"}
115 reason: QueueEnqueueReason,
117 actor_type: ActorType,
118 command: str |
None =
None,
120 """Emit a ``queue.enqueue`` operation."""
122 if reason
not in QUEUE_ENQUEUE_REASONS:
123 raise ValueError(f
"invalid queue enqueue reason: {reason}")
124 _require_positive_position(position)
125 return registry.execute_domain_command(
132 "position": position,
135 actor_type=actor_type,
137 related_plan_id=plan_id,
151 actor_type: ActorType,
152 command: str |
None =
None,
154 """Emit a ``queue.move`` operation."""
156 _require_positive_position(from_position)
157 _require_positive_position(to_position)
158 if not reason.strip():
159 raise ValueError(
"queue move reason is required")
160 return registry.execute_domain_command(
166 "from_slot": from_slot,
168 "from_position": from_position,
169 "to_position": to_position,
172 actor_type=actor_type,
174 related_plan_id=plan_id,
184 reason: QueueDequeueReason,
186 actor_type: ActorType,
187 command: str |
None =
None,
189 """Emit a ``queue.dequeue`` operation."""
191 if reason
not in QUEUE_DEQUEUE_REASONS:
192 raise ValueError(f
"invalid queue dequeue reason: {reason}")
193 _require_positive_position(position)
194 return registry.execute_domain_command(
201 "position": position,
204 actor_type=actor_type,
206 related_plan_id=plan_id,
215 merge_commit_sha: str,
217 actor_type: ActorType,
218 command: str |
None =
None,
220 """Emit a ``queue.complete`` operation."""
223 raise ValueError(
"round_no must be positive")
224 return registry.execute_domain_command(
230 "round_no": round_no,
231 "merge_commit_sha": merge_commit_sha,
233 actor_type=actor_type,
235 related_plan_id=plan_id,
236 related_round=round_no,
248 body_observed_at: str |
None =
None,
249 plan_type: str |
None =
None,
250 model_override: str |
None =
None,
251 thinking_override: str |
None =
None,
252 session_override: str |
None =
None,
254 actor_type: ActorType,
255 command: str |
None =
None,
257 """Emit a ``plan.create`` operation."""
260 raise ValueError(
"plan_number must be positive")
261 return registry.execute_domain_command(
267 "plan_number": plan_number,
269 "plan_type": plan_type,
270 "body_path": body_path,
271 "body_sha256": body_sha256,
272 "body_observed_at": body_observed_at,
273 "model_override": model_override,
274 "thinking_override": thinking_override,
275 "session_override": session_override,
277 actor_type=actor_type,
279 related_plan_id=plan_id,
287 from_status: PlanStatus,
288 to_status: PlanStatus,
289 source: PlanStatusSource,
290 note: str |
None =
None,
292 actor_type: ActorType,
293 command: str |
None =
None,
295 """Emit a ``plan.set_status`` operation."""
297 _require_status(from_status,
"from_status")
298 _require_status(to_status,
"to_status")
299 if source
not in PLAN_STATUS_SOURCES:
300 raise ValueError(f
"invalid plan status source: {source}")
301 if to_status
in {
"partial",
"failed"}
and not (note
and note.strip()):
302 raise ValueError(
"note is required for partial and failed status transitions")
303 return registry.execute_domain_command(
309 "from_status": from_status,
310 "to_status": to_status,
314 actor_type=actor_type,
316 related_plan_id=plan_id,
324 from_slot: str |
None,
330 actor_type: ActorType,
331 command: str |
None =
None,
333 """Emit a ``plan.assign`` operation."""
335 _require_positive_position(queue_position)
336 return registry.execute_domain_command(
342 "from_slot": from_slot,
345 "worktree": worktree,
346 "queue_position": queue_position,
348 actor_type=actor_type,
350 related_plan_id=plan_id,
358 superseded_by_plan_id: str,
361 actor_type: ActorType,
362 command: str |
None =
None,
364 """Emit a ``plan.supersede`` operation."""
366 if not reason.strip():
367 raise ValueError(
"supersede reason is required")
368 return registry.execute_domain_command(
374 "superseded_by_plan_id": superseded_by_plan_id,
377 actor_type=actor_type,
379 related_plan_id=plan_id,
387 previous_body_sha256: str,
388 new_body_sha256: str,
389 previous_body_observed_at: str,
390 new_body_observed_at: str,
392 actor_type: ActorType,
393 command: str |
None =
None,
395 """Emit a ``plan.reimport`` operation."""
397 return registry.execute_domain_command(
403 "previous_body_sha256": previous_body_sha256,
404 "new_body_sha256": new_body_sha256,
405 "previous_body_observed_at": previous_body_observed_at,
406 "new_body_observed_at": new_body_observed_at,
408 actor_type=actor_type,
410 related_plan_id=plan_id,
414def auth_register_key(
419 secret_fingerprint: str,
421 actor_type: ActorType,
422 command: str |
None =
None,
424 """Emit an ``auth.register_key`` audit operation."""
426 return registry.execute_domain_command(
432 "hmac_key_id": hmac_key_id,
433 "secret_fingerprint": secret_fingerprint,
435 actor_type=actor_type,
444 previous_key_ids_revoked: list[str],
448 actor_type: ActorType,
449 command: str |
None =
None,
451 """Emit an ``auth.reset`` audit operation."""
453 return registry.execute_domain_command(
459 "previous_key_ids_revoked": previous_key_ids_revoked,
460 "new_key_id": new_key_id,
463 actor_type=actor_type,
468def handover_initiate(
475 actor_type: ActorType,
476 command: str |
None =
None,
478 """Emit a handover-start operation."""
480 return registry.execute_domain_command(
481 "role.handover.start",
485 "from_slot": from_slot,
489 actor_type=actor_type,
490 command=command
or "handover.initiate",
501 parent_operation_id: int,
503 actor_type: ActorType,
504 command: str |
None =
None,
506 """Emit a handover-complete operation."""
508 return registry.execute_domain_command(
509 "role.handover.complete",
513 "from_slot": from_slot,
515 "started_at": started_at,
516 "completed_at": completed_at,
517 "parent_operation_id": parent_operation_id,
519 actor_type=actor_type,
520 command=command
or "handover.accept",
521 parent_operation_id=parent_operation_id,
525RoundCleanupOutcome = Literal[
"clean",
"failed"]
532 orchestrator_slot: str,
533 plan_id: str |
None =
None,
534 merge_order: int |
None =
None,
536 actor_type: ActorType,
537 command: str |
None =
None,
539 """Emit a ``round.open`` operation (idempotent-additive per SF-001)."""
542 raise ValueError(
"round_no must be positive")
543 payload: dict[str, object] = {
545 "round_no": round_no,
546 "orchestrator_slot": orchestrator_slot,
548 if plan_id
is not None:
549 payload[
"plan_id"] = plan_id
550 if merge_order
is not None:
551 payload[
"merge_order"] = merge_order
552 return registry.execute_domain_command(
556 actor_type=actor_type,
558 related_plan_id=plan_id,
559 related_round=round_no,
569 merge_commit_sha: str,
570 base_sha: str |
None =
None,
571 staging_branch: str |
None =
None,
572 merge_base_sha: str |
None =
None,
574 actor_type: ActorType,
575 command: str |
None =
None,
577 """Emit a ``round.merge`` operation populating ``merge_commit_sha``."""
580 raise ValueError(
"round_no must be positive")
582 raise ValueError(
"merge_order must be positive")
583 return registry.execute_domain_command(
588 "round_no": round_no,
590 "merge_order": merge_order,
591 "base_sha": base_sha,
592 "merge_commit_sha": merge_commit_sha,
593 "staging_branch": staging_branch,
594 "merge_base_sha": merge_base_sha,
596 actor_type=actor_type,
598 related_plan_id=plan_id,
599 related_round=round_no,
607 test_count_before: int |
None,
608 test_count_after: int |
None,
610 result: Literal[
"pass",
"fail"],
611 duration_seconds: float |
None =
None,
612 plan_results: list[dict[str, object]] |
None =
None,
613 artifact_paths: list[str] |
None =
None,
615 actor_type: ActorType,
616 command: str |
None =
None,
618 """Emit a ``round.test`` operation recording the full-suite result."""
620 return registry.execute_domain_command(
625 "round_no": round_no,
626 "test_count_before": test_count_before,
627 "test_count_after": test_count_after,
628 "input_hash": input_hash,
630 "duration_seconds": duration_seconds,
631 "plan_results": plan_results
or [],
632 "artifact_paths": artifact_paths
or [],
634 actor_type=actor_type,
636 related_round=round_no,
648 result: Literal[
"pass",
"fail"],
649 artifact_paths: list[str] |
None =
None,
651 actor_type: ActorType,
652 command: str |
None =
None,
654 """Emit a ``round.lint`` operation recording the strict-profile result."""
656 return registry.execute_domain_command(
661 "round_no": round_no,
662 "staging_sha": staging_sha,
664 "warnings": warnings,
665 "input_hash": input_hash,
667 "artifact_paths": artifact_paths
or [],
669 actor_type=actor_type,
671 related_round=round_no,
675def round_consolidate(
679 final_commit_sha: str,
681 actor_type: ActorType,
682 command: str |
None =
None,
684 """Emit a ``round.consolidate`` operation; flips status to ``committed``."""
686 return registry.execute_domain_command(
691 "round_no": round_no,
692 "final_commit_sha": final_commit_sha,
694 actor_type=actor_type,
696 related_round=round_no,
707 actor_type: ActorType,
708 command: str |
None =
None,
710 """Emit a ``round.ff_main`` operation recording the fast-forward."""
712 return registry.execute_domain_command(
717 "round_no": round_no,
718 "from_sha": from_sha,
721 actor_type=actor_type,
723 related_round=round_no,
731 outcome: RoundCleanupOutcome,
732 branches_deleted: list[str] |
None =
None,
733 worktrees_removed: list[str] |
None =
None,
734 error_summary: str |
None =
None,
735 roadmap_version: str |
None =
None,
736 app_version: str |
None =
None,
738 actor_type: ActorType,
739 command: str |
None =
None,
741 """Emit a ``round.cleanup`` operation; closes the round when ``clean``."""
743 if outcome
not in {
"clean",
"failed"}:
744 raise ValueError(f
"invalid cleanup outcome: {outcome}")
745 return registry.execute_domain_command(
750 "round_no": round_no,
752 "branches_deleted": branches_deleted
or [],
753 "worktrees_removed": worktrees_removed
or [],
754 "error_summary": error_summary,
755 "roadmap_version": roadmap_version,
756 "app_version": app_version,
758 actor_type=actor_type,
760 related_round=round_no,
770 actor_type: ActorType,
771 command: str |
None =
None,
773 """Emit a ``round.abort`` operation; returns plans to ``ready_for_merge``."""
775 if not reason.strip():
776 raise ValueError(
"abort reason is required")
777 return registry.execute_domain_command(
782 "round_no": round_no,
785 actor_type=actor_type,
787 related_round=round_no,
794 message: Mapping[str, object],
797 actor_type: ActorType,
798 related_plan_id: str |
None =
None,
800 """Emit a ``hub.message`` operation carrying one addressed hub message.
802 Plan 259 P4. The hub's registry backend records a message as ONE
803 operation-log row and nothing else -- there is no projection, because
804 a message changes no projected state; it IS the state.
806 ``command`` is the hub kind, so the log is filterable by kind without
807 decoding a payload. ``related_plan_id`` is the message's subject when
808 that subject is a plan id, and ``None`` otherwise: the column is
809 plan-shaped, which the design note ยง5 names as the seam a second work
810 item kind will find, and smuggling a ticket id into it would make the
811 column lie before that seam is even reached.
813 **The ROW's ``actor_type`` and ``hmac_key_id`` are the signature; the
814 message's own ``provenance`` is the CLAIM.** Those are different
815 things and the hub keeps them apart on purpose: an agent can write
816 any provenance it likes into a payload, but the row's actor type is
817 derived from the slot by the storage layer and cannot be chosen.
820 raise ValueError(
"a hub message must name its kind")
821 return registry.execute_domain_command(
826 "message": dict(message),
828 actor_type=actor_type,
830 related_plan_id=related_plan_id,
834def _require_positive_position(position: int) ->
None:
836 raise ValueError(
"queue position must be positive")
839def _require_status(value: str, field: str) ->
None:
840 if value
not in PLAN_STATUSES:
841 raise ValueError(f
"invalid {field}: {value}")