fix: bind skill reviews to mother and team roots
This commit is contained in:
parent
8519b8c492
commit
b9e4938a0f
5 changed files with 31 additions and 13 deletions
|
|
@ -2,7 +2,7 @@ schema: tcs.module-lock/v1
|
||||||
module_id: TCS-AGENT-SANITIZED-MIRROR-001
|
module_id: TCS-AGENT-SANITIZED-MIRROR-001
|
||||||
module_source_sha256: 1210b30c7dabc56b8b989763230a3cf56c0fe02a3b409be5f3833dee9853d771
|
module_source_sha256: 1210b30c7dabc56b8b989763230a3cf56c0fe02a3b409be5f3833dee9853d771
|
||||||
module_gir_sha256: 0e7b1bbd5f3f8dbc83df6c72e4a1e0532b6f44beed21c8c8315f85cc76096712
|
module_gir_sha256: 0e7b1bbd5f3f8dbc83df6c72e4a1e0532b6f44beed21c8c8315f85cc76096712
|
||||||
runtime_sha256: 4b396550a144fa83614e340a34d087a96e2fb3e18045dabc801150f63b93c211
|
runtime_sha256: 155e3fc332a4474593ca095b7ac03e9baf65fbcaaf3d40e393a1419444ec5c8b
|
||||||
mirror_map_sha256: a03b5ba9e06f5fd3f626301305d75add0332ae06948a67c8bcd03c3cab248fe1
|
mirror_map_sha256: a03b5ba9e06f5fd3f626301305d75add0332ae06948a67c8bcd03c3cab248fe1
|
||||||
protocol_source_sha256: cf198e7939e4ab70828b74cef8a2b84c2dac2ff262582bb3d5663904d602d71b
|
protocol_source_sha256: cf198e7939e4ab70828b74cef8a2b84c2dac2ff262582bb3d5663904d602d71b
|
||||||
protocol_gir_sha256: 69f1a2a5cfc5ee61bdc2bf2065751d9cc41b9f662b28c35e61b393fd2187e1dc
|
protocol_gir_sha256: 69f1a2a5cfc5ee61bdc2bf2065751d9cc41b9f662b28c35e61b393fd2187e1dc
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
schema: tcs.module-self-test-receipt/v1
|
schema: tcs.module-self-test-receipt/v1
|
||||||
module_id: TCS-AGENT-SANITIZED-MIRROR-001
|
module_id: TCS-AGENT-SANITIZED-MIRROR-001
|
||||||
result: PASS
|
result: PASS
|
||||||
tests: 7/7
|
tests: 8/8
|
||||||
verified:
|
verified:
|
||||||
- committed architecture projection contains no private absolute path
|
- committed architecture projection contains no private absolute path
|
||||||
- missing persona share decision creates no candidate
|
- missing persona share decision creates no candidate
|
||||||
|
|
@ -9,6 +9,7 @@ verified:
|
||||||
- sanitization produces quarantine only and never publication
|
- sanitization produces quarantine only and never publication
|
||||||
- mother acceptance is required before team review
|
- mother acceptance is required before team review
|
||||||
- both reviews bind the exact candidate hash and use independent reviewers
|
- 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
|
- replay with changed content and review hash drift are rejected
|
||||||
repository_regression: node --test 483/483 PASS
|
repository_regression: node --test 483/483 PASS
|
||||||
tcs_root_regression: 5/5 PASS
|
tcs_root_regression: 5/5 PASS
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@
|
||||||
"forbidden_public_content": ["PRIVATE_MEMORY_PATH","RAW_PRIVATE_MEMORY","RAW_DIALOGUE","SECRET","CREDENTIAL","PRIVATE_RELATIONSHIP_CONTEXT","UNREGISTERED_EXTERNAL_IDENTITY"]
|
"forbidden_public_content": ["PRIVATE_MEMORY_PATH","RAW_PRIVATE_MEMORY","RAW_DIALOGUE","SECRET","CREDENTIAL","PRIVATE_RELATIONSHIP_CONTEXT","UNREGISTERED_EXTERNAL_IDENTITY"]
|
||||||
},
|
},
|
||||||
"review_gates": [
|
"review_gates": [
|
||||||
{"order":1,"reviewer_kind":"TCS_MOTHER","decision":"ACCEPT_OR_HOLD_OR_REJECT","cannot_publish":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","decision":"ACCEPT_OR_HOLD_OR_REJECT","requires_previous":"TCS_MOTHER_ACCEPT","reality_responsibility":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},
|
"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"}
|
"compatibility": {"GLS-LIGHT-ARRIVAL-0001":"PARENT_INSTANCE_ARCHIVE_PROTOCOL_SEMANTIC_PRESERVED_NEW_SKILL_SUBCHANNEL_ONLY","GLS-0238":"PRIVATE_CONSENT_GATE_PRESERVED"}
|
||||||
|
|
|
||||||
|
|
@ -209,6 +209,10 @@ def review(event: dict[str, Any], state: Path, kind: str) -> dict[str, Any]:
|
||||||
decision = required_string(event, "decision")
|
decision = required_string(event, "decision")
|
||||||
if decision not in {"ACCEPT", "HOLD", "REJECT"}:
|
if decision not in {"ACCEPT", "HOLD", "REJECT"}:
|
||||||
raise MirrorError("INVALID_REVIEW_DECISION")
|
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())
|
candidate = json.loads(candidate_path(state, module_id, contribution_id).read_text())
|
||||||
if event.get("candidate_sha256") != candidate["candidate_sha256"]:
|
if event.get("candidate_sha256") != candidate["candidate_sha256"]:
|
||||||
raise MirrorError("CANDIDATE_HASH_MISMATCH")
|
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")
|
raise MirrorError("MOTHER_ACCEPT_REQUIRED_BEFORE_TEAM_REVIEW")
|
||||||
receipt = {
|
receipt = {
|
||||||
"schema": f"guanghu.lighthouse-persona-skill-{kind}-review/v1",
|
"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,
|
"reviewer_id": reviewer_id,
|
||||||
"module_id": module_id,
|
"module_id": module_id,
|
||||||
"contribution_id": contribution_id,
|
"contribution_id": contribution_id,
|
||||||
|
|
@ -226,6 +231,7 @@ def review(event: dict[str, Any], state: Path, kind: str) -> dict[str, Any]:
|
||||||
"decision": decision,
|
"decision": decision,
|
||||||
"reason_code": event.get("reason_code", "NONE"),
|
"reason_code": event.get("reason_code", "NONE"),
|
||||||
"external_effect": "NONE",
|
"external_effect": "NONE",
|
||||||
|
"authentication_state": "LOCAL_STRUCTURAL_ASSERTION_PENDING_SIGNED_PUBLICATION_GATE",
|
||||||
}
|
}
|
||||||
atomic_json(state / "reviews" / kind / f"{contribution_id}.json", receipt)
|
atomic_json(state / "reviews" / kind / f"{contribution_id}.json", receipt)
|
||||||
return receipt
|
return receipt
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,10 @@ def contribution(**overrides):
|
||||||
return value
|
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):
|
class MirrorAgentTest(unittest.TestCase):
|
||||||
def test_architecture_mirror_allowlists_and_has_no_private_path(self):
|
def test_architecture_mirror_allowlists_and_has_no_private_path(self):
|
||||||
with tempfile.TemporaryDirectory() as temp:
|
with tempfile.TemporaryDirectory() as temp:
|
||||||
|
|
@ -59,11 +63,10 @@ class MirrorAgentTest(unittest.TestCase):
|
||||||
with tempfile.TemporaryDirectory() as temp:
|
with tempfile.TemporaryDirectory() as temp:
|
||||||
state = Path(temp)
|
state = Path(temp)
|
||||||
item = M.prepare_contribution(contribution(), state)
|
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"):
|
with self.assertRaisesRegex(M.MirrorError, "MOTHER_ACCEPT"):
|
||||||
M.review({**base,"reviewer_id":"TEAM-1"}, state, "team")
|
M.review(review_event(item, "team", "TEAM-1"), state, "team")
|
||||||
M.review({**base,"reviewer_id":"MOTHER-1"}, state, "mother")
|
M.review(review_event(item, "mother", "MOTHER-1"), state, "mother")
|
||||||
M.review({**base,"reviewer_id":"TEAM-1"}, state, "team")
|
M.review(review_event(item, "team", "TEAM-1"), state, "team")
|
||||||
record = M.register(item["module_id"], item["contribution_id"], state)
|
record = M.register(item["module_id"], item["contribution_id"], state)
|
||||||
self.assertEqual(record["state"], "ARRIVAL_REGISTERED_LOCAL_NOT_PUBLISHED")
|
self.assertEqual(record["state"], "ARRIVAL_REGISTERED_LOCAL_NOT_PUBLISHED")
|
||||||
self.assertFalse(record["runtime_enabled"])
|
self.assertFalse(record["runtime_enabled"])
|
||||||
|
|
@ -72,18 +75,26 @@ class MirrorAgentTest(unittest.TestCase):
|
||||||
with tempfile.TemporaryDirectory() as temp:
|
with tempfile.TemporaryDirectory() as temp:
|
||||||
state = Path(temp)
|
state = Path(temp)
|
||||||
item = M.prepare_contribution(contribution(), state)
|
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(review_event(item, "mother", "SAME"), state, "mother")
|
||||||
M.review(base, state, "mother")
|
M.review(review_event(item, "team", "SAME"), state, "team")
|
||||||
M.review(base, state, "team")
|
|
||||||
with self.assertRaisesRegex(M.MirrorError, "INDEPENDENT_REVIEWERS"):
|
with self.assertRaisesRegex(M.MirrorError, "INDEPENDENT_REVIEWERS"):
|
||||||
M.register(item["module_id"], item["contribution_id"], state)
|
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):
|
def test_hash_drift_and_replay_rejected(self):
|
||||||
with tempfile.TemporaryDirectory() as temp:
|
with tempfile.TemporaryDirectory() as temp:
|
||||||
state = Path(temp)
|
state = Path(temp)
|
||||||
item = M.prepare_contribution(contribution(), state)
|
item = M.prepare_contribution(contribution(), state)
|
||||||
with self.assertRaisesRegex(M.MirrorError, "CANDIDATE_HASH"):
|
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"):
|
with self.assertRaisesRegex(M.MirrorError, "REPLAY"):
|
||||||
M.prepare_contribution(contribution(summary="changed"), state)
|
M.prepare_contribution(contribution(summary="changed"), state)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue