101 lines
4.3 KiB
Python
101 lines
4.3 KiB
Python
"""Owner-aware read adapter over work_item / task metadata.
|
|
|
|
Company-mode historically embedded ~150+ context keys on ``Task.metadata``
|
|
and now stores WorkItem-owned fields on ``DelegationWorkItem.metadata``.
|
|
This adapter inverts old task-first reads: WorkItem-owned
|
|
keys are read from ``work_item.metadata`` first, and task-side fallback is
|
|
only allowed for keys explicitly marked as legacy fallback in
|
|
``metadata_ownership``. When no work item is in scope, it degrades to the
|
|
task-mode behavior and serves task metadata directly.
|
|
|
|
This module exposes a thin synchronous view that prefers work_item.metadata
|
|
and degrades to task.metadata. It's used by:
|
|
|
|
* ``opc.plugins.office_ui.snapshot_builder.work_item_to_kanban`` — the
|
|
kanban renderer previously read ``linked_task.metadata`` for
|
|
``progress_log`` / ``work_item_role_name`` / ``employee_prompt_context``
|
|
/ ``employee_delta_context``. This path switches it to the view
|
|
so it transparently prefers the work_item-side mirror once the mirror
|
|
starts dual-writing.
|
|
* ``opc.layer1_perception.context_assembler`` and
|
|
``opc.layer3_agent.external_broker`` — This path migrates those
|
|
consumers the same way. Because the view degrades cleanly to task.metadata
|
|
when no work_item is linked (task-mode path), task-mode callers
|
|
continue to function without any mode branching.
|
|
|
|
**Design constraints (things the view is explicitly NOT):**
|
|
|
|
* **No async / no I/O.** Construct from an in-scope work_item + task pair.
|
|
The snapshot builder and context assembler already have both; adding
|
|
async store reads would double the round-trips per render.
|
|
* **No cache.** Two dict reads is fast enough — measure before optimising.
|
|
* **No ``.set()`` writer.** Writes still go through the store via
|
|
``update_delegation_work_item`` / ``save_task``. The view is a read
|
|
surface only.
|
|
* **Defensive copies for list/dict.** Callers must not be able to
|
|
accidentally mutate the source dict through the view.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from opc.layer2_organization.metadata_ownership import supports_legacy_task_fallback
|
|
|
|
|
|
class WorkItemContextView:
|
|
"""Read-only view preferring WorkItem-owned metadata.
|
|
|
|
Constructor snapshots both dicts at construction. In company mode, task
|
|
fallback is a legacy compatibility path controlled by the owner matrix.
|
|
In task mode (no WorkItem), all task metadata remains visible.
|
|
"""
|
|
|
|
__slots__ = ("_wi_meta", "_task_meta", "_has_work_item")
|
|
|
|
def __init__(self, work_item: Any = None, task: Any = None):
|
|
wi_meta = getattr(work_item, "metadata", None) if work_item is not None else None
|
|
task_meta = getattr(task, "metadata", None) if task is not None else None
|
|
self._wi_meta: dict = dict(wi_meta) if isinstance(wi_meta, dict) else {}
|
|
self._task_meta: dict = dict(task_meta) if isinstance(task_meta, dict) else {}
|
|
self._has_work_item = isinstance(wi_meta, dict)
|
|
|
|
def get(self, key: str, default: Any = None) -> Any:
|
|
"""Return metadata according to the owner matrix.
|
|
|
|
Returns ``None`` if the key exists but is None on the work_item side;
|
|
this preserves dict.get semantics and prevents accidental fallback
|
|
over an explicit WorkItem value.
|
|
"""
|
|
if key in self._wi_meta:
|
|
return self._wi_meta[key]
|
|
if self._has_work_item and not supports_legacy_task_fallback(key):
|
|
return default
|
|
return self._task_meta.get(key, default)
|
|
|
|
def get_list(self, key: str) -> list:
|
|
"""Return a fresh list copy. Empty list for missing / non-list keys."""
|
|
v = self.get(key, None)
|
|
if isinstance(v, (list, tuple)):
|
|
return list(v)
|
|
return []
|
|
|
|
def get_dict(self, key: str) -> dict:
|
|
"""Return a fresh dict copy. Empty dict for missing / non-dict keys."""
|
|
v = self.get(key, None)
|
|
if isinstance(v, dict):
|
|
return dict(v)
|
|
return {}
|
|
|
|
def has(self, key: str) -> bool:
|
|
"""True iff the key is visible through this owner-aware view."""
|
|
return key in self._wi_meta or (
|
|
key in self._task_meta
|
|
and (not self._has_work_item or supports_legacy_task_fallback(key))
|
|
)
|
|
|
|
def __repr__(self) -> str:
|
|
return (
|
|
"WorkItemContextView("
|
|
f"wi_keys={len(self._wi_meta)}, task_keys={len(self._task_meta)})"
|
|
)
|