From e86ff25c3d80cee0895eb7818722bbf88de2beb5 Mon Sep 17 00:00:00 2001 From: "@permadeath.com" Date: Fri, 11 Sep 2026 18:49:48 -0400 Subject: [PATCH] feat(policy-site): start a new policy in Cedar A new policy's engine is bot.did.policy#cedar with an empty document; the regex engine stays one select away, and records already written keep theirs. Co-Authored-By: Claude Opus 5 (1M context) Change-Id: Iee04b49db06a2538e76e3a760b333c23309e8763 --- policy-site/src/ui/policy-document.ts | 11 +++++++++++ policy-site/src/ui/policy-editor.ts | 5 +++-- policy-site/tests/check-flows.mjs | 10 ++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/policy-site/src/ui/policy-document.ts b/policy-site/src/ui/policy-document.ts index 7e310d64..d31373a6 100644 --- a/policy-site/src/ui/policy-document.ts +++ b/policy-site/src/ui/policy-document.ts @@ -90,6 +90,17 @@ export function joinDocument( return { value: ordered(isObject(loaded) ? loaded : {}, fields) }; } +/** + * The engine a new policy starts in, where this build has it: Cedar, whose + * documents are text rather than JSON. + */ +export const NEW_POLICY_ENGINE = "bot.did.policy#cedar"; + +/** The engine a new policy starts in, out of the ones this build carries. */ +export function defaultEngine(engines: string[]): string | undefined { + return engines.includes(NEW_POLICY_ENGINE) ? NEW_POLICY_ENGINE : engines[0]; +} + /** An empty document of `type`, as editor text. */ export function emptyDocument(type: string): DocumentText { const engine = DOCUMENT_BODIES[type]; diff --git a/policy-site/src/ui/policy-editor.ts b/policy-site/src/ui/policy-editor.ts index bea40857..eea4734c 100644 --- a/policy-site/src/ui/policy-editor.ts +++ b/policy-site/src/ui/policy-editor.ts @@ -10,6 +10,7 @@ import { checkboxes, debounce, labelled, renderReport, stamp, type EditorTarget import { actionsOf, assemblePolicy, + defaultEngine, emptyDocument, joinDocument, languageOf, @@ -50,8 +51,8 @@ export function policyEditor(checks: Checks, target: EditorTarget): PolicyEditor const createdAt = h("input", { attrs: { readonly: "" } }); createdAt.value = typeof loaded.createdAt === "string" ? loaded.createdAt : ""; - // A new policy with no document starts as the first engine's, empty. - const first = vocabulary.engines[0]; + // A new policy with no document starts empty, in the default engine. + const first = defaultEngine(vocabulary.engines); let held: DocumentText = loaded.document === undefined && first ? emptyDocument(first) : start.document; const modeOf = (doc: DocumentText): EditorMode => diff --git a/policy-site/tests/check-flows.mjs b/policy-site/tests/check-flows.mjs index 6e8f678e..ce50455f 100644 --- a/policy-site/tests/check-flows.mjs +++ b/policy-site/tests/check-flows.mjs @@ -216,6 +216,16 @@ function expectWrites(name, batch, writes) { } } +// A new policy starts in the engine whose documents are Cedar text. +{ + const { defaultEngine, emptyDocument } = await import("../src/ui/policy-document.ts"); + const engines = ["bot.did.policy#regex", "bot.did.policy#cedar"]; + if (defaultEngine(engines) !== "bot.did.policy#cedar") fail(`a new policy starts as ${defaultEngine(engines)}`); + const started = emptyDocument(defaultEngine(engines)); + if (started.text !== "" || started.whole) fail(`a new Cedar policy starts as ${JSON.stringify(started)}`); + if (defaultEngine(["bot.did.policy#regex"]) !== "bot.did.policy#regex") fail("a build without Cedar does not fall back"); +} + // A sign-in comes back only to one of the page's own addresses. { const back = "/recommended/no-posts/customize/edit"; -- 2.51.2