fix: stop misreading per-work-item spec as run-level company plan

Every completed company session's follow-up was answered with the canned
"Legacy company runtime run ... read-only" text (project 000, 2026-07-07).
Root cause: snapshot loaders read task.metadata work_item_runtime_plan as a
serialized run-level CompanyWorkItemRuntimePlan, but work-item tasks persist a
per-item assignment spec (projection_id/turn_type/summary/deliverables/...)
under that key. from_dict on the wrong shape silently yields an empty plan
(no projections, empty metadata), _runtime_uses_multi_team_org returns False,
and the resume path falls through to the legacy read-only branch.

Add is_serialized_company_work_item_runtime_plan (a run-level plan always
serializes with projections + runtime_model; a spec always carries
projection_id) and route all full-plan metadata reads through
serialized_company_plan_from_metadata, which skips wrong-shaped candidates so
the loaders fall back to the sample-metadata-constructed plan instead of an
empty one. Fixes existing DBs read-side; no data migration.

Verified: old path on the 000 shape classifies multi_team_org=False, new path
True; regression tests cover shape discrimination, snapshot classification,
and the follow-up never reporting legacy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
LZH-YS1998
2026-07-07 22:33:39 +08:00
parent c901800062
commit b4d28aefeb
3 changed files with 241 additions and 13 deletions
+4 -7
View File
@@ -88,6 +88,7 @@ from opc.layer2_organization.company_mode import (
deserialize_company_work_item_runtime_plan,
serialize_company_runtime_spec,
serialize_company_work_item_runtime_plan,
serialized_company_plan_from_metadata,
)
from opc.layer2_organization.company_runtime import canonical_role_session_id
from opc.layer2_organization.metadata_ownership import (
@@ -4988,7 +4989,7 @@ class OPCEngine:
plan_data = None
for task in sorted(latest_by_projection_id.values(), key=lambda item: (item.created_at, item.id), reverse=True):
candidate = task.metadata.get("company_work_item_plan") or task.metadata.get("work_item_runtime_plan")
candidate = serialized_company_plan_from_metadata(task.metadata)
if candidate:
plan_data = candidate
break
@@ -6458,7 +6459,7 @@ class OPCEngine:
for parent_session_id, group in runtime_groups.items():
plan_data = None
for task in sorted(group, key=lambda item: (item.created_at, item.id), reverse=True):
candidate = task.metadata.get("company_work_item_plan") or task.metadata.get("work_item_runtime_plan")
candidate = serialized_company_plan_from_metadata(task.metadata)
if candidate:
plan_data = candidate
break
@@ -8112,11 +8113,7 @@ class OPCEngine:
or task_metadata.get("company_profile", "")
or getattr(self.config.org, "company_profile", "corporate")
).strip() or "corporate"
plan_payload = (
task_metadata.get("company_work_item_plan")
or task_metadata.get("work_item_runtime_plan")
or {}
)
plan_payload = serialized_company_plan_from_metadata(task_metadata) or {}
work_item_plan = (
deserialize_company_work_item_runtime_plan(plan_payload)
if isinstance(plan_payload, dict) and plan_payload