context-compiler 0.9.0.dev8__tar.gz → 0.9.0.dev10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/PKG-INFO +6 -7
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/README.md +5 -6
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/demos/08_llm_replacement_precondition.py +24 -24
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/demos/09_llm_confirmation_no_directive.py +24 -27
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/pyproject.toml +1 -1
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/src/context_compiler/engine.py +15 -50
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/src/context_compiler/grammar.py +257 -90
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/.gitignore +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/LICENSE +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/demos/01_llm_contradiction_error.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/demos/02_llm_constraint_guardrail.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/demos/03_llm_premise_guardrail.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/demos/04_llm_tool_denylist_guardrail.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/demos/05_llm_prompt_drift_vs_state.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/demos/06_llm_context_compaction.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/demos/07_llm_prompt_vs_state.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/demos/README.md +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/demos/__init__.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/demos/common.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/demos/llm_client.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/demos/run_demo.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/examples/01_persistent_guardrails.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/examples/02_configuration_and_correction.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/examples/03_ambiguity_with_error.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/examples/04_tool_governance_denylist.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/examples/05_llm_integration_pattern.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/examples/06_step_sequence_and_state_restore.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/examples/07_single_policy_correction.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/examples/README.md +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/examples/_util.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/host_support/__init__.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/host_support/provider_mode.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/src/context_compiler/__init__.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/src/context_compiler/const.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/src/context_compiler/decision_helpers.py +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/src/context_compiler/py.typed +0 -0
- {context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/src/context_compiler/repl.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: context-compiler
|
|
3
|
-
Version: 0.9.0.
|
|
3
|
+
Version: 0.9.0.dev10
|
|
4
4
|
Summary: Deterministic conversational state engine for LLM applications.
|
|
5
5
|
Project-URL: Homepage, https://github.com/rlippmann/context-compiler
|
|
6
6
|
Project-URL: Repository, https://github.com/rlippmann/context-compiler
|
|
@@ -414,10 +414,9 @@ Replacement:
|
|
|
414
414
|
User: use podman instead of docker
|
|
415
415
|
```
|
|
416
416
|
|
|
417
|
-
If `docker` is absent from saved state, that
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
semantically valid.
|
|
417
|
+
If `docker` is absent from saved state, that is a semantic `error`.
|
|
418
|
+
Canonical replacement requires an active existing source `use` policy and
|
|
419
|
+
does not degrade to plain `use podman`.
|
|
421
420
|
|
|
422
421
|
Removal and reset:
|
|
423
422
|
|
|
@@ -435,8 +434,8 @@ evaluation against authoritative state.
|
|
|
435
434
|
Pending continuation is a separate runtime layer. It may exist only after a
|
|
436
435
|
canonical directive reaches a supported semantic `error` case. It never
|
|
437
436
|
repairs malformed syntax or reinterprets non-canonical input as a directive.
|
|
438
|
-
An absent source item in a canonical replacement directive is
|
|
439
|
-
|
|
437
|
+
An absent source item in a canonical replacement directive is itself a
|
|
438
|
+
semantic `error` case and does not authorize degradation to plain `use`.
|
|
440
439
|
|
|
441
440
|
Examples:
|
|
442
441
|
|
|
@@ -392,10 +392,9 @@ Replacement:
|
|
|
392
392
|
User: use podman instead of docker
|
|
393
393
|
```
|
|
394
394
|
|
|
395
|
-
If `docker` is absent from saved state, that
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
semantically valid.
|
|
395
|
+
If `docker` is absent from saved state, that is a semantic `error`.
|
|
396
|
+
Canonical replacement requires an active existing source `use` policy and
|
|
397
|
+
does not degrade to plain `use podman`.
|
|
399
398
|
|
|
400
399
|
Removal and reset:
|
|
401
400
|
|
|
@@ -413,8 +412,8 @@ evaluation against authoritative state.
|
|
|
413
412
|
Pending continuation is a separate runtime layer. It may exist only after a
|
|
414
413
|
canonical directive reaches a supported semantic `error` case. It never
|
|
415
414
|
repairs malformed syntax or reinterprets non-canonical input as a directive.
|
|
416
|
-
An absent source item in a canonical replacement directive is
|
|
417
|
-
|
|
415
|
+
An absent source item in a canonical replacement directive is itself a
|
|
416
|
+
semantic `error` case and does not authorize degradation to plain `use`.
|
|
418
417
|
|
|
419
418
|
Examples:
|
|
420
419
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
"""Demo 8: missing-source replacement
|
|
1
|
+
"""Demo 8: missing-source replacement fails without mutating authoritative state."""
|
|
2
2
|
|
|
3
3
|
from collections.abc import Mapping
|
|
4
4
|
|
|
5
5
|
from context_compiler import (
|
|
6
6
|
Engine,
|
|
7
|
-
|
|
7
|
+
is_error,
|
|
8
8
|
)
|
|
9
9
|
from demos.common import (
|
|
10
10
|
build_baseline_messages,
|
|
@@ -22,7 +22,7 @@ from demos.common import (
|
|
|
22
22
|
)
|
|
23
23
|
from demos.llm_client import complete_messages
|
|
24
24
|
|
|
25
|
-
DEMO_NAME = "08_replacement_precondition — missing-source replacement
|
|
25
|
+
DEMO_NAME = "08_replacement_precondition — missing-source replacement requires active source"
|
|
26
26
|
USER_INPUT = "use podman instead of docker"
|
|
27
27
|
|
|
28
28
|
|
|
@@ -69,38 +69,38 @@ def main() -> None:
|
|
|
69
69
|
reinjected_output = complete_messages(reinjected_messages)
|
|
70
70
|
print_model_output("Reinjected-state", reinjected_output)
|
|
71
71
|
|
|
72
|
-
if
|
|
72
|
+
if is_error(decision):
|
|
73
73
|
print_messages("compiler-mediated (full)", [])
|
|
74
|
-
mediated_output = "[no call] authoritative state
|
|
74
|
+
mediated_output = "[no call] authoritative state blocked replacement without source use"
|
|
75
75
|
print_model_output("Compiler-mediated (full)", mediated_output)
|
|
76
76
|
else:
|
|
77
77
|
print_messages("compiler-mediated (full)", [])
|
|
78
|
-
mediated_output = "[no call] expected
|
|
78
|
+
mediated_output = "[no call] expected semantic replacement error was not produced"
|
|
79
79
|
print_model_output("Compiler-mediated (full)", mediated_output)
|
|
80
80
|
|
|
81
81
|
compacted_turns, compacted_state, compacted_prompt = compact_user_turns(user_inputs)
|
|
82
|
-
if compacted_prompt is None:
|
|
82
|
+
if compacted_prompt is not None:
|
|
83
83
|
print_messages("compiler-mediated + compact", [])
|
|
84
|
-
compact_output = "[no call]
|
|
84
|
+
compact_output = "[no call] unexpected error was produced during compaction"
|
|
85
85
|
print_model_output("Compiler-mediated + compact", compact_output)
|
|
86
86
|
else:
|
|
87
87
|
print_messages("compiler-mediated + compact", [])
|
|
88
|
-
compact_output = "[no call]
|
|
88
|
+
compact_output = "[no call] compaction preserved replacement error without state mutation"
|
|
89
89
|
print_model_output("Compiler-mediated + compact", compact_output)
|
|
90
90
|
|
|
91
91
|
premise, policies = observe_engine(engine)
|
|
92
92
|
compacted_premise, compacted_policies = state_observations(compacted_state)
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
state_preserved = _is_initial_authoritative_state(premise=premise, policies=policies)
|
|
94
|
+
compact_state_preserved = _is_initial_authoritative_state(
|
|
95
95
|
premise=compacted_premise,
|
|
96
96
|
policies=compacted_policies,
|
|
97
97
|
)
|
|
98
|
-
|
|
98
|
+
compact_error_preserved = compacted_prompt is not None
|
|
99
99
|
|
|
100
100
|
baseline_has_authoritative_precondition = False
|
|
101
101
|
reinjected_has_authoritative_precondition = False
|
|
102
|
-
compiler_pass =
|
|
103
|
-
compact_pass =
|
|
102
|
+
compiler_pass = is_error(decision) and state_preserved
|
|
103
|
+
compact_pass = compact_error_preserved and compact_state_preserved
|
|
104
104
|
|
|
105
105
|
print_host_check(
|
|
106
106
|
"BASELINE_AUTHORITATIVE_PRECONDITION",
|
|
@@ -114,12 +114,12 @@ def main() -> None:
|
|
|
114
114
|
)
|
|
115
115
|
print_host_check(
|
|
116
116
|
"COMPILER_BLOCKED_INVALID_REPLACEMENT",
|
|
117
|
-
yes_no(
|
|
117
|
+
yes_no(is_error(decision)),
|
|
118
118
|
context="compiler-mediated",
|
|
119
119
|
)
|
|
120
120
|
print_host_check(
|
|
121
|
-
"
|
|
122
|
-
yes_no(
|
|
121
|
+
"COMPILER_STATE_PRESERVED",
|
|
122
|
+
yes_no(state_preserved),
|
|
123
123
|
context="compiler-mediated",
|
|
124
124
|
)
|
|
125
125
|
|
|
@@ -130,18 +130,18 @@ def main() -> None:
|
|
|
130
130
|
compiler_pass=compiler_pass,
|
|
131
131
|
compiler_compact_pass=compact_pass,
|
|
132
132
|
expected=(
|
|
133
|
-
"missing-source replacement should
|
|
134
|
-
"
|
|
133
|
+
"missing-source replacement should return semantic error without mutating "
|
|
134
|
+
"authoritative state"
|
|
135
135
|
),
|
|
136
136
|
actual=(
|
|
137
|
-
"compiler
|
|
138
|
-
"still lack authoritative state enforcement"
|
|
137
|
+
"compiler blocked missing-source replacement without mutating state; baseline and "
|
|
138
|
+
"reinjected paths still lack authoritative state enforcement"
|
|
139
139
|
if compiler_pass and compact_pass
|
|
140
|
-
else "compiler did not consistently
|
|
140
|
+
else "compiler did not consistently enforce the replacement source precondition"
|
|
141
141
|
),
|
|
142
142
|
passed=compiler_pass and compact_pass,
|
|
143
|
-
result_pass="missing-source replacement
|
|
144
|
-
result_fail="missing-source replacement
|
|
143
|
+
result_pass="missing-source replacement was rejected without mutation",
|
|
144
|
+
result_fail="missing-source replacement was not rejected correctly",
|
|
145
145
|
)
|
|
146
146
|
|
|
147
147
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
"""Demo 9: confirmation-style
|
|
1
|
+
"""Demo 9: replacement errors do not create confirmation-style followup state."""
|
|
2
2
|
|
|
3
3
|
from collections.abc import Mapping
|
|
4
4
|
|
|
5
5
|
from context_compiler import (
|
|
6
6
|
Engine,
|
|
7
|
+
is_error,
|
|
7
8
|
is_no_directive,
|
|
8
|
-
is_update,
|
|
9
9
|
)
|
|
10
10
|
from demos.common import (
|
|
11
11
|
build_baseline_messages,
|
|
@@ -24,8 +24,7 @@ from demos.common import (
|
|
|
24
24
|
from demos.llm_client import complete_messages
|
|
25
25
|
|
|
26
26
|
DEMO_NAME = (
|
|
27
|
-
"09_confirmation_no_directive_boundary — "
|
|
28
|
-
"missing-source replacement does not create a confirmation state"
|
|
27
|
+
"09_confirmation_no_directive_boundary — replacement errors do not create a confirmation state"
|
|
29
28
|
)
|
|
30
29
|
TURN_1 = "use podman instead of docker"
|
|
31
30
|
TURN_2 = "maybe"
|
|
@@ -34,10 +33,6 @@ INITIAL_PREMISE: str | None = None
|
|
|
34
33
|
INITIAL_POLICIES: dict[str, str] = {}
|
|
35
34
|
|
|
36
35
|
|
|
37
|
-
def _has_podman_use(policies: Mapping[str, str]) -> bool:
|
|
38
|
-
return policies.get("podman") == "use"
|
|
39
|
-
|
|
40
|
-
|
|
41
36
|
def _is_initial_authoritative_state(*, premise: str | None, policies: Mapping[str, str]) -> bool:
|
|
42
37
|
return premise == INITIAL_PREMISE and dict(policies) == INITIAL_POLICIES
|
|
43
38
|
|
|
@@ -50,12 +45,17 @@ def main() -> None:
|
|
|
50
45
|
first = engine.step(TURN_1)
|
|
51
46
|
premise, policies = observe_engine(engine)
|
|
52
47
|
print_decision("turn 1", first, premise=premise, policies=policies)
|
|
53
|
-
|
|
48
|
+
state_preserved_after_first = _is_initial_authoritative_state(
|
|
49
|
+
premise=premise, policies=policies
|
|
50
|
+
)
|
|
54
51
|
|
|
55
52
|
second = engine.step(TURN_2)
|
|
56
53
|
premise, policies = observe_engine(engine)
|
|
57
54
|
print_decision("turn 2", second, premise=premise, policies=policies)
|
|
58
|
-
state_preserved_after_second =
|
|
55
|
+
state_preserved_after_second = _is_initial_authoritative_state(
|
|
56
|
+
premise=premise,
|
|
57
|
+
policies=policies,
|
|
58
|
+
)
|
|
59
59
|
|
|
60
60
|
third = engine.step(TURN_3)
|
|
61
61
|
premise, policies = observe_engine(engine)
|
|
@@ -96,7 +96,7 @@ def main() -> None:
|
|
|
96
96
|
compacted_turns, compacted_state, compacted_prompt = compact_user_turns(user_inputs)
|
|
97
97
|
if compacted_prompt is not None:
|
|
98
98
|
print_messages("compiler-mediated + compact", [])
|
|
99
|
-
compact_output = f"[no call] error
|
|
99
|
+
compact_output = f"[no call] replacement error preserved: {compacted_prompt}"
|
|
100
100
|
print_model_output("Compiler-mediated + compact", compact_output)
|
|
101
101
|
else:
|
|
102
102
|
print_messages("compiler-mediated + compact", [])
|
|
@@ -105,32 +105,30 @@ def main() -> None:
|
|
|
105
105
|
)
|
|
106
106
|
print_model_output("Compiler-mediated + compact", compact_output)
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
deterministic_initial_error = is_error(first) and state_preserved_after_first
|
|
109
109
|
unrelated_followup_no_directive = is_no_directive(second) and state_preserved_after_second
|
|
110
110
|
confirmation_token_not_consumed = is_no_directive(third)
|
|
111
111
|
premise, policies = observe_engine(engine)
|
|
112
|
-
deterministic_final_state =
|
|
112
|
+
deterministic_final_state = _is_initial_authoritative_state(premise=premise, policies=policies)
|
|
113
113
|
_, compacted_policies = state_observations(compacted_state)
|
|
114
114
|
|
|
115
115
|
baseline_has_confirmation_state_machine = False
|
|
116
116
|
reinjected_has_confirmation_state_machine = False
|
|
117
117
|
|
|
118
118
|
compiler_pass = (
|
|
119
|
-
|
|
119
|
+
deterministic_initial_error
|
|
120
120
|
and unrelated_followup_no_directive
|
|
121
121
|
and confirmation_token_not_consumed
|
|
122
122
|
and deterministic_final_state
|
|
123
123
|
)
|
|
124
124
|
|
|
125
125
|
compact_pass = (
|
|
126
|
-
compacted_prompt is None
|
|
127
|
-
and compacted_turns == [TURN_2, TURN_3]
|
|
128
|
-
and _has_podman_use(compacted_policies)
|
|
126
|
+
compacted_prompt is not None and compacted_turns == [TURN_1] and compacted_policies == {}
|
|
129
127
|
)
|
|
130
128
|
|
|
131
129
|
print_host_check(
|
|
132
|
-
"
|
|
133
|
-
yes_no(
|
|
130
|
+
"DETERMINISTIC_INITIAL_ERROR",
|
|
131
|
+
yes_no(deterministic_initial_error),
|
|
134
132
|
context="compiler-mediated",
|
|
135
133
|
)
|
|
136
134
|
print_host_check(
|
|
@@ -144,7 +142,7 @@ def main() -> None:
|
|
|
144
142
|
context="compiler-mediated",
|
|
145
143
|
)
|
|
146
144
|
print_host_check(
|
|
147
|
-
"
|
|
145
|
+
"FINAL_STATE_UNCHANGED",
|
|
148
146
|
yes_no(deterministic_final_state),
|
|
149
147
|
context="compiler-mediated",
|
|
150
148
|
)
|
|
@@ -156,19 +154,18 @@ def main() -> None:
|
|
|
156
154
|
compiler_pass=compiler_pass,
|
|
157
155
|
compiler_compact_pass=compact_pass,
|
|
158
156
|
expected=(
|
|
159
|
-
"
|
|
160
|
-
"
|
|
161
|
-
"no_directive"
|
|
157
|
+
"replacement error should not create an engine-owned confirmation state, "
|
|
158
|
+
"and later yes/no-style input should remain ordinary no_directive"
|
|
162
159
|
),
|
|
163
160
|
actual=(
|
|
164
|
-
"compiler
|
|
165
|
-
"
|
|
161
|
+
"compiler returned semantic error and treated later inputs as ordinary "
|
|
162
|
+
"no_directive without mutating state"
|
|
166
163
|
if compiler_pass and compact_pass
|
|
167
164
|
else "compiler did not consistently preserve the confirmation-no_directive boundary"
|
|
168
165
|
),
|
|
169
166
|
passed=compiler_pass and compact_pass,
|
|
170
|
-
result_pass="
|
|
171
|
-
result_fail="
|
|
167
|
+
result_pass="replacement error stayed outside engine-owned confirmation state",
|
|
168
|
+
result_fail="replacement error still behaved like engine-owned confirmation state",
|
|
172
169
|
)
|
|
173
170
|
|
|
174
171
|
|
|
@@ -19,7 +19,7 @@ from .const import (
|
|
|
19
19
|
STATE_PREMISE,
|
|
20
20
|
STATE_VERSION,
|
|
21
21
|
)
|
|
22
|
-
from .grammar import CanonicalDirective,
|
|
22
|
+
from .grammar import CanonicalDirective, DirectiveKind, decompose_directive
|
|
23
23
|
|
|
24
24
|
PolicyValue = Literal["use", "prohibit"]
|
|
25
25
|
|
|
@@ -129,61 +129,26 @@ class Engine:
|
|
|
129
129
|
) -> Decision | None:
|
|
130
130
|
candidate_state = self._state if state is None else state
|
|
131
131
|
# Single error path: all error outcomes are detected before any mutation.
|
|
132
|
-
if directive.kind in {_DirectiveKind.SET_PREMISE, _DirectiveKind.CHANGE_PREMISE}:
|
|
133
|
-
value = directive.operands["value"]
|
|
134
|
-
if _sanitize_premise_value(value) == "":
|
|
135
|
-
if directive.kind is _DirectiveKind.SET_PREMISE:
|
|
136
|
-
return _error(
|
|
137
|
-
"Premise value cannot be empty.\n"
|
|
138
|
-
"Use 'set premise <value>' with a non-empty value."
|
|
139
|
-
)
|
|
140
|
-
return _error(
|
|
141
|
-
"Premise value cannot be empty.\n"
|
|
142
|
-
"Use 'change premise to <value>' with a non-empty value."
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
if (
|
|
146
|
-
directive.kind is _DirectiveKind.REMOVE_POLICY
|
|
147
|
-
and _normalize_item(directive.operands["item"]) == ""
|
|
148
|
-
):
|
|
149
|
-
return _error(
|
|
150
|
-
"Policy item cannot be empty.\nUse 'remove policy <item>' with a non-empty value."
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
if (
|
|
154
|
-
directive.kind is _DirectiveKind.USE_ITEM
|
|
155
|
-
and _normalize_item(directive.operands["item"]) == ""
|
|
156
|
-
):
|
|
157
|
-
return _error("Policy item cannot be empty.\nUse 'use <item>' with a non-empty value.")
|
|
158
|
-
|
|
159
|
-
if (
|
|
160
|
-
directive.kind is _DirectiveKind.PROHIBIT_ITEM
|
|
161
|
-
and _normalize_item(directive.operands["item"]) == ""
|
|
162
|
-
):
|
|
163
|
-
return _error(
|
|
164
|
-
"Policy item cannot be empty.\nUse 'prohibit <item>' with a non-empty value."
|
|
165
|
-
)
|
|
166
|
-
|
|
167
132
|
if (
|
|
168
|
-
directive.kind is
|
|
133
|
+
directive.kind is DirectiveKind.SET_PREMISE
|
|
169
134
|
and candidate_state[STATE_PREMISE] is not None
|
|
170
135
|
):
|
|
171
136
|
return _error("Premise already set.\nUse 'change premise to <value>' to modify it.")
|
|
172
137
|
|
|
173
138
|
if (
|
|
174
|
-
directive.kind is
|
|
139
|
+
directive.kind is DirectiveKind.CHANGE_PREMISE
|
|
175
140
|
and candidate_state[STATE_PREMISE] is None
|
|
176
141
|
):
|
|
177
142
|
return _error("No premise is set.\nUse 'set premise <value>' to define one.")
|
|
178
143
|
|
|
179
|
-
if directive.kind is
|
|
144
|
+
if directive.kind is DirectiveKind.USE_ITEM:
|
|
180
145
|
item_key = _normalize_item(directive.operands["item"])
|
|
181
146
|
if candidate_state[STATE_POLICIES].get(item_key) == POLICY_PROHIBIT:
|
|
182
147
|
return _error(
|
|
183
148
|
f'"{item_key}" is currently prohibited.\nRemove or replace it before using it.'
|
|
184
149
|
)
|
|
185
150
|
|
|
186
|
-
if directive.kind is
|
|
151
|
+
if directive.kind is DirectiveKind.PROHIBIT_ITEM:
|
|
187
152
|
item_key = _normalize_item(directive.operands["item"])
|
|
188
153
|
if candidate_state[STATE_POLICIES].get(item_key) == POLICY_USE:
|
|
189
154
|
return _error(
|
|
@@ -191,7 +156,7 @@ class Engine:
|
|
|
191
156
|
"Remove or replace it before prohibiting it."
|
|
192
157
|
)
|
|
193
158
|
|
|
194
|
-
if directive.kind is
|
|
159
|
+
if directive.kind is DirectiveKind.REPLACE_USE:
|
|
195
160
|
new_item = directive.operands["new_item"]
|
|
196
161
|
old_item = directive.operands["old_item"]
|
|
197
162
|
new_key = _normalize_item(new_item)
|
|
@@ -211,7 +176,7 @@ class Engine:
|
|
|
211
176
|
f'"{new_item}" is currently prohibited.\n'
|
|
212
177
|
"Submit explicit directive(s) to remove it or use a different item."
|
|
213
178
|
)
|
|
214
|
-
if old_state
|
|
179
|
+
if old_state != POLICY_USE:
|
|
215
180
|
return _error(
|
|
216
181
|
f'"{old_item}" is not currently in use.\n'
|
|
217
182
|
"Replacement requires an active 'use' policy."
|
|
@@ -222,27 +187,27 @@ class Engine:
|
|
|
222
187
|
def _apply_directive(self, directive: CanonicalDirective, *, state: _State) -> _State:
|
|
223
188
|
next_state = deepcopy(state)
|
|
224
189
|
|
|
225
|
-
if directive.kind is
|
|
190
|
+
if directive.kind is DirectiveKind.SET_PREMISE:
|
|
226
191
|
next_state[STATE_PREMISE] = _sanitize_premise_value(directive.operands["value"])
|
|
227
192
|
return next_state
|
|
228
193
|
|
|
229
|
-
if directive.kind is
|
|
194
|
+
if directive.kind is DirectiveKind.CHANGE_PREMISE:
|
|
230
195
|
next_state[STATE_PREMISE] = _sanitize_premise_value(directive.operands["value"])
|
|
231
196
|
return next_state
|
|
232
197
|
|
|
233
|
-
if directive.kind is
|
|
198
|
+
if directive.kind is DirectiveKind.USE_ITEM:
|
|
234
199
|
item_key = _normalize_item(directive.operands["item"])
|
|
235
200
|
# Idempotent directives are updates even if state does not change.
|
|
236
201
|
next_state[STATE_POLICIES][item_key] = POLICY_USE
|
|
237
202
|
return next_state
|
|
238
203
|
|
|
239
|
-
if directive.kind is
|
|
204
|
+
if directive.kind is DirectiveKind.PROHIBIT_ITEM:
|
|
240
205
|
item_key = _normalize_item(directive.operands["item"])
|
|
241
206
|
# Idempotent directives are updates even if state does not change.
|
|
242
207
|
next_state[STATE_POLICIES][item_key] = POLICY_PROHIBIT
|
|
243
208
|
return next_state
|
|
244
209
|
|
|
245
|
-
if directive.kind is
|
|
210
|
+
if directive.kind is DirectiveKind.REPLACE_USE:
|
|
246
211
|
self._apply_replacement_explicit(
|
|
247
212
|
next_state,
|
|
248
213
|
directive.operands["new_item"],
|
|
@@ -250,16 +215,16 @@ class Engine:
|
|
|
250
215
|
)
|
|
251
216
|
return next_state
|
|
252
217
|
|
|
253
|
-
if directive.kind is
|
|
218
|
+
if directive.kind is DirectiveKind.REMOVE_POLICY:
|
|
254
219
|
item_key = _normalize_item(directive.operands["item"])
|
|
255
220
|
next_state[STATE_POLICIES].pop(item_key, None)
|
|
256
221
|
return next_state
|
|
257
222
|
|
|
258
|
-
if directive.kind is
|
|
223
|
+
if directive.kind is DirectiveKind.CLEAR_PREMISE:
|
|
259
224
|
next_state[STATE_PREMISE] = None
|
|
260
225
|
return next_state
|
|
261
226
|
|
|
262
|
-
if directive.kind is
|
|
227
|
+
if directive.kind is DirectiveKind.RESET_POLICIES:
|
|
263
228
|
next_state[STATE_POLICIES] = {}
|
|
264
229
|
return next_state
|
|
265
230
|
|
{context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/src/context_compiler/grammar.py
RENAMED
|
@@ -7,7 +7,7 @@ from enum import StrEnum
|
|
|
7
7
|
from types import MappingProxyType
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class
|
|
10
|
+
class DirectiveKind(StrEnum):
|
|
11
11
|
"""Enumerate the supported canonical directive families."""
|
|
12
12
|
|
|
13
13
|
SET_PREMISE = "set_premise"
|
|
@@ -21,6 +21,14 @@ class _DirectiveKind(StrEnum):
|
|
|
21
21
|
CLEAR_STATE = "clear_state"
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
class DirectiveSyntaxFailure(StrEnum):
|
|
25
|
+
"""Enumerate minimal grammar failure categories for directive-shaped input."""
|
|
26
|
+
|
|
27
|
+
COMPOUND_DIRECTIVE = "compound_directive"
|
|
28
|
+
MISSING_REQUIRED_OPERAND = "missing_required_operand"
|
|
29
|
+
MALFORMED_DIRECTIVE = "malformed_directive"
|
|
30
|
+
|
|
31
|
+
|
|
24
32
|
@dataclass(frozen=True, slots=True)
|
|
25
33
|
class CanonicalDirective:
|
|
26
34
|
"""Represent one parsed canonical directive and its named operands.
|
|
@@ -30,7 +38,7 @@ class CanonicalDirective:
|
|
|
30
38
|
"""
|
|
31
39
|
|
|
32
40
|
text: str
|
|
33
|
-
kind:
|
|
41
|
+
kind: DirectiveKind
|
|
34
42
|
operands: MappingProxyType[str, str]
|
|
35
43
|
|
|
36
44
|
|
|
@@ -38,20 +46,39 @@ class CanonicalDirective:
|
|
|
38
46
|
class InvalidDirectiveSyntax:
|
|
39
47
|
"""Represent directive-shaped input that fails canonical syntax parsing."""
|
|
40
48
|
|
|
49
|
+
failure: DirectiveSyntaxFailure = DirectiveSyntaxFailure.MALFORMED_DIRECTIVE
|
|
50
|
+
directive_kind: DirectiveKind | None = None
|
|
51
|
+
missing_operand: str | None = None
|
|
52
|
+
|
|
41
53
|
|
|
42
54
|
@dataclass(frozen=True, slots=True)
|
|
43
55
|
class _DirectiveSpec:
|
|
44
|
-
kind:
|
|
56
|
+
kind: DirectiveKind
|
|
57
|
+
canonical_start: str
|
|
45
58
|
operand_names: tuple[str, ...]
|
|
46
59
|
exact_text: str | None
|
|
47
60
|
renderer: Callable[[MappingProxyType[str, str]], str]
|
|
48
61
|
|
|
49
62
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
@dataclass(frozen=True, slots=True)
|
|
64
|
+
class DirectiveMetadata:
|
|
65
|
+
"""Describe one canonical directive family without exposing parser internals."""
|
|
66
|
+
|
|
67
|
+
kind: DirectiveKind
|
|
68
|
+
canonical_start: str
|
|
69
|
+
operand_names: tuple[str, ...]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
_SET_PREMISE_START = "set premise"
|
|
73
|
+
_CHANGE_PREMISE_START = "change premise to"
|
|
74
|
+
_USE_START = "use"
|
|
75
|
+
_PROHIBIT_START = "prohibit"
|
|
76
|
+
_REMOVE_POLICY_START = "remove policy"
|
|
77
|
+
_SET_PREMISE_PREFIX = f"{_SET_PREMISE_START} "
|
|
78
|
+
_CHANGE_PREMISE_PREFIX = f"{_CHANGE_PREMISE_START} "
|
|
79
|
+
_USE_PREFIX = f"{_USE_START} "
|
|
80
|
+
_PROHIBIT_PREFIX = f"{_PROHIBIT_START} "
|
|
81
|
+
_REMOVE_POLICY_PREFIX = f"{_REMOVE_POLICY_START} "
|
|
55
82
|
_CLEAR_PREMISE_TEXT = "clear premise"
|
|
56
83
|
_RESET_POLICIES_TEXT = "reset policies"
|
|
57
84
|
_CLEAR_STATE_TEXT = "clear state"
|
|
@@ -69,39 +96,6 @@ _REPLACE_RE = re.compile(
|
|
|
69
96
|
r"(?i)^use[ \t]+(?P<new_item>.*?)[ \t]+instead[ \t]+of[ \t]+(?P<old_item>.+)$"
|
|
70
97
|
)
|
|
71
98
|
|
|
72
|
-
_PREFIX_DIRECTIVE_STARTS: tuple[tuple[str, bool], ...] = (
|
|
73
|
-
(_CHANGE_PREMISE_PREFIX.removesuffix(" "), True),
|
|
74
|
-
(_SET_PREMISE_PREFIX.removesuffix(" "), True),
|
|
75
|
-
(_REMOVE_POLICY_PREFIX.removesuffix(" "), True),
|
|
76
|
-
(_PROHIBIT_PREFIX.removesuffix(" "), True),
|
|
77
|
-
(_USE_PREFIX.removesuffix(" "), True),
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
_EXACT_DIRECTIVE_STARTS: tuple[tuple[str, bool], ...] = (
|
|
81
|
-
(_RESET_POLICIES_TEXT, False),
|
|
82
|
-
(_CLEAR_PREMISE_TEXT, False),
|
|
83
|
-
(_CLEAR_STATE_TEXT, False),
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
_CANONICAL_DIRECTIVE_STARTS: tuple[tuple[str, bool], ...] = (
|
|
87
|
-
_PREFIX_DIRECTIVE_STARTS[0],
|
|
88
|
-
_PREFIX_DIRECTIVE_STARTS[1],
|
|
89
|
-
_PREFIX_DIRECTIVE_STARTS[2],
|
|
90
|
-
_EXACT_DIRECTIVE_STARTS[0],
|
|
91
|
-
_EXACT_DIRECTIVE_STARTS[1],
|
|
92
|
-
_EXACT_DIRECTIVE_STARTS[2],
|
|
93
|
-
_PREFIX_DIRECTIVE_STARTS[3],
|
|
94
|
-
_PREFIX_DIRECTIVE_STARTS[4],
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
_DIRECTIVE_FAMILY_STARTS: tuple[tuple[str, bool], ...] = (
|
|
98
|
-
(_CHANGE_PREMISE_FAMILY, True),
|
|
99
|
-
_PREFIX_DIRECTIVE_STARTS[1],
|
|
100
|
-
_PREFIX_DIRECTIVE_STARTS[2],
|
|
101
|
-
*_EXACT_DIRECTIVE_STARTS,
|
|
102
|
-
*_PREFIX_DIRECTIVE_STARTS[3:],
|
|
103
|
-
)
|
|
104
|
-
|
|
105
99
|
|
|
106
100
|
def _render_with_prefix(
|
|
107
101
|
prefix: str, operand_name: str
|
|
@@ -126,56 +120,65 @@ def _render_exact(text: str) -> Callable[[MappingProxyType[str, str]], str]:
|
|
|
126
120
|
|
|
127
121
|
_DIRECTIVE_SPECS = MappingProxyType(
|
|
128
122
|
{
|
|
129
|
-
|
|
130
|
-
kind=
|
|
123
|
+
DirectiveKind.SET_PREMISE: _DirectiveSpec(
|
|
124
|
+
kind=DirectiveKind.SET_PREMISE,
|
|
125
|
+
canonical_start=_SET_PREMISE_START,
|
|
131
126
|
operand_names=("value",),
|
|
132
127
|
exact_text=None,
|
|
133
128
|
renderer=_render_with_prefix(_SET_PREMISE_PREFIX, "value"),
|
|
134
129
|
),
|
|
135
|
-
|
|
136
|
-
kind=
|
|
130
|
+
DirectiveKind.CHANGE_PREMISE: _DirectiveSpec(
|
|
131
|
+
kind=DirectiveKind.CHANGE_PREMISE,
|
|
132
|
+
canonical_start=_CHANGE_PREMISE_START,
|
|
137
133
|
operand_names=("value",),
|
|
138
134
|
exact_text=None,
|
|
139
135
|
renderer=_render_with_prefix(_CHANGE_PREMISE_PREFIX, "value"),
|
|
140
136
|
),
|
|
141
|
-
|
|
142
|
-
kind=
|
|
137
|
+
DirectiveKind.USE_ITEM: _DirectiveSpec(
|
|
138
|
+
kind=DirectiveKind.USE_ITEM,
|
|
139
|
+
canonical_start=_USE_START,
|
|
143
140
|
operand_names=("item",),
|
|
144
141
|
exact_text=None,
|
|
145
142
|
renderer=_render_with_prefix(_USE_PREFIX, "item"),
|
|
146
143
|
),
|
|
147
|
-
|
|
148
|
-
kind=
|
|
144
|
+
DirectiveKind.PROHIBIT_ITEM: _DirectiveSpec(
|
|
145
|
+
kind=DirectiveKind.PROHIBIT_ITEM,
|
|
146
|
+
canonical_start=_PROHIBIT_START,
|
|
149
147
|
operand_names=("item",),
|
|
150
148
|
exact_text=None,
|
|
151
149
|
renderer=_render_with_prefix(_PROHIBIT_PREFIX, "item"),
|
|
152
150
|
),
|
|
153
|
-
|
|
154
|
-
kind=
|
|
151
|
+
DirectiveKind.REMOVE_POLICY: _DirectiveSpec(
|
|
152
|
+
kind=DirectiveKind.REMOVE_POLICY,
|
|
153
|
+
canonical_start=_REMOVE_POLICY_START,
|
|
155
154
|
operand_names=("item",),
|
|
156
155
|
exact_text=None,
|
|
157
156
|
renderer=_render_with_prefix(_REMOVE_POLICY_PREFIX, "item"),
|
|
158
157
|
),
|
|
159
|
-
|
|
160
|
-
kind=
|
|
158
|
+
DirectiveKind.REPLACE_USE: _DirectiveSpec(
|
|
159
|
+
kind=DirectiveKind.REPLACE_USE,
|
|
160
|
+
canonical_start=_USE_START,
|
|
161
161
|
operand_names=("new_item", "old_item"),
|
|
162
162
|
exact_text=None,
|
|
163
163
|
renderer=_render_replace_use,
|
|
164
164
|
),
|
|
165
|
-
|
|
166
|
-
kind=
|
|
165
|
+
DirectiveKind.CLEAR_PREMISE: _DirectiveSpec(
|
|
166
|
+
kind=DirectiveKind.CLEAR_PREMISE,
|
|
167
|
+
canonical_start=_CLEAR_PREMISE_TEXT,
|
|
167
168
|
operand_names=(),
|
|
168
169
|
exact_text=_CLEAR_PREMISE_TEXT,
|
|
169
170
|
renderer=_render_exact(_CLEAR_PREMISE_TEXT),
|
|
170
171
|
),
|
|
171
|
-
|
|
172
|
-
kind=
|
|
172
|
+
DirectiveKind.RESET_POLICIES: _DirectiveSpec(
|
|
173
|
+
kind=DirectiveKind.RESET_POLICIES,
|
|
174
|
+
canonical_start=_RESET_POLICIES_TEXT,
|
|
173
175
|
operand_names=(),
|
|
174
176
|
exact_text=_RESET_POLICIES_TEXT,
|
|
175
177
|
renderer=_render_exact(_RESET_POLICIES_TEXT),
|
|
176
178
|
),
|
|
177
|
-
|
|
178
|
-
kind=
|
|
179
|
+
DirectiveKind.CLEAR_STATE: _DirectiveSpec(
|
|
180
|
+
kind=DirectiveKind.CLEAR_STATE,
|
|
181
|
+
canonical_start=_CLEAR_STATE_TEXT,
|
|
179
182
|
operand_names=(),
|
|
180
183
|
exact_text=_CLEAR_STATE_TEXT,
|
|
181
184
|
renderer=_render_exact(_CLEAR_STATE_TEXT),
|
|
@@ -184,6 +187,64 @@ _DIRECTIVE_SPECS = MappingProxyType(
|
|
|
184
187
|
)
|
|
185
188
|
|
|
186
189
|
|
|
190
|
+
def _starts_with_descriptor(spec: _DirectiveSpec) -> tuple[str, bool]:
|
|
191
|
+
return (spec.canonical_start, bool(spec.operand_names))
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _unique_start_descriptors(specs: tuple[_DirectiveSpec, ...]) -> tuple[tuple[str, bool], ...]:
|
|
195
|
+
descriptors: list[tuple[str, bool]] = []
|
|
196
|
+
seen: set[tuple[str, bool]] = set()
|
|
197
|
+
for spec in specs:
|
|
198
|
+
descriptor = _starts_with_descriptor(spec)
|
|
199
|
+
if descriptor not in seen:
|
|
200
|
+
seen.add(descriptor)
|
|
201
|
+
descriptors.append(descriptor)
|
|
202
|
+
return tuple(descriptors)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
_CANONICAL_START_ORDER = (
|
|
206
|
+
DirectiveKind.CHANGE_PREMISE,
|
|
207
|
+
DirectiveKind.SET_PREMISE,
|
|
208
|
+
DirectiveKind.REMOVE_POLICY,
|
|
209
|
+
DirectiveKind.RESET_POLICIES,
|
|
210
|
+
DirectiveKind.CLEAR_PREMISE,
|
|
211
|
+
DirectiveKind.CLEAR_STATE,
|
|
212
|
+
DirectiveKind.PROHIBIT_ITEM,
|
|
213
|
+
DirectiveKind.USE_ITEM,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
_CANONICAL_DIRECTIVE_STARTS = _unique_start_descriptors(
|
|
217
|
+
tuple(_DIRECTIVE_SPECS[kind] for kind in _CANONICAL_START_ORDER)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
_DIRECTIVE_FAMILY_STARTS = (
|
|
221
|
+
(_CHANGE_PREMISE_FAMILY, True),
|
|
222
|
+
*_unique_start_descriptors(
|
|
223
|
+
tuple(
|
|
224
|
+
_DIRECTIVE_SPECS[kind]
|
|
225
|
+
for kind in (
|
|
226
|
+
DirectiveKind.SET_PREMISE,
|
|
227
|
+
DirectiveKind.REMOVE_POLICY,
|
|
228
|
+
DirectiveKind.RESET_POLICIES,
|
|
229
|
+
DirectiveKind.CLEAR_PREMISE,
|
|
230
|
+
DirectiveKind.CLEAR_STATE,
|
|
231
|
+
DirectiveKind.PROHIBIT_ITEM,
|
|
232
|
+
DirectiveKind.USE_ITEM,
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
),
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
_PUBLIC_DIRECTIVE_METADATA = tuple(
|
|
239
|
+
DirectiveMetadata(
|
|
240
|
+
kind=spec.kind,
|
|
241
|
+
canonical_start=spec.canonical_start,
|
|
242
|
+
operand_names=spec.operand_names,
|
|
243
|
+
)
|
|
244
|
+
for spec in _DIRECTIVE_SPECS.values()
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
187
248
|
def _trim_ascii_whitespace(text: str) -> str:
|
|
188
249
|
return text.strip(_ASCII_WHITESPACE)
|
|
189
250
|
|
|
@@ -305,11 +366,24 @@ def _parse_replace_use(trimmed_text: str) -> CanonicalDirective | None:
|
|
|
305
366
|
return None
|
|
306
367
|
return CanonicalDirective(
|
|
307
368
|
text=trimmed_text,
|
|
308
|
-
kind=
|
|
369
|
+
kind=DirectiveKind.REPLACE_USE,
|
|
309
370
|
operands=MappingProxyType({"new_item": new_item, "old_item": old_item}),
|
|
310
371
|
)
|
|
311
372
|
|
|
312
373
|
|
|
374
|
+
def _invalid_directive_syntax(
|
|
375
|
+
failure: DirectiveSyntaxFailure,
|
|
376
|
+
*,
|
|
377
|
+
directive_kind: DirectiveKind | None = None,
|
|
378
|
+
missing_operand: str | None = None,
|
|
379
|
+
) -> InvalidDirectiveSyntax:
|
|
380
|
+
return InvalidDirectiveSyntax(
|
|
381
|
+
failure=failure,
|
|
382
|
+
directive_kind=directive_kind,
|
|
383
|
+
missing_operand=missing_operand,
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
|
|
313
387
|
def decompose_directive(text: str) -> CanonicalDirective | InvalidDirectiveSyntax | None:
|
|
314
388
|
"""Parse one canonical directive into its semantic kind and operands.
|
|
315
389
|
|
|
@@ -326,50 +400,76 @@ def decompose_directive(text: str) -> CanonicalDirective | InvalidDirectiveSynta
|
|
|
326
400
|
if not _starts_with_directive_family(trimmed_text):
|
|
327
401
|
return None
|
|
328
402
|
if _contains_multiple_canonical_directives(trimmed_text):
|
|
329
|
-
return
|
|
330
|
-
|
|
331
|
-
invalid_result = InvalidDirectiveSyntax()
|
|
403
|
+
return _invalid_directive_syntax(DirectiveSyntaxFailure.COMPOUND_DIRECTIVE)
|
|
332
404
|
|
|
333
405
|
normalized = _normalized_for_matching(trimmed_text)
|
|
334
406
|
|
|
335
407
|
if normalized == _CLEAR_PREMISE_TEXT:
|
|
336
408
|
return CanonicalDirective(
|
|
337
|
-
text=text, kind=
|
|
409
|
+
text=text, kind=DirectiveKind.CLEAR_PREMISE, operands=MappingProxyType({})
|
|
338
410
|
)
|
|
339
411
|
if normalized == _RESET_POLICIES_TEXT:
|
|
340
412
|
return CanonicalDirective(
|
|
341
413
|
text=text,
|
|
342
|
-
kind=
|
|
414
|
+
kind=DirectiveKind.RESET_POLICIES,
|
|
343
415
|
operands=MappingProxyType({}),
|
|
344
416
|
)
|
|
345
417
|
if normalized == _CLEAR_STATE_TEXT:
|
|
346
418
|
return CanonicalDirective(
|
|
347
|
-
text=text, kind=
|
|
419
|
+
text=text, kind=DirectiveKind.CLEAR_STATE, operands=MappingProxyType({})
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
if normalized == "set premise":
|
|
423
|
+
return _invalid_directive_syntax(
|
|
424
|
+
DirectiveSyntaxFailure.MISSING_REQUIRED_OPERAND,
|
|
425
|
+
directive_kind=DirectiveKind.SET_PREMISE,
|
|
426
|
+
missing_operand="value",
|
|
348
427
|
)
|
|
349
428
|
|
|
350
429
|
if normalized.startswith("set premise "):
|
|
351
430
|
match = _SET_PREMISE_RE.fullmatch(trimmed_text)
|
|
352
431
|
if match is None:
|
|
353
|
-
return
|
|
432
|
+
return _invalid_directive_syntax(
|
|
433
|
+
DirectiveSyntaxFailure.MALFORMED_DIRECTIVE,
|
|
434
|
+
directive_kind=DirectiveKind.SET_PREMISE,
|
|
435
|
+
)
|
|
354
436
|
value = match.group("value")
|
|
355
437
|
if not _operand_has_content(value) or _operand_starts_with_token(value, "to"):
|
|
356
|
-
return
|
|
438
|
+
return _invalid_directive_syntax(
|
|
439
|
+
DirectiveSyntaxFailure.MALFORMED_DIRECTIVE,
|
|
440
|
+
directive_kind=DirectiveKind.SET_PREMISE,
|
|
441
|
+
)
|
|
357
442
|
return CanonicalDirective(
|
|
358
443
|
text=text,
|
|
359
|
-
kind=
|
|
444
|
+
kind=DirectiveKind.SET_PREMISE,
|
|
360
445
|
operands=MappingProxyType({"value": value}),
|
|
361
446
|
)
|
|
362
447
|
|
|
448
|
+
if normalized == "change premise to":
|
|
449
|
+
return _invalid_directive_syntax(
|
|
450
|
+
DirectiveSyntaxFailure.MISSING_REQUIRED_OPERAND,
|
|
451
|
+
directive_kind=DirectiveKind.CHANGE_PREMISE,
|
|
452
|
+
missing_operand="value",
|
|
453
|
+
)
|
|
454
|
+
|
|
363
455
|
if normalized.startswith("change premise to "):
|
|
364
456
|
match = _CHANGE_PREMISE_RE.fullmatch(trimmed_text)
|
|
365
457
|
if match is None:
|
|
366
|
-
return
|
|
458
|
+
return _invalid_directive_syntax(
|
|
459
|
+
DirectiveSyntaxFailure.MISSING_REQUIRED_OPERAND,
|
|
460
|
+
directive_kind=DirectiveKind.CHANGE_PREMISE,
|
|
461
|
+
missing_operand="value",
|
|
462
|
+
)
|
|
367
463
|
value = match.group("value")
|
|
368
464
|
if not _operand_has_content(value):
|
|
369
|
-
return
|
|
465
|
+
return _invalid_directive_syntax(
|
|
466
|
+
DirectiveSyntaxFailure.MISSING_REQUIRED_OPERAND,
|
|
467
|
+
directive_kind=DirectiveKind.CHANGE_PREMISE,
|
|
468
|
+
missing_operand="value",
|
|
469
|
+
)
|
|
370
470
|
return CanonicalDirective(
|
|
371
471
|
text=text,
|
|
372
|
-
kind=
|
|
472
|
+
kind=DirectiveKind.CHANGE_PREMISE,
|
|
373
473
|
operands=MappingProxyType({"value": value}),
|
|
374
474
|
)
|
|
375
475
|
|
|
@@ -377,58 +477,121 @@ def decompose_directive(text: str) -> CanonicalDirective | InvalidDirectiveSynta
|
|
|
377
477
|
if replacement is not None:
|
|
378
478
|
return replacement
|
|
379
479
|
|
|
480
|
+
if normalized == "use":
|
|
481
|
+
return _invalid_directive_syntax(
|
|
482
|
+
DirectiveSyntaxFailure.MISSING_REQUIRED_OPERAND,
|
|
483
|
+
directive_kind=DirectiveKind.USE_ITEM,
|
|
484
|
+
missing_operand="item",
|
|
485
|
+
)
|
|
486
|
+
|
|
380
487
|
if normalized.startswith("use "):
|
|
381
488
|
match = _USE_RE.fullmatch(trimmed_text)
|
|
382
489
|
if match is None:
|
|
383
|
-
return
|
|
490
|
+
return _invalid_directive_syntax(
|
|
491
|
+
DirectiveSyntaxFailure.MISSING_REQUIRED_OPERAND,
|
|
492
|
+
directive_kind=DirectiveKind.USE_ITEM,
|
|
493
|
+
missing_operand="item",
|
|
494
|
+
)
|
|
384
495
|
item = match.group("item")
|
|
385
496
|
normalized_item = _normalized_for_matching(item)
|
|
386
|
-
if (
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
497
|
+
if not _operand_has_content(item):
|
|
498
|
+
return _invalid_directive_syntax(
|
|
499
|
+
DirectiveSyntaxFailure.MISSING_REQUIRED_OPERAND,
|
|
500
|
+
directive_kind=DirectiveKind.USE_ITEM,
|
|
501
|
+
missing_operand="item",
|
|
502
|
+
)
|
|
503
|
+
if normalized_item == "instead of" or normalized_item.startswith("instead of "):
|
|
504
|
+
return _invalid_directive_syntax(
|
|
505
|
+
DirectiveSyntaxFailure.MISSING_REQUIRED_OPERAND,
|
|
506
|
+
directive_kind=DirectiveKind.REPLACE_USE,
|
|
507
|
+
missing_operand="new_item",
|
|
508
|
+
)
|
|
509
|
+
if normalized_item.endswith(" instead of"):
|
|
510
|
+
return _invalid_directive_syntax(
|
|
511
|
+
DirectiveSyntaxFailure.MISSING_REQUIRED_OPERAND,
|
|
512
|
+
directive_kind=DirectiveKind.REPLACE_USE,
|
|
513
|
+
missing_operand="old_item",
|
|
514
|
+
)
|
|
515
|
+
if _INSTEAD_OF_DELIMITER in normalized_item:
|
|
516
|
+
return _invalid_directive_syntax(
|
|
517
|
+
DirectiveSyntaxFailure.MALFORMED_DIRECTIVE,
|
|
518
|
+
directive_kind=DirectiveKind.USE_ITEM,
|
|
519
|
+
)
|
|
393
520
|
return CanonicalDirective(
|
|
394
521
|
text=text,
|
|
395
|
-
kind=
|
|
522
|
+
kind=DirectiveKind.USE_ITEM,
|
|
396
523
|
operands=MappingProxyType({"item": item}),
|
|
397
524
|
)
|
|
398
525
|
|
|
526
|
+
if normalized == "prohibit":
|
|
527
|
+
return _invalid_directive_syntax(
|
|
528
|
+
DirectiveSyntaxFailure.MISSING_REQUIRED_OPERAND,
|
|
529
|
+
directive_kind=DirectiveKind.PROHIBIT_ITEM,
|
|
530
|
+
missing_operand="item",
|
|
531
|
+
)
|
|
532
|
+
|
|
399
533
|
if normalized.startswith("prohibit "):
|
|
400
534
|
match = _PROHIBIT_RE.fullmatch(trimmed_text)
|
|
401
535
|
if match is None:
|
|
402
|
-
return
|
|
536
|
+
return _invalid_directive_syntax(
|
|
537
|
+
DirectiveSyntaxFailure.MISSING_REQUIRED_OPERAND,
|
|
538
|
+
directive_kind=DirectiveKind.PROHIBIT_ITEM,
|
|
539
|
+
missing_operand="item",
|
|
540
|
+
)
|
|
403
541
|
item = match.group("item")
|
|
404
542
|
if not _operand_has_content(item):
|
|
405
|
-
return
|
|
543
|
+
return _invalid_directive_syntax(
|
|
544
|
+
DirectiveSyntaxFailure.MISSING_REQUIRED_OPERAND,
|
|
545
|
+
directive_kind=DirectiveKind.PROHIBIT_ITEM,
|
|
546
|
+
missing_operand="item",
|
|
547
|
+
)
|
|
406
548
|
return CanonicalDirective(
|
|
407
549
|
text=text,
|
|
408
|
-
kind=
|
|
550
|
+
kind=DirectiveKind.PROHIBIT_ITEM,
|
|
409
551
|
operands=MappingProxyType({"item": item}),
|
|
410
552
|
)
|
|
411
553
|
|
|
554
|
+
if normalized == "remove policy":
|
|
555
|
+
return _invalid_directive_syntax(
|
|
556
|
+
DirectiveSyntaxFailure.MISSING_REQUIRED_OPERAND,
|
|
557
|
+
directive_kind=DirectiveKind.REMOVE_POLICY,
|
|
558
|
+
missing_operand="item",
|
|
559
|
+
)
|
|
560
|
+
|
|
412
561
|
if normalized.startswith("remove policy "):
|
|
413
562
|
match = _REMOVE_POLICY_RE.fullmatch(trimmed_text)
|
|
414
563
|
if match is None:
|
|
415
|
-
return
|
|
564
|
+
return _invalid_directive_syntax(
|
|
565
|
+
DirectiveSyntaxFailure.MISSING_REQUIRED_OPERAND,
|
|
566
|
+
directive_kind=DirectiveKind.REMOVE_POLICY,
|
|
567
|
+
missing_operand="item",
|
|
568
|
+
)
|
|
416
569
|
item = match.group("item")
|
|
417
570
|
if not _operand_has_content(item):
|
|
418
|
-
return
|
|
571
|
+
return _invalid_directive_syntax(
|
|
572
|
+
DirectiveSyntaxFailure.MISSING_REQUIRED_OPERAND,
|
|
573
|
+
directive_kind=DirectiveKind.REMOVE_POLICY,
|
|
574
|
+
missing_operand="item",
|
|
575
|
+
)
|
|
419
576
|
return CanonicalDirective(
|
|
420
577
|
text=text,
|
|
421
|
-
kind=
|
|
578
|
+
kind=DirectiveKind.REMOVE_POLICY,
|
|
422
579
|
operands=MappingProxyType({"item": item}),
|
|
423
580
|
)
|
|
424
581
|
|
|
425
|
-
return
|
|
582
|
+
return _invalid_directive_syntax(DirectiveSyntaxFailure.MALFORMED_DIRECTIVE)
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def get_directive_metadata() -> tuple[DirectiveMetadata, ...]:
|
|
586
|
+
"""Return immutable public directive metadata derived from internal specs."""
|
|
587
|
+
|
|
588
|
+
return _PUBLIC_DIRECTIVE_METADATA
|
|
426
589
|
|
|
427
590
|
|
|
428
|
-
def _render_directive(kind:
|
|
591
|
+
def _render_directive(kind: DirectiveKind | str, /, **operands: str) -> str:
|
|
429
592
|
"""Produce canonical directive text from a semantic kind and operands."""
|
|
430
593
|
try:
|
|
431
|
-
normalized_kind = kind if isinstance(kind,
|
|
594
|
+
normalized_kind = kind if isinstance(kind, DirectiveKind) else DirectiveKind(kind)
|
|
432
595
|
spec = _DIRECTIVE_SPECS[normalized_kind]
|
|
433
596
|
except (KeyError, ValueError) as exc:
|
|
434
597
|
raise ValueError(f"Unsupported directive kind: {kind!r}") from exc
|
|
@@ -462,7 +625,11 @@ def _render_directive(kind: _DirectiveKind | str, /, **operands: str) -> str:
|
|
|
462
625
|
|
|
463
626
|
|
|
464
627
|
__all__ = [
|
|
628
|
+
"DirectiveKind",
|
|
629
|
+
"DirectiveSyntaxFailure",
|
|
630
|
+
"DirectiveMetadata",
|
|
465
631
|
"CanonicalDirective",
|
|
466
632
|
"InvalidDirectiveSyntax",
|
|
633
|
+
"get_directive_metadata",
|
|
467
634
|
"decompose_directive",
|
|
468
635
|
]
|
|
File without changes
|
|
File without changes
|
{context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/demos/01_llm_contradiction_error.py
RENAMED
|
File without changes
|
{context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/demos/02_llm_constraint_guardrail.py
RENAMED
|
File without changes
|
{context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/demos/03_llm_premise_guardrail.py
RENAMED
|
File without changes
|
{context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/demos/04_llm_tool_denylist_guardrail.py
RENAMED
|
File without changes
|
{context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/demos/05_llm_prompt_drift_vs_state.py
RENAMED
|
File without changes
|
{context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/demos/06_llm_context_compaction.py
RENAMED
|
File without changes
|
{context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/demos/07_llm_prompt_vs_state.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/examples/01_persistent_guardrails.py
RENAMED
|
File without changes
|
|
File without changes
|
{context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/examples/03_ambiguity_with_error.py
RENAMED
|
File without changes
|
{context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/examples/04_tool_governance_denylist.py
RENAMED
|
File without changes
|
{context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/examples/05_llm_integration_pattern.py
RENAMED
|
File without changes
|
|
File without changes
|
{context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/examples/07_single_policy_correction.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{context_compiler-0.9.0.dev8 → context_compiler-0.9.0.dev10}/src/context_compiler/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|