init: company-haness 설계
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
"""Shared divergent-lens policy derived from the role registries.
|
||||
|
||||
The policy deliberately reasons about registered family/role capabilities. A
|
||||
caller supplied lens label is never sufficient evidence that a role can carry
|
||||
that lens.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
ROOT = os.environ.get("CLAUDE_PROJECT_DIR") or os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
)
|
||||
REG = os.path.join(ROOT, "org-os", "00-role-registry")
|
||||
TIERS = os.path.join(ROOT, "org-os", "06-agent-work", "governance-tiers.yaml")
|
||||
|
||||
|
||||
def _load(path: str) -> dict[str, Any]:
|
||||
try:
|
||||
with open(path, encoding="utf-8") as handle:
|
||||
return yaml.safe_load(handle) or {}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def registries() -> tuple[dict[str, Any], dict[str, Any], list[str]]:
|
||||
families_doc = _load(os.path.join(REG, "capability-families.yaml"))
|
||||
lenses_doc = _load(os.path.join(REG, "lens-registry.yaml"))
|
||||
families = {
|
||||
str(item.get("family-id")): item
|
||||
for item in (families_doc.get("capability-families", {}) or {}).get("families", []) or []
|
||||
if isinstance(item, dict) and item.get("family-id")
|
||||
}
|
||||
lens_items = (lenses_doc.get("lens-registry", {}) or {}).get("lenses", []) or []
|
||||
lenses = {
|
||||
str(item.get("lens-id")): item
|
||||
for item in lens_items if isinstance(item, dict) and item.get("lens-id")
|
||||
}
|
||||
order = [str(item.get("lens-id")) for item in lens_items
|
||||
if isinstance(item, dict) and item.get("lens-id")]
|
||||
return families, lenses, order
|
||||
|
||||
|
||||
def normalize_family_ids(values: Any) -> list[str]:
|
||||
if isinstance(values, str):
|
||||
values = [values]
|
||||
if not isinstance(values, list):
|
||||
return []
|
||||
return [str(value).strip().upper() for value in values if str(value or "").strip()]
|
||||
|
||||
|
||||
def candidate_family_errors(values: Any, *, tier: str, mode: str,
|
||||
enforce_lens_floor: bool = False) -> list[str]:
|
||||
family_ids = normalize_family_ids(values)
|
||||
families, _lenses, _order = registries()
|
||||
errors: list[str] = []
|
||||
if not family_ids:
|
||||
return ["candidate-families는 비어 있지 않은 등록 family 목록이어야 한다"]
|
||||
duplicates = sorted({value for value in family_ids if family_ids.count(value) > 1})
|
||||
unknown = sorted(set(family_ids) - set(families))
|
||||
if duplicates:
|
||||
errors.append(f"candidate-families 중복: {duplicates}")
|
||||
if unknown:
|
||||
errors.append(f"candidate-families 미등록 family: {unknown}")
|
||||
if errors or (str(mode).lower() != "divergent" and not enforce_lens_floor):
|
||||
return errors
|
||||
|
||||
available = available_lenses(family_ids)
|
||||
policy = divergent_policy(tier)
|
||||
minimum = policy.get("min-distinct-lenses")
|
||||
if isinstance(minimum, int) and len(available) < minimum:
|
||||
errors.append(
|
||||
f"candidate-families 이론 렌즈 커버리지 부족: {len(available)} < tier {tier} 최소 {minimum}"
|
||||
)
|
||||
if policy.get("contrarian-required") and "LENS-CONTRARIAN" not in available:
|
||||
errors.append(
|
||||
"candidate-families에 contrarian rotation을 맡을 audit-capable family가 없다"
|
||||
)
|
||||
return errors
|
||||
|
||||
|
||||
def divergent_policy(tier: str) -> dict[str, Any]:
|
||||
doc = _load(TIERS).get("governance-tiers", {}) or {}
|
||||
return (((doc.get("tiers") or {}).get(str(tier).lower()) or {}).get("divergent") or {})
|
||||
|
||||
|
||||
def available_lenses(family_ids: list[str]) -> set[str]:
|
||||
"""Return lenses the candidate set can actually assign.
|
||||
|
||||
Contrarian is special: the registry intentionally has no fixed carrier. It
|
||||
becomes available only when the candidate set contains an audit-capable
|
||||
family that can be rotated in independently.
|
||||
"""
|
||||
families, lenses, _order = registries()
|
||||
selected = {family_id for family_id in family_ids if family_id in families}
|
||||
result: set[str] = set()
|
||||
for lens_id, lens in lenses.items():
|
||||
if set(lens.get("carrier-families", []) or []) & selected:
|
||||
result.add(lens_id)
|
||||
for family_id in selected:
|
||||
result.update(families[family_id].get("carries-lenses", []) or [])
|
||||
if any(families[family_id].get("audit-capable") for family_id in selected):
|
||||
result.add("LENS-CONTRARIAN")
|
||||
return result
|
||||
|
||||
|
||||
def required_lenses(family_ids: list[str], *, tier: str, mode: str) -> set[str]:
|
||||
if str(mode).lower() != "divergent":
|
||||
return set()
|
||||
available = available_lenses(family_ids)
|
||||
policy = divergent_policy(tier)
|
||||
minimum = policy.get("min-distinct-lenses")
|
||||
contrarian = bool(policy.get("contrarian-required"))
|
||||
if minimum == "all-relevant":
|
||||
return available
|
||||
count = int(minimum or 0)
|
||||
required: list[str] = []
|
||||
if contrarian and "LENS-CONTRARIAN" in available:
|
||||
required.append("LENS-CONTRARIAN")
|
||||
_families, _lenses, order = registries()
|
||||
required.extend(lens for lens in order if lens in available and lens not in required)
|
||||
return set(required[:count])
|
||||
|
||||
|
||||
def family_for_role(role_id: str) -> tuple[str | None, dict[str, Any] | None]:
|
||||
families, _lenses, _order = registries()
|
||||
wanted = str(role_id or "").upper()
|
||||
for family_id, family in families.items():
|
||||
if wanted in {str(value).upper() for value in family.get("member-role-ids", []) or []}:
|
||||
return family_id, family
|
||||
return None, None
|
||||
|
||||
|
||||
def role_can_carry_lens(role_id: str, lens_id: str) -> bool:
|
||||
family_id, family = family_for_role(role_id)
|
||||
if not family_id or not family:
|
||||
return False
|
||||
lens_id = str(lens_id or "").upper()
|
||||
if lens_id == "LENS-CONTRARIAN":
|
||||
return bool(family.get("audit-capable"))
|
||||
return lens_id in available_lenses([family_id])
|
||||
Reference in New Issue
Block a user