From b67ab66fee50d47c606e7b68d2ddc4ce4486b974 Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Tue, 25 Aug 2026 23:49:25 -0400 Subject: [PATCH 1/7] test: sync Python grammar namespace contract --- tests/fixtures/.source-commit | 2 +- .../conformance/api/public-api-v2.json | 155 ++++++++++++++---- 2 files changed, 122 insertions(+), 35 deletions(-) diff --git a/tests/fixtures/.source-commit b/tests/fixtures/.source-commit index 4d86ee8..283d8f3 100644 --- a/tests/fixtures/.source-commit +++ b/tests/fixtures/.source-commit @@ -1 +1 @@ -e1e04bd6464aa46f5e3693d634faaa264d890f66 +8d8f77202c235e7050eab93b30a36a6e1c0c6f72 diff --git a/tests/fixtures/conformance/api/public-api-v2.json b/tests/fixtures/conformance/api/public-api-v2.json index 1c1a2f7..a3a4dd2 100644 --- a/tests/fixtures/conformance/api/public-api-v2.json +++ b/tests/fixtures/conformance/api/public-api-v2.json @@ -90,16 +90,26 @@ "return_shape": { "kind": "decision_variant", "type": "NoDirectiveDecision", - "required_attributes": ["kind"] + "required_attributes": [ + "kind" + ] } }, { - "args": ["unexpected"], - "raises": {"type": "TypeError"} + "args": [ + "unexpected" + ], + "raises": { + "type": "TypeError" + } }, { - "kwargs": {"value": "unexpected"}, - "raises": {"type": "TypeError"} + "kwargs": { + "value": "unexpected" + }, + "raises": { + "type": "TypeError" + } } ] }, @@ -107,16 +117,32 @@ "kind": "class", "signature": { "params": [ - {"name": "failure", "kind": "POSITIONAL_OR_KEYWORD", "has_default": false}, - {"name": "directive", "kind": "POSITIONAL_OR_KEYWORD", "has_default": false}, - {"name": "repairs", "kind": "POSITIONAL_OR_KEYWORD", "has_default": true} + { + "name": "failure", + "kind": "POSITIONAL_OR_KEYWORD", + "has_default": false + }, + { + "name": "directive", + "kind": "POSITIONAL_OR_KEYWORD", + "has_default": false + }, + { + "name": "repairs", + "kind": "POSITIONAL_OR_KEYWORD", + "has_default": true + } ] }, "construction_probes": [ { "kwargs": { - "failure": {"fixture": "item_prohibited_failure"}, - "directive": {"fixture": "use_docker_directive"} + "failure": { + "fixture": "item_prohibited_failure" + }, + "directive": { + "fixture": "use_docker_directive" + } }, "return_shape": { "kind": "decision_variant", @@ -132,26 +158,44 @@ }, { "args": [ - {"fixture": "item_prohibited_failure"}, - {"fixture": "use_docker_directive"} + { + "fixture": "item_prohibited_failure" + }, + { + "fixture": "use_docker_directive" + } ], "return_shape": { "kind": "decision_variant", "type": "SemanticErrorDecision", - "required_attributes": ["kind", "failure", "directive", "repairs", "message"] + "required_attributes": [ + "kind", + "failure", + "directive", + "repairs", + "message" + ] } }, { "args": [], - "raises": {"type": "TypeError"} + "raises": { + "type": "TypeError" + } }, { "kwargs": { - "failure": {"fixture": "item_prohibited_failure"}, - "directive": {"fixture": "use_docker_directive"}, + "failure": { + "fixture": "item_prohibited_failure" + }, + "directive": { + "fixture": "use_docker_directive" + }, "unexpected": true }, - "raises": {"type": "TypeError"} + "raises": { + "type": "TypeError" + } } ] }, @@ -172,33 +216,53 @@ "kind": "class", "signature": { "params": [ - {"name": "changed", "kind": "POSITIONAL_OR_KEYWORD", "has_default": false} + { + "name": "changed", + "kind": "POSITIONAL_OR_KEYWORD", + "has_default": false + } ] }, "construction_probes": [ { - "kwargs": {"changed": true}, + "kwargs": { + "changed": true + }, "return_shape": { "kind": "decision_variant", "type": "UpdateDecision", - "required_attributes": ["kind", "changed"] + "required_attributes": [ + "kind", + "changed" + ] } }, { - "args": [true], + "args": [ + true + ], "return_shape": { "kind": "decision_variant", "type": "UpdateDecision", - "required_attributes": ["kind", "changed"] + "required_attributes": [ + "kind", + "changed" + ] } }, { "args": [], - "raises": {"type": "TypeError"} + "raises": { + "type": "TypeError" + } }, { - "kwargs": {"value": true}, - "raises": {"type": "TypeError"} + "kwargs": { + "value": true + }, + "raises": { + "type": "TypeError" + } } ] }, @@ -221,15 +285,25 @@ }, "construction_probes": [ { - "return_shape": {"kind": "engine_instance"} + "return_shape": { + "kind": "engine_instance" + } }, { - "args": ["unexpected"], - "raises": {"type": "TypeError"} + "args": [ + "unexpected" + ], + "raises": { + "type": "TypeError" + } }, { - "kwargs": {"state": {}}, - "raises": {"type": "TypeError"} + "kwargs": { + "state": {} + }, + "raises": { + "type": "TypeError" + } } ] }, @@ -274,25 +348,33 @@ }, "probes": [ { - "args": ["use docker"], + "args": [ + "use docker" + ], "raises": { "type": "AttributeError" } }, { - "args": [null], + "args": [ + null + ], "raises": { "type": "AttributeError" } }, { - "args": [1], + "args": [ + 1 + ], "raises": { "type": "AttributeError" } }, { - "args": [{}], + "args": [ + {} + ], "raises": { "type": "AttributeError" } @@ -336,5 +418,10 @@ } } } + }, + "namespaces": { + "context_compiler.grammar": { + "contract": "public-grammar-v1" + } } } From 2fd7b6174ef461589caafc5c05a64f39c90c3604 Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Tue, 25 Aug 2026 23:51:20 -0400 Subject: [PATCH 2/7] test: consume grammar namespace contract --- tests/api_parity.test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/api_parity.test.ts b/tests/api_parity.test.ts index dfa678f..8a7941c 100644 --- a/tests/api_parity.test.ts +++ b/tests/api_parity.test.ts @@ -4,6 +4,7 @@ import { resolve } from 'node:path'; import * as ts from 'typescript'; import { describe, expect, it } from 'vitest'; import * as cc from '../src/index.js'; +import * as grammar from '../src/grammar.js'; import { CanonicalDirective } from '../src/grammar.js'; import type { Decision } from '../src/index.js'; @@ -71,6 +72,7 @@ type ApiContractFixture = { members: Record; }; }; + namespaces?: Record; }; function getGeneratedDeclarationExportNames(): Map { @@ -106,6 +108,13 @@ function loadApiContractFixture(): ApiContractFixture { return JSON.parse(raw) as ApiContractFixture; } +function loadGrammarContract(): { exports: { names: string[] } } { + const path = resolve(process.cwd(), 'tests', 'fixtures', 'conformance', 'api', 'public-grammar-v1.json'); + return JSON.parse(readFileSync(path, 'utf8')) as { exports: { names: string[] } }; +} + +const contract = loadApiContractFixture(); + function getCanonicalRuntimeExportNames(fixture: ApiContractFixture): string[] { return fixture.exports.names.filter((name) => { const member = fixture.exports.members[name]; @@ -249,6 +258,17 @@ function expectShape(value: unknown, shape: ReturnShape, label: string): void { } describe('public API parity contract (conformance fixture)', () => { + it('consumes declared public namespace contracts', () => { + const namespaces = contract.namespaces ?? {}; + const grammarNamespace = namespaces['context_compiler.grammar']; + expect(grammarNamespace, 'Missing grammar namespace contract').toEqual({ contract: 'public-grammar-v1' }); + + if (grammarNamespace?.contract === 'public-grammar-v1') { + const grammarContract = loadGrammarContract(); + expect(Object.keys(grammar).sort(), 'Grammar namespace exports').toEqual([...grammarContract.exports.names].sort()); + } + }); + it('syncs the stricter canonical Python fixture schema', () => { const fixture = loadApiContractFixture(); expect(fixture.exports.mode).toBe('exact'); From f4263a2d0d6853b4289eb511b4e8d45eebb2b2be Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Tue, 25 Aug 2026 23:56:32 -0400 Subject: [PATCH 3/7] feat: expose grammar namespace package entry --- README.md | 3 +++ package.json | 4 ++++ tests/api_parity.test.ts | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/README.md b/README.md index 9192f5d..35eda2c 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,9 @@ The package root exposes the Python 0.9 decision model, policy constants, and the `Engine` surface. Checkpoint persistence and the former controller/helper aliases are not part of the 0.9 package API. +The public grammar API is available from the `@rlippmann/context-compiler/grammar` +namespace. + ## Directive Drafting Directive drafting now lives in diff --git a/package.json b/package.json index 38ccf84..1465e2c 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,10 @@ ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" + }, + "./grammar": { + "types": "./dist/src/grammar.d.ts", + "import": "./dist/src/grammar.js" } }, "files": [ diff --git a/tests/api_parity.test.ts b/tests/api_parity.test.ts index 8a7941c..fa3b4b6 100644 --- a/tests/api_parity.test.ts +++ b/tests/api_parity.test.ts @@ -269,6 +269,24 @@ describe('public API parity contract (conformance fixture)', () => { } }); + it('exposes declared namespaces through the package exports map', async () => { + const packageJson = JSON.parse(readFileSync(resolve(process.cwd(), 'package.json'), 'utf8')) as { + exports?: Record; + }; + const grammarNamespace = contract.namespaces?.['context_compiler.grammar']; + expect(grammarNamespace, 'Missing grammar namespace contract').toEqual({ contract: 'public-grammar-v1' }); + expect(packageJson.exports?.['./grammar'], 'Missing package ./grammar export').toEqual({ + types: './dist/src/grammar.d.ts', + import: './dist/src/grammar.js' + }); + + const packageGrammar = await import('@rlippmann/context-compiler/grammar'); + const grammarContract = loadGrammarContract(); + expect(Object.keys(packageGrammar).sort(), 'Packaged grammar namespace exports').toEqual( + [...grammarContract.exports.names].sort() + ); + }); + it('syncs the stricter canonical Python fixture schema', () => { const fixture = loadApiContractFixture(); expect(fixture.exports.mode).toBe('exact'); From e319dc67432827a26a82dd542598ce8c5b4e1f63 Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Wed, 26 Aug 2026 00:10:12 -0400 Subject: [PATCH 4/7] chore: bump prerelease to 0.9.0-dev.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 180b659..c69cd9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@rlippmann/context-compiler", - "version": "0.9.0-dev.0", + "version": "0.9.0-dev.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@rlippmann/context-compiler", - "version": "0.9.0-dev.0", + "version": "0.9.0-dev.1", "license": "Apache-2.0", "devDependencies": { "typescript": "^5.9.3", diff --git a/package.json b/package.json index 1465e2c..bd38f4a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rlippmann/context-compiler", - "version": "0.9.0-dev.0", + "version": "0.9.0-dev.1", "description": "Store AI rules and corrections separately from chat history so they stay consistent across turns.", "keywords": [ "llm", From c14930e385cc5bbe279e5ffecbae176318c7b307 Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Wed, 26 Aug 2026 00:18:25 -0400 Subject: [PATCH 5/7] docs: update 0.9 API documentation --- README.md | 34 ++++++++++++------- tests/fixtures/README.md | 25 +++----------- .../conformance/mutation-isolation/README.md | 9 ++--- 3 files changed, 29 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 35eda2c..f18002d 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,7 @@ Context Compiler solves a common state-management problem: storing user rules is easy, but deciding when those rules are allowed to change is not. It gives your app deterministic rules for explicit state changes such as -setting a premise, replacing a policy, blocking a conflicting update, or -asking for clarification before anything changes. +setting a premise, replacing a policy, or blocking a conflicting update. A dict stores state. Context Compiler makes state changes verifiable. @@ -27,24 +26,23 @@ availability, or other host behavior, but your app still needs rules for when that state is allowed to change: - when a replacement is valid -- when a conflicting update should stop and ask for confirmation - when a change should be rejected instead of silently overwriting state -- how to restore both saved state and an in-progress clarification flow +- how to save and restore authoritative state ## How it solves it Context Compiler lets a host application: - prevent silent overwrites when a new update conflicts with what is already saved -- require clarification before conflicting or confirmation-only changes are accepted -- let the host preview a change before applying it and keep live state unchanged until it is accepted -- restore both saved state and an in-progress clarification flow safely between requests +- preserve state until an explicit directive is accepted +- let the host inspect advisory repairs without applying them automatically +- save and restore state through the JSON persistence API Each user input produces a decision for the host: -- `update` -> stored premise/policy rules changed -- `passthrough` -> input does not affect saved state -- `clarify` -> do not mutate state; ask the user to confirm or clarify +- `update` -> the directive was accepted; `changed` reports whether state changed +- `no_directive` -> input did not produce a canonical directive +- `error` -> the directive was rejected; the result identifies the semantic failure and advisory repairs Directive examples: @@ -78,12 +76,24 @@ changing them. ## Public API The package root exposes the Python 0.9 decision model, policy constants, and -the `Engine` surface. Checkpoint persistence and the former controller/helper -aliases are not part of the 0.9 package API. +the `Engine` surface. The supported engine persistence methods are +`export_json()` and `import_json()`. The public grammar API is available from the `@rlippmann/context-compiler/grammar` namespace. +```ts +import { + CanonicalDirective, + DirectiveKind, + decompose_directive +} from '@rlippmann/context-compiler/grammar'; +``` + +The grammar namespace contains the public directive constructors, metadata, +syntax classifications, and parsing helpers. Internal parsing helpers are not +part of the supported API. + ## Directive Drafting Directive drafting now lives in diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md index 5130ccc..e8cdb16 100644 --- a/tests/fixtures/README.md +++ b/tests/fixtures/README.md @@ -8,18 +8,18 @@ These fixtures are synchronized from `rlippmann/context-compiler` at the commit * [`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. `conformance/` and `engine-regression/structured/` both cover engine behavior at different layers. Both synchronized fixture families must come from the same Python checkout revision recorded in `tests/fixtures/.source-commit`. ## API contract fixture -[`conformance/api/public-api-v1.json`](conformance/api/public-api-v1.json) defines a small portable core API presence contract for the current Python 0.9 surface that ports must expose. +[`conformance/api/public-api-v2.json`](conformance/api/public-api-v2.json) defines the portable package-root API contract for the current Python 0.9 surface. The `context_compiler.grammar` namespace references [`public-grammar-v1.json`](conformance/api/public-grammar-v1.json) for the separate grammar API. Ports may sync this artifact with conformance fixtures. -Ports should check equivalent public exports and methods using language-appropriate names where casing differs, while preserving the current alias-compatible API contract. +Ports should check equivalent public exports and methods using language-appropriate names where casing differs. Behavioral semantics remain covered by conformance and structured fixtures. @@ -37,10 +37,6 @@ Then asserts: * returned `Decision` * final `engine.state` -### Prelude - -`prelude` simulates prior user inputs to reach states that are not representable via `initial_state` (for example, pending clarification). - ## State JSON fixtures For [`conformance/state-json/`](conformance/state-json/): @@ -49,14 +45,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-clarification clearing semantics. - ## Controller fixtures For [`conformance/controller/`](conformance/controller/): @@ -83,15 +71,13 @@ Files under synchronized fixture directories must not be edited manually; update [`engine-regression/structured/`](engine-regression/structured/) -These fixtures capture deterministic per-turn engine behavior, including checkpoint snapshots, and are exercised by the TypeScript structured regression test in [`structured-regression-fixtures.test.ts`](../structured-regression-fixtures.test.ts). +These fixtures capture deterministic per-turn engine behavior and are exercised by the TypeScript structured regression test in [`structured-regression-fixtures.test.ts`](../structured-regression-fixtures.test.ts). They validate: * per-turn input handling * `Decision.kind` outcomes -* clarification prompt behavior -* checkpoint export parity against expected snapshots -* continuation state restoration from checkpoints +* decision outcomes and state snapshots Directive-drafter conformance is maintained in the separate `context-compiler-directive-drafter` repositories and is not part of the core @@ -103,6 +89,5 @@ See the TypeScript fixture runners in this repository for execution details: * [`step-fixtures.test.ts`](../step-fixtures.test.ts) * [`state-json-fixtures.test.ts`](../state-json-fixtures.test.ts) -* [`checkpoint-fixtures.test.ts`](../checkpoint-fixtures.test.ts) * [`controller-fixtures.test.ts`](../controller-fixtures.test.ts) * [`structured-regression-fixtures.test.ts`](../structured-regression-fixtures.test.ts) diff --git a/tests/fixtures/conformance/mutation-isolation/README.md b/tests/fixtures/conformance/mutation-isolation/README.md index dad4d6c..9ebab55 100644 --- a/tests/fixtures/conformance/mutation-isolation/README.md +++ b/tests/fixtures/conformance/mutation-isolation/README.md @@ -94,10 +94,5 @@ now. These fixtures cover only the shared API surface for Python and the unsynchronized TypeScript port. -They intentionally do **not** include: - -* checkpoint APIs -* removed continuation-state APIs -* obsolete TypeScript-only authority surfaces -* implementation-mechanism requirements such as `deepcopy`, frozen objects, or - `readonly` +They intentionally do **not** include implementation-mechanism requirements +such as `deepcopy`, frozen objects, or `readonly`. From d793c77cbf857f9b34d9998d9e5a0eab820d20c9 Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Wed, 26 Aug 2026 00:22:30 -0400 Subject: [PATCH 6/7] docs: preserve synchronized fixture documentation --- tests/fixtures/conformance/mutation-isolation/README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/fixtures/conformance/mutation-isolation/README.md b/tests/fixtures/conformance/mutation-isolation/README.md index 9ebab55..dad4d6c 100644 --- a/tests/fixtures/conformance/mutation-isolation/README.md +++ b/tests/fixtures/conformance/mutation-isolation/README.md @@ -94,5 +94,10 @@ now. These fixtures cover only the shared API surface for Python and the unsynchronized TypeScript port. -They intentionally do **not** include implementation-mechanism requirements -such as `deepcopy`, frozen objects, or `readonly`. +They intentionally do **not** include: + +* checkpoint APIs +* removed continuation-state APIs +* obsolete TypeScript-only authority surfaces +* implementation-mechanism requirements such as `deepcopy`, frozen objects, or + `readonly` From 9bad6c841abf82c9dc4dd3a508ef7a2b6a8520cc Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Wed, 26 Aug 2026 00:39:52 -0400 Subject: [PATCH 7/7] test: sync retired API documentation --- tests/fixtures/.source-commit | 2 +- tests/fixtures/conformance/mutation-isolation/README.md | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/fixtures/.source-commit b/tests/fixtures/.source-commit index 283d8f3..7569023 100644 --- a/tests/fixtures/.source-commit +++ b/tests/fixtures/.source-commit @@ -1 +1 @@ -8d8f77202c235e7050eab93b30a36a6e1c0c6f72 +ee49df7fdcd8965392da31d4ead37c7514d53d29 diff --git a/tests/fixtures/conformance/mutation-isolation/README.md b/tests/fixtures/conformance/mutation-isolation/README.md index dad4d6c..4a32a22 100644 --- a/tests/fixtures/conformance/mutation-isolation/README.md +++ b/tests/fixtures/conformance/mutation-isolation/README.md @@ -96,8 +96,7 @@ unsynchronized TypeScript port. They intentionally do **not** include: -* checkpoint APIs -* removed continuation-state APIs +* checkpoint and continuation-state APIs retired from the 0.9 public contract * obsolete TypeScript-only authority surfaces * implementation-mechanism requirements such as `deepcopy`, frozen objects, or `readonly`