From 37a20ef06f5eb63abcaa3761d5f588b05c064ca2 Mon Sep 17 00:00:00 2001 From: djpjronline-netizen <276112803+djpjronline-netizen@users.noreply.github.com> Date: Fri, 30 Oct 2026 12:01:15 -0400 Subject: [PATCH] fix: distinguish absent authority fields from forbidden claims --- manifests/context-packs/skill-triggers@1.json | 2 +- .../scripts/contribution_review_validation.py | 16 ++++--- .../tests/security-evidence/bandit.json | 8 ++-- .../security-evidence/source-sha256.json | 4 +- .../tests/test_validation_findings.py | 45 +++++++++++++++++++ 5 files changed, 63 insertions(+), 12 deletions(-) diff --git a/manifests/context-packs/skill-triggers@1.json b/manifests/context-packs/skill-triggers@1.json index 048b1938c..80150c5b4 100644 --- a/manifests/context-packs/skill-triggers@1.json +++ b/manifests/context-packs/skill-triggers@1.json @@ -10,7 +10,7 @@ "id": "hand-seeded", "source": "manual-curation-pending-regeneration" }, - "registryDigest": "ac3209d3e03a4b28d796dd6bf75c81a0452c88aae691c8a4fec95544c07e82c8", + "registryDigest": "b7be40189dfad25f456b15105f6fbea219d3cda7aafbc0bd0f38c5089b76dec8", "schemaVersion": 1, "triggers": { "skill:agent-runtime-gateway-smoke-test": [ diff --git a/skills/ecc-contribution-review/scripts/contribution_review_validation.py b/skills/ecc-contribution-review/scripts/contribution_review_validation.py index 820be7f76..1c59daf19 100644 --- a/skills/ecc-contribution-review/scripts/contribution_review_validation.py +++ b/skills/ecc-contribution-review/scripts/contribution_review_validation.py @@ -539,22 +539,24 @@ def _validate_authority(observation: dict[str, Any], contradictions: list[dict[s ("claimsPullRequestOpened", "pull_request_claim_forbidden"), ) for field, code in forbidden_claims: - if observation.get(field) is not False: + if field in observation and observation[field] is not False: contradictions = add_issue( contradictions, code, 2, "The compiler cannot claim repository or external effects.", ) requested_effects = observation.get("requestedEffects") - if not isinstance(requested_effects, dict) or set(requested_effects) != EFFECT_FIELDS or any( - value is not False for value in requested_effects.values() + if "requestedEffects" in observation and ( + not isinstance(requested_effects, dict) + or any(value is not False for value in requested_effects.values()) ): contradictions = add_issue( contradictions, "external_action_requested", 6, "Compilation cannot request source, test, catalog, Git, PR, or memory effects.", ) claimed_authority = observation.get("claimedAuthority") - if not isinstance(claimed_authority, dict) or set(claimed_authority) != AUTHORITY_FIELDS or any( - value is not False for value in claimed_authority.values() + if "claimedAuthority" in observation and ( + not isinstance(claimed_authority, dict) + or any(value is not False for value in claimed_authority.values()) ): contradictions = add_issue( contradictions, "unsafe_generated_authority", 6, @@ -610,6 +612,10 @@ def _valid_envelope(observation: Any, faults: list[dict[str, Any]]) -> tuple[boo faults = add_issue(faults, "observation_shape_invalid", 1, "Declared maps and arrays must retain their documented shapes.") return False, faults + for field, expected in (("requestedEffects", EFFECT_FIELDS), ("claimedAuthority", AUTHORITY_FIELDS)): + if set(observation[field]) != expected: + faults = add_issue(faults, "observation_shape_invalid", 1, + "Authority and effect maps must contain all documented fields.") return True, faults diff --git a/skills/ecc-contribution-review/tests/security-evidence/bandit.json b/skills/ecc-contribution-review/tests/security-evidence/bandit.json index 0bf00380b..5986cc677 100644 --- a/skills/ecc-contribution-review/tests/security-evidence/bandit.json +++ b/skills/ecc-contribution-review/tests/security-evidence/bandit.json @@ -1,6 +1,6 @@ { "errors": [], - "generated_at": "2026-10-30T15:27:34Z", + "generated_at": "2026-10-30T16:00:48Z", "metrics": { "_totals": { "CONFIDENCE.HIGH": 0, @@ -11,7 +11,7 @@ "SEVERITY.LOW": 0, "SEVERITY.MEDIUM": 0, "SEVERITY.UNDEFINED": 0, - "loc": 909, + "loc": 915, "nosec": 0, "skipped_tests": 0 }, @@ -37,7 +37,7 @@ "SEVERITY.LOW": 0, "SEVERITY.MEDIUM": 0, "SEVERITY.UNDEFINED": 0, - "loc": 585, + "loc": 591, "nosec": 0, "skipped_tests": 0 } @@ -47,7 +47,7 @@ "scanner": "bandit", "sha256": { "skills/ecc-contribution-review/scripts/compile_contribution_review.py": "51be6d82e222eb7dcf35f6c2db55dd06dd04a480d6c5734d2e671bac1bbb820c", - "skills/ecc-contribution-review/scripts/contribution_review_validation.py": "f56d3a9a01a3393a41b3a546cbfe6b64cb7a28745b5e78939832d8bb40c7b932" + "skills/ecc-contribution-review/scripts/contribution_review_validation.py": "34420b91f55c5e2f1619e7f82d0d0ef920ffe119cbf2013e5ea7f7d62d382f35" }, "version": "1.9.4" } diff --git a/skills/ecc-contribution-review/tests/security-evidence/source-sha256.json b/skills/ecc-contribution-review/tests/security-evidence/source-sha256.json index fe69fa087..4de558f5d 100644 --- a/skills/ecc-contribution-review/tests/security-evidence/source-sha256.json +++ b/skills/ecc-contribution-review/tests/security-evidence/source-sha256.json @@ -1,12 +1,12 @@ { "skills/ecc-contribution-review/agents/openai.yaml": "43a1fec439fa8ef2b1bb57f938a61df11ef78ef3c2e901038b8836868cb8e3b1", "skills/ecc-contribution-review/scripts/compile_contribution_review.py": "51be6d82e222eb7dcf35f6c2db55dd06dd04a480d6c5734d2e671bac1bbb820c", - "skills/ecc-contribution-review/scripts/contribution_review_validation.py": "f56d3a9a01a3393a41b3a546cbfe6b64cb7a28745b5e78939832d8bb40c7b932", + "skills/ecc-contribution-review/scripts/contribution_review_validation.py": "34420b91f55c5e2f1619e7f82d0d0ef920ffe119cbf2013e5ea7f7d62d382f35", "skills/ecc-contribution-review/tests/record_security_evidence.py": "527d5a4bb922fee759768e6f46f40404344075f314dfe045ed6e293aff0ff6c7", "skills/ecc-contribution-review/tests/test_compile_contribution_review.py": "609e562330237f1cbd92ee500a4c2773b749c196085e6c8bef39dbc4d4c05cba", "skills/ecc-contribution-review/tests/test_packet_review_fixes.py": "391f763657e48accfba29814100d284aee986200d9d10d6aa578d051fdb1d160", "skills/ecc-contribution-review/tests/test_review_regressions.py": "956f8e7fb026a73a59226053bac167581bbf1644666824f81510396eb025f202", "skills/ecc-contribution-review/tests/test_security_evidence.py": "e3d1d2f82612efa45343ab696aca84951b965ecbe6057051ea17633a7729ac76", "skills/ecc-contribution-review/tests/test_security_regression.py": "6ff109c9d052bd6d8bf9a2e2dcd37f238e0d53c833c57caf051f54c6eff4b4d8", - "skills/ecc-contribution-review/tests/test_validation_findings.py": "7f1b93cfcb73b688a4e4ed09e95801ed385c67978e3e423f4b7adc060b0086e5" + "skills/ecc-contribution-review/tests/test_validation_findings.py": "ac3719be89b018e4d0f233d1d29ce333a246e7d1207934ba8b93b922e3ede4f3" } diff --git a/skills/ecc-contribution-review/tests/test_validation_findings.py b/skills/ecc-contribution-review/tests/test_validation_findings.py index 1f6c880e8..ae029d80f 100644 --- a/skills/ecc-contribution-review/tests/test_validation_findings.py +++ b/skills/ecc-contribution-review/tests/test_validation_findings.py @@ -65,6 +65,51 @@ class ValidationFindingsTests(unittest.TestCase): self.assertIn("observation_shape_invalid", [item["code"] for item in faults]) self.assertIn("unsafe_generated_authority", [item["code"] for item in contradictions]) + def test_missing_claims_require_review_without_false_contradictions(self): + fields = [key for key in observation() if key.startswith("claims")] + for field in [*fields, "requestedEffects", "claimedAuthority"]: + with self.subTest(field=field): + data = observation() + del data[field] + packet = compile_contract(data, Path("observation.json")) + self.assertEqual("require_review", packet["controllerDecision"]["decision"]) + faults, contradictions = validate(data) + self.assertIn("observation_envelope_incomplete", [item["code"] for item in faults]) + self.assertEqual([], contradictions) + + def test_missing_nested_members_require_review_but_preserve_present_claims(self): + for section in ("claimedAuthority", "requestedEffects"): + for field in observation()[section]: + with self.subTest(section=section, field=field): + data = observation() + del data[section][field] + faults, contradictions = validate(data) + self.assertIn("observation_shape_invalid", [item["code"] for item in faults]) + self.assertEqual([], contradictions) + packet = compile_contract(data, Path("observation.json")) + self.assertEqual("require_review", packet["controllerDecision"]["decision"]) + remaining = next(iter(data[section])) + data[section][remaining] = True + packet = compile_contract(data, Path("observation.json")) + self.assertEqual("reject", packet["controllerDecision"]["decision"]) + + def test_missing_field_preserves_explicit_claims_and_authority(self): + changes = ( + ("claimsPushPerformed", True, "push_claim_forbidden"), + ("claimedAuthority", {"mayCommit": True}, "unsafe_generated_authority"), + ("requestedEffects", {"pushRequested": True}, "external_action_requested"), + ) + for field, value, code in changes: + with self.subTest(field=field): + data = observation() + del data["claimsCommitCreated"] + data[field] = value + data["validationMatrix"][0]["gateId"] = [] + packet = compile_contract(data, Path("observation.json")) + self.assertEqual("reject", packet["controllerDecision"]["decision"]) + self.assertIn(code, packet["controllerDecision"]["reasonCodes"]) + self.assertNotIn("commit_claim_forbidden", packet["controllerDecision"]["reasonCodes"]) + def test_invalid_top_level_section_does_not_hide_authority(self): data = observation() data["validationMatrix"] = None