Seeded repo with intent and initial content.

This commit is contained in:
tegwick 2026-10-03 14:13:45 +02:00
parent 86b9063721
commit 12d0c31cff
27 changed files with 2683 additions and 1 deletions

View file

@ -0,0 +1,16 @@
"""Reference implementation of the Emergent Security decision function (canon v0.8).
Modules, one concern each:
constitution rule identifiers
model Request, Decision, Outcome (pure data)
scales ordinal risk scales and comparisons
envelope the default envelope (canon A6)
delegation registry delegations and the Grant they produce
memory security memory entries
tenant platform tenant envelopes, tenant governance, validation
decide the ordered decision function (canon A3)
"""
from emergent_security.decide import decide
__all__ = ["decide"]

View file

@ -0,0 +1,13 @@
"""Stable rule identifiers of the canon (constitution C*, precedence P*)."""
from __future__ import annotations
CONSTITUTION = frozenset(f"C{number}" for number in range(1, 15))
PRECEDENCE = frozenset(f"P{number}" for number in range(1, 8))
RULE_IDS = CONSTITUTION | PRECEDENCE
# Rule cited when a platform tenant envelope bounds an effect, and when a tenant's own
# governance envelope prohibits it.
PLATFORM_TENANT_BOUND = "C14"
TENANT_GOVERNANCE = "P6"
ROOT = "C12"

View file

@ -0,0 +1,121 @@
"""The decision function (canon A3).
`decide` is pure: it reads its inputs, never mutates them, holds no state and calls no
model. The same request, envelope, registry, tenant context and memory always produce
the same Decision.
"""
from __future__ import annotations
from typing import Iterable
from emergent_security.constitution import PLATFORM_TENANT_BOUND, ROOT
from emergent_security.delegation import Delegation, Grant, select_delegation
from emergent_security.envelope import DefaultEnvelope, Row
from emergent_security.memory import MemoryEntry
from emergent_security.model import Decision, Outcome, Request
from emergent_security.scales import RiskScales
from emergent_security.tenant import TenantContext
REQUIRED_SCOPE = ("level", "temperature", "phase", "tenant")
def decide(
request: Request,
*,
envelope: DefaultEnvelope,
scales: RiskScales,
registry: Iterable[Delegation] = (),
tenant: TenantContext | None = None,
memory: Iterable[MemoryEntry] = (),
) -> Decision:
"""Evaluate one privileged effect; the first matching step decides."""
def escalate(condition: str, route_to: str, **fields) -> Decision:
return Decision(request.request_id, request.decision_class, Outcome.ESCALATE,
blocking_condition=condition, route_to=route_to, **fields)
# 1. scope unresolved
unresolved = _unresolved_scope(request)
if unresolved:
return escalate("need_context", "delegator", missing=unresolved)
row = envelope.row(request.decision_class, request.attributes)
# 2. constitution or explicit prohibition
if row is not None and row.mode is Outcome.DENY:
return _deny(request, row.rules, envelope.id)
if tenant is not None and tenant.prohibition(request.decision_class):
return _deny(request, tenant.prohibition(request.decision_class), tenant.platform.id)
# 3. platform-root
if request.scope.get("level") == "L4" or (row is not None and row.mode is Outcome.ROOT_AUTHORIZATION_REQUIRED):
return Decision(request.request_id, request.decision_class, Outcome.ROOT_AUTHORIZATION_REQUIRED,
route_to="root_steward", rules=(ROOT,), source=envelope.id)
# 4. outside the platform tenant envelope
if tenant is not None:
outside = tenant.outside(request, scales)
if outside:
return escalate("outside_tenant_envelope", "platform", rules=(PLATFORM_TENANT_BOUND,), missing=outside)
route = "platform" if tenant is not None else "delegator"
# 5. delegation within the effective envelope
grant = _grant(request, row, envelope, registry, tenant, scales)
if grant is None:
return escalate("no_delegation", route, rules=row.rules if row else ())
# 6. negative memory
if any(entry.blocks(request) for entry in memory):
return escalate("negative_memory", route, source=grant.source)
# 7. evidence
missing_evidence = tuple(item for item in grant.evidence_required if item not in request.evidence)
if missing_evidence:
return escalate("missing_evidence", route, missing=missing_evidence, source=grant.source)
# 8. risk against the envelope's declared dimensions
exceeded = scales.exceeded(request.risk, grant.risk_envelope)
if exceeded:
return escalate("risk", route, missing=exceeded, source=grant.source)
# 9. tenant reserved the class for a human decision
if tenant is not None and tenant.requires_human(request.decision_class):
return escalate("tenant_human_decision", "tenant_admin", source=grant.source)
# 10. autonomous
return Decision(request.request_id, request.decision_class, grant.outcome,
rules=grant.rules, obligations=grant.obligations, source=grant.source)
def _unresolved_scope(request: Request) -> tuple[str, ...]:
missing = [name for name in ("principal", "decision_class") if not getattr(request, name)]
missing += [f"scope.{key}" for key in REQUIRED_SCOPE if request.scope.get(key) in (None, "")]
return tuple(missing)
def _deny(request: Request, rules: tuple[str, ...], source: str) -> Decision:
return Decision(request.request_id, request.decision_class, Outcome.DENY, rules=rules, source=source)
def _grant(
request: Request,
row: Row | None,
envelope: DefaultEnvelope,
registry: Iterable[Delegation],
tenant: TenantContext | None,
scales: RiskScales,
) -> Grant | None:
"""Registry first, then the platform tenant envelope, then the default envelope (A10)."""
delegation = select_delegation(registry, request)
if delegation is not None:
grant = delegation.as_grant()
elif tenant is not None:
grant = tenant.platform.grant_for(request.decision_class)
elif row is not None and row.mode is not None and row.mode.grants and envelope.applies_to(request.scope):
grant = Grant(source=envelope.id, outcome=row.mode, obligations=row.obligations,
evidence_required=row.evidence_required, risk_envelope=envelope.risk_envelope, rules=row.rules)
else:
return None
return grant.bounded_by(tenant.ceilings(scales), scales) if tenant is not None and grant else grant

