Files
muratcankoylan__agent-skill…/researcher/scripts/validate_spec_lifecycle.py

2829 lines
106 KiB
Python

#!/usr/bin/env python3
"""Validate specification lifecycle transitions against an exact Git base.
The corpus inventory validates the candidate tree in isolation. This gate owns
the temporal question that an isolated tree cannot answer: whether a proposed
status or revision is a legal successor to the protected base. GitHub remains
the authority for whether that candidate was actually human-merged.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import re
import subprocess
import sys
from dataclasses import dataclass, replace
from pathlib import Path
from typing import Any, Iterable, Mapping
ROOT = Path(__file__).resolve().parents[2]
SPEC_PATH_RE = re.compile(r"^docs/specs/SPEC-[0-9]{3}-[a-z0-9-]+\.md$")
SPEC_HEADING_RE = re.compile(r"^# (?P<id>SPEC-[0-9]{3}): (?P<title>.+)$")
ADR_PATH_RE = re.compile(r"^docs/decisions/[0-9]{4}-[a-z0-9-]+\.md$")
ADR_HEADING_RE = re.compile(r"^# (?P<id>ADR-[0-9]{4}): (?P<title>.+)$")
ACTIVE_STATUSES = {
"draft",
"architecture_reviewed",
"accepted",
"implemented",
"verified",
"operational",
}
STAGE_RANK = {
"draft": 0,
"architecture_reviewed": 1,
"accepted": 2,
"implemented": 3,
"verified": 4,
"operational": 5,
}
AUTHORITY_VOCABULARY_SPEC = "SPEC-000"
AUTHORITY_VOCABULARY_MIN_REVISION = 2
AUTHORITY_VOCABULARY_PATH = "governance/authority-vocabulary.json"
AUTHORITY_VOCABULARY_SCHEMA_VERSION = "1.0.0"
AUTHORITY_FIXTURE_MANIFEST_PATH = "governance/fixtures/authority-vocabulary.json"
AUTHORITY_FIXTURE_MANIFEST_SCHEMA_VERSION = "1.0.0"
AUTHORITY_CONFORMANCE_RECEIPT_PATH = (
"governance/generated/authority-vocabulary-conformance.json"
)
AUTHORITY_CONFORMANCE_RECEIPT_SCHEMA_VERSION = "1.0.0"
AUTHORITY_CONSTITUTION_POLICY_PATH = "governance/constitution.yaml"
AUTHORITY_VALIDATOR_PATH = "researcher/scripts/validate_spec_lifecycle.py"
AUTHORITY_VALIDATOR_VERSION = "1.0.0"
AUTHORITY_VOCABULARY_READY_STATUSES = {
"accepted",
"implemented",
"verified",
"operational",
}
AUTHORITY_IMPLEMENTED_STATUSES = {"implemented", "verified", "operational"}
AUTHORITY_PREIMPLEMENTATION_STATUSES = {
"draft",
"architecture_reviewed",
"accepted",
}
AUTHORITY_ENTRY_KEYS = {
"action",
"resource",
"owner_spec",
"actor_classes",
"max_effect",
"dependency_floor",
"decision_context_fields",
"grant_operation",
}
AUTHORITY_MAX_EFFECT_KEYS = {
"code",
"max_targets",
"max_state_transitions",
}
AUTHORITY_FIXTURE_POINTER_KEYS = {"path", "digest", "version"}
AUTHORITY_FIXTURE_ENTRY_KEYS = {"action", "resource", "cases"}
AUTHORITY_FIXTURE_CASE_KEYS = {
"case",
"actor_class",
"decision_context_fields",
"effect",
"expected_decision",
"expected_policy_decision",
"expected_registry_decision",
"grant_operation",
"omitted_context_field",
"dependency_evidence",
}
AUTHORITY_CATALOG_FIXTURE_CASE_KEYS = AUTHORITY_FIXTURE_CASE_KEYS | {
"action",
"resource",
}
AUTHORITY_DEPENDENCY_EVIDENCE_KEYS = {"spec", "revision", "status"}
AUTHORITY_CONFORMANCE_POLICY_KEYS = {"path", "digest", "version"}
AUTHORITY_CONFORMANCE_VALIDATOR_KEYS = {"path", "digest", "version"}
AUTHORITY_CONFORMANCE_CASE_KEYS = {
"action",
"resource",
"case",
"expected_decision",
"expected_policy_decision",
"expected_registry_decision",
"registry_decision",
"registry_reason_code",
"policy_decision",
"actual_decision",
"context_digest",
"matched_rule",
"reason_code",
}
TERMINAL_STATUSES = {"amended", "superseded", "retired"}
KNOWN_STATUSES = ACTIVE_STATUSES | TERMINAL_STATUSES
ACCEPTED_OR_LATER = {
"accepted",
"implemented",
"verified",
"operational",
*TERMINAL_STATUSES,
}
LEGAL_SAME_REVISION_TRANSITIONS = {
"draft": {"draft", "architecture_reviewed", "retired"},
"architecture_reviewed": {
"architecture_reviewed",
"accepted",
"amended",
"superseded",
"retired",
},
"accepted": {"accepted", "implemented", "amended", "superseded", "retired"},
"implemented": {"implemented", "verified", "amended", "superseded", "retired"},
"verified": {"verified", "operational", "amended", "superseded", "retired"},
"operational": {"operational", "amended", "superseded", "retired"},
"amended": {"amended"},
"superseded": {"superseded"},
"retired": {"retired"},
}
LEGACY_ADOPTION = {
"SPEC-000": ("implementing", "implemented"),
"SPEC-001": ("implementing", "implemented"),
"SPEC-002": ("implementing", "implemented"),
"SPEC-003": ("implemented", "implemented"),
}
LEGACY_ADOPTION_DECISION = "ADR-0005"
LEGACY_ADOPTION_OWNERS = {
"SPEC-000": "human maintainer; governance agent",
"SPEC-001": "corpus steward agent; human maintainer",
"SPEC-002": "human maintainer; export steward agent",
"SPEC-003": "platform steward agent; human maintainer",
}
LEGACY_ADOPTION_DEPENDENCY_REVISIONS = {
"SPEC-000": "none",
"SPEC-001": "SPEC-000@1",
"SPEC-002": "SPEC-000@1",
"SPEC-003": "SPEC-001@1, SPEC-002@1",
}
CONTRACT_METADATA_KEYS = {
"Wave",
"Classification",
"Owners",
"Depends on",
"Activation",
}
@dataclass(frozen=True)
class LifecycleFinding:
code: str
path: str
spec_id: str | None
message: str
@dataclass(frozen=True)
class SpecRevision:
path: str
spec_id: str
title: str
status: str
revision: int
revises: str
adoption_decision: str | None
lifecycle_decision: str | None
replacement: str | None
metadata: Mapping[str, str]
contract_body: str
exact_bytes: bytes
legacy_metadata: bool = False
@property
def digest(self) -> str:
return f"sha256:{hashlib.sha256(self.exact_bytes).hexdigest()}"
@property
def contract_identity(self) -> tuple[object, ...]:
mutable_lines = (
b"Status:",
b"Dependency revisions:",
b"Lifecycle decision:",
b"Replacement:",
)
stable_lines: list[bytes] = []
in_header = True
for line in self.exact_bytes.splitlines(keepends=True):
if line.startswith(b"## "):
in_header = False
if in_header and line.startswith(mutable_lines):
continue
stable_lines.append(line)
return (b"".join(stable_lines),)
@dataclass(frozen=True)
class AdrRevision:
path: str
adr_id: str
title: str
status: str
lifecycle_transition: str | None
exact_bytes: bytes
@dataclass(frozen=True)
class AuthoritySemanticProfile:
"""Closed semantics for one constitution-recognized action/resource pair."""
owner_spec: str
actor_classes: tuple[str, ...]
max_effect_code: str
max_targets: int
max_state_transitions: int
dependency_floor: tuple[str, ...]
decision_context_fields: tuple[str, ...]
grant_operation: str
@property
def max_effect(self) -> dict[str, object]:
return {
"code": self.max_effect_code,
"max_state_transitions": self.max_state_transitions,
"max_targets": self.max_targets,
}
def _authority_context(*additional: str) -> tuple[str, ...]:
return tuple(
sorted(
{
"expected_target_version",
"operation_id",
"target_id",
*additional,
}
)
)
def _authority_profile(
owner_spec: str,
actor_classes: tuple[str, ...],
max_effect_code: str,
max_state_transitions: int,
grant_operation: str,
*,
context: tuple[str, ...] = (),
dependency_floor: tuple[str, ...] = (),
max_targets: int = 1,
) -> AuthoritySemanticProfile:
floor = dependency_floor or (f"{owner_spec}@1",)
return AuthoritySemanticProfile(
owner_spec=owner_spec,
actor_classes=tuple(sorted(actor_classes)),
max_effect_code=max_effect_code,
max_targets=max_targets,
max_state_transitions=max_state_transitions,
dependency_floor=tuple(sorted(floor)),
decision_context_fields=_authority_context(*context),
grant_operation=grant_operation,
)
POLICY_ONLY_READ_ACTORS = (
"change_author",
"community_contributor",
"human_maintainer",
"independent_verifier",
"release_attestor",
"research_proposer",
"runtime_operator",
)
POLICY_ONLY_READ_PROFILES: Mapping[tuple[str, str], AuthoritySemanticProfile] = {
("read", "candidate_artifact"): _authority_profile(
"SPEC-000",
POLICY_ONLY_READ_ACTORS,
"read_one_candidate_artifact",
0,
"read_candidate_artifact",
dependency_floor=("SPEC-000@2",),
),
("read", "public_content_draft"): _authority_profile(
"SPEC-000",
POLICY_ONLY_READ_ACTORS,
"read_one_public_content_draft",
0,
"read_public_content_draft",
dependency_floor=("SPEC-000@2",),
),
("read", "public_repository"): _authority_profile(
"SPEC-000",
POLICY_ONLY_READ_ACTORS,
"read_one_public_repository_view",
0,
"read_public_repository",
dependency_floor=("SPEC-000@2",),
),
("read", "pull_request"): _authority_profile(
"SPEC-000",
POLICY_ONLY_READ_ACTORS,
"read_one_pull_request",
0,
"read_pull_request",
dependency_floor=("SPEC-000@2",),
),
("read", "research_artifact"): _authority_profile(
"SPEC-000",
POLICY_ONLY_READ_ACTORS,
"read_one_research_artifact",
0,
"read_research_artifact",
dependency_floor=("SPEC-000@2",),
),
}
# This is intentionally a closed, mechanism-level profile rather than an owner
# lookup. A registry cannot gain meaning by inventing plausible strings: every
# actor, guard, effect ceiling, dependency floor, and grant operation is
# reviewed as part of the SPEC-000 revision that changes this table.
REQUIRED_AUTHORITY_PROFILES: Mapping[
tuple[str, str], AuthoritySemanticProfile
] = {
("amend_constitution", "constitution"): _authority_profile(
"SPEC-000",
("human_maintainer",),
"propose_one_constitution_amendment",
1,
"amend_constitution",
context=("human_review_path",),
dependency_floor=("SPEC-000@2",),
),
("activate_production", "production_deployment"): _authority_profile(
"SPEC-025",
("human_maintainer",),
"activate_one_deployment_pointer",
1,
"activate_deployment_pointer",
context=(
"accepted_public_commit",
"canary_attestation",
"configuration_digest",
"manifest_digest",
"operation_id",
"promotion_record",
),
),
("approve_weight_training", "weight_training"): _authority_profile(
"SPEC-000",
("human_maintainer",),
"approve_one_weight_training_run",
1,
"approve_weight_training",
dependency_floor=("SPEC-000@2",),
),
("attest_release", "candidate_artifact"): _authority_profile(
"SPEC-000",
("release_attestor",),
"record_one_candidate_release_attestation",
1,
"attest_release",
context=("candidate_digest_verified", "independent_of_author"),
dependency_floor=("SPEC-000@2",),
),
("attest_release", "pull_request"): _authority_profile(
"SPEC-000",
("release_attestor",),
"record_one_pull_request_release_attestation",
1,
"attest_release",
context=("candidate_digest_verified", "independent_of_author"),
dependency_floor=("SPEC-000@2",),
),
("authorize_credential_destination", "credential_destination"): _authority_profile(
"SPEC-000",
("human_maintainer",),
"authorize_one_credential_destination",
1,
"authorize_credential_destination",
dependency_floor=("SPEC-000@2",),
),
("attest_canary_health", "deployment_canary"): _authority_profile(
"SPEC-025",
("independent_canary_attestor",),
"attest_one_canary_result",
1,
"attest_canary_result",
context=(
"accepted_public_commit",
"activation_request",
"canary_epoch",
"clock_proofs",
"configuration_digest",
"expected_deployment_version",
"gate_set",
"manifest_digest",
"observation_interval",
"policy_digest",
"prior_active_pointer",
"promotion_record",
"raw_measurement_refs",
),
),
("authorize_disaster_recovery", "event_journal"): _authority_profile(
"SPEC-004",
("human_maintainer",),
"create_one_degraded_disaster_generation",
1,
"create_degraded_disaster_generation",
context=(
"accepted_public_commit",
"deployment_transition",
"disaster_declaration",
"halted_tail",
"operation_id",
"possible_loss_interval",
),
dependency_floor=("SPEC-004@1", "SPEC-025@1"),
),
("authorize_hidden_evaluation", "evaluation_epoch"): _authority_profile(
"SPEC-016",
("human_maintainer",),
"authorize_one_hidden_evaluation_epoch",
1,
"authorize_hidden_epoch",
context=(
"accepted_public_commit",
"budget_ceiling",
"manifest_digest",
"operation_id",
"split_and_look_ceiling",
),
),
("change_protected_surface", "evaluator_policy"): _authority_profile(
"SPEC-000",
("human_maintainer",),
"propose_one_evaluator_policy_change",
1,
"change_protected_surface",
context=("human_review_path",),
dependency_floor=("SPEC-000@2",),
),
("change_protected_surface", "hidden_evaluation"): _authority_profile(
"SPEC-000",
("human_maintainer",),
"propose_one_hidden_evaluation_change",
1,
"change_protected_surface",
context=("human_review_path",),
dependency_floor=("SPEC-000@2",),
),
("change_protected_surface", "protected_surface"): _authority_profile(
"SPEC-000",
("human_maintainer",),
"propose_one_protected_surface_change",
1,
"change_protected_surface",
context=("human_review_path",),
dependency_floor=("SPEC-000@2",),
),
("change_protected_surface", "public_private_boundary"): _authority_profile(
"SPEC-000",
("human_maintainer",),
"propose_one_public_private_boundary_change",
1,
"change_protected_surface",
context=("human_review_path",),
dependency_floor=("SPEC-000@2",),
),
("cancel_work", "work_order"): _authority_profile(
"SPEC-006",
("human_maintainer",),
"request_one_work_cancellation",
1,
"request_work_cancellation",
context=("accepted_public_commit", "attempt_id", "fence", "operation_id"),
),
("change_deployment", "production_deployment"): _authority_profile(
"SPEC-025",
("human_maintainer",),
"stage_one_deployment_change",
1,
"stage_deployment_change",
context=(
"accepted_public_commit",
"configuration_digest",
"manifest_digest",
"operation_id",
),
),
("close_run", "research_run"): _authority_profile(
"SPEC-006",
("human_maintainer",),
"close_one_research_run",
1,
"transition_research_run_close",
context=("accepted_public_commit", "operation_id"),
),
("create_content_draft", "content_draft"): _authority_profile(
"SPEC-015",
("content_proposer",),
"create_one_private_content_draft",
1,
"create_content_draft",
context=("accepted_public_commit", "operation_id", "source_evidence_digest"),
),
("evaluate", "candidate_artifact"): _authority_profile(
"SPEC-000",
("independent_verifier",),
"record_one_independent_candidate_evaluation",
1,
"evaluate_candidate_artifact",
context=("independent_of_author",),
dependency_floor=("SPEC-000@2",),
),
("evaluate", "research_artifact"): _authority_profile(
"SPEC-000",
("independent_verifier",),
"record_one_independent_research_evaluation",
1,
"evaluate_research_artifact",
context=("independent_of_author",),
dependency_floor=("SPEC-000@2",),
),
("emergency_disable", "emergency_control"): _authority_profile(
"SPEC-025",
("human_maintainer", "runtime_operator"),
"disable_one_emergency_scope",
1,
"disable_production_effects",
context=("operation_id", "safety_ledger_version"),
),
("export_approved_draft", "content_draft"): _authority_profile(
"SPEC-015",
("content_exporter",),
"export_one_approved_draft_locally",
1,
"export_approved_content_draft",
context=("approval_receipt", "destination_class", "operation_id"),
),
("execute_work", "candidate_artifact"): _authority_profile(
"SPEC-000",
("runtime_operator",),
"execute_one_bounded_candidate_work_effect",
1,
"execute_candidate_work",
context=("capability_grant_valid",),
dependency_floor=("SPEC-000@2",),
),
("execute_work", "research_artifact"): _authority_profile(
"SPEC-000",
("runtime_operator",),
"execute_one_bounded_research_work_effect",
1,
"execute_research_work",
context=("capability_grant_valid",),
dependency_floor=("SPEC-000@2",),
),
("expose_private_record", "private_record"): _authority_profile(
"SPEC-000",
("human_maintainer",),
"export_one_approved_private_record",
1,
"expose_private_record",
dependency_floor=("SPEC-000@2",),
),
("initialize_repository_acceptance", "repository_acceptance"): _authority_profile(
"SPEC-004",
("human_maintainer",),
"atomically_create_repository_acceptance_baseline_and_organization_epoch_from_absent_pointer",
2,
"initialize_repository_acceptance",
context=(
"accepted_commit",
"accepted_tree",
"accepted_pointer_absent",
"git_observation",
"inventory_digest",
"organization_epoch",
"repository_identity",
"source_tree_digest",
),
max_targets=2,
),
("invoke_break_glass", "credential_binding"): _authority_profile(
"SPEC-024",
("human_maintainer",),
"invoke_one_bounded_break_glass_binding",
1,
"invoke_break_glass",
context=(
"accepted_public_commit",
"break_glass_expiry",
"incident_id",
"one_use_receipt",
"operation_id",
),
),
("manage_credential_binding", "credential_binding"): _authority_profile(
"SPEC-024",
("human_maintainer",),
"change_one_credential_binding",
1,
"manage_credential_binding",
context=(
"accepted_public_commit",
"binding_digest",
"credential_class",
"operation_id",
),
),
("merge", "default_branch"): _authority_profile(
"SPEC-000",
("human_maintainer",),
"advance_one_default_branch_to_reviewed_head",
1,
"merge_reviewed_head",
context=("latest_commit_reviewed", "required_checks_passed"),
dependency_floor=("SPEC-000@2",),
),
("merge", "pull_request"): _authority_profile(
"SPEC-000",
("human_maintainer",),
"merge_one_pull_request_reviewed_head",
1,
"merge_reviewed_head",
context=("latest_commit_reviewed", "required_checks_passed"),
dependency_floor=("SPEC-000@2",),
),
("open_pull_request", "pull_request"): _authority_profile(
"SPEC-000",
("change_author", "community_contributor", "human_maintainer", "research_proposer"),
"open_one_governed_pull_request",
1,
"open_pull_request",
context=("automated_identity_disclosed", "human_review_path"),
dependency_floor=("SPEC-000@2",),
),
("operate_deployment", "production_deployment"): _authority_profile(
"SPEC-025",
("authenticated_runtime_operator",),
"execute_one_in_epoch_operation",
1,
"operate_within_activation_epoch",
context=("activation_epoch", "configuration_digest", "operation_id"),
),
("park_run", "research_run"): _authority_profile(
"SPEC-006",
("human_maintainer",),
"park_one_research_run",
1,
"transition_research_run_park",
context=("accepted_public_commit", "operation_id"),
),
("pause_run", "research_run"): _authority_profile(
"SPEC-006",
("human_maintainer",),
"pause_one_research_run",
1,
"transition_research_run_pause",
context=("accepted_public_commit", "operation_id"),
),
("prove_clock_deadline", "clock_domain"): _authority_profile(
"SPEC-005",
("trusted_clock_reducer",),
"emit_one_purpose_scoped_deadline_proof",
0,
"emit_clock_deadline_proof",
context=("clock_observation", "deadline", "proof_purpose"),
),
("propose_change", "candidate_artifact"): _authority_profile(
"SPEC-000",
(
"change_author",
"community_contributor",
"human_maintainer",
"research_proposer",
),
"propose_one_candidate_artifact_change",
1,
"propose_candidate_change",
context=("automated_identity_disclosed", "human_review_path"),
dependency_floor=("SPEC-000@2",),
),
("propose_change", "research_artifact"): _authority_profile(
"SPEC-000",
("human_maintainer", "research_proposer"),
"propose_one_research_artifact_change",
1,
"propose_research_change",
context=("automated_identity_disclosed",),
dependency_floor=("SPEC-000@2",),
),
("push_proposal", "proposal_branch"): _authority_profile(
"SPEC-000",
("change_author", "human_maintainer", "research_proposer"),
"push_one_proposal_branch_head",
1,
"push_proposal_branch",
context=("automated_identity_disclosed", "human_review_path"),
dependency_floor=("SPEC-000@2",),
),
("query_status", "status_projection"): _authority_profile(
"SPEC-006",
("authenticated_reader",),
"read_one_status_projection",
0,
"read_status_projection",
),
("reconcile_clock", "clock_domain"): _authority_profile(
"SPEC-005",
("human_maintainer",),
"accept_one_bounded_clock_bridge",
1,
"accept_clock_reconciliation",
context=(
"accepted_public_commit",
"clock_bridge",
"current_observation",
"operation_id",
"prior_observation",
),
),
("reconcile_repository_acceptance", "repository_acceptance"): _authority_profile(
"SPEC-007",
("repository_acceptance_reconciler",),
"append_one_repository_acceptance_event_and_accepted_pointer_receipt",
1,
"record_repository_acceptance",
context=(
"authority_set_digest",
"base_commit",
"default_branch",
"expected_accepted_pointer",
"expected_accepted_pointer_version",
"first_parent_interval",
"git_observation_receipts",
"head_commit",
"merge_commit",
"merge_actor",
"merge_tree",
"provider_revisions",
"pull_request",
"repository_identity",
"ruleset_digest",
),
),
("record_draft_decision", "content_draft"): _authority_profile(
"SPEC-015",
("human_maintainer",),
"record_one_content_draft_decision",
1,
"record_content_draft_decision",
context=("accepted_public_commit", "draft_digest", "operation_id"),
),
("record_pr_decision", "pull_request"): _authority_profile(
"SPEC-007",
("human_maintainer",),
"record_one_pull_request_readiness_decision",
1,
"record_pull_request_decision",
context=("accepted_public_commit", "head_sha", "operation_id", "pull_request"),
),
("recover_journal", "event_journal"): _authority_profile(
"SPEC-004",
("human_maintainer",),
"reopen_one_continuous_journal_generation",
1,
"reopen_continuous_journal_generation",
context=(
"accepted_public_commit",
"continuation_proof",
"halted_tail",
"operation_id",
"recovery_ticket",
),
),
("resolve_ambiguous_effect", "external_effect"): _authority_profile(
"SPEC-005",
("human_maintainer",),
"record_one_ambiguous_effect_disposition",
1,
"record_ambiguous_effect_disposition",
context=(
"accepted_public_commit",
"attempt_id",
"evidence_digest",
"fence",
"operation_id",
),
),
("research", "candidate_artifact"): _authority_profile(
"SPEC-000",
("human_maintainer", "research_proposer"),
"append_one_candidate_research_artifact",
1,
"research_candidate_artifact",
context=("automated_identity_disclosed",),
dependency_floor=("SPEC-000@2",),
),
("research", "research_artifact"): _authority_profile(
"SPEC-000",
("human_maintainer", "research_proposer"),
"append_one_research_artifact",
1,
"research_artifact",
context=("automated_identity_disclosed",),
dependency_floor=("SPEC-000@2",),
),
("respond_to_review", "pull_request"): _authority_profile(
"SPEC-000",
("change_author", "community_contributor", "human_maintainer", "research_proposer"),
"append_one_pull_request_review_response",
1,
"respond_to_pull_request_review",
context=("automated_identity_disclosed", "human_review_path"),
dependency_floor=("SPEC-000@2",),
),
("resume_run", "research_run"): _authority_profile(
"SPEC-006",
("human_maintainer",),
"resume_one_research_run",
1,
"transition_research_run_resume",
context=("accepted_public_commit", "operation_id"),
),
("revoke_capability", "capability"): _authority_profile(
"SPEC-024",
("credential_reconciler",),
"revoke_one_capability_stop_only",
1,
"revoke_capability",
context=("capability_digest", "operation_id", "revocation_reason"),
),
("sample_clock", "clock_domain"): _authority_profile(
"SPEC-005",
("attested_supervisor_clock_sampler",),
"append_one_clock_observation",
1,
"append_clock_observation",
context=("boot_id", "clock_source", "observation_digest", "operation_id"),
),
("seal_hidden_evaluation", "evaluation_epoch"): _authority_profile(
"SPEC-016",
("independent_epoch_sealer",),
"activate_one_human_authorized_hidden_evaluation_epoch_and_append_immutable_seal_receipt",
1,
"seal_hidden_epoch",
context=("authorization_receipt", "manifest_digest", "operation_id"),
),
}
REQUIRED_AUTHORITY_VOCABULARY_OWNERS = {
pair: profile.owner_spec for pair, profile in REQUIRED_AUTHORITY_PROFILES.items()
}
@dataclass(frozen=True)
class AuthorityFixtureCase:
action: str
resource: str
case: str
actor_class: str
decision_context_fields: tuple[str, ...]
effect_code: str
max_targets: int
max_state_transitions: int
expected_decision: str
expected_policy_decision: str
expected_registry_decision: str
grant_operation: str
omitted_context_field: str
dependency_evidence: tuple[tuple[str, int, str], ...]
@dataclass(frozen=True)
class AuthorityFixtureManifestBinding:
path: str
digest: str
schema_version: str
constitution_revision: int
registry_version: int
fixture_pairs: tuple[tuple[str, str], ...]
cases: tuple[AuthorityFixtureCase, ...]
@dataclass(frozen=True)
class AuthorityPolicyConformanceBinding:
path: str
digest: str
schema_version: str
constitution_policy_digest: str
registry_digest: str
fixture_manifest_digest: str
validator_digest: str
validator_version: str
case_count: int
@dataclass(frozen=True)
class AuthorityVocabularyBinding:
path: str
digest: str
schema_version: str
constitution_revision: int
registry_version: int
entries: tuple[tuple[str, str, str], ...]
fixture_manifest_path: str
fixture_manifest_digest: str
fixture_manifest_version: int
fixture_cases: tuple[AuthorityFixtureCase, ...]
policy_conformance: AuthorityPolicyConformanceBinding | None = None
@property
def policy_conformance_receipt_digest(self) -> str | None:
if self.policy_conformance is None:
return None
return self.policy_conformance.digest
def _strict_json_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
result: dict[str, Any] = {}
for key, value in pairs:
if key in result:
raise ValueError(f"duplicate JSON key: {key}")
result[key] = value
return result
def _strict_json_integer(value: str) -> int:
if re.fullmatch(r"-?(?:0|[1-9][0-9]*)", value) is None:
raise ValueError(f"invalid JSON integer: {value}")
parsed = int(value)
if abs(parsed) > 9_007_199_254_740_991:
raise ValueError(f"JSON integer exceeds the portable exact range: {value}")
return parsed
def _strict_json_loads(exact_bytes: bytes, *, label: str) -> Any:
try:
return json.loads(
exact_bytes.decode("utf-8"),
object_pairs_hook=_strict_json_object,
parse_int=_strict_json_integer,
parse_float=lambda value: (_ for _ in ()).throw(
ValueError(f"floating point is forbidden: {value}")
),
parse_constant=lambda value: (_ for _ in ()).throw(
ValueError(f"non-finite number is forbidden: {value}")
),
)
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise ValueError(f"{label} is not strict UTF-8 JSON: {exc}") from exc
def _canonical_json_bytes(value: Any) -> bytes:
return (json.dumps(value, ensure_ascii=False, sort_keys=True, indent=2) + "\n").encode(
"utf-8"
)
def expected_authority_dependency_evidence(
profile: AuthoritySemanticProfile,
) -> list[dict[str, object]]:
evidence: list[dict[str, object]] = []
for binding in profile.dependency_floor:
spec_id, revision = binding.split("@", maxsplit=1)
evidence.append(
{
"spec": spec_id,
"revision": int(revision),
"status": "operational",
}
)
return evidence
def expected_authority_fixture_cases(
profile: AuthoritySemanticProfile,
) -> tuple[dict[str, object], ...]:
"""Return the exhaustive actor, guard, dependency, effect, and grant cases."""
allowed_actor = profile.actor_classes[0]
fields = list(profile.decision_context_fields)
exact_effect = profile.max_effect
dependency_evidence = expected_authority_dependency_evidence(profile)
common: dict[str, object] = {
"actor_class": allowed_actor,
"decision_context_fields": fields,
"effect": exact_effect,
"expected_decision": "deny",
"expected_policy_decision": "deny",
"expected_registry_decision": "deny",
"grant_operation": profile.grant_operation,
"omitted_context_field": "none",
"dependency_evidence": dependency_evidence,
}
cases: list[dict[str, object]] = []
for actor_class in profile.actor_classes:
cases.append(
dict(
common,
case=f"allow__{actor_class}",
actor_class=actor_class,
expected_decision="allow",
expected_policy_decision="allow",
expected_registry_decision="allow",
)
)
for omitted_field in profile.decision_context_fields:
cases.append(
dict(
common,
case=f"missing_guard__{omitted_field}",
decision_context_fields=[
field for field in fields if field != omitted_field
],
omitted_context_field=omitted_field,
)
)
below_floor = [dict(item) for item in dependency_evidence]
below_floor[0]["revision"] = int(below_floor[0]["revision"]) - 1
cases.append(
dict(
common,
case="below_dependency_floor",
dependency_evidence=below_floor,
)
)
owner_inactive = [dict(item) for item in dependency_evidence]
owner_record = next(
item for item in owner_inactive if item["spec"] == profile.owner_spec
)
owner_record["status"] = "draft"
cases.append(
dict(
common,
case="owner_inactive",
dependency_evidence=owner_inactive,
)
)
cases.append(
dict(
common,
case="widened_effect",
effect={
"code": "unbounded_effect",
"max_state_transitions": profile.max_state_transitions + 1,
"max_targets": profile.max_targets + 1,
},
)
)
cases.append(dict(common, case="wrong_actor", actor_class="unauthorized_actor"))
cases.append(
dict(
common,
case="wrong_grant",
grant_operation="unauthorized_operation",
)
)
return tuple(sorted(cases, key=lambda case: str(case["case"])))
def expected_authority_catalog_boundary_cases() -> tuple[dict[str, object], ...]:
base_pair = ("query_status", "status_projection")
profile = REQUIRED_AUTHORITY_PROFILES[base_pair]
allow = next(
case
for case in expected_authority_fixture_cases(profile)
if str(case["case"]).startswith("allow__")
)
unknown_action = dict(
allow,
action="unknown_action",
resource=base_pair[1],
case="unknown_action",
expected_decision="deny",
expected_policy_decision="deny",
expected_registry_decision="deny",
)
unknown_resource = dict(
allow,
action=base_pair[0],
resource="unknown_resource",
case="unknown_resource",
expected_decision="deny",
expected_policy_decision="deny",
expected_registry_decision="deny",
)
unknown_pair = dict(
allow,
action=base_pair[0],
resource="work_order",
case="unknown_pair",
expected_decision="deny",
expected_policy_decision="deny",
expected_registry_decision="deny",
)
if (unknown_pair["action"], unknown_pair["resource"]) in REQUIRED_AUTHORITY_PROFILES:
raise RuntimeError("unknown-pair fixture accidentally became a registered pair")
legacy_publish = dict(
allow,
action="publish_draft",
resource="public_content_draft",
case="legacy_publish_draft_denied",
actor_class="research_proposer",
expected_decision="deny",
expected_policy_decision="deny",
expected_registry_decision="deny",
dependency_evidence=[
{"spec": "SPEC-000", "revision": 2, "status": "operational"}
],
)
read_cases: list[dict[str, object]] = []
for (read_action, read_resource), read_profile in sorted(
POLICY_ONLY_READ_PROFILES.items()
):
profile_cases = expected_authority_fixture_cases(read_profile)
for profile_case in profile_cases:
is_allow = str(profile_case["case"]).startswith("allow__")
read_cases.append(
dict(
profile_case,
action=read_action,
resource=read_resource,
case=f"read_{read_resource}__{profile_case['case']}",
expected_registry_decision="non_event" if is_allow else "deny",
)
)
allowed_case = next(
case
for case in profile_cases
if case["case"] == "allow__human_maintainer"
)
read_cases.append(
dict(
allowed_case,
action=read_action,
resource=read_resource,
case=f"read_{read_resource}__event_append_denied",
effect={
"code": "append_one_organization_event",
"max_state_transitions": 1,
"max_targets": 1,
},
grant_operation="append_organization_event",
expected_decision="deny",
expected_policy_decision="deny",
expected_registry_decision="deny",
)
)
representative_read = next(
case
for case in read_cases
if case["case"] == "read_public_repository__allow__human_maintainer"
)
unknown_read_resource = dict(
representative_read,
resource="unknown_read_resource",
case="read_unknown_resource_denied",
expected_decision="deny",
expected_policy_decision="deny",
expected_registry_decision="deny",
)
noncatalog_append = dict(
allow,
action="noncatalog_event_append",
resource="organization_event",
case="noncatalog_event_append_denied",
effect={
"code": "append_one_organization_event",
"max_state_transitions": 1,
"max_targets": 1,
},
grant_operation="append_organization_event",
expected_decision="deny",
expected_policy_decision="deny",
expected_registry_decision="deny",
dependency_evidence=[
{"spec": "SPEC-000", "revision": 2, "status": "operational"}
],
)
return tuple(
sorted(
(
legacy_publish,
noncatalog_append,
*read_cases,
unknown_read_resource,
unknown_action,
unknown_pair,
unknown_resource,
),
key=lambda case: str(case["case"]),
)
)
def _validate_max_effect_shape(value: Any, *, label: str) -> None:
if not isinstance(value, dict) or set(value) != AUTHORITY_MAX_EFFECT_KEYS:
raise ValueError(f"{label} max_effect does not match the closed schema")
if not isinstance(value["code"], str) or re.fullmatch(
r"[a-z][a-z0-9_]*", value["code"]
) is None:
raise ValueError(f"{label} max_effect code is invalid")
for key in ("max_targets", "max_state_transitions"):
if type(value[key]) is not int or value[key] < 0: # bool is not an integer here
raise ValueError(f"{label} {key} must be a non-negative integer")
def _validate_dependency_evidence(value: Any, *, label: str) -> None:
if not isinstance(value, list) or not value:
raise ValueError(f"{label} dependency_evidence must be a non-empty array")
identities: list[str] = []
for index, item in enumerate(value):
if not isinstance(item, dict) or set(item) != AUTHORITY_DEPENDENCY_EVIDENCE_KEYS:
raise ValueError(f"{label} dependency evidence {index} has an invalid schema")
if not isinstance(item["spec"], str) or re.fullmatch(
r"SPEC-[0-9]{3}", item["spec"]
) is None:
raise ValueError(f"{label} dependency evidence {index} has an invalid spec")
if type(item["revision"]) is not int or item["revision"] < 0:
raise ValueError(f"{label} dependency evidence {index} has an invalid revision")
if item["status"] not in ACTIVE_STATUSES:
raise ValueError(f"{label} dependency evidence {index} has an invalid status")
identities.append(item["spec"])
if identities != sorted(set(identities)):
raise ValueError(f"{label} dependency evidence must be sorted and duplicate-free")
def parse_authority_fixture_manifest(
exact_bytes: bytes,
*,
expected_digest: str,
expected_constitution_revision: int,
expected_registry_version: int,
expected_profiles: Mapping[tuple[str, str], AuthoritySemanticProfile],
) -> AuthorityFixtureManifestBinding:
"""Validate the digest-bound, exhaustive allow/deny fixture manifest."""
actual_digest = f"sha256:{hashlib.sha256(exact_bytes).hexdigest()}"
if actual_digest != expected_digest:
raise ValueError(
f"authority fixture manifest digest mismatch: expected {expected_digest}, "
f"got {actual_digest}"
)
data = _strict_json_loads(exact_bytes, label="authority fixture manifest")
if not isinstance(data, dict):
raise ValueError("authority fixture manifest root must be an object")
root_keys = {
"kind",
"schema_version",
"constitution_revision",
"registry_version",
"entries",
"catalog_boundary_cases",
}
if set(data) != root_keys:
raise ValueError("authority fixture manifest root fields do not match the closed schema")
if data["kind"] != "AuthorityVocabularyFixtureManifest":
raise ValueError("authority fixture manifest kind is invalid")
if data["schema_version"] != AUTHORITY_FIXTURE_MANIFEST_SCHEMA_VERSION:
raise ValueError(
"authority fixture manifest schema_version must be "
f"{AUTHORITY_FIXTURE_MANIFEST_SCHEMA_VERSION}"
)
if (
type(data["constitution_revision"]) is not int
or data["constitution_revision"] != expected_constitution_revision
):
raise ValueError("authority fixture manifest constitution_revision is not current")
if (
type(data["registry_version"]) is not int
or data["registry_version"] != expected_registry_version
):
raise ValueError("authority fixture manifest registry_version is not current")
entries = data["entries"]
if not isinstance(entries, list) or not entries:
raise ValueError("authority fixture manifest entries must be a non-empty array")
parsed_pairs: list[tuple[str, str]] = []
parsed_cases: list[AuthorityFixtureCase] = []
seen_pairs: set[tuple[str, str]] = set()
for index, entry in enumerate(entries):
if not isinstance(entry, dict) or set(entry) != AUTHORITY_FIXTURE_ENTRY_KEYS:
raise ValueError(f"authority fixture entry {index} does not match the closed schema")
action = entry["action"]
resource = entry["resource"]
if not isinstance(action, str) or not isinstance(resource, str):
raise ValueError(f"authority fixture entry {index} has an invalid identity")
pair = (action, resource)
if pair in seen_pairs:
raise ValueError(f"duplicate authority fixture entry: {action}/{resource}")
seen_pairs.add(pair)
profile = expected_profiles.get(pair)
if profile is None:
raise ValueError(f"detached authority fixture entry: {action}/{resource}")
cases = entry["cases"]
if not isinstance(cases, list):
raise ValueError(f"authority fixture entry {index} cases must be an array")
names: list[str] = []
for case_index, case in enumerate(cases):
if not isinstance(case, dict) or set(case) != AUTHORITY_FIXTURE_CASE_KEYS:
raise ValueError(
f"authority fixture entry {index} case {case_index} does not match "
"the closed schema"
)
if not isinstance(case["case"], str) or re.fullmatch(
r"[a-z][a-z0-9_]*", case["case"]
) is None:
raise ValueError(f"authority fixture entry {index} has an invalid case name")
names.append(case["case"])
if not isinstance(case["actor_class"], str):
raise ValueError(f"authority fixture entry {index} has an invalid actor")
if (
not isinstance(case["decision_context_fields"], list)
or any(not isinstance(field, str) for field in case["decision_context_fields"])
or case["decision_context_fields"]
!= sorted(set(case["decision_context_fields"]))
):
raise ValueError(
f"authority fixture entry {index} has invalid decision context fields"
)
_validate_max_effect_shape(
case["effect"], label=f"authority fixture entry {index} case {case_index}"
)
_validate_dependency_evidence(
case["dependency_evidence"],
label=f"authority fixture entry {index} case {case_index}",
)
if case["expected_decision"] not in {"allow", "deny"}:
raise ValueError(
f"authority fixture entry {index} has an invalid expected decision"
)
if case["expected_policy_decision"] not in {"allow", "deny"} or case[
"expected_registry_decision"
] not in {"allow", "deny", "non_event"}:
raise ValueError(
f"authority fixture entry {index} has invalid boundary expectations"
)
if not isinstance(case["grant_operation"], str) or not isinstance(
case["omitted_context_field"], str
):
raise ValueError(f"authority fixture entry {index} has invalid case metadata")
expected_cases = list(expected_authority_fixture_cases(profile))
if names != sorted(set(names)) or cases != expected_cases:
raise ValueError(
f"authority fixture entry {index} must prove every actor and guard plus "
"the exact negative semantic profile"
)
for case in cases:
effect = case["effect"]
parsed_cases.append(
AuthorityFixtureCase(
action=action,
resource=resource,
case=case["case"],
actor_class=case["actor_class"],
decision_context_fields=tuple(case["decision_context_fields"]),
effect_code=effect["code"],
max_targets=effect["max_targets"],
max_state_transitions=effect["max_state_transitions"],
expected_decision=case["expected_decision"],
expected_policy_decision=case["expected_policy_decision"],
expected_registry_decision=case["expected_registry_decision"],
grant_operation=case["grant_operation"],
omitted_context_field=case["omitted_context_field"],
dependency_evidence=tuple(
(item["spec"], item["revision"], item["status"])
for item in case["dependency_evidence"]
),
)
)
parsed_pairs.append(pair)
expected_pairs = sorted(expected_profiles)
if parsed_pairs != expected_pairs:
raise ValueError(
"authority fixture entries must be sorted and cover every registry entry exactly once"
)
catalog_boundary_cases = data["catalog_boundary_cases"]
expected_boundary_cases = list(expected_authority_catalog_boundary_cases())
if (
not isinstance(catalog_boundary_cases, list)
or catalog_boundary_cases != expected_boundary_cases
):
raise ValueError(
"authority fixture manifest must contain the exact catalog-boundary cases"
)
for index, case in enumerate(catalog_boundary_cases):
if not isinstance(case, dict) or set(case) != AUTHORITY_CATALOG_FIXTURE_CASE_KEYS:
raise ValueError(f"authority catalog-boundary fixture {index} has an invalid schema")
_validate_max_effect_shape(
case["effect"], label=f"authority catalog-boundary fixture {index}"
)
_validate_dependency_evidence(
case["dependency_evidence"],
label=f"authority catalog-boundary fixture {index}",
)
parsed_cases.append(
AuthorityFixtureCase(
action=case["action"],
resource=case["resource"],
case=case["case"],
actor_class=case["actor_class"],
decision_context_fields=tuple(case["decision_context_fields"]),
effect_code=case["effect"]["code"],
max_targets=case["effect"]["max_targets"],
max_state_transitions=case["effect"]["max_state_transitions"],
expected_decision=case["expected_decision"],
expected_policy_decision=case["expected_policy_decision"],
expected_registry_decision=case["expected_registry_decision"],
grant_operation=case["grant_operation"],
omitted_context_field=case["omitted_context_field"],
dependency_evidence=tuple(
(item["spec"], item["revision"], item["status"])
for item in case["dependency_evidence"]
),
)
)
if exact_bytes != _canonical_json_bytes(data):
raise ValueError(
"authority fixture manifest JSON must use canonical sorted pretty serialization"
)
return AuthorityFixtureManifestBinding(
path=AUTHORITY_FIXTURE_MANIFEST_PATH,
digest=actual_digest,
schema_version=data["schema_version"],
constitution_revision=data["constitution_revision"],
registry_version=data["registry_version"],
fixture_pairs=tuple(parsed_pairs),
cases=tuple(parsed_cases),
)
def parse_authority_vocabulary(
exact_bytes: bytes,
*,
expected_digest: str,
expected_constitution_revision: int,
expected_registry_version: int,
fixture_manifest_bytes: bytes | None = None,
) -> AuthorityVocabularyBinding:
"""Strictly parse the machine authority vocabulary bound by SPEC-000."""
actual_digest = f"sha256:{hashlib.sha256(exact_bytes).hexdigest()}"
if actual_digest != expected_digest:
raise ValueError(
f"authority vocabulary digest mismatch: expected {expected_digest}, got {actual_digest}"
)
data = _strict_json_loads(exact_bytes, label="authority vocabulary")
if not isinstance(data, dict):
raise ValueError("authority vocabulary root must be an object")
root_keys = {
"kind",
"schema_version",
"owner_spec",
"constitution_revision",
"registry_version",
"entries",
"fixture_manifest",
}
if set(data) != root_keys:
raise ValueError("authority vocabulary root fields do not match the closed schema")
if data["kind"] != "AuthorityVocabularyRegistry":
raise ValueError("authority vocabulary kind must be AuthorityVocabularyRegistry")
if data["schema_version"] != AUTHORITY_VOCABULARY_SCHEMA_VERSION:
raise ValueError(
f"authority vocabulary schema_version must be {AUTHORITY_VOCABULARY_SCHEMA_VERSION}"
)
if data["owner_spec"] != AUTHORITY_VOCABULARY_SPEC:
raise ValueError("authority vocabulary owner_spec must be SPEC-000")
if (
type(data["constitution_revision"]) is not int
or data["constitution_revision"] != expected_constitution_revision
or expected_constitution_revision < AUTHORITY_VOCABULARY_MIN_REVISION
):
raise ValueError("authority vocabulary constitution_revision is not current")
if (
type(data["registry_version"]) is not int
or data["registry_version"] != expected_registry_version
or expected_registry_version != expected_constitution_revision
):
raise ValueError("authority vocabulary registry_version is invalid or stale")
entries = data["entries"]
if not isinstance(entries, list) or not entries:
raise ValueError("authority vocabulary entries must be a non-empty array")
fixture_pointer = data["fixture_manifest"]
if (
not isinstance(fixture_pointer, dict)
or set(fixture_pointer) != AUTHORITY_FIXTURE_POINTER_KEYS
):
raise ValueError("authority fixture manifest pointer does not match the closed schema")
if fixture_pointer["path"] != AUTHORITY_FIXTURE_MANIFEST_PATH:
raise ValueError(
f"authority fixture manifest path must be {AUTHORITY_FIXTURE_MANIFEST_PATH}"
)
if not isinstance(fixture_pointer["digest"], str) or re.fullmatch(
r"sha256:[0-9a-f]{64}", fixture_pointer["digest"]
) is None:
raise ValueError("authority fixture manifest digest is invalid")
if (
type(fixture_pointer["version"]) is not int
or fixture_pointer["version"] != expected_registry_version
):
raise ValueError("authority fixture manifest version is invalid or stale")
parsed_entries: list[tuple[str, str, str]] = []
seen_pairs: set[tuple[str, str]] = set()
token = re.compile(r"^[a-z][a-z0-9_]*$")
for index, entry in enumerate(entries):
if not isinstance(entry, dict) or set(entry) != AUTHORITY_ENTRY_KEYS:
raise ValueError(f"authority entry {index} does not match the closed schema")
action = entry["action"]
resource = entry["resource"]
owner_spec = entry["owner_spec"]
if not isinstance(action, str) or token.fullmatch(action) is None:
raise ValueError(f"authority entry {index} has invalid action")
if not isinstance(resource, str) or token.fullmatch(resource) is None:
raise ValueError(f"authority entry {index} has invalid resource")
if not isinstance(owner_spec, str) or re.fullmatch(
r"SPEC-[0-9]{3}", owner_spec
) is None:
raise ValueError(f"authority entry {index} has invalid owner_spec")
pair = (action, resource)
if pair in seen_pairs:
raise ValueError(f"duplicate authority pair: {action}/{resource}")
seen_pairs.add(pair)
profile = REQUIRED_AUTHORITY_PROFILES.get(pair)
if profile is None:
raise ValueError(f"unreviewed authority pair: {action}/{resource}")
_validate_max_effect_shape(entry["max_effect"], label=f"authority entry {index}")
if owner_spec != profile.owner_spec:
raise ValueError(
f"required authority pair {action}/{resource} must be owned by "
f"{profile.owner_spec}"
)
if entry["actor_classes"] != list(profile.actor_classes):
raise ValueError(f"authority entry {index} actor_classes do not match the profile")
if entry["max_effect"] != profile.max_effect:
raise ValueError(f"authority entry {index} max_effect does not match the profile")
if entry["dependency_floor"] != list(profile.dependency_floor):
raise ValueError(
f"authority entry {index} dependency_floor does not match the profile"
)
if entry["decision_context_fields"] != list(profile.decision_context_fields):
raise ValueError(
f"authority entry {index} decision_context_fields do not match the profile"
)
if entry["grant_operation"] != profile.grant_operation:
raise ValueError(
f"authority entry {index} grant_operation does not match the profile"
)
parsed_entries.append((action, resource, owner_spec))
if parsed_entries != sorted(parsed_entries):
raise ValueError("authority vocabulary entries must be sorted by action/resource/owner")
if [(action, resource) for action, resource, _owner in parsed_entries] != sorted(
REQUIRED_AUTHORITY_PROFILES
):
raise ValueError("authority vocabulary must contain every reviewed pair exactly once")
if exact_bytes != _canonical_json_bytes(data):
raise ValueError("authority vocabulary JSON must use canonical sorted pretty serialization")
if fixture_manifest_bytes is None:
raise ValueError("authority fixture manifest is required and cannot be detached")
fixture_binding = parse_authority_fixture_manifest(
fixture_manifest_bytes,
expected_digest=fixture_pointer["digest"],
expected_constitution_revision=expected_constitution_revision,
expected_registry_version=expected_registry_version,
expected_profiles=REQUIRED_AUTHORITY_PROFILES,
)
return AuthorityVocabularyBinding(
path=AUTHORITY_VOCABULARY_PATH,
digest=actual_digest,
schema_version=data["schema_version"],
constitution_revision=data["constitution_revision"],
registry_version=data["registry_version"],
entries=tuple(parsed_entries),
fixture_manifest_path=fixture_binding.path,
fixture_manifest_digest=fixture_binding.digest,
fixture_manifest_version=fixture_binding.registry_version,
fixture_cases=fixture_binding.cases,
)
def _authority_registry_boundary_decision(
case: AuthorityFixtureCase,
) -> tuple[str, str]:
if case.action == "read":
profile = POLICY_ONLY_READ_PROFILES.get((case.action, case.resource))
if profile is None:
return ("deny", "REGISTRY_NON_EVENT_APPEND_DENY")
if case.actor_class not in profile.actor_classes:
return ("deny", "REGISTRY_ACTOR_DENY")
if case.decision_context_fields != profile.decision_context_fields:
return ("deny", "REGISTRY_GUARD_DENY")
if (
case.effect_code != profile.max_effect_code
or case.max_targets != profile.max_targets
or case.max_state_transitions != profile.max_state_transitions
):
return ("deny", "REGISTRY_NON_EVENT_APPEND_DENY")
if case.grant_operation != profile.grant_operation:
return ("deny", "REGISTRY_GRANT_DENY")
if not _authority_dependency_floor_satisfied(case, profile):
return ("deny", "REGISTRY_DEPENDENCY_DENY")
return ("non_event", "REGISTRY_POLICY_ONLY_READ")
profile = REQUIRED_AUTHORITY_PROFILES.get((case.action, case.resource))
if profile is None:
return ("deny", "REGISTRY_PAIR_DENY")
if case.actor_class not in profile.actor_classes:
return ("deny", "REGISTRY_ACTOR_DENY")
if case.decision_context_fields != profile.decision_context_fields:
return ("deny", "REGISTRY_GUARD_DENY")
if (
case.effect_code != profile.max_effect_code
or case.max_targets != profile.max_targets
or case.max_state_transitions != profile.max_state_transitions
):
return ("deny", "REGISTRY_EFFECT_DENY")
if case.grant_operation != profile.grant_operation:
return ("deny", "REGISTRY_GRANT_DENY")
if not _authority_dependency_floor_satisfied(case, profile):
return ("deny", "REGISTRY_DEPENDENCY_DENY")
return ("allow", "REGISTRY_ALLOW")
def _authority_dependency_floor_satisfied(
case: AuthorityFixtureCase,
profile: AuthoritySemanticProfile | None,
) -> bool:
"""Evaluate only the exact revision/status floor, independent of other denials."""
if profile is None:
required = {"SPEC-000": 2}
else:
required = {
binding.split("@", maxsplit=1)[0]: int(
binding.split("@", maxsplit=1)[1]
)
for binding in profile.dependency_floor
}
evidence = {
spec_id: (revision, status)
for spec_id, revision, status in case.dependency_evidence
}
return set(evidence) == set(required) and all(
evidence[spec_id][0] >= revision
and evidence[spec_id][1] in AUTHORITY_VOCABULARY_READY_STATUSES
for spec_id, revision in required.items()
)
def authority_policy_case_results(
registry: AuthorityVocabularyBinding,
constitution: Any,
) -> tuple[dict[str, object], ...]:
"""Execute the combined registry boundary and Constitution.decide gate."""
results: list[dict[str, object]] = []
for case in registry.fixture_cases:
profile = REQUIRED_AUTHORITY_PROFILES.get(
(case.action, case.resource)
) or POLICY_ONLY_READ_PROFILES.get((case.action, case.resource))
registry_decision, registry_reason = _authority_registry_boundary_decision(case)
canonical_context = {
field: True for field in sorted(case.decision_context_fields)
}
dependency_value = [
{"spec": spec_id, "revision": revision, "status": status}
for spec_id, revision, status in case.dependency_evidence
]
canonical_context.update(
{
"dependency_evidence_digest": f"sha256:{hashlib.sha256(_canonical_json_bytes(dependency_value)).hexdigest()}",
"dependency_floor_satisfied": _authority_dependency_floor_satisfied(
case, profile
),
"grant_operation": case.grant_operation,
"requested_effect_code": case.effect_code,
"requested_max_state_transitions": case.max_state_transitions,
"requested_max_targets": case.max_targets,
}
)
context_digest = f"sha256:{hashlib.sha256(_canonical_json_bytes(canonical_context)).hexdigest()}"
decision = constitution.decide(
case.actor_class,
case.action,
case.resource,
canonical_context,
)
policy_decision = "allow" if decision.allowed else "deny"
actual_decision = (
"allow"
if registry_decision in {"allow", "non_event"} and policy_decision == "allow"
else "deny"
)
matched_rule = decision.matched_rule
reason_code = decision.reason_code
results.append(
{
"action": case.action,
"resource": case.resource,
"case": case.case,
"expected_decision": case.expected_decision,
"expected_policy_decision": case.expected_policy_decision,
"expected_registry_decision": case.expected_registry_decision,
"registry_decision": registry_decision,
"registry_reason_code": registry_reason,
"policy_decision": policy_decision,
"actual_decision": actual_decision,
"context_digest": context_digest,
"matched_rule": matched_rule,
"reason_code": reason_code,
}
)
return tuple(results)
def expected_authority_policy_conditions(
profile: AuthoritySemanticProfile,
) -> tuple[dict[str, object], ...]:
"""Return the sole accepted allow-rule predicate shape for one profile."""
conditions: dict[str, dict[str, object]] = {
field: {"key": field, "operator": "present"}
for field in profile.decision_context_fields
}
conditions.update(
{
"dependency_evidence_digest": {
"key": "dependency_evidence_digest",
"operator": "present",
},
"dependency_floor_satisfied": {
"key": "dependency_floor_satisfied",
"operator": "equals",
"value": True,
},
"grant_operation": {
"key": "grant_operation",
"operator": "equals",
"value": profile.grant_operation,
},
"requested_effect_code": {
"key": "requested_effect_code",
"operator": "equals",
"value": profile.max_effect_code,
},
"requested_max_state_transitions": {
"key": "requested_max_state_transitions",
"operator": "equals",
"value": profile.max_state_transitions,
},
"requested_max_targets": {
"key": "requested_max_targets",
"operator": "equals",
"value": profile.max_targets,
},
}
)
return tuple(conditions[key] for key in sorted(conditions))
def validate_authority_policy_closure(constitution: Any) -> None:
"""Reject noncatalog, overbroad, duplicate, or weaker allow-rule paths."""
try:
rules = constitution.rules
except (AttributeError, KeyError, TypeError) as exc:
raise ValueError("constitution does not expose validated policy rules") from exc
seen_paths: set[tuple[str, str, str]] = set()
for rule in rules:
if rule.get("effect") != "allow":
continue
for action in rule["actions"]:
for resource in rule["resources"]:
pair = (action, resource)
profile = REQUIRED_AUTHORITY_PROFILES.get(
pair
) or POLICY_ONLY_READ_PROFILES.get(pair)
if profile is None:
raise ValueError(
f"allow rule {rule['rule_id']} contains noncatalog pair "
f"{action}/{resource}"
)
if not set(rule["actors"]).issubset(profile.actor_classes):
raise ValueError(
f"allow rule {rule['rule_id']} widens actors for "
f"{action}/{resource}"
)
if rule.get("conditions", []) != list(
expected_authority_policy_conditions(profile)
):
raise ValueError(
f"allow rule {rule['rule_id']} has a noncanonical predicate "
f"for {action}/{resource}"
)
for actor in rule["actors"]:
path = (actor, action, resource)
if path in seen_paths:
raise ValueError(
f"duplicate allow path for {actor}/{action}/{resource}"
)
seen_paths.add(path)
def parse_authority_policy_conformance(
exact_bytes: bytes,
*,
registry: AuthorityVocabularyBinding,
constitution: Any,
validator_bytes: bytes,
) -> AuthorityPolicyConformanceBinding:
"""Validate and independently reproduce an implemented-stage receipt."""
data = _strict_json_loads(exact_bytes, label="authority policy conformance receipt")
if not isinstance(data, dict):
raise ValueError("authority policy conformance receipt root must be an object")
root_keys = {
"kind",
"schema_version",
"constitution_revision",
"registry_version",
"constitution_policy",
"registry_digest",
"fixture_manifest_digest",
"validator",
"case_count",
"skipped_case_count",
"result",
"cases",
}
if set(data) != root_keys:
raise ValueError(
"authority policy conformance receipt fields do not match the closed schema"
)
if data["kind"] != "AuthorityVocabularyConformanceReceipt":
raise ValueError("authority policy conformance receipt kind is invalid")
if data["schema_version"] != AUTHORITY_CONFORMANCE_RECEIPT_SCHEMA_VERSION:
raise ValueError(
"authority policy conformance receipt schema_version must be "
f"{AUTHORITY_CONFORMANCE_RECEIPT_SCHEMA_VERSION}"
)
if (
type(data["constitution_revision"]) is not int
or data["constitution_revision"] != registry.constitution_revision
or type(data["registry_version"]) is not int
or data["registry_version"] != registry.registry_version
):
raise ValueError("authority policy conformance receipt revision binding is stale")
policy = data["constitution_policy"]
if not isinstance(policy, dict) or set(policy) != AUTHORITY_CONFORMANCE_POLICY_KEYS:
raise ValueError("authority policy conformance policy binding is invalid")
expected_policy_digest = f"sha256:{constitution.digest}"
if (
policy["path"] != AUTHORITY_CONSTITUTION_POLICY_PATH
or policy["digest"] != expected_policy_digest
or policy["version"] != constitution.version
):
raise ValueError("authority policy conformance receipt binds the wrong constitution")
if data["registry_digest"] != registry.digest:
raise ValueError("authority policy conformance receipt binds the wrong registry")
if data["fixture_manifest_digest"] != registry.fixture_manifest_digest:
raise ValueError("authority policy conformance receipt binds the wrong fixture manifest")
validator = data["validator"]
if not isinstance(validator, dict) or set(validator) != AUTHORITY_CONFORMANCE_VALIDATOR_KEYS:
raise ValueError("authority policy conformance validator binding is invalid")
expected_validator_digest = f"sha256:{hashlib.sha256(validator_bytes).hexdigest()}"
if (
validator["path"] != AUTHORITY_VALIDATOR_PATH
or validator["digest"] != expected_validator_digest
or validator["version"] != AUTHORITY_VALIDATOR_VERSION
):
raise ValueError("authority policy conformance receipt binds the wrong validator")
expected_results = list(authority_policy_case_results(registry, constitution))
try:
validate_authority_policy_closure(constitution)
except ValueError as exc:
raise ValueError(
"constitution policy does not implement the authority registry: "
f"{exc}"
) from exc
nonconforming = [
result
for result in expected_results
if result["policy_decision"] != result["expected_policy_decision"]
or result["registry_decision"] != result["expected_registry_decision"]
or result["actual_decision"] != result["expected_decision"]
]
if nonconforming:
first = nonconforming[0]
raise ValueError(
"constitution policy does not implement the authority registry: "
f"{first['action']}/{first['resource']}/{first['case']} expected "
f"policy={first['expected_policy_decision']}, "
f"registry={first['expected_registry_decision']}, "
f"combined={first['expected_decision']}; got "
f"policy={first['policy_decision']}, "
f"registry={first['registry_decision']}, "
f"combined={first['actual_decision']}"
)
if (
type(data["case_count"]) is not int
or data["case_count"] != len(expected_results)
or type(data["skipped_case_count"]) is not int
or data["skipped_case_count"] != 0
or data["result"] != "pass"
):
raise ValueError("authority policy conformance receipt summary is not an exact pass")
cases = data["cases"]
if not isinstance(cases, list):
raise ValueError("authority policy conformance receipt cases must be an array")
for index, case in enumerate(cases):
if not isinstance(case, dict) or set(case) != AUTHORITY_CONFORMANCE_CASE_KEYS:
raise ValueError(
f"authority policy conformance case {index} does not match the closed schema"
)
if cases != expected_results:
raise ValueError(
"authority policy conformance receipt does not cover every manifest case exactly"
)
if exact_bytes != _canonical_json_bytes(data):
raise ValueError(
"authority policy conformance receipt must use canonical sorted pretty serialization"
)
return AuthorityPolicyConformanceBinding(
path=AUTHORITY_CONFORMANCE_RECEIPT_PATH,
digest=f"sha256:{hashlib.sha256(exact_bytes).hexdigest()}",
schema_version=data["schema_version"],
constitution_policy_digest=policy["digest"],
registry_digest=data["registry_digest"],
fixture_manifest_digest=data["fixture_manifest_digest"],
validator_digest=validator["digest"],
validator_version=validator["version"],
case_count=data["case_count"],
)
def _load_constitution_for_conformance(path: Path) -> Any:
try:
from researcher.scripts.governance_policy import Constitution
except ModuleNotFoundError: # direct script execution from researcher/scripts
from governance_policy import Constitution
return Constitution.load(path)
def load_authority_vocabulary(
root: Path,
metadata: Mapping[str, str],
*,
expected_constitution_revision: int,
) -> AuthorityVocabularyBinding:
path_value = metadata.get("Authority vocabulary")
digest = metadata.get("Authority vocabulary digest")
version_value = metadata.get("Authority vocabulary version")
if path_value != AUTHORITY_VOCABULARY_PATH:
raise ValueError(
f"Authority vocabulary must be the canonical path {AUTHORITY_VOCABULARY_PATH}"
)
if digest is None or re.fullmatch(r"sha256:[0-9a-f]{64}", digest) is None:
raise ValueError("Authority vocabulary digest must be sha256:<64 lowercase hex>")
if version_value is None or re.fullmatch(r"[1-9][0-9]*", version_value) is None:
raise ValueError("Authority vocabulary version must be a canonical positive integer")
registry_version = int(version_value)
path = root / AUTHORITY_VOCABULARY_PATH
if path.is_symlink() or not path.is_file():
raise ValueError("authority vocabulary must be a regular canonical repository file")
fixture_path = root / AUTHORITY_FIXTURE_MANIFEST_PATH
if fixture_path.is_symlink() or not fixture_path.is_file():
raise ValueError("authority fixture manifest must be a regular canonical repository file")
binding = parse_authority_vocabulary(
path.read_bytes(),
expected_digest=digest,
expected_constitution_revision=expected_constitution_revision,
expected_registry_version=registry_version,
fixture_manifest_bytes=fixture_path.read_bytes(),
)
receipt_path = root / AUTHORITY_CONFORMANCE_RECEIPT_PATH
if receipt_path.is_symlink():
raise ValueError(
"authority policy conformance receipt must be a regular canonical repository file"
)
if not receipt_path.exists():
return binding
if not receipt_path.is_file():
raise ValueError(
"authority policy conformance receipt must be a regular canonical repository file"
)
constitution_path = root / AUTHORITY_CONSTITUTION_POLICY_PATH
validator_path = root / AUTHORITY_VALIDATOR_PATH
if constitution_path.is_symlink() or not constitution_path.is_file():
raise ValueError("authority constitution policy must be a regular canonical file")
if validator_path.is_symlink() or not validator_path.is_file():
raise ValueError("authority conformance validator must be a regular canonical file")
conformance = parse_authority_policy_conformance(
receipt_path.read_bytes(),
registry=binding,
constitution=_load_constitution_for_conformance(constitution_path),
validator_bytes=validator_path.read_bytes(),
)
return replace(binding, policy_conformance=conformance)
def load_candidate_authority_vocabulary(
root: Path,
candidate_specs: Mapping[str, SpecRevision],
) -> AuthorityVocabularyBinding | None:
"""Load the sole canonical registry or reject detached registry artifacts."""
authority_spec = candidate_specs.get(AUTHORITY_VOCABULARY_SPEC)
metadata_keys = {
"Authority vocabulary",
"Authority vocabulary digest",
"Authority vocabulary version",
}
has_metadata = authority_spec is not None and bool(
metadata_keys.intersection(authority_spec.metadata)
)
registry_path = root / AUTHORITY_VOCABULARY_PATH
fixture_path = root / AUTHORITY_FIXTURE_MANIFEST_PATH
registry_present = registry_path.exists() or registry_path.is_symlink()
fixture_present = fixture_path.exists() or fixture_path.is_symlink()
receipt_path = root / AUTHORITY_CONFORMANCE_RECEIPT_PATH
receipt_present = receipt_path.exists() or receipt_path.is_symlink()
if not (has_metadata or registry_present or fixture_present or receipt_present):
return None
if authority_spec is None:
raise ValueError("authority vocabulary artifacts require canonical SPEC-000")
if (
authority_spec.revision >= AUTHORITY_VOCABULARY_MIN_REVISION
and authority_spec.status in AUTHORITY_PREIMPLEMENTATION_STATUSES
and receipt_present
):
raise ValueError(
"pre-implementation SPEC-000 revisions forbid the canonical "
"authority-policy conformance receipt"
)
binding = load_authority_vocabulary(
root,
authority_spec.metadata,
expected_constitution_revision=authority_spec.revision,
)
if (
authority_spec.status in AUTHORITY_IMPLEMENTED_STATUSES
and binding.policy_conformance is None
):
raise ValueError(
"implemented, verified, and operational SPEC-000 revisions require "
"the validated authority-policy conformance receipt"
)
return binding
def parse_spec_revision(path: str, exact_bytes: bytes, *, allow_legacy: bool) -> SpecRevision:
try:
text = exact_bytes.decode("utf-8")
except UnicodeDecodeError as exc:
raise ValueError(f"specification is not UTF-8: {exc}") from exc
lines = text.splitlines()
heading = SPEC_HEADING_RE.fullmatch(lines[0] if lines else "")
if heading is None:
raise ValueError("first line must be '# SPEC-NNN: Title'")
metadata: dict[str, str] = {}
body_start = len(lines)
for index, raw_line in enumerate(lines[1:], start=1):
stripped = raw_line.strip()
if stripped.startswith("## "):
body_start = index
break
if not stripped:
continue
if stripped.startswith("- ") and allow_legacy:
stripped = stripped[2:].strip()
if ":" not in stripped:
raise ValueError(f"metadata line has no colon: {stripped!r}")
key, value = (part.strip() for part in stripped.split(":", 1))
if not allow_legacy and raw_line != f"{key}: {value}":
raise ValueError(
"metadata must use canonical 'Key: value' serialization without extra whitespace"
)
if key in metadata:
raise ValueError(f"duplicate metadata key: {key}")
metadata[key] = value
if not allow_legacy:
blank_lines = [
index for index, line in enumerate(lines[:body_start]) if line == ""
]
if blank_lines != [1, body_start - 1]:
raise ValueError(
"specification header requires one blank line after the heading and one before the body"
)
status = metadata.get("Status", "")
legacy_metadata = allow_legacy and "Revision" not in metadata
if not legacy_metadata and status not in KNOWN_STATUSES:
raise ValueError(f"unsupported status: {status!r}")
if legacy_metadata:
revision = 1
revises = "none"
else:
try:
revision = int(metadata.get("Revision", ""))
except ValueError as exc:
raise ValueError(f"invalid revision: {metadata.get('Revision')!r}") from exc
if revision < 1:
raise ValueError("revision must be positive")
revises = metadata.get("Revises", "")
return SpecRevision(
path=path,
spec_id=heading.group("id"),
title=heading.group("title"),
status=status,
revision=revision,
revises=revises,
adoption_decision=metadata.get("Adoption decision"),
lifecycle_decision=metadata.get("Lifecycle decision"),
replacement=metadata.get("Replacement"),
metadata=metadata,
contract_body="\n".join(lines[body_start:]) + ("\n" if text.endswith("\n") else ""),
exact_bytes=exact_bytes,
legacy_metadata=legacy_metadata,
)
def parse_adr_revision(path: str, exact_bytes: bytes) -> AdrRevision:
try:
text = exact_bytes.decode("utf-8")
except UnicodeDecodeError as exc:
raise ValueError(f"architecture decision is not UTF-8: {exc}") from exc
lines = text.splitlines()
heading = ADR_HEADING_RE.fullmatch(lines[0] if lines else "")
if heading is None:
raise ValueError("ADR first line must be '# ADR-NNNN: Title'")
status: str | None = None
lifecycle_transition: str | None = None
for raw_line in lines[1:]:
stripped = raw_line.strip()
if stripped.startswith("## "):
break
if not stripped:
continue
if stripped.startswith("- Status:"):
if status is not None:
raise ValueError("duplicate ADR Status metadata")
status = stripped.split(":", 1)[1].strip()
elif stripped.startswith("- Lifecycle transition:"):
if lifecycle_transition is not None:
raise ValueError("duplicate ADR Lifecycle transition metadata")
lifecycle_transition = stripped.split(":", 1)[1].strip()
if not status:
raise ValueError("ADR Status metadata is required")
return AdrRevision(
path=path,
adr_id=heading.group("id"),
title=heading.group("title"),
status=status,
lifecycle_transition=lifecycle_transition,
exact_bytes=exact_bytes,
)
def validate_adr_lifecycle(
base: Mapping[str, AdrRevision],
candidate: Mapping[str, AdrRevision],
) -> list[LifecycleFinding]:
findings: list[LifecycleFinding] = []
base_numbers = [int(adr_id[-4:]) for adr_id in base]
maximum_base_number = max(base_numbers, default=-1)
for adr_id in sorted(set(candidate) - set(base)):
current = candidate[adr_id]
if int(adr_id[-4:]) <= maximum_base_number:
findings.append(
LifecycleFinding(
"ADR_NUMBER_NOT_APPEND_ONLY",
current.path,
adr_id,
f"new ADR number must exceed base maximum ADR-{maximum_base_number:04d}",
)
)
for adr_id, previous in sorted(base.items()):
if previous.status != "accepted":
continue
current = candidate.get(adr_id)
if current is None:
findings.append(
LifecycleFinding(
"ADR_DELETION_FORBIDDEN",
previous.path,
adr_id,
"an accepted architecture decision cannot be deleted",
)
)
elif current.path != previous.path or current.exact_bytes != previous.exact_bytes:
findings.append(
LifecycleFinding(
"ACCEPTED_ADR_IMMUTABLE",
current.path,
adr_id,
"an accepted architecture decision is byte-immutable; add a later ADR that explicitly supersedes it",
)
)
return sorted(findings, key=lambda item: (item.path, item.code, item.message))
def validate_lifecycle_decision_bindings(
base: Mapping[str, SpecRevision],
candidate: Mapping[str, SpecRevision],
candidate_adrs: Mapping[str, AdrRevision],
) -> list[LifecycleFinding]:
"""Bind every terminal transition to one exact accepted ADR decision."""
findings: list[LifecycleFinding] = []
for spec_id, current in sorted(candidate.items()):
previous = base.get(spec_id)
if (
previous is None
or previous.legacy_metadata
or current.status == previous.status
or current.status not in TERMINAL_STATUSES
or current.lifecycle_decision is None
):
continue
decision = candidate_adrs.get(current.lifecycle_decision)
expected_target = (
current.replacement
if current.status in {"amended", "superseded"}
else "none"
)
expected = (
f"{current.spec_id}@{current.revision} -> "
f"{current.status} -> {expected_target}"
)
if (
decision is None
or decision.status != "accepted"
or decision.lifecycle_transition != expected
):
findings.append(
LifecycleFinding(
"SPEC_LIFECYCLE_DECISION_MISMATCH",
current.path,
current.spec_id,
f"{current.lifecycle_decision} must be accepted and bind exact "
f"transition {expected!r}",
)
)
return sorted(findings, key=lambda item: (item.path, item.code, item.message))
def validate_lifecycle(
base: Mapping[str, SpecRevision],
candidate: Mapping[str, SpecRevision],
*,
authority_vocabulary: AuthorityVocabularyBinding | None = None,
) -> list[LifecycleFinding]:
findings: list[LifecycleFinding] = []
def report(code: str, record: SpecRevision, message: str) -> None:
findings.append(LifecycleFinding(code, record.path, record.spec_id, message))
def direct_dependencies(record: SpecRevision) -> tuple[str, ...]:
value = record.metadata.get("Depends on", "")
if value == "none":
return ()
return tuple(part.strip() for part in value.split(",") if part.strip())
def dependency_revisions(record: SpecRevision) -> dict[str, int] | None:
value = record.metadata.get("Dependency revisions")
if value == "none":
return {}
if not value:
return None
parsed: dict[str, int] = {}
for part in value.split(","):
match = re.fullmatch(
r"(SPEC-[0-9]{3})@([1-9][0-9]*)",
part.strip(),
)
if match is None or match.group(1) in parsed:
return None
parsed[match.group(1)] = int(match.group(2))
return parsed
def authority_binding_matches(record: SpecRevision | None) -> bool:
if record is None or authority_vocabulary is None:
return False
return (
authority_vocabulary.path
== record.metadata.get("Authority vocabulary")
and authority_vocabulary.digest
== record.metadata.get("Authority vocabulary digest")
and str(authority_vocabulary.registry_version)
== record.metadata.get("Authority vocabulary version")
and authority_vocabulary.constitution_revision == record.revision
and authority_vocabulary.constitution_revision
>= AUTHORITY_VOCABULARY_MIN_REVISION
and authority_vocabulary.fixture_manifest_path
== AUTHORITY_FIXTURE_MANIFEST_PATH
and authority_vocabulary.fixture_manifest_version
== authority_vocabulary.registry_version
and re.fullmatch(
r"sha256:[0-9a-f]{64}",
authority_vocabulary.fixture_manifest_digest,
)
is not None
)
def policy_conformance_matches() -> bool:
if authority_vocabulary is None:
return False
receipt = authority_vocabulary.policy_conformance
return (
receipt is not None
and receipt.path == AUTHORITY_CONFORMANCE_RECEIPT_PATH
and receipt.schema_version
== AUTHORITY_CONFORMANCE_RECEIPT_SCHEMA_VERSION
and receipt.registry_digest == authority_vocabulary.digest
and receipt.fixture_manifest_digest
== authority_vocabulary.fixture_manifest_digest
and receipt.validator_version == AUTHORITY_VALIDATOR_VERSION
and re.fullmatch(r"sha256:[0-9a-f]{64}", receipt.validator_digest)
is not None
and receipt.case_count == len(authority_vocabulary.fixture_cases)
)
for spec_id in sorted(set(base) - set(candidate)):
record = base[spec_id]
report(
"SPEC_DELETION_FORBIDDEN",
record,
"canonical specification revisions remain in history and cannot be deleted",
)
for spec_id, current in sorted(candidate.items()):
previous = base.get(spec_id)
if (
current.spec_id == AUTHORITY_VOCABULARY_SPEC
and current.revision >= AUTHORITY_VOCABULARY_MIN_REVISION
and not authority_binding_matches(current)
):
report(
"SPEC_AUTHORITY_VOCABULARY_INVALID",
current,
"SPEC-000 revision 2 or later requires the exact revision-bound, "
"digest-pinned AuthorityVocabularyRegistry and fixture manifest",
)
if (
current.spec_id == AUTHORITY_VOCABULARY_SPEC
and current.revision >= AUTHORITY_VOCABULARY_MIN_REVISION
and current.status in AUTHORITY_PREIMPLEMENTATION_STATUSES
and authority_vocabulary is not None
and authority_vocabulary.policy_conformance is not None
):
report(
"SPEC_AUTHORITY_POLICY_CONFORMANCE_PREMATURE",
current,
"draft, architecture-reviewed, and accepted SPEC-000 revisions "
"must not carry an implementation-stage conformance receipt",
)
requires_policy_conformance = current.status in AUTHORITY_IMPLEMENTED_STATUSES and (
(
current.spec_id == AUTHORITY_VOCABULARY_SPEC
and current.revision >= AUTHORITY_VOCABULARY_MIN_REVISION
)
or int(current.spec_id[-3:]) >= 4
)
authority_spec = (
current
if current.spec_id == AUTHORITY_VOCABULARY_SPEC
else candidate.get(AUTHORITY_VOCABULARY_SPEC)
)
if requires_policy_conformance and (
not authority_binding_matches(authority_spec)
or not policy_conformance_matches()
):
report(
"SPEC_AUTHORITY_POLICY_CONFORMANCE_REQUIRED",
current,
"implemented, verified, and operational stages require the exact "
"recomputed authority-policy conformance receipt with zero skipped cases",
)
if previous is None:
if current.status != "draft" or current.revision != 1 or current.revises != "none":
report(
"INVALID_INITIAL_SPEC_REVISION",
current,
"a new specification must enter as draft revision 1 with Revises: none",
)
if (
current.spec_id == "SPEC-026"
and current.revision == 1
and current.metadata.get("Activation") != "deferred"
):
report(
"INVALID_SPEC_ACTIVATION",
current,
"SPEC-026 revision 1 must remain Activation: deferred",
)
continue
adoption = LEGACY_ADOPTION.get(spec_id)
if previous.legacy_metadata and adoption is not None:
expected_base, expected_current = adoption
expected_keys = set(previous.metadata) | {
"Revision",
"Revises",
"Owners",
"Adoption decision",
"Dependency revisions",
}
preserved_legacy_metadata = all(
current.metadata.get(key) == value
for key, value in previous.metadata.items()
if key != "Status"
)
if (
previous.status == expected_base
and current.status == expected_current
and current.revision == 1
and current.revises == "none"
and current.adoption_decision == LEGACY_ADOPTION_DECISION
and current.lifecycle_decision is None
and current.metadata.get("Owners") == LEGACY_ADOPTION_OWNERS[spec_id]
and current.metadata.get("Dependency revisions")
== LEGACY_ADOPTION_DEPENDENCY_REVISIONS[spec_id]
and set(current.metadata) == expected_keys
and preserved_legacy_metadata
and previous.path == current.path
and previous.title == current.title
and previous.contract_body == current.contract_body
):
continue
report(
"INVALID_LEGACY_LIFECYCLE_ADOPTION",
current,
"legacy SPEC-000 through SPEC-003 normalization must match ADR-0005 exactly",
)
continue
if previous.legacy_metadata:
report(
"UNDECLARED_LEGACY_SPEC",
current,
"legacy metadata is allowed only for the explicit ADR-0005 adoption set",
)
continue
if current.path != previous.path:
report(
"SPEC_PATH_CHANGED",
current,
"a specification identity cannot move to a different canonical path",
)
if current.revision < previous.revision:
report(
"SPEC_REVISION_REGRESSION",
current,
f"revision {current.revision} is older than base revision {previous.revision}",
)
continue
if current.revision > previous.revision:
if current.revision != previous.revision + 1:
report(
"SPEC_REVISION_GAP",
current,
"a replacement revision must increment by exactly one",
)
if current.status != "draft":
report(
"SPEC_REVISION_NOT_DRAFT",
current,
"a replacement revision must restart at draft",
)
if current.revises != previous.digest:
report(
"SPEC_REVISION_DIGEST_MISMATCH",
current,
f"Revises must bind the exact base bytes as {previous.digest}",
)
if previous.status not in {"amended", "superseded"}:
report(
"SPEC_REVISION_PREDECESSOR_ACTIVE",
current,
"the prior revision must first be human-merged as amended or superseded",
)
expected_replacement = f"{current.spec_id}@{current.revision}"
if previous.status in {"amended", "superseded"} and (
previous.replacement != expected_replacement
):
report(
"SPEC_REPLACEMENT_MISMATCH",
current,
"the terminal predecessor must name this exact replacement as "
f"{expected_replacement}",
)
continue
if current.revises != previous.revises:
report(
"SPEC_REVISION_IDENTITY_CHANGED",
current,
"Revises cannot change without a new revision",
)
if current.adoption_decision != previous.adoption_decision:
report(
"SPEC_ADOPTION_METADATA_CHANGED",
current,
"the one-time adoption decision is immutable within its revision",
)
if (
previous.status != "draft"
and current.metadata.get("Dependency revisions")
!= previous.metadata.get("Dependency revisions")
):
report(
"SPEC_DEPENDENCY_BINDING_CHANGED",
current,
"dependency revision bindings are immutable after architecture review",
)
if (
current.spec_id == "SPEC-026"
and current.metadata.get("Activation") != previous.metadata.get("Activation")
):
report(
"SPEC_ACTIVATION_REVISION_REQUIRED",
current,
"changing SPEC-026 Activation requires a new digest-linked revision",
)
lifecycle_metadata_changed = (
current.lifecycle_decision != previous.lifecycle_decision
or current.replacement != previous.replacement
)
lifecycle_metadata_initialization = (
previous.lifecycle_decision is None
and previous.replacement is None
and current.status != previous.status
and current.status in TERMINAL_STATUSES
)
if lifecycle_metadata_changed and not lifecycle_metadata_initialization:
report(
"SPEC_LIFECYCLE_METADATA_CHANGED",
current,
"Lifecycle decision and Replacement may be written once only on entry to a terminal state",
)
allowed = LEGAL_SAME_REVISION_TRANSITIONS.get(previous.status, set())
if current.status not in allowed:
report(
"INVALID_SPEC_STATUS_TRANSITION",
current,
f"{previous.status} -> {current.status} is not a legal same-revision transition",
)
if current.status in TERMINAL_STATUSES and current.lifecycle_decision is None:
report(
"MISSING_SPEC_LIFECYCLE_DECISION",
current,
"a terminal transition requires a Lifecycle decision",
)
if current.status in ACTIVE_STATUSES and current.lifecycle_decision is not None:
report(
"INVALID_SPEC_LIFECYCLE_DECISION",
current,
"Lifecycle decision must be absent until entry to a terminal state",
)
if current.status in {"amended", "superseded"} and not current.replacement:
report(
"MISSING_SPEC_REPLACEMENT",
current,
"an amended or superseded transition requires an exact Replacement",
)
elif current.status in {"amended", "superseded"}:
expected_replacement = f"{current.spec_id}@{current.revision + 1}"
if current.replacement != expected_replacement:
report(
"SPEC_REPLACEMENT_MISMATCH",
current,
"an amended or superseded transition must name the next revision of "
f"the same specification as {expected_replacement}",
)
contract_changed = current.contract_identity != previous.contract_identity
status_changed = current.status != previous.status
if status_changed and contract_changed:
report(
"SPEC_STATUS_TRANSITION_NOT_ISOLATED",
current,
"a lifecycle transition cannot also change the contract",
)
elif previous.status in ACCEPTED_OR_LATER and contract_changed:
report(
"SPEC_REVISION_REQUIRED",
current,
"an accepted contract change requires a new draft revision",
)
elif previous.status == "architecture_reviewed" and contract_changed:
report(
"SPEC_REVISION_REQUIRED",
current,
"an architecture-reviewed contract change requires a new draft revision",
)
if (
status_changed
and current.status in STAGE_RANK
and current.status != "draft"
):
if int(current.spec_id[-3:]) >= 4:
authority_spec = candidate.get(AUTHORITY_VOCABULARY_SPEC)
if (
authority_spec is None
or authority_spec.revision < AUTHORITY_VOCABULARY_MIN_REVISION
or authority_spec.status not in AUTHORITY_VOCABULARY_READY_STATUSES
or not authority_binding_matches(authority_spec)
):
report(
"SPEC_AUTHORITY_VOCABULARY_NOT_READY",
current,
"SPEC-004 and later cannot advance until the candidate tree "
"contains accepted SPEC-000 revision 2 or later with the "
"AuthorityVocabularyRegistry",
)
required_rank = STAGE_RANK[current.status]
expected_dependencies = set(direct_dependencies(current))
bound_revisions = dependency_revisions(current)
if bound_revisions is None or set(bound_revisions) != expected_dependencies:
report(
"SPEC_DEPENDENCY_REVISION_MISMATCH",
current,
"the transition must bind every direct dependency exactly as SPEC-NNN@revision",
)
bound_revisions = {}
for dependency_id in direct_dependencies(current):
dependency = candidate.get(dependency_id)
if dependency is None:
report(
"SPEC_DEPENDENCY_NOT_READY",
current,
f"direct dependency {dependency_id} is absent from the candidate tree",
)
continue
if bound_revisions.get(dependency_id) != dependency.revision:
report(
"SPEC_DEPENDENCY_REVISION_MISMATCH",
current,
f"binding for {dependency_id} must match candidate revision "
f"{dependency.revision}",
)
if (
dependency_id == AUTHORITY_VOCABULARY_SPEC
and int(current.spec_id[-3:]) >= 4
and bound_revisions.get(dependency_id, 0)
< AUTHORITY_VOCABULARY_MIN_REVISION
):
report(
"SPEC_AUTHORITY_VOCABULARY_NOT_READY",
current,
"the frozen SPEC-000 dependency must be revision 2 or later",
)
dependency_rank = STAGE_RANK.get(dependency.status)
if dependency_rank is None or dependency_rank < required_rank:
report(
"SPEC_DEPENDENCY_NOT_READY",
current,
f"{current.status} requires {dependency_id}@{dependency.revision} "
f"at {current.status} or later, found {dependency.status}",
)
return sorted(findings, key=lambda item: (item.path, item.code, item.message))
def _git(root: Path, arguments: list[str]) -> bytes:
completed = subprocess.run(
["git", *arguments],
cwd=root,
check=False,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
if completed.returncode != 0:
stderr = completed.stderr.decode("utf-8", errors="replace").strip()
raise RuntimeError(stderr or f"git {' '.join(arguments)} failed")
return completed.stdout
def load_candidate(root: Path) -> dict[str, SpecRevision]:
records: dict[str, SpecRevision] = {}
for path in sorted((root / "docs" / "specs").glob("SPEC-*.md")):
relative = path.relative_to(root).as_posix()
if SPEC_PATH_RE.fullmatch(relative) is None:
continue
record = parse_spec_revision(relative, path.read_bytes(), allow_legacy=False)
if record.spec_id in records:
raise ValueError(f"duplicate specification ID: {record.spec_id}")
records[record.spec_id] = record
return records
def load_base(root: Path, base_ref: str) -> dict[str, SpecRevision]:
_git(root, ["rev-parse", "--verify", f"{base_ref}^{{commit}}"])
paths = _git(
root,
["ls-tree", "-r", "--name-only", base_ref, "--", "docs/specs"],
).decode("utf-8").splitlines()
records: dict[str, SpecRevision] = {}
for path in sorted(path for path in paths if SPEC_PATH_RE.fullmatch(path)):
record = parse_spec_revision(
path,
_git(root, ["show", f"{base_ref}:{path}"]),
allow_legacy=True,
)
if record.spec_id in records:
raise ValueError(f"duplicate base specification ID: {record.spec_id}")
records[record.spec_id] = record
return records
def load_candidate_adrs(root: Path) -> dict[str, AdrRevision]:
records: dict[str, AdrRevision] = {}
for path in sorted((root / "docs" / "decisions").glob("[0-9][0-9][0-9][0-9]-*.md")):
relative = path.relative_to(root).as_posix()
if ADR_PATH_RE.fullmatch(relative) is None:
continue
record = parse_adr_revision(relative, path.read_bytes())
if record.adr_id in records:
raise ValueError(f"duplicate architecture decision ID: {record.adr_id}")
records[record.adr_id] = record
return records
def load_base_adrs(root: Path, base_ref: str) -> dict[str, AdrRevision]:
paths = _git(
root,
["ls-tree", "-r", "--name-only", base_ref, "--", "docs/decisions"],
).decode("utf-8").splitlines()
records: dict[str, AdrRevision] = {}
for path in sorted(path for path in paths if ADR_PATH_RE.fullmatch(path)):
record = parse_adr_revision(path, _git(root, ["show", f"{base_ref}:{path}"]))
if record.adr_id in records:
raise ValueError(f"duplicate base architecture decision ID: {record.adr_id}")
records[record.adr_id] = record
return records
def print_findings(findings: Iterable[LifecycleFinding]) -> None:
for finding in findings:
identity = f" ({finding.spec_id})" if finding.spec_id else ""
print(
f"[{finding.code}] {finding.path}{identity}: {finding.message}",
file=sys.stderr,
)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--base-ref", required=True, help="exact Git commit or protected base ref")
parser.add_argument("--root", type=Path, default=ROOT)
args = parser.parse_args()
root = args.root.resolve()
try:
base_specs = load_base(root, args.base_ref)
candidate_specs = load_candidate(root)
candidate_adrs = load_candidate_adrs(root)
authority_vocabulary = load_candidate_authority_vocabulary(root, candidate_specs)
findings = validate_lifecycle(
base_specs,
candidate_specs,
authority_vocabulary=authority_vocabulary,
)
findings.extend(
validate_lifecycle_decision_bindings(
base_specs,
candidate_specs,
candidate_adrs,
)
)
findings.extend(
validate_adr_lifecycle(
load_base_adrs(root, args.base_ref),
candidate_adrs,
)
)
findings = sorted(findings, key=lambda item: (item.path, item.code, item.message))
except (OSError, RuntimeError, ValueError) as exc:
print(f"[SPEC_LIFECYCLE_ERROR] {exc}", file=sys.stderr)
return 1
if findings:
print_findings(findings)
return 1
print(f"Specification lifecycle check passed against {args.base_ref}")
return 0
if __name__ == "__main__":
raise SystemExit(main())