Skip to content

Commit d248dd5

Browse files
committed
refactor(coordination): isolate retained source APIs from shadow producers
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
1 parent 8d5a6fc commit d248dd5

21 files changed

Lines changed: 642 additions & 531 deletions

‎docs/architecture/rfcs/typescript-control-plane-migration-v0.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,20 @@ caller-isolation exit, not deletion of the live legacy writer or completion of
112112
receiver adoption, Host execution, PostgreSQL or D2 qualification.
113113
[Contract and retained duties](../../reference/canonical-todo-completion-update.md#team-plan-source-writer-isolation).
114114

115+
Public CLI isolation now keeps shadow configuration in `coordination/configuration.py`
116+
and complete source capture in `coordination/authority_source_capture.py`. Cold import,
117+
source inspection and historical promotion recovery reuse those retained owners;
118+
only explicit enabled shadow actions load the old producers. Ordinary new-Goal
119+
SQLite creation, leased work, once-only settlement and reversible exit, plus cold
120+
File/SQLite import and original-operation replay, run through the real CLI with
121+
all four shadow producers physically absent in disposable source/normal-wheel
122+
copies. The old module still re-exports the retained APIs for current consumers;
123+
enabled capture, outbox and legacy commands remain supported. Preserve exact
124+
source identity, archive dependencies, safe lease reads and later-write recovery.
125+
This removes an eager-import blocker; deleting live producers still requires
126+
their last-caller exit. App/Lark contracts, provider transactions, cohort adoption
127+
and frozen D2 evidence remain separate.
128+
115129
Existing Goal migration, two-policy ownership retirement and storage-format
116130
upgrade retain separate receipts and exits. Original-receipt recovery does not
117131
justify retaining `legacy` as a live policy. Required migration readers remain.

‎docs/architecture/rfcs/typescript-control-plane-migration-v0.zh-CN.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,16 @@ canonical team-plan 只在受支持的未迁移分支加载 capture 和 Markdown
9191
不等于删除活跃旧 writer,或完成 receiver adoption、Host 执行、PostgreSQL/D2
9292
资格。见[合同及保留义务](../../reference/canonical-todo-completion-update.md#team-plan-source-writer-isolation)。
9393

94+
公开 CLI 的 shadow 配置归入 `coordination/configuration.py`,完整源捕获归入
95+
`coordination/authority_source_capture.py`;冷导入、源检查和历史 promotion 恢复
96+
复用保留 owner,只有显式开启的 shadow 动作加载旧 producer。隔离源码及正常
97+
wheel 的副本中物理删除四个 shadow producer 后,真实 CLI 仍覆盖新 Goal 默认
98+
SQLite、租约工作、一次结算及可回退出口,以及冷 File/SQLite 导入和原操作重放。
99+
旧模块为现有消费者保留 API 重导出;启用的 capture、outbox 和旧命令仍受支持。
100+
精确源身份、归档依赖、安全 lease 读取和后续写入恢复均须保留。本批移除提前导入
101+
阻塞;删除活跃 producer 仍须其最后调用方退出。App/Lark 合同、provider 事务、
102+
cohort 采用及冻结 D2 证据保持独立。
103+
94104
存量 Goal 迁移、两策略退役和格式升级仍各有独立回执及出口;原回执恢复不能成为
95105
保留 legacy 活跃策略的理由,必要迁移 reader 保留。
96106

‎loopx/capabilities/machine_configuration/goal_storage.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def initialize_goal_storage_target(runtime_root: Path, goal: Mapping[str, Any],
7575
# owner can request a complete source capture for unfinished creation.
7676
if result.get("source_capture_required") is not True:
7777
return result
78-
from ...control_plane.coordination.runtime_shadow import build_runtime_shadow_source_snapshot
78+
from ...control_plane.coordination.authority_source_capture import build_runtime_shadow_source_snapshot
7979
projection, snapshot = build_runtime_shadow_source_snapshot(goal=goal, runtime_root=runtime_root,
8080
state_path=state_path, registry_path=registry_path)
8181
request.update(creation_operation_id=goal.get("creation_operation_id"), projection=projection, source_snapshot=snapshot)

‎loopx/cli_commands/authority_shadow.py‎

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,12 @@
44
from collections.abc import Callable
55
from pathlib import Path
66

7-
from ..control_plane.coordination.local_authority_shadow_adapter import (
8-
CLI_DRAIN_LOCK_TIMEOUT_SECONDS,
9-
drain_local_authority_shadow_outbox,
10-
local_authority_shadow_status,
11-
)
12-
from ..control_plane.coordination.local_authority_shadow_outbox import OutboxError
137
from ..paths import effective_runtime_root
148
from ..file_lock import LockAcquireTimeoutError
159

1610

1711
AUTHORITY_SHADOW_CLI_SCHEMA = "loopx_authority_shadow_cli_v0"
12+
CLI_DRAIN_LOCK_TIMEOUT_SECONDS = 5.0
1813
CLI_DRAIN_MAX_ENTRIES = 256
1914
CLI_DRAIN_BUDGET_SECONDS = 30.0
2015

@@ -152,43 +147,50 @@ def handle_authority_shadow_command(
152147
action = str(getattr(args, "authority_shadow_command", None))
153148
payload: dict[str, object]
154149
try:
155-
# The same resolver every writer hook uses, so drain and status address
156-
# the lineage those hooks wrote.
157-
runtime_root = effective_runtime_root(registry_path, runtime_root_arg)
158-
if action == "drain":
159-
if args.max_entries < 1:
160-
raise ValueError("--max-entries must be at least 1")
161-
result = drain_local_authority_shadow_outbox(
162-
registry_path=registry_path,
163-
runtime_root=runtime_root,
164-
goal_id=args.goal_id,
165-
max_entries=args.max_entries,
166-
budget_seconds=args.budget_seconds,
167-
lock_timeout_seconds=args.lock_timeout_seconds,
168-
)
169-
payload = {
170-
"schema_version": AUTHORITY_SHADOW_CLI_SCHEMA,
171-
"action": "drain",
172-
**result.to_payload(),
173-
}
174-
else:
175-
payload = {
176-
"schema_version": AUTHORITY_SHADOW_CLI_SCHEMA,
177-
**local_authority_shadow_status(
150+
from ..control_plane.coordination.local_authority_shadow_adapter import (
151+
drain_local_authority_shadow_outbox,
152+
local_authority_shadow_status,
153+
)
154+
from ..control_plane.coordination.local_authority_shadow_outbox import OutboxError
155+
156+
try:
157+
# The same resolver every writer hook uses, so drain and status address
158+
# the lineage those hooks wrote.
159+
runtime_root = effective_runtime_root(registry_path, runtime_root_arg)
160+
if action == "drain":
161+
if args.max_entries < 1:
162+
raise ValueError("--max-entries must be at least 1")
163+
result = drain_local_authority_shadow_outbox(
178164
registry_path=registry_path,
179165
runtime_root=runtime_root,
180166
goal_id=args.goal_id,
181-
),
167+
max_entries=args.max_entries,
168+
budget_seconds=args.budget_seconds,
169+
lock_timeout_seconds=args.lock_timeout_seconds,
170+
)
171+
payload = {
172+
"schema_version": AUTHORITY_SHADOW_CLI_SCHEMA,
173+
"action": "drain",
174+
**result.to_payload(),
175+
}
176+
else:
177+
payload = {
178+
"schema_version": AUTHORITY_SHADOW_CLI_SCHEMA,
179+
**local_authority_shadow_status(
180+
registry_path=registry_path,
181+
runtime_root=runtime_root,
182+
goal_id=args.goal_id,
183+
),
184+
}
185+
except OutboxError as exc:
186+
payload = {
187+
"ok": False,
188+
"schema_version": AUTHORITY_SHADOW_CLI_SCHEMA,
189+
"action": action,
190+
"goal_id": args.goal_id,
191+
"error": str(exc),
192+
"error_code": exc.reason_code,
182193
}
183-
except OutboxError as exc:
184-
payload = {
185-
"ok": False,
186-
"schema_version": AUTHORITY_SHADOW_CLI_SCHEMA,
187-
"action": action,
188-
"goal_id": args.goal_id,
189-
"error": str(exc),
190-
"error_code": exc.reason_code,
191-
}
192194
except LockAcquireTimeoutError as exc:
193195
payload = {
194196
"ok": False,

‎loopx/cli_commands/coordination_shadow.py‎

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,8 @@
88

99
from ..agent_registry import registered_agent_ids_for_goal
1010

11-
# The projection builder is reached through this module by
12-
# tests that seed and read the shadow through the command surface; keep them
13-
# importable here even when the command does not call them directly.
14-
from ..control_plane.coordination.runtime_shadow import ( # noqa: F401
15-
bootstrap_coordination_runtime_shadow,
16-
build_runtime_shadow_source_snapshot,
17-
build_todo_runtime_shadow_projection,
18-
inspect_coordination_runtime_shadow,
19-
qualify_coordination_runtime_shadow,
20-
read_coordination_runtime_shadow_todo_candidate,
21-
review_local_coordination_authority_promotion,
22-
resolve_coordination_runtime_shadow_config,
23-
rollback_coordination_runtime_shadow,
24-
)
11+
from ..control_plane.coordination.authority_source_capture import build_runtime_shadow_source_snapshot
12+
from ..control_plane.coordination.configuration import resolve_coordination_runtime_shadow_config
2513
from ..control_plane.coordination.promotion_review import execute_reviewed_coordination_promotion
2614
from ..control_plane.coordination.shadow_goal_scope import shadow_goal_scope
2715
from ..control_plane.projects.registry_codec import load_project_registry
@@ -356,6 +344,9 @@ def handle_coordination_shadow_command(
356344
}
357345
print_payload(payload, output_format(args), _render)
358346
return 1
347+
# Only explicit, enabled legacy shadow actions load the producer.
348+
from ..control_plane.coordination import runtime_shadow as shadow
349+
359350
_, _, state_path = resolve_goal_state(registry=registry, goal_id=args.goal_id,
360351
project_override=args.project, state_file_override=args.state_file)
361352
if args.coordination_shadow_command == "rollback":
@@ -366,7 +357,7 @@ def handle_coordination_shadow_command(
366357
projection_version = _projection_version(projection)
367358
projected_todos = projection.get("todos")
368359
projected_leases = projection.get("leases")
369-
inspection = {"status": "not_evaluated"} if args.coordination_shadow_command == "rollback" else inspect_coordination_runtime_shadow(
360+
inspection = {"status": "not_evaluated"} if args.coordination_shadow_command == "rollback" else shadow.inspect_coordination_runtime_shadow(
370361
goal=goal,
371362
runtime_root=runtime_root,
372363
goal_id=args.goal_id,
@@ -408,7 +399,7 @@ def handle_coordination_shadow_command(
408399
operation_id = f"shadow-bootstrap:{args.goal_id}:{operation_digest}"
409400
with shadow_goal_scope(registry_path, goal_id=args.goal_id) as scope:
410401
goal = scope.goal
411-
bootstrap = bootstrap_coordination_runtime_shadow(
402+
bootstrap = shadow.bootstrap_coordination_runtime_shadow(
412403
goal=goal,
413404
runtime_root=runtime_root,
414405
goal_id=args.goal_id,
@@ -421,7 +412,7 @@ def handle_coordination_shadow_command(
421412
payload["executed"] = True
422413
payload["bootstrap"] = bootstrap
423414
if bootstrap.get("status") in {"applied", "replayed", "recovered"}:
424-
payload["inspection"] = inspect_coordination_runtime_shadow(
415+
payload["inspection"] = shadow.inspect_coordination_runtime_shadow(
425416
goal=goal,
426417
runtime_root=runtime_root,
427418
goal_id=args.goal_id,
@@ -435,7 +426,7 @@ def handle_coordination_shadow_command(
435426
and final_inspection.get("status") == "matched"
436427
)
437428
if args.coordination_shadow_command == "qualify":
438-
qualification = qualify_coordination_runtime_shadow(
429+
qualification = shadow.qualify_coordination_runtime_shadow(
439430
goal=goal,
440431
runtime_root=runtime_root,
441432
goal_id=args.goal_id,
@@ -447,7 +438,7 @@ def handle_coordination_shadow_command(
447438
payload["qualification"] = qualification
448439
payload["ok"] = qualification.get("status") == "qualified"
449440
if args.coordination_shadow_command == "read-candidate":
450-
read_candidate = read_coordination_runtime_shadow_todo_candidate(
441+
read_candidate = shadow.read_coordination_runtime_shadow_todo_candidate(
451442
goal=goal,
452443
runtime_root=runtime_root,
453444
goal_id=args.goal_id,
@@ -484,7 +475,7 @@ def handle_coordination_shadow_command(
484475
"registered_agents": registered_agents,
485476
}
486477
)
487-
promotion = review_local_coordination_authority_promotion(
478+
promotion = shadow.review_local_coordination_authority_promotion(
488479
goal=goal,
489480
runtime_root=runtime_root,
490481
goal_id=args.goal_id,
@@ -508,7 +499,7 @@ def handle_coordination_shadow_command(
508499
payload["expected_provider_revision"] = provider_revision
509500
payload["expected_bootstrap_operation_id"] = pending_bootstrap
510501
if args.execute:
511-
rollback = rollback_coordination_runtime_shadow(
502+
rollback = shadow.rollback_coordination_runtime_shadow(
512503
goal=goal, runtime_root=runtime_root, goal_id=args.goal_id,
513504
operation_id=f"shadow-rollback:{args.goal_id}:{provider_revision or pending_bootstrap}",
514505
expected_provider_revision=provider_revision,

‎loopx/configure_goal.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
)
5050
from .control_plane.agents.supervisor import normalize_peer_supervisor
5151
from .control_plane.agents.work_mode import normalize_agent_work_modes
52-
from .control_plane.coordination import runtime_shadow as shadow
52+
from .control_plane.coordination import configuration as shadow
5353
from .control_plane.coordination.configuration import normalize_goal_write_scope
5454
from .control_plane.operator_inbox_binding import local_private_config_digest
5555
from .control_plane.projects.registry_codec import (

0 commit comments

Comments
 (0)