Core invariant
- • The model proposes one fixture option.
- • Policy independently binds act, escalate, or stop.
- • Model text cannot grant money, access, or override authority.
A code-faithful map of the proof of concept: model proposes, deterministic policy decides, validated stay memory persists.
/api/act.| Boundary | Current control | Consequence |
|---|---|---|
| Browser → Worker | /api/act requires X-Act-Key. The UI key is intentionally public and time-boxed; the operator key is a Worker secret. | The UI key bounds the demo write window. It is not user authentication. |
| Client fixture → policy | The server accepts only a restricted fixture filename and reloads JSON through the Worker asset binding. | A caller cannot forge agent_may, legality, or money flags in the server-gated path. |
| Model → action | The proposer returns an option and text. veto() derives the call and ticket independently. | A model-provided call: "act" or call: "override" cannot bypass policy. |
| Policy → KV | validateMemoryRecord() accepts an exact top-level and escalation-row shape. | Raw reasoning, tool traces, and unknown guest data cannot be written through this path. |
| Worker → model provider | Provider key, base URL, and model resolve from Worker secrets and variables. | The API credential never ships in the static page. |
| State | Owner | Lifetime and use |
|---|---|---|
| Scenario fixtures | Repository static assets | Fictional stay facts, options, expected calls, UI story, and per-verdict memory specs. |
| Browser session | public/app.js | Current playlist, thread, proposal, verdict, trace, and fallback memory. Reset or reload discards it. |
| Stay memory | Workers KV | One JSON record per booking ID: version, commitments, offers, escalations, thread, and language. |
| Decision trace | Response only | Tool names, arguments, and results return to the UI. The demo does not persist a durable trace ledger. |
| Model reasoning | Not stored in KV | The response includes a short model rationale for demonstration. Raw private chain-of-thought is outside the allowed memory shape. |
| Failure | Observed behavior | Safety property |
|---|---|---|
| Missing provider key | Proposal endpoints return 503. The UI can use the deterministic stand-in. | No external model call and no server-side memory write. |
| Provider error or invalid JSON | Worker returns a fallback error. The browser uses the stand-in path. | Fallback remains subject to policy, but only in browser memory. |
| Expired or missing act key | /api/act returns 401; Play falls back to the client path. | The failed call cannot mutate KV. |
| Unknown fixture | Worker rejects filenames outside the allowlisted pattern or missing asset set. | Untrusted option flags never reach server policy. |
| Withdrawn inventory | Policy returns stop, no guest confirmation, and a re-query/page-host workflow. | Time-of-check/time-of-use failure cannot become a fake confirmation. |
| Invalid memory shape | Validator returns null; Worker skips the KV write. | Schema drift fails closed for persistence. |
/api/propose is intentionally ungated and has no repository-level rate limiter. It can consume provider capacity without creating side effects.Offline tests exercise act, escalate, stop, host override, memory application, memory validation, model proposal parsing, tool calls, and the judgment fixture. CI runs the policy and proposer suites plus a Wrangler dry run. A push to main deploys the Worker when the repository has a Cloudflare API token.
The local Node server serves the same static assets and /api/propose. It does not implement the Worker’s server-gated /api/act or durable KV path.
public/index.htmlDemo shell and visible guest/operator panels.
public/app.jsBrowser story runner, API calls, fallback, rendering, and host-override demonstration.
public/policy.jsStand-in proposer, deterministic veto, host override, and pure memory application.
src/worker.jsCloudflare routes, act-key check, server-gated loop, KV read/write, and asset fallback.
scripts/lib/guest-propose.mjsProvider resolution, system prompt, tool loop, JSON parsing, and proposal trace.
src/lib/trusted-fixtures.mjsServer-owned fixture and stay loading through the asset binding.
src/lib/memory-record.mjsExact-shape persistence allowlist.
public/fixtures/*.jsonFictional stays, journeys, options, expected calls, and memory specifications.
.github/workflows/ci.ymlNode 22 tests, required-file checks, dry run, and conditional deployment.
scripts/guest-demo-server.mjsLoopback static server and local proposal endpoint.