diff --git a/docs/architecture/rfcs/complete-state-recovery-v0.md b/docs/architecture/rfcs/complete-state-recovery-v0.md index 0cb9cab292..066dcaad8e 100644 --- a/docs/architecture/rfcs/complete-state-recovery-v0.md +++ b/docs/architecture/rfcs/complete-state-recovery-v0.md @@ -778,7 +778,7 @@ Those measurements are historical evidence, not measurements of a later merge. | Existing transport limits: 64 MiB private snapshot only for the two manifest checks; normal RPC and worker input 2 MiB; owner stdout 2 Mi characters; saved-audit input 16 MiB | Bound exchanges and historical report decoding without widening ordinary transport | Large-manifest transport and audit-integrity tests cover the path. These independent limits are not additive capacity or proof that every maximum-size audit is publishable. | | Free destination space at least copied regular-file bytes plus 16 MiB before copying | Refuse a known-insufficient destination before publication | Source check plus failure-cleanup tests; concurrent disk consumption and a successful worst-case space reservation are not guaranteed. | -Source owners: [`state_recovery.py`](../../../loopx/state_recovery.py), +Source owners: [`state_recovery_io.py`](../../../loopx/control_plane/state_recovery_io.py), [`state_recovery.ts`](../../../loopx/control_plane/state_recovery.ts), and [`state_recovery_worker.ts`](../../../loopx/control_plane/state_recovery_worker.ts). Existing [Python recovery cases](../../../tests/test_state_recovery.py) and diff --git a/docs/architecture/rfcs/complete-state-recovery-v0.zh-CN.md b/docs/architecture/rfcs/complete-state-recovery-v0.zh-CN.md index 7e2c5382fa..224b3bab4f 100644 --- a/docs/architecture/rfcs/complete-state-recovery-v0.zh-CN.md +++ b/docs/architecture/rfcs/complete-state-recovery-v0.zh-CN.md @@ -668,7 +668,7 @@ provider、捕获一致性、身份接纳或 M2/M3 重新启用。同条评论 | 既有 transport:仅两项清单检查用 64 MiB private snapshot;普通 RPC 和 worker 输入 2 MiB;owner stdout 2 Mi 字符;历史 audit 输入 16 MiB | 限制传输和历史报告解码,不扩大普通 transport | 大清单 transport 与 audit 完整性测试覆盖路径。独立上限不能相加,也不保证任意最大 audit 均可发布。 | | 复制前目标剩余空间至少为普通文件复制字节数加 16 MiB | 发布前拒绝已知空间不足 | 有源码检查和失败清理测试;不保证抵御并发磁盘消耗,也不证明最坏空间预留成功。 | -源码 owner 为 [`state_recovery.py`](../../../loopx/state_recovery.py)、 +源码 owner 为 [`state_recovery_io.py`](../../../loopx/control_plane/state_recovery_io.py)、 [`state_recovery.ts`](../../../loopx/control_plane/state_recovery.ts) 和 [`state_recovery_worker.ts`](../../../loopx/control_plane/state_recovery_worker.ts)。 既有 [Python 恢复用例](../../../tests/test_state_recovery.py)及 diff --git a/examples/semantic-vocabulary-drift-smoke.py b/examples/semantic-vocabulary-drift-smoke.py index b7eb6f3d3f..f24251c8ec 100755 --- a/examples/semantic-vocabulary-drift-smoke.py +++ b/examples/semantic-vocabulary-drift-smoke.py @@ -192,7 +192,7 @@ # silently; that is the gap the anchor exists to close. COVERAGE_ANCHOR = { "vocabularies": 28, - "owner_symbols": 53, + "owner_symbols": 54, "literal_scan_fields": 1, "projections": 1, "relations": 9, diff --git a/loopx/cli_commands/support_control_backup.py b/loopx/cli_commands/support_control_backup.py index 73c12fa7ed..57b6cb7ede 100644 --- a/loopx/cli_commands/support_control_backup.py +++ b/loopx/cli_commands/support_control_backup.py @@ -88,7 +88,7 @@ def handle_backup_state_command( output_format: FormatSelector, ) -> int: if getattr(args, "backup_action", None) == "verify": - from ..state_recovery import StateRecoveryError, render_state_recovery_markdown, verify_state_backup + from ..control_plane.state_recovery_io import StateRecoveryError, render_state_recovery_markdown, verify_state_backup try: if not all(getattr(args, key, None) for key in ("archive", "manifest", "destination")): diff --git a/loopx/state_recovery.py b/loopx/control_plane/state_recovery_io.py similarity index 99% rename from loopx/state_recovery.py rename to loopx/control_plane/state_recovery_io.py index 8afa6fc959..60d56f6be9 100644 --- a/loopx/state_recovery.py +++ b/loopx/control_plane/state_recovery_io.py @@ -23,7 +23,7 @@ import zlib from typing import Any, BinaryIO, Iterator, NoReturn -from .control_plane.effect_runtime import _node_executable, effect_runtime_result, effect_runtime_request_scope +from .effect_runtime import _node_executable, effect_runtime_result, effect_runtime_request_scope MAX_ARCHIVE_BYTES = 128 * 1024 * 1024 MAX_EXPANDED_BYTES = 512 * 1024 * 1024 @@ -87,7 +87,7 @@ def _runtime(method: str, request: dict[str, Any]) -> dict[str, Any]: def _audit_owners(request: dict[str, Any]) -> dict[str, Any]: command = [_node_executable(), "--no-warnings", "--max-old-space-size=512", "--experimental-sqlite", "--experimental-strip-types", - str(Path(__file__).parent / "control_plane/state_recovery_worker.ts")] + str(Path(__file__).with_name("state_recovery_worker.ts"))] try: completed = subprocess.run(command, input=json.dumps(request), text=True, encoding="utf-8", capture_output=True, timeout=_OWNER_TIMEOUT_SECONDS, check=False) diff --git a/loopx/presentation/state_recovery_api.py b/loopx/presentation/state_recovery_api.py index 9f596399d9..492ee6a560 100644 --- a/loopx/presentation/state_recovery_api.py +++ b/loopx/presentation/state_recovery_api.py @@ -10,7 +10,7 @@ import time from uuid import uuid4 -from ..state_recovery import ( +from ..control_plane.state_recovery_io import ( MAX_ARCHIVE_BYTES, MAX_MANIFEST_BYTES, StateRecoveryError, read_state_recovery_audit, verify_state_backup, ) diff --git a/loopx/semantics/vocabulary_v0.json b/loopx/semantics/vocabulary_v0.json index dcfc15d8ce..443f29d648 100644 --- a/loopx/semantics/vocabulary_v0.json +++ b/loopx/semantics/vocabulary_v0.json @@ -171,7 +171,7 @@ }, "coverage_floor": { "vocabularies": 28, - "owner_symbols": 53, + "owner_symbols": 54, "literal_scan_fields": 1, "projections": 1, "relations": 9, diff --git a/tests/architecture/test_semantic_vocabulary_drift.py b/tests/architecture/test_semantic_vocabulary_drift.py index e768d8da4f..8dd97081b9 100644 --- a/tests/architecture/test_semantic_vocabulary_drift.py +++ b/tests/architecture/test_semantic_vocabulary_drift.py @@ -970,7 +970,7 @@ def test_native_lifecycle_relation_keeps_the_live_owner_and_verification_boundar smoke = runpy.run_path(str(SMOKE)) registry = smoke['load_registry']() assert registry['coverage_floor']['vocabularies'] == 28 - assert registry['coverage_floor']['owner_symbols'] == 53 + assert registry['coverage_floor']['owner_symbols'] == 54 assert registry['coverage_floor']['relations'] == 9 relation = next(row for row in registry['relations']['subsets'] if row['name'] == 'task_lease_lifecycle_decision_operation') diff --git a/tests/test_state_recovery.py b/tests/test_state_recovery.py index e1a0e4d3dd..23877ae432 100644 --- a/tests/test_state_recovery.py +++ b/tests/test_state_recovery.py @@ -16,7 +16,7 @@ import pytest from loopx.state_backup import build_state_backup_plan, execute_state_backup_plan -from loopx.state_recovery import StateRecoveryError, read_state_recovery_audit, render_state_recovery_markdown, verify_state_backup +from loopx.control_plane.state_recovery_io import StateRecoveryError, read_state_recovery_audit, render_state_recovery_markdown, verify_state_backup from tests.control_plane.canonical_authority_fixture import promoted_create_fixture @@ -133,7 +133,7 @@ def test_valid_internal_symlink_stays_inert_and_chained_escape_is_refused(tmp_pa def test_capacity_and_private_input_fail_before_publication(tmp_path, monkeypatch): - import loopx.state_recovery as recovery + import loopx.control_plane.state_recovery_io as recovery archive, manifest = _backup(tmp_path) original = archive.stat().st_mode @@ -185,7 +185,7 @@ def test_missing_selected_storage_is_unknown_without_creation_or_fallback(tmp_pa archive, manifest = _backup(tmp_path, provider) destination = tmp_path / "inert" original = verify_state_backup(archive=archive, manifest=manifest, destination=destination, execute=True) - from loopx.state_recovery import _audit_owners + from loopx.control_plane.state_recovery_io import _audit_owners selected = destination / "data/runtime-root/authority" / f"{provider}-v0" sqlite_files = [path.relative_to(destination / "data").as_posix() for path in selected.glob("*.sqlite")] @@ -244,7 +244,7 @@ def test_sqlite_views_are_never_evaluated_as_authority_tables(tmp_path): def test_offline_worker_timeout_does_not_leave_a_copy_or_stop_shared_runtime(tmp_path, monkeypatch): - import loopx.state_recovery as recovery + import loopx.control_plane.state_recovery_io as recovery from loopx.control_plane.effect_runtime import effect_runtime_result archive, manifest = _backup(tmp_path) @@ -378,7 +378,7 @@ def unexpected_decode(*_args): @pytest.mark.parametrize("kind", [tarfile.XHDTYPE, tarfile.XGLTYPE, tarfile.SOLARIS_XHDTYPE]) def test_oversized_pax_variants_are_refused_without_reading_the_body(kind): - from loopx.state_recovery import _scan + from loopx.control_plane.state_recovery_io import _scan member = tarfile.TarInfo("oversized-pax") member.type, member.size = kind, 128 * 1024 + 1 @@ -388,7 +388,7 @@ def test_oversized_pax_variants_are_refused_without_reading_the_body(kind): def test_malformed_pax_record_length_is_a_bounded_archive_error(): - from loopx.state_recovery import _scan + from loopx.control_plane.state_recovery_io import _scan payload = b"9" * 5000 + b" comment=invalid\n" member = tarfile.TarInfo("malformed-pax") @@ -400,7 +400,7 @@ def test_malformed_pax_record_length_is_a_bounded_archive_error(): def test_failed_publication_cleans_staging_even_when_destination_becomes_occupied(tmp_path, monkeypatch): - import loopx.state_recovery as recovery + import loopx.control_plane.state_recovery_io as recovery archive, manifest = _backup(tmp_path) destination = tmp_path / "inert"