"""The test-driver lab — the system under test. Grown from the T04 seed (`lab/minimal.py`, now removed). Users belong to tenants, own resources, share them with READ or WRITE permission, and revoke that sharing. Every state change is audited. Two access paths exist, and the distinction is the whole point: * the **enforcement path** (`request`) is what an actor uses. It checks authorization and can therefore be *wrong* — that is where a seeded authorization defect lives. * the **observation channel** (`ObservationChannel`) reads stored state directly and probes enforcement out-of-band. It is the independent channel required by decision D-07. An oracle consulting only stored state would verify test-driver's own reimplementation of the rules rather than the system's enforcement of them. An oracle consulting only enforcement could not notice that record and enforcement disagree. test-driver observes both and treats disagreement as meaningful in itself — that disagreement is the precise signature of an authorization defect which leaves the audit trail looking correct. Mutations are applied by `lab.mutations.build_lab`, never by editing this file. """ from __future__ import annotations import time from dataclasses import dataclass, field from typing import Any, Callable, Literal Permission = Literal["READ", "WRITE"] BASE_VERSION = "lab-0.2.0" class Denied(Exception): """The enforcement path refused the request.""" def __init__(self, reason: str, status: int = 403) -> None: super().__init__(reason) self.status = status @dataclass(slots=True) class AuditRecord: sequence: int event: str actor_id: str resource_id: str | None = None subject_id: str | None = None permission: str | None = None @dataclass(slots=True) class User: id: str tenant_id: str token: str @dataclass(slots=True) class LabApp: """In-process resource-sharing service. Mutation hooks are plain attributes holding callables or flags. A mutation replaces one, so that every mutated build differs from baseline in exactly one named way and the difference is inspectable at runtime. """ version: str = f"{BASE_VERSION}-baseline" applied_mutations: tuple[str, ...] = () users: dict[str, User] = field(default_factory=dict) _tokens: dict[str, str] = field(default_factory=dict) resources: dict[str, dict[str, Any]] = field(default_factory=dict) grants: dict[tuple[str, str], Permission] = field(default_factory=dict) audit: list[AuditRecord] = field(default_factory=list) _seq: int = 0 # --- mutation hooks (baseline behaviour) ---------------------------- may_read: Callable[["LabApp", str, str], bool] | None = None grant_permission_for: Callable[[str | None], Permission] | None = None audit_revoke: bool = True revoke_delay_seconds: float = 0.0 denied_status: int = 403 require_share_acceptance: bool = False enforce_tenant_isolation: bool = True response_id_field: str = "resource_id" latency_seconds: float = 0.0 # --- presentation hooks (surface only; never affect domain semantics) --- ui_share_control: str = "inline" ui_dom_style: str = "flat" ui_labels: str = "plain" ui_field_order: str = "natural" ui_button_element: str = "button" ui_test_ids: str = "stable" ui_field_names: str = "canonical" ui_confirm_revoke: bool = False api_path_style: str = "long" api_grant_path: str = "grant" tenant_sharing_announced: bool = False # -- setup ----------------------------------------------------------- def add_user(self, user_id: str, tenant_id: str = "t-acme") -> str: token = f"tok-{user_id}" self.users[user_id] = User(user_id, tenant_id, token) self._tokens[token] = user_id return token def _audit(self, event: str, actor_id: str, **kw: Any) -> None: self._seq += 1 self.audit.append(AuditRecord(self._seq, event, actor_id, **kw)) def _whoami(self, token: str) -> str: if token not in self._tokens: raise Denied("unknown token", status=401) return self._tokens[token] def tenant_of(self, user_id: str) -> str | None: user = self.users.get(user_id) return user.tenant_id if user else None # -- enforcement path (what actors use) ------------------------------ def request(self, token: str, op: str, **args: Any) -> Any: """The single entry point actors go through. Authorization is enforced here.""" if self.latency_seconds: time.sleep(self.latency_seconds) user_id = self._whoami(token) handler = getattr(self, f"_op_{op}", None) if handler is None: raise Denied(f"unknown operation {op!r}", status=404) return handler(user_id, **args) def _op_create_resource(self, user_id: str, resource_id: str, content: str) -> dict: self.resources[resource_id] = {"owner": user_id, "content": content} self._audit("create", user_id, resource_id=resource_id) return {self.response_id_field: resource_id} def _op_read_resource(self, user_id: str, resource_id: str) -> dict: resource = self.resources.get(resource_id) if resource is None: raise Denied("no such resource", status=404) if not self._may_read(user_id, resource_id): raise Denied("not authorized to read", status=self.denied_status) return {self.response_id_field: resource_id, "content": resource["content"]} def _op_write_resource(self, user_id: str, resource_id: str, content: str) -> dict: resource = self.resources.get(resource_id) if resource is None: raise Denied("no such resource", status=404) if not self._may_write(user_id, resource_id): raise Denied("not authorized to write", status=self.denied_status) resource["content"] = content self._audit("write", user_id, resource_id=resource_id) return {"written": True} def _op_grant( self, user_id: str, resource_id: str, subject_id: str, permission: Permission | None = None, ) -> dict: resource = self.resources.get(resource_id) if resource is None or resource["owner"] != user_id: raise Denied("only the owner may grant") if self.enforce_tenant_isolation: if self.tenant_of(subject_id) != self.tenant_of(user_id): raise Denied("cross-tenant sharing is not permitted") effective = ( self.grant_permission_for(permission) if self.grant_permission_for else (permission or "READ") ) self.grants[(resource_id, subject_id)] = effective if self.require_share_acceptance: self.resources[resource_id].setdefault("pending", set()).add(subject_id) self._audit( "grant", user_id, resource_id=resource_id, subject_id=subject_id, permission=effective, ) return {"granted": effective} def _op_accept_share(self, user_id: str, resource_id: str) -> dict: pending = self.resources.get(resource_id, {}).get("pending") if pending: pending.discard(user_id) self._audit("accept", user_id, resource_id=resource_id, subject_id=user_id) return {"accepted": True} def _op_revoke(self, user_id: str, resource_id: str, subject_id: str) -> dict: resource = self.resources.get(resource_id) if resource is None or resource["owner"] != user_id: raise Denied("only the owner may revoke") if self.revoke_delay_seconds: self.resources[resource_id].setdefault("revoke_after", {})[subject_id] = ( time.monotonic() + self.revoke_delay_seconds ) else: self.grants.pop((resource_id, subject_id), None) if self.audit_revoke: self._audit("revoke", user_id, resource_id=resource_id, subject_id=subject_id) return {"revoked": True} def _may_read(self, user_id: str, resource_id: str) -> bool: """Authorization as the system actually enforces it.""" if self.may_read is not None: return self.may_read(self, user_id, resource_id) return self.baseline_may_read(user_id, resource_id) def _may_write(self, user_id: str, resource_id: str) -> bool: resource = self.resources.get(resource_id) if resource is None: return False if resource["owner"] == user_id: return True return self.grants.get((resource_id, user_id)) == "WRITE" def baseline_may_read(self, user_id: str, resource_id: str) -> bool: resource = self.resources.get(resource_id) if resource is None: return False if resource["owner"] == user_id: return True deadline = resource.get("revoke_after", {}).get(user_id) if deadline is not None and time.monotonic() < deadline: return True # revocation is scheduled but not yet effective if user_id in resource.get("pending", set()): return False return (resource_id, user_id) in self.grants class ObservationChannel: """Independent read access to lab state — decision D-07. Bypasses authorization deliberately. This is the channel test-driver requires of any system under test, and the main integration burden the framework imposes on an adopter. """ def __init__(self, app: LabApp) -> None: self._app = app @property def version(self) -> str: return self._app.version def state_permission(self, user_id: str, resource_id: str) -> str | None: """What the stored record says, independent of any enforcement decision.""" resource = self._app.resources.get(resource_id) if resource is None: return None if resource["owner"] == user_id: return "OWNER" return self._app.grants.get((resource_id, user_id)) def probe_read(self, user_id: str, resource_id: str) -> bool: """Exercise the enforcement path out-of-band and report what it did. This uses the subject's own credentials, which can look like a violation of actor isolation but is not: independence means the *actor's report* is never the evidence. The observer issues its own request and records the raw outcome. No actor is ever asked whether it succeeded. """ user = self._app.users.get(user_id) if user is None: return False try: self._app.request(user.token, "read_resource", resource_id=resource_id) except Denied: return False return True def probe_write(self, user_id: str, resource_id: str) -> bool: """Out-of-band probe of the write path, non-destructive on refusal.""" user = self._app.users.get(user_id) if user is None: return False original = self._app.resources.get(resource_id, {}).get("content") try: self._app.request( user.token, "write_resource", resource_id=resource_id, content=original ) except Denied: return False return True def audit_events(self, resource_id: str) -> list[dict[str, Any]]: return [ { "sequence": r.sequence, "event": r.event, "actor_id": r.actor_id, "subject_id": r.subject_id, "permission": r.permission, } for r in self._app.audit if r.resource_id == resource_id ] def build_baseline() -> tuple[LabApp, dict[str, str]]: """Known initial state, so that runs replay from the same starting point.""" app = LabApp() tokens = {u: app.add_user(u) for u in ("alice", "bob", "carol")} tokens["mallory"] = app.add_user("mallory", tenant_id="t-other") return app, tokens