feat(capability): Master Capability Registry v0 (Phase 2C, Compliance Execution domain)

Third instance of the identity-machine pattern (after Master Controls and Master
Obligations). New compliance/capability/ package: MasterCapability with stable MCAP
ids, CapabilityCandidate minting, seven typed relation types, a VERSIONED derivation
policy, and identity lifecycle (merge/split/deprecate/redirect with provenance).

Stored: identities, sources, relationship types, policy versions, lifecycle events,
provenance. Derived (never stored): confidence/status via evaluate_relation under a
policy version. Hard rule (structurally guarded): a certification alone can never
yield CONFIRMED — only CONFIRMS + concrete artifact (or expert) does.

Built from the Reasoning session per user directive but this IS the Compliance
Execution model (Execution owns Capability) — handed off via the board. Metadata-first:
CapabilityRelation is registry metadata, NOT a new meta-model class (freeze v1.0
untouched). No Company-Gap, no real ISO/cert mappings, no UI/RAG, no generic
canonicalization engine. 11 tests; mypy --strict clean; LOC ok.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-06-26 21:35:12 +02:00
parent 7eb7f61483
commit 6ccc6c87c1
5 changed files with 629 additions and 0 deletions
@@ -0,0 +1,191 @@
"""Master Capability Registry v0 — minting, derivation, identity lifecycle.
STORED on the registry: identities, sources, relation types, policy versions,
lifecycle events, provenance. DERIVED (never stored): confidence/status, via
`evaluate_relation` under a versioned policy.
Python 3.9 compatible (no `|` unions).
"""
from __future__ import annotations
from typing import Dict, List, Optional, Set
from pydantic import BaseModel, Field
from .policy import DEFAULT_POLICY
from .schemas import (
AssertionStatus,
CapabilityCandidate,
CapabilityRelation,
Confidence,
DerivedAssessment,
IdentityLifecycleEvent,
LifecycleEventType,
LifecycleState,
MasterCapability,
PolicyVersion,
Provenance,
)
class CapabilityRegistry(BaseModel):
# NOTE: no confidence/coverage field anywhere — those are DERIVED, never stored.
capabilities: Dict[str, MasterCapability] = Field(default_factory=dict)
relations: List[CapabilityRelation] = Field(default_factory=list)
lifecycle_events: List[IdentityLifecycleEvent] = Field(default_factory=list)
policy: PolicyVersion = Field(default_factory=lambda: DEFAULT_POLICY)
next_serial: int = 1
def _mcap_id(serial: int) -> str:
return "MCAP-%05d" % serial
def _next_event_id(registry: "CapabilityRegistry") -> str:
return "evt-%d" % (len(registry.lifecycle_events) + 1)
def mint_capability(
registry: CapabilityRegistry,
candidate: CapabilityCandidate,
provenance: Optional[Provenance] = None,
name: str = "",
definition: str = "",
category: str = "",
domains: Optional[List[str]] = None,
) -> MasterCapability:
"""Assign the next stable MCAP id to a candidate and register it (with provenance)."""
cap_id = _mcap_id(registry.next_serial)
cap = MasterCapability(
capability_id=cap_id,
name=name or candidate.normalized or candidate.raw_term,
definition=definition,
category=category,
domains=domains or [],
provenance=provenance
or Provenance(author="system", basis="minted from candidate '%s'" % candidate.raw_term),
)
registry.capabilities[cap_id] = cap
registry.next_serial += 1
return cap
def evaluate_relation(
relation: CapabilityRelation, policy: Optional[PolicyVersion] = None
) -> DerivedAssessment:
"""Derive (status, confidence) from (relationship_type, evidence_kind) under a
versioned policy. Deterministic; result is returned, never stored."""
pol = policy if policy is not None else DEFAULT_POLICY
status = AssertionStatus.UNKNOWN
confidence = Confidence.LOW
found = False
for rule in pol.rules:
if (
rule.relationship_type == relation.relationship_type
and rule.evidence_kind == relation.evidence_kind
):
status, confidence, found = rule.status, rule.confidence, True
break
expl = "%s + %s under %s -> %s/%s%s" % (
relation.relationship_type.value,
relation.evidence_kind.value,
pol.policy_version,
status.value,
confidence.value,
"" if found else " (no rule)",
)
return DerivedAssessment(
target_capability_id=relation.target_capability_id,
status=status,
confidence=confidence,
policy_version=pol.policy_version,
explanation=expl,
)
def resolve(
registry: CapabilityRegistry, capability_id: str, _seen: Optional[Set[str]] = None
) -> Optional[MasterCapability]:
"""Follow redirects (from merge/deprecate) to the current canonical capability."""
seen = _seen if _seen is not None else set()
if capability_id in seen:
return None # redirect cycle guard
seen.add(capability_id)
cap = registry.capabilities.get(capability_id)
if cap is None:
return None
if cap.redirect_to:
return resolve(registry, cap.redirect_to, seen)
# terminal: only an ACTIVE capability resolves; a deprecated dead-end -> None
return cap if cap.state == LifecycleState.ACTIVE else None
def deprecate_capability(
registry: CapabilityRegistry,
capability_id: str,
redirect_to: Optional[str] = None,
provenance: Optional[Provenance] = None,
) -> IdentityLifecycleEvent:
cap = registry.capabilities.get(capability_id)
if cap is None:
raise KeyError(capability_id)
cap.state = LifecycleState.DEPRECATED
cap.redirect_to = redirect_to
event = IdentityLifecycleEvent(
event_id=_next_event_id(registry),
event_type=LifecycleEventType.REDIRECT if redirect_to else LifecycleEventType.DEPRECATE,
from_ids=[capability_id],
to_ids=[redirect_to] if redirect_to else [],
provenance=provenance or Provenance(author="system", basis="deprecate %s" % capability_id),
)
registry.lifecycle_events.append(event)
return event
def merge_capabilities(
registry: CapabilityRegistry,
from_id: str,
into_id: str,
provenance: Optional[Provenance] = None,
) -> IdentityLifecycleEvent:
"""Merge `from_id` into `into_id`: deprecate `from_id` with a redirect to `into_id`."""
if from_id not in registry.capabilities or into_id not in registry.capabilities:
raise KeyError("%s or %s" % (from_id, into_id))
frm = registry.capabilities[from_id]
frm.state = LifecycleState.DEPRECATED
frm.redirect_to = into_id
event = IdentityLifecycleEvent(
event_id=_next_event_id(registry),
event_type=LifecycleEventType.MERGE,
from_ids=[from_id],
to_ids=[into_id],
provenance=provenance or Provenance(author="system", basis="merge %s -> %s" % (from_id, into_id)),
)
registry.lifecycle_events.append(event)
return event
def split_capability(
registry: CapabilityRegistry,
from_id: str,
into_ids: List[str],
primary: Optional[str] = None,
provenance: Optional[Provenance] = None,
) -> IdentityLifecycleEvent:
"""Split `from_id` into several capabilities. The old id deprecates; it redirects
to `primary` only if one is given (else it resolves to None — split is ambiguous)."""
if from_id not in registry.capabilities:
raise KeyError(from_id)
frm = registry.capabilities[from_id]
frm.state = LifecycleState.DEPRECATED
frm.redirect_to = primary
event = IdentityLifecycleEvent(
event_id=_next_event_id(registry),
event_type=LifecycleEventType.SPLIT,
from_ids=[from_id],
to_ids=list(into_ids),
provenance=provenance or Provenance(author="system", basis="split %s" % from_id),
)
registry.lifecycle_events.append(event)
return event