diff --git a/modules/fifth-domain-lighthouse-sanitized-mirror/module.lock.hdlp b/modules/fifth-domain-lighthouse-sanitized-mirror/module.lock.hdlp index 89fa4d2..617db83 100644 --- a/modules/fifth-domain-lighthouse-sanitized-mirror/module.lock.hdlp +++ b/modules/fifth-domain-lighthouse-sanitized-mirror/module.lock.hdlp @@ -2,7 +2,7 @@ schema: tcs.module-lock/v1 module_id: TCS-AGENT-SANITIZED-MIRROR-001 module_source_sha256: 1210b30c7dabc56b8b989763230a3cf56c0fe02a3b409be5f3833dee9853d771 module_gir_sha256: 0e7b1bbd5f3f8dbc83df6c72e4a1e0532b6f44beed21c8c8315f85cc76096712 -runtime_sha256: 4b396550a144fa83614e340a34d087a96e2fb3e18045dabc801150f63b93c211 +runtime_sha256: 155e3fc332a4474593ca095b7ac03e9baf65fbcaaf3d40e393a1419444ec5c8b mirror_map_sha256: a03b5ba9e06f5fd3f626301305d75add0332ae06948a67c8bcd03c3cab248fe1 protocol_source_sha256: cf198e7939e4ab70828b74cef8a2b84c2dac2ff262582bb3d5663904d602d71b protocol_gir_sha256: 69f1a2a5cfc5ee61bdc2bf2065751d9cc41b9f662b28c35e61b393fd2187e1dc diff --git a/modules/fifth-domain-lighthouse-sanitized-mirror/receipts/self-test.hdlp b/modules/fifth-domain-lighthouse-sanitized-mirror/receipts/self-test.hdlp index 8c842e5..0598f29 100644 --- a/modules/fifth-domain-lighthouse-sanitized-mirror/receipts/self-test.hdlp +++ b/modules/fifth-domain-lighthouse-sanitized-mirror/receipts/self-test.hdlp @@ -1,7 +1,7 @@ schema: tcs.module-self-test-receipt/v1 module_id: TCS-AGENT-SANITIZED-MIRROR-001 result: PASS -tests: 7/7 +tests: 8/8 verified: - committed architecture projection contains no private absolute path - missing persona share decision creates no candidate @@ -9,6 +9,7 @@ verified: - sanitization produces quarantine only and never publication - mother acceptance is required before team review - both reviews bind the exact candidate hash and use independent reviewers + - review commands cannot replace the registered mother or team authority root - replay with changed content and review hash drift are rejected repository_regression: node --test 483/483 PASS tcs_root_regression: 5/5 PASS diff --git a/routing/lighthouse-persona-skill-harbor-map.json b/routing/lighthouse-persona-skill-harbor-map.json index 41d4269..ac4fbda 100644 --- a/routing/lighthouse-persona-skill-harbor-map.json +++ b/routing/lighthouse-persona-skill-harbor-map.json @@ -20,8 +20,8 @@ "forbidden_public_content": ["PRIVATE_MEMORY_PATH","RAW_PRIVATE_MEMORY","RAW_DIALOGUE","SECRET","CREDENTIAL","PRIVATE_RELATIONSHIP_CONTEXT","UNREGISTERED_EXTERNAL_IDENTITY"] }, "review_gates": [ - {"order":1,"reviewer_kind":"TCS_MOTHER","decision":"ACCEPT_OR_HOLD_OR_REJECT","cannot_publish":true}, - {"order":2,"reviewer_kind":"GUANGHU_HUMAN_TEAM","decision":"ACCEPT_OR_HOLD_OR_REJECT","requires_previous":"TCS_MOTHER_ACCEPT","reality_responsibility":true} + {"order":1,"reviewer_kind":"TCS_MOTHER","authority_root":"TCS-MOTHER-LPM-0001","decision":"ACCEPT_OR_HOLD_OR_REJECT","cannot_publish":true}, + {"order":2,"reviewer_kind":"GUANGHU_HUMAN_TEAM","authority_root":"TCS-0002∞","decision":"ACCEPT_OR_HOLD_OR_REJECT","requires_previous":"TCS_MOTHER_ACCEPT","reality_responsibility":true} ], "publication_boundary": {"arrival_registration_requires_both_accept":true,"catalog_visibility_requires_arrival_registration":true,"external_deploy_requires_separate_current_authorization_and_receipt":true,"market_transaction_not_implemented":true}, "compatibility": {"GLS-LIGHT-ARRIVAL-0001":"PARENT_INSTANCE_ARCHIVE_PROTOCOL_SEMANTIC_PRESERVED_NEW_SKILL_SUBCHANNEL_ONLY","GLS-0238":"PRIVATE_CONSENT_GATE_PRESERVED"} diff --git a/server-tools/fifth-domain-lighthouse-mirror/public_mirror_agent.py b/server-tools/fifth-domain-lighthouse-mirror/public_mirror_agent.py index 3efbc9e..f43d98a 100644 --- a/server-tools/fifth-domain-lighthouse-mirror/public_mirror_agent.py +++ b/server-tools/fifth-domain-lighthouse-mirror/public_mirror_agent.py @@ -209,6 +209,10 @@ def review(event: dict[str, Any], state: Path, kind: str) -> dict[str, Any]: decision = required_string(event, "decision") if decision not in {"ACCEPT", "HOLD", "REJECT"}: raise MirrorError("INVALID_REVIEW_DECISION") + expected_kind = "TCS_MOTHER" if kind == "mother" else "GUANGHU_HUMAN_TEAM" + expected_root = "TCS-MOTHER-LPM-0001" if kind == "mother" else "TCS-0002∞" + if event.get("reviewer_kind") != expected_kind or event.get("reviewer_authority_root") != expected_root: + raise MirrorError("REVIEWER_KIND_OR_AUTHORITY_ROOT_MISMATCH") candidate = json.loads(candidate_path(state, module_id, contribution_id).read_text()) if event.get("candidate_sha256") != candidate["candidate_sha256"]: raise MirrorError("CANDIDATE_HASH_MISMATCH") @@ -218,7 +222,8 @@ def review(event: dict[str, Any], state: Path, kind: str) -> dict[str, Any]: raise MirrorError("MOTHER_ACCEPT_REQUIRED_BEFORE_TEAM_REVIEW") receipt = { "schema": f"guanghu.lighthouse-persona-skill-{kind}-review/v1", - "reviewer_kind": "TCS_MOTHER" if kind == "mother" else "GUANGHU_HUMAN_TEAM", + "reviewer_kind": expected_kind, + "reviewer_authority_root": expected_root, "reviewer_id": reviewer_id, "module_id": module_id, "contribution_id": contribution_id, @@ -226,6 +231,7 @@ def review(event: dict[str, Any], state: Path, kind: str) -> dict[str, Any]: "decision": decision, "reason_code": event.get("reason_code", "NONE"), "external_effect": "NONE", + "authentication_state": "LOCAL_STRUCTURAL_ASSERTION_PENDING_SIGNED_PUBLICATION_GATE", } atomic_json(state / "reviews" / kind / f"{contribution_id}.json", receipt) return receipt diff --git a/server-tools/fifth-domain-lighthouse-mirror/test_public_mirror_agent.py b/server-tools/fifth-domain-lighthouse-mirror/test_public_mirror_agent.py index f8b1bbd..71eaee6 100644 --- a/server-tools/fifth-domain-lighthouse-mirror/test_public_mirror_agent.py +++ b/server-tools/fifth-domain-lighthouse-mirror/test_public_mirror_agent.py @@ -25,6 +25,10 @@ def contribution(**overrides): return value +def review_event(item, kind, reviewer): + return {"module_id":item["module_id"], "contribution_id":item["contribution_id"], "candidate_sha256":item["candidate_sha256"], "decision":"ACCEPT", "reviewer_id":reviewer, "reviewer_kind":"TCS_MOTHER" if kind == "mother" else "GUANGHU_HUMAN_TEAM", "reviewer_authority_root":"TCS-MOTHER-LPM-0001" if kind == "mother" else "TCS-0002∞"} + + class MirrorAgentTest(unittest.TestCase): def test_architecture_mirror_allowlists_and_has_no_private_path(self): with tempfile.TemporaryDirectory() as temp: @@ -59,11 +63,10 @@ class MirrorAgentTest(unittest.TestCase): with tempfile.TemporaryDirectory() as temp: state = Path(temp) item = M.prepare_contribution(contribution(), state) - base = {"module_id":item["module_id"],"contribution_id":item["contribution_id"],"candidate_sha256":item["candidate_sha256"],"decision":"ACCEPT"} with self.assertRaisesRegex(M.MirrorError, "MOTHER_ACCEPT"): - M.review({**base,"reviewer_id":"TEAM-1"}, state, "team") - M.review({**base,"reviewer_id":"MOTHER-1"}, state, "mother") - M.review({**base,"reviewer_id":"TEAM-1"}, state, "team") + M.review(review_event(item, "team", "TEAM-1"), state, "team") + M.review(review_event(item, "mother", "MOTHER-1"), state, "mother") + M.review(review_event(item, "team", "TEAM-1"), state, "team") record = M.register(item["module_id"], item["contribution_id"], state) self.assertEqual(record["state"], "ARRIVAL_REGISTERED_LOCAL_NOT_PUBLISHED") self.assertFalse(record["runtime_enabled"]) @@ -72,18 +75,26 @@ class MirrorAgentTest(unittest.TestCase): with tempfile.TemporaryDirectory() as temp: state = Path(temp) item = M.prepare_contribution(contribution(), state) - base = {"module_id":item["module_id"],"contribution_id":item["contribution_id"],"candidate_sha256":item["candidate_sha256"],"decision":"ACCEPT","reviewer_id":"SAME"} - M.review(base, state, "mother") - M.review(base, state, "team") + M.review(review_event(item, "mother", "SAME"), state, "mother") + M.review(review_event(item, "team", "SAME"), state, "team") with self.assertRaisesRegex(M.MirrorError, "INDEPENDENT_REVIEWERS"): M.register(item["module_id"], item["contribution_id"], state) + def test_review_command_cannot_change_registered_authority_root(self): + with tempfile.TemporaryDirectory() as temp: + state = Path(temp) + item = M.prepare_contribution(contribution(), state) + event = review_event(item, "mother", "MOTHER-1") + event["reviewer_authority_root"] = "TCS-0002∞" + with self.assertRaisesRegex(M.MirrorError, "AUTHORITY_ROOT"): + M.review(event, state, "mother") + def test_hash_drift_and_replay_rejected(self): with tempfile.TemporaryDirectory() as temp: state = Path(temp) item = M.prepare_contribution(contribution(), state) with self.assertRaisesRegex(M.MirrorError, "CANDIDATE_HASH"): - M.review({"module_id":item["module_id"],"contribution_id":item["contribution_id"],"candidate_sha256":"0"*64,"decision":"ACCEPT","reviewer_id":"M"}, state, "mother") + M.review({**review_event(item, "mother", "M"), "candidate_sha256":"0"*64}, state, "mother") with self.assertRaisesRegex(M.MirrorError, "REPLAY"): M.prepare_contribution(contribution(summary="changed"), state)