View file

@ -0,0 +1,75 @@
"""Registry delegations (canon B1) and the Grant every delegation source resolves to."""
from __future__ import annotations
from dataclasses import dataclass, replace
from typing import Any, Iterable, Mapping
from emergent_security.model import Outcome, Request, frozen_mapping, frozen_tuple
from emergent_security.scales import RiskScales
@dataclass(frozen=True)
class Grant:
"""What a delegation source allows for one request; source-agnostic input to A3 steps 6-10."""
source: str
outcome: Outcome
obligations: tuple[str, ...] = ()
evidence_required: tuple[str, ...] = ()
risk_envelope: Mapping[str, Any] = frozen_mapping(None)
rules: tuple[str, ...] = ()
def bounded_by(self, ceilings: Mapping[str, Any], scales: RiskScales) -> Grant:
return replace(self, risk_envelope=frozen_mapping(scales.intersect(self.risk_envelope, ceilings)))
@dataclass(frozen=True)
class Delegation:
id: str
decision_class: str
scope: Mapping[str, Any]
mode_name: str
obligations: tuple[str, ...]
evidence_required: tuple[str, ...]
risk_envelope: Mapping[str, Any]
@classmethod
def from_dict(cls, data: Mapping[str, Any]) -> Delegation:
return cls(
id=str(data["id"]),
decision_class=str(data["decision_class"]),
scope=frozen_mapping(data.get("scope")),
mode_name=str(data.get("mode")),
obligations=frozen_tuple(data.get("obligations")),
evidence_required=frozen_tuple(data.get("evidence_required")),
risk_envelope=frozen_mapping(data.get("risk_envelope")),
)
@property
def specificity(self) -> int:
return len(self.scope)
def covers(self, request: Request) -> bool:
outcome = Outcome.from_mode(self.mode_name)
return (
outcome is not None
and outcome.grants
and self.decision_class == request.decision_class
and all(request.scope.get(key) == value for key, value in self.scope.items())
)
def as_grant(self) -> Grant:
return Grant(
source=self.id,
outcome=Outcome.from_mode(self.mode_name),
obligations=self.obligations,
evidence_required=self.evidence_required,
risk_envelope=self.risk_envelope,
)
def select_delegation(registry: Iterable[Delegation], request: Request) -> Delegation | None:
"""Most specific covering delegation; ties broken by id so registry order never matters."""
candidates = sorted((d for d in registry if d.covers(request)), key=lambda d: (-d.specificity, d.id))
return candidates[0] if candidates else None

