From 6653c9645d6db9389751152b921c033fd9c0411f Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Thu, 28 May 2026 01:25:15 -0400 Subject: [PATCH] docs: add example 08 controller preview and diff flow --- examples/08_controller_preview_diff.ts | 102 +++++++++++++++++++++++++ examples/README.md | 5 ++ 2 files changed, 107 insertions(+) create mode 100644 examples/08_controller_preview_diff.ts diff --git a/examples/08_controller_preview_diff.ts b/examples/08_controller_preview_diff.ts new file mode 100644 index 0000000..184892c --- /dev/null +++ b/examples/08_controller_preview_diff.ts @@ -0,0 +1,102 @@ +import { + DECISION_CLARIFY, + DECISION_PASSTHROUGH, + DECISION_UPDATE, + createEngine, + getPolicyItems, + getPremiseValue, + get_decision_state, + is_clarify, + is_update, + preview, + state_diff, + step, + type Decision, + type EngineState +} from '../src/index.js'; + +declare const process: { argv: string[] }; + +function summarizeState(state: EngineState): { + premise: string | null; + usePolicies: string[]; + prohibitPolicies: string[]; +} { + return { + premise: getPremiseValue(state), + usePolicies: getPolicyItems(state, 'use'), + prohibitPolicies: getPolicyItems(state, 'prohibit') + }; +} + +function summarizeDecision(decision: Decision): { + kind: typeof DECISION_UPDATE | typeof DECISION_CLARIFY | typeof DECISION_PASSTHROUGH; + promptToUser: string | null; + decisionState: ReturnType | null; +} { + if (is_update(decision)) { + const decisionState = get_decision_state(decision); + return { + kind: DECISION_UPDATE, + promptToUser: null, + decisionState: decisionState ? summarizeState(decisionState) : null + }; + } + + if (is_clarify(decision)) { + return { + kind: DECISION_CLARIFY, + promptToUser: decision.prompt_to_user, + decisionState: null + }; + } + + return { + kind: DECISION_PASSTHROUGH, + promptToUser: null, + decisionState: null + }; +} + +export function runExample08(): { + stateBeforePreview: ReturnType; + preview: { + wouldMutate: boolean; + decision: ReturnType; + }; + stateChangedAfterPreview: boolean; + apply: { + decision: ReturnType; + stateAfterStep: ReturnType; + }; +} { + const engine = createEngine(); + + const stateBeforePreview = engine.state; + + const previewResult = preview(engine, 'prohibit peanuts'); + + const stateAfterPreview = engine.state; + const diffAfterPreview = state_diff(stateBeforePreview, stateAfterPreview); + + const stepResult = step(engine, 'prohibit peanuts'); + + return { + stateBeforePreview: summarizeState(stateBeforePreview), + preview: { + wouldMutate: previewResult.would_mutate, + decision: summarizeDecision(previewResult.decision) + }, + stateChangedAfterPreview: diffAfterPreview.changed, + apply: { + decision: summarizeDecision(stepResult.decision), + stateAfterStep: summarizeState(stepResult.state) + } + }; +} + +if (typeof process !== 'undefined' && process.argv[1] && import.meta.url === new URL(process.argv[1], 'file://').href) { + const result = runExample08(); + console.log('example 08: controller preview + state diff + apply flow'); + console.log(JSON.stringify(result, null, 2)); +} diff --git a/examples/README.md b/examples/README.md index 7c344e6..4de769b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -36,3 +36,8 @@ Demonstrates transcript replay behavior with `compile_transcript(messages)` and Demonstrates explicit single-policy correction without `reset policies`: `prohibit peanuts` -> `remove policy peanuts` -> `use peanuts`. + +## 08_controller_preview_diff.ts + +Demonstrates controller-layer auditability with `preview(engine, input)` and `state_diff(before, after)`. +Shows that preview does not mutate live engine state, then applies the same input with `step(engine, input)`.