From f56bc9eb1eb105555d267659d38dc3de206c6ed3 Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Fri, 24 Jul 2026 01:55:39 -0400 Subject: [PATCH 1/5] refactor: remove checkpoint persistence and pending replacement flow --- demos/08_llm_replacement_precondition.py | 50 +- demos/09_llm_pending_clarification.py | 47 +- examples/06_step_sequence_and_checkpoint.py | 8 +- src/context_compiler/__init__.py | 2 - src/context_compiler/controller.py | 4 +- src/context_compiler/engine.py | 61 +-- src/context_compiler/repl.py | 23 - .../conformance/api/public-api-v1.json | 40 -- ...import_checkpoint_non_object_rejected.json | 24 - ...eckpoint_unsupported_version_rejected.json | 32 -- ...kpoint_invalid_pending_shape_rejected.json | 34 -- ...nt_invalid_replacement_shape_rejected.json | 40 -- ...thoritative_state_rejected_atomically.json | 49 -- ..._pending_null_clears_existing_pending.json | 51 -- ...ending_absent_clears_existing_pending.json | 50 -- ..._export_checkpoint_json_wrapper_shape.json | 38 -- ...stores_authoritative_state_no_pending.json | 39 -- ...n_round_trip_state_and_pending_parity.json | 37 -- ...heckpoint_json_restore_yes_resolution.json | 37 -- ...rt_checkpoint_json_malformed_rejected.json | 25 - ...heckpoint_json_invalid_shape_rejected.json | 25 - ..._invalid_authoritative_state_rejected.json | 25 - ...son_invalid_payload_is_all_or_nothing.json | 42 -- ...rts_non_mutating_and_restores_pending.json | 24 +- ...s_mutation_but_preserves_live_pending.json | 12 +- ...replace_missing_source_clarify_prompt.json | 16 +- ..._pending_affirmative_normalized_token.json | 4 +- ...010_pending_negative_normalized_token.json | 4 +- .../011_pending_unmatched_reuses_prompt.json | 4 +- ...pending_affirmative_punctuation_token.json | 4 +- ...19_pending_negative_punctuation_token.json | 4 +- .../preview_clarify_no_mutation.json | 9 +- .../expected/contradiction_clarify.json | 28 +- .../expected/premise_lifecycle.json | 60 +-- .../prohibited_replacement_new_clarify.json | 62 +-- .../prohibited_replacement_old_clarify.json | 62 +-- .../expected/replacement_clarify.json | 16 +- .../scenarios/contradiction_clarify.json | 2 +- .../scenarios/premise_lifecycle.json | 2 +- .../prohibited_replacement_new_clarify.json | 2 +- .../prohibited_replacement_old_clarify.json | 2 +- .../scenarios/replacement_clarify.json | 4 +- tests/test_04_grammar_edge_cases.py | 8 +- tests/test_controller.py | 43 +- tests/test_demo_08_09_behavior.py | 8 +- tests/test_engine.py | 474 ++---------------- tests/test_examples_behavior.py | 30 +- tests/test_examples_smoke.py | 2 +- tests/test_fixtures.py | 56 --- tests/test_repl.py | 231 +-------- tests/test_structured_regression.py | 22 +- 51 files changed, 325 insertions(+), 1653 deletions(-) delete mode 100644 tests/fixtures/conformance/checkpoint/001_import_checkpoint_non_object_rejected.json delete mode 100644 tests/fixtures/conformance/checkpoint/002_import_checkpoint_unsupported_version_rejected.json delete mode 100644 tests/fixtures/conformance/checkpoint/003_import_checkpoint_invalid_pending_shape_rejected.json delete mode 100644 tests/fixtures/conformance/checkpoint/004_import_checkpoint_invalid_replacement_shape_rejected.json delete mode 100644 tests/fixtures/conformance/checkpoint/005_import_checkpoint_invalid_authoritative_state_rejected_atomically.json delete mode 100644 tests/fixtures/conformance/checkpoint/006_import_checkpoint_pending_null_clears_existing_pending.json delete mode 100644 tests/fixtures/conformance/checkpoint/007_import_checkpoint_pending_absent_clears_existing_pending.json delete mode 100644 tests/fixtures/conformance/checkpoint/008_export_checkpoint_json_wrapper_shape.json delete mode 100644 tests/fixtures/conformance/checkpoint/009_import_checkpoint_json_restores_authoritative_state_no_pending.json delete mode 100644 tests/fixtures/conformance/checkpoint/010_checkpoint_json_round_trip_state_and_pending_parity.json delete mode 100644 tests/fixtures/conformance/checkpoint/011_pending_clarification_through_checkpoint_json_restore_yes_resolution.json delete mode 100644 tests/fixtures/conformance/checkpoint/012_import_checkpoint_json_malformed_rejected.json delete mode 100644 tests/fixtures/conformance/checkpoint/013_import_checkpoint_json_invalid_shape_rejected.json delete mode 100644 tests/fixtures/conformance/checkpoint/014_import_checkpoint_json_invalid_authoritative_state_rejected.json delete mode 100644 tests/fixtures/conformance/checkpoint/015_import_checkpoint_json_invalid_payload_is_all_or_nothing.json diff --git a/demos/08_llm_replacement_precondition.py b/demos/08_llm_replacement_precondition.py index 254c7ed..9cca704 100644 --- a/demos/08_llm_replacement_precondition.py +++ b/demos/08_llm_replacement_precondition.py @@ -1,6 +1,6 @@ -"""Demo 8: replacement precondition is enforced by authoritative state.""" +"""Demo 8: missing-source replacement applies deterministically from authoritative state.""" -from context_compiler import DECISION_CLARIFY, State, create_engine +from context_compiler import DECISION_UPDATE, State, create_engine from demos.common import ( build_baseline_messages, build_reinjected_messages, @@ -15,7 +15,7 @@ ) from demos.llm_client import complete_messages -DEMO_NAME = "08_replacement_precondition — invalid replacement blocked" +DEMO_NAME = "08_replacement_precondition — missing-source replacement applies deterministically" USER_INPUT = "use podman instead of docker" @@ -61,34 +61,34 @@ def main() -> None: reinjected_output = complete_messages(reinjected_messages) print_model_output("Reinjected-state", reinjected_output) - if decision["kind"] == DECISION_CLARIFY: + if decision["kind"] == DECISION_UPDATE: print_messages("compiler-mediated (full)", []) - mediated_output = f"[no call] clarification required: {decision['prompt_to_user']}" + mediated_output = "[no call] authoritative state applied deterministic replacement update" print_model_output("Compiler-mediated (full)", mediated_output) else: print_messages("compiler-mediated (full)", []) - mediated_output = "[no call] expected clarify was not produced" + mediated_output = "[no call] expected update was not produced" print_model_output("Compiler-mediated (full)", mediated_output) compacted_turns, compacted_state, compacted_prompt = compact_user_turns(user_inputs) - if compacted_prompt is not None: + if compacted_prompt is None: print_messages("compiler-mediated + compact", []) - compact_output = f"[no call] clarification required: {compacted_prompt}" + compact_output = "[no call] compaction preserved deterministic state update" print_model_output("Compiler-mediated + compact", compact_output) else: print_messages("compiler-mediated + compact", []) - compact_output = "[no call] expected clarify was not produced" + compact_output = "[no call] unexpected clarify was produced during compaction" print_model_output("Compiler-mediated + compact", compact_output) - state_unchanged = _is_initial_authoritative_state(engine.state) - compact_state_unchanged = _is_initial_authoritative_state(compacted_state) - no_pending = engine.has_pending_clarification() - compact_pending = compacted_prompt is not None + state_applied = not _is_initial_authoritative_state(engine.state) + compact_state_applied = not _is_initial_authoritative_state(compacted_state) + no_pending = not engine.has_pending_clarification() + compact_no_pending = compacted_prompt is None baseline_has_authoritative_precondition = False reinjected_has_authoritative_precondition = False - compiler_pass = decision["kind"] == DECISION_CLARIFY and state_unchanged and no_pending - compact_pass = compacted_prompt is not None and compact_state_unchanged and compact_pending + compiler_pass = decision["kind"] == DECISION_UPDATE and state_applied and no_pending + compact_pass = compacted_prompt is None and compact_state_applied and compact_no_pending print_host_check( "BASELINE_AUTHORITATIVE_PRECONDITION", @@ -102,12 +102,12 @@ def main() -> None: ) print_host_check( "COMPILER_BLOCKED_INVALID_REPLACEMENT", - yes_no(decision["kind"] == DECISION_CLARIFY), + yes_no(decision["kind"] == DECISION_UPDATE), context="compiler-mediated", ) print_host_check( - "COMPILER_STATE_UNCHANGED", - yes_no(state_unchanged), + "COMPILER_STATE_APPLIED", + yes_no(state_applied), context="compiler-mediated", ) @@ -118,18 +118,18 @@ def main() -> None: compiler_pass=compiler_pass, compiler_compact_pass=compact_pass, expected=( - "invalid replacement should be blocked with clarification and no authoritative state " - "mutation" + "missing-source replacement should deterministically apply the resulting use update " + "without pending continuation" ), actual=( - "compiler blocked invalid replacement and preserved state; baseline and reinjected " - "paths have no authoritative replacement precondition" + "compiler applied deterministic replacement update; baseline and reinjected paths " + "still lack authoritative state enforcement" if compiler_pass and compact_pass - else "compiler did not consistently enforce replacement precondition behavior" + else "compiler did not consistently apply deterministic replacement behavior" ), passed=compiler_pass and compact_pass, - result_pass="invalid replacement precondition enforced deterministically", - result_fail="invalid replacement precondition not enforced deterministically", + result_pass="missing-source replacement applied deterministically", + result_fail="missing-source replacement did not apply deterministically", ) diff --git a/demos/09_llm_pending_clarification.py b/demos/09_llm_pending_clarification.py index 3c0dfc8..e8516a8 100644 --- a/demos/09_llm_pending_clarification.py +++ b/demos/09_llm_pending_clarification.py @@ -1,8 +1,8 @@ -"""Demo 9: invalid replacement does not create core pending continuation.""" +"""Demo 9: missing-source replacement applies without creating pending continuation.""" from context_compiler import ( - DECISION_CLARIFY, DECISION_PASSTHROUGH, + DECISION_UPDATE, POLICY_USE, State, create_engine, @@ -22,7 +22,7 @@ ) from demos.llm_client import complete_messages -DEMO_NAME = "09_pending_clarification_boundary — invalid replacement stays non-pending" +DEMO_NAME = "09_pending_clarification_boundary — missing-source replacement stays non-pending" TURN_1 = "use podman instead of docker" TURN_2 = "maybe" TURN_3 = "yes" @@ -45,13 +45,12 @@ def main() -> None: first = engine.step(TURN_1) print_decision("turn 1", first, engine.state) pending_after_first = engine.has_pending_clarification() - state_unchanged_after_first = _is_initial_authoritative_state(engine.state) - checkpoint_after_first = engine.export_checkpoint() + state_applied_after_first = _has_podman_use(engine.state) second = engine.step(TURN_2) print_decision("turn 2", second, engine.state) pending_after_second = engine.has_pending_clarification() - state_unchanged_after_second = _is_initial_authoritative_state(engine.state) + state_preserved_after_second = _has_podman_use(engine.state) third = engine.step(TURN_3) print_decision("turn 3", third, engine.state) @@ -99,25 +98,23 @@ def main() -> None: compact_output = "[no call] compact replay did not create pending continuation" print_model_output("Compiler-mediated + compact", compact_output) - blocked_initial_mutation = first["kind"] == DECISION_CLARIFY and state_unchanged_after_first - no_pending_after_invalid_replacement = ( - not pending_after_first and checkpoint_after_first["pending"] is None - ) + deterministic_initial_update = first["kind"] == DECISION_UPDATE and state_applied_after_first + no_pending_after_invalid_replacement = not pending_after_first unrelated_followup_passthrough = ( second["kind"] == DECISION_PASSTHROUGH and not pending_after_second - and state_unchanged_after_second + and state_preserved_after_second ) confirmation_token_not_consumed = ( third["kind"] == DECISION_PASSTHROUGH and not pending_after_third ) - deterministic_final_state = not _has_podman_use(engine.state) + deterministic_final_state = _has_podman_use(engine.state) baseline_has_pending_state_machine = False reinjected_has_pending_state_machine = False compiler_pass = ( - blocked_initial_mutation + deterministic_initial_update and no_pending_after_invalid_replacement and unrelated_followup_passthrough and confirmation_token_not_consumed @@ -125,14 +122,14 @@ def main() -> None: ) compact_pass = ( - compacted_prompt is not None - and compacted_turns == [TURN_1] - and _is_initial_authoritative_state(compacted_state) + compacted_prompt is None + and compacted_turns == [TURN_2, TURN_3] + and _has_podman_use(compacted_state) ) print_host_check( - "BLOCKED_INITIAL_MUTATION", - yes_no(blocked_initial_mutation), + "DETERMINISTIC_INITIAL_UPDATE", + yes_no(deterministic_initial_update), context="compiler-mediated", ) print_host_check( @@ -151,7 +148,7 @@ def main() -> None: context="compiler-mediated", ) print_host_check( - "FINAL_POLICY_PODMAN_ABSENT", + "FINAL_POLICY_PODMAN_PRESENT", yes_no(deterministic_final_state), context="compiler-mediated", ) @@ -163,18 +160,18 @@ def main() -> None: compiler_pass=compiler_pass, compiler_compact_pass=compact_pass, expected=( - "invalid replacement should clarify without creating pending continuation, " - "and later yes/no-style input should not resolve a synthesized operation" + "missing-source replacement should apply without creating pending continuation, " + "and later yes/no-style input should remain ordinary passthrough" ), actual=( - "compiler blocked mutation, kept checkpoint pending null, and treated later " - "inputs as ordinary passthrough" + "compiler applied deterministic replacement update and treated later inputs as " + "ordinary passthrough" if compiler_pass and compact_pass else "compiler did not consistently preserve the non-pending replacement boundary" ), passed=compiler_pass and compact_pass, - result_pass="invalid replacement stayed outside core pending continuation", - result_fail="invalid replacement still behaved like core pending continuation", + result_pass="missing-source replacement stayed outside core pending continuation", + result_fail="missing-source replacement still behaved like core pending continuation", ) diff --git a/examples/06_step_sequence_and_checkpoint.py b/examples/06_step_sequence_and_checkpoint.py index c75ce86..245a0c7 100644 --- a/examples/06_step_sequence_and_checkpoint.py +++ b/examples/06_step_sequence_and_checkpoint.py @@ -1,4 +1,4 @@ -"""Example 6: explicit step sequencing and checkpoint restore.""" +"""Example 6: explicit step sequencing and state restore.""" from _util import print_decision_summary, print_state_summary @@ -19,11 +19,11 @@ def main() -> None: print_decision_summary(engine.step(turn)) print() - checkpoint = engine.export_checkpoint_json() + state_json = engine.export_json() restored = create_engine() - restored.import_checkpoint_json(checkpoint) + restored.import_json(state_json) - print("Checkpoint restore keeps authority state:") + print("JSON restore keeps authority state:") print_state_summary(restored.state, "restored state") diff --git a/src/context_compiler/__init__.py b/src/context_compiler/__init__.py index 2b34905..12521f5 100644 --- a/src/context_compiler/__init__.py +++ b/src/context_compiler/__init__.py @@ -29,7 +29,6 @@ is_update, ) from .engine import ( - Checkpoint, Decision, Engine, State, @@ -41,7 +40,6 @@ __version__ = version("context-compiler") __all__ = [ - "Checkpoint", "Decision", "DECISION_CLARIFY", "DECISION_PASSTHROUGH", diff --git a/src/context_compiler/controller.py b/src/context_compiler/controller.py index a5c3513..1cbe9e6 100644 --- a/src/context_compiler/controller.py +++ b/src/context_compiler/controller.py @@ -122,7 +122,7 @@ def step(engine: Engine, user_input: str) -> StepResult: def preview(engine: Engine, user_input: str) -> PreviewResult: - checkpoint = engine.export_checkpoint() + state_json = engine.export_json() state_before = engine.state decision: Decision | None = None @@ -131,7 +131,7 @@ def preview(engine: Engine, user_input: str) -> PreviewResult: decision = engine.step(user_input) state_after = engine.state finally: - engine.import_checkpoint(checkpoint) + engine.import_json(state_json) assert decision is not None assert state_after is not None diff --git a/src/context_compiler/engine.py b/src/context_compiler/engine.py index 722c2b0..e99c69f 100644 --- a/src/context_compiler/engine.py +++ b/src/context_compiler/engine.py @@ -5,7 +5,7 @@ from copy import deepcopy from dataclasses import dataclass from enum import StrEnum -from typing import Literal, NotRequired, TypedDict +from typing import Literal, TypedDict from unicodedata import normalize as unicode_normalize from .const import ( @@ -30,12 +30,6 @@ class State(TypedDict): version: Literal[2] -class Checkpoint(TypedDict): - checkpoint_version: Literal[1] - authoritative_state: State - pending: NotRequired[None] - - class DecisionKind(StrEnum): UPDATE = "update" PASSTHROUGH = "passthrough" @@ -78,7 +72,6 @@ class Action: _AFFIRMATIVE_CONFIRMATIONS = {"yes", "yes please", "yep", "yeah", "sure", "ok", "okay"} _NEGATIVE_CONFIRMATIONS = {"no", "nope", "no thanks"} _TRAILING_CONFIRM_PUNCT_RE = re.compile(r"[.,!?]+$") -_CHECKPOINT_VERSION: Literal[1] = 1 _COMPOUND_DIRECTIVE_PROMPT = ( "Multiple directives are not supported in one input.\nSubmit each directive separately." ) @@ -137,30 +130,6 @@ def export_json(self) -> str: def import_json(self, payload: str) -> None: self._replace_state(_load_state_json(payload)) - def export_checkpoint(self) -> Checkpoint: - # Reuse authoritative export/import path for canonicalized state payload. - authoritative_state = _load_state_json(self.export_json()) - return { - "checkpoint_version": _CHECKPOINT_VERSION, - "authoritative_state": authoritative_state, - "pending": None, - } - - def import_checkpoint(self, payload: Checkpoint) -> None: - state = _load_checkpoint_obj(payload) - self._replace_state(state) - - def export_checkpoint_json(self) -> str: - return json.dumps(self.export_checkpoint(), sort_keys=True, separators=(",", ":")) - - def import_checkpoint_json(self, payload: str) -> None: - try: - raw = json.loads(payload) - except json.JSONDecodeError as exc: - raise ValueError("Invalid JSON payload.") from exc - - self.import_checkpoint(raw) - def step(self, user_input: str) -> Decision: action = _parse_directive(user_input) if action is None: @@ -264,7 +233,7 @@ def _pre_mutation_clarify(self, action: Action) -> Decision | None: f'"{action.new_item}" is currently prohibited.\n' "Submit explicit directive(s) to remove it or use a different item." ) - if old_state != POLICY_USE: + if old_state not in {None, POLICY_USE}: return _clarify( f'"{action.old_item}" is not currently in use.\n' "Replacement requires an active 'use' policy." @@ -464,32 +433,6 @@ def _load_state_obj(raw: object) -> State: } -def _load_checkpoint_obj(raw: object) -> State: - if not isinstance(raw, dict): - raise ValueError("Invalid checkpoint payload.") - - keys = set(raw.keys()) - if keys not in ( - {"checkpoint_version", "authoritative_state"}, - {"checkpoint_version", "authoritative_state", "pending"}, - ): - raise ValueError("Invalid checkpoint payload.") - - checkpoint_version = raw["checkpoint_version"] - if checkpoint_version != _CHECKPOINT_VERSION: - raise ValueError(f"Unsupported checkpoint version: {checkpoint_version!r}") - - authoritative_state = _load_state_obj(raw["authoritative_state"]) - _load_checkpoint_pending_obj(raw.get("pending")) - return authoritative_state - - -def _load_checkpoint_pending_obj(raw: object) -> None: - if raw is None: - return - raise ValueError("Invalid checkpoint payload.") - - def _sanitize_premise_value(value: str) -> str: sanitized = unicode_normalize("NFKC", value) sanitized = sanitized.replace("’", "'").replace("`", "'") diff --git a/src/context_compiler/repl.py b/src/context_compiler/repl.py index 60b31d6..96c1572 100644 --- a/src/context_compiler/repl.py +++ b/src/context_compiler/repl.py @@ -28,7 +28,6 @@ _CLI_HELP_TEXT = """Usage: context-compiler [--help] [--version] [--json] [--initial-state-json | --initial-state-file ] - [--initial-checkpoint-json | --initial-checkpoint-file ] Options: --help Show this help message and exit. @@ -36,10 +35,6 @@ --json Emit machine-readable NDJSON output (non-interactive only) --initial-state-json Initialize authoritative state from exported state JSON text --initial-state-file Initialize authoritative state from UTF-8 state JSON file - --initial-checkpoint-json - Restore runtime continuation from checkpoint JSON text - --initial-checkpoint-file - Restore runtime continuation from UTF-8 checkpoint JSON file """ @@ -219,8 +214,6 @@ def _parse_cli_options(args: list[str]) -> tuple[dict[str, str | bool], str | No value_flags = { "--initial-state-json": "initial_state_json", "--initial-state-file": "initial_state_file", - "--initial-checkpoint-json": "initial_checkpoint_json", - "--initial-checkpoint-file": "initial_checkpoint_file", } idx = 0 @@ -243,15 +236,9 @@ def _parse_cli_options(args: list[str]) -> tuple[dict[str, str | bool], str | No has_state_json = "initial_state_json" in options has_state_file = "initial_state_file" in options - has_checkpoint_json = "initial_checkpoint_json" in options - has_checkpoint_file = "initial_checkpoint_file" in options if has_state_json and has_state_file: return {}, "state preload options are mutually exclusive" - if has_checkpoint_json and has_checkpoint_file: - return {}, "checkpoint preload options are mutually exclusive" - if (has_state_json or has_state_file) and (has_checkpoint_json or has_checkpoint_file): - return {}, "state preload and checkpoint preload are mutually exclusive" return options, None @@ -266,16 +253,6 @@ def _apply_preload_from_options(engine: Engine, options: dict[str, str | bool]) path = options["initial_state_file"] assert isinstance(path, str) engine.import_json(_read_utf8_file(path)) - return - if "initial_checkpoint_json" in options: - raw = options["initial_checkpoint_json"] - assert isinstance(raw, str) - engine.import_checkpoint_json(raw) - return - if "initial_checkpoint_file" in options: - path = options["initial_checkpoint_file"] - assert isinstance(path, str) - engine.import_checkpoint_json(_read_utf8_file(path)) def _has_pending_clarification(engine: Engine) -> bool: diff --git a/tests/fixtures/conformance/api/public-api-v1.json b/tests/fixtures/conformance/api/public-api-v1.json index 77bd820..ed609cd 100644 --- a/tests/fixtures/conformance/api/public-api-v1.json +++ b/tests/fixtures/conformance/api/public-api-v1.json @@ -14,7 +14,6 @@ "exports": { "mode": "exact", "names": [ - "Checkpoint", "Decision", "DECISION_CLARIFY", "DECISION_PASSTHROUGH", @@ -45,9 +44,6 @@ "step" ], "members": { - "Checkpoint": { - "kind": "type" - }, "Decision": { "kind": "type" }, @@ -539,18 +535,6 @@ "public_members": { "mode": "exact", "members": { - "export_checkpoint": { - "kind": "method", - "signature": { - "params": [] - } - }, - "export_checkpoint_json": { - "kind": "method", - "signature": { - "params": [] - } - }, "export_json": { "kind": "method", "signature": { @@ -563,30 +547,6 @@ "params": [] } }, - "import_checkpoint": { - "kind": "method", - "signature": { - "params": [ - { - "name": "payload", - "kind": "POSITIONAL_OR_KEYWORD", - "has_default": false - } - ] - } - }, - "import_checkpoint_json": { - "kind": "method", - "signature": { - "params": [ - { - "name": "payload", - "kind": "POSITIONAL_OR_KEYWORD", - "has_default": false - } - ] - } - }, "import_json": { "kind": "method", "signature": { diff --git a/tests/fixtures/conformance/checkpoint/001_import_checkpoint_non_object_rejected.json b/tests/fixtures/conformance/checkpoint/001_import_checkpoint_non_object_rejected.json deleted file mode 100644 index b9715bf..0000000 --- a/tests/fixtures/conformance/checkpoint/001_import_checkpoint_non_object_rejected.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "id": "checkpoint_import_checkpoint_non_object_rejected", - "kind": "checkpoint", - "initial_state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "action": { - "fn": "import_checkpoint", - "payload": [] - }, - "expected": { - "error": { - "type": "ValueError", - "message_contains": "Invalid checkpoint payload" - }, - "state": { - "premise": null, - "policies": {}, - "version": 2 - } - } -} diff --git a/tests/fixtures/conformance/checkpoint/002_import_checkpoint_unsupported_version_rejected.json b/tests/fixtures/conformance/checkpoint/002_import_checkpoint_unsupported_version_rejected.json deleted file mode 100644 index 2a9f1e0..0000000 --- a/tests/fixtures/conformance/checkpoint/002_import_checkpoint_unsupported_version_rejected.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "id": "checkpoint_import_checkpoint_unsupported_version_rejected", - "kind": "checkpoint", - "initial_state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "action": { - "fn": "import_checkpoint", - "payload": { - "checkpoint_version": 2, - "authoritative_state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "pending": null - } - }, - "expected": { - "error": { - "type": "ValueError", - "message_contains": "Unsupported checkpoint version" - }, - "state": { - "premise": null, - "policies": {}, - "version": 2 - } - } -} diff --git a/tests/fixtures/conformance/checkpoint/003_import_checkpoint_invalid_pending_shape_rejected.json b/tests/fixtures/conformance/checkpoint/003_import_checkpoint_invalid_pending_shape_rejected.json deleted file mode 100644 index 019a42d..0000000 --- a/tests/fixtures/conformance/checkpoint/003_import_checkpoint_invalid_pending_shape_rejected.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "id": "checkpoint_import_checkpoint_invalid_pending_shape_rejected", - "kind": "checkpoint", - "initial_state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "action": { - "fn": "import_checkpoint", - "payload": { - "checkpoint_version": 1, - "authoritative_state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "pending": { - "kind": "replacement" - } - } - }, - "expected": { - "error": { - "type": "ValueError", - "message_contains": "Invalid checkpoint payload" - }, - "state": { - "premise": null, - "policies": {}, - "version": 2 - } - } -} diff --git a/tests/fixtures/conformance/checkpoint/004_import_checkpoint_invalid_replacement_shape_rejected.json b/tests/fixtures/conformance/checkpoint/004_import_checkpoint_invalid_replacement_shape_rejected.json deleted file mode 100644 index 8baed64..0000000 --- a/tests/fixtures/conformance/checkpoint/004_import_checkpoint_invalid_replacement_shape_rejected.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "id": "checkpoint_import_checkpoint_invalid_replacement_shape_rejected", - "kind": "checkpoint", - "initial_state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "action": { - "fn": "import_checkpoint", - "payload": { - "checkpoint_version": 1, - "authoritative_state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "pending": { - "kind": "replacement", - "replacement": { - "kind": "replace_use", - "new_item": "kubectl", - "old_item": "docker" - }, - "prompt_to_user": "confirm?" - } - } - }, - "expected": { - "error": { - "type": "ValueError", - "message_contains": "Invalid checkpoint payload" - }, - "state": { - "premise": null, - "policies": {}, - "version": 2 - } - } -} diff --git a/tests/fixtures/conformance/checkpoint/005_import_checkpoint_invalid_authoritative_state_rejected_atomically.json b/tests/fixtures/conformance/checkpoint/005_import_checkpoint_invalid_authoritative_state_rejected_atomically.json deleted file mode 100644 index c0fbf37..0000000 --- a/tests/fixtures/conformance/checkpoint/005_import_checkpoint_invalid_authoritative_state_rejected_atomically.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "id": "checkpoint_import_checkpoint_invalid_authoritative_state_rejected_atomically", - "kind": "checkpoint", - "initial_state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "prelude": [ - "use kubectl instead of docker" - ], - "action": { - "fn": "import_checkpoint", - "payload": { - "checkpoint_version": 1, - "authoritative_state": { - "premise": null, - "policies": [], - "version": 2 - }, - "pending": null - } - }, - "expected": { - "error": { - "type": "ValueError", - "message_contains": "Invalid state payload" - }, - "has_pending_clarification": false, - "state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "followup": { - "input": "maybe", - "decision": { - "kind": "passthrough", - "state": null, - "prompt_to_user": null - }, - "state": { - "premise": null, - "policies": {}, - "version": 2 - } - } - } -} diff --git a/tests/fixtures/conformance/checkpoint/006_import_checkpoint_pending_null_clears_existing_pending.json b/tests/fixtures/conformance/checkpoint/006_import_checkpoint_pending_null_clears_existing_pending.json deleted file mode 100644 index f0ab567..0000000 --- a/tests/fixtures/conformance/checkpoint/006_import_checkpoint_pending_null_clears_existing_pending.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "id": "checkpoint_import_checkpoint_pending_null_clears_existing_pending", - "kind": "checkpoint", - "initial_state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "prelude": [ - "use kubectl instead of docker" - ], - "action": { - "fn": "import_checkpoint", - "payload": { - "checkpoint_version": 1, - "authoritative_state": { - "premise": "baseline", - "policies": { - "pytest": "use" - }, - "version": 2 - }, - "pending": null - } - }, - "expected": { - "has_pending_clarification": false, - "state": { - "premise": "baseline", - "policies": { - "pytest": "use" - }, - "version": 2 - }, - "followup": { - "input": "yes", - "decision": { - "kind": "passthrough", - "state": null, - "prompt_to_user": null - }, - "state": { - "premise": "baseline", - "policies": { - "pytest": "use" - }, - "version": 2 - } - } - } -} diff --git a/tests/fixtures/conformance/checkpoint/007_import_checkpoint_pending_absent_clears_existing_pending.json b/tests/fixtures/conformance/checkpoint/007_import_checkpoint_pending_absent_clears_existing_pending.json deleted file mode 100644 index f8f0bfc..0000000 --- a/tests/fixtures/conformance/checkpoint/007_import_checkpoint_pending_absent_clears_existing_pending.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "id": "checkpoint_import_checkpoint_pending_absent_clears_existing_pending", - "kind": "checkpoint", - "initial_state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "prelude": [ - "use kubectl instead of docker" - ], - "action": { - "fn": "import_checkpoint", - "payload": { - "checkpoint_version": 1, - "authoritative_state": { - "premise": "baseline", - "policies": { - "pytest": "use" - }, - "version": 2 - } - } - }, - "expected": { - "has_pending_clarification": false, - "state": { - "premise": "baseline", - "policies": { - "pytest": "use" - }, - "version": 2 - }, - "followup": { - "input": "yes", - "decision": { - "kind": "passthrough", - "state": null, - "prompt_to_user": null - }, - "state": { - "premise": "baseline", - "policies": { - "pytest": "use" - }, - "version": 2 - } - } - } -} diff --git a/tests/fixtures/conformance/checkpoint/008_export_checkpoint_json_wrapper_shape.json b/tests/fixtures/conformance/checkpoint/008_export_checkpoint_json_wrapper_shape.json deleted file mode 100644 index 869bc6c..0000000 --- a/tests/fixtures/conformance/checkpoint/008_export_checkpoint_json_wrapper_shape.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "id": "checkpoint_export_checkpoint_json_wrapper_shape", - "kind": "checkpoint", - "initial_state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "prelude": [ - "set premise concise replies", - "use docker" - ], - "action": { - "fn": "export_checkpoint_json" - }, - "expected": { - "payload_json_parseable": true, - "payload_object": { - "checkpoint_version": 1, - "authoritative_state": { - "premise": "concise replies", - "policies": { - "docker": "use" - }, - "version": 2 - }, - "pending": null - }, - "has_pending_clarification": false, - "state": { - "premise": "concise replies", - "policies": { - "docker": "use" - }, - "version": 2 - } - } -} diff --git a/tests/fixtures/conformance/checkpoint/009_import_checkpoint_json_restores_authoritative_state_no_pending.json b/tests/fixtures/conformance/checkpoint/009_import_checkpoint_json_restores_authoritative_state_no_pending.json deleted file mode 100644 index 05e43f1..0000000 --- a/tests/fixtures/conformance/checkpoint/009_import_checkpoint_json_restores_authoritative_state_no_pending.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "id": "checkpoint_import_checkpoint_json_restores_authoritative_state_no_pending", - "kind": "checkpoint", - "initial_state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "action": { - "fn": "import_checkpoint_json", - "payload": "{\"authoritative_state\":{\"policies\":{\"pytest\":\"use\"},\"premise\":\"baseline\",\"version\":2},\"checkpoint_version\":1,\"pending\":null}" - }, - "expected": { - "has_pending_clarification": false, - "state": { - "premise": "baseline", - "policies": { - "pytest": "use" - }, - "version": 2 - }, - "followup": { - "input": "yes", - "decision": { - "kind": "passthrough", - "state": null, - "prompt_to_user": null - }, - "state": { - "premise": "baseline", - "policies": { - "pytest": "use" - }, - "version": 2 - }, - "has_pending_clarification": false - } - } -} diff --git a/tests/fixtures/conformance/checkpoint/010_checkpoint_json_round_trip_state_and_pending_parity.json b/tests/fixtures/conformance/checkpoint/010_checkpoint_json_round_trip_state_and_pending_parity.json deleted file mode 100644 index 36cd554..0000000 --- a/tests/fixtures/conformance/checkpoint/010_checkpoint_json_round_trip_state_and_pending_parity.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "id": "checkpoint_checkpoint_json_round_trip_state_and_pending_parity", - "kind": "checkpoint", - "initial_state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "prelude": [ - "use kubectl instead of docker" - ], - "action": { - "fn": "checkpoint_json_round_trip" - }, - "expected": { - "state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "has_pending_clarification": false, - "followup": { - "input": "maybe", - "decision": { - "kind": "passthrough", - "state": null, - "prompt_to_user": null - }, - "state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "has_pending_clarification": false - } - } -} diff --git a/tests/fixtures/conformance/checkpoint/011_pending_clarification_through_checkpoint_json_restore_yes_resolution.json b/tests/fixtures/conformance/checkpoint/011_pending_clarification_through_checkpoint_json_restore_yes_resolution.json deleted file mode 100644 index ba8f5d1..0000000 --- a/tests/fixtures/conformance/checkpoint/011_pending_clarification_through_checkpoint_json_restore_yes_resolution.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "id": "checkpoint_pending_clarification_through_checkpoint_json_restore_yes_resolution", - "kind": "checkpoint", - "initial_state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "prelude": [ - "use kubectl instead of docker" - ], - "action": { - "fn": "checkpoint_json_round_trip" - }, - "expected": { - "state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "has_pending_clarification": false, - "followup": { - "input": "yes", - "decision": { - "kind": "passthrough", - "state": null, - "prompt_to_user": null - }, - "state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "has_pending_clarification": false - } - } -} diff --git a/tests/fixtures/conformance/checkpoint/012_import_checkpoint_json_malformed_rejected.json b/tests/fixtures/conformance/checkpoint/012_import_checkpoint_json_malformed_rejected.json deleted file mode 100644 index 74a70ac..0000000 --- a/tests/fixtures/conformance/checkpoint/012_import_checkpoint_json_malformed_rejected.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "id": "checkpoint_import_checkpoint_json_malformed_rejected", - "kind": "checkpoint", - "initial_state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "action": { - "fn": "import_checkpoint_json", - "payload": "{" - }, - "expected": { - "error": { - "type": "ValueError", - "message_contains": "Invalid JSON payload" - }, - "has_pending_clarification": false, - "state": { - "premise": null, - "policies": {}, - "version": 2 - } - } -} diff --git a/tests/fixtures/conformance/checkpoint/013_import_checkpoint_json_invalid_shape_rejected.json b/tests/fixtures/conformance/checkpoint/013_import_checkpoint_json_invalid_shape_rejected.json deleted file mode 100644 index 8f9c0c9..0000000 --- a/tests/fixtures/conformance/checkpoint/013_import_checkpoint_json_invalid_shape_rejected.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "id": "checkpoint_import_checkpoint_json_invalid_shape_rejected", - "kind": "checkpoint", - "initial_state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "action": { - "fn": "import_checkpoint_json", - "payload": "[]" - }, - "expected": { - "error": { - "type": "ValueError", - "message_contains": "Invalid checkpoint payload" - }, - "has_pending_clarification": false, - "state": { - "premise": null, - "policies": {}, - "version": 2 - } - } -} diff --git a/tests/fixtures/conformance/checkpoint/014_import_checkpoint_json_invalid_authoritative_state_rejected.json b/tests/fixtures/conformance/checkpoint/014_import_checkpoint_json_invalid_authoritative_state_rejected.json deleted file mode 100644 index 338504c..0000000 --- a/tests/fixtures/conformance/checkpoint/014_import_checkpoint_json_invalid_authoritative_state_rejected.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "id": "checkpoint_import_checkpoint_json_invalid_authoritative_state_rejected", - "kind": "checkpoint", - "initial_state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "action": { - "fn": "import_checkpoint_json", - "payload": "{\"checkpoint_version\":1,\"authoritative_state\":{\"premise\":null,\"policies\":[],\"version\":2},\"pending\":null}" - }, - "expected": { - "error": { - "type": "ValueError", - "message_contains": "Invalid state payload" - }, - "has_pending_clarification": false, - "state": { - "premise": null, - "policies": {}, - "version": 2 - } - } -} diff --git a/tests/fixtures/conformance/checkpoint/015_import_checkpoint_json_invalid_payload_is_all_or_nothing.json b/tests/fixtures/conformance/checkpoint/015_import_checkpoint_json_invalid_payload_is_all_or_nothing.json deleted file mode 100644 index da0a901..0000000 --- a/tests/fixtures/conformance/checkpoint/015_import_checkpoint_json_invalid_payload_is_all_or_nothing.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "id": "checkpoint_import_checkpoint_json_invalid_payload_is_all_or_nothing", - "kind": "checkpoint", - "initial_state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "prelude": [ - "use kubectl instead of docker" - ], - "action": { - "fn": "import_checkpoint_json", - "payload": "{\"checkpoint_version\":1,\"authoritative_state\":{\"premise\":\"new premise\",\"policies\":{\"pytest\":\"use\"},\"version\":2},\"pending\":{\"kind\":\"replacement\",\"replacement\":{\"kind\":\"replace_use\",\"new_item\":\"kubectl\",\"old_item\":\"docker\"},\"prompt_to_user\":\"confirm?\"}}" - }, - "expected": { - "error": { - "type": "ValueError", - "message_contains": "Invalid checkpoint payload" - }, - "has_pending_clarification": false, - "state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "followup": { - "input": "maybe", - "decision": { - "kind": "passthrough", - "state": null, - "prompt_to_user": null - }, - "state": { - "premise": null, - "policies": {}, - "version": 2 - }, - "has_pending_clarification": false - } - } -} diff --git a/tests/fixtures/conformance/controller/004_preview_clarify_reports_non_mutating_and_restores_pending.json b/tests/fixtures/conformance/controller/004_preview_clarify_reports_non_mutating_and_restores_pending.json index 7ad6a6d..894db98 100644 --- a/tests/fixtures/conformance/controller/004_preview_clarify_reports_non_mutating_and_restores_pending.json +++ b/tests/fixtures/conformance/controller/004_preview_clarify_reports_non_mutating_and_restores_pending.json @@ -15,9 +15,15 @@ "output_version": 1, "mode": "preview", "decision": { - "kind": "clarify", - "state": null, - "prompt_to_user": "\"docker\" is not currently in use.\nReplacement requires an active 'use' policy." + "kind": "update", + "state": { + "premise": null, + "policies": { + "kubectl": "use" + }, + "version": 2 + }, + "prompt_to_user": null }, "state_before": { "premise": null, @@ -26,23 +32,27 @@ }, "state_after": { "premise": null, - "policies": {}, + "policies": { + "kubectl": "use" + }, "version": 2 }, "diff": { - "changed": false, + "changed": true, "premise": { "before": null, "after": null, "changed": false }, "policies": { - "added": {}, + "added": { + "kubectl": "use" + }, "removed": {}, "changed": {} } }, - "would_mutate": false + "would_mutate": true }, "state_after_preview": { "premise": null, diff --git a/tests/fixtures/conformance/controller/005_preview_pending_yes_reports_mutation_but_preserves_live_pending.json b/tests/fixtures/conformance/controller/005_preview_pending_yes_reports_mutation_but_preserves_live_pending.json index 6c860e7..071c74a 100644 --- a/tests/fixtures/conformance/controller/005_preview_pending_yes_reports_mutation_but_preserves_live_pending.json +++ b/tests/fixtures/conformance/controller/005_preview_pending_yes_reports_mutation_but_preserves_live_pending.json @@ -24,12 +24,16 @@ }, "state_before": { "premise": null, - "policies": {}, + "policies": { + "kubectl": "use" + }, "version": 2 }, "state_after": { "premise": null, - "policies": {}, + "policies": { + "kubectl": "use" + }, "version": 2 }, "diff": { @@ -49,7 +53,9 @@ }, "state_after_preview": { "premise": null, - "policies": {}, + "policies": { + "kubectl": "use" + }, "version": 2 }, "has_pending_clarification": false diff --git a/tests/fixtures/conformance/step/008_replace_missing_source_clarify_prompt.json b/tests/fixtures/conformance/step/008_replace_missing_source_clarify_prompt.json index 3ece4f0..b95a669 100644 --- a/tests/fixtures/conformance/step/008_replace_missing_source_clarify_prompt.json +++ b/tests/fixtures/conformance/step/008_replace_missing_source_clarify_prompt.json @@ -9,14 +9,22 @@ "input": "use kubectl instead of docker", "expected": { "decision": { - "kind": "clarify", - "prompt_to_user": "\"docker\" is not currently in use.\nReplacement requires an active 'use' policy.", - "state": null + "kind": "update", + "prompt_to_user": null, + "state": { + "premise": null, + "policies": { + "kubectl": "use" + }, + "version": 2 + } }, "has_pending_clarification": false, "state": { "premise": null, - "policies": {}, + "policies": { + "kubectl": "use" + }, "version": 2 } } diff --git a/tests/fixtures/conformance/step/009_pending_affirmative_normalized_token.json b/tests/fixtures/conformance/step/009_pending_affirmative_normalized_token.json index 741a295..3b2c781 100644 --- a/tests/fixtures/conformance/step/009_pending_affirmative_normalized_token.json +++ b/tests/fixtures/conformance/step/009_pending_affirmative_normalized_token.json @@ -19,7 +19,9 @@ "has_pending_clarification": false, "state": { "premise": null, - "policies": {}, + "policies": { + "kubectl": "use" + }, "version": 2 } } diff --git a/tests/fixtures/conformance/step/010_pending_negative_normalized_token.json b/tests/fixtures/conformance/step/010_pending_negative_normalized_token.json index e53fd8e..dbbdedf 100644 --- a/tests/fixtures/conformance/step/010_pending_negative_normalized_token.json +++ b/tests/fixtures/conformance/step/010_pending_negative_normalized_token.json @@ -19,7 +19,9 @@ "has_pending_clarification": false, "state": { "premise": null, - "policies": {}, + "policies": { + "kubectl": "use" + }, "version": 2 } } diff --git a/tests/fixtures/conformance/step/011_pending_unmatched_reuses_prompt.json b/tests/fixtures/conformance/step/011_pending_unmatched_reuses_prompt.json index b3fabff..b760583 100644 --- a/tests/fixtures/conformance/step/011_pending_unmatched_reuses_prompt.json +++ b/tests/fixtures/conformance/step/011_pending_unmatched_reuses_prompt.json @@ -19,7 +19,9 @@ "has_pending_clarification": false, "state": { "premise": null, - "policies": {}, + "policies": { + "kubectl": "use" + }, "version": 2 } } diff --git a/tests/fixtures/conformance/step/018_pending_affirmative_punctuation_token.json b/tests/fixtures/conformance/step/018_pending_affirmative_punctuation_token.json index e67c2f1..27377e7 100644 --- a/tests/fixtures/conformance/step/018_pending_affirmative_punctuation_token.json +++ b/tests/fixtures/conformance/step/018_pending_affirmative_punctuation_token.json @@ -19,7 +19,9 @@ "has_pending_clarification": false, "state": { "premise": null, - "policies": {}, + "policies": { + "kubectl": "use" + }, "version": 2 } } diff --git a/tests/fixtures/conformance/step/019_pending_negative_punctuation_token.json b/tests/fixtures/conformance/step/019_pending_negative_punctuation_token.json index fe0bb75..7365ecd 100644 --- a/tests/fixtures/conformance/step/019_pending_negative_punctuation_token.json +++ b/tests/fixtures/conformance/step/019_pending_negative_punctuation_token.json @@ -19,7 +19,9 @@ "has_pending_clarification": false, "state": { "premise": null, - "policies": {}, + "policies": { + "kubectl": "use" + }, "version": 2 } } diff --git a/tests/fixtures/controller/preview_clarify_no_mutation.json b/tests/fixtures/controller/preview_clarify_no_mutation.json index 088a039..5b6174d 100644 --- a/tests/fixtures/controller/preview_clarify_no_mutation.json +++ b/tests/fixtures/controller/preview_clarify_no_mutation.json @@ -3,11 +3,10 @@ "kind": "controller_preview", "input": "use kubectl instead of docker", "expected": { - "decision_kind": "clarify", - "would_mutate": false, - "diff_changed": false, - "state_before_equals_after": true, + "decision_kind": "update", + "would_mutate": true, + "diff_changed": true, + "state_before_equals_after": false, "engine_state_unchanged_after_preview": true } } - diff --git a/tests/fixtures/engine-regression/structured/expected/contradiction_clarify.json b/tests/fixtures/engine-regression/structured/expected/contradiction_clarify.json index 453b0f1..825e800 100644 --- a/tests/fixtures/engine-regression/structured/expected/contradiction_clarify.json +++ b/tests/fixtures/engine-regression/structured/expected/contradiction_clarify.json @@ -2,16 +2,12 @@ "id": "contradiction_clarify", "turns": [ { - "checkpoint": { - "authoritative_state": { - "policies": { - "docker": "use" - }, - "premise": null, - "version": 2 + "state": { + "policies": { + "docker": "use" }, - "checkpoint_version": 1, - "pending": null + "premise": null, + "version": 2 }, "decision": { "kind": "update", @@ -20,16 +16,12 @@ "input": "use docker" }, { - "checkpoint": { - "authoritative_state": { - "policies": { - "docker": "use" - }, - "premise": null, - "version": 2 + "state": { + "policies": { + "docker": "use" }, - "checkpoint_version": 1, - "pending": null + "premise": null, + "version": 2 }, "decision": { "kind": "clarify", diff --git a/tests/fixtures/engine-regression/structured/expected/premise_lifecycle.json b/tests/fixtures/engine-regression/structured/expected/premise_lifecycle.json index d6e086f..85ad7e9 100644 --- a/tests/fixtures/engine-regression/structured/expected/premise_lifecycle.json +++ b/tests/fixtures/engine-regression/structured/expected/premise_lifecycle.json @@ -2,14 +2,10 @@ "id": "premise_lifecycle", "turns": [ { - "checkpoint": { - "authoritative_state": { - "policies": {}, - "premise": "concise replies", - "version": 2 - }, - "checkpoint_version": 1, - "pending": null + "state": { + "policies": {}, + "premise": "concise replies", + "version": 2 }, "decision": { "kind": "update", @@ -18,14 +14,10 @@ "input": "set premise concise replies" }, { - "checkpoint": { - "authoritative_state": { - "policies": {}, - "premise": "concise replies", - "version": 2 - }, - "checkpoint_version": 1, - "pending": null + "state": { + "policies": {}, + "premise": "concise replies", + "version": 2 }, "decision": { "kind": "clarify", @@ -34,14 +26,10 @@ "input": "set premise formal replies" }, { - "checkpoint": { - "authoritative_state": { - "policies": {}, - "premise": "concise bullet points", - "version": 2 - }, - "checkpoint_version": 1, - "pending": null + "state": { + "policies": {}, + "premise": "concise bullet points", + "version": 2 }, "decision": { "kind": "update", @@ -50,14 +38,10 @@ "input": "change premise to concise bullet points" }, { - "checkpoint": { - "authoritative_state": { - "policies": {}, - "premise": null, - "version": 2 - }, - "checkpoint_version": 1, - "pending": null + "state": { + "policies": {}, + "premise": null, + "version": 2 }, "decision": { "kind": "update", @@ -66,14 +50,10 @@ "input": "clear premise" }, { - "checkpoint": { - "authoritative_state": { - "policies": {}, - "premise": null, - "version": 2 - }, - "checkpoint_version": 1, - "pending": null + "state": { + "policies": {}, + "premise": null, + "version": 2 }, "decision": { "kind": "clarify", diff --git a/tests/fixtures/engine-regression/structured/expected/prohibited_replacement_new_clarify.json b/tests/fixtures/engine-regression/structured/expected/prohibited_replacement_new_clarify.json index 66107af..63db1d1 100644 --- a/tests/fixtures/engine-regression/structured/expected/prohibited_replacement_new_clarify.json +++ b/tests/fixtures/engine-regression/structured/expected/prohibited_replacement_new_clarify.json @@ -2,16 +2,12 @@ "id": "prohibited_replacement_new_clarify", "turns": [ { - "checkpoint": { - "authoritative_state": { - "policies": { - "docker": "use" - }, - "premise": null, - "version": 2 + "state": { + "policies": { + "docker": "use" }, - "checkpoint_version": 1, - "pending": null + "premise": null, + "version": 2 }, "decision": { "kind": "update", @@ -20,17 +16,13 @@ "input": "use docker" }, { - "checkpoint": { - "authoritative_state": { - "policies": { - "docker": "use", - "kubectl": "prohibit" - }, - "premise": null, - "version": 2 + "state": { + "policies": { + "docker": "use", + "kubectl": "prohibit" }, - "checkpoint_version": 1, - "pending": null + "premise": null, + "version": 2 }, "decision": { "kind": "update", @@ -39,17 +31,13 @@ "input": "prohibit kubectl" }, { - "checkpoint": { - "authoritative_state": { - "policies": { - "docker": "use", - "kubectl": "prohibit" - }, - "premise": null, - "version": 2 + "state": { + "policies": { + "docker": "use", + "kubectl": "prohibit" }, - "checkpoint_version": 1, - "pending": null + "premise": null, + "version": 2 }, "decision": { "kind": "clarify", @@ -58,17 +46,13 @@ "input": "use kubectl instead of docker" }, { - "checkpoint": { - "authoritative_state": { - "policies": { - "docker": "use", - "kubectl": "prohibit" - }, - "premise": null, - "version": 2 + "state": { + "policies": { + "docker": "use", + "kubectl": "prohibit" }, - "checkpoint_version": 1, - "pending": null + "premise": null, + "version": 2 }, "decision": { "kind": "passthrough", diff --git a/tests/fixtures/engine-regression/structured/expected/prohibited_replacement_old_clarify.json b/tests/fixtures/engine-regression/structured/expected/prohibited_replacement_old_clarify.json index 4e056fa..4a11529 100644 --- a/tests/fixtures/engine-regression/structured/expected/prohibited_replacement_old_clarify.json +++ b/tests/fixtures/engine-regression/structured/expected/prohibited_replacement_old_clarify.json @@ -2,16 +2,12 @@ "id": "prohibited_replacement_old_clarify", "turns": [ { - "checkpoint": { - "authoritative_state": { - "policies": { - "docker": "prohibit" - }, - "premise": null, - "version": 2 + "state": { + "policies": { + "docker": "prohibit" }, - "checkpoint_version": 1, - "pending": null + "premise": null, + "version": 2 }, "decision": { "kind": "update", @@ -20,17 +16,13 @@ "input": "prohibit docker" }, { - "checkpoint": { - "authoritative_state": { - "policies": { - "docker": "prohibit", - "pytest": "use" - }, - "premise": null, - "version": 2 + "state": { + "policies": { + "docker": "prohibit", + "pytest": "use" }, - "checkpoint_version": 1, - "pending": null + "premise": null, + "version": 2 }, "decision": { "kind": "update", @@ -39,17 +31,13 @@ "input": "use pytest" }, { - "checkpoint": { - "authoritative_state": { - "policies": { - "docker": "prohibit", - "pytest": "use" - }, - "premise": null, - "version": 2 + "state": { + "policies": { + "docker": "prohibit", + "pytest": "use" }, - "checkpoint_version": 1, - "pending": null + "premise": null, + "version": 2 }, "decision": { "kind": "clarify", @@ -58,17 +46,13 @@ "input": "use kubectl instead of docker" }, { - "checkpoint": { - "authoritative_state": { - "policies": { - "docker": "prohibit", - "pytest": "use" - }, - "premise": null, - "version": 2 + "state": { + "policies": { + "docker": "prohibit", + "pytest": "use" }, - "checkpoint_version": 1, - "pending": null + "premise": null, + "version": 2 }, "decision": { "kind": "passthrough", diff --git a/tests/fixtures/engine-regression/structured/expected/replacement_clarify.json b/tests/fixtures/engine-regression/structured/expected/replacement_clarify.json index 6cb9c87..bf093f9 100644 --- a/tests/fixtures/engine-regression/structured/expected/replacement_clarify.json +++ b/tests/fixtures/engine-regression/structured/expected/replacement_clarify.json @@ -2,18 +2,16 @@ "id": "replacement_clarify", "turns": [ { - "checkpoint": { - "authoritative_state": { - "policies": {}, - "premise": null, - "version": 2 + "state": { + "policies": { + "podman": "use" }, - "checkpoint_version": 1, - "pending": null + "premise": null, + "version": 2 }, "decision": { - "kind": "clarify", - "prompt_to_user": "\"docker\" is not currently in use.\nReplacement requires an active 'use' policy." + "kind": "update", + "prompt_to_user": null }, "input": "use podman instead of docker" } diff --git a/tests/fixtures/engine-regression/structured/scenarios/contradiction_clarify.json b/tests/fixtures/engine-regression/structured/scenarios/contradiction_clarify.json index 7e02235..b3712b7 100644 --- a/tests/fixtures/engine-regression/structured/scenarios/contradiction_clarify.json +++ b/tests/fixtures/engine-regression/structured/scenarios/contradiction_clarify.json @@ -1,7 +1,7 @@ { "description": "Policy contradiction yields clarify with no mutation", "id": "contradiction_clarify", - "initial_checkpoint": null, + "initial_state": null, "inputs": [ "use docker", "prohibit docker" diff --git a/tests/fixtures/engine-regression/structured/scenarios/premise_lifecycle.json b/tests/fixtures/engine-regression/structured/scenarios/premise_lifecycle.json index 30dd71f..83dd1a2 100644 --- a/tests/fixtures/engine-regression/structured/scenarios/premise_lifecycle.json +++ b/tests/fixtures/engine-regression/structured/scenarios/premise_lifecycle.json @@ -1,7 +1,7 @@ { "description": "Premise lifecycle updates and clarify gates", "id": "premise_lifecycle", - "initial_checkpoint": null, + "initial_state": null, "inputs": [ "set premise concise replies", "set premise formal replies", diff --git a/tests/fixtures/engine-regression/structured/scenarios/prohibited_replacement_new_clarify.json b/tests/fixtures/engine-regression/structured/scenarios/prohibited_replacement_new_clarify.json index 9f8f70c..89fa6d6 100644 --- a/tests/fixtures/engine-regression/structured/scenarios/prohibited_replacement_new_clarify.json +++ b/tests/fixtures/engine-regression/structured/scenarios/prohibited_replacement_new_clarify.json @@ -1,7 +1,7 @@ { "description": "Replacement to a prohibited new item stays an ordinary clarify", "id": "prohibited_replacement_new_clarify", - "initial_checkpoint": null, + "initial_state": null, "inputs": [ "use docker", "prohibit kubectl", diff --git a/tests/fixtures/engine-regression/structured/scenarios/prohibited_replacement_old_clarify.json b/tests/fixtures/engine-regression/structured/scenarios/prohibited_replacement_old_clarify.json index 5a2a0d2..eb54736 100644 --- a/tests/fixtures/engine-regression/structured/scenarios/prohibited_replacement_old_clarify.json +++ b/tests/fixtures/engine-regression/structured/scenarios/prohibited_replacement_old_clarify.json @@ -1,7 +1,7 @@ { "description": "Replacement from a prohibited old item stays an ordinary clarify", "id": "prohibited_replacement_old_clarify", - "initial_checkpoint": null, + "initial_state": null, "inputs": [ "prohibit docker", "use pytest", diff --git a/tests/fixtures/engine-regression/structured/scenarios/replacement_clarify.json b/tests/fixtures/engine-regression/structured/scenarios/replacement_clarify.json index c7ac3b3..0211edb 100644 --- a/tests/fixtures/engine-regression/structured/scenarios/replacement_clarify.json +++ b/tests/fixtures/engine-regression/structured/scenarios/replacement_clarify.json @@ -1,7 +1,7 @@ { - "description": "Replacement from missing old item creates pending clarify", + "description": "Replacement from missing old item applies as deterministic use update", "id": "replacement_clarify", - "initial_checkpoint": null, + "initial_state": null, "inputs": [ "use podman instead of docker" ] diff --git a/tests/test_04_grammar_edge_cases.py b/tests/test_04_grammar_edge_cases.py index 3bbb928..8f935d5 100644 --- a/tests/test_04_grammar_edge_cases.py +++ b/tests/test_04_grammar_edge_cases.py @@ -100,17 +100,17 @@ def test_remove_policy_missing_or_whitespace_payload_remains_passthrough() -> No def test_invalid_replacement_does_not_block_following_directives() -> None: engine = create_engine() first = engine.step("use kubectl instead of docker") - assert first["kind"] == "clarify" + assert first["kind"] == "update" second = engine.step("set premise concise") assert second == { "kind": "update", - "state": {"premise": "concise", "policies": {}, "version": 2}, + "state": {"premise": "concise", "policies": {"kubectl": "use"}, "version": 2}, "prompt_to_user": None, } assert engine.state == { "premise": "concise", - "policies": {}, + "policies": {"kubectl": "use"}, "version": 2, } @@ -120,5 +120,5 @@ def test_invalid_replacement_non_confirmation_followup_is_passthrough() -> None: first = engine.step("use kubectl instead of docker") second = engine.step("sounds good") - assert first["kind"] == "clarify" + assert first["kind"] == "update" assert second == {"kind": "passthrough", "state": None, "prompt_to_user": None} diff --git a/tests/test_controller.py b/tests/test_controller.py index 43e6392..47076f9 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -52,20 +52,23 @@ def test_preview_update_does_not_mutate_engine_state() -> None: assert engine.has_pending_clarification() is False -def test_preview_clarify_path_does_not_create_pending_for_invalid_replacement() -> None: +def test_preview_missing_source_replacement_reports_update_without_live_mutation() -> None: engine = create_engine() result = preview(engine, "use kubectl instead of docker") assert result["decision"] == { - "kind": "clarify", - "state": None, - "prompt_to_user": ( - "\"docker\" is not currently in use.\nReplacement requires an active 'use' policy." - ), + "kind": "update", + "state": {"premise": None, "policies": {"kubectl": "use"}, "version": 2}, + "prompt_to_user": None, } - assert result["state_before"] == result["state_after"] - assert result["diff"]["changed"] is False - assert result["would_mutate"] is False + assert result["state_before"] == {"premise": None, "policies": {}, "version": 2} + assert result["state_after"] == { + "premise": None, + "policies": {"kubectl": "use"}, + "version": 2, + } + assert result["diff"]["changed"] is True + assert result["would_mutate"] is True assert engine.has_pending_clarification() is False yes = engine.step("yes") @@ -155,7 +158,7 @@ def test_state_diff_policy_removed_and_value_changed() -> None: assert diff["policies"]["added"] == {} -def test_preview_fails_when_checkpoint_restore_fails( +def test_preview_fails_when_state_restore_fails( monkeypatch: pytest.MonkeyPatch, ) -> None: engine = create_engine() @@ -163,7 +166,7 @@ def test_preview_fails_when_checkpoint_restore_fails( def _boom(_: object) -> None: raise RuntimeError("restore failed") - monkeypatch.setattr(engine, "import_checkpoint", _boom) + monkeypatch.setattr(engine, "import_json", _boom) with pytest.raises(RuntimeError, match="restore failed"): preview(engine, "set premise concise replies") @@ -249,11 +252,9 @@ def test_preview_followup_tokens_after_invalid_replacement_are_passthrough( engine = create_engine() initial = engine.step("use kubectl instead of docker") assert initial == { - "kind": "clarify", - "state": None, - "prompt_to_user": ( - "\"docker\" is not currently in use.\nReplacement requires an active 'use' policy." - ), + "kind": "update", + "state": {"premise": None, "policies": {"kubectl": "use"}, "version": 2}, + "prompt_to_user": None, } assert engine.has_pending_clarification() is False @@ -263,13 +264,17 @@ def test_preview_followup_tokens_after_invalid_replacement_are_passthrough( "state": None, "prompt_to_user": None, } - assert preview_result["state_after"] == {"premise": None, "policies": {}, "version": 2} + assert preview_result["state_after"] == { + "premise": None, + "policies": {"kubectl": "use"}, + "version": 2, + } assert preview_result["would_mutate"] is False assert engine.has_pending_clarification() is False - assert engine.state == {"premise": None, "policies": {}, "version": 2} + assert engine.state == {"premise": None, "policies": {"kubectl": "use"}, "version": 2} final = step(engine, confirmation) assert final["decision"] == {"kind": "passthrough", "state": None, "prompt_to_user": None} - assert final["state"] == {"premise": None, "policies": {}, "version": 2} + assert final["state"] == {"premise": None, "policies": {"kubectl": "use"}, "version": 2} assert engine.has_pending_clarification() is False diff --git a/tests/test_demo_08_09_behavior.py b/tests/test_demo_08_09_behavior.py index 312cdc7..5aaaf1e 100644 --- a/tests/test_demo_08_09_behavior.py +++ b/tests/test_demo_08_09_behavior.py @@ -35,7 +35,7 @@ def _fake_complete_messages(_messages: object) -> str: return _fake_complete_messages -def test_demo_08_reports_invalid_replacement_block_and_unchanged_state( +def test_demo_08_reports_deterministic_missing_source_replacement_update( monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] ) -> None: module = _load_demo_module("08_llm_replacement_precondition.py") @@ -58,12 +58,12 @@ def test_demo_08_reports_invalid_replacement_block_and_unchanged_state( assert report["name"].startswith("08_replacement_precondition") assert report["baseline_pass"] is False assert report["reinjected_state_pass"] is False - assert report["compiler_pass"] is False + assert report["compiler_pass"] is True assert report["compiler_compact_pass"] is True - assert report["demo_pass"] is False + assert report["demo_pass"] is True assert "baseline: FAIL" in output assert "reinjected-state: FAIL" in output - assert "compiler: FAIL" in output + assert "compiler: PASS" in output def test_demo_08_reinjected_path_does_not_instantiate_engine( diff --git a/tests/test_engine.py b/tests/test_engine.py index 0ddf7c6..3a8a3c4 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -285,127 +285,10 @@ def test_import_json_accepts_valid_policy_key_and_normalizes_it() -> None: assert engine.state == {"premise": None, "policies": {"docker": "use"}, "version": 2} -@pytest.mark.contract -def test_export_checkpoint_contains_version_authoritative_state_and_pending_none() -> None: - engine = create_engine() - engine.step("set premise concise") - engine.step("use docker") - - checkpoint = engine.export_checkpoint() - - assert checkpoint == { - "checkpoint_version": 1, - "authoritative_state": { - "premise": "concise", - "policies": {"docker": "use"}, - "version": 2, - }, - "pending": None, - } - - -@pytest.mark.contract -def test_export_checkpoint_json_round_trip_preserves_authoritative_and_pending_state() -> None: - source = create_engine() - source.step("use kubectl instead of docker") - - payload = source.export_checkpoint_json() - target = create_engine() - target.import_checkpoint_json(payload) - - assert target.export_checkpoint() == source.export_checkpoint() - - -@pytest.mark.contract -def test_export_checkpoint_json_is_canonical_sorted_and_compact() -> None: - engine = create_engine() - engine.step("set premise concise") - engine.step("use zeta") - engine.step("use alpha") - - payload = engine.export_checkpoint_json() - - assert payload == ( - '{"authoritative_state":{"policies":{"alpha":"use","zeta":"use"},' - '"premise":"concise","version":2},"checkpoint_version":1,"pending":null}' - ) - - -@pytest.mark.contract -def test_export_checkpoint_does_not_serialize_pending_for_missing_source_replacement_clarify() -> ( - None -): - engine = create_engine() - clarify = engine.step("use kubectl instead of docker") - assert clarify == { - "kind": "clarify", - "state": None, - "prompt_to_user": ( - "\"docker\" is not currently in use.\nReplacement requires an active 'use' policy." - ), - } - - checkpoint = engine.export_checkpoint() - assert checkpoint["pending"] is None - - -@pytest.mark.contract -def test_export_checkpoint_does_not_serialize_pending_for_prohibited_replacement_clarify() -> None: - source = create_engine() - source.step("use docker") - source.step("prohibit kubectl") - clarify = source.step("use kubectl instead of docker") - assert clarify == { - "kind": "clarify", - "state": None, - "prompt_to_user": ( - '"kubectl" is currently prohibited.\n' - "Submit explicit directive(s) to remove it or use a different item." - ), - } - - checkpoint = source.export_checkpoint() - assert checkpoint["pending"] is None - - -@pytest.mark.contract -def test_export_checkpoint_json_object_and_restore_paths_are_behaviorally_equivalent() -> None: - source = create_engine() - source.step("use kubectl instead of docker") - - checkpoint_obj = source.export_checkpoint() - checkpoint_json = source.export_checkpoint_json() - assert json.loads(checkpoint_json) == checkpoint_obj - - via_obj = create_engine() - via_obj.import_checkpoint(checkpoint_obj) - - via_json = create_engine() - via_json.import_checkpoint_json(checkpoint_json) - - assert via_obj.step("yes") == via_json.step("yes") - assert via_obj.state == via_json.state - - -@pytest.mark.contract -def test_yes_without_pending_after_checkpoint_round_trip_is_passthrough() -> None: - source = create_engine() - source.step("use kubectl instead of docker") - checkpoint = source.export_checkpoint() - - target = create_engine() - target.import_checkpoint(checkpoint) - assert target.has_pending_clarification() is False - decision = target.step("yes") - - assert decision == {"kind": "passthrough", "state": None, "prompt_to_user": None} - assert target.state == {"premise": None, "policies": {}, "version": 2} - - def test_has_pending_clarification_remains_false_after_invalid_replacement_followup() -> None: engine = create_engine() decision = engine.step("use kubectl instead of docker") - assert decision["kind"] == "clarify" + assert decision["kind"] == "update" assert engine.has_pending_clarification() is False no_decision = engine.step("no") @@ -413,209 +296,15 @@ def test_has_pending_clarification_remains_false_after_invalid_replacement_follo assert engine.has_pending_clarification() is False -def test_import_checkpoint_invalid_json_and_invalid_object_payload_are_rejected() -> None: - engine = create_engine() - - with pytest.raises(ValueError, match="Invalid JSON payload"): - engine.import_checkpoint_json("{") - - with pytest.raises(ValueError, match="Invalid checkpoint payload"): - engine.import_checkpoint( # type: ignore[arg-type] - { - "checkpoint_version": 1, - "pending": None, - } - ) - - -def test_import_checkpoint_rejects_authoritative_state_with_empty_normalized_policy_key() -> None: - engine = create_engine() - with pytest.raises(ValueError, match="Invalid state payload"): - engine.import_checkpoint( # type: ignore[arg-type] - { - "checkpoint_version": 1, - "authoritative_state": { - "premise": None, - "policies": {"a": "use"}, - "version": 2, - }, - "pending": None, - } - ) - - -def test_import_checkpoint_json_rejects_authoritative_state_with_empty_normalized_policy_key() -> ( - None -): - engine = create_engine() - with pytest.raises(ValueError, match="Invalid state payload"): - engine.import_checkpoint_json( - json.dumps( - { - "checkpoint_version": 1, - "authoritative_state": { - "premise": None, - "policies": {"a": "use"}, - "version": 2, - }, - "pending": None, - } - ) - ) - - -def test_import_checkpoint_rejects_non_object_payload() -> None: - engine = create_engine() - with pytest.raises(ValueError, match="Invalid checkpoint payload"): - engine.import_checkpoint([]) # type: ignore[arg-type] - - -def test_import_checkpoint_rejects_unsupported_checkpoint_version() -> None: - engine = create_engine() - with pytest.raises(ValueError, match="Unsupported checkpoint version"): - engine.import_checkpoint( # type: ignore[arg-type] - { - "checkpoint_version": 2, - "authoritative_state": {"premise": None, "policies": {}, "version": 2}, - "pending": None, - } - ) - - -def test_import_checkpoint_json_rejects_unsupported_checkpoint_version() -> None: - engine = create_engine() - with pytest.raises(ValueError, match="Unsupported checkpoint version"): - engine.import_checkpoint_json( - json.dumps( - { - "checkpoint_version": 2, - "authoritative_state": {"premise": None, "policies": {}, "version": 2}, - "pending": None, - } - ) - ) - - -@pytest.mark.parametrize( - "pending", - [ - "bad", - {"kind": "replacement"}, - { - "kind": "wrong", - "replacement": {"kind": "use_only", "new_item": "x", "old_item": None}, - "prompt_to_user": "p", - }, - { - "kind": "replacement", - "replacement": {"kind": "use_only", "new_item": "x", "old_item": None}, - "prompt_to_user": 1, - }, - ], -) -def test_import_checkpoint_rejects_invalid_pending_payload_shapes(pending: object) -> None: - engine = create_engine() - with pytest.raises(ValueError, match="Invalid checkpoint payload"): - engine.import_checkpoint( # type: ignore[arg-type] - { - "checkpoint_version": 1, - "authoritative_state": {"premise": None, "policies": {}, "version": 2}, - "pending": pending, - } - ) - - def test_normalize_confirmation_collapses_unicode_spacing_and_trailing_punctuation() -> None: assert _normalize_confirmation(" YES!! ") == "yes" assert _normalize_confirmation("No\t\tthanks...\n") == "no thanks" -@pytest.mark.parametrize( - "replacement", - [ - "bad", - {"kind": "use_only", "new_item": "x"}, - {"kind": "other", "new_item": "x", "old_item": None}, - {"kind": "replace_use", "new_item": "x", "old_item": "y"}, - {"kind": "use_only", "new_item": 1, "old_item": None}, - {"kind": "use_only", "new_item": "x", "old_item": "y"}, - ], -) -def test_import_checkpoint_rejects_invalid_pending_replacement_payload_shapes( - replacement: object, -) -> None: - engine = create_engine() - with pytest.raises(ValueError, match="Invalid checkpoint payload"): - engine.import_checkpoint( # type: ignore[arg-type] - { - "checkpoint_version": 1, - "authoritative_state": {"premise": None, "policies": {}, "version": 2}, - "pending": { - "kind": "replacement", - "replacement": replacement, - "prompt_to_user": "confirm?", - }, - } - ) - - -def test_import_checkpoint_is_all_or_nothing_when_pending_is_invalid() -> None: - engine = create_engine() - engine.step("set premise baseline") - before = engine.state - - with pytest.raises(ValueError, match="Invalid checkpoint payload"): - engine.import_checkpoint( # type: ignore[arg-type] - { - "checkpoint_version": 1, - "authoritative_state": { - "premise": "new premise", - "policies": {"pytest": "use"}, - "version": 2, - }, - "pending": { - "kind": "replacement", - "replacement": { - "kind": "use_only", - "new_item": "kubectl", - "old_item": "docker", - }, - "prompt_to_user": "confirm?", - }, - } - ) - - assert engine.state == before - - -def test_import_checkpoint_is_all_or_nothing_when_authoritative_state_is_invalid() -> None: - engine = create_engine() - first = engine.step("use kubectl instead of docker") - assert first["kind"] == "clarify" - before = engine.state - - with pytest.raises(ValueError, match="Invalid state payload"): - engine.import_checkpoint( # type: ignore[arg-type] - { - "checkpoint_version": 1, - "authoritative_state": { - "premise": None, - "policies": [], - "version": 2, - }, - "pending": None, - } - ) - - second = engine.step("maybe") - assert second == {"kind": "passthrough", "state": None, "prompt_to_user": None} - assert engine.state == before - - def test_has_pending_clarification_stays_false_after_import_json() -> None: engine = create_engine() clarify = engine.step("use kubectl instead of docker") - assert clarify["kind"] == "clarify" + assert clarify["kind"] == "update" assert engine.has_pending_clarification() is False engine.import_json('{"policies":{},"premise":null,"version":2}') @@ -639,68 +328,6 @@ def test_replace_use_clarifies_when_old_policy_is_not_use_in_invalid_internal_st } -@pytest.mark.contract -def test_import_checkpoint_with_pending_none_clears_existing_pending() -> None: - engine = create_engine() - assert engine.has_pending_clarification() is False - - engine.import_checkpoint( - { - "checkpoint_version": 1, - "authoritative_state": { - "premise": "baseline", - "policies": {"pytest": "use"}, - "version": 2, - }, - "pending": None, - } - ) - assert engine.has_pending_clarification() is False - - yes_decision = engine.step("yes") - assert yes_decision == {"kind": "passthrough", "state": None, "prompt_to_user": None} - assert engine.state == {"premise": "baseline", "policies": {"pytest": "use"}, "version": 2} - - -@pytest.mark.contract -def test_import_checkpoint_with_pending_absent_clears_existing_pending() -> None: - engine = create_engine() - assert engine.has_pending_clarification() is False - - engine.import_checkpoint( - { - "checkpoint_version": 1, - "authoritative_state": { - "premise": "baseline", - "policies": {"pytest": "use"}, - "version": 2, - }, - } - ) - assert engine.has_pending_clarification() is False - - yes_decision = engine.step("yes") - assert yes_decision == {"kind": "passthrough", "state": None, "prompt_to_user": None} - assert engine.state == {"premise": "baseline", "policies": {"pytest": "use"}, "version": 2} - - -@pytest.mark.parametrize("new_item", ["", " ", "the", "an", "a"]) -def test_import_checkpoint_rejects_pending_use_only_with_invalid_new_item(new_item: str) -> None: - engine = create_engine() - with pytest.raises(ValueError, match="Invalid checkpoint payload"): - engine.import_checkpoint( # type: ignore[arg-type] - { - "checkpoint_version": 1, - "authoritative_state": {"premise": None, "policies": {}, "version": 2}, - "pending": { - "kind": "replacement", - "replacement": {"kind": "use_only", "new_item": new_item, "old_item": None}, - "prompt_to_user": "confirm?", - }, - } - ) - - @pytest.mark.parametrize( "payload", [ @@ -1120,39 +747,33 @@ def test_replace_use_identity_is_noop_update() -> None: assert engine.state == before -def test_replace_use_missing_source_clarifies_as_invalid_replacement_without_pending() -> None: +def test_replace_use_missing_source_applies_as_use_update_without_pending() -> None: engine = create_engine() - expected_prompt = ( - "\"docker\" is not currently in use.\nReplacement requires an active 'use' policy." - ) d1 = engine.step("use kubectl instead of docker") assert d1 == { - "kind": "clarify", - "state": None, - "prompt_to_user": expected_prompt, + "kind": "update", + "state": {"premise": None, "policies": {"kubectl": "use"}, "version": 2}, + "prompt_to_user": None, } - assert engine.state == {"premise": None, "policies": {}, "version": 2} + assert engine.state == {"premise": None, "policies": {"kubectl": "use"}, "version": 2} assert engine.has_pending_clarification() is False def test_replace_use_missing_source_yes_confirmation_is_passthrough() -> None: engine = create_engine() - expected_prompt = ( - "\"docker\" is not currently in use.\nReplacement requires an active 'use' policy." - ) first = engine.step("use kubectl instead of docker") assert first == { - "kind": "clarify", - "state": None, - "prompt_to_user": expected_prompt, + "kind": "update", + "state": {"premise": None, "policies": {"kubectl": "use"}, "version": 2}, + "prompt_to_user": None, } - assert engine.state == {"premise": None, "policies": {}, "version": 2} + assert engine.state == {"premise": None, "policies": {"kubectl": "use"}, "version": 2} second = engine.step("yes") assert second == {"kind": "passthrough", "state": None, "prompt_to_user": None} - assert engine.state == {"premise": None, "policies": {}, "version": 2} + assert engine.state == {"premise": None, "policies": {"kubectl": "use"}, "version": 2} def test_replace_use_missing_source_no_confirmation_has_no_mutation() -> None: @@ -1187,13 +808,8 @@ def test_replace_use_missing_source_ignores_unrelated_existing_policies() -> Non engine.step("use python and docker") decision = engine.step("use kubectl instead of python") - assert decision == { - "kind": "clarify", - "state": None, - "prompt_to_user": ( - "\"python\" is not currently in use.\nReplacement requires an active 'use' policy." - ), - } + assert decision["kind"] == "update" + assert engine.state["policies"] == {"kubectl": "use", "python and docker": "use"} def test_replace_use_missing_source_ignores_other_conflicting_entries() -> None: @@ -1202,29 +818,25 @@ def test_replace_use_missing_source_ignores_other_conflicting_entries() -> None: engine.step("prohibit python tooling") decision = engine.step("use kubectl instead of python") - assert decision == { - "kind": "clarify", - "state": None, - "prompt_to_user": ( - "\"python\" is not currently in use.\nReplacement requires an active 'use' policy." - ), + assert decision["kind"] == "update" + assert engine.state["policies"] == { + "kubectl": "use", + "python and docker": "use", + "python tooling": "prohibit", } def test_replace_use_missing_source_with_empty_probe_uses_invalid_prompt() -> None: engine = create_engine() engine.step("use python and docker") - before = engine.state decision = engine.step("use kubectl instead of the") - assert decision == { - "kind": "clarify", - "state": None, - "prompt_to_user": ( - "\"the\" is not currently in use.\nReplacement requires an active 'use' policy." - ), + assert decision["kind"] == "update" + assert engine.state == { + "premise": None, + "policies": {"kubectl": "use", "python and docker": "use"}, + "version": 2, } - assert engine.state == before def test_replace_use_ky_prohibit_returns_non_pending_clarify() -> None: @@ -1332,31 +944,27 @@ def test_replace_use_kx_prohibit_no_confirmation_has_no_mutation() -> None: def test_missing_source_replacement_does_not_block_following_directives() -> None: engine = create_engine() first = engine.step("use kubectl instead of docker") - assert first == { - "kind": "clarify", - "state": None, - "prompt_to_user": ( - "\"docker\" is not currently in use.\nReplacement requires an active 'use' policy." - ), - } + assert first["kind"] == "update" second = engine.step("use docker") assert second["kind"] == "update" - assert engine.state["policies"] == {"docker": "use"} + assert engine.state["policies"] == {"docker": "use", "kubectl": "use"} third = engine.step("yes") assert third == {"kind": "passthrough", "state": None, "prompt_to_user": None} - assert engine.state["policies"] == {"docker": "use"} + assert engine.state["policies"] == {"docker": "use", "kubectl": "use"} def test_missing_source_replacement_does_not_suspend_admin_commands() -> None: engine = create_engine() engine.step("use kubectl instead of docker") - before = engine.state + before = {"premise": None, "policies": {"kubectl": "use"}, "version": 2} + + assert engine.state == before assert engine.step("clear state")["kind"] == "update" assert engine.step("reset policies")["kind"] == "update" - assert engine.state == before == {"premise": None, "policies": {}, "version": 2} + assert engine.state == {"premise": None, "policies": {}, "version": 2} resolved = engine.step("yes") assert resolved == {"kind": "passthrough", "state": None, "prompt_to_user": None} @@ -1370,7 +978,7 @@ def test_missing_source_replacement_negative_followup_is_passthrough() -> None: decision = engine.step("no") assert decision == {"kind": "passthrough", "state": None, "prompt_to_user": None} - assert engine.state["policies"] == {} + assert engine.state["policies"] == {"kubectl": "use"} def test_missing_source_replacement_confirmation_tokens_are_not_consumed() -> None: @@ -1379,7 +987,7 @@ def test_missing_source_replacement_confirmation_tokens_are_not_consumed() -> No decision = engine.step(" YES!!! ") assert decision["kind"] == "passthrough" - assert engine.state["policies"] == {} + assert engine.state["policies"] == {"kubectl": "use"} def test_missing_source_replacement_affirmative_token_variants_are_passthrough() -> None: @@ -1388,7 +996,7 @@ def test_missing_source_replacement_affirmative_token_variants_are_passthrough() engine.step("use kubectl instead of docker") decision = engine.step(token) assert decision["kind"] == "passthrough" - assert engine.state["policies"] == {} + assert engine.state["policies"] == {"kubectl": "use"} def test_missing_source_replacement_negative_tokens_are_passthrough() -> None: @@ -1462,7 +1070,7 @@ def test_prohibited_replacement_yes_cannot_override_conflicting_target_polarity( def test_import_json_clears_pending_clarification_yes_no_not_confirmation() -> None: engine = create_engine() first = engine.step("use kubectl instead of docker") - assert first["kind"] == "clarify" + assert first["kind"] == "update" imported = {"premise": "baseline", "policies": {"pytest": "use"}, "version": 2} engine.import_json(json.dumps(imported)) @@ -1699,22 +1307,20 @@ def test_all_canonical_directive_starts_remain_single_directive_when_valid( ) -def test_compound_passthrough_after_prior_replacement_clarify() -> None: +def test_compound_passthrough_after_prior_missing_source_replacement_update() -> None: engine = create_engine() first = engine.step("use kubectl instead of docker") assert first == { - "kind": "clarify", - "state": None, - "prompt_to_user": ( - "\"docker\" is not currently in use.\nReplacement requires an active 'use' policy." - ), + "kind": "update", + "state": {"premise": None, "policies": {"kubectl": "use"}, "version": 2}, + "prompt_to_user": None, } assert engine.has_pending_clarification() is False decision = engine.step("use docker and prohibit peanuts") assert decision == {"kind": "passthrough", "state": None, "prompt_to_user": None} - assert engine.state == {"premise": None, "policies": {}, "version": 2} + assert engine.state == {"premise": None, "policies": {"kubectl": "use"}, "version": 2} assert engine.has_pending_clarification() is False diff --git a/tests/test_examples_behavior.py b/tests/test_examples_behavior.py index d1ba186..1fe8432 100644 --- a/tests/test_examples_behavior.py +++ b/tests/test_examples_behavior.py @@ -113,39 +113,39 @@ def capture_fake_llm(state: object, user_input: str) -> str: assert llm_calls[1][1] == "set premise concise replies" -def test_example_06_sequences_steps_and_restores_checkpoint( +def test_example_06_sequences_steps_and_restores_state_json( monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] ) -> None: module = _load_example_module("06_step_sequence_and_checkpoint.py") original_create_engine = module.create_engine step_calls: list[str] = [] - checkpoint_exports = 0 - checkpoint_imports = 0 + state_exports = 0 + state_imports = 0 def create_engine_wrapper() -> object: - nonlocal checkpoint_exports, checkpoint_imports + nonlocal state_exports, state_imports engine = original_create_engine() original_step = engine.step - original_export = engine.export_checkpoint_json - original_import = engine.import_checkpoint_json + original_export = engine.export_json + original_import = engine.import_json def step_wrapper(user_input: str) -> object: step_calls.append(user_input) return original_step(user_input) def export_wrapper() -> str: - nonlocal checkpoint_exports - checkpoint_exports += 1 + nonlocal state_exports + state_exports += 1 return original_export() def import_wrapper(payload: str) -> None: - nonlocal checkpoint_imports - checkpoint_imports += 1 + nonlocal state_imports + state_imports += 1 original_import(payload) engine.step = step_wrapper # type: ignore[assignment] - engine.export_checkpoint_json = export_wrapper # type: ignore[assignment] - engine.import_checkpoint_json = import_wrapper # type: ignore[assignment] + engine.export_json = export_wrapper # type: ignore[assignment] + engine.import_json = import_wrapper # type: ignore[assignment] return engine monkeypatch.setattr(module, "create_engine", create_engine_wrapper) @@ -154,11 +154,11 @@ def import_wrapper(payload: str) -> None: output = capsys.readouterr().out assert "Sequence directives through engine.step():" in output - assert "Checkpoint restore keeps authority state:" in output + assert "JSON restore keeps authority state:" in output assert step_calls == [ "prohibit peanuts", "set premise vegetarian curry", "change premise to vegan curry", ] - assert checkpoint_exports == 1 - assert checkpoint_imports == 1 + assert state_exports == 1 + assert state_imports == 1 diff --git a/tests/test_examples_smoke.py b/tests/test_examples_smoke.py index dd7ef4e..2ae08a7 100644 --- a/tests/test_examples_smoke.py +++ b/tests/test_examples_smoke.py @@ -50,7 +50,7 @@ "06_step_sequence_and_checkpoint.py", ( "Sequence directives through engine.step():", - "Checkpoint restore keeps authority state:", + "JSON restore keeps authority state:", ), ), ( diff --git a/tests/test_fixtures.py b/tests/test_fixtures.py index 1c3266e..886dd07 100644 --- a/tests/test_fixtures.py +++ b/tests/test_fixtures.py @@ -11,9 +11,6 @@ _STATE_JSON_FIXTURES_DIR = ( Path(__file__).resolve().parent / "fixtures" / "conformance" / "state-json" ) -_CHECKPOINT_FIXTURES_DIR = ( - Path(__file__).resolve().parent / "fixtures" / "conformance" / "checkpoint" -) _CONTROLLER_FIXTURES_DIR = ( Path(__file__).resolve().parent / "fixtures" / "conformance" / "controller" ) @@ -112,59 +109,6 @@ def test_state_json_fixtures() -> None: assert engine.state == expected["state"], fixture_id -def test_checkpoint_fixtures() -> None: - for path in _json_files(_CHECKPOINT_FIXTURES_DIR): - fixture = _load(path) - fixture_id = fixture["id"] - - assert fixture["kind"] == "checkpoint", fixture_id - engine = create_engine(state=fixture["initial_state"]) - _apply_prelude(engine, fixture.get("prelude", [])) - - action = fixture["action"] - expected = fixture["expected"] - fn = action["fn"] - - if fn == "import_checkpoint": - payload = action["payload"] - error = expected.get("error") - if error is None: - engine.import_checkpoint(payload) - else: - with pytest.raises(ValueError, match=error["message_contains"]): - engine.import_checkpoint(payload) - elif fn == "export_checkpoint_json": - payload = engine.export_checkpoint_json() - if expected.get("payload_json_parseable"): - parsed = json.loads(payload) - assert parsed == expected["payload_object"], fixture_id - elif fn == "import_checkpoint_json": - payload = action["payload"] - error = expected.get("error") - if error is None: - engine.import_checkpoint_json(payload) - else: - with pytest.raises(ValueError, match=error["message_contains"]): - engine.import_checkpoint_json(payload) - elif fn == "checkpoint_json_round_trip": - payload = engine.export_checkpoint_json() - target = create_engine() - target.import_checkpoint_json(payload) - engine = target - else: - raise AssertionError(f"Unknown checkpoint action: {fn}") - - assert engine.state == expected["state"], fixture_id - _assert_optional_pending_flag(expected, engine, fixture_id) - - followup = expected.get("followup") - if followup is not None: - decision = engine.step(followup["input"]) - assert decision == followup["decision"], fixture_id - assert engine.state == followup["state"], fixture_id - _assert_optional_pending_flag(followup, engine, fixture_id) - - def test_controller_fixtures() -> None: for path in _json_files(_CONTROLLER_FIXTURES_DIR): fixture = _load(path) diff --git a/tests/test_repl.py b/tests/test_repl.py index c290841..81c7635 100644 --- a/tests/test_repl.py +++ b/tests/test_repl.py @@ -90,7 +90,6 @@ def test_main_help_flag_prints_usage_and_exits_zero( "Usage:\n" " context-compiler [--help] [--version] [--json]\n" " [--initial-state-json | --initial-state-file ]\n" - " [--initial-checkpoint-json | --initial-checkpoint-file ]\n" "\n" "Options:\n" " --help Show this help message and exit.\n" @@ -98,10 +97,6 @@ def test_main_help_flag_prints_usage_and_exits_zero( " --json Emit machine-readable NDJSON output (non-interactive only)\n" " --initial-state-json Initialize authoritative state from exported state JSON text\n" " --initial-state-file Initialize authoritative state from UTF-8 state JSON file\n" - " --initial-checkpoint-json\n" - " Restore runtime continuation from checkpoint JSON text\n" - " --initial-checkpoint-file\n" - " Restore runtime continuation from UTF-8 checkpoint JSON file\n" ) assert captured.err == "" @@ -354,30 +349,6 @@ def test_cli_initial_state_file_preload_works(tmp_path: pathlib.Path) -> None: assert result.stderr == "" -def test_cli_initial_checkpoint_json_preload_without_pending_confirmation_keeps_state() -> None: - engine = create_engine() - engine.step("use kubectl instead of docker") - payload = engine.export_checkpoint_json() - - result = _run_repl_cli("--initial-checkpoint-json", payload, input_text="yes\nquit\n") - assert result.returncode == 0 - assert result.stdout == "passthrough\n" - assert result.stderr == "" - - -def test_cli_initial_checkpoint_file_preload_works(tmp_path: pathlib.Path) -> None: - engine = create_engine() - engine.step("set premise concise") - checkpoint = engine.export_checkpoint_json() - path = tmp_path / "checkpoint.json" - path.write_text(checkpoint, encoding="utf-8") - - result = _run_repl_cli("--initial-checkpoint-file", str(path), input_text="state\nquit\n") - assert result.returncode == 0 - assert "premise: concise" in result.stdout - assert result.stderr == "" - - def test_cli_invalid_initial_state_preload_fails_fast() -> None: result = _run_repl_cli("--initial-state-json", '{"bad":true}', input_text="state\nquit\n") assert result.returncode == 1 @@ -385,13 +356,6 @@ def test_cli_invalid_initial_state_preload_fails_fast() -> None: assert result.stderr == "error: preload failed: Invalid state payload.\n" -def test_cli_invalid_initial_checkpoint_preload_fails_fast() -> None: - result = _run_repl_cli("--initial-checkpoint-json", '{"bad":true}', input_text="state\nquit\n") - assert result.returncode == 1 - assert result.stdout == "" - assert result.stderr == "error: preload failed: Invalid checkpoint payload.\n" - - def test_cli_preload_mutual_exclusion_state_json_vs_file() -> None: result = _run_repl_cli("--initial-state-json", "{}", "--initial-state-file", "/tmp/ignored") assert result.returncode == 1 @@ -403,30 +367,6 @@ def test_cli_preload_mutual_exclusion_state_json_vs_file() -> None: assert result.stderr == expected -def test_cli_preload_mutual_exclusion_checkpoint_json_vs_file() -> None: - result = _run_repl_cli( - "--initial-checkpoint-json", "{}", "--initial-checkpoint-file", "/tmp/ignored" - ) - assert result.returncode == 1 - assert result.stdout == "" - expected = ( - "error: checkpoint preload options are mutually exclusive\n" - "Try 'context-compiler --help' for usage.\n" - ) - assert result.stderr == expected - - -def test_cli_preload_mutual_exclusion_state_vs_checkpoint() -> None: - result = _run_repl_cli("--initial-state-json", "{}", "--initial-checkpoint-json", "{}") - assert result.returncode == 1 - assert result.stdout == "" - expected = ( - "error: state preload and checkpoint preload are mutually exclusive\n" - "Try 'context-compiler --help' for usage.\n" - ) - assert result.stderr == expected - - def test_cli_preload_works_with_json_mode() -> None: engine = create_engine() engine.step("set premise concise") @@ -470,27 +410,6 @@ def _boom(_path: str) -> str: assert captured.err == "error: preload failed: cannot read preload file\n" -def test_cli_initial_checkpoint_file_preload_permission_failure_is_deterministic( - monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] -) -> None: - def _boom(_path: str) -> str: - raise PermissionError("permission denied") - - monkeypatch.setattr(repl_module, "_read_utf8_file", _boom) - monkeypatch.setattr( - sys, - "argv", - ["context-compiler", "--initial-checkpoint-file", "/tmp/checkpoint.json"], - ) - - result = repl_module.main() - captured = capsys.readouterr() - - assert result == 1 - assert captured.out == "" - assert captured.err == "error: preload failed: permission denied\n" - - def test_cli_initial_state_file_preload_utf8_decode_failure_is_deterministic( monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] ) -> None: @@ -535,82 +454,6 @@ def test_parse_cli_options_mutual_exclusion_errors() -> None: assert options_state == {} assert error_state == "state preload options are mutually exclusive" - options_ckpt, error_ckpt = repl_module._parse_cli_options( - ["--initial-checkpoint-json", "{}", "--initial-checkpoint-file", "/tmp/x"] - ) - assert options_ckpt == {} - assert error_ckpt == "checkpoint preload options are mutually exclusive" - - options_cross, error_cross = repl_module._parse_cli_options( - ["--initial-state-json", "{}", "--initial-checkpoint-json", "{}"] - ) - assert options_cross == {} - assert error_cross == "state preload and checkpoint preload are mutually exclusive" - - -def test_apply_preload_from_options_state_and_checkpoint_file_paths( - tmp_path: pathlib.Path, -) -> None: - baseline = create_engine() - baseline.step("set premise concise") - baseline_checkpoint_engine = create_engine() - baseline_checkpoint_engine.step("use kubectl instead of docker") - - state_path = tmp_path / "initial_state.json" - checkpoint_path = tmp_path / "initial_checkpoint.json" - state_path.write_text(baseline.export_json(), encoding="utf-8") - checkpoint_path.write_text( - baseline_checkpoint_engine.export_checkpoint_json(), encoding="utf-8" - ) - - state_engine = create_engine() - repl_module._apply_preload_from_options( - state_engine, - {"json_mode": False, "initial_state_file": str(state_path)}, - ) - assert state_engine.state["premise"] == "concise" - - checkpoint_engine = create_engine() - repl_module._apply_preload_from_options( - checkpoint_engine, - { - "json_mode": False, - "initial_checkpoint_file": str(checkpoint_path), - }, - ) - decision = checkpoint_engine.step("yes") - assert decision["kind"] == "passthrough" - assert checkpoint_engine.state["policies"] == {} - - -def test_apply_preload_from_options_state_and_checkpoint_json() -> None: - source_state = create_engine() - source_state.step("set premise concise") - source_checkpoint = create_engine() - source_checkpoint.step("use kubectl instead of docker") - - state_engine = create_engine() - repl_module._apply_preload_from_options( - state_engine, - { - "json_mode": False, - "initial_state_json": source_state.export_json(), - }, - ) - assert state_engine.state["premise"] == "concise" - - checkpoint_engine = create_engine() - repl_module._apply_preload_from_options( - checkpoint_engine, - { - "json_mode": False, - "initial_checkpoint_json": source_checkpoint.export_checkpoint_json(), - }, - ) - decision = checkpoint_engine.step("yes") - assert decision["kind"] == "passthrough" - assert checkpoint_engine.state["policies"] == {} - def test_repl_update_flow() -> None: lines = _run_non_interactive_lines("set premise concise\nquit\n") @@ -785,11 +628,11 @@ def test_repl_non_interactive_json_step_pending_confirmation_error() -> None: "decision": { "kind": "update", "prompt_to_user": None, - "state": {"premise": "concise", "policies": {}, "version": 2}, + "state": {"premise": "concise", "policies": {"kubectl": "use"}, "version": 2}, }, "mode": "step", "output_version": 1, - "state": {"premise": "concise", "policies": {}, "version": 2}, + "state": {"premise": "concise", "policies": {"kubectl": "use"}, "version": 2}, } @@ -822,10 +665,7 @@ def test_repl_non_interactive_preview_reports_no_mutation_after_invalid_replacem assert _contains_subsequence( lines, - [ - 'error: "docker" is not currently in use.', - "Replacement requires an active 'use' policy.", - ], + ["updated", "premise: (none)", "policies:", "- use kubectl"], ) assert _contains_subsequence(lines, ["preview", "passthrough", "would_mutate: no"]) assert lines[-1] == "passthrough" @@ -864,12 +704,9 @@ def test_repl_state_command_after_invalid_replacement_shows_unchanged_state() -> assert _contains_subsequence( lines, - [ - 'error: "docker" is not currently in use.', - "Replacement requires an active 'use' policy.", - ], + ["updated", "premise: (none)", "policies:", "- use kubectl"], ) - assert _contains_subsequence(lines, ["premise: (none)", "policies: (none)"]) + assert _contains_subsequence(lines, ["premise: (none)", "policies:", "- use kubectl"]) assert lines[-1] == "passthrough" @@ -883,12 +720,12 @@ def test_repl_step_command_runs_normally_after_invalid_replacement() -> None: assert _contains_subsequence( lines, - [ - 'error: "docker" is not currently in use.', - "Replacement requires an active 'use' policy.", - ], + ["updated", "premise: (none)", "policies:", "- use kubectl"], + ) + assert _contains_subsequence( + lines, + ["updated", "premise: concise", "policies:", "- use kubectl"], ) - assert _contains_subsequence(lines, ["updated", "premise: concise", "policies: (none)"]) assert lines[-1] == "passthrough" @@ -899,10 +736,7 @@ def test_repl_step_command_confirmation_token_is_passthrough_without_pending() - assert _contains_subsequence( lines, - [ - 'error: "docker" is not currently in use.', - "Replacement requires an active 'use' policy.", - ], + ["updated", "premise: (none)", "policies:", "- use kubectl"], ) assert lines[-1] == "passthrough" @@ -914,10 +748,7 @@ def test_repl_step_command_negative_confirmation_is_passthrough_without_pending( assert _contains_subsequence( lines, - [ - 'error: "docker" is not currently in use.', - "Replacement requires an active 'use' policy.", - ], + ["updated", "premise: (none)", "policies:", "- use kubectl"], ) assert lines[-1] == "passthrough" @@ -936,10 +767,7 @@ def test_repl_interactive_preview_available_after_invalid_replacement() -> None: assert _contains_subsequence( lines, - [ - 'error: "docker" is not currently in use.', - "Replacement requires an active 'use' policy.", - ], + ["updated", "premise: (none)", "policies:", "- use kubectl"], ) assert _contains_subsequence(lines, ["preview", "passthrough", "would_mutate: no"]) assert lines[-1] == "passthrough" @@ -950,10 +778,7 @@ def test_repl_interactive_help_available_after_invalid_replacement() -> None: assert _contains_subsequence( lines, - [ - 'error: "docker" is not currently in use.', - "Replacement requires an active 'use' policy.", - ], + ["updated", "premise: (none)", "policies:", "- use kubectl"], ) assert _contains_subsequence(lines, ["Commands: help/? exit/quit"]) assert _contains_subsequence(lines, ["REPL command layer (not engine directives):"]) @@ -1125,13 +950,7 @@ def test_repl_replace_use_when_old_policy_not_use_renders_exact_error( def test_repl_replacement_invalid_followups_are_passthrough() -> None: lines = _run_non_interactive_lines("use podman instead of docker\nmaybe\nyes please!!\nquit\n") - assert _contains_subsequence( - lines, - [ - 'error: "docker" is not currently in use.', - "Replacement requires an active 'use' policy.", - ], - ) + assert _contains_subsequence(lines, ["updated", "premise: (none)", "policies:", "- use podman"]) assert lines[-2:] == ["passthrough", "passthrough"] @@ -1145,7 +964,7 @@ def test_repl_interactive_prints_confirm_and_error_for_clarify_types() -> None: confirm_out = _TTYStringIO() run_repl(_TTYStringIO("use podman instead of docker\nquit\n"), confirm_out) confirm_lines = confirm_out.getvalue().splitlines() - assert 'error: "docker" is not currently in use.' in confirm_lines + assert "updated" in confirm_lines def test_repl_prohibited_replacement_followup_tokens_remain_passthrough() -> None: @@ -1209,19 +1028,19 @@ def test_repl_interactive_renders_updated_state_blocks_for_multiple_operations() def test_repl_interactive_tokens_after_invalid_replacement_are_passthrough() -> None: lines_yes = _run_interactive_lines("use podman instead of docker\nyeah\nquit\n") - assert 'error: "docker" is not currently in use.' in lines_yes + assert "updated" in lines_yes assert lines_yes[-1] == "passthrough" lines_ok = _run_interactive_lines("use buildah instead of docker\nok\nquit\n") - assert 'error: "docker" is not currently in use.' in lines_ok + assert "updated" in lines_ok assert lines_ok[-1] == "passthrough" lines_nope = _run_interactive_lines("use nerdctl instead of docker\nnope\nquit\n") - assert 'error: "docker" is not currently in use.' in lines_nope + assert "updated" in lines_nope assert lines_nope[-1] == "passthrough" lines_no_thanks = _run_interactive_lines("use helm instead of docker\nno thanks\nquit\n") - assert 'error: "docker" is not currently in use.' in lines_no_thanks + assert "updated" in lines_no_thanks assert lines_no_thanks[-1] == "passthrough" @@ -1247,7 +1066,10 @@ def test_repl_interactive_confirm_vs_error_alignment_for_actual_clarify_behavior assert "Use 'change premise to ' to modify it." in lines assert ('error: "docker" is currently in use.') in lines assert "Remove or replace it before prohibiting it." in lines - assert 'error: "buildx" is not currently in use.' in lines + assert _contains_subsequence( + lines, + ["updated", "premise: concise", "policies:", "- use docker", "- use podman"], + ) def test_repl_interactive_passthrough_prints_passthrough_label() -> None: @@ -1330,10 +1152,7 @@ def test_repl_interactive_step_command_paths_after_invalid_replacement() -> None assert _contains_subsequence( lines, - [ - 'error: "docker" is not currently in use.', - "Replacement requires an active 'use' policy.", - ], + ["updated", "premise: (none)", "policies:", "- use kubectl"], ) assert lines[-2:] == ["passthrough", "passthrough"] diff --git a/tests/test_structured_regression.py b/tests/test_structured_regression.py index 2148340..5a92170 100644 --- a/tests/test_structured_regression.py +++ b/tests/test_structured_regression.py @@ -21,15 +21,15 @@ def _load_json(path: Path) -> dict[str, object]: return json.loads(path.read_text(encoding="utf-8")) -def _checkpoint_diff(expected: object, actual: object) -> str: +def _state_diff(expected: object, actual: object) -> str: expected_lines = json.dumps(expected, indent=2, sort_keys=True).splitlines() actual_lines = json.dumps(actual, indent=2, sort_keys=True).splitlines() return "\n".join( difflib.unified_diff( expected_lines, actual_lines, - fromfile="expected_checkpoint", - tofile="actual_checkpoint", + fromfile="expected_state", + tofile="actual_state", lineterm="", ) ) @@ -47,9 +47,9 @@ def test_structured_regression_scenarios() -> None: engine = create_engine() - initial_checkpoint = scenario.get("initial_checkpoint") - if initial_checkpoint is not None: - engine.import_checkpoint(initial_checkpoint) + initial_state = scenario.get("initial_state") + if initial_state is not None: + engine.import_json(json.dumps(initial_state, sort_keys=True, separators=(",", ":"))) inputs = scenario["inputs"] expected_turns = expected["turns"] @@ -57,7 +57,7 @@ def test_structured_regression_scenarios() -> None: for turn_index, user_input in enumerate(inputs): decision = engine.step(user_input) - checkpoint = engine.export_checkpoint() + state = engine.state expected_turn = expected_turns[turn_index] context = f"scenario={scenario_id} turn={turn_index} input={user_input!r}" @@ -72,7 +72,7 @@ def test_structured_regression_scenarios() -> None: f"{context} prompt_to_user_mismatch" ) - expected_checkpoint = expected_turn["checkpoint"] - if checkpoint != expected_checkpoint: - diff = _checkpoint_diff(expected_checkpoint, checkpoint) - pytest.fail(f"{context} checkpoint_mismatch\n{diff}") + expected_state = expected_turn["state"] + if state != expected_state: + diff = _state_diff(expected_state, state) + pytest.fail(f"{context} state_mismatch\n{diff}") From 1a2bc0ac6ff7b947e5003df98bfb444cbbaba498 Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Fri, 24 Jul 2026 02:15:51 -0400 Subject: [PATCH 2/5] test: add repl preload coverage --- tests/test_repl.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/test_repl.py b/tests/test_repl.py index 81c7635..182e988 100644 --- a/tests/test_repl.py +++ b/tests/test_repl.py @@ -380,6 +380,37 @@ def test_cli_preload_works_with_json_mode() -> None: assert result.stderr == "" +@pytest.mark.contract +def test_apply_preload_from_options_initial_state_json_restores_state() -> None: + source_engine = create_engine() + source_engine.step("set premise concise") + source_engine.step("use docker") + + target_engine = create_engine() + repl_module._apply_preload_from_options( + target_engine, + { + "json_mode": False, + "initial_state_json": source_engine.export_json(), + }, + ) + + assert target_engine.state == { + "premise": "concise", + "policies": {"docker": "use"}, + "version": 2, + } + + +@pytest.mark.contract +def test_read_utf8_file_returns_exact_contents(tmp_path: pathlib.Path) -> None: + payload = '{"premise":"concise","policies":{"docker":"use"},"version":2}\n' + path = tmp_path / "state.json" + path.write_text(payload, encoding="utf-8") + + assert repl_module._read_utf8_file(str(path)) == payload + + def test_cli_preload_missing_value_errors() -> None: result = _run_repl_cli("--initial-state-json") assert result.returncode == 1 From 11a44159a9cbb29c55f128393734417edc01fbf0 Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Fri, 24 Jul 2026 02:28:54 -0400 Subject: [PATCH 3/5] test: align docs and contract coverage after checkpoint removal --- examples/README.md | 6 +++-- tests/fixtures/README.md | 23 ++++++----------- .../engine-regression/structured/README.md | 15 +++++------ tests/test_repl.py | 25 ++++++++++++++++--- 4 files changed, 42 insertions(+), 27 deletions(-) diff --git a/examples/README.md b/examples/README.md index 89edfe4..77873c2 100644 --- a/examples/README.md +++ b/examples/README.md @@ -9,7 +9,7 @@ These examples are intended to teach: - `Decision` handling - engine lifecycle - state access -- checkpoints +- state restoration through `export_json()` / `import_json()` - controller APIs - authority-layer usage patterns @@ -58,7 +58,9 @@ Includes a single-item policy removal step via `remove policy `. ## 06_step_sequence_and_checkpoint.py Shows the recommended authority-layer sequencing pattern with `engine.step(...)`. -Shows checkpoint export/import for restoring authoritative state without replay helpers. +The filename is a legacy holdover from the earlier checkpoint workflow. +The example now demonstrates authoritative state restoration with +`export_json()` / `import_json()` without replay helpers. ## 07_single_policy_correction.py diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md index ea0e9ac..4d410d7 100644 --- a/tests/fixtures/README.md +++ b/tests/fixtures/README.md @@ -6,7 +6,7 @@ This directory contains multiple fixture suites with different contracts. * [`conformance/`](conformance/) — core engine cross-language conformance contract. Includes a small public API presence contract under `conformance/api/`. -* [`engine-regression/structured/`](engine-regression/structured/) — deterministic per-turn engine regression fixtures (including checkpoint snapshots). +* [`engine-regression/structured/`](engine-regression/structured/) — deterministic per-turn engine regression fixtures using authoritative state snapshots. `conformance/` and `engine-regression/structured/` both cover engine behavior at different layers. ## API contract fixture @@ -53,7 +53,9 @@ Then asserts: ### Prelude -`prelude` simulates prior user inputs to reach states that are not representable via `initial_state` (for example, pending semantic continuation when the active engine contract supports it). +`prelude` simulates prior user inputs to reach states that are not representable +via `initial_state` (for example, runtime-only semantic continuation when the +active engine contract supports it). ## State JSON fixtures @@ -63,15 +65,6 @@ Portable serialization contract coverage for `engine.export_json()` and `engine.import_json(...)`, including canonical export payload shape and deterministic validation/error boundaries. -## Checkpoint fixtures - -For [`conformance/checkpoint/`](conformance/checkpoint/): - -Portable checkpoint import contract coverage for -`engine.import_checkpoint(...)`, including deterministic validation/error -boundaries, atomic failure behavior, and pending semantic-continuation restore -and clearing semantics. - ## Controller fixtures For [`conformance/controller/`](conformance/controller/): @@ -96,15 +89,16 @@ portable fixture contract. [`engine-regression/structured/`](engine-regression/structured/) -These fixtures capture deterministic per-turn engine behavior, including checkpoint snapshots, and are exercised by [`tests/test_structured_regression.py`](../test_structured_regression.py). +These fixtures capture deterministic per-turn engine behavior using +authoritative state snapshots, and are exercised by +[`tests/test_structured_regression.py`](../test_structured_regression.py). They validate: * per-turn input handling * `Decision.kind` outcomes * clarification prompt behavior -* checkpoint export parity against expected snapshots -* continuation state restoration from checkpoints +* authoritative state parity against expected snapshots Planned conformance coverage for the restored continuation contract should include: @@ -114,7 +108,6 @@ include: * malformed replacement syntax does not create pending continuation * valid canonical directives that clarify without pending continuation * valid canonical directives that clarify and create pending continuation -* checkpoint round-trip of supported pending continuation * deterministic `yes` resolution of the exact blocked transition * deterministic `no` rejection that clears pending continuation * deterministic handling of unrelated input while pending diff --git a/tests/fixtures/engine-regression/structured/README.md b/tests/fixtures/engine-regression/structured/README.md index 8f8e7dc..63c470a 100644 --- a/tests/fixtures/engine-regression/structured/README.md +++ b/tests/fixtures/engine-regression/structured/README.md @@ -4,7 +4,7 @@ These fixtures define deterministic, per-turn behavioral regression coverage for ## Layout -* `scenarios/`: input stimuli and optional setup (`initial_checkpoint`) +* `scenarios/`: input stimuli and optional setup (`initial_state`) * `expected/`: exact per-turn expected outputs Each scenario file in `scenarios/` must have a matching file in `expected/` with the same `id`. @@ -16,16 +16,17 @@ Each expected turn uses: * `input` * `decision.kind` * `decision.prompt_to_user` -* `checkpoint` +* `state` -`decision.state` is intentionally omitted because `checkpoint` is the authoritative resumable artifact. +`decision.state` is intentionally omitted because the expected authoritative +state snapshot is stored separately in each turn artifact. -## Why Checkpoint Every Turn +## Why Store State Every Turn -A full checkpoint is stored and compared on every turn so regressions are visible in: +A full authoritative state snapshot is stored and compared on every turn so +regressions are visible in: * authoritative state -* pending continuation state ## Prompt Matching @@ -42,7 +43,7 @@ A full checkpoint is stored and compared on every turn so regressions are visibl These fixtures validate **deterministic engine behavior only**: * `engine.step(...)` outputs (`Decision.kind`, `prompt_to_user`) -* post-turn checkpoint (`authoritative_state` + `pending`) +* post-turn authoritative state snapshot They do **not** cover: diff --git a/tests/test_repl.py b/tests/test_repl.py index 182e988..c7624c8 100644 --- a/tests/test_repl.py +++ b/tests/test_repl.py @@ -403,12 +403,31 @@ def test_apply_preload_from_options_initial_state_json_restores_state() -> None: @pytest.mark.contract -def test_read_utf8_file_returns_exact_contents(tmp_path: pathlib.Path) -> None: - payload = '{"premise":"concise","policies":{"docker":"use"},"version":2}\n' +def test_apply_preload_from_options_initial_state_file_restores_state( + tmp_path: pathlib.Path, +) -> None: + source_engine = create_engine() + source_engine.step("set premise concise") + source_engine.step("use docker") + + payload = source_engine.export_json() path = tmp_path / "state.json" path.write_text(payload, encoding="utf-8") - assert repl_module._read_utf8_file(str(path)) == payload + target_engine = create_engine() + repl_module._apply_preload_from_options( + target_engine, + { + "json_mode": False, + "initial_state_file": str(path), + }, + ) + + assert target_engine.state == { + "premise": "concise", + "policies": {"docker": "use"}, + "version": 2, + } def test_cli_preload_missing_value_errors() -> None: From cf1543e86a2add232e5622cb515f41d52b1e16f3 Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Fri, 24 Jul 2026 02:32:28 -0400 Subject: [PATCH 4/5] docs: clean stale checkpoint references --- demos/README.md | 2 +- tests/fixtures/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/demos/README.md b/demos/README.md index 67a6849..c47cdc7 100644 --- a/demos/README.md +++ b/demos/README.md @@ -35,7 +35,7 @@ Runnable application-layer enforcement-point integrations live in | [05](./05_llm_prompt_drift_vs_state.py) | Prompt drift | long transcript failure | weaker long-context models ([see Demo 05 example](#demo-05-example-prompt-drift-under-longer-context)) | | [06](./06_llm_context_compaction.py) | Context compaction | saved compiler state replacing transcript context | small or local models | | [07](./07_llm_prompt_vs_state.py) | Prompt engineering comparison | prompting vs saved compiler state | any model with long transcript sensitivity | -| [08](./08_llm_replacement_precondition.py) | Replacement precondition | invalid replacement blocked without state mutation | any model | +| [08](./08_llm_replacement_precondition.py) | Replacement precondition | missing-source replacement applies deterministically from authoritative state | any model | | [09](./09_llm_pending_clarification.py) | Pending boundary | invalid replacement does not become pending continuation | any model | Stronger frontier models may show these behaviors less often, but the same diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md index 4d410d7..eb68ae3 100644 --- a/tests/fixtures/README.md +++ b/tests/fixtures/README.md @@ -100,8 +100,8 @@ They validate: * clarification prompt behavior * authoritative state parity against expected snapshots -Planned conformance coverage for the restored continuation contract should -include: +If a future engine contract restores supported continuation behavior, +planned conformance coverage should include: * incomplete directives do not create pending continuation * compound directives do not create pending continuation From cdcc51699679abcfa2cc6f1cc81f7df2d385a585 Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Fri, 24 Jul 2026 02:35:45 -0400 Subject: [PATCH 5/5] docs: finish checkpoint cleanup --- AGENTS.md | 2 +- ...d_checkpoint.py => 06_step_sequence_and_state_restore.py} | 0 examples/README.md | 5 ++--- tests/test_examples_behavior.py | 2 +- tests/test_examples_smoke.py | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) rename examples/{06_step_sequence_and_checkpoint.py => 06_step_sequence_and_state_restore.py} (100%) diff --git a/AGENTS.md b/AGENTS.md index 0e94850..3b206c0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,7 +25,7 @@ Before opening a PR, consider: User-facing behavior includes: * engine decision outcomes (`kind`, `prompt_to_user`) -* checkpoint export/import and continuation behavior +* `export_json()` / `import_json()` persistence and continuation behavior * clarify/confirmation flows (`yes` / `no`) * controller behavior (`step`, `preview`, `state_diff`) * REPL / CLI behavior diff --git a/examples/06_step_sequence_and_checkpoint.py b/examples/06_step_sequence_and_state_restore.py similarity index 100% rename from examples/06_step_sequence_and_checkpoint.py rename to examples/06_step_sequence_and_state_restore.py diff --git a/examples/README.md b/examples/README.md index 77873c2..e67460f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -55,11 +55,10 @@ Shows what to do on `clarify`, when to call the model, and how host code can use saved state downstream. Includes a single-item policy removal step via `remove policy `. -## 06_step_sequence_and_checkpoint.py +## 06_step_sequence_and_state_restore.py Shows the recommended authority-layer sequencing pattern with `engine.step(...)`. -The filename is a legacy holdover from the earlier checkpoint workflow. -The example now demonstrates authoritative state restoration with +The example demonstrates authoritative state restoration with `export_json()` / `import_json()` without replay helpers. ## 07_single_policy_correction.py diff --git a/tests/test_examples_behavior.py b/tests/test_examples_behavior.py index 1fe8432..03d7634 100644 --- a/tests/test_examples_behavior.py +++ b/tests/test_examples_behavior.py @@ -116,7 +116,7 @@ def capture_fake_llm(state: object, user_input: str) -> str: def test_example_06_sequences_steps_and_restores_state_json( monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] ) -> None: - module = _load_example_module("06_step_sequence_and_checkpoint.py") + module = _load_example_module("06_step_sequence_and_state_restore.py") original_create_engine = module.create_engine step_calls: list[str] = [] state_exports = 0 diff --git a/tests/test_examples_smoke.py b/tests/test_examples_smoke.py index 2ae08a7..c340fc3 100644 --- a/tests/test_examples_smoke.py +++ b/tests/test_examples_smoke.py @@ -47,7 +47,7 @@ ), ), ( - "06_step_sequence_and_checkpoint.py", + "06_step_sequence_and_state_restore.py", ( "Sequence directives through engine.step():", "JSON restore keeps authority state:",