Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/architecture/rfcs/monorepo-distribution-split-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,35 @@ Approval needs a reviewed decision, not implementation progress or silence.
exact expiry remains a release-owner decision. M1 is open. No module-count,
loading-performance, split-package or R1 managed-Turn benefit is claimed.

### 2026-11-04 — M1 public status owner migration

- **Delta:** the complete `ChatStatusRequestMixin` and `goal_read_runtime_root`
implementation moves to `loopx.chat.status_api`. Chat HTTP and Todo detail
import that owner directly. The characterization prerequisite
[#8001](https://github.com/loopx-project/loopx/pull/8001) is already merged.
- **Direct migration:** the historical top-level `loopx.chat_status_api` module is
removed rather than retained as an alias or warning shim. The repository callers
and published-surface audit found no external importer; all active callers and
tests now use `loopx.chat.status_api`. This intentional compatibility exemption
applies only to this internal-only relocation and creates no general precedent
for removing a supported public import.
- **Authority boundary:** retarget both exact owner-path entries in
`tests/architecture/test_source_session_registry_denial.py`, leaving every
denial assertion and the `_discovery_registry` exception unchanged. Real HTTP
coverage permits lifecycle-only registry discovery but refuses full status
before collector execution. Moving the presentation owner grants no new
runtime, source, provider or execution authority.
- **Evidence:** owner tests now require the canonical import and absence of the
top-level shim. Existing HTTP, File/SQLite source routing, source loss/recovery
and delivery-review tests protect the original behavior. Absolute-import-
resolved AST comparison covers the complete owner; the denial oracle differs
only in its two owner paths.
- **Remaining:** this is one public-owner M1 slice, not completion of the wider
public Chat migration, D3, split distributions or R1 managed minimal. The
removed internal-only top-level module is one representational cleanup, not
evidence of loading-performance or long-horizon benefit. Source and isolated-
wheel qualification do not establish an upgrade of an existing installed host.

### 2026-11-04 — Capability discovery selected loading

- **Baseline:** `79b781865`. `capability list` / `show` and command help now
Expand Down
21 changes: 21 additions & 0 deletions docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,27 @@ R0 可以先于目录移动交付,但不代表内核合格。R3/R4 可选,
版本,D3 精确退役时间仍由发布负责人决定。M1 未关闭,不声明模块数量、加载性能、
拆包或 R1 managed Turn 收益。

### 2026-11-04 — M1 公开 status owner 迁移

- **变化:** 完整 `ChatStatusRequestMixin` 与 `goal_read_runtime_root` 实现迁入
`loopx.chat.status_api`,Chat HTTP 与 Todo detail 直接导入该 owner。刻画前置
[#8001](https://github.com/loopx-project/loopx/pull/8001) 已合入。
- **直接迁移:** 历史顶层 `loopx.chat_status_api` 模块直接删除,不保留 alias 或
warning shim。仓库调用方与公开面审计均未发现外部 importer;所有活跃调用方和
测试改用 `loopx.chat.status_api`。该兼容豁免仅适用于这个内部模块迁移,不构成
删除受支持公开 import 的通用先例。
- **权限边界:** 同步重定向
`tests/architecture/test_source_session_registry_denial.py` 的两个精确 owner
路径项,全部拒绝断言及 `_discovery_registry` 例外不变。真实 HTTP 覆盖允许仅生命周期
registry 的发现,但在 collector 执行前拒绝完整 status。移动 presentation owner
不授予新的 runtime、来源、provider 或执行权威。
- **证据:** owner 测试要求 canonical import,并断言顶层 shim 不存在。既有 HTTP、
File/SQLite 源路由、源丢失/恢复和 delivery review 测试保护原行为。解析为绝对
import 后的 AST 对比覆盖完整 owner;拒绝 oracle 仅有两个 owner 路径差异。
- **剩余:** 这是 M1 的一个公开 owner 片,不关闭更广的公开 Chat 迁移、D3、发行物
拆分或 R1 managed minimal。删除内部顶层模块属于表示层整理,不代表加载性能或
长程成果收益。源码与隔离 wheel 验证不等于升级既有已安装宿主。

### 2026-11-04 — Capability 发现路径的所选加载

- **基线:** `79b781865`。`capability list` / `show` 及命令帮助复用既有
Expand Down
40 changes: 20 additions & 20 deletions loopx/chat_status_api.py → loopx/chat/status_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,48 @@
from typing import Any
from urllib.parse import parse_qs, urlparse

from .chat import redact_local_paths
from .chat.goal_subagent_api import goal_subagent_configuration_enabled
from .chat.workspace_directory import workspace_goal_directory
from .codex_app_thread_activity import (
from ..chat import redact_local_paths
from .goal_subagent_api import goal_subagent_configuration_enabled
from .workspace_directory import workspace_goal_directory
from ..codex_app_thread_activity import (
codex_delivery_expectations,
codex_thread_observers,
)
from .control_plane.agents.host_thread_activity import (
from ..control_plane.agents.host_thread_activity import (
attach_host_delivery_windows,
attach_host_thread_activity,
)
from .control_plane.effect_runtime import (
from ..control_plane.effect_runtime import (
EffectRuntimePermanentIOError,
EffectRuntimeRemoteError,
EffectRuntimeStartupError,
effect_runtime_request_scope,
)
from .control_plane.coordination.local_authority import LocalCoordinationAuthorityUnavailable
from .control_plane.runtime.public_safety import (
from ..control_plane.coordination.local_authority import LocalCoordinationAuthorityUnavailable
from ..control_plane.runtime.public_safety import (
public_safe_compact_text,
validate_public_safe_value,
)
from .control_plane.todos.contract import (
from ..control_plane.todos.contract import (
TODO_STATUS_OPEN,
TODO_TASK_CLASS_MONITOR,
normalize_todo_status,
todo_done_for_status,
)
from .control_plane.work_items.task_graph import build_goal_task_map
from .control_plane.projection_envelope_facts import seal_projection_envelope, source_fact
from .control_plane.runtime.time import now_utc_iso
from .feedback import validate_goal_id
from .history import load_registry
from .control_plane.projects.registry_codec import load_project_registry
from .status import collect_status
from .status_server import (
from ..control_plane.work_items.task_graph import build_goal_task_map
from ..control_plane.projection_envelope_facts import seal_projection_envelope, source_fact
from ..control_plane.runtime.time import now_utc_iso
from ..feedback import validate_goal_id
from ..history import load_registry
from ..control_plane.projects.registry_codec import load_project_registry
from ..status import collect_status
from ..status_server import (
DEFAULT_EXTENSION_PRESENTATION_SURFACES_PATH,
DEFAULT_EXTENSION_PROJECTION_PATH,
ExtensionPresentationRequestMixin,
parse_goal_activation_filter,
)
from .control_plane.todos.list_readback import list_goal_todos
from ..control_plane.todos.list_readback import list_goal_todos


def goal_read_runtime_root(server: Any) -> str | None:
Expand Down Expand Up @@ -241,8 +241,8 @@ def _status(self, *, delivery_review: bool = False) -> None:
return
linked = None
if goal_id and registry.get("goal_source_links"):
from .control_plane.status.goal_source import SourceScopeError
from .presentation.goal_source_routing import goal_source_catalog
from ..control_plane.status.goal_source import SourceScopeError
from ..presentation.goal_source_routing import goal_source_catalog
catalog = goal_source_catalog(self.server)
if delivery_review and catalog.resolve(goal_id, registry):
self._send_error("Execution review belongs to the original Goal runtime.", status=403)
Expand Down
2 changes: 1 addition & 1 deletion loopx/chat/todo_detail.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import hashlib
from urllib.parse import parse_qs, urlparse

from ..chat_status_api import goal_read_runtime_root
from .status_api import goal_read_runtime_root
from ..status_server import is_loopback_host
from ..control_plane.todos.list_readback import list_goal_todos

Expand Down
2 changes: 1 addition & 1 deletion loopx/chat_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
)
from .capabilities.native_chat.model_settings_api import ChatModelSettingsRequestMixin
from .capabilities.native_chat.queue_api import ChatQueueRequestMixin
from .chat_status_api import ChatStatusRequestMixin
from .chat.status_api import ChatStatusRequestMixin
from .chat_runtime import (
STEERING_NOT_DELIVERED_CODES,
ChatRuntimeController,
Expand Down
4 changes: 2 additions & 2 deletions tests/architecture/test_source_session_registry_denial.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"loopx/control_plane/coordination/new_goal_storage_transport.py",
"loopx/chat_server.py",
"loopx/presentation/goal_direction_api.py",
"loopx/chat_status_api.py",
"loopx/chat/status_api.py",
"loopx/control_plane/goals/goal_amendment_runtime.py",
"loopx/control_plane/goals/source_session_registration.py",
"loopx/presentation/goal_amendment_api.py",
Expand Down Expand Up @@ -139,7 +139,7 @@ def test_direct_project_registry_loaders_have_source_session_denial() -> None:
"loopx/presentation/request_control_api.py": {"_request_control_source"},
"loopx/chat_server.py": {"serve_chat"},
"loopx/presentation/goal_direction_api.py": {"_direction_route"},
"loopx/chat_status_api.py": {"_discovery_registry"},
"loopx/chat/status_api.py": {"_discovery_registry"},
"loopx/control_plane/goals/direction_runtime.py": {"_source"},
"loopx/control_plane/coordination/new_goal_storage_transport.py": {"initialize_goal_storage_target"},
"loopx/presentation/goal_amendment_api.py": {"_amendment_inspect"},
Expand Down
4 changes: 2 additions & 2 deletions tests/control_plane/test_chat_status_source_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ def status_api(routed, tmp_path, monkeypatch):
server.limit = 80
server.verbose = False
server.goal_subagent_configuration_enabled = False
monkeypatch.setattr("loopx.chat_status_api.attach_host_thread_activity", lambda *a, **k: None)
monkeypatch.setattr("loopx.chat_status_api.attach_host_delivery_windows", lambda *a, **k: None)
monkeypatch.setattr("loopx.chat.status_api.attach_host_thread_activity", lambda *a, **k: None)
monkeypatch.setattr("loopx.chat.status_api.attach_host_delivery_windows", lambda *a, **k: None)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()

Expand Down
6 changes: 3 additions & 3 deletions tests/control_plane/test_chat_workbench_roots.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from canonical_authority_fixture import isolate_sqlite_runtime, promoted_create_fixture
from loopx.chat_runtime import ChatRuntimeController
from loopx.chat_status_api import ChatStatusRequestMixin
from loopx.chat.status_api import ChatStatusRequestMixin
from loopx.chat_store import ChatSessionStore


Expand All @@ -28,8 +28,8 @@ def test_status_reads_registered_work_while_chat_keeps_its_own_store(tmp_path, m
store.append_message(session["session_id"], role="agent", text="Keep the original private history.")
before = {path: path.read_bytes() for path in store.root.rglob("*.json")}
# External host activity is an optional observer, not the status authority.
monkeypatch.setattr("loopx.chat_status_api.attach_host_thread_activity", lambda *a, **k: None)
monkeypatch.setattr("loopx.chat_status_api.attach_host_delivery_windows", lambda *a, **k: None)
monkeypatch.setattr("loopx.chat.status_api.attach_host_thread_activity", lambda *a, **k: None)
monkeypatch.setattr("loopx.chat.status_api.attach_host_delivery_windows", lambda *a, **k: None)

class Reader(ChatStatusRequestMixin):
server = SimpleNamespace(registry_path=registry, runtime_root=store.root.parent,
Expand Down
8 changes: 8 additions & 0 deletions tests/test_chat_internal_module_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from loopx.chat.monitor_actions import ChatMonitorActionMixin
from loopx.chat.session_open import open_chat_session
from loopx.chat.ssh_source_api import SshSourceRequestMixin
from loopx.chat.status_api import ChatStatusRequestMixin, goal_read_runtime_root
from loopx.chat.todo_actions import ChatTodoActionMixin
from loopx.chat.todo_detail import TodoDetailRequestMixin
from loopx.chat.usage_statistics_api import UsageStatisticsRequestMixin
Expand Down Expand Up @@ -43,6 +44,7 @@ def test_chat_internal_helpers_load_below_the_public_chat_namespace() -> None:

def test_chat_internal_helpers_have_no_top_level_compatibility_shims() -> None:
for old_module in (
"loopx.chat_status_api",
"loopx.chat_event_cache",
"loopx.chat_automation_cadence_api",
"loopx.chat_goal_configuration_api",
Expand All @@ -56,3 +58,9 @@ def test_chat_internal_helpers_have_no_top_level_compatibility_shims() -> None:
"loopx.chat_workspace_directory",
):
assert importlib.util.find_spec(old_module) is None


def test_public_status_owner_is_nested_without_a_top_level_shim() -> None:
assert ChatStatusRequestMixin.__module__ == "loopx.chat.status_api"
assert goal_read_runtime_root.__module__ == "loopx.chat.status_api"
assert importlib.util.find_spec("loopx.chat_status_api") is None
6 changes: 3 additions & 3 deletions tests/test_chat_path_redaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
redact_response_markdown,
)
from loopx.chat_acp import ACPStdioAdapter
from loopx.chat_status_api import ChatStatusRequestMixin
from loopx.chat.status_api import ChatStatusRequestMixin


def test_windows_near_root_finishes_without_exponential_case_backtracking():
Expand Down Expand Up @@ -571,15 +571,15 @@ def test_stream_holds_long_encoded_private_root_prefix_until_redacting(encoding)
def test_status_http_hides_the_selected_custom_runtime_root(monkeypatch):
root = Path("/custom-volume/private-state")
monkeypatch.setattr(
"loopx.chat_status_api.collect_status",
"loopx.chat.status_api.collect_status",
lambda **kwargs: {
"ok": True,
"runtime_root": str(root),
"diagnostic": str(root / "private/gate.json"),
},
)
monkeypatch.setattr(
"loopx.chat_status_api.attach_host_thread_activity",
"loopx.chat.status_api.attach_host_thread_activity",
lambda *args, **kwargs: None,
)

Expand Down
24 changes: 12 additions & 12 deletions tests/test_chat_server_cors.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ def fake_collect_status(**kwargs):
calls.append(kwargs)
return {"ok": True, "scope": kwargs.get("activation_state_filter")}

monkeypatch.setattr("loopx.chat_status_api.collect_status", fake_collect_status)
monkeypatch.setattr("loopx.chat.status_api.collect_status", fake_collect_status)
server, thread = _start_server()
server.goal_subagent_configuration_enabled = True
try:
Expand Down Expand Up @@ -506,7 +506,7 @@ def fake_collect_status(**kwargs):

def test_chat_status_rejects_invalid_goal_activation_scope(monkeypatch) -> None:
monkeypatch.setattr(
"loopx.chat_status_api.collect_status",
"loopx.chat.status_api.collect_status",
lambda **_kwargs: (_ for _ in ()).throw(AssertionError("must fail before collection")),
)
server, thread = _start_server()
Expand Down Expand Up @@ -574,8 +574,8 @@ def test_workspace_directory_still_fails_on_an_unreadable_registry(tmp_path) ->

def test_workspace_directory_skips_expensive_projection_and_respects_scope(monkeypatch) -> None:
registry = {"goals": [{"id": "alpha", "display_name": "Alpha"}, {"id": "beta"}]}
monkeypatch.setattr("loopx.chat_status_api.load_project_registry", lambda _: registry)
monkeypatch.setattr("loopx.chat_status_api.collect_status", lambda **_: (_ for _ in ()).throw(AssertionError("directory must not collect status")))
monkeypatch.setattr("loopx.chat.status_api.load_project_registry", lambda _: registry)
monkeypatch.setattr("loopx.chat.status_api.collect_status", lambda **_: (_ for _ in ()).throw(AssertionError("directory must not collect status")))
server, thread = _start_server()
server.selected_goal_id = "alpha"
try:
Expand All @@ -596,12 +596,12 @@ def test_workspace_directory_skips_expensive_projection_and_respects_scope(monke

def test_workspace_scoped_status_revision_and_membership_fences(monkeypatch) -> None:
registry = {"goals": [{"id": "alpha"}, {"id": "beta"}]}
monkeypatch.setattr("loopx.chat_status_api.load_registry", lambda _: registry)
monkeypatch.setattr("loopx.chat.status_api.load_registry", lambda _: registry)
calls = []
def collect(**kwargs):
calls.append(kwargs["goal_id"])
return {"ok": True}
monkeypatch.setattr("loopx.chat_status_api.collect_status", collect)
monkeypatch.setattr("loopx.chat.status_api.collect_status", collect)
server, thread = _start_server()
try:
response = _request(server.server_address[1], method="GET", origin=None, path="/status.json?goal_id=alpha")
Expand All @@ -616,7 +616,7 @@ def collect(**kwargs):
def racing_collect(**kwargs):
registry["goals"].pop()
return {"ok": True}
monkeypatch.setattr("loopx.chat_status_api.collect_status", racing_collect)
monkeypatch.setattr("loopx.chat.status_api.collect_status", racing_collect)
response = _request(server.server_address[1], method="GET", origin=None, path="/status.json?goal_id=alpha")
response.read()
assert response.status == 409
Expand Down Expand Up @@ -689,13 +689,13 @@ def _status_failure(kind: str) -> BaseException:
)],
)
def test_workspace_status_access_error_contract(monkeypatch, kind: str, access: bool) -> None:
monkeypatch.setattr("loopx.chat_status_api.load_registry", lambda _: {"goals": [{"id": "alpha"}]})
monkeypatch.setattr("loopx.chat.status_api.load_registry", lambda _: {"goals": [{"id": "alpha"}]})
failure = _status_failure(kind)

def fail(**_kwargs):
raise failure

monkeypatch.setattr("loopx.chat_status_api.collect_status", fail)
monkeypatch.setattr("loopx.chat.status_api.collect_status", fail)
server, thread = _start_server()
try:
response = _request(server.server_address[1], method="GET", origin=None, path="/status.json?goal_id=alpha")
Expand All @@ -715,9 +715,9 @@ def test_workspace_access_code_does_not_change_other_status_routes(monkeypatch,
def fail(*_args, **_kwargs):
raise PermissionError("synthetic confidential diagnostic")

monkeypatch.setattr("loopx.chat_status_api.load_registry", fail)
monkeypatch.setattr("loopx.chat_status_api.load_project_registry", fail)
monkeypatch.setattr("loopx.chat_status_api.collect_status", fail)
monkeypatch.setattr("loopx.chat.status_api.load_registry", fail)
monkeypatch.setattr("loopx.chat.status_api.load_project_registry", fail)
monkeypatch.setattr("loopx.chat.status_api.collect_status", fail)
server, thread = _start_server()
try:
response = _request(server.server_address[1], method="GET", origin=None, path="/status.json" + query)
Expand Down
Loading
Loading