init: vaultmesh mcp server
This commit is contained in:
118
tests/governance/test_constitution_hash.py
Normal file
118
tests/governance/test_constitution_hash.py
Normal file
@@ -0,0 +1,118 @@
|
||||
"""
|
||||
Test: Constitution Hash Gate
|
||||
|
||||
Ensures the constitution has not been modified without proper amendment.
|
||||
CI MUST fail if the constitution hash doesn't match the lock file.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import blake3
|
||||
|
||||
|
||||
class TestConstitutionHash:
|
||||
"""Constitution integrity tests - HARD GATE."""
|
||||
|
||||
def test_constitution_exists(self, constitution_path):
|
||||
"""Constitution file must exist."""
|
||||
assert constitution_path.exists(), "MCP-CONSTITUTION.md not found"
|
||||
|
||||
def test_lock_file_exists(self, constitution_lock_path):
|
||||
"""Constitution lock file must exist."""
|
||||
assert constitution_lock_path.exists(), "governance/constitution.lock not found"
|
||||
|
||||
def test_constitution_hash_matches_lock(self, constitution_path, parse_lock_file):
|
||||
"""
|
||||
HARD GATE: Constitution hash must match lock file.
|
||||
|
||||
If this fails, either:
|
||||
1. Constitution was modified without amendment procedure
|
||||
2. Lock file needs updating via proper amendment
|
||||
"""
|
||||
# Read constitution
|
||||
content = constitution_path.read_text()
|
||||
lines = content.split('\n')
|
||||
|
||||
# Hash excludes signature block (last 12 lines as per original ceremony)
|
||||
# But after amendment protocol was added, we need to use the locked line count
|
||||
hash_lines = int(parse_lock_file.get("hash_lines", 288))
|
||||
hashable_content = '\n'.join(lines[:hash_lines])
|
||||
|
||||
computed_hash = f"blake3:{blake3.blake3(hashable_content.encode()).hexdigest()}"
|
||||
locked_hash = parse_lock_file["hash"]
|
||||
|
||||
assert computed_hash == locked_hash, (
|
||||
f"Constitution hash mismatch!\n"
|
||||
f" Computed: {computed_hash}\n"
|
||||
f" Locked: {locked_hash}\n"
|
||||
f" If intentional, follow amendment procedure."
|
||||
)
|
||||
|
||||
def test_version_not_decreased(self, parse_lock_file):
|
||||
"""Version must not decrease (no rollbacks without amendment)."""
|
||||
version = parse_lock_file["version"]
|
||||
parts = [int(p) for p in version.split(".")]
|
||||
|
||||
# Version 1.0.0 is the minimum
|
||||
assert parts >= [1, 0, 0], "Constitution version cannot be below 1.0.0"
|
||||
|
||||
def test_immutable_rules_count(self, parse_lock_file):
|
||||
"""Immutable rules count must be exactly 5."""
|
||||
immutable_count = int(parse_lock_file["immutable_rules"])
|
||||
assert immutable_count == 5, (
|
||||
f"Immutable rules count changed from 5 to {immutable_count}. "
|
||||
"This violates immutability clause."
|
||||
)
|
||||
|
||||
def test_cooldown_days_minimum(self, parse_lock_file):
|
||||
"""Amendment cooldown must be at least 7 days."""
|
||||
cooldown = int(parse_lock_file["cooldown_days"])
|
||||
assert cooldown >= 7, (
|
||||
f"Cooldown period reduced to {cooldown} days. "
|
||||
"Minimum is 7 days per constitution."
|
||||
)
|
||||
|
||||
def test_btc_anchor_required(self, parse_lock_file):
|
||||
"""BTC anchor requirement must be true."""
|
||||
requires_anchor = parse_lock_file["requires_btc_anchor"].lower() == "true"
|
||||
assert requires_anchor, "BTC anchor requirement cannot be disabled"
|
||||
|
||||
def test_sovereign_key_present(self, parse_lock_file):
|
||||
"""Sovereign key must be specified."""
|
||||
sovereign_key = parse_lock_file.get("sovereign_key")
|
||||
assert sovereign_key and sovereign_key.startswith("key_"), (
|
||||
"Sovereign key must be specified in lock file"
|
||||
)
|
||||
|
||||
|
||||
class TestConstitutionContent:
|
||||
"""Tests that verify constitution content invariants."""
|
||||
|
||||
def test_profiles_defined(self, constitution_path):
|
||||
"""All five profiles must be defined."""
|
||||
content = constitution_path.read_text()
|
||||
profiles = ["OBSERVER", "OPERATOR", "GUARDIAN", "PHOENIX", "SOVEREIGN"]
|
||||
|
||||
for profile in profiles:
|
||||
assert profile in content, f"Profile {profile} not found in constitution"
|
||||
|
||||
def test_immutable_clauses_present(self, constitution_path):
|
||||
"""All immutable clauses must be present."""
|
||||
content = constitution_path.read_text()
|
||||
immutables = [
|
||||
"SOVEREIGN profile requires human verification",
|
||||
"No AI may grant itself SOVEREIGN authority",
|
||||
"Every mutation emits a receipt",
|
||||
"Authority collapses downward, never upward",
|
||||
"This immutability clause itself",
|
||||
]
|
||||
|
||||
for clause in immutables:
|
||||
assert clause in content, f"Immutable clause missing: {clause}"
|
||||
|
||||
def test_amendment_protocol_exists(self, constitution_path):
|
||||
"""Amendment protocol must be defined."""
|
||||
content = constitution_path.read_text()
|
||||
assert "Amendment Protocol" in content, "Amendment protocol section missing"
|
||||
assert "Cooling Period" in content or "cooling" in content.lower(), (
|
||||
"Cooling period not defined in amendment protocol"
|
||||
)
|
||||
Reference in New Issue
Block a user