View file

@ -0,0 +1,152 @@
"""The default envelope (canon A6): which decision classes an L0 agent may decide alone."""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Mapping
import yaml
from emergent_security.constitution import RULE_IDS
from emergent_security.model import Outcome, frozen_mapping, frozen_tuple, same_value
from emergent_security.scales import RiskScales
@dataclass(frozen=True)
class Row:
"""A class rule resolved against a request's attributes."""
class_id: str
mode_name: str
rules: tuple[str, ...]
obligations: tuple[str, ...]
evidence_required: tuple[str, ...]
@property
def mode(self) -> Outcome | None:
return Outcome.from_mode(self.mode_name)
@property
def constitutional(self) -> bool:
"""Deny and root rows apply in every scope and no delegation overrides them."""
return self.mode in (Outcome.DENY, Outcome.ROOT_AUTHORIZATION_REQUIRED)
@dataclass(frozen=True)
class Variant:
when: Mapping[str, Any]
mode_name: str
rules: tuple[str, ...] | None
obligations: tuple[str, ...] | None
evidence_required: tuple[str, ...] | None
@classmethod
def from_dict(cls, data: Mapping[str, Any]) -> Variant:
def optional(key: str) -> tuple[str, ...] | None:
return frozen_tuple(data[key]) if key in data else None
return cls(
when=frozen_mapping(data.get("when")),
mode_name=str(data.get("mode")),
rules=optional("rules"),
obligations=optional("obligations"),
evidence_required=optional("evidence_required"),
)
def matches(self, attributes: Mapping[str, Any]) -> bool:
return all(same_value(attributes.get(key), value) for key, value in self.when.items())
@dataclass(frozen=True)
class ClassRule:
id: str
title: str
mode_name: str
rules: tuple[str, ...]
obligations: tuple[str, ...]
evidence_required: tuple[str, ...]
variants: tuple[Variant, ...]
@classmethod
def from_dict(cls, data: Mapping[str, Any]) -> ClassRule:
return cls(
id=str(data["id"]),
title=str(data.get("title", "")),
mode_name=str(data.get("mode")),
rules=frozen_tuple(data.get("rules")),
obligations=frozen_tuple(data.get("obligations")),
evidence_required=frozen_tuple(data.get("evidence_required")),
variants=tuple(Variant.from_dict(v) for v in data.get("variants") or ()),
)
def resolve(self, attributes: Mapping[str, Any]) -> Row:
"""First matching variant replaces the base mode and any fields it sets."""
base = Row(self.id, self.mode_name, self.rules, self.obligations, self.evidence_required)
for variant in self.variants:
if variant.matches(attributes):
return Row(
class_id=self.id,
mode_name=variant.mode_name,
rules=base.rules if variant.rules is None else variant.rules,
obligations=base.obligations if variant.obligations is None else variant.obligations,
evidence_required=base.evidence_required if variant.evidence_required is None else variant.evidence_required,
)
return base
@dataclass(frozen=True)
class DefaultEnvelope:
id: str
delegator: str
applies_when: Mapping[str, Any]
risk_envelope: Mapping[str, Any]
classes: tuple[ClassRule, ...]
@classmethod
def from_dict(cls, data: Mapping[str, Any]) -> DefaultEnvelope:
return cls(
id=str(data["id"]),
delegator=str(data.get("delegator", "task_assigner")),
applies_when=frozen_mapping(data.get("applies_when")),
risk_envelope=frozen_mapping(data.get("risk_envelope")),
classes=tuple(ClassRule.from_dict(c) for c in data.get("classes") or ()),
)
@classmethod
def load(cls, path: Path) -> DefaultEnvelope:
with Path(path).open(encoding="utf-8") as handle:
return cls.from_dict(yaml.safe_load(handle))
@property
def class_ids(self) -> tuple[str, ...]:
return tuple(rule.id for rule in self.classes)
def _class(self, class_id: str | None) -> ClassRule | None:
return next((rule for rule in self.classes if rule.id == class_id), None)
def row(self, class_id: str | None, attributes: Mapping[str, Any] | None = None) -> Row | None:
rule = self._class(class_id)
return rule.resolve(attributes or {}) if rule else None
def all_rules(self, class_id: str) -> set[str]:
rule = self._class(class_id)
if rule is None:
return set()
return set(rule.rules).union(*(v.rules or () for v in rule.variants))
def applies_to(self, scope: Mapping[str, Any]) -> bool:
return all(scope.get(key) == value for key, value in self.applies_when.items())
def validate(self, scales: RiskScales) -> list[str]:
errors: list[str] = []
seen: set[str] = set()
for rule in self.classes:
if rule.id in seen:
errors.append(f"duplicate class id: {rule.id}")
seen.add(rule.id)
modes = [rule.mode_name, *(v.mode_name for v in rule.variants)]
errors += [f"{rule.id}: unknown mode {m}" for m in modes if Outcome.from_mode(m) is None]
errors += [f"{rule.id}: unknown rule {r}" for r in sorted(self.all_rules(rule.id) - RULE_IDS)]
errors += scales.check(self.risk_envelope, f"{self.id} risk_envelope")
return errors

