Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,10 +392,9 @@ Replacement:
User: use podman instead of docker
```

If `docker` is absent from saved state, that does not make the directive
pending. The user's intended resulting state is still unambiguous, so the
replacement follows the deterministic `use podman` transition when otherwise
semantically valid.
If `docker` is absent from saved state, that is a semantic `error`.
Canonical replacement requires an active existing source `use` policy and
does not degrade to plain `use podman`.

Removal and reset:

Expand All @@ -413,8 +412,8 @@ evaluation against authoritative state.
Pending continuation is a separate runtime layer. It may exist only after a
canonical directive reaches a supported semantic `error` case. It never
repairs malformed syntax or reinterprets non-canonical input as a directive.
An absent source item in a canonical replacement directive is not, by itself,
such a `error` case.
An absent source item in a canonical replacement directive is itself a
semantic `error` case and does not authorize degradation to plain `use`.

Examples:

Expand Down
48 changes: 24 additions & 24 deletions demos/08_llm_replacement_precondition.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""Demo 8: missing-source replacement applies deterministically from authoritative state."""
"""Demo 8: missing-source replacement fails without mutating authoritative state."""

from collections.abc import Mapping

from context_compiler import (
Engine,
is_update,
is_error,
)
from demos.common import (
build_baseline_messages,
Expand All @@ -22,7 +22,7 @@
)
from demos.llm_client import complete_messages

DEMO_NAME = "08_replacement_precondition — missing-source replacement applies deterministically"
DEMO_NAME = "08_replacement_precondition — missing-source replacement requires active source"
USER_INPUT = "use podman instead of docker"


Expand Down Expand Up @@ -69,38 +69,38 @@ def main() -> None:
reinjected_output = complete_messages(reinjected_messages)
print_model_output("Reinjected-state", reinjected_output)

if is_update(decision):
if is_error(decision):
print_messages("compiler-mediated (full)", [])
mediated_output = "[no call] authoritative state applied deterministic replacement update"
mediated_output = "[no call] authoritative state blocked replacement without source use"
print_model_output("Compiler-mediated (full)", mediated_output)
else:
print_messages("compiler-mediated (full)", [])
mediated_output = "[no call] expected update was not produced"
mediated_output = "[no call] expected semantic replacement error 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 None:
if compacted_prompt is not None:
print_messages("compiler-mediated + compact", [])
compact_output = "[no call] compaction preserved deterministic state update"
compact_output = "[no call] unexpected error was produced during compaction"
print_model_output("Compiler-mediated + compact", compact_output)
else:
print_messages("compiler-mediated + compact", [])
compact_output = "[no call] unexpected error was produced during compaction"
compact_output = "[no call] compaction preserved replacement error without state mutation"
print_model_output("Compiler-mediated + compact", compact_output)

premise, policies = observe_engine(engine)
compacted_premise, compacted_policies = state_observations(compacted_state)
state_applied = not _is_initial_authoritative_state(premise=premise, policies=policies)
compact_state_applied = not _is_initial_authoritative_state(
state_preserved = _is_initial_authoritative_state(premise=premise, policies=policies)
compact_state_preserved = _is_initial_authoritative_state(
premise=compacted_premise,
policies=compacted_policies,
)
compact_no_pending = compacted_prompt is None
compact_error_preserved = compacted_prompt is not None

baseline_has_authoritative_precondition = False
reinjected_has_authoritative_precondition = False
compiler_pass = is_update(decision) and state_applied
compact_pass = compacted_prompt is None and compact_state_applied and compact_no_pending
compiler_pass = is_error(decision) and state_preserved
compact_pass = compact_error_preserved and compact_state_preserved

print_host_check(
"BASELINE_AUTHORITATIVE_PRECONDITION",
Expand All @@ -114,12 +114,12 @@ def main() -> None:
)
print_host_check(
"COMPILER_BLOCKED_INVALID_REPLACEMENT",
yes_no(is_update(decision)),
yes_no(is_error(decision)),
context="compiler-mediated",
)
print_host_check(
"COMPILER_STATE_APPLIED",
yes_no(state_applied),
"COMPILER_STATE_PRESERVED",
yes_no(state_preserved),
context="compiler-mediated",
)

Expand All @@ -130,18 +130,18 @@ def main() -> None:
compiler_pass=compiler_pass,
compiler_compact_pass=compact_pass,
expected=(
"missing-source replacement should deterministically apply the resulting use update "
"without pending continuation"
"missing-source replacement should return semantic error without mutating "
"authoritative state"
),
actual=(
"compiler applied deterministic replacement update; baseline and reinjected paths "
"still lack authoritative state enforcement"
"compiler blocked missing-source replacement without mutating state; baseline and "
"reinjected paths still lack authoritative state enforcement"
if compiler_pass and compact_pass
else "compiler did not consistently apply deterministic replacement behavior"
else "compiler did not consistently enforce the replacement source precondition"
),
passed=compiler_pass and compact_pass,
result_pass="missing-source replacement applied deterministically",
result_fail="missing-source replacement did not apply deterministically",
result_pass="missing-source replacement was rejected without mutation",
result_fail="missing-source replacement was not rejected correctly",
)


Expand Down
51 changes: 24 additions & 27 deletions demos/09_llm_confirmation_no_directive.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""Demo 9: confirmation-style followups remain ordinary no_directive."""
"""Demo 9: replacement errors do not create confirmation-style followup state."""

from collections.abc import Mapping

from context_compiler import (
Engine,
is_error,
is_no_directive,
is_update,
)
from demos.common import (
build_baseline_messages,
Expand All @@ -24,8 +24,7 @@
from demos.llm_client import complete_messages

DEMO_NAME = (
"09_confirmation_no_directive_boundary — "
"missing-source replacement does not create a confirmation state"
"09_confirmation_no_directive_boundary — replacement errors do not create a confirmation state"
)
TURN_1 = "use podman instead of docker"
TURN_2 = "maybe"
Expand All @@ -34,10 +33,6 @@
INITIAL_POLICIES: dict[str, str] = {}


def _has_podman_use(policies: Mapping[str, str]) -> bool:
return policies.get("podman") == "use"


def _is_initial_authoritative_state(*, premise: str | None, policies: Mapping[str, str]) -> bool:
return premise == INITIAL_PREMISE and dict(policies) == INITIAL_POLICIES

Expand All @@ -50,12 +45,17 @@ def main() -> None:
first = engine.step(TURN_1)
premise, policies = observe_engine(engine)
print_decision("turn 1", first, premise=premise, policies=policies)
state_applied_after_first = _has_podman_use(policies)
state_preserved_after_first = _is_initial_authoritative_state(
premise=premise, policies=policies
)

second = engine.step(TURN_2)
premise, policies = observe_engine(engine)
print_decision("turn 2", second, premise=premise, policies=policies)
state_preserved_after_second = _has_podman_use(policies)
state_preserved_after_second = _is_initial_authoritative_state(
premise=premise,
policies=policies,
)

third = engine.step(TURN_3)
premise, policies = observe_engine(engine)
Expand Down Expand Up @@ -96,7 +96,7 @@ def main() -> None:
compacted_turns, compacted_state, compacted_prompt = compact_user_turns(user_inputs)
if compacted_prompt is not None:
print_messages("compiler-mediated + compact", [])
compact_output = f"[no call] error required: {compacted_prompt}"
compact_output = f"[no call] replacement error preserved: {compacted_prompt}"
print_model_output("Compiler-mediated + compact", compact_output)
else:
print_messages("compiler-mediated + compact", [])
Expand All @@ -105,32 +105,30 @@ def main() -> None:
)
print_model_output("Compiler-mediated + compact", compact_output)

deterministic_initial_update = is_update(first) and state_applied_after_first
deterministic_initial_error = is_error(first) and state_preserved_after_first
unrelated_followup_no_directive = is_no_directive(second) and state_preserved_after_second
confirmation_token_not_consumed = is_no_directive(third)
premise, policies = observe_engine(engine)
deterministic_final_state = _has_podman_use(policies)
deterministic_final_state = _is_initial_authoritative_state(premise=premise, policies=policies)
_, compacted_policies = state_observations(compacted_state)

baseline_has_confirmation_state_machine = False
reinjected_has_confirmation_state_machine = False

compiler_pass = (
deterministic_initial_update
deterministic_initial_error
and unrelated_followup_no_directive
and confirmation_token_not_consumed
and deterministic_final_state
)

compact_pass = (
compacted_prompt is None
and compacted_turns == [TURN_2, TURN_3]
and _has_podman_use(compacted_policies)
compacted_prompt is not None and compacted_turns == [TURN_1] and compacted_policies == {}
)

print_host_check(
"DETERMINISTIC_INITIAL_UPDATE",
yes_no(deterministic_initial_update),
"DETERMINISTIC_INITIAL_ERROR",
yes_no(deterministic_initial_error),
context="compiler-mediated",
)
print_host_check(
Expand All @@ -144,7 +142,7 @@ def main() -> None:
context="compiler-mediated",
)
print_host_check(
"FINAL_POLICY_PODMAN_PRESENT",
"FINAL_STATE_UNCHANGED",
yes_no(deterministic_final_state),
context="compiler-mediated",
)
Expand All @@ -156,19 +154,18 @@ def main() -> None:
compiler_pass=compiler_pass,
compiler_compact_pass=compact_pass,
expected=(
"missing-source replacement should apply without creating an engine-owned "
"confirmation state, and later yes/no-style input should remain ordinary "
"no_directive"
"replacement error should not create an engine-owned confirmation state, "
"and later yes/no-style input should remain ordinary no_directive"
),
actual=(
"compiler applied deterministic replacement update and treated later inputs as "
"ordinary no_directive"
"compiler returned semantic error and treated later inputs as ordinary "
"no_directive without mutating state"
if compiler_pass and compact_pass
else "compiler did not consistently preserve the confirmation-no_directive boundary"
),
passed=compiler_pass and compact_pass,
result_pass="missing-source replacement stayed outside engine-owned confirmation state",
result_fail="missing-source replacement still behaved like engine-owned confirmation state",
result_pass="replacement error stayed outside engine-owned confirmation state",
result_fail="replacement error still behaved like engine-owned confirmation state",
)


Expand Down
25 changes: 11 additions & 14 deletions docs/DirectiveGrammarSpec.md
Original file line number Diff line number Diff line change
Expand Up @@ -564,14 +564,12 @@ Let `kx` be the policy identity key for `REPLACE_NEW` under Section 10.1 and
The replacement-specific `error` cases are state-dependent and belong to
semantic evaluation, not parsing.

Normative classification for the historical missing-source case:

- if `ky` is absent and applying `use <new>` is otherwise semantically valid,
`use <new> instead of <old>` is not a error case;
- core applies the deterministic resulting transition of asserting
`REPLACE_NEW` as `use`;
- the user's incorrect assumption about the current presence of `<old>` does
not by itself create semantic ambiguity or pending continuation;
Normative classification for the missing-source case:

- if `ky` is absent, `use <new> instead of <old>` is an `error` case;
- replacement requires an active existing `use` policy for `REPLACE_OLD`;
- core must not degrade the canonical replacement directive into plain
`use <new>`;
- malformed replacement syntax remains invalid grammar, and other semantic
conflicts for a canonical replacement may still return `error`.

Expand Down Expand Up @@ -633,11 +631,10 @@ This specification preserves the grammar hardening established after `0.8.x`:

Within semantic evaluation, pending continuation is intended only for
deterministic blocked transitions that do not expand authority beyond the
parsed canonical operation. In particular, the historical missing-source
replacement case (`use <new> instead of <old>` when `<old>` is absent) is not
a pending or error case under this specification; it deterministically
applies the resulting `use <new>` transition when otherwise semantically
valid.
parsed canonical operation. In particular, the missing-source replacement
case (`use <new> instead of <old>` when `<old>` is absent) is an `error`
case under this specification and must not be reinterpreted as plain
`use <new>`.

## 11. Storage Normalization

Expand Down Expand Up @@ -719,7 +716,7 @@ source material for later conformance fixtures.
| `prohibit peanuts` | canonical directive | prohibit item | may apply, no-op, or error |
| `remove policy docker` | canonical directive | remove policy | may apply or no-op |
| `use podman instead of docker` | canonical directive | replace use | may apply, no-op, or error |
| `use podman instead of docker` when `docker` is absent and `use podman` is otherwise valid | canonical directive | replace use | applies deterministically as the resulting `use podman` transition; not a pending/error case |
| `use podman instead of docker` when `docker` is absent | canonical directive | replace use | semantic error; replacement requires an active source `use` policy |
| `clear premise` | canonical directive | clear premise | may apply or no-op |
| `reset policies` | canonical directive | reset policies | may apply or no-op |
| `clear state` | canonical directive | clear state | may apply or no-op |
Expand Down
5 changes: 2 additions & 3 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,8 @@ Boundary notes:
code should send canonical directives when it wants deterministic mutation
- failed replacement requests are not reinterpreted by core into different
directives
- `use <new> instead of <old>` with an absent `<old>` is not a pending or
error-only runtime category; it follows the deterministic semantic
rules defined in the specification
- `use <new> instead of <old>` with an absent `<old>` is a semantic `error`;
core does not degrade it into plain `use <new>`

`CanonicalDirective.operands` preserves the grammar-recognized operand text.
Core does not lowercase operands, collapse internal operand whitespace, or
Expand Down
10 changes: 4 additions & 6 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,15 @@ Boundary:
canonical operations already established by core
- pending continuation is runtime state, not grammar
- malformed or non-canonical input must never create pending continuation
- a missing source item in `use <new> instead of <old>` is evaluated as a
deterministic state transition question, not as a justification for pending
continuation
- a missing source item in `use <new> instead of <old>` is a semantic
replacement error, not a justification for pending continuation

Current repository note:

- the intended contract allows semantic pending continuation for supported
deterministic blocked transitions
- that continuation boundary is independent from the historical replacement
case where the requested old item is absent from state
- the current runtime implementation does not yet fully restore that contract
- that continuation boundary remains independent from replacement errors where
the requested old item is absent from state
and should be treated as lagging the updated specification until runtime work
lands

Expand Down
2 changes: 1 addition & 1 deletion src/context_compiler/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def _pre_mutation_error(
f'"{new_item}" is currently prohibited.\n'
"Submit explicit directive(s) to remove it or use a different item."
)
if old_state not in {None, POLICY_USE}:
if old_state != POLICY_USE:
return _error(
f'"{old_item}" is not currently in use.\n'
"Replacement requires an active 'use' policy."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@
},
"state": {
"premise": null,
"policies": {
"kubectl": "use"
},
"policies": {},
"version": 2
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@
},
"state": {
"premise": null,
"policies": {
"kubectl": "use"
},
"policies": {},
"version": 2
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@
},
"state": {
"premise": null,
"policies": {
"kubectl": "use"
},
"policies": {},
"version": 2
}
}
Expand Down
Loading