All four gate criteria met. False Adaptation Rate 0/7 with 12 of 13 mechanical mutations absorbed. 178 tests pass. TD-WP-0002 finished. Fitness loop closed via F-0003: actor isolation was a property of scenarios written to expose it, not of runs. Actors now carry an automatic private marker and the runner examines all of them on every scenario, with two permanent regressions behind it. Compression - six abstractions removed, each declared and never used: Verdict.SUSPICIOUS (a verdict no oracle could emit), Step.expect_refusal, ActorIsolationError, World.seed, EvidencePack.latest, Trajectory.method. F-0008: Temperature may be redundant. Crystallization was built without it ever being consulted; measured stability of realization did the work, and is observed rather than declared. Gated for removal alongside energy.py. INTENT_CHANGED and REALIZATION_FAILED had never run. Both now have purpose-built cases and a test that fails if a seventh outcome is added without one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 1629012@bnt-lap001 Assistant-Session: 78d4fb13-8a1e-474b-87a3-9b9261c49a39
163 lines
6.2 KiB
Python
163 lines
6.2 KiB
Python
"""The reference scenario.
|
|
|
|
Alice owns resource R. Alice grants Bob READ access. Bob can read R.
|
|
Carol cannot read R. Alice revokes Bob's access. Bob can no longer read R.
|
|
|
|
Written as Python rather than YAML on purpose: claims are predicates over
|
|
observations, and a YAML dialect capable of expressing them would be a
|
|
programming language with worse tooling. A declarative surface is a later
|
|
question, once we know which predicates actually recur.
|
|
|
|
Every claim here is `Provenance.HUMAN` — authored from the use case in INTENT.md,
|
|
not derived from watching the lab behave. See D-06.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from testdriver import (
|
|
Actor, Cast, Claim, DirectDriver, Invariant, Oracle, Provenance,
|
|
Scenario, SemanticAction, StateObserver, Step, UseCase,
|
|
VerificationAsset, Watch, World,
|
|
)
|
|
from lab.mutations import ObservationChannel, build_lab
|
|
|
|
RESOURCE = "R"
|
|
API = frozenset({"api"})
|
|
|
|
# --- claims ---------------------------------------------------------------
|
|
# Each reads the independent observation snapshot. None consults an actor.
|
|
|
|
|
|
def _bob_can_read(obs) -> bool:
|
|
return obs["probe_read:bob:R"] is True
|
|
|
|
|
|
def _bob_cannot_read(obs) -> bool:
|
|
return obs["probe_read:bob:R"] is False
|
|
|
|
|
|
def _carol_cannot_read(obs) -> bool:
|
|
return obs["probe_read:carol:R"] is False
|
|
|
|
|
|
def _bob_cannot_write(obs) -> bool:
|
|
"""A READ grant must not confer write.
|
|
|
|
INTENT.md derives exactly this question from the reference use case:
|
|
"Can Bob write when only read permission was granted?" It is part of what
|
|
sharing means, not an extra.
|
|
"""
|
|
return obs["probe_write:bob:R"] is False
|
|
|
|
|
|
def _revocation_is_audited(obs) -> bool:
|
|
"""Revocation must leave a trace.
|
|
|
|
Enforcement being correct is not sufficient. An access change nobody can
|
|
later evidence is a compliance failure even when the access itself is right.
|
|
"""
|
|
return any(event["event"] == "revoke" for event in obs["audit:R"])
|
|
|
|
|
|
def _enforcement_matches_record(obs) -> bool:
|
|
"""Enforcement and stored record must agree about every watched subject.
|
|
|
|
This invariant is what catches an authorization defect that leaves the audit
|
|
trail looking correct: the grant is recorded as revoked, yet the enforcement
|
|
path still allows the read. Neither observation alone would notice.
|
|
"""
|
|
for key, permitted in obs.items():
|
|
if not key.startswith("probe_read:"):
|
|
continue
|
|
recorded = obs.get("state_permission:" + key.removeprefix("probe_read:"))
|
|
if permitted != (recorded is not None):
|
|
return False
|
|
return True
|
|
|
|
|
|
def _audit_is_append_only(obs) -> bool:
|
|
events = obs["audit:R"]
|
|
sequences = [e["sequence"] for e in events]
|
|
return sequences == sorted(sequences)
|
|
|
|
|
|
USE_CASE = UseCase(
|
|
id="uc-share-resource",
|
|
title="Share a resource and revoke the share",
|
|
narrative=(
|
|
"Alice owns resource R. Alice grants Bob READ access. Bob can read R. "
|
|
"Carol cannot read R. Alice revokes Bob's access. "
|
|
"Bob can no longer read R."
|
|
),
|
|
provenance=Provenance.HUMAN,
|
|
source_ref="INTENT.md#first-reference-scenario",
|
|
claims=(
|
|
Claim("c-bob-reads", "Bob can read R after the grant",
|
|
Provenance.HUMAN, _bob_can_read, after_step="s2-grant"),
|
|
Claim("c-carol-denied", "Carol can never read R",
|
|
Provenance.HUMAN, _carol_cannot_read, after_step="s2-grant"),
|
|
Claim("c-bob-cannot-write", "A READ grant does not let Bob write R",
|
|
Provenance.HUMAN, _bob_cannot_write, after_step="s2-grant",
|
|
source_ref="INTENT.md#security-by-use-case-mutation"),
|
|
Claim("c-bob-revoked", "Bob cannot read R after revocation",
|
|
Provenance.HUMAN, _bob_cannot_read, after_step="s3-revoke"),
|
|
Claim("c-revoke-audited", "Revocation is recorded in the audit trail",
|
|
Provenance.HUMAN, _revocation_is_audited, after_step="s3-revoke"),
|
|
),
|
|
invariants=(
|
|
Invariant("i-enforcement-matches-record",
|
|
"Enforcement and the stored record agree for every subject",
|
|
Provenance.HUMAN, _enforcement_matches_record),
|
|
Invariant("i-audit-append-only", "The audit trail is append-only",
|
|
Provenance.HUMAN, _audit_is_append_only),
|
|
),
|
|
)
|
|
|
|
|
|
def build(*mutations: str):
|
|
"""Assemble world, driver, observer and asset from a known initial state.
|
|
|
|
`mutations` names entries from the lab catalogue. The same scenario runs
|
|
unchanged against every lab version — that is the point: the use case does
|
|
not know the implementation moved.
|
|
"""
|
|
lab, tokens = build_lab(*mutations)
|
|
variant = "+".join(mutations) if mutations else "baseline"
|
|
cast = Cast()
|
|
for name in ("alice", "bob", "carol"):
|
|
cast.add(Actor(id=name, display_name=name.title(),
|
|
credentials={"token": tokens[name]}))
|
|
|
|
world = World(id="w-baseline", sut=lab, sut_version=lab.version, cast=cast)
|
|
|
|
scenario = Scenario(
|
|
id="sc-share-resource",
|
|
use_case=USE_CASE,
|
|
variant=variant,
|
|
watches=(Watch("bob", RESOURCE), Watch("carol", RESOURCE)),
|
|
steps=(
|
|
Step("s1-create", "alice", SemanticAction(
|
|
"create_resource",
|
|
{"resource_id": RESOURCE, "content": "the secret"},
|
|
permitted_surfaces=API,
|
|
postcondition=lambda obs: "audit:R" in obs,
|
|
)),
|
|
Step("s2-grant", "alice", SemanticAction(
|
|
"grant_access",
|
|
{"resource_id": RESOURCE, "subject_id": "bob", "permission": "READ"},
|
|
permitted_surfaces=API,
|
|
postcondition=lambda obs: obs["state_permission:bob:R"] == "READ",
|
|
)),
|
|
Step("s3-revoke", "alice", SemanticAction(
|
|
"revoke_access",
|
|
{"resource_id": RESOURCE, "subject_id": "bob"},
|
|
permitted_surfaces=API,
|
|
postcondition=lambda obs: obs["state_permission:bob:R"] is None,
|
|
)),
|
|
),
|
|
)
|
|
|
|
driver = DirectDriver(lab, tokens)
|
|
observer = StateObserver(ObservationChannel(lab), scenario.watches)
|
|
asset = VerificationAsset(id="va-share-resource", scenario=scenario, maturity="T5")
|
|
return world, driver, observer, asset, Oracle()
|