T09: crystallization
A stable agentic realization becomes deterministic code. All four exit criteria met; 163 tests pass. - crystallization.py: trajectory capture, stability assessment requiring the same path across several runs, CrystallizedDriver, pytest codegen - crystallized/test_grant_access.py: generated, runs with no model, carries its lineage in the docstring - descendant preserves the ancestor's oracle set, agrees with it across five lab versions, and still catches a seeded defect - reversibility shown both ways via new M24 (grant endpoint renamed): the frozen descendant fails loudly rather than searching, and the agentic ancestor recovers from the same mutation F-0007 (open): the 54% cost reduction must not be quoted in support of the thesis. The T07 runtime is token-free, so the measured saving is one page fetch, one parse and a two-candidate scoring pass. The saving the concept actually claims - tokens, latency, retry variance - is unmeasured. Together with F-0005 this makes a bounded live-model experiment the highest-value next investment. Assertions in the generated test are imported rather than restated, so it is not fully standalone. Deliberate: paraphrased claims would be a second unverified statement of intent. 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
This commit is contained in:
parent
b9ad259468
commit
eee7722714
28 changed files with 794 additions and 10 deletions
227
tests/test_crystallization.py
Normal file
227
tests/test_crystallization.py
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
"""H-003: a stable agentic realization becomes deterministic code.
|
||||
|
||||
What must be preserved is the judgment, not the path. These tests check that the
|
||||
descendant asserts everything its ancestor did, agrees with it wherever both can
|
||||
run, and costs less — and that it fails loudly rather than searching when the
|
||||
surface moves out from under it.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
from testdriver import Runner, Verdict
|
||||
from testdriver.crystallization import (
|
||||
CrystallizedDriver, Trajectory, assess_stability, capture,
|
||||
generate_test_module,
|
||||
)
|
||||
from scenarios.alice_bob_carol import USE_CASE
|
||||
from scenarios.browser_grant import build_agentic, build_crystallized, lab_server
|
||||
|
||||
RUNS = 4
|
||||
|
||||
|
||||
def agentic_pack(*mutations: str) -> dict:
|
||||
with lab_server(*mutations) as (app, tokens, base_url):
|
||||
world, driver, observer, asset, oracle = build_agentic(app, tokens, base_url)
|
||||
return json.loads(Runner(world, driver, observer, oracle).run(asset).evidence.to_json())
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def stability():
|
||||
return assess_stability([agentic_pack() for _ in range(RUNS)])
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def frozen(stability):
|
||||
assert stability.stable
|
||||
return stability.trajectories
|
||||
|
||||
|
||||
# --- stability ------------------------------------------------------------
|
||||
|
||||
|
||||
def test_one_successful_run_is_not_enough_to_freeze():
|
||||
"""One run proves the agent can find a way, not that the surface has settled."""
|
||||
report = assess_stability([agentic_pack()])
|
||||
assert not report.stable
|
||||
assert "at least" in report.reason
|
||||
|
||||
|
||||
def test_a_moving_surface_is_not_frozen():
|
||||
"""Different paths across runs means the surface is still moving."""
|
||||
packs = [agentic_pack(), agentic_pack("M24"), agentic_pack(), agentic_pack()]
|
||||
report = assess_stability(packs)
|
||||
assert not report.stable
|
||||
assert report.distinct_paths > 1
|
||||
|
||||
|
||||
def test_a_settled_surface_is_frozen(stability):
|
||||
assert stability.stable
|
||||
assert stability.observations == RUNS
|
||||
assert stability.distinct_paths == 1
|
||||
|
||||
|
||||
# --- the descendant -------------------------------------------------------
|
||||
|
||||
|
||||
def run_descendant(frozen, *mutations: str):
|
||||
with lab_server(*mutations) as (app, tokens, base_url):
|
||||
world, driver, observer, asset, oracle = build_crystallized(
|
||||
app, tokens, base_url, frozen
|
||||
)
|
||||
return Runner(world, driver, observer, oracle).run(asset)
|
||||
|
||||
|
||||
def test_the_descendant_runs_with_no_model_and_no_discovery(frozen):
|
||||
result = run_descendant(frozen)
|
||||
assert result.verdict is Verdict.PASS
|
||||
mechanics = [
|
||||
obs.data["mechanics"] for obs in result.evidence.observations
|
||||
if obs.kind == "realization"
|
||||
][0]
|
||||
assert mechanics["runtime"] == "crystallized-driver"
|
||||
assert mechanics["metrics"]["candidates_considered"] == 0
|
||||
assert mechanics["metrics"]["tokens_in"] == 0
|
||||
|
||||
|
||||
def test_the_descendant_preserves_the_ancestors_oracle_set(frozen):
|
||||
ancestor = agentic_pack()
|
||||
descendant = run_descendant(frozen)
|
||||
before = {v["assertion_id"] for v in ancestor["verdicts"]}
|
||||
after = {j.assertion_id for j in descendant.judgments}
|
||||
assert before <= after, f"crystallization dropped {before - after}"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mutation", ["", "M15", "M17", "M16", "M13"])
|
||||
def test_ancestor_and_descendant_agree(frozen, mutation):
|
||||
"""Same verdicts on the same lab version. Disagreement falsifies H-003."""
|
||||
mutations = (mutation,) if mutation else ()
|
||||
ancestor = agentic_pack(*mutations)
|
||||
descendant = run_descendant(frozen, *mutations)
|
||||
before = {(v["assertion_id"], v["step_id"]): v["verdict"] for v in ancestor["verdicts"]}
|
||||
after = {(j.assertion_id, j.step_id): j.verdict.value for j in descendant.judgments}
|
||||
assert before == after
|
||||
|
||||
|
||||
def test_the_descendant_still_catches_a_defect(frozen):
|
||||
"""Crystallization must not trade rigour for speed."""
|
||||
result = run_descendant(frozen, "M17")
|
||||
assert result.verdict is Verdict.FAIL
|
||||
assert result.judgment("c-carol-denied").verdict is Verdict.FAIL
|
||||
|
||||
|
||||
def test_the_descendant_fails_loudly_when_its_path_breaks(frozen):
|
||||
"""A frozen driver must not fall back to searching.
|
||||
|
||||
Falling back would quietly turn a T5 asset into a T1 one and hide the very
|
||||
signal crystallization exists to produce: the surface moved.
|
||||
|
||||
M24 is the reversibility case from `INTENT.md`: the agentic ancestor recovers
|
||||
by reading the form, the frozen descendant cannot, and the right response is
|
||||
to thaw the asset rather than patch the generated file.
|
||||
"""
|
||||
result = run_descendant(frozen, "M24") # the grant endpoint is renamed
|
||||
raised = [
|
||||
obs.data["raised"] for obs in result.evidence.observations
|
||||
if obs.kind == "realization"
|
||||
]
|
||||
assert any(raised), "the frozen path should have stopped working"
|
||||
|
||||
|
||||
def test_lineage_is_retained(frozen):
|
||||
with lab_server() as (app, tokens, base_url):
|
||||
_, _, _, descendant, _ = build_crystallized(app, tokens, base_url, frozen)
|
||||
assert descendant.parent_id == "va-grant-via-browser"
|
||||
assert descendant.maturity == "T5"
|
||||
|
||||
|
||||
# --- cost -----------------------------------------------------------------
|
||||
|
||||
|
||||
def test_the_descendant_costs_less_to_execute(frozen):
|
||||
"""H-003's economic criterion.
|
||||
|
||||
With a scripted runtime the saving is only the page fetch, parse and scoring
|
||||
— real but small. See F-0007: the meaningful comparison needs a live model,
|
||||
and this number is a floor, not the answer.
|
||||
"""
|
||||
def timed(fn, *args):
|
||||
started = time.perf_counter()
|
||||
fn(*args)
|
||||
return time.perf_counter() - started
|
||||
|
||||
with lab_server() as (app, tokens, base_url):
|
||||
agentic = build_agentic(app, tokens, base_url)
|
||||
crystal = build_crystallized(app, tokens, base_url, frozen)
|
||||
agentic_ms = min(timed(lambda: Runner(*agentic[:3], agentic[4]).run(agentic[3]))
|
||||
for _ in range(3))
|
||||
crystal_ms = min(timed(lambda: Runner(*crystal[:3], crystal[4]).run(crystal[3]))
|
||||
for _ in range(3))
|
||||
assert crystal_ms < agentic_ms, (agentic_ms, crystal_ms)
|
||||
|
||||
|
||||
# --- the generated artefact ----------------------------------------------
|
||||
|
||||
|
||||
def test_the_generated_module_carries_its_lineage(frozen, stability):
|
||||
source = generate_test_module(
|
||||
trajectory=frozen[0],
|
||||
action_args={"subject_id": "bob", "permission": "READ"},
|
||||
ancestor_id="va-grant-via-browser", ancestor_maturity="T1",
|
||||
descendant_id="va-grant-crystallized", runs=stability.observations,
|
||||
sut_version="lab-0.2.0-baseline",
|
||||
claims=[c for c in USE_CASE.claims if c.id == "c-bob-reads"],
|
||||
claims_module="scenarios.alice_bob_carol",
|
||||
)
|
||||
assert "ancestor asset : va-grant-via-browser" in source
|
||||
assert f"frozen from : {RUNS} identical realizations" in source
|
||||
assert "No model is involved" in source
|
||||
|
||||
|
||||
def test_the_generated_module_imports_no_agentic_machinery():
|
||||
source = open("crystallized/test_grant_access.py").read()
|
||||
imports = [
|
||||
line for line in source.splitlines()
|
||||
if line.startswith(("import ", "from ")) and "urllib" not in line
|
||||
]
|
||||
assert imports == [
|
||||
"from __future__ import annotations",
|
||||
"from scenarios.alice_bob_carol import _bob_can_read, "
|
||||
"_bob_cannot_write, _carol_cannot_read",
|
||||
], imports
|
||||
|
||||
|
||||
def test_the_generated_module_restates_no_assertions():
|
||||
"""Assertions are imported, never paraphrased.
|
||||
|
||||
A generated test that restates its claims creates a second, unverified
|
||||
statement of intent, and any drift between the two is silent.
|
||||
"""
|
||||
source = open("crystallized/test_grant_access.py").read()
|
||||
assert "from scenarios.alice_bob_carol import" in source
|
||||
|
||||
|
||||
def test_the_ancestor_recovers_where_the_descendant_cannot(frozen):
|
||||
"""The reversibility case, both halves.
|
||||
|
||||
This is what distinguishes crystallization from ordinary code generation: the
|
||||
agentic form is not discarded when the deterministic one is created, because
|
||||
it is the thing that can recover when the surface moves again.
|
||||
"""
|
||||
ancestor = agentic_pack("M24")
|
||||
ancestor_raised = [
|
||||
obs["data"].get("raised") for obs in ancestor["observations"]
|
||||
if obs["kind"] == "realization"
|
||||
]
|
||||
assert not any(ancestor_raised), "the agentic ancestor should have recovered"
|
||||
|
||||
descendant = run_descendant(frozen, "M24")
|
||||
descendant_raised = [
|
||||
obs.data.get("raised") for obs in descendant.evidence.observations
|
||||
if obs.kind == "realization"
|
||||
]
|
||||
assert any(descendant_raised), "the frozen descendant should have failed loudly"
|
||||
Loading…
Add table
Add a link
Reference in a new issue