View file

@ -0,0 +1,33 @@
"""Security memory (canon B5). Memory informs; negative memory blocks until cleared (C10)."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Mapping
from emergent_security.model import Request, frozen_mapping
@dataclass(frozen=True)
class MemoryEntry:
decision_class: str
scope: Mapping[str, Any]
polarity: str
cleared: bool = False
@classmethod
def from_dict(cls, data: Mapping[str, Any]) -> MemoryEntry:
return cls(
decision_class=str(data["decision_class"]),
scope=frozen_mapping(data.get("scope")),
polarity=str(data.get("polarity", "positive")),
cleared=bool(data.get("cleared", False)),
)
def blocks(self, request: Request) -> bool:
return (
self.polarity == "negative"
and not self.cleared
and self.decision_class == request.decision_class
and all(request.scope.get(key) == value for key, value in self.scope.items())
)

View file

@ -0,0 +1,107 @@
"""Pure data types shared by every module: the request, the decision and its outcome."""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
from types import MappingProxyType
from typing import Any, Iterable, Mapping
def same_value(left: Any, right: Any) -> bool:
"""Type-strict equality, so that YAML `true` never matches `1` or `"true"`."""
return type(left) is type(right) and left == right
def frozen_mapping(value: Mapping[str, Any] | None) -> Mapping[str, Any]:
return MappingProxyType(dict(value or {}))
def frozen_tuple(value: Iterable[str] | None) -> tuple[str, ...]:
return tuple(value or ())
class Outcome(str, Enum):
AUTONOMOUS = "AUTONOMOUS"
AUTONOMOUS_WITH_OBLIGATIONS = "AUTONOMOUS_WITH_OBLIGATIONS"
ESCALATE = "ESCALATE"
DENY = "DENY"
ROOT_AUTHORIZATION_REQUIRED = "ROOT_AUTHORIZATION_REQUIRED"
@classmethod
def from_mode(cls, mode: Any) -> Outcome | None:
"""Map an envelope `mode` string to an Outcome; None for unknown modes."""
try:
return cls(str(mode).upper())
except ValueError:
return None
@property
def grants(self) -> bool:
return self in (Outcome.AUTONOMOUS, Outcome.AUTONOMOUS_WITH_OBLIGATIONS)
@property
def effect(self) -> str:
if self.grants:
return "allow"
if self is Outcome.DENY:
return "deny"
return "escalate"
@dataclass(frozen=True)
class Request:
"""A privileged effect an agent intends, described independently of any credential (A4)."""
request_id: str
principal: str | None
decision_class: str | None
scope: Mapping[str, Any]
attributes: Mapping[str, Any]
risk: Mapping[str, Any]
evidence: frozenset[str]
@classmethod
def from_dict(cls, data: Mapping[str, Any]) -> Request:
return cls(
request_id=str(data.get("request_id", "")),
principal=data.get("principal"),
decision_class=data.get("decision_class"),
scope=frozen_mapping(data.get("scope")),
attributes=frozen_mapping(data.get("attributes")),
risk=frozen_mapping(data.get("risk")),
evidence=frozenset(data.get("evidence") or ()),
)
@dataclass(frozen=True)
class Decision:
"""Decision record (canon B2). `missing` lists what stands between the request and an allow."""
request_id: str
decision_class: str | None
outcome: Outcome
blocking_condition: str | None = None
route_to: str | None = None
rules: tuple[str, ...] = ()
obligations: tuple[str, ...] = ()
missing: tuple[str, ...] = ()
source: str | None = None
@property
def effect(self) -> str:
return self.outcome.effect
def to_dict(self) -> dict[str, Any]:
return {
"request_id": self.request_id,
"decision_class": self.decision_class,
"outcome": self.outcome.value,
"effect": self.effect,
"blocking_condition": self.blocking_condition,
"route_to": self.route_to,
"rules": list(self.rules),
"obligations": list(self.obligations),
"missing": list(self.missing),
"source": self.source,
}

View file

@ -0,0 +1,81 @@
"""Ordinal risk scales (canon B1 risk dimensions, A3 step 8).
Every comparison fails safe: a value that cannot be ranked is treated as exceeding.
"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Mapping
import yaml
from emergent_security.model import same_value
@dataclass(frozen=True)
class RiskScales:
scales: Mapping[str, tuple[Any, ...]]
@classmethod
def from_dict(cls, data: Mapping[str, Any]) -> RiskScales:
return cls(scales={dim: tuple(levels) for dim, levels in data["scales"].items()})
@classmethod
def load(cls, path: Path) -> RiskScales:
with Path(path).open(encoding="utf-8") as handle:
return cls.from_dict(yaml.safe_load(handle))
@property
def dimensions(self) -> tuple[str, ...]:
return tuple(self.scales)
def rank(self, dimension: str, value: Any) -> int | None:
for index, level in enumerate(self.scales.get(dimension, ())):
if same_value(level, value):
return index
return None
def within(self, dimension: str, value: Any, ceiling: Any) -> bool:
value_rank, ceiling_rank = self.rank(dimension, value), self.rank(dimension, ceiling)
return value_rank is not None and ceiling_rank is not None and value_rank <= ceiling_rank
def narrower(self, dimension: str, left: Any, right: Any) -> Any:
"""The more restrictive of two ceilings; an unrankable value wins so that checks fail safe."""
left_rank, right_rank = self.rank(dimension, left), self.rank(dimension, right)
if left_rank is None:
return left
if right_rank is None:
return right
return left if left_rank <= right_rank else right
def intersect(self, *ceilings: Mapping[str, Any]) -> dict[str, Any]:
result: dict[str, Any] = {}
for mapping in ceilings:
for dimension, value in mapping.items():
result[dimension] = self.narrower(dimension, result[dimension], value) if dimension in result else value
return result
def exceeded(self, risk: Mapping[str, Any], ceilings: Mapping[str, Any], *, declared_only: bool = False) -> tuple[str, ...]:
"""Dimensions where `risk` is above `ceilings`, in scale order.
By default a dimension the ceilings declare but the request does not classify
counts as exceeded (A3 step 8). With declared_only, only dimensions the request
states are checked.
"""
ordered = [d for d in self.dimensions if d in ceilings] + sorted(d for d in ceilings if d not in self.scales)
return tuple(
dimension
for dimension in ordered
if not (declared_only and risk.get(dimension) is None)
and not self.within(dimension, risk.get(dimension), ceilings[dimension])
)
def check(self, mapping: Mapping[str, Any], where: str) -> list[str]:
"""Validation errors for a ceilings mapping."""
return [
f"{where}: unknown value for {dimension}: {value}"
for dimension, value in mapping.items()
if self.rank(dimension, value) is None
]

View file

@ -0,0 +1,168 @@
"""Platform tenant envelopes (B10), tenant governance (B11) and their invariants (C14, G7).
The platform envelope is the outer bound. Tenant governance can only narrow it:
effective envelope = platform tenant envelope ∩ tenant governance envelope.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Mapping
from emergent_security.constitution import PLATFORM_TENANT_BOUND, TENANT_GOVERNANCE
from emergent_security.delegation import Grant
from emergent_security.model import Outcome, Request, frozen_mapping, frozen_tuple
from emergent_security.scales import RiskScales
GOVERNANCE_MODES = ("auto", "self_governed")
DEADLINE_ACTIONS = ("escalate_to_next_admin", "escalate_to_platform") # never auto-approve
GOVERNANCE_FIELDS = frozenset({
"apiVersion", "kind", "tenant", "mode", "bounded_by", "established_by", "restrictions",
"human_decision_classes", "escalation_target", "deadline_default", "on_deadline",
})
RESTRICTION_FIELDS = frozenset({"prohibitions", "ceilings"})
@dataclass(frozen=True)
class PlatformTenantEnvelope:
id: str
tenant: str
capabilities: tuple[str, ...]
delegated: Mapping[str, Mapping[str, Any]]
ceilings: Mapping[str, Any]
prohibitions: tuple[str, ...]
@classmethod
def from_dict(cls, data: Mapping[str, Any]) -> PlatformTenantEnvelope:
return cls(
id=str(data["id"]),
tenant=str(data["tenant"]),
capabilities=frozen_tuple(data.get("capabilities")),
delegated=frozen_mapping({k: frozen_mapping(v) for k, v in (data.get("delegated") or {}).items()}),
ceilings=frozen_mapping(data.get("ceilings")),
prohibitions=frozen_tuple(data.get("prohibitions")),
)
def grant_for(self, class_id: str | None) -> Grant | None:
spec = self.delegated.get(class_id) if class_id else None
outcome = Outcome.from_mode(spec.get("mode")) if spec else None
if outcome is None or not outcome.grants:
return None
return Grant(
source=self.id,
outcome=outcome,
obligations=frozen_tuple(spec.get("obligations")),
evidence_required=frozen_tuple(spec.get("evidence_required")),
risk_envelope=self.ceilings,
)
def validate(self, scales: RiskScales) -> list[str]:
errors = [f"delegated class outside capabilities: {c}" for c in self.delegated if c not in self.capabilities]
errors += [f"both delegated and prohibited: {c}" for c in self.delegated if c in self.prohibitions]
errors += [
f"{c}: delegated mode must grant: {spec.get('mode')}"
for c, spec in self.delegated.items()
if not (Outcome.from_mode(spec.get("mode")) or Outcome.DENY).grants
]
return errors + scales.check(self.ceilings, f"{self.id} ceilings")
@dataclass(frozen=True)
class TenantGovernance:
tenant: str
mode: str
bounded_by: str
prohibitions: tuple[str, ...]
ceilings: Mapping[str, Any]
human_decision_classes: tuple[str, ...]
@classmethod
def from_dict(cls, data: Mapping[str, Any]) -> TenantGovernance:
restrictions = data.get("restrictions") or {}
return cls(
tenant=str(data.get("tenant")),
mode=str(data.get("mode", "auto")),
bounded_by=str(data.get("bounded_by")),
prohibitions=frozen_tuple(restrictions.get("prohibitions")),
ceilings=frozen_mapping(restrictions.get("ceilings")),
human_decision_classes=frozen_tuple(data.get("human_decision_classes")),
)
@property
def self_governed(self) -> bool:
return self.mode == "self_governed"
def validate_tenant_governance(
data: Mapping[str, Any], platform: PlatformTenantEnvelope, scales: RiskScales
) -> list[str]:
"""All reasons a tenant governance document would widen, or fail to bound, the platform envelope.
Works on the raw document so that unknown (widening) fields are caught before parsing.
A non-empty result means the document is rejected as a whole.
"""
errors = [f"field not allowed: {k} (tenant governance may only narrow)" for k in sorted(set(data) - GOVERNANCE_FIELDS)]
mode = data.get("mode")
restrictions = data.get("restrictions") or {}
human_classes = data.get("human_decision_classes") or []
if mode not in GOVERNANCE_MODES:
errors.append(f"mode must be one of {', '.join(GOVERNANCE_MODES)}: {mode}")
if data.get("tenant") != platform.tenant:
errors.append(f"tenant does not match platform envelope: {data.get('tenant')}")
if data.get("bounded_by") != platform.id:
errors.append(f"bounded_by does not match platform envelope {platform.id}: {data.get('bounded_by')}")
if not str(data.get("established_by", "")).startswith("human://"):
errors.append(f"established_by must be a human principal: {data.get('established_by')}")
if data.get("on_deadline") not in DEADLINE_ACTIONS:
errors.append(f"on_deadline must be one of {', '.join(DEADLINE_ACTIONS)}: {data.get('on_deadline')}")
if mode == "auto" and (restrictions or human_classes):
errors.append("auto mode carries no restrictions; use self_governed")
errors += [f"restriction not allowed: {k}" for k in sorted(set(restrictions) - RESTRICTION_FIELDS)]
for dimension, value in (restrictions.get("ceilings") or {}).items():
if scales.rank(dimension, value) is None:
errors.append(f"unknown value for {dimension}: {value}")
elif dimension in platform.ceilings and not scales.within(dimension, value, platform.ceilings[dimension]):
errors.append(f"ceiling {dimension}={value} exceeds platform ceiling {platform.ceilings[dimension]}")
errors += [f"human decision class outside platform capabilities: {c}" for c in human_classes if c not in platform.capabilities]
return errors
def effective_ceilings(
platform: PlatformTenantEnvelope, governance: TenantGovernance | None, scales: RiskScales
) -> dict[str, Any]:
"""Recomputed on every call, so platform narrowing propagates and tenant restrictions persist."""
if governance is None or not governance.self_governed:
return dict(platform.ceilings)
return scales.intersect(platform.ceilings, governance.ceilings)
@dataclass(frozen=True)
class TenantContext:
platform: PlatformTenantEnvelope
governance: TenantGovernance | None = None
@property
def self_governed(self) -> bool:
return self.governance is not None and self.governance.self_governed
def prohibition(self, class_id: str | None) -> tuple[str, ...]:
"""Rule ids of an explicit prohibition on this class, or () if none."""
if class_id in self.platform.prohibitions:
return (PLATFORM_TENANT_BOUND,)
if self.self_governed and class_id in self.governance.prohibitions:
return (TENANT_GOVERNANCE,)
return ()
def outside(self, request: Request, scales: RiskScales) -> tuple[str, ...]:
"""What places the request outside the platform tenant envelope (A3 step 4); () if inside."""
if request.decision_class not in self.platform.capabilities:
return (f"capability:{request.decision_class}",)
return scales.exceeded(request.risk, self.platform.ceilings, declared_only=True)
def ceilings(self, scales: RiskScales) -> dict[str, Any]:
return effective_ceilings(self.platform, self.governance, scales)
def requires_human(self, class_id: str | None) -> bool:
return self.self_governed and class_id in self.governance.human_decision_classes