feat(company): Company Intelligence 2A — Company Capability Profile foundation
HEAD of the spine Company->Capability->Product->Regulation->Obligation->Procedure ->Evidence. New compliance/company/ package: CompanyContext container + a four-state trust model (declared/inferred/confirmed/unknown). Hard rule (structural): a certification yields at most an INFERRED candidate and is never auto-treated as CONFIRMED/"erfuellt". A certification produces evidence-of- capability; only real ExistingEvidence promotes a capability to CONFIRMED. Ownership: Reasoning owns the container + trust-state; the Certification->Capability mapping is Execution's domain, consumed via an injected contract. No mapping data in product code (tests inject mocks). No endpoint/UI/RAG/new regs/controls; no meta-model classes (freeze v1.0 untouched). 8 tests; mypy --strict clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
"""Tests for Company Intelligence (Phase 2A) — Company Capability Profile.
|
||||
|
||||
Acceptance: from a CompanyContext (certifications, declarations, evidence) the
|
||||
engine derives operational capabilities with a four-state trust model and a HARD
|
||||
RULE: a certification is NEVER auto-treated as "erfuellt" — at most INFERRED.
|
||||
|
||||
The Certification->Capability mapping is Execution's domain. It is injected here as
|
||||
a MOCK (the yaml-like dict below lives ONLY in tests); product code ships no table.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from compliance.company import (
|
||||
CapabilityMappingEntry,
|
||||
Certification,
|
||||
CompanyContext,
|
||||
Declaration,
|
||||
ExistingEvidence,
|
||||
VerificationStatus,
|
||||
build_company_profile,
|
||||
)
|
||||
from compliance.reasoning.enums import Confidence
|
||||
|
||||
# --- MOCK mapping (Execution-owned in reality; here only for the tests) -------
|
||||
# mapping:
|
||||
# ISO27001 -> [cap_patch_management, cap_supplier_management]
|
||||
MOCK_MAPPING = {
|
||||
"ISO27001": CapabilityMappingEntry(
|
||||
capability_ids=["cap_patch_management", "cap_supplier_management"],
|
||||
confidence=Confidence.MEDIUM,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def _candidate(profile, capability_id):
|
||||
return [c for c in profile.candidate_capabilities if c.capability_id == capability_id]
|
||||
|
||||
|
||||
def _confirmed_ids(profile):
|
||||
return {c.capability_id for c in profile.confirmed_capabilities}
|
||||
|
||||
|
||||
# A certification yields INFERRED candidates via the injected mapping.
|
||||
def test_certification_infers_candidates_via_injected_mapping():
|
||||
ctx = CompanyContext(company_id="acme", certifications=[Certification(certification_id="ISO27001")])
|
||||
profile = build_company_profile(ctx, MOCK_MAPPING)
|
||||
ids = {c.capability_id for c in profile.candidate_capabilities}
|
||||
assert ids == {"cap_patch_management", "cap_supplier_management"}
|
||||
for c in profile.candidate_capabilities:
|
||||
assert c.verification_status == VerificationStatus.INFERRED
|
||||
assert c.source == "certification:ISO27001"
|
||||
|
||||
|
||||
# Without an injected mapping there are NO inferred capabilities — only the claim.
|
||||
# This is the architectural guarantee that the table lives only in Execution.
|
||||
def test_no_mapping_no_inferred_capabilities():
|
||||
ctx = CompanyContext(company_id="acme", certifications=[Certification(certification_id="ISO27001")])
|
||||
profile = build_company_profile(ctx) # default EMPTY mapping
|
||||
assert profile.candidate_capabilities == []
|
||||
# the certification still produced evidence-of-claim (refinement 1)
|
||||
assert len(profile.capability_evidence) == 1
|
||||
assert profile.capability_evidence[0].source == "certification:ISO27001"
|
||||
assert profile.capability_evidence[0].certification_id == "ISO27001"
|
||||
|
||||
|
||||
# A customer declaration yields a DECLARED candidate.
|
||||
def test_declaration_yields_declared_candidate():
|
||||
ctx = CompanyContext(company_id="acme", declarations=[Declaration(capability_id="cap_patch_management")])
|
||||
profile = build_company_profile(ctx, MOCK_MAPPING)
|
||||
cands = _candidate(profile, "cap_patch_management")
|
||||
assert len(cands) == 1
|
||||
assert cands[0].verification_status == VerificationStatus.DECLARED
|
||||
|
||||
|
||||
# declared + inferred coexist as distinct signals for the same capability.
|
||||
def test_declared_and_inferred_coexist():
|
||||
ctx = CompanyContext(
|
||||
company_id="acme",
|
||||
certifications=[Certification(certification_id="ISO27001")],
|
||||
declarations=[Declaration(capability_id="cap_patch_management")],
|
||||
)
|
||||
profile = build_company_profile(ctx, MOCK_MAPPING)
|
||||
statuses = {c.verification_status for c in _candidate(profile, "cap_patch_management")}
|
||||
assert statuses == {VerificationStatus.DECLARED, VerificationStatus.INFERRED}
|
||||
|
||||
|
||||
# HARD RULE: a certification alone NEVER yields a confirmed capability.
|
||||
def test_hard_rule_certification_never_confirmed():
|
||||
ctx = CompanyContext(company_id="acme", certifications=[Certification(certification_id="ISO27001")])
|
||||
profile = build_company_profile(ctx, MOCK_MAPPING)
|
||||
assert _confirmed_ids(profile) == set()
|
||||
for c in profile.candidate_capabilities:
|
||||
assert c.verification_status != VerificationStatus.CONFIRMED
|
||||
|
||||
|
||||
# Only real evidence confirms a capability — and it leaves the candidate list.
|
||||
def test_evidence_confirms_capability():
|
||||
ctx = CompanyContext(
|
||||
company_id="acme",
|
||||
certifications=[Certification(certification_id="ISO27001")],
|
||||
evidence=[ExistingEvidence(evidence_id="pol-1", evidence_type="policy", proves_capability_id="cap_patch_management")],
|
||||
)
|
||||
profile = build_company_profile(ctx, MOCK_MAPPING)
|
||||
assert "cap_patch_management" in _confirmed_ids(profile)
|
||||
confirmed = [c for c in profile.confirmed_capabilities if c.capability_id == "cap_patch_management"][0]
|
||||
assert confirmed.verification_status == VerificationStatus.CONFIRMED
|
||||
assert confirmed.confidence == Confidence.HIGH
|
||||
assert confirmed.sources == ["pol-1"]
|
||||
# a confirmed capability is no longer a mere candidate
|
||||
assert _candidate(profile, "cap_patch_management") == []
|
||||
# the un-proven capability stays an inferred candidate
|
||||
assert _candidate(profile, "cap_supplier_management")[0].verification_status == VerificationStatus.INFERRED
|
||||
|
||||
|
||||
# The four-state vocabulary exists and is ordered declared->inferred->confirmed (+unknown).
|
||||
def test_four_states_present():
|
||||
assert {s.value for s in VerificationStatus} == {"declared", "inferred", "confirmed", "unknown"}
|
||||
|
||||
|
||||
# verification_status is a FOURTH vocabulary, disjoint from ClaimCoverage and DeltaType.
|
||||
def test_verification_status_distinct_vocabulary():
|
||||
from compliance.rci.schemas import DeltaType
|
||||
from compliance.reasoning.enums import ClaimCoverage
|
||||
|
||||
verif = {s.value for s in VerificationStatus}
|
||||
assert verif.isdisjoint({c.value for c in ClaimCoverage})
|
||||
assert verif.isdisjoint({d.value for d in DeltaType})
|
||||
Reference in New Issue
Block a user