From 55deb419a1238da5bdf4f1c6f764d6e306e00743 Mon Sep 17 00:00:00 2001 From: Madraka <8898640+Madraka@users.noreply.github.com> Date: Thu, 22 Oct 2026 10:02:43 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20collection=20permalinks,=20step=201=20?= =?UTF-8?q?=E2=80=94=20the=20contract=20and=20one=20entry=20path=20functio?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RFC-0038 / ADR-0040, step 1 (no behaviour change): - Contract: `Site.collections..permalink`, a template with a closed token set; v1 accepts fixed segments plus `/{slug}`. Fixtures: one valid, three invalid (trailing slash, unknown token, no slug). TS and Rust regenerated. - core-ts `permalinks.ts`: `entryPath(site, entry)`, `entryAt(site, path)`, `collectionPermalink`, `permalinkPrefix`; browser-safe subpath `@cloudhal/core-ts/permalinks`. - Every entry URL (about 35 inline `/${collection}/${slug}` copies and the renderer's one-argument `entryPath`) now comes from them: renderer routes, SEO, feeds, previews, surfaces, navigation, comments; starter; core-ts automations, search, the store's automatic redirect; API assets, menus, search, comments; MCP analytics, comments, search, schedules; CLI analytics; Studio screens through `EntryPath` / `useSitePermalinks`. - The renderer's `resolvePath` reads entries through `entryAt`. - Themes without a Site get the templates through `MediaContext.permalinks`. With no `collections` set every path is unchanged. Refs #50 Co-Authored-By: Claude Opus 5.5 Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com> --- .changeset/collection-permalinks-step1.md | 10 + .../src/components/AnalyticsSections.tsx | 33 +- apps/studio/src/components/EntryPath.tsx | 38 + .../src/components/GovernanceDialog.tsx | 5 +- apps/studio/src/components/ScheduleDialog.tsx | 3 +- apps/studio/src/components/Seo.tsx | 5 +- .../src/components/SnapshotDiffDialog.tsx | 3 +- apps/studio/src/components/Taxonomy.tsx | 19 +- apps/studio/src/components/useEditorByPath.ts | 28 + apps/studio/src/components/useMe.ts | 2 +- apps/studio/src/lib/menus.ts | 16 +- apps/studio/src/lib/redirects.ts | 5 +- apps/studio/src/routes/ArticleEditorPage.tsx | 10 +- apps/studio/src/routes/ArticlesPage.tsx | 5 +- apps/studio/src/routes/CommentThreadPage.tsx | 5 +- apps/studio/src/routes/RedirectsPage.tsx | 10 +- crates/cloudhal-schema/src/generated/site.rs | 68 + packages/api/src/routes/assets.ts | 12 +- packages/api/src/routes/comments.ts | 10 +- packages/api/src/routes/menus.ts | 3 +- packages/api/src/routes/search.ts | 1 + packages/astro-renderer/src/comments-view.ts | 11 +- packages/astro-renderer/src/comments.ts | 4 +- .../src/components/ContentList.astro | 5 +- packages/astro-renderer/src/feeds.ts | 6 +- packages/astro-renderer/src/locale-feeds.ts | 12 +- packages/astro-renderer/src/locales.ts | 10 +- packages/astro-renderer/src/media.ts | 3 + packages/astro-renderer/src/navigation.ts | 7 +- packages/astro-renderer/src/preview.ts | 21 +- packages/astro-renderer/src/routing.ts | 38 +- packages/astro-renderer/src/seo.ts | 6 +- packages/astro-renderer/src/source.ts | 1 + packages/astro-renderer/src/surfaces/tree.ts | 15 +- packages/astro-renderer/test/surfaces.test.ts | 1 + packages/cli/src/commands/analytics.ts | 7 +- .../site/permalink-trailing-slash.json | 25 + .../invalid/site/permalink-unknown-token.json | 25 + .../invalid/site/permalink-without-slug.json | 25 + .../valid/site/collection-permalinks.json | 26 + .../core-contracts/schemas/site.schema.json | 17 + .../core-contracts/src/generated/index.ts | 2 +- .../core-contracts/src/generated/schemas.ts | 21 + packages/core-contracts/src/generated/site.ts | 16 + .../src/generated/validators.js | 1545 +++++++++-------- packages/core-ts/package.json | 5 + packages/core-ts/src/automations.ts | 20 +- packages/core-ts/src/index.ts | 1 + packages/core-ts/src/permalinks.ts | 59 + packages/core-ts/src/search-rows.ts | 10 +- packages/core-ts/src/search.ts | 9 +- packages/core-ts/src/site-search.ts | 21 +- packages/core-ts/src/store/entries.ts | 6 +- packages/core-ts/test/permalinks.test.ts | 52 + packages/mcp-server/src/analytics-reports.ts | 7 +- packages/mcp-server/src/comment-tools.ts | 8 +- packages/mcp-server/src/comments.ts | 27 +- packages/mcp-server/src/release-titles.ts | 34 + packages/mcp-server/src/schedules.ts | 30 +- packages/mcp-server/src/search-tools.ts | 1 + scripts/size-baseline.json | 14 +- starters/blog/src/components/EntryList.astro | 2 +- starters/blog/src/pages/[...path].astro | 8 +- 63 files changed, 1533 insertions(+), 921 deletions(-) create mode 100644 .changeset/collection-permalinks-step1.md create mode 100644 apps/studio/src/components/EntryPath.tsx create mode 100644 apps/studio/src/components/useEditorByPath.ts create mode 100644 packages/core-contracts/fixtures/invalid/site/permalink-trailing-slash.json create mode 100644 packages/core-contracts/fixtures/invalid/site/permalink-unknown-token.json create mode 100644 packages/core-contracts/fixtures/invalid/site/permalink-without-slug.json create mode 100644 packages/core-contracts/fixtures/valid/site/collection-permalinks.json create mode 100644 packages/core-ts/src/permalinks.ts create mode 100644 packages/core-ts/test/permalinks.test.ts create mode 100644 packages/mcp-server/src/release-titles.ts diff --git a/.changeset/collection-permalinks-step1.md b/.changeset/collection-permalinks-step1.md new file mode 100644 index 00000000..ad2b3768 --- /dev/null +++ b/.changeset/collection-permalinks-step1.md @@ -0,0 +1,10 @@ +--- +"@cloudhal/core-contracts": patch +"@cloudhal/core-ts": patch +"@cloudhal/astro-renderer": patch +"@cloudhal/api": patch +"@cloudhal/mcp-server": patch +"@cloudhal/cli": patch +--- + +RFC-0038 step 1: `Site.collections..permalink` in the contracts (a template, `/blog/{slug}`; v1 is fixed segments plus `{slug}`), and `entryPath(site, entry)` / `entryAt(site, path)` in `@cloudhal/core-ts` (also `@cloudhal/core-ts/permalinks`). Every entry URL in the renderer, starter, API, MCP server, CLI and Studio now comes from them; with no `collections` set, every URL is the same as before. The renderer's one-argument `entryPath(entry)` is replaced by core's `entryPath(site, entry)` (re-exported from `@cloudhal/astro-renderer/routing`); themes pass the site, or `MediaContext.permalinks`. diff --git a/apps/studio/src/components/AnalyticsSections.tsx b/apps/studio/src/components/AnalyticsSections.tsx index 1b012f1a..68a7ab1e 100644 --- a/apps/studio/src/components/AnalyticsSections.tsx +++ b/apps/studio/src/components/AnalyticsSections.tsx @@ -84,7 +84,8 @@ import { RedirectDialog } from "./RedirectDialog.tsx"; import { RelativeTime } from "./RelativeTime.tsx"; import { SettingRow } from "./Settings.tsx"; import { TableSkeleton } from "./TableSkeleton.tsx"; -import { taxonomyKeys, useTaxonomyData } from "./Taxonomy.tsx"; +import { servedPathsInput, taxonomyKeys, useTaxonomyData } from "./Taxonomy.tsx"; +import { useEditorByPath } from "./useEditorByPath.ts"; import { useGate } from "./useMe.ts"; /** What every card reads for: the site, the range (and its UTC days), the filter, polling. */ @@ -565,28 +566,6 @@ function statusVariant(status: number): "warning" | "secondary" | "error" | "inf return status >= 500 ? "error" : "info"; } -/** Where a same-site path is edited: pages by path, published articles by `//`. */ -function useEditorByPath(siteId: string, enabled: boolean) { - const pages = useQuery({ - queryKey: ["pages", siteId], - queryFn: () => api.pages.list(siteId), - enabled, - }); - const entries = useQuery({ - queryKey: ["entries", siteId, "all"], - queryFn: () => api.entries.list(siteId), - enabled, - }); - return useMemo(() => { - const map = new Map(); - for (const p of pages.data ?? []) map.set(p.path, `/sites/${siteId}/pages/${p.id}`); - for (const e of entries.data ?? []) - if (e.status === "published") - map.set(`/${e.collection}/${e.slug}`, `/sites/${siteId}/articles/${e.id}`); - return map; - }, [pages.data, entries.data, siteId]); -} - /** The redirect dialog, prefilled with a broken path; it loads what it checks against when opened. */ function CreateRedirect({ siteId, @@ -614,13 +593,7 @@ function CreateRedirect({ queryFn: () => api.entries.list(siteId), enabled: open, }); - const served = servedPaths({ - pages: pages.data ?? [], - entries: entries.data ?? [], - defaultLocale: data.defaultLocale, - taxonomies: data.taxonomies.data ?? [], - terms: data.items.map((i) => i.term), - }); + const served = servedPaths(servedPathsInput(data, pages.data, entries.data)); return ( /` while the site sets none). The site comes + * from the shared `["site", siteId]` query; until it loads, paths use the default template. + */ +import type { ContentEntry } from "@cloudhal/core-contracts"; +import { entryPath, type PermalinkSite } from "@cloudhal/core-ts/permalinks"; +import { Text } from "@cloudhal/ui"; +import { useQuery } from "@tanstack/react-query"; +import { api } from "../lib/api.ts"; +import { currentSiteId } from "./useMe.ts"; + +/** The site as `entryPath` reads it (undefined while loading). */ +export function useSitePermalinks(siteId: string): PermalinkSite | undefined { + return useQuery({ + queryKey: ["site", siteId], + queryFn: () => api.sites.get(siteId), + enabled: siteId !== "", + }).data; +} + +/** An article's path in mono text; `siteId` defaults to the site the Studio URL is about. */ +export function EntryPath({ + siteId = currentSiteId() ?? "", + entry, + truncate = false, +}: { + siteId?: string; + entry: Pick; + truncate?: boolean; +}) { + const site = useSitePermalinks(siteId); + return ( + + {entryPath(site, entry)} + + ); +} diff --git a/apps/studio/src/components/GovernanceDialog.tsx b/apps/studio/src/components/GovernanceDialog.tsx index f63a5c47..a409fcb7 100644 --- a/apps/studio/src/components/GovernanceDialog.tsx +++ b/apps/studio/src/components/GovernanceDialog.tsx @@ -31,6 +31,7 @@ import { TERM_LIMITS, validateSlug, } from "../lib/taxonomy.ts"; +import { EntryPath } from "./EntryPath.tsx"; import { ErrorBanner } from "./ErrorBanner.tsx"; import { Status } from "./Status.tsx"; import { invalidateTaxonomy, type TaxonomyData } from "./Taxonomy.tsx"; @@ -436,9 +437,7 @@ function PreviewSection({ {e.title || t("articles.untitled")} - - /{e.collection}/{e.slug} - + diff --git a/apps/studio/src/components/ScheduleDialog.tsx b/apps/studio/src/components/ScheduleDialog.tsx index ec2dd32e..96f3ea3c 100644 --- a/apps/studio/src/components/ScheduleDialog.tsx +++ b/apps/studio/src/components/ScheduleDialog.tsx @@ -6,6 +6,7 @@ * it waits in Approvals; what the items need that is not live yet joins them (the closure). */ import type { PublishStatus } from "@cloudhal/core-contracts"; +import { entryPath } from "@cloudhal/core-ts/permalinks"; import { Banner, Combobox, @@ -132,7 +133,7 @@ export function ScheduleDialog({ value: e.id, label: e.title || e.slug, kind: "contentEntry" as const, - detail: `/${e.collection}/${e.slug}`, + detail: entryPath(zone.site, e), })), ...(pages.data ?? []).map((p) => ({ value: p.id, diff --git a/apps/studio/src/components/Seo.tsx b/apps/studio/src/components/Seo.tsx index 2b5eca36..f697365e 100644 --- a/apps/studio/src/components/Seo.tsx +++ b/apps/studio/src/components/Seo.tsx @@ -1,4 +1,5 @@ import type { ContentEntry, Page } from "@cloudhal/core-contracts"; +import { entryPath } from "@cloudhal/core-ts/permalinks"; import { Badge, Banner, @@ -34,6 +35,7 @@ import { truncateForSearch, } from "../lib/seo.ts"; import { setOptional } from "../lib/util.ts"; +import { useSitePermalinks } from "./EntryPath.tsx"; import { ImageRefField } from "./MediaPicker.tsx"; import { SettingRow } from "./Settings.tsx"; @@ -190,7 +192,8 @@ export function ArticleSeoPreviewCard({ const title = entry.title.trim() || t("articles.untitled"); const summary = entry.summary?.trim() ?? ""; const description = summary ? truncateForSearch(summary).text : t("seo.preview.noSummary"); - const path = `/${entry.collection}/${entry.slug}`; + const site = useSitePermalinks(entry.siteId); + const path = entryPath(site, entry); const cover = entry.coverImage; const coverUrl = cover ? imageRefPreviewUrl(entry.siteId, cover, host) : undefined; const titleState = titleStatus(entry.title); diff --git a/apps/studio/src/components/SnapshotDiffDialog.tsx b/apps/studio/src/components/SnapshotDiffDialog.tsx index 2abd36a5..08466061 100644 --- a/apps/studio/src/components/SnapshotDiffDialog.tsx +++ b/apps/studio/src/components/SnapshotDiffDialog.tsx @@ -1,4 +1,5 @@ import type { Snapshot } from "@cloudhal/core-contracts"; +import { entryPath } from "@cloudhal/core-ts/permalinks"; import { Badge, InlineCopyText, @@ -195,7 +196,7 @@ function useNodeNames(siteId: string, enabled: boolean) { return entry ? { title: entry.title, - detail: `/${entry.collection}/${entry.slug}`, + detail: entryPath(site.data, entry), href: `/sites/${siteId}/articles/${entry.id}`, } : {}; diff --git a/apps/studio/src/components/Taxonomy.tsx b/apps/studio/src/components/Taxonomy.tsx index bc80a08a..3ce1b4c9 100644 --- a/apps/studio/src/components/Taxonomy.tsx +++ b/apps/studio/src/components/Taxonomy.tsx @@ -1,4 +1,4 @@ -import type { Taxonomy, Term } from "@cloudhal/core-contracts"; +import type { ContentEntry, Page, Taxonomy, Term } from "@cloudhal/core-contracts"; import { Badge, DirectionalIcon, Link, Text, Tooltip } from "@cloudhal/ui"; import { ArrowElbowDownRight, @@ -10,6 +10,7 @@ import { import { type QueryClient, useQuery } from "@tanstack/react-query"; import { useI18n, useT } from "../i18n/index.tsx"; import { api, type TermListItem, type TermUsage } from "../lib/api.ts"; +import type { servedPaths } from "../lib/redirects.ts"; import { archivePath, defaultTaxonomies, @@ -87,6 +88,22 @@ export function useTaxonomyData(siteId: string, enabled = true) { export type TaxonomyData = ReturnType; +/** What `servedPaths` reads, from the taxonomy data (entries under the site's permalinks). */ +export function servedPathsInput( + data: TaxonomyData, + pages: readonly Page[] = [], + entries: readonly ContentEntry[] = [], +): Parameters[0] { + return { + pages, + entries, + defaultLocale: data.defaultLocale, + taxonomies: data.taxonomies.data ?? [], + terms: data.items.map((i) => i.term), + site: data.site.data, + }; +} + /** After a taxonomy change: terms, taxonomies, articles, redirects and snapshots may all move. */ export async function invalidateTaxonomy(qc: QueryClient, siteId: string): Promise { await Promise.all([ diff --git a/apps/studio/src/components/useEditorByPath.ts b/apps/studio/src/components/useEditorByPath.ts new file mode 100644 index 00000000..c0cee9b6 --- /dev/null +++ b/apps/studio/src/components/useEditorByPath.ts @@ -0,0 +1,28 @@ +import { entryPath } from "@cloudhal/core-ts/permalinks"; +import { useQuery } from "@tanstack/react-query"; +import { useMemo } from "react"; +import { api } from "../lib/api.ts"; +import { useSitePermalinks } from "./EntryPath.tsx"; + +/** Where a same-site path is edited: pages by path, published articles by their permalink. */ +export function useEditorByPath(siteId: string, enabled: boolean) { + const pages = useQuery({ + queryKey: ["pages", siteId], + queryFn: () => api.pages.list(siteId), + enabled, + }); + const entries = useQuery({ + queryKey: ["entries", siteId, "all"], + queryFn: () => api.entries.list(siteId), + enabled, + }); + const site = useSitePermalinks(siteId); + return useMemo(() => { + const map = new Map(); + for (const p of pages.data ?? []) map.set(p.path, `/sites/${siteId}/pages/${p.id}`); + for (const e of entries.data ?? []) + if (e.status === "published") + map.set(entryPath(site, e), `/sites/${siteId}/articles/${e.id}`); + return map; + }, [pages.data, entries.data, siteId, site]); +} diff --git a/apps/studio/src/components/useMe.ts b/apps/studio/src/components/useMe.ts index c89a9434..a1d7cf19 100644 --- a/apps/studio/src/components/useMe.ts +++ b/apps/studio/src/components/useMe.ts @@ -15,7 +15,7 @@ export const ME_KEY = ["me"] as const; * The site the Studio URL is about (`/sites//…`), read from the location so the hook also works * outside the router (dialogs in tests, the account surface). */ -function currentSiteId(): string | undefined { +export function currentSiteId(): string | undefined { const match = /^\/sites\/([^/]+)/.exec(globalThis.location?.pathname ?? ""); return match?.[1] ? decodeURIComponent(match[1]) : undefined; } diff --git a/apps/studio/src/lib/menus.ts b/apps/studio/src/lib/menus.ts index e0125cf8..20ef1907 100644 --- a/apps/studio/src/lib/menus.ts +++ b/apps/studio/src/lib/menus.ts @@ -29,7 +29,8 @@ import { type ResolvedMenuItem, walkMenu, } from "@cloudhal/core-ts/menu"; -import { termLabel } from "./taxonomy.ts"; +import { entryPath } from "@cloudhal/core-ts/permalinks"; +import { archivePath, termLabel } from "./taxonomy.ts"; export type { MenuDelta, MenuHiddenReason, ResolvedMenu, ResolvedMenuItem }; export { MENU_LIMITS, menuDelta, newMenuItemId }; @@ -418,7 +419,7 @@ export function hostOf(href: string): string | undefined { /** What Studio knows about the site to name targets and offer them. */ export interface TargetData { - site: Pick; + site: Pick; pages: readonly Page[]; entries: readonly ContentEntry[]; taxonomies: readonly Taxonomy[]; @@ -473,9 +474,6 @@ export function choiceOf(value: string): TargetChoice | undefined { return undefined; } -const archivePathOf = (tax: Pick, slug: string) => - `${tax.archive.basePath}/${slug}`; - /** Every target the picker offers, grouped: pages, articles, each taxonomy, collections, other. */ export function targetOptions(data: TargetData): TargetOption[] { const locale = data.site.defaultLocale; @@ -496,7 +494,7 @@ export function targetOptions(data: TargetData): TargetOption[] { value: `contentEntry:${e.id}`, group: "articles" as const, label: e.title, - detail: `/${e.collection}/${e.slug}`, + detail: entryPath(data.site, e), ...(e.status !== "published" ? { status: e.status } : {}), })); const terms: TargetOption[] = []; @@ -519,7 +517,7 @@ export function targetOptions(data: TargetData): TargetOption[] { group: "terms", taxonomy: tax.key, label: termLabel(t, locale, ...data.site.locales), - ...(tax.archive.enabled ? { detail: archivePathOf(tax, t.slug) } : {}), + ...(tax.archive.enabled ? { detail: archivePath(tax, t.slug) } : {}), ...(t.status === "proposed" ? { status: "proposed" } : t.visibility === "internal" @@ -592,7 +590,7 @@ export function summarizeTarget(item: MenuItem, data: TargetData): TargetSummary case "contentEntry": { const e = data.entries.find((x) => x.id === item.entryId); return e - ? { kind: "contentEntry", where: `/${e.collection}/${e.slug}`, title: e.title } + ? { kind: "contentEntry", where: entryPath(data.site, e), title: e.title } : { kind: "contentEntry", missing: true }; } case "term": { @@ -603,7 +601,7 @@ export function summarizeTarget(item: MenuItem, data: TargetData): TargetSummary kind: "term", title: termLabel(t, data.site.defaultLocale), ...(tax ? { taxonomy: tax.key } : {}), - ...(tax?.archive.enabled ? { where: archivePathOf(tax, t.slug) } : {}), + ...(tax?.archive.enabled ? { where: archivePath(tax, t.slug) } : {}), } : { kind: "term", missing: true }; } diff --git a/apps/studio/src/lib/redirects.ts b/apps/studio/src/lib/redirects.ts index 513912d6..a0038b47 100644 --- a/apps/studio/src/lib/redirects.ts +++ b/apps/studio/src/lib/redirects.ts @@ -1,4 +1,5 @@ import type { ContentEntry, Page, Redirect, Taxonomy, Term } from "@cloudhal/core-contracts"; +import { entryPath, type PermalinkSite } from "@cloudhal/core-ts/permalinks"; /** * Manual redirects in Studio (RFC-0002 invariant 14). The patterns are the contract's @@ -53,11 +54,13 @@ export function servedPaths(input: { defaultLocale: string; taxonomies: readonly Pick[]; terms: readonly Pick[]; + /** The site's collection permalinks (RFC-0038), for articles' paths. */ + site?: PermalinkSite | undefined; }): Set { const out = new Set(input.pages.map((p) => p.path)); for (const e of input.entries) if (e.status === "published" && e.locale === input.defaultLocale) - out.add(`/${e.collection}/${e.slug}`); + out.add(entryPath(input.site, e)); for (const tax of input.taxonomies) { if (!tax.archive.enabled) continue; for (const term of input.terms) diff --git a/apps/studio/src/routes/ArticleEditorPage.tsx b/apps/studio/src/routes/ArticleEditorPage.tsx index 0e5e73c3..555a2a6c 100644 --- a/apps/studio/src/routes/ArticleEditorPage.tsx +++ b/apps/studio/src/routes/ArticleEditorPage.tsx @@ -1,4 +1,5 @@ import type { ContentEntry, PublishStatus } from "@cloudhal/core-contracts"; +import { collectionPermalink, permalinkPrefix } from "@cloudhal/core-ts/permalinks"; import { ApprovalDialog, Button, @@ -43,6 +44,7 @@ import { ScheduleAside, ScheduleLock, } from "../components/EditorSchedule.tsx"; +import { EntryPath } from "../components/EntryPath.tsx"; import { ErrorBanner } from "../components/ErrorBanner.tsx"; import { ViewOnlyBanner } from "../components/Gating.tsx"; import { MenuDeleteNotice, useMenuReferences } from "../components/Menus.tsx"; @@ -278,9 +280,7 @@ export function ArticleEditorPage() { description={ - - /{draft.collection}/{draft.slug} - + } actions={ @@ -405,7 +405,9 @@ export function ArticleEditorPage() { } > - /{draft.collection}/ + + {permalinkPrefix(collectionPermalink(site.data, draft.collection))}/ + - - /{e.collection}/{e.slug} - + diff --git a/apps/studio/src/routes/CommentThreadPage.tsx b/apps/studio/src/routes/CommentThreadPage.tsx index 11e2bfe0..8907dc78 100644 --- a/apps/studio/src/routes/CommentThreadPage.tsx +++ b/apps/studio/src/routes/CommentThreadPage.tsx @@ -59,6 +59,7 @@ import { } from "../components/CommentThreadControls.tsx"; import { SiteCrumbs } from "../components/Crumbs.tsx"; import { DetailsCard, EditorLayout } from "../components/EditorLayout.tsx"; +import { EntryPath } from "../components/EntryPath.tsx"; import { ErrorBanner } from "../components/ErrorBanner.tsx"; import { SettingRow } from "../components/Settings.tsx"; import { useToast } from "../components/Toast.ts"; @@ -668,9 +669,7 @@ export function CommentThreadPage() { ? [ { label: t("comments.thread.path"), - value: ( - {`/${entry.data.collection}/${entry.data.slug}`} - ), + value: , }, ] : []), diff --git a/apps/studio/src/routes/RedirectsPage.tsx b/apps/studio/src/routes/RedirectsPage.tsx index 57fdd1a6..3eaee407 100644 --- a/apps/studio/src/routes/RedirectsPage.tsx +++ b/apps/studio/src/routes/RedirectsPage.tsx @@ -39,7 +39,7 @@ import { ListToolbar } from "../components/ListToolbar.tsx"; import { RedirectDialog } from "../components/RedirectDialog.tsx"; import { RelativeTime } from "../components/RelativeTime.tsx"; import { TableSkeleton } from "../components/TableSkeleton.tsx"; -import { taxonomyKeys, useTaxonomyData } from "../components/Taxonomy.tsx"; +import { servedPathsInput, taxonomyKeys, useTaxonomyData } from "../components/Taxonomy.tsx"; import { useToast } from "../components/Toast.ts"; import { useGate } from "../components/useMe.ts"; import { usePaging } from "../components/usePaging.ts"; @@ -93,13 +93,7 @@ export function RedirectsPage() { list, (r, q, fold) => fold(r.from).includes(q) || fold(r.to).includes(q), ); - const served = servedPaths({ - pages: pages.data ?? [], - entries: entries.data ?? [], - defaultLocale: data.defaultLocale, - taxonomies: data.taxonomies.data ?? [], - terms: data.items.map((i) => i.term), - }); + const served = servedPaths(servedPathsInput(data, pages.data, entries.data)); const remove = useMutation({ mutationFn: (r: Redirect) => api.redirects.remove(r), onSuccess: async (_x, r) => { diff --git a/crates/cloudhal-schema/src/generated/site.rs b/crates/cloudhal-schema/src/generated/site.rs index e445d8fc..622d0a93 100644 --- a/crates/cloudhal-schema/src/generated/site.rs +++ b/crates/cloudhal-schema/src/generated/site.rs @@ -1657,6 +1657,68 @@ impl ::std::convert::TryFrom<::std::string::String> for ChromeHeaderWidth { value.parse() } } +#[doc = "`CollectionSettings`"] +#[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug, Default, PartialEq)] +#[serde(deny_unknown_fields)] +pub struct CollectionSettings { + #[doc = "RFC-0038: where the collection's entries live, as a template with a closed token set. Version 1: one or more fixed lower-case path segments followed by /{slug}, e.g. /blog/{slug}. Absent means //{slug}."] + #[serde(skip_serializing_if = "::std::option::Option::is_none")] + pub permalink: ::std::option::Option, +} +#[doc = "RFC-0038: where the collection's entries live, as a template with a closed token set. Version 1: one or more fixed lower-case path segments followed by /{slug}, e.g. /blog/{slug}. Absent means //{slug}."] +#[derive(:: serde :: Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[serde(transparent)] +pub struct CollectionSettingsPermalink(::std::string::String); +impl ::std::ops::Deref for CollectionSettingsPermalink { + type Target = ::std::string::String; + fn deref(&self) -> &::std::string::String { + &self.0 + } +} +impl ::std::convert::From for ::std::string::String { + fn from(value: CollectionSettingsPermalink) -> Self { + value.0 + } +} +impl ::std::str::FromStr for CollectionSettingsPermalink { + type Err = self::error::ConversionError; + fn from_str(value: &str) -> ::std::result::Result { + static PATTERN: ::std::sync::LazyLock<::regress::Regex> = + ::std::sync::LazyLock::new(|| { + ::regress::Regex::new("^(/[a-z0-9]+(-[a-z0-9]+)*)+/\\{slug\\}$").unwrap() + }); + if PATTERN.find(value).is_none() { + return Err("doesn't match pattern \"^(/[a-z0-9]+(-[a-z0-9]+)*)+/\\{slug\\}$\"".into()); + } + Ok(Self(value.to_string())) + } +} +impl ::std::convert::TryFrom<&str> for CollectionSettingsPermalink { + type Error = self::error::ConversionError; + fn try_from(value: &str) -> ::std::result::Result { + value.parse() + } +} +impl ::std::convert::TryFrom<::std::string::String> for CollectionSettingsPermalink { + type Error = self::error::ConversionError; + fn try_from( + value: ::std::string::String, + ) -> ::std::result::Result { + value.parse() + } +} +impl<'de> ::serde::Deserialize<'de> for CollectionSettingsPermalink { + fn deserialize(deserializer: D) -> ::std::result::Result + where + D: ::serde::Deserializer<'de>, + { + ::std::string::String::deserialize(deserializer)? + .parse() + .map_err(|e: self::error::ConversionError| { + ::custom(e.to_string()) + }) + } +} #[doc = "`Consent`"] #[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug, PartialEq)] #[serde(deny_unknown_fields)] @@ -3485,6 +3547,12 @@ pub struct Site { pub brand: ::std::option::Option, #[serde(skip_serializing_if = "::std::option::Option::is_none")] pub chrome: ::std::option::Option, + #[doc = "RFC-0038: per-collection settings, keyed by collection (ContentEntry.collection). A collection without an entry here uses the defaults."] + #[serde( + default, + skip_serializing_if = ":: std :: collections :: HashMap::is_empty" + )] + pub collections: ::std::collections::HashMap, #[serde(rename = "createdAt")] pub created_at: Timestamp, #[serde(rename = "defaultLocale")] diff --git a/packages/api/src/routes/assets.ts b/packages/api/src/routes/assets.ts index 6b12a69a..aa983d98 100644 --- a/packages/api/src/routes/assets.ts +++ b/packages/api/src/routes/assets.ts @@ -14,7 +14,7 @@ import { holds } from "@cloudhal/agent-runtime"; import type { Actor, Asset, Site } from "@cloudhal/core-contracts"; -import { InvariantError, termName, type UploadTicket } from "@cloudhal/core-ts"; +import { entryPath, InvariantError, termName, type UploadTicket } from "@cloudhal/core-ts"; import { assetUrl, formatBytes, @@ -355,7 +355,7 @@ function assetName(asset: Asset, locale: string): string { } /** Where an asset is used, with each node's title and path (Studio's "Used by" list). */ -async function usesOf(c: Context, asset: Asset, locale: string): Promise { +async function usesOf(c: Context, asset: Asset, site: Site): Promise { const store = c.get("store"); const usage = (await store.assetUsage(asset.siteId, [asset.id])).get(asset.id); const out: AssetUse[] = []; @@ -366,7 +366,7 @@ async function usesOf(c: Context, asset: Asset, locale: string): Promise, asset: Asset, locale: string): Promise = new Hono() const store = c.get("store"); const site = await requireSite(c); const asset = await loadAsset(c); - const uses = await usesOf(c, asset, site.defaultLocale); + const uses = await usesOf(c, asset, site); const blob = asset.storage.kind === "blob" ? await store.blobState(site.id, asset.sha256) : undefined; const detail: AssetDetail = { @@ -785,7 +785,7 @@ export const assets: Hono = new Hono() await c.get("store").deleteAsset({ ...asset, rev }); } catch (e) { if (e instanceof InvariantError && e.code === "in_use") { - const uses = await usesOf(c, asset, site.defaultLocale); + const uses = await usesOf(c, asset, site); throw apiError(409, "in_use", e.message, { uses }); } throw e; diff --git a/packages/api/src/routes/comments.ts b/packages/api/src/routes/comments.ts index 6f5678bf..6611776d 100644 --- a/packages/api/src/routes/comments.ts +++ b/packages/api/src/routes/comments.ts @@ -342,7 +342,7 @@ export const comments: Hono = new Hono() ]); const entries = await commentEntries( c.get("store"), - site.id, + site, stats.busiest.map((b) => b.nodeId), ); return c.json({ @@ -388,7 +388,7 @@ export const comments: Hono = new Hono() ).flat(); const entries = await commentEntries( c.get("store"), - site.id, + site, found.map((x) => x.nodeId), ); const own = new Set((await ownScope(c, "comments:moderate", entries.values())) ?? []); @@ -406,7 +406,7 @@ export const comments: Hono = new Hono() if (!report) throw notFound("report not found"); const comment = await d.store.get(site.id, report.commentId); const entry = comment - ? (await commentEntries(c.get("store"), site.id, [comment.nodeId])).get(comment.nodeId) + ? (await commentEntries(c.get("store"), site, [comment.nodeId])).get(comment.nodeId) : undefined; if (entry) await assertOwn(c, "comments:moderate", [entry]); const resolution = typeof b.resolution === "string" ? b.resolution : ""; @@ -690,7 +690,7 @@ export const comments: Hono = new Hono() const d = deps(c); const site = await requireSite(c); const nodeId = param(c, "nodeId"); - const entry = (await commentEntries(c.get("store"), site.id, [nodeId])).get(nodeId); + const entry = (await commentEntries(c.get("store"), site, [nodeId])).get(nodeId); if (!entry) throw notFound("article not found"); return c.json({ thread: await threadView(d, site.id, nodeId, entry, c.get("now")()), @@ -703,7 +703,7 @@ export const comments: Hono = new Hono() const actor = person(c); const b = await body(c); const nodeId = param(c, "nodeId"); - const entry = (await commentEntries(c.get("store"), site.id, [nodeId])).get(nodeId); + const entry = (await commentEntries(c.get("store"), site, [nodeId])).get(nodeId); if (!entry) throw notFound("article not found"); await assertOwn(c, "comments:moderate", [entry]); if (!Number.isInteger(b.rev)) throw badRequest("rev is required (0 before the first change)"); diff --git a/packages/api/src/routes/menus.ts b/packages/api/src/routes/menus.ts index e0ad76a8..181a7444 100644 --- a/packages/api/src/routes/menus.ts +++ b/packages/api/src/routes/menus.ts @@ -14,6 +14,7 @@ import { archivePath, taxonomyIndex, termAncestors } from "@cloudhal/astro-rende import type { Menu, MenuItem, Site } from "@cloudhal/core-contracts"; import { ConflictError, + entryPath, InvariantError, normalisePath, type SiteGraphStore, @@ -228,7 +229,7 @@ function preview( const nav = toNavigation(resolved); if (!path) return { key, derived: false, resolved, navigation: markActive(nav) }; const clean = normalisePath(path); - const entry = data.entries.find((e) => `/${e.collection}/${e.slug}` === clean); + const entry = data.entries.find((e) => entryPath(data.site, e) === clean); const index = taxonomyIndex(data.taxonomies, data.terms, locale); const archive = data.terms.find((t) => { const tax = index.byId.get(t.taxonomyId); diff --git a/packages/api/src/routes/search.ts b/packages/api/src/routes/search.ts index 837e14f3..7117039f 100644 --- a/packages/api/src/routes/search.ts +++ b/packages/api/src/routes/search.ts @@ -26,6 +26,7 @@ export const siteSearch: Hono = new Hono().get("/", async (c) => { return c.json( await searchSiteNodes(c.get("store"), site.id, c.req.query("q") ?? "", { kinds, + site, ...(Number.isInteger(limit) && limit > 0 ? { limit } : {}), }), ); diff --git a/packages/astro-renderer/src/comments-view.ts b/packages/astro-renderer/src/comments-view.ts index 5c639779..ac6bd6c7 100644 --- a/packages/astro-renderer/src/comments-view.ts +++ b/packages/astro-renderer/src/comments-view.ts @@ -35,10 +35,10 @@ import { withoutScriptAllowed, } from "@cloudhal/comments"; import type { ContentEntry } from "@cloudhal/core-contracts"; +import { entryPath, type PermalinkSite } from "@cloudhal/core-ts"; import { addCacheTags, commentsCacheTag } from "./cache-tags.ts"; import { turnstileCsp } from "./csp.ts"; import type { CloudhalRendererConfig } from "./integration.ts"; -import { entryPath } from "./routing.ts"; export { type CommentStrings, commentStrings, commentsHtml } from "./comments-html.ts"; @@ -107,7 +107,12 @@ export async function loadComments( config: CloudhalRendererConfig, astro: CommentsAstro, entry: ContentEntry, - options: { readonly locale?: string; readonly now?: Date } = {}, + options: { + readonly locale?: string; + readonly now?: Date; + /** The site's collection permalinks (RFC-0038), for the entry's path. */ + readonly site?: PermalinkSite; + } = {}, ): Promise { const backend = await commentsBackend(config); if (!backend) return undefined; @@ -115,7 +120,7 @@ export async function loadComments( addCacheTags(headers, [commentsCacheTag(entry.id)]); const base = { nodeId: entry.id, - path: entryPath(entry), + path: entryPath(options.site, entry), locale: options.locale ?? entry.locale, ...(config.commentsScript ? { script: config.commentsScript } : {}), }; diff --git a/packages/astro-renderer/src/comments.ts b/packages/astro-renderer/src/comments.ts index 8a3b4d88..85d0ff87 100644 --- a/packages/astro-renderer/src/comments.ts +++ b/packages/astro-renderer/src/comments.ts @@ -38,7 +38,7 @@ import { } from "@cloudhal/comments"; import type { SqlCommentStore } from "@cloudhal/comments/sql"; import type { Site } from "@cloudhal/core-contracts"; -import { entryPath } from "./routing.ts"; +import { entryPath } from "@cloudhal/core-ts"; import type { GraphSource } from "./source.ts"; export const COMMENTS_PATH = "/__cloudhal/comments"; @@ -146,7 +146,7 @@ export class SiteComments { return { nodeId: entry.id, collection: entry.collection, - path: entryPath(entry), + path: entryPath(await source.site(), entry), publishedAt: entry.publishedAt ?? null, }; } diff --git a/packages/astro-renderer/src/components/ContentList.astro b/packages/astro-renderer/src/components/ContentList.astro index 451def2b..f3ce4790 100644 --- a/packages/astro-renderer/src/components/ContentList.astro +++ b/packages/astro-renderer/src/components/ContentList.astro @@ -1,12 +1,15 @@ --- import type { ContentEntry, ContentListSection } from "@cloudhal/core-contracts"; +import { entryPath } from "@cloudhal/core-ts"; +import type { MediaContext } from "../media.ts"; interface Props { section: ContentListSection; entries: ContentEntry[]; + media?: MediaContext | undefined; hrefFor?: (entry: ContentEntry) => string; } -const { section, entries, hrefFor = (e) => `/${e.collection}/${e.slug}` } = Astro.props; +const { section, entries, media, hrefFor = (e) => entryPath(media?.permalinks, e) } = Astro.props; const sorted = [...entries] .sort((a, b) => section.sort === "title" diff --git a/packages/astro-renderer/src/feeds.ts b/packages/astro-renderer/src/feeds.ts index 5d65b1b6..109e292e 100644 --- a/packages/astro-renderer/src/feeds.ts +++ b/packages/astro-renderer/src/feeds.ts @@ -4,11 +4,11 @@ * published only, noindex respected, canonical elsewhere excluded — live in one tested place. */ import type { ContentEntry, Page, Redirect, Site } from "@cloudhal/core-contracts"; +import { entryPath } from "@cloudhal/core-ts"; import type { ArchiveSummary, ArchiveView } from "./archive.ts"; import { markdownText, renderBody } from "./markdown.ts"; import type { AssetIndex } from "./media.ts"; import { notFoundPageIds } from "./not-found.ts"; -import { entryPath } from "./routing.ts"; import { absoluteUrl } from "./seo.ts"; import { entryTaxonomy, type TaxonomyIndex } from "./taxonomy.ts"; @@ -75,7 +75,7 @@ export function rssXml(options: FeedOptions): string { const items = published(options.entries) .slice(0, FEED_LIMIT) .map((e) => { - const url = `${base}${entryPath(e)}`; + const url = `${base}${entryPath(site, e)}`; const date = new Date(e.publishedAt ?? e.createdAt).toUTCString(); const cover = e.coverImage?.assetId ? options.assets?.get(e.coverImage.assetId) : undefined; const image = cover @@ -181,7 +181,7 @@ export function sitemapUrls( urls.push({ loc: own, lastmod: page.updatedAt }); } for (const entry of published(entries)) { - const path = entryPath(entry); + const path = entryPath(site, entry); if (entry.locale !== site.defaultLocale || seen.has(path)) continue; seen.add(path); urls.push({ loc: `${origin}${path}`, lastmod: entry.updatedAt }); diff --git a/packages/astro-renderer/src/locale-feeds.ts b/packages/astro-renderer/src/locale-feeds.ts index 50ccd220..4bd15203 100644 --- a/packages/astro-renderer/src/locale-feeds.ts +++ b/packages/astro-renderer/src/locale-feeds.ts @@ -4,17 +4,17 @@ * single-locale functions in `feeds.ts`; this module only loops over locales and localizes URLs. */ import type { ContentEntry, Page, Site } from "@cloudhal/core-contracts"; +import { entryPath, type PermalinkSite } from "@cloudhal/core-ts"; import { archiveSummaries } from "./archive.ts"; import { FEED_LIMIT, rssXml, type SitemapUrl, sitemapUrls } from "./feeds.ts"; import { hostLocale, localeUrl } from "./locales.ts"; import { loadAssetIndex } from "./media.ts"; import { notFoundPageIds } from "./not-found.ts"; -import { entryPath } from "./routing.ts"; import type { GraphSource } from "./source.ts"; import { loadTaxonomyIndex } from "./taxonomy.ts"; type Localized = Page | ContentEntry; -const pathOf = (n: Localized) => ("path" in n ? n.path : entryPath(n)); +const pathOf = (site: PermalinkSite, n: Localized) => ("path" in n ? n.path : entryPath(site, n)); /** The locales a host serves: its own one, or every locale without a domain of its own. */ export function hostLocales(site: Site, host: string): string[] { @@ -46,7 +46,7 @@ function alternateIndex(site: Site, nodes: readonly Localized[], origin: string) const out = members .filter((m) => site.locales.includes(m.locale)) .sort((a, b) => site.locales.indexOf(a.locale) - site.locales.indexOf(b.locale)) - .map((m) => ({ locale: m.locale, href: localeUrl(site, m.locale, pathOf(m), origin) })); + .map((m) => ({ locale: m.locale, href: localeUrl(site, m.locale, pathOf(site, m), origin) })); const fallback = out.find((a) => a.locale === site.defaultLocale); return fallback ? [...out, { locale: "x-default", href: fallback.href }] : out; }; @@ -72,7 +72,9 @@ export async function localizedSitemap( const archives = archiveSummaries(await loadTaxonomyIndex(source, locale), entries); // `sitemapUrls` lists the site's default locale: view the site as if `locale` were it. const urls = sitemapUrls({ ...site, defaultLocale: locale }, pages, entries, origin, archives); - const byPath = new Map(nodes.filter((n) => n.locale === locale).map((n) => [pathOf(n), n])); + const byPath = new Map( + nodes.filter((n) => n.locale === locale).map((n) => [pathOf(site, n), n]), + ); for (const u of urls) { const path = u.loc.slice(origin.length) || "/"; const node = byPath.get(path); @@ -127,7 +129,7 @@ export async function llmsTxt( const entries = (await source.allEntries(locale)).filter(visible).slice(0, FEED_LIMIT * 4); const line = (n: Localized) => { const summary = "summary" in n && n.summary ? `: ${n.summary.replace(/\s+/g, " ")}` : ""; - return `- [${n.title}](${localeUrl(site, locale, pathOf(n), origin)})${summary}`; + return `- [${n.title}](${localeUrl(site, locale, pathOf(site, n), origin)})${summary}`; }; const others = site.locales.filter((l) => l !== locale); const description = home?.seo?.description ?? site.name; diff --git a/packages/astro-renderer/src/locales.ts b/packages/astro-renderer/src/locales.ts index f7be2605..1e5b54b0 100644 --- a/packages/astro-renderer/src/locales.ts +++ b/packages/astro-renderer/src/locales.ts @@ -7,7 +7,7 @@ * links to the same locale. Worker-safe. */ import type { ContentEntry, Page, Site, Taxonomy, Term } from "@cloudhal/core-contracts"; -import { termSlug } from "@cloudhal/core-ts"; +import { entryPath, type PermalinkSite, termSlug } from "@cloudhal/core-ts"; import type { HeadMeta } from "./seo.ts"; import type { GraphSource } from "./source.ts"; @@ -114,7 +114,7 @@ export interface Alternate { } type Localized = Page | ContentEntry; -const pathOf = (n: Localized) => ("path" in n ? n.path : `/${n.collection}/${n.slug}`); +const pathOf = (site: PermalinkSite, n: Localized) => ("path" in n ? n.path : entryPath(site, n)); /** The visible members of `group` in this source (pages, or entries in the site's locales). */ async function groupMembers( @@ -159,7 +159,7 @@ export async function alternatesFor( ); const out: Alternate[] = own.map((m) => ({ locale: m.locale, - href: localeUrl(site, m.locale, pathOf(m), origin), + href: localeUrl(site, m.locale, pathOf(site, m), origin), current: m.id === node.id, ...(m.id === group ? { original: true } : {}), })); @@ -169,7 +169,7 @@ export async function alternatesFor( if (peerSite.locales.includes(m.locale) && !out.some((a) => a.locale === m.locale)) out.push({ locale: m.locale, - href: localeUrl(peerSite, m.locale, pathOf(m), peer.origin), + href: localeUrl(peerSite, m.locale, pathOf(peerSite, m), peer.origin), current: false, ...(m.id === group ? { original: true } : {}), }); @@ -222,7 +222,7 @@ export async function linkLocalizer( const map = new Map(); for (const n of [...pages.filter((p) => p.locale === def), ...entries]) { const twin = byGroup.get(`${"path" in n ? "p" : "e"}:${n.translation?.group ?? n.id}`); - map.set(pathOf(n), twin ? here(pathOf(twin)) : home(pathOf(n))); + map.set(pathOf(site, n), twin ? here(pathOf(site, twin)) : home(pathOf(site, n))); } const archive = archiveLocalizer(taxonomies, terms, locale, def); return (path) => map.get(path) ?? (archive(path) ? here(archive(path) as string) : here(path)); diff --git a/packages/astro-renderer/src/media.ts b/packages/astro-renderer/src/media.ts index 31c4b0db..266bf572 100644 --- a/packages/astro-renderer/src/media.ts +++ b/packages/astro-renderer/src/media.ts @@ -10,6 +10,7 @@ */ import type { Asset, ImageRef } from "@cloudhal/core-contracts"; +import type { PermalinkSite } from "@cloudhal/core-ts"; import type { BlockRegistry } from "@cloudhal/core-ts/blocks"; import type { IconSet } from "@cloudhal/core-ts/icons"; import { assetUrl, formatBytes, MEDIA_TYPES, mediaPath } from "@cloudhal/core-ts/media"; @@ -45,6 +46,8 @@ export interface MediaContext { | undefined; /** RFC-0028 §4: the page's placeholder allowance, shared by every image it draws. */ readonly budget?: PlaceholderBudget | undefined; + /** RFC-0038: the site's collection permalinks, for links to entries (`entryPath`). */ + readonly permalinks?: PermalinkSite | undefined; } export interface ImageAttributeOptions { diff --git a/packages/astro-renderer/src/navigation.ts b/packages/astro-renderer/src/navigation.ts index 8bd6a69d..d9246a1d 100644 --- a/packages/astro-renderer/src/navigation.ts +++ b/packages/astro-renderer/src/navigation.ts @@ -19,7 +19,7 @@ import type { Taxonomy, Term, } from "@cloudhal/core-contracts"; -import { normalisePath, termName } from "@cloudhal/core-ts"; +import { entryPath, normalisePath, termName } from "@cloudhal/core-ts"; import { type MenuLookup, type MenuLookupNode, @@ -79,9 +79,6 @@ export interface LookupData { readonly collectionIndex?: CollectionIndexHook; } -/** `//`. */ -const entryHref = (e: Pick) => `/${e.collection}/${e.slug}`; - /** * A `MenuLookup` over a site's nodes (§7.1 table). Term items follow `archiveState` (200 only when * the archive lists entries the site serves in the locale); a collection index comes from the theme @@ -124,7 +121,7 @@ export function menuLookup(data: LookupData): MenuLookup { }, entry: (id) => { const e = entries.get(id); - return e ? node(e, entryHref(e)) : undefined; + return e ? node(e, entryPath(data.site, e)) : undefined; }, term: (id, locale) => { const term = terms.get(id); diff --git a/packages/astro-renderer/src/preview.ts b/packages/astro-renderer/src/preview.ts index 3d282bb2..81a307c6 100644 --- a/packages/astro-renderer/src/preview.ts +++ b/packages/astro-renderer/src/preview.ts @@ -7,6 +7,7 @@ import type { Taxonomy, Term, } from "@cloudhal/core-contracts"; +import { entryPath } from "@cloudhal/core-ts"; import { type ArchiveState, type ArchiveView, @@ -19,7 +20,6 @@ import { renderComponentSection } from "./blocks.ts"; import { gateNotice } from "./gates.ts"; import { renderBody, renderEntryArticle, renderMarkdown, safeHref, safeSrc } from "./markdown.ts"; import { assetCaption, imageAttributes, imgHtml, type MediaContext } from "./media.ts"; -import { entryPath } from "./routing.ts"; import { archiveHead, siteOrigin } from "./seo.ts"; import { pickEntries, renderSurface, surfaceSources } from "./surfaces.ts"; import { @@ -126,7 +126,7 @@ export function renderSectionHtml( const items = list .map( (e) => - `
  • ${esc(e.title)}${e.summary ? `

    ${esc(e.summary)}

    ` : ""}
  • `, + `
  • ${esc(e.title)}${e.summary ? `

    ${esc(e.summary)}

    ` : ""}
  • `, ) .join(""); return `
    ${ @@ -142,7 +142,10 @@ export function renderSectionHtml( // filter is applied on the site; a preview shows the collection's newest). case "surface": { const sources = Object.fromEntries( - surfaceSources(section).map((q) => [q.key, pickEntries(entries[q.collection] ?? [], q)]), + surfaceSources(section).map((q) => [ + q.key, + pickEntries(entries[q.collection] ?? [], q, media?.permalinks), + ]), ); return renderSurface(section, { ...(media ? { media } : {}), sources, preview: true }); } @@ -210,7 +213,13 @@ export function renderPagePreview( /** Previews resolve `asset:` references and asset-backed images, never through transformations. */ function previewMedia(site: Site, media: MediaContext | undefined): MediaContext { - return { locale: site.defaultLocale, defaultLocale: site.defaultLocale, ...media, preview: true }; + return { + locale: site.defaultLocale, + defaultLocale: site.defaultLocale, + permalinks: site, + ...media, + preview: true, + }; } /** @@ -228,7 +237,7 @@ export function renderEntryPreview( renderEntryArticle(entry, site.defaultLocale, previewMedia(site, media)); return { title: entry.title, - path: `/${entry.collection}/${entry.slug}`, + path: entryPath(site, entry), html: document(site, entry.title, entry.summary, body), }; } @@ -418,7 +427,7 @@ export function renderArchivePreview( : `
      ${view.entries .map( (e) => - `
    • ${esc(e.title)}${e.summary ? `

      ${esc(e.summary)}

      ` : ""}${ + `
    • ${esc(e.title)}${e.summary ? `

      ${esc(e.summary)}

      ` : ""}${ e.publishedAt ? `` : "" diff --git a/packages/astro-renderer/src/routing.ts b/packages/astro-renderer/src/routing.ts index 8cae8a88..05727248 100644 --- a/packages/astro-renderer/src/routing.ts +++ b/packages/astro-renderer/src/routing.ts @@ -1,12 +1,13 @@ /** * How URLs map onto the graph, shared by the starter's routes, sitemaps, feeds and previews: a - * Page owns its `path`; an entry lives at `//`; a term archive at + * Page owns its `path`; an entry lives at its collection's permalink (core-ts `entryPath`, + * `//` by default, RFC-0038); a term archive at * `/[/page/|/rss.xml]` (RFC-0002 §7). Pages win when several could answer, so a * page at `/posts/x` is never shadowed by an article. Anything else asks the site's redirects * before it is a 404 (or a 410 for a retired term). */ import type { ContentEntry, Page, Redirect, Site } from "@cloudhal/core-contracts"; -import { normalisePath, SEARCH_PATH, termName } from "@cloudhal/core-ts"; +import { entryAt, entryPath, normalisePath, SEARCH_PATH, termName } from "@cloudhal/core-ts"; import { type ArchiveSummary, type ArchiveView, @@ -31,33 +32,46 @@ import { taxonomyIndex, } from "./taxonomy.ts"; -export function entryPath(entry: Pick): string { - return `/${entry.collection}/${entry.slug}`; -} +/** Themes link entries with core-ts's `entryPath(site, entry)`; re-exported for them. */ +export { entryAt, entryPath }; export type Resolved = { kind: "page"; page: Page } | { kind: "entry"; entry: ContentEntry }; +const SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; + /** The page or entry at `path` (leading slash, no trailing slash except "/") in `locale`, if any. */ export async function resolvePath( source: GraphSource, path: string, locale?: string, ): Promise { - const page = await source.pageByPath(path, locale); + return resolveIn(source, path, { locale }); +} + +/** `resolvePath` with the site already loaded (`resolveRoute` has it). */ +async function resolveIn( + source: GraphSource, + path: string, + options: { locale?: string | undefined; site?: Site }, +): Promise { + const page = await source.pageByPath(path, options.locale); if (page) return { kind: "page", page }; - const m = /^\/([a-z0-9]+(?:-[a-z0-9]+)*)\/([a-z0-9]+(?:-[a-z0-9]+)*)$/.exec(path); - if (!m) return undefined; - const entry = await source.entryBySlug(m[1] as string, m[2] as string, locale); + const at = entryAt(options.site ?? (await source.site()), path); + if (!at || !SLUG.test(at.collection) || !SLUG.test(at.slug)) return undefined; + const entry = await source.entryBySlug(at.collection, at.slug, options.locale); return entry ? { kind: "entry", entry } : undefined; } /** Every page and entry path of a locale (default: the site's), as graph paths without prefix. */ export async function routablePaths(source: GraphSource, locale?: string): Promise { const pages = await source.pages(); - const loc = locale ?? (await source.site()).defaultLocale; + const site = await source.site(); + const loc = locale ?? site.defaultLocale; const pagePaths = pages.filter((p) => p.locale === loc).map((p) => p.path); const taken = new Set(pagePaths); - const entryPaths = (await source.allEntries(loc)).map(entryPath).filter((p) => !taken.has(p)); + const entryPaths = (await source.allEntries(loc)) + .map((e) => entryPath(site, e)) + .filter((p) => !taken.has(p)); return [...pagePaths, ...entryPaths]; } @@ -148,7 +162,7 @@ export async function resolveRoute( const path = normalisePath(requestPath); const site = await source.site(); const locale = options.locale ?? site.defaultLocale; - const resolved = await resolvePath(source, path, locale); + const resolved = await resolveIn(source, path, { locale, site }); if (resolved) return resolved; if (path === SEARCH_PATH && source.search) { const { query, page } = searchRequest(options.search ?? ""); diff --git a/packages/astro-renderer/src/seo.ts b/packages/astro-renderer/src/seo.ts index ee9fbf85..91a7765d 100644 --- a/packages/astro-renderer/src/seo.ts +++ b/packages/astro-renderer/src/seo.ts @@ -4,10 +4,10 @@ * gets the same, tested output and themes only decide where to render ``. */ import type { ContentEntry, ImageRef, Page, Site } from "@cloudhal/core-contracts"; +import { entryPath } from "@cloudhal/core-ts"; import type { ArchiveView } from "./archive.ts"; import { inlineMarkdownText, markdownText, safeSrc } from "./markdown.ts"; import { assetAlt, imageObject, type MediaContext, socialImageUrl } from "./media.ts"; -import { entryPath } from "./routing.ts"; import { type EntryTaxonomy, entryTaxonomy, type ShownTerm } from "./taxonomy.ts"; export interface HeadMeta { @@ -227,7 +227,7 @@ export function entryHead( media?: MediaContext, comments?: { readonly commentCount: number; readonly comment?: readonly object[] }, ): HeadMeta { - const url = `${origin}${entryPath(entry)}`; + const url = `${origin}${entryPath(site, entry)}`; const description = clip(entry.summary); const image = cardImage(entry.coverImage, origin, entry.locale, media); const title = `${entry.title} · ${site.name}`; @@ -384,7 +384,7 @@ export function archiveHead( itemListElement: view.entries.map((e, i) => ({ "@type": "ListItem", position: (view.page - 1) * view.perPage + i + 1, - url: `${origin}${entryPath(e)}`, + url: `${origin}${entryPath(site, e)}`, })), }, }, diff --git a/packages/astro-renderer/src/source.ts b/packages/astro-renderer/src/source.ts index c5a879e3..7f3805a1 100644 --- a/packages/astro-renderer/src/source.ts +++ b/packages/astro-renderer/src/source.ts @@ -255,6 +255,7 @@ export class LiveSource implements GraphSource { const rows = searchDocuments({ locales: [locale], defaultLocale: site.defaultLocale, + site, entries, pages: pages.filter((p) => p.locale === locale), taxonomies, diff --git a/packages/astro-renderer/src/surfaces/tree.ts b/packages/astro-renderer/src/surfaces/tree.ts index 75163cae..850fdd93 100644 --- a/packages/astro-renderer/src/surfaces/tree.ts +++ b/packages/astro-renderer/src/surfaces/tree.ts @@ -4,6 +4,7 @@ * out. Worker-safe. */ import type { ContentEntry, SurfaceSection } from "@cloudhal/core-contracts"; +import { entryPath, type PermalinkSite } from "@cloudhal/core-ts"; import type { BlockRegistry } from "@cloudhal/core-ts/blocks"; import { type Catalog, @@ -69,10 +70,13 @@ export interface SurfaceEntryCard { collection: string; } -export function surfaceEntryCard(e: ContentEntry): SurfaceEntryCard { +export function surfaceEntryCard( + site: PermalinkSite | undefined, + e: ContentEntry, +): SurfaceEntryCard { return { id: e.id, - href: `/${e.collection}/${e.slug}`, + href: entryPath(site, e), title: e.title, collection: e.collection, ...(e.summary ? { summary: e.summary } : {}), @@ -104,6 +108,7 @@ export function surfaceSources( export function pickEntries( entries: readonly ContentEntry[], q: { sort: string; limit: number }, + site?: PermalinkSite, ): SurfaceEntryCard[] { const at = (e: ContentEntry) => e.publishedAt ?? e.createdAt; // The page's source already holds only what this response may show (published, or the draft @@ -117,7 +122,7 @@ export function pickEntries( : at(b).localeCompare(at(a)), ) .slice(0, q.limit) - .map(surfaceEntryCard); + .map((e) => surfaceEntryCard(site, e)); } export interface SurfaceContext { @@ -290,6 +295,7 @@ export function surfaceTree(section: SurfaceSection, ctx: SurfaceContext = {}): /** What a page's source offers for entry sources (`GraphSource`). */ export interface SurfaceEntrySource { + site(): Promise; entries(collection: string): Promise; entriesByFilter(filter: string): Promise; } @@ -303,6 +309,7 @@ export async function loadSurfaceSources( sections: readonly { kind: string }[], ): Promise>> { const out: Record> = {}; + const site = sections.some((s) => s.kind === "surface") ? await source.site() : undefined; for (const s of sections) { if (s.kind !== "surface") continue; const section = s as SurfaceSection; @@ -313,7 +320,7 @@ export async function loadSurfaceSources( .entriesByFilter(`collection:${q.collection}+${q.filter}`) .catch(() => [] as ContentEntry[]) : await source.entries(q.collection); - loaded[q.key] = pickEntries(list, q); + loaded[q.key] = pickEntries(list, q, site); } out[section.id] = loaded; } diff --git a/packages/astro-renderer/test/surfaces.test.ts b/packages/astro-renderer/test/surfaces.test.ts index 0b6665a5..0e072e26 100644 --- a/packages/astro-renderer/test/surfaces.test.ts +++ b/packages/astro-renderer/test/surfaces.test.ts @@ -190,6 +190,7 @@ describe("entry sources", () => { const asked: string[] = []; const sources = await loadSurfaceSources( { + site: async () => ({}), entries: async () => posts, entriesByFilter: async (f) => { asked.push(f); diff --git a/packages/cli/src/commands/analytics.ts b/packages/cli/src/commands/analytics.ts index 95fdaeaa..c3105427 100644 --- a/packages/cli/src/commands/analytics.ts +++ b/packages/cli/src/commands/analytics.ts @@ -14,7 +14,7 @@ import { rangeFromParams, } from "@cloudhal/analytics/service"; import { SqlAnalytics } from "@cloudhal/analytics/sql"; -import type { SiteGraphStore } from "@cloudhal/core-ts"; +import { entryPath, type SiteGraphStore } from "@cloudhal/core-ts"; import { type LocalStore, openLocalStore } from "@cloudhal/core-ts/local"; import type { SqlDriver } from "@cloudhal/storage"; import { @@ -247,7 +247,8 @@ export interface SeedCliOptions { /** The site's published pages, articles and public archives, most important first. */ async function publishedNodes(store: SiteGraphStore, siteId: string): Promise { - const [pages, entries, terms, taxonomies] = await Promise.all([ + const [site, pages, entries, terms, taxonomies] = await Promise.all([ + store.getSite(siteId), store.listPages(siteId), store.listContentEntries(siteId, { status: ["published"] }), store.listTerms(siteId), @@ -265,7 +266,7 @@ async function publishedNodes(store: SiteGraphStore, siteId: string): Promise ({ nodeId: e.id, nodeKind: "entry" as const, - path: `/${e.collection}/${e.slug}`, + path: entryPath(site, e), locale: e.locale, })), ...pages diff --git a/packages/core-contracts/fixtures/invalid/site/permalink-trailing-slash.json b/packages/core-contracts/fixtures/invalid/site/permalink-trailing-slash.json new file mode 100644 index 00000000..3c4743e3 --- /dev/null +++ b/packages/core-contracts/fixtures/invalid/site/permalink-trailing-slash.json @@ -0,0 +1,25 @@ +{ + "reason": "a permalink ends in /{slug}, with no trailing slash", + "data": { + "schemaVersion": 1, + "id": "site_01HZX8K3Q9T1", + "projectId": "proj_01HZX8K3Q9AB", + "name": "Minimal", + "slug": "minimal", + "defaultLocale": "en", + "locales": ["en"], + "domains": [], + "pageIds": [], + "theme": { + "name": "blog" + }, + "rev": 0, + "createdAt": "2026-10-07T20:00:00Z", + "updatedAt": "2026-10-07T20:00:00Z", + "collections": { + "posts": { + "permalink": "/blog/{slug}/" + } + } + } +} diff --git a/packages/core-contracts/fixtures/invalid/site/permalink-unknown-token.json b/packages/core-contracts/fixtures/invalid/site/permalink-unknown-token.json new file mode 100644 index 00000000..b3b16ef6 --- /dev/null +++ b/packages/core-contracts/fixtures/invalid/site/permalink-unknown-token.json @@ -0,0 +1,25 @@ +{ + "reason": "version 1 permalinks have only the {slug} token", + "data": { + "schemaVersion": 1, + "id": "site_01HZX8K3Q9T2", + "projectId": "proj_01HZX8K3Q9AB", + "name": "Minimal", + "slug": "minimal", + "defaultLocale": "en", + "locales": ["en"], + "domains": [], + "pageIds": [], + "theme": { + "name": "blog" + }, + "rev": 0, + "createdAt": "2026-10-07T20:00:00Z", + "updatedAt": "2026-10-07T20:00:00Z", + "collections": { + "posts": { + "permalink": "/{year}/{slug}" + } + } + } +} diff --git a/packages/core-contracts/fixtures/invalid/site/permalink-without-slug.json b/packages/core-contracts/fixtures/invalid/site/permalink-without-slug.json new file mode 100644 index 00000000..83830efc --- /dev/null +++ b/packages/core-contracts/fixtures/invalid/site/permalink-without-slug.json @@ -0,0 +1,25 @@ +{ + "reason": "a permalink must end in {slug}", + "data": { + "schemaVersion": 1, + "id": "site_01HZX8K3Q9T3", + "projectId": "proj_01HZX8K3Q9AB", + "name": "Minimal", + "slug": "minimal", + "defaultLocale": "en", + "locales": ["en"], + "domains": [], + "pageIds": [], + "theme": { + "name": "blog" + }, + "rev": 0, + "createdAt": "2026-10-07T20:00:00Z", + "updatedAt": "2026-10-07T20:00:00Z", + "collections": { + "posts": { + "permalink": "/blog" + } + } + } +} diff --git a/packages/core-contracts/fixtures/valid/site/collection-permalinks.json b/packages/core-contracts/fixtures/valid/site/collection-permalinks.json new file mode 100644 index 00000000..8a36c7ab --- /dev/null +++ b/packages/core-contracts/fixtures/valid/site/collection-permalinks.json @@ -0,0 +1,26 @@ +{ + "schemaVersion": 1, + "id": "site_01HZX8K3Q9CP", + "projectId": "proj_01HZX8K3Q9AB", + "name": "Minimal", + "slug": "minimal", + "defaultLocale": "en", + "locales": ["en"], + "domains": [], + "pageIds": [], + "theme": { + "name": "blog" + }, + "rev": 0, + "createdAt": "2026-10-07T20:00:00Z", + "updatedAt": "2026-10-07T20:00:00Z", + "collections": { + "posts": { + "permalink": "/blog/{slug}" + }, + "guides": { + "permalink": "/docs/guides/{slug}" + }, + "news": {} + } +} diff --git a/packages/core-contracts/schemas/site.schema.json b/packages/core-contracts/schemas/site.schema.json index 177d66cb..f8e93423 100644 --- a/packages/core-contracts/schemas/site.schema.json +++ b/packages/core-contracts/schemas/site.schema.json @@ -45,6 +45,12 @@ }, "homePageId": { "$ref": "common.schema.json#/$defs/Id" }, "localization": { "$ref": "#/$defs/Localization" }, + "collections": { + "type": "object", + "description": "RFC-0038: per-collection settings, keyed by collection (ContentEntry.collection). A collection without an entry here uses the defaults.", + "propertyNames": { "$ref": "common.schema.json#/$defs/Slug" }, + "additionalProperties": { "$ref": "#/$defs/CollectionSettings" } + }, "theme": { "$ref": "#/$defs/ThemeRef" }, "design": { "$ref": "#/$defs/Design" }, "brand": { "$ref": "#/$defs/Brand" }, @@ -63,6 +69,17 @@ "metadata": { "$ref": "common.schema.json#/$defs/Metadata" } }, "$defs": { + "CollectionSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "permalink": { + "type": "string", + "description": "RFC-0038: where the collection's entries live, as a template with a closed token set. Version 1: one or more fixed lower-case path segments followed by /{slug}, e.g. /blog/{slug}. Absent means //{slug}.", + "pattern": "^(/[a-z0-9]+(-[a-z0-9]+)*)+/\\{slug\\}$" + } + } + }, "Brand": { "type": "object", "description": "The site's logo and social links (RFC-0025 \u00a71), shared by the header, the footer and SEO.", diff --git a/packages/core-contracts/src/generated/index.ts b/packages/core-contracts/src/generated/index.ts index 9015974e..e8961d20 100644 --- a/packages/core-contracts/src/generated/index.ts +++ b/packages/core-contracts/src/generated/index.ts @@ -6,7 +6,7 @@ */ export type { SchemaVersion, Id, Slug, Revision, Timestamp, Project, Metadata } from "./project.ts"; -export type { Locale, Section, BodyProfile, ActorKind, BodyBlocks, SurfaceComponentId, Path, Section1, TimeZone, Site, Domain, Localization, ThemeRef, Design, Brand, ImageRef, SocialLink, Chrome, ChromeHeader, HeaderAction, ChromeFooter, FooterColumn, HeroSection, Link, RichTextSection, BodyBlock, Actor, ImageSection, ContentListSection, ComponentSection, SurfaceSection, SurfaceComponent, Announcement, Consent } from "./site.ts"; +export type { Locale, Section, BodyProfile, ActorKind, BodyBlocks, SurfaceComponentId, Path, Section1, TimeZone, Site, Domain, Localization, CollectionSettings, ThemeRef, Design, Brand, ImageRef, SocialLink, Chrome, ChromeHeader, HeaderAction, ChromeFooter, FooterColumn, HeroSection, Link, RichTextSection, BodyBlock, Actor, ImageSection, ContentListSection, ComponentSection, SurfaceSection, SurfaceComponent, Announcement, Consent } from "./site.ts"; export type { PublishStatus, TranslationMethod, MotionLevel, Page, Seo, Availability, Translation } from "./page.ts"; export type { ContentEntry, Body, TermAssignment, Correction } from "./content-entry.ts"; export type { Snapshot, NodeRef, ChangeSummary } from "./snapshot.ts"; diff --git a/packages/core-contracts/src/generated/schemas.ts b/packages/core-contracts/src/generated/schemas.ts index 6d0a2af9..b7283584 100644 --- a/packages/core-contracts/src/generated/schemas.ts +++ b/packages/core-contracts/src/generated/schemas.ts @@ -174,6 +174,16 @@ export const SiteSchema = { "localization": { "$ref": "#/$defs/Localization" }, + "collections": { + "type": "object", + "description": "RFC-0038: per-collection settings, keyed by collection (ContentEntry.collection). A collection without an entry here uses the defaults.", + "propertyNames": { + "$ref": "#/$defs/Slug" + }, + "additionalProperties": { + "$ref": "#/$defs/CollectionSettings" + } + }, "theme": { "$ref": "#/$defs/ThemeRef" }, @@ -548,6 +558,17 @@ export const SiteSchema = { } } }, + "CollectionSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "permalink": { + "type": "string", + "description": "RFC-0038: where the collection's entries live, as a template with a closed token set. Version 1: one or more fixed lower-case path segments followed by /{slug}, e.g. /blog/{slug}. Absent means //{slug}.", + "pattern": "^(/[a-z0-9]+(-[a-z0-9]+)*)+/\\{slug\\}$" + } + } + }, "Consent": { "type": "object", "additionalProperties": false, diff --git a/packages/core-contracts/src/generated/site.ts b/packages/core-contracts/src/generated/site.ts index 450a3e05..7b28b95c 100644 --- a/packages/core-contracts/src/generated/site.ts +++ b/packages/core-contracts/src/generated/site.ts @@ -136,6 +136,12 @@ export interface Site { pageIds: Id[]; homePageId?: Id; localization?: Localization; + /** + * RFC-0038: per-collection settings, keyed by collection (ContentEntry.collection). A collection without an entry here uses the defaults. + */ + collections?: { + [k: string]: CollectionSettings | undefined; + }; theme: ThemeRef; design?: Design; brand?: Brand; @@ -193,6 +199,16 @@ export interface Localization { */ label?: "unreviewed" | "always"; } +/** + * This interface was referenced by `Site`'s JSON-Schema + * via the `definition` "CollectionSettings". + */ +export interface CollectionSettings { + /** + * RFC-0038: where the collection's entries live, as a template with a closed token set. Version 1: one or more fixed lower-case path segments followed by /{slug}, e.g. /blog/{slug}. Absent means //{slug}. + */ + permalink?: string; +} /** * Theme (a.k.a. Experience) applied by the renderer. Config is theme-defined and validated by the theme's own schema, not here. * diff --git a/packages/core-contracts/src/generated/validators.js b/packages/core-contracts/src/generated/validators.js index 60f1aad2..2d7c062d 100644 --- a/packages/core-contracts/src/generated/validators.js +++ b/packages/core-contracts/src/generated/validators.js @@ -530,20 +530,22 @@ return errors === 0; validate20.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; export const validateSite = validate21; -const schema41 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/site.schema.json","title":"Site","description":"Root node of a Site Graph: one deployable website with its locales, domains, theme and page index. Pages and content entries are separate nodes referenced by id so they can be versioned and locked independently.","type":"object","additionalProperties":false,"required":["schemaVersion","id","projectId","name","slug","defaultLocale","locales","domains","pageIds","theme","rev","createdAt","updatedAt"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/Id"},"projectId":{"$ref":"#/$defs/Id"},"name":{"type":"string","minLength":1,"maxLength":200},"slug":{"$ref":"#/$defs/Slug"},"defaultLocale":{"$ref":"#/$defs/Locale"},"locales":{"type":"array","items":{"$ref":"#/$defs/Locale"},"minItems":1,"uniqueItems":true},"domains":{"type":"array","description":"Hostnames serving this site. Exactly one should be primary: the address canonical links, feeds and previews use. Sites run on their own domains or their Cloudflare account's workers.dev address, never on a Cloudhal domain (ADR-0037).","items":{"$ref":"#/$defs/Domain"}},"pageIds":{"type":"array","items":{"$ref":"#/$defs/Id"},"uniqueItems":true},"homePageId":{"$ref":"#/$defs/Id"},"localization":{"$ref":"#/$defs/Localization"},"theme":{"$ref":"#/$defs/ThemeRef"},"design":{"$ref":"#/$defs/Design"},"brand":{"$ref":"#/$defs/Brand"},"chrome":{"$ref":"#/$defs/Chrome"},"timeZone":{"$ref":"#/$defs/TimeZone","description":"The site's time zone (RFC-0008): Studio's calendar, schedules entered as wall-clock times and recurring automations use it. Absent means UTC."},"publishedSnapshotId":{"$ref":"#/$defs/Id","description":"Snapshot currently serving production. Rollback = pointing this at an older snapshot."},"rev":{"$ref":"#/$defs/Revision"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"metadata":{"$ref":"#/$defs/Metadata"}},"$defs":{"Actor":{"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}},"ActorKind":{"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]},"Announcement":{"type":"object","additionalProperties":false,"required":["id","text"],"properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,40}$","description":"Change it when the text changes, so readers who dismissed the old one see the new one."},"text":{"type":"string","minLength":1,"maxLength":200},"label":{"type":"string","minLength":1,"maxLength":40},"href":{"type":"string","minLength":1,"maxLength":2048},"tone":{"type":"string","enum":["info","brand","warning"]},"dismissible":{"type":"boolean"}}},"BodyBlock":{"type":"object","description":"One top-level block of a markdown body in the store's index (RFC-0014 §3). Written by the store only: a client's values are replaced on every write. `by`, `via` and `at` say who last changed the block's content, through which channel and when; they are absent for content written before the index existed.","additionalProperties":false,"required":["id","hash"],"properties":{"id":{"type":"string","pattern":"^blk_[0-9A-Za-z]{8,24}$","description":"Stable across writes that keep the block's content or its place among unchanged blocks."},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"SHA-256 (hex) of the block's markdown without trailing whitespace."},"by":{"$ref":"#/$defs/Actor"},"via":{"type":"string","enum":["studio","mcp","api","import","ai-command","proposal"],"description":"The channel of that change."},"at":{"$ref":"#/$defs/Timestamp"}}},"BodyBlocks":{"type":"array","description":"A markdown body's block index, in body order (RFC-0014 §3). Absent means not indexed (yet, or an HTML body), never empty.","maxItems":5000,"items":{"$ref":"#/$defs/BodyBlock"}},"BodyProfile":{"type":"integer","enum":[1],"description":"The markdown dialect a body follows (RFC-0014 §1): 1 = Cloudhal Markdown v1 (CommonMark + GFM + `asset:` + directives; raw HTML escaped). Set by the store on markdown bodies."},"Brand":{"type":"object","description":"The site's logo and social links (RFC-0025 §1), shared by the header, the footer and SEO.","additionalProperties":false,"properties":{"logo":{"$ref":"#/$defs/ImageRef"},"logoDark":{"$ref":"#/$defs/ImageRef"},"logoHeight":{"type":"string","enum":["s","m","l"]},"social":{"type":"array","maxItems":8,"items":{"$ref":"#/$defs/SocialLink"}}}},"Chrome":{"type":"object","description":"The site's header, footer and bars as data (RFC-0025): enums, menu keys and short texts; the theme renders them with landmarks, focus and contrast guaranteed.","additionalProperties":false,"properties":{"header":{"$ref":"#/$defs/ChromeHeader"},"footer":{"$ref":"#/$defs/ChromeFooter"},"announcement":{"$ref":"#/$defs/Announcement"},"consent":{"$ref":"#/$defs/Consent"}}},"ChromeFooter":{"type":"object","additionalProperties":false,"properties":{"layout":{"type":"string","enum":["simple","columns","cta-band","minimal"]},"surface":{"type":"string","enum":["plain","muted","inverse"]},"tagline":{"type":"string","minLength":1,"maxLength":200},"columns":{"type":"array","maxItems":5,"items":{"$ref":"#/$defs/FooterColumn"}},"ctaHeading":{"type":"string","minLength":1,"maxLength":120},"ctaLabel":{"type":"string","minLength":1,"maxLength":40},"ctaHref":{"type":"string","minLength":1,"maxLength":2048},"social":{"type":"boolean"},"copyright":{"type":"string","minLength":1,"maxLength":200,"description":"The legal line; {year} and {site} are the only placeholders."},"legal":{"$ref":"#/$defs/Slug","description":"A menu key (RFC-0003)."},"extras":{"type":"array","maxItems":3,"uniqueItems":true,"items":{"type":"string","enum":["locale","theme-toggle","motion-toggle"]}},"credit":{"type":"boolean"},"region":{"$ref":"#/$defs/Section","description":"RFC-0025 §2: one optional surface (kind `surface`) above the footer's columns, built and checked like any surface; the store refuses other section kinds."}}},"ChromeHeader":{"type":"object","additionalProperties":false,"properties":{"layout":{"type":"string","enum":["logo-left","logo-left-nav-center","centered","split","minimal"]},"behavior":{"type":"string","enum":["static","sticky","hide-on-scroll","fixed"]},"shrink":{"type":"boolean"},"overHero":{"type":"string","enum":["solid","transparent"]},"width":{"type":"string","enum":["content","wide","full"]},"surface":{"type":"string","enum":["plain","muted","raised","inverse","brand"]},"mobileStyle":{"type":"string","enum":["dropdown","drawer-start","drawer-end","fullscreen"]},"collapseAt":{"type":"string","enum":["sm","md","lg","always"]},"nav":{"$ref":"#/$defs/Slug","description":"A menu key (RFC-0003)."},"utility":{"$ref":"#/$defs/Slug","description":"A menu key (RFC-0003)."},"actions":{"type":"array","maxItems":3,"items":{"$ref":"#/$defs/HeaderAction"}}}},"Consent":{"type":"object","additionalProperties":false,"required":["mode"],"properties":{"mode":{"type":"string","enum":["none","notice","choices"]},"policy":{"$ref":"#/$defs/Path"}}},"Design":{"type":"object","description":"The site's design rules for people and agents (RFC-0021): Markdown that agents read with the theme's DESIGN.md before they build. Guidance only: it changes neither rendering nor permissions.","additionalProperties":false,"required":["markdown"],"properties":{"markdown":{"type":"string","minLength":1,"maxLength":24000}}},"Domain":{"type":"object","additionalProperties":false,"required":["hostname","primary"],"properties":{"hostname":{"type":"string","pattern":"^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$","maxLength":253},"primary":{"type":"boolean"}}},"FooterColumn":{"type":"object","additionalProperties":false,"required":["heading","menu"],"properties":{"heading":{"type":"string","minLength":1,"maxLength":60},"menu":{"$ref":"#/$defs/Slug","description":"A menu key (RFC-0003)."}}},"HeaderAction":{"type":"object","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["button","search","locale","theme-toggle","motion-toggle"]},"label":{"type":"string","minLength":1,"maxLength":40},"href":{"type":"string","minLength":1,"maxLength":2048},"variant":{"type":"string","enum":["primary","secondary"]}}},"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"ImageRef":{"type":"object","description":"Reference to an image. With `assetId` the image is an Asset node (RFC-0004): the store writes its media URL into `src`, and its size into `width` and `height`, as a fallback for readers that do not resolve assets. Without it, `src` is a relative path or an absolute URL, as before.","additionalProperties":false,"required":["src","alt"],"properties":{"assetId":{"$ref":"#/$defs/Id","description":"The Asset node shown (RFC-0004). Renderers that know assets resolve it and ignore `src`."},"src":{"type":"string","minLength":1,"maxLength":2048},"alt":{"type":"string","maxLength":500},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1}}},"Link":{"type":"object","additionalProperties":false,"required":["label","href"],"properties":{"label":{"type":"string","minLength":1,"maxLength":200},"href":{"type":"string","minLength":1,"maxLength":2048},"external":{"type":"boolean"}}},"Locale":{"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"},"Localization":{"type":"object","description":"How the site serves its locales (RFC-0033 §2). Absent fields mean: prefix-except-default routing, no fallback, no required review, the machine-translation notice until reviewed.","additionalProperties":false,"properties":{"routing":{"type":"string","description":"prefix-except-default: the default locale at the root, the others under //. prefix-all: every locale prefixed, / is a language chooser (x-default).","enum":["prefix-except-default","prefix-all"]},"domains":{"type":"object","description":"Optional hostname per locale; that locale is then served at the host's root.","propertyNames":{"$ref":"#/$defs/Locale"},"additionalProperties":{"type":"string","pattern":"^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$","maxLength":253}},"fallback":{"type":"string","description":"none: a missing translation is not found. default-with-notice: the default locale's page is served with a notice and noindex.","enum":["none","default-with-notice"]},"review":{"type":"object","description":"Collections whose machine translations need a person's review before they publish ('pages' stands for pages).","additionalProperties":false,"properties":{"required":{"type":"array","items":{"type":"string","minLength":1,"maxLength":100},"uniqueItems":true}}},"label":{"type":"string","description":"When readers see the machine-translation notice: unreviewed (until a person reviews) or always.","enum":["unreviewed","always"]}}},"Metadata":{"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}},"Path":{"type":"string","description":"Absolute URL path of a route the site owns: starts with '/', lower-case slug segments, no trailing slash except root.","pattern":"^/(?:[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*)?$","maxLength":1024},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Section":{"title":"Section","description":"A block inside a page, discriminated by `kind`. Variants are written inline (not via $ref) and `kind` is a single-value string enum so that TypeScript gets a literal union and Rust gets a serde internally-tagged enum.","oneOf":[{"type":"object","title":"HeroSection","additionalProperties":false,"required":["id","kind","heading"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["hero"]},"heading":{"type":"string","minLength":1,"maxLength":300},"subheading":{"type":"string","maxLength":1000},"image":{"$ref":"#/$defs/ImageRef"},"cta":{"$ref":"#/$defs/Link"}}},{"type":"object","title":"RichTextSection","additionalProperties":false,"required":["id","kind","format","body"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["richText"]},"format":{"type":"string","enum":["markdown","html"]},"body":{"type":"string","maxLength":200000},"profile":{"$ref":"#/$defs/BodyProfile"},"blocks":{"$ref":"#/$defs/BodyBlocks"}}},{"type":"object","title":"ImageSection","additionalProperties":false,"required":["id","kind","image"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["image"]},"image":{"$ref":"#/$defs/ImageRef"},"caption":{"type":"string","maxLength":1000}}},{"type":"object","title":"ContentListSection","description":"Renders the newest entries of a content collection (e.g. blog posts).","additionalProperties":false,"required":["id","kind","collection","limit","sort"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["contentList"]},"collection":{"$ref":"#/$defs/Slug"},"limit":{"type":"integer","minimum":1,"maximum":100},"sort":{"type":"string","enum":["newest","oldest","title"]},"filter":{"type":"string","description":"Filter expression over term assignments (RFC-0002), e.g. 'category:news+tag:ai' or 'category:news>*'.","minLength":1,"maxLength":512}}},{"type":"object","title":"ComponentSection","description":"A typed block from the block registry (RFC-0015): `type` names a registered block type (one global namespace of English names), `props` follow that type's props schema, which the store validates.","additionalProperties":false,"required":["id","kind","type","props"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["component"]},"type":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,40}$"},"props":{"type":"object","description":"Validated against the type's props schema by the store, not by this contract.","additionalProperties":true}}},{"type":"object","title":"SurfaceSection","description":"A generative UI surface (RFC-0020): a flat list of catalog components with a `root`, plus a data model their props may bind to. Written and streamed by agents as data, never code; the store checks the tree, the catalog and the limits.","additionalProperties":false,"required":["id","kind","catalog","components"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["surface"]},"catalog":{"type":"string","description":"The catalog the surface was written for: `@` (RFC-0018 manifest).","pattern":"^[a-z][a-z0-9-]{0,40}@[0-9]{1,4}$"},"components":{"type":"array","minItems":1,"maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponent"}},"data":{"type":"object","description":"The surface's data model: JSON its components bind to with `{path}` (at most 64 KB serialized; the store enforces the size).","additionalProperties":true}}}]},"Slug":{"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128},"SocialLink":{"type":"object","additionalProperties":false,"required":["network","href"],"properties":{"network":{"type":"string","enum":["x","bluesky","mastodon","github","linkedin","instagram","facebook","youtube","tiktok","threads","rss","email"]},"href":{"type":"string","minLength":1,"maxLength":2048}}},"SurfaceComponent":{"type":"object","description":"One element of a generative UI surface (RFC-0020 §1), in a flat list: `component` names a catalog entry (a PascalCase built-in primitive such as `Stack`, or a registry block type such as `callout`), `children` refer to other ids of the same surface, and `props` follow the entry's schema, which the store checks. Field names follow A2UI v0.9.","additionalProperties":false,"required":["id","component"],"properties":{"id":{"$ref":"#/$defs/SurfaceComponentId"},"component":{"type":"string","pattern":"^(?:[A-Z][A-Za-z0-9]{1,40}|[a-z][a-z0-9-]{1,40})$"},"children":{"type":"array","maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponentId"}},"props":{"type":"object","description":"Literal values, `{path}` bindings into the surface's data, or `{call}` named functions; validated by the store against the catalog, not by this contract.","additionalProperties":true},"visible":{"type":"object","description":"A visibility condition over the surface's data (`eq`, `ne`, `gt`, `lt`, `and`, `or`, `not`); validated by the store.","additionalProperties":true},"motion":{"type":"object","description":"Motion presets for this component (RFC-0024 §2): `reveal`, `hover`, `speed`, `delay`, `stagger`, `replay` and later `scroll`, `loop`; enums and clamped numbers only, validated by the store against the catalog. The renderer turns them into CSS; agents never write CSS.","additionalProperties":true}}},"SurfaceComponentId":{"type":"string","description":"A component's id, unique within its surface; `root` is the tree's root (RFC-0020 §1).","pattern":"^[a-z][a-z0-9-]{0,40}$"},"ThemeRef":{"type":"object","description":"Theme (a.k.a. Experience) applied by the renderer. Config is theme-defined and validated by the theme's own schema, not here.","additionalProperties":false,"required":["name"],"properties":{"name":{"$ref":"#/$defs/Slug"},"version":{"type":"string","maxLength":64},"config":{"type":"object","additionalProperties":true}}},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"},"TimeZone":{"type":"string","description":"IANA time zone name from the tz database (RFC-0008), e.g. Europe/Istanbul, America/Argentina/Buenos_Aires, Etc/GMT+3 or UTC. Wall-clock times a person enters are kept with their zone, so the instant is recomputed when governments change the rules. The pattern bounds the shape only; writers also check the name against the runtime's Intl zone list.","pattern":"^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$","maxLength":64}}}; +const schema41 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/site.schema.json","title":"Site","description":"Root node of a Site Graph: one deployable website with its locales, domains, theme and page index. Pages and content entries are separate nodes referenced by id so they can be versioned and locked independently.","type":"object","additionalProperties":false,"required":["schemaVersion","id","projectId","name","slug","defaultLocale","locales","domains","pageIds","theme","rev","createdAt","updatedAt"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/Id"},"projectId":{"$ref":"#/$defs/Id"},"name":{"type":"string","minLength":1,"maxLength":200},"slug":{"$ref":"#/$defs/Slug"},"defaultLocale":{"$ref":"#/$defs/Locale"},"locales":{"type":"array","items":{"$ref":"#/$defs/Locale"},"minItems":1,"uniqueItems":true},"domains":{"type":"array","description":"Hostnames serving this site. Exactly one should be primary: the address canonical links, feeds and previews use. Sites run on their own domains or their Cloudflare account's workers.dev address, never on a Cloudhal domain (ADR-0037).","items":{"$ref":"#/$defs/Domain"}},"pageIds":{"type":"array","items":{"$ref":"#/$defs/Id"},"uniqueItems":true},"homePageId":{"$ref":"#/$defs/Id"},"localization":{"$ref":"#/$defs/Localization"},"collections":{"type":"object","description":"RFC-0038: per-collection settings, keyed by collection (ContentEntry.collection). A collection without an entry here uses the defaults.","propertyNames":{"$ref":"#/$defs/Slug"},"additionalProperties":{"$ref":"#/$defs/CollectionSettings"}},"theme":{"$ref":"#/$defs/ThemeRef"},"design":{"$ref":"#/$defs/Design"},"brand":{"$ref":"#/$defs/Brand"},"chrome":{"$ref":"#/$defs/Chrome"},"timeZone":{"$ref":"#/$defs/TimeZone","description":"The site's time zone (RFC-0008): Studio's calendar, schedules entered as wall-clock times and recurring automations use it. Absent means UTC."},"publishedSnapshotId":{"$ref":"#/$defs/Id","description":"Snapshot currently serving production. Rollback = pointing this at an older snapshot."},"rev":{"$ref":"#/$defs/Revision"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"metadata":{"$ref":"#/$defs/Metadata"}},"$defs":{"Actor":{"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}},"ActorKind":{"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]},"Announcement":{"type":"object","additionalProperties":false,"required":["id","text"],"properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,40}$","description":"Change it when the text changes, so readers who dismissed the old one see the new one."},"text":{"type":"string","minLength":1,"maxLength":200},"label":{"type":"string","minLength":1,"maxLength":40},"href":{"type":"string","minLength":1,"maxLength":2048},"tone":{"type":"string","enum":["info","brand","warning"]},"dismissible":{"type":"boolean"}}},"BodyBlock":{"type":"object","description":"One top-level block of a markdown body in the store's index (RFC-0014 §3). Written by the store only: a client's values are replaced on every write. `by`, `via` and `at` say who last changed the block's content, through which channel and when; they are absent for content written before the index existed.","additionalProperties":false,"required":["id","hash"],"properties":{"id":{"type":"string","pattern":"^blk_[0-9A-Za-z]{8,24}$","description":"Stable across writes that keep the block's content or its place among unchanged blocks."},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"SHA-256 (hex) of the block's markdown without trailing whitespace."},"by":{"$ref":"#/$defs/Actor"},"via":{"type":"string","enum":["studio","mcp","api","import","ai-command","proposal"],"description":"The channel of that change."},"at":{"$ref":"#/$defs/Timestamp"}}},"BodyBlocks":{"type":"array","description":"A markdown body's block index, in body order (RFC-0014 §3). Absent means not indexed (yet, or an HTML body), never empty.","maxItems":5000,"items":{"$ref":"#/$defs/BodyBlock"}},"BodyProfile":{"type":"integer","enum":[1],"description":"The markdown dialect a body follows (RFC-0014 §1): 1 = Cloudhal Markdown v1 (CommonMark + GFM + `asset:` + directives; raw HTML escaped). Set by the store on markdown bodies."},"Brand":{"type":"object","description":"The site's logo and social links (RFC-0025 §1), shared by the header, the footer and SEO.","additionalProperties":false,"properties":{"logo":{"$ref":"#/$defs/ImageRef"},"logoDark":{"$ref":"#/$defs/ImageRef"},"logoHeight":{"type":"string","enum":["s","m","l"]},"social":{"type":"array","maxItems":8,"items":{"$ref":"#/$defs/SocialLink"}}}},"Chrome":{"type":"object","description":"The site's header, footer and bars as data (RFC-0025): enums, menu keys and short texts; the theme renders them with landmarks, focus and contrast guaranteed.","additionalProperties":false,"properties":{"header":{"$ref":"#/$defs/ChromeHeader"},"footer":{"$ref":"#/$defs/ChromeFooter"},"announcement":{"$ref":"#/$defs/Announcement"},"consent":{"$ref":"#/$defs/Consent"}}},"ChromeFooter":{"type":"object","additionalProperties":false,"properties":{"layout":{"type":"string","enum":["simple","columns","cta-band","minimal"]},"surface":{"type":"string","enum":["plain","muted","inverse"]},"tagline":{"type":"string","minLength":1,"maxLength":200},"columns":{"type":"array","maxItems":5,"items":{"$ref":"#/$defs/FooterColumn"}},"ctaHeading":{"type":"string","minLength":1,"maxLength":120},"ctaLabel":{"type":"string","minLength":1,"maxLength":40},"ctaHref":{"type":"string","minLength":1,"maxLength":2048},"social":{"type":"boolean"},"copyright":{"type":"string","minLength":1,"maxLength":200,"description":"The legal line; {year} and {site} are the only placeholders."},"legal":{"$ref":"#/$defs/Slug","description":"A menu key (RFC-0003)."},"extras":{"type":"array","maxItems":3,"uniqueItems":true,"items":{"type":"string","enum":["locale","theme-toggle","motion-toggle"]}},"credit":{"type":"boolean"},"region":{"$ref":"#/$defs/Section","description":"RFC-0025 §2: one optional surface (kind `surface`) above the footer's columns, built and checked like any surface; the store refuses other section kinds."}}},"ChromeHeader":{"type":"object","additionalProperties":false,"properties":{"layout":{"type":"string","enum":["logo-left","logo-left-nav-center","centered","split","minimal"]},"behavior":{"type":"string","enum":["static","sticky","hide-on-scroll","fixed"]},"shrink":{"type":"boolean"},"overHero":{"type":"string","enum":["solid","transparent"]},"width":{"type":"string","enum":["content","wide","full"]},"surface":{"type":"string","enum":["plain","muted","raised","inverse","brand"]},"mobileStyle":{"type":"string","enum":["dropdown","drawer-start","drawer-end","fullscreen"]},"collapseAt":{"type":"string","enum":["sm","md","lg","always"]},"nav":{"$ref":"#/$defs/Slug","description":"A menu key (RFC-0003)."},"utility":{"$ref":"#/$defs/Slug","description":"A menu key (RFC-0003)."},"actions":{"type":"array","maxItems":3,"items":{"$ref":"#/$defs/HeaderAction"}}}},"CollectionSettings":{"type":"object","additionalProperties":false,"properties":{"permalink":{"type":"string","description":"RFC-0038: where the collection's entries live, as a template with a closed token set. Version 1: one or more fixed lower-case path segments followed by /{slug}, e.g. /blog/{slug}. Absent means //{slug}.","pattern":"^(/[a-z0-9]+(-[a-z0-9]+)*)+/\\{slug\\}$"}}},"Consent":{"type":"object","additionalProperties":false,"required":["mode"],"properties":{"mode":{"type":"string","enum":["none","notice","choices"]},"policy":{"$ref":"#/$defs/Path"}}},"Design":{"type":"object","description":"The site's design rules for people and agents (RFC-0021): Markdown that agents read with the theme's DESIGN.md before they build. Guidance only: it changes neither rendering nor permissions.","additionalProperties":false,"required":["markdown"],"properties":{"markdown":{"type":"string","minLength":1,"maxLength":24000}}},"Domain":{"type":"object","additionalProperties":false,"required":["hostname","primary"],"properties":{"hostname":{"type":"string","pattern":"^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$","maxLength":253},"primary":{"type":"boolean"}}},"FooterColumn":{"type":"object","additionalProperties":false,"required":["heading","menu"],"properties":{"heading":{"type":"string","minLength":1,"maxLength":60},"menu":{"$ref":"#/$defs/Slug","description":"A menu key (RFC-0003)."}}},"HeaderAction":{"type":"object","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["button","search","locale","theme-toggle","motion-toggle"]},"label":{"type":"string","minLength":1,"maxLength":40},"href":{"type":"string","minLength":1,"maxLength":2048},"variant":{"type":"string","enum":["primary","secondary"]}}},"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"ImageRef":{"type":"object","description":"Reference to an image. With `assetId` the image is an Asset node (RFC-0004): the store writes its media URL into `src`, and its size into `width` and `height`, as a fallback for readers that do not resolve assets. Without it, `src` is a relative path or an absolute URL, as before.","additionalProperties":false,"required":["src","alt"],"properties":{"assetId":{"$ref":"#/$defs/Id","description":"The Asset node shown (RFC-0004). Renderers that know assets resolve it and ignore `src`."},"src":{"type":"string","minLength":1,"maxLength":2048},"alt":{"type":"string","maxLength":500},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1}}},"Link":{"type":"object","additionalProperties":false,"required":["label","href"],"properties":{"label":{"type":"string","minLength":1,"maxLength":200},"href":{"type":"string","minLength":1,"maxLength":2048},"external":{"type":"boolean"}}},"Locale":{"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"},"Localization":{"type":"object","description":"How the site serves its locales (RFC-0033 §2). Absent fields mean: prefix-except-default routing, no fallback, no required review, the machine-translation notice until reviewed.","additionalProperties":false,"properties":{"routing":{"type":"string","description":"prefix-except-default: the default locale at the root, the others under //. prefix-all: every locale prefixed, / is a language chooser (x-default).","enum":["prefix-except-default","prefix-all"]},"domains":{"type":"object","description":"Optional hostname per locale; that locale is then served at the host's root.","propertyNames":{"$ref":"#/$defs/Locale"},"additionalProperties":{"type":"string","pattern":"^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$","maxLength":253}},"fallback":{"type":"string","description":"none: a missing translation is not found. default-with-notice: the default locale's page is served with a notice and noindex.","enum":["none","default-with-notice"]},"review":{"type":"object","description":"Collections whose machine translations need a person's review before they publish ('pages' stands for pages).","additionalProperties":false,"properties":{"required":{"type":"array","items":{"type":"string","minLength":1,"maxLength":100},"uniqueItems":true}}},"label":{"type":"string","description":"When readers see the machine-translation notice: unreviewed (until a person reviews) or always.","enum":["unreviewed","always"]}}},"Metadata":{"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}},"Path":{"type":"string","description":"Absolute URL path of a route the site owns: starts with '/', lower-case slug segments, no trailing slash except root.","pattern":"^/(?:[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*)?$","maxLength":1024},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Section":{"title":"Section","description":"A block inside a page, discriminated by `kind`. Variants are written inline (not via $ref) and `kind` is a single-value string enum so that TypeScript gets a literal union and Rust gets a serde internally-tagged enum.","oneOf":[{"type":"object","title":"HeroSection","additionalProperties":false,"required":["id","kind","heading"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["hero"]},"heading":{"type":"string","minLength":1,"maxLength":300},"subheading":{"type":"string","maxLength":1000},"image":{"$ref":"#/$defs/ImageRef"},"cta":{"$ref":"#/$defs/Link"}}},{"type":"object","title":"RichTextSection","additionalProperties":false,"required":["id","kind","format","body"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["richText"]},"format":{"type":"string","enum":["markdown","html"]},"body":{"type":"string","maxLength":200000},"profile":{"$ref":"#/$defs/BodyProfile"},"blocks":{"$ref":"#/$defs/BodyBlocks"}}},{"type":"object","title":"ImageSection","additionalProperties":false,"required":["id","kind","image"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["image"]},"image":{"$ref":"#/$defs/ImageRef"},"caption":{"type":"string","maxLength":1000}}},{"type":"object","title":"ContentListSection","description":"Renders the newest entries of a content collection (e.g. blog posts).","additionalProperties":false,"required":["id","kind","collection","limit","sort"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["contentList"]},"collection":{"$ref":"#/$defs/Slug"},"limit":{"type":"integer","minimum":1,"maximum":100},"sort":{"type":"string","enum":["newest","oldest","title"]},"filter":{"type":"string","description":"Filter expression over term assignments (RFC-0002), e.g. 'category:news+tag:ai' or 'category:news>*'.","minLength":1,"maxLength":512}}},{"type":"object","title":"ComponentSection","description":"A typed block from the block registry (RFC-0015): `type` names a registered block type (one global namespace of English names), `props` follow that type's props schema, which the store validates.","additionalProperties":false,"required":["id","kind","type","props"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["component"]},"type":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,40}$"},"props":{"type":"object","description":"Validated against the type's props schema by the store, not by this contract.","additionalProperties":true}}},{"type":"object","title":"SurfaceSection","description":"A generative UI surface (RFC-0020): a flat list of catalog components with a `root`, plus a data model their props may bind to. Written and streamed by agents as data, never code; the store checks the tree, the catalog and the limits.","additionalProperties":false,"required":["id","kind","catalog","components"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["surface"]},"catalog":{"type":"string","description":"The catalog the surface was written for: `@` (RFC-0018 manifest).","pattern":"^[a-z][a-z0-9-]{0,40}@[0-9]{1,4}$"},"components":{"type":"array","minItems":1,"maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponent"}},"data":{"type":"object","description":"The surface's data model: JSON its components bind to with `{path}` (at most 64 KB serialized; the store enforces the size).","additionalProperties":true}}}]},"Slug":{"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128},"SocialLink":{"type":"object","additionalProperties":false,"required":["network","href"],"properties":{"network":{"type":"string","enum":["x","bluesky","mastodon","github","linkedin","instagram","facebook","youtube","tiktok","threads","rss","email"]},"href":{"type":"string","minLength":1,"maxLength":2048}}},"SurfaceComponent":{"type":"object","description":"One element of a generative UI surface (RFC-0020 §1), in a flat list: `component` names a catalog entry (a PascalCase built-in primitive such as `Stack`, or a registry block type such as `callout`), `children` refer to other ids of the same surface, and `props` follow the entry's schema, which the store checks. Field names follow A2UI v0.9.","additionalProperties":false,"required":["id","component"],"properties":{"id":{"$ref":"#/$defs/SurfaceComponentId"},"component":{"type":"string","pattern":"^(?:[A-Z][A-Za-z0-9]{1,40}|[a-z][a-z0-9-]{1,40})$"},"children":{"type":"array","maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponentId"}},"props":{"type":"object","description":"Literal values, `{path}` bindings into the surface's data, or `{call}` named functions; validated by the store against the catalog, not by this contract.","additionalProperties":true},"visible":{"type":"object","description":"A visibility condition over the surface's data (`eq`, `ne`, `gt`, `lt`, `and`, `or`, `not`); validated by the store.","additionalProperties":true},"motion":{"type":"object","description":"Motion presets for this component (RFC-0024 §2): `reveal`, `hover`, `speed`, `delay`, `stagger`, `replay` and later `scroll`, `loop`; enums and clamped numbers only, validated by the store against the catalog. The renderer turns them into CSS; agents never write CSS.","additionalProperties":true}}},"SurfaceComponentId":{"type":"string","description":"A component's id, unique within its surface; `root` is the tree's root (RFC-0020 §1).","pattern":"^[a-z][a-z0-9-]{0,40}$"},"ThemeRef":{"type":"object","description":"Theme (a.k.a. Experience) applied by the renderer. Config is theme-defined and validated by the theme's own schema, not here.","additionalProperties":false,"required":["name"],"properties":{"name":{"$ref":"#/$defs/Slug"},"version":{"type":"string","maxLength":64},"config":{"type":"object","additionalProperties":true}}},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"},"TimeZone":{"type":"string","description":"IANA time zone name from the tz database (RFC-0008), e.g. Europe/Istanbul, America/Argentina/Buenos_Aires, Etc/GMT+3 or UTC. Wall-clock times a person enters are kept with their zone, so the instant is recomputed when governments change the rules. The pattern bounds the shape only; writers also check the name against the runtime's Intl zone list.","pattern":"^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$","maxLength":64}}}; const schema42 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; const schema43 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; const schema45 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128}; const schema46 = {"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"}; const schema48 = {"type":"object","additionalProperties":false,"required":["hostname","primary"],"properties":{"hostname":{"type":"string","pattern":"^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$","maxLength":253},"primary":{"type":"boolean"}}}; -const schema55 = {"type":"object","description":"The site's design rules for people and agents (RFC-0021): Markdown that agents read with the theme's DESIGN.md before they build. Guidance only: it changes neither rendering nor permissions.","additionalProperties":false,"required":["markdown"],"properties":{"markdown":{"type":"string","minLength":1,"maxLength":24000}}}; -const schema91 = {"type":"string","description":"IANA time zone name from the tz database (RFC-0008), e.g. Europe/Istanbul, America/Argentina/Buenos_Aires, Etc/GMT+3 or UTC. Wall-clock times a person enters are kept with their zone, so the instant is recomputed when governments change the rules. The pattern bounds the shape only; writers also check the name against the runtime's Intl zone list.","pattern":"^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$","maxLength":64}; -const schema93 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; -const schema79 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; -const schema96 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}}; +const schema54 = {"type":"object","additionalProperties":false,"properties":{"permalink":{"type":"string","description":"RFC-0038: where the collection's entries live, as a template with a closed token set. Version 1: one or more fixed lower-case path segments followed by /{slug}, e.g. /blog/{slug}. Absent means //{slug}.","pattern":"^(/[a-z0-9]+(-[a-z0-9]+)*)+/\\{slug\\}$"}}}; +const schema57 = {"type":"object","description":"The site's design rules for people and agents (RFC-0021): Markdown that agents read with the theme's DESIGN.md before they build. Guidance only: it changes neither rendering nor permissions.","additionalProperties":false,"required":["markdown"],"properties":{"markdown":{"type":"string","minLength":1,"maxLength":24000}}}; +const schema93 = {"type":"string","description":"IANA time zone name from the tz database (RFC-0008), e.g. Europe/Istanbul, America/Argentina/Buenos_Aires, Etc/GMT+3 or UTC. Wall-clock times a person enters are kept with their zone, so the instant is recomputed when governments change the rules. The pattern bounds the shape only; writers also check the name against the runtime's Intl zone list.","pattern":"^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$","maxLength":64}; +const schema95 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; +const schema81 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; +const schema98 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}}; const pattern11 = new RegExp("^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$", "u"); const pattern13 = new RegExp("^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$", "u"); -const pattern41 = new RegExp("^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$", "u"); +const pattern19 = new RegExp("^(/[a-z0-9]+(-[a-z0-9]+)*)+/\\{slug\\}$", "u"); +const pattern43 = new RegExp("^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$", "u"); const schema51 = {"type":"object","description":"How the site serves its locales (RFC-0033 §2). Absent fields mean: prefix-except-default routing, no fallback, no required review, the machine-translation notice until reviewed.","additionalProperties":false,"properties":{"routing":{"type":"string","description":"prefix-except-default: the default locale at the root, the others under //. prefix-all: every locale prefixed, / is a language chooser (x-default).","enum":["prefix-except-default","prefix-all"]},"domains":{"type":"object","description":"Optional hostname per locale; that locale is then served at the host's root.","propertyNames":{"$ref":"#/$defs/Locale"},"additionalProperties":{"type":"string","pattern":"^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$","maxLength":253}},"fallback":{"type":"string","description":"none: a missing translation is not found. default-with-notice: the default locale's page is served with a notice and noindex.","enum":["none","default-with-notice"]},"review":{"type":"object","description":"Collections whose machine translations need a person's review before they publish ('pages' stands for pages).","additionalProperties":false,"properties":{"required":{"type":"array","items":{"type":"string","minLength":1,"maxLength":100},"uniqueItems":true}}},"label":{"type":"string","description":"When readers see the machine-translation notice: unreviewed (until a person reviews) or always.","enum":["unreviewed","always"]}}}; function validate22(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ @@ -842,7 +844,7 @@ return errors === 0; } validate22.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; -const schema53 = {"type":"object","description":"Theme (a.k.a. Experience) applied by the renderer. Config is theme-defined and validated by the theme's own schema, not here.","additionalProperties":false,"required":["name"],"properties":{"name":{"$ref":"#/$defs/Slug"},"version":{"type":"string","maxLength":64},"config":{"type":"object","additionalProperties":true}}}; +const schema55 = {"type":"object","description":"Theme (a.k.a. Experience) applied by the renderer. Config is theme-defined and validated by the theme's own schema, not here.","additionalProperties":false,"required":["name"],"properties":{"name":{"$ref":"#/$defs/Slug"},"version":{"type":"string","maxLength":64},"config":{"type":"object","additionalProperties":true}}}; function validate24(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -978,9 +980,9 @@ return errors === 0; } validate24.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; -const schema56 = {"type":"object","description":"The site's logo and social links (RFC-0025 §1), shared by the header, the footer and SEO.","additionalProperties":false,"properties":{"logo":{"$ref":"#/$defs/ImageRef"},"logoDark":{"$ref":"#/$defs/ImageRef"},"logoHeight":{"type":"string","enum":["s","m","l"]},"social":{"type":"array","maxItems":8,"items":{"$ref":"#/$defs/SocialLink"}}}}; -const schema59 = {"type":"object","additionalProperties":false,"required":["network","href"],"properties":{"network":{"type":"string","enum":["x","bluesky","mastodon","github","linkedin","instagram","facebook","youtube","tiktok","threads","rss","email"]},"href":{"type":"string","minLength":1,"maxLength":2048}}}; -const schema57 = {"type":"object","description":"Reference to an image. With `assetId` the image is an Asset node (RFC-0004): the store writes its media URL into `src`, and its size into `width` and `height`, as a fallback for readers that do not resolve assets. Without it, `src` is a relative path or an absolute URL, as before.","additionalProperties":false,"required":["src","alt"],"properties":{"assetId":{"$ref":"#/$defs/Id","description":"The Asset node shown (RFC-0004). Renderers that know assets resolve it and ignore `src`."},"src":{"type":"string","minLength":1,"maxLength":2048},"alt":{"type":"string","maxLength":500},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1}}}; +const schema58 = {"type":"object","description":"The site's logo and social links (RFC-0025 §1), shared by the header, the footer and SEO.","additionalProperties":false,"properties":{"logo":{"$ref":"#/$defs/ImageRef"},"logoDark":{"$ref":"#/$defs/ImageRef"},"logoHeight":{"type":"string","enum":["s","m","l"]},"social":{"type":"array","maxItems":8,"items":{"$ref":"#/$defs/SocialLink"}}}}; +const schema61 = {"type":"object","additionalProperties":false,"required":["network","href"],"properties":{"network":{"type":"string","enum":["x","bluesky","mastodon","github","linkedin","instagram","facebook","youtube","tiktok","threads","rss","email"]},"href":{"type":"string","minLength":1,"maxLength":2048}}}; +const schema59 = {"type":"object","description":"Reference to an image. With `assetId` the image is an Asset node (RFC-0004): the store writes its media URL into `src`, and its size into `width` and `height`, as a fallback for readers that do not resolve assets. Without it, `src` is a relative path or an absolute URL, as before.","additionalProperties":false,"required":["src","alt"],"properties":{"assetId":{"$ref":"#/$defs/Id","description":"The Asset node shown (RFC-0004). Renderers that know assets resolve it and ignore `src`."},"src":{"type":"string","minLength":1,"maxLength":2048},"alt":{"type":"string","maxLength":500},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1}}}; function validate27(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -1245,7 +1247,7 @@ vErrors.push(err1); errors++; } if(!(((data2 === "s") || (data2 === "m")) || (data2 === "l"))){ -const err2 = {instancePath:instancePath+"/logoHeight",schemaPath:"#/properties/logoHeight/enum",keyword:"enum",params:{allowedValues: schema56.properties.logoHeight.enum},message:"must be equal to one of the allowed values"}; +const err2 = {instancePath:instancePath+"/logoHeight",schemaPath:"#/properties/logoHeight/enum",keyword:"enum",params:{allowedValues: schema58.properties.logoHeight.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err2]; } @@ -1317,7 +1319,7 @@ vErrors.push(err7); errors++; } if(!((((((((((((data5 === "x") || (data5 === "bluesky")) || (data5 === "mastodon")) || (data5 === "github")) || (data5 === "linkedin")) || (data5 === "instagram")) || (data5 === "facebook")) || (data5 === "youtube")) || (data5 === "tiktok")) || (data5 === "threads")) || (data5 === "rss")) || (data5 === "email"))){ -const err8 = {instancePath:instancePath+"/social/" + i0+"/network",schemaPath:"#/$defs/SocialLink/properties/network/enum",keyword:"enum",params:{allowedValues: schema59.properties.network.enum},message:"must be equal to one of the allowed values"}; +const err8 = {instancePath:instancePath+"/social/" + i0+"/network",schemaPath:"#/$defs/SocialLink/properties/network/enum",keyword:"enum",params:{allowedValues: schema61.properties.network.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err8]; } @@ -1402,10 +1404,10 @@ return errors === 0; } validate26.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; -const schema60 = {"type":"object","description":"The site's header, footer and bars as data (RFC-0025): enums, menu keys and short texts; the theme renders them with landmarks, focus and contrast guaranteed.","additionalProperties":false,"properties":{"header":{"$ref":"#/$defs/ChromeHeader"},"footer":{"$ref":"#/$defs/ChromeFooter"},"announcement":{"$ref":"#/$defs/Announcement"},"consent":{"$ref":"#/$defs/Consent"}}}; -const schema88 = {"type":"object","additionalProperties":false,"required":["id","text"],"properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,40}$","description":"Change it when the text changes, so readers who dismissed the old one see the new one."},"text":{"type":"string","minLength":1,"maxLength":200},"label":{"type":"string","minLength":1,"maxLength":40},"href":{"type":"string","minLength":1,"maxLength":2048},"tone":{"type":"string","enum":["info","brand","warning"]},"dismissible":{"type":"boolean"}}}; -const schema61 = {"type":"object","additionalProperties":false,"properties":{"layout":{"type":"string","enum":["logo-left","logo-left-nav-center","centered","split","minimal"]},"behavior":{"type":"string","enum":["static","sticky","hide-on-scroll","fixed"]},"shrink":{"type":"boolean"},"overHero":{"type":"string","enum":["solid","transparent"]},"width":{"type":"string","enum":["content","wide","full"]},"surface":{"type":"string","enum":["plain","muted","raised","inverse","brand"]},"mobileStyle":{"type":"string","enum":["dropdown","drawer-start","drawer-end","fullscreen"]},"collapseAt":{"type":"string","enum":["sm","md","lg","always"]},"nav":{"$ref":"#/$defs/Slug","description":"A menu key (RFC-0003)."},"utility":{"$ref":"#/$defs/Slug","description":"A menu key (RFC-0003)."},"actions":{"type":"array","maxItems":3,"items":{"$ref":"#/$defs/HeaderAction"}}}}; -const schema64 = {"type":"object","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["button","search","locale","theme-toggle","motion-toggle"]},"label":{"type":"string","minLength":1,"maxLength":40},"href":{"type":"string","minLength":1,"maxLength":2048},"variant":{"type":"string","enum":["primary","secondary"]}}}; +const schema62 = {"type":"object","description":"The site's header, footer and bars as data (RFC-0025): enums, menu keys and short texts; the theme renders them with landmarks, focus and contrast guaranteed.","additionalProperties":false,"properties":{"header":{"$ref":"#/$defs/ChromeHeader"},"footer":{"$ref":"#/$defs/ChromeFooter"},"announcement":{"$ref":"#/$defs/Announcement"},"consent":{"$ref":"#/$defs/Consent"}}}; +const schema90 = {"type":"object","additionalProperties":false,"required":["id","text"],"properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,40}$","description":"Change it when the text changes, so readers who dismissed the old one see the new one."},"text":{"type":"string","minLength":1,"maxLength":200},"label":{"type":"string","minLength":1,"maxLength":40},"href":{"type":"string","minLength":1,"maxLength":2048},"tone":{"type":"string","enum":["info","brand","warning"]},"dismissible":{"type":"boolean"}}}; +const schema63 = {"type":"object","additionalProperties":false,"properties":{"layout":{"type":"string","enum":["logo-left","logo-left-nav-center","centered","split","minimal"]},"behavior":{"type":"string","enum":["static","sticky","hide-on-scroll","fixed"]},"shrink":{"type":"boolean"},"overHero":{"type":"string","enum":["solid","transparent"]},"width":{"type":"string","enum":["content","wide","full"]},"surface":{"type":"string","enum":["plain","muted","raised","inverse","brand"]},"mobileStyle":{"type":"string","enum":["dropdown","drawer-start","drawer-end","fullscreen"]},"collapseAt":{"type":"string","enum":["sm","md","lg","always"]},"nav":{"$ref":"#/$defs/Slug","description":"A menu key (RFC-0003)."},"utility":{"$ref":"#/$defs/Slug","description":"A menu key (RFC-0003)."},"actions":{"type":"array","maxItems":3,"items":{"$ref":"#/$defs/HeaderAction"}}}}; +const schema66 = {"type":"object","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["button","search","locale","theme-toggle","motion-toggle"]},"label":{"type":"string","minLength":1,"maxLength":40},"href":{"type":"string","minLength":1,"maxLength":2048},"variant":{"type":"string","enum":["primary","secondary"]}}}; function validate32(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -1419,7 +1421,7 @@ evaluated0.items = undefined; } if(data && typeof data == "object" && !Array.isArray(data)){ for(const key0 in data){ -if(!(func1.call(schema61.properties, key0))){ +if(!(func1.call(schema63.properties, key0))){ const err0 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}; if(vErrors === null){ vErrors = [err0]; @@ -1443,7 +1445,7 @@ vErrors.push(err1); errors++; } if(!(((((data0 === "logo-left") || (data0 === "logo-left-nav-center")) || (data0 === "centered")) || (data0 === "split")) || (data0 === "minimal"))){ -const err2 = {instancePath:instancePath+"/layout",schemaPath:"#/properties/layout/enum",keyword:"enum",params:{allowedValues: schema61.properties.layout.enum},message:"must be equal to one of the allowed values"}; +const err2 = {instancePath:instancePath+"/layout",schemaPath:"#/properties/layout/enum",keyword:"enum",params:{allowedValues: schema63.properties.layout.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err2]; } @@ -1466,7 +1468,7 @@ vErrors.push(err3); errors++; } if(!((((data1 === "static") || (data1 === "sticky")) || (data1 === "hide-on-scroll")) || (data1 === "fixed"))){ -const err4 = {instancePath:instancePath+"/behavior",schemaPath:"#/properties/behavior/enum",keyword:"enum",params:{allowedValues: schema61.properties.behavior.enum},message:"must be equal to one of the allowed values"}; +const err4 = {instancePath:instancePath+"/behavior",schemaPath:"#/properties/behavior/enum",keyword:"enum",params:{allowedValues: schema63.properties.behavior.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err4]; } @@ -1501,7 +1503,7 @@ vErrors.push(err6); errors++; } if(!((data3 === "solid") || (data3 === "transparent"))){ -const err7 = {instancePath:instancePath+"/overHero",schemaPath:"#/properties/overHero/enum",keyword:"enum",params:{allowedValues: schema61.properties.overHero.enum},message:"must be equal to one of the allowed values"}; +const err7 = {instancePath:instancePath+"/overHero",schemaPath:"#/properties/overHero/enum",keyword:"enum",params:{allowedValues: schema63.properties.overHero.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err7]; } @@ -1524,7 +1526,7 @@ vErrors.push(err8); errors++; } if(!(((data4 === "content") || (data4 === "wide")) || (data4 === "full"))){ -const err9 = {instancePath:instancePath+"/width",schemaPath:"#/properties/width/enum",keyword:"enum",params:{allowedValues: schema61.properties.width.enum},message:"must be equal to one of the allowed values"}; +const err9 = {instancePath:instancePath+"/width",schemaPath:"#/properties/width/enum",keyword:"enum",params:{allowedValues: schema63.properties.width.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err9]; } @@ -1547,7 +1549,7 @@ vErrors.push(err10); errors++; } if(!(((((data5 === "plain") || (data5 === "muted")) || (data5 === "raised")) || (data5 === "inverse")) || (data5 === "brand"))){ -const err11 = {instancePath:instancePath+"/surface",schemaPath:"#/properties/surface/enum",keyword:"enum",params:{allowedValues: schema61.properties.surface.enum},message:"must be equal to one of the allowed values"}; +const err11 = {instancePath:instancePath+"/surface",schemaPath:"#/properties/surface/enum",keyword:"enum",params:{allowedValues: schema63.properties.surface.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err11]; } @@ -1570,7 +1572,7 @@ vErrors.push(err12); errors++; } if(!((((data6 === "dropdown") || (data6 === "drawer-start")) || (data6 === "drawer-end")) || (data6 === "fullscreen"))){ -const err13 = {instancePath:instancePath+"/mobileStyle",schemaPath:"#/properties/mobileStyle/enum",keyword:"enum",params:{allowedValues: schema61.properties.mobileStyle.enum},message:"must be equal to one of the allowed values"}; +const err13 = {instancePath:instancePath+"/mobileStyle",schemaPath:"#/properties/mobileStyle/enum",keyword:"enum",params:{allowedValues: schema63.properties.mobileStyle.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err13]; } @@ -1593,7 +1595,7 @@ vErrors.push(err14); errors++; } if(!((((data7 === "sm") || (data7 === "md")) || (data7 === "lg")) || (data7 === "always"))){ -const err15 = {instancePath:instancePath+"/collapseAt",schemaPath:"#/properties/collapseAt/enum",keyword:"enum",params:{allowedValues: schema61.properties.collapseAt.enum},message:"must be equal to one of the allowed values"}; +const err15 = {instancePath:instancePath+"/collapseAt",schemaPath:"#/properties/collapseAt/enum",keyword:"enum",params:{allowedValues: schema63.properties.collapseAt.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err15]; } @@ -1745,7 +1747,7 @@ vErrors.push(err27); errors++; } if(!(((((data12 === "button") || (data12 === "search")) || (data12 === "locale")) || (data12 === "theme-toggle")) || (data12 === "motion-toggle"))){ -const err28 = {instancePath:instancePath+"/actions/" + i0+"/kind",schemaPath:"#/$defs/HeaderAction/properties/kind/enum",keyword:"enum",params:{allowedValues: schema64.properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err28 = {instancePath:instancePath+"/actions/" + i0+"/kind",schemaPath:"#/$defs/HeaderAction/properties/kind/enum",keyword:"enum",params:{allowedValues: schema66.properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err28]; } @@ -1838,7 +1840,7 @@ vErrors.push(err35); errors++; } if(!((data15 === "primary") || (data15 === "secondary"))){ -const err36 = {instancePath:instancePath+"/actions/" + i0+"/variant",schemaPath:"#/$defs/HeaderAction/properties/variant/enum",keyword:"enum",params:{allowedValues: schema64.properties.variant.enum},message:"must be equal to one of the allowed values"}; +const err36 = {instancePath:instancePath+"/actions/" + i0+"/variant",schemaPath:"#/$defs/HeaderAction/properties/variant/enum",keyword:"enum",params:{allowedValues: schema66.properties.variant.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err36]; } @@ -1888,8 +1890,8 @@ return errors === 0; } validate32.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; -const schema65 = {"type":"object","additionalProperties":false,"properties":{"layout":{"type":"string","enum":["simple","columns","cta-band","minimal"]},"surface":{"type":"string","enum":["plain","muted","inverse"]},"tagline":{"type":"string","minLength":1,"maxLength":200},"columns":{"type":"array","maxItems":5,"items":{"$ref":"#/$defs/FooterColumn"}},"ctaHeading":{"type":"string","minLength":1,"maxLength":120},"ctaLabel":{"type":"string","minLength":1,"maxLength":40},"ctaHref":{"type":"string","minLength":1,"maxLength":2048},"social":{"type":"boolean"},"copyright":{"type":"string","minLength":1,"maxLength":200,"description":"The legal line; {year} and {site} are the only placeholders."},"legal":{"$ref":"#/$defs/Slug","description":"A menu key (RFC-0003)."},"extras":{"type":"array","maxItems":3,"uniqueItems":true,"items":{"type":"string","enum":["locale","theme-toggle","motion-toggle"]}},"credit":{"type":"boolean"},"region":{"$ref":"#/$defs/Section","description":"RFC-0025 §2: one optional surface (kind `surface`) above the footer's columns, built and checked like any surface; the store refuses other section kinds."}}}; -const schema66 = {"type":"object","additionalProperties":false,"required":["heading","menu"],"properties":{"heading":{"type":"string","minLength":1,"maxLength":60},"menu":{"$ref":"#/$defs/Slug","description":"A menu key (RFC-0003)."}}}; +const schema67 = {"type":"object","additionalProperties":false,"properties":{"layout":{"type":"string","enum":["simple","columns","cta-band","minimal"]},"surface":{"type":"string","enum":["plain","muted","inverse"]},"tagline":{"type":"string","minLength":1,"maxLength":200},"columns":{"type":"array","maxItems":5,"items":{"$ref":"#/$defs/FooterColumn"}},"ctaHeading":{"type":"string","minLength":1,"maxLength":120},"ctaLabel":{"type":"string","minLength":1,"maxLength":40},"ctaHref":{"type":"string","minLength":1,"maxLength":2048},"social":{"type":"boolean"},"copyright":{"type":"string","minLength":1,"maxLength":200,"description":"The legal line; {year} and {site} are the only placeholders."},"legal":{"$ref":"#/$defs/Slug","description":"A menu key (RFC-0003)."},"extras":{"type":"array","maxItems":3,"uniqueItems":true,"items":{"type":"string","enum":["locale","theme-toggle","motion-toggle"]}},"credit":{"type":"boolean"},"region":{"$ref":"#/$defs/Section","description":"RFC-0025 §2: one optional surface (kind `surface`) above the footer's columns, built and checked like any surface; the store refuses other section kinds."}}}; +const schema68 = {"type":"object","additionalProperties":false,"required":["heading","menu"],"properties":{"heading":{"type":"string","minLength":1,"maxLength":60},"menu":{"$ref":"#/$defs/Slug","description":"A menu key (RFC-0003)."}}}; function validate35(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -2030,17 +2032,17 @@ return errors === 0; } validate35.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; -const schema69 = {"title":"Section","description":"A block inside a page, discriminated by `kind`. Variants are written inline (not via $ref) and `kind` is a single-value string enum so that TypeScript gets a literal union and Rust gets a serde internally-tagged enum.","oneOf":[{"type":"object","title":"HeroSection","additionalProperties":false,"required":["id","kind","heading"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["hero"]},"heading":{"type":"string","minLength":1,"maxLength":300},"subheading":{"type":"string","maxLength":1000},"image":{"$ref":"#/$defs/ImageRef"},"cta":{"$ref":"#/$defs/Link"}}},{"type":"object","title":"RichTextSection","additionalProperties":false,"required":["id","kind","format","body"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["richText"]},"format":{"type":"string","enum":["markdown","html"]},"body":{"type":"string","maxLength":200000},"profile":{"$ref":"#/$defs/BodyProfile"},"blocks":{"$ref":"#/$defs/BodyBlocks"}}},{"type":"object","title":"ImageSection","additionalProperties":false,"required":["id","kind","image"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["image"]},"image":{"$ref":"#/$defs/ImageRef"},"caption":{"type":"string","maxLength":1000}}},{"type":"object","title":"ContentListSection","description":"Renders the newest entries of a content collection (e.g. blog posts).","additionalProperties":false,"required":["id","kind","collection","limit","sort"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["contentList"]},"collection":{"$ref":"#/$defs/Slug"},"limit":{"type":"integer","minimum":1,"maximum":100},"sort":{"type":"string","enum":["newest","oldest","title"]},"filter":{"type":"string","description":"Filter expression over term assignments (RFC-0002), e.g. 'category:news+tag:ai' or 'category:news>*'.","minLength":1,"maxLength":512}}},{"type":"object","title":"ComponentSection","description":"A typed block from the block registry (RFC-0015): `type` names a registered block type (one global namespace of English names), `props` follow that type's props schema, which the store validates.","additionalProperties":false,"required":["id","kind","type","props"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["component"]},"type":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,40}$"},"props":{"type":"object","description":"Validated against the type's props schema by the store, not by this contract.","additionalProperties":true}}},{"type":"object","title":"SurfaceSection","description":"A generative UI surface (RFC-0020): a flat list of catalog components with a `root`, plus a data model their props may bind to. Written and streamed by agents as data, never code; the store checks the tree, the catalog and the limits.","additionalProperties":false,"required":["id","kind","catalog","components"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["surface"]},"catalog":{"type":"string","description":"The catalog the surface was written for: `@` (RFC-0018 manifest).","pattern":"^[a-z][a-z0-9-]{0,40}@[0-9]{1,4}$"},"components":{"type":"array","minItems":1,"maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponent"}},"data":{"type":"object","description":"The surface's data model: JSON its components bind to with `{path}` (at most 64 KB serialized; the store enforces the size).","additionalProperties":true}}}]}; -const schema71 = {"type":"object","additionalProperties":false,"required":["label","href"],"properties":{"label":{"type":"string","minLength":1,"maxLength":200},"href":{"type":"string","minLength":1,"maxLength":2048},"external":{"type":"boolean"}}}; -const schema73 = {"type":"integer","enum":[1],"description":"The markdown dialect a body follows (RFC-0014 §1): 1 = Cloudhal Markdown v1 (CommonMark + GFM + `asset:` + directives; raw HTML escaped). Set by the store on markdown bodies."}; -const pattern33 = new RegExp("^[a-z][a-z0-9-]{1,40}$", "u"); -const pattern35 = new RegExp("^[a-z][a-z0-9-]{0,40}@[0-9]{1,4}$", "u"); -const schema74 = {"type":"array","description":"A markdown body's block index, in body order (RFC-0014 §3). Absent means not indexed (yet, or an HTML body), never empty.","maxItems":5000,"items":{"$ref":"#/$defs/BodyBlock"}}; -const schema75 = {"type":"object","description":"One top-level block of a markdown body in the store's index (RFC-0014 §3). Written by the store only: a client's values are replaced on every write. `by`, `via` and `at` say who last changed the block's content, through which channel and when; they are absent for content written before the index existed.","additionalProperties":false,"required":["id","hash"],"properties":{"id":{"type":"string","pattern":"^blk_[0-9A-Za-z]{8,24}$","description":"Stable across writes that keep the block's content or its place among unchanged blocks."},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"SHA-256 (hex) of the block's markdown without trailing whitespace."},"by":{"$ref":"#/$defs/Actor"},"via":{"type":"string","enum":["studio","mcp","api","import","ai-command","proposal"],"description":"The channel of that change."},"at":{"$ref":"#/$defs/Timestamp"}}}; -const pattern26 = new RegExp("^blk_[0-9A-Za-z]{8,24}$", "u"); -const pattern27 = new RegExp("^[0-9a-f]{64}$", "u"); -const schema76 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}}; -const schema77 = {"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]}; +const schema71 = {"title":"Section","description":"A block inside a page, discriminated by `kind`. Variants are written inline (not via $ref) and `kind` is a single-value string enum so that TypeScript gets a literal union and Rust gets a serde internally-tagged enum.","oneOf":[{"type":"object","title":"HeroSection","additionalProperties":false,"required":["id","kind","heading"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["hero"]},"heading":{"type":"string","minLength":1,"maxLength":300},"subheading":{"type":"string","maxLength":1000},"image":{"$ref":"#/$defs/ImageRef"},"cta":{"$ref":"#/$defs/Link"}}},{"type":"object","title":"RichTextSection","additionalProperties":false,"required":["id","kind","format","body"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["richText"]},"format":{"type":"string","enum":["markdown","html"]},"body":{"type":"string","maxLength":200000},"profile":{"$ref":"#/$defs/BodyProfile"},"blocks":{"$ref":"#/$defs/BodyBlocks"}}},{"type":"object","title":"ImageSection","additionalProperties":false,"required":["id","kind","image"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["image"]},"image":{"$ref":"#/$defs/ImageRef"},"caption":{"type":"string","maxLength":1000}}},{"type":"object","title":"ContentListSection","description":"Renders the newest entries of a content collection (e.g. blog posts).","additionalProperties":false,"required":["id","kind","collection","limit","sort"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["contentList"]},"collection":{"$ref":"#/$defs/Slug"},"limit":{"type":"integer","minimum":1,"maximum":100},"sort":{"type":"string","enum":["newest","oldest","title"]},"filter":{"type":"string","description":"Filter expression over term assignments (RFC-0002), e.g. 'category:news+tag:ai' or 'category:news>*'.","minLength":1,"maxLength":512}}},{"type":"object","title":"ComponentSection","description":"A typed block from the block registry (RFC-0015): `type` names a registered block type (one global namespace of English names), `props` follow that type's props schema, which the store validates.","additionalProperties":false,"required":["id","kind","type","props"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["component"]},"type":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,40}$"},"props":{"type":"object","description":"Validated against the type's props schema by the store, not by this contract.","additionalProperties":true}}},{"type":"object","title":"SurfaceSection","description":"A generative UI surface (RFC-0020): a flat list of catalog components with a `root`, plus a data model their props may bind to. Written and streamed by agents as data, never code; the store checks the tree, the catalog and the limits.","additionalProperties":false,"required":["id","kind","catalog","components"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["surface"]},"catalog":{"type":"string","description":"The catalog the surface was written for: `@` (RFC-0018 manifest).","pattern":"^[a-z][a-z0-9-]{0,40}@[0-9]{1,4}$"},"components":{"type":"array","minItems":1,"maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponent"}},"data":{"type":"object","description":"The surface's data model: JSON its components bind to with `{path}` (at most 64 KB serialized; the store enforces the size).","additionalProperties":true}}}]}; +const schema73 = {"type":"object","additionalProperties":false,"required":["label","href"],"properties":{"label":{"type":"string","minLength":1,"maxLength":200},"href":{"type":"string","minLength":1,"maxLength":2048},"external":{"type":"boolean"}}}; +const schema75 = {"type":"integer","enum":[1],"description":"The markdown dialect a body follows (RFC-0014 §1): 1 = Cloudhal Markdown v1 (CommonMark + GFM + `asset:` + directives; raw HTML escaped). Set by the store on markdown bodies."}; +const pattern35 = new RegExp("^[a-z][a-z0-9-]{1,40}$", "u"); +const pattern37 = new RegExp("^[a-z][a-z0-9-]{0,40}@[0-9]{1,4}$", "u"); +const schema76 = {"type":"array","description":"A markdown body's block index, in body order (RFC-0014 §3). Absent means not indexed (yet, or an HTML body), never empty.","maxItems":5000,"items":{"$ref":"#/$defs/BodyBlock"}}; +const schema77 = {"type":"object","description":"One top-level block of a markdown body in the store's index (RFC-0014 §3). Written by the store only: a client's values are replaced on every write. `by`, `via` and `at` say who last changed the block's content, through which channel and when; they are absent for content written before the index existed.","additionalProperties":false,"required":["id","hash"],"properties":{"id":{"type":"string","pattern":"^blk_[0-9A-Za-z]{8,24}$","description":"Stable across writes that keep the block's content or its place among unchanged blocks."},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"SHA-256 (hex) of the block's markdown without trailing whitespace."},"by":{"$ref":"#/$defs/Actor"},"via":{"type":"string","enum":["studio","mcp","api","import","ai-command","proposal"],"description":"The channel of that change."},"at":{"$ref":"#/$defs/Timestamp"}}}; +const pattern28 = new RegExp("^blk_[0-9A-Za-z]{8,24}$", "u"); +const pattern29 = new RegExp("^[0-9a-f]{64}$", "u"); +const schema78 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}}; +const schema79 = {"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]}; function validate41(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -2098,7 +2100,7 @@ vErrors.push(err3); errors++; } if(!(((data0 === "human") || (data0 === "agent")) || (data0 === "system"))){ -const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/$defs/ActorKind/enum",keyword:"enum",params:{allowedValues: schema77.enum},message:"must be equal to one of the allowed values"}; +const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/$defs/ActorKind/enum",keyword:"enum",params:{allowedValues: schema79.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err4]; } @@ -2241,7 +2243,7 @@ errors++; if(data.id !== undefined){ let data0 = data.id; if(typeof data0 === "string"){ -if(!pattern26.test(data0)){ +if(!pattern28.test(data0)){ const err3 = {instancePath:instancePath+"/id",schemaPath:"#/properties/id/pattern",keyword:"pattern",params:{pattern: "^blk_[0-9A-Za-z]{8,24}$"},message:"must match pattern \""+"^blk_[0-9A-Za-z]{8,24}$"+"\""}; if(vErrors === null){ vErrors = [err3]; @@ -2266,7 +2268,7 @@ errors++; if(data.hash !== undefined){ let data1 = data.hash; if(typeof data1 === "string"){ -if(!pattern27.test(data1)){ +if(!pattern29.test(data1)){ const err5 = {instancePath:instancePath+"/hash",schemaPath:"#/properties/hash/pattern",keyword:"pattern",params:{pattern: "^[0-9a-f]{64}$"},message:"must match pattern \""+"^[0-9a-f]{64}$"+"\""}; if(vErrors === null){ vErrors = [err5]; @@ -2307,7 +2309,7 @@ vErrors.push(err7); errors++; } if(!((((((data3 === "studio") || (data3 === "mcp")) || (data3 === "api")) || (data3 === "import")) || (data3 === "ai-command")) || (data3 === "proposal"))){ -const err8 = {instancePath:instancePath+"/via",schemaPath:"#/properties/via/enum",keyword:"enum",params:{allowedValues: schema75.properties.via.enum},message:"must be equal to one of the allowed values"}; +const err8 = {instancePath:instancePath+"/via",schemaPath:"#/properties/via/enum",keyword:"enum",params:{allowedValues: schema77.properties.via.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err8]; } @@ -2403,10 +2405,10 @@ return errors === 0; } validate39.evaluated = {"items":true,"dynamicProps":false,"dynamicItems":false}; -const schema85 = {"type":"object","description":"One element of a generative UI surface (RFC-0020 §1), in a flat list: `component` names a catalog entry (a PascalCase built-in primitive such as `Stack`, or a registry block type such as `callout`), `children` refer to other ids of the same surface, and `props` follow the entry's schema, which the store checks. Field names follow A2UI v0.9.","additionalProperties":false,"required":["id","component"],"properties":{"id":{"$ref":"#/$defs/SurfaceComponentId"},"component":{"type":"string","pattern":"^(?:[A-Z][A-Za-z0-9]{1,40}|[a-z][a-z0-9-]{1,40})$"},"children":{"type":"array","maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponentId"}},"props":{"type":"object","description":"Literal values, `{path}` bindings into the surface's data, or `{call}` named functions; validated by the store against the catalog, not by this contract.","additionalProperties":true},"visible":{"type":"object","description":"A visibility condition over the surface's data (`eq`, `ne`, `gt`, `lt`, `and`, `or`, `not`); validated by the store.","additionalProperties":true},"motion":{"type":"object","description":"Motion presets for this component (RFC-0024 §2): `reveal`, `hover`, `speed`, `delay`, `stagger`, `replay` and later `scroll`, `loop`; enums and clamped numbers only, validated by the store against the catalog. The renderer turns them into CSS; agents never write CSS.","additionalProperties":true}}}; -const schema86 = {"type":"string","description":"A component's id, unique within its surface; `root` is the tree's root (RFC-0020 §1).","pattern":"^[a-z][a-z0-9-]{0,40}$"}; -const pattern36 = new RegExp("^[a-z][a-z0-9-]{0,40}$", "u"); -const pattern37 = new RegExp("^(?:[A-Z][A-Za-z0-9]{1,40}|[a-z][a-z0-9-]{1,40})$", "u"); +const schema87 = {"type":"object","description":"One element of a generative UI surface (RFC-0020 §1), in a flat list: `component` names a catalog entry (a PascalCase built-in primitive such as `Stack`, or a registry block type such as `callout`), `children` refer to other ids of the same surface, and `props` follow the entry's schema, which the store checks. Field names follow A2UI v0.9.","additionalProperties":false,"required":["id","component"],"properties":{"id":{"$ref":"#/$defs/SurfaceComponentId"},"component":{"type":"string","pattern":"^(?:[A-Z][A-Za-z0-9]{1,40}|[a-z][a-z0-9-]{1,40})$"},"children":{"type":"array","maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponentId"}},"props":{"type":"object","description":"Literal values, `{path}` bindings into the surface's data, or `{call}` named functions; validated by the store against the catalog, not by this contract.","additionalProperties":true},"visible":{"type":"object","description":"A visibility condition over the surface's data (`eq`, `ne`, `gt`, `lt`, `and`, `or`, `not`); validated by the store.","additionalProperties":true},"motion":{"type":"object","description":"Motion presets for this component (RFC-0024 §2): `reveal`, `hover`, `speed`, `delay`, `stagger`, `replay` and later `scroll`, `loop`; enums and clamped numbers only, validated by the store against the catalog. The renderer turns them into CSS; agents never write CSS.","additionalProperties":true}}}; +const schema88 = {"type":"string","description":"A component's id, unique within its surface; `root` is the tree's root (RFC-0020 §1).","pattern":"^[a-z][a-z0-9-]{0,40}$"}; +const pattern38 = new RegExp("^[a-z][a-z0-9-]{0,40}$", "u"); +const pattern39 = new RegExp("^(?:[A-Z][A-Za-z0-9]{1,40}|[a-z][a-z0-9-]{1,40})$", "u"); function validate46(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -2454,7 +2456,7 @@ errors++; if(data.id !== undefined){ let data0 = data.id; if(typeof data0 === "string"){ -if(!pattern36.test(data0)){ +if(!pattern38.test(data0)){ const err3 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/SurfaceComponentId/pattern",keyword:"pattern",params:{pattern: "^[a-z][a-z0-9-]{0,40}$"},message:"must match pattern \""+"^[a-z][a-z0-9-]{0,40}$"+"\""}; if(vErrors === null){ vErrors = [err3]; @@ -2479,7 +2481,7 @@ errors++; if(data.component !== undefined){ let data1 = data.component; if(typeof data1 === "string"){ -if(!pattern37.test(data1)){ +if(!pattern39.test(data1)){ const err5 = {instancePath:instancePath+"/component",schemaPath:"#/properties/component/pattern",keyword:"pattern",params:{pattern: "^(?:[A-Z][A-Za-z0-9]{1,40}|[a-z][a-z0-9-]{1,40})$"},message:"must match pattern \""+"^(?:[A-Z][A-Za-z0-9]{1,40}|[a-z][a-z0-9-]{1,40})$"+"\""}; if(vErrors === null){ vErrors = [err5]; @@ -2518,7 +2520,7 @@ const len0 = data2.length; for(let i0=0; i0 128){ +const err57 = {instancePath:instancePath+"/collections",schemaPath:"#/$defs/Slug/maxLength",keyword:"maxLength",params:{limit: 128},message:"must NOT have more than 128 characters",propertyName:key2}; if(vErrors === null){ vErrors = [err57]; } @@ -5609,9 +5608,8 @@ vErrors.push(err57); } errors++; } -for(const key2 in data17){ -if(!(key2 === "markdown")){ -const err58 = {instancePath:instancePath+"/design",schemaPath:"#/$defs/Design/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"}; +if(func2(key2) < 1){ +const err58 = {instancePath:instancePath+"/collections",schemaPath:"#/$defs/Slug/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters",propertyName:key2}; if(vErrors === null){ vErrors = [err58]; } @@ -5620,12 +5618,8 @@ vErrors.push(err58); } errors++; } -} -if(data17.markdown !== undefined){ -let data18 = data17.markdown; -if(typeof data18 === "string"){ -if(func2(data18) > 24000){ -const err59 = {instancePath:instancePath+"/design/markdown",schemaPath:"#/$defs/Design/properties/markdown/maxLength",keyword:"maxLength",params:{limit: 24000},message:"must NOT have more than 24000 characters"}; +if(!pattern5.test(key2)){ +const err59 = {instancePath:instancePath+"/collections",schemaPath:"#/$defs/Slug/pattern",keyword:"pattern",params:{pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$"},message:"must match pattern \""+"^[a-z0-9]+(?:-[a-z0-9]+)*$"+"\"",propertyName:key2}; if(vErrors === null){ vErrors = [err59]; } @@ -5634,8 +5628,9 @@ vErrors.push(err59); } errors++; } -if(func2(data18) < 1){ -const err60 = {instancePath:instancePath+"/design/markdown",schemaPath:"#/$defs/Design/properties/markdown/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"}; +} +else { +const err60 = {instancePath:instancePath+"/collections",schemaPath:"#/$defs/Slug/type",keyword:"type",params:{type: "string"},message:"must be string",propertyName:key2}; if(vErrors === null){ vErrors = [err60]; } @@ -5644,9 +5639,9 @@ vErrors.push(err60); } errors++; } -} -else { -const err61 = {instancePath:instancePath+"/design/markdown",schemaPath:"#/$defs/Design/properties/markdown/type",keyword:"type",params:{type: "string"},message:"must be string"}; +var valid19 = _errs45 === errors; +if(!valid19){ +const err61 = {instancePath:instancePath+"/collections",schemaPath:"#/properties/collections/propertyNames",keyword:"propertyNames",params:{propertyName: key2},message:"property name must be valid"}; if(vErrors === null){ vErrors = [err61]; } @@ -5656,9 +5651,12 @@ vErrors.push(err61); errors++; } } -} -else { -const err62 = {instancePath:instancePath+"/design",schemaPath:"#/$defs/Design/type",keyword:"type",params:{type: "object"},message:"must be object"}; +for(const key3 in data16){ +let data17 = data16[key3]; +if(data17 && typeof data17 == "object" && !Array.isArray(data17)){ +for(const key4 in data17){ +if(!(key4 === "permalink")){ +const err62 = {instancePath:instancePath+"/collections/" + key3.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/$defs/CollectionSettings/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"}; if(vErrors === null){ vErrors = [err62]; } @@ -5668,23 +5666,11 @@ vErrors.push(err62); errors++; } } -if(data.brand !== undefined){ -if(!(validate26(data.brand, {instancePath:instancePath+"/brand",parentData:data,parentDataProperty:"brand",rootData,dynamicAnchors}))){ -vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors); -errors = vErrors.length; -} -} -if(data.chrome !== undefined){ -if(!(validate31(data.chrome, {instancePath:instancePath+"/chrome",parentData:data,parentDataProperty:"chrome",rootData,dynamicAnchors}))){ -vErrors = vErrors === null ? validate31.errors : vErrors.concat(validate31.errors); -errors = vErrors.length; -} -} -if(data.timeZone !== undefined){ -let data21 = data.timeZone; -if(typeof data21 === "string"){ -if(func2(data21) > 64){ -const err63 = {instancePath:instancePath+"/timeZone",schemaPath:"#/$defs/TimeZone/maxLength",keyword:"maxLength",params:{limit: 64},message:"must NOT have more than 64 characters"}; +if(data17.permalink !== undefined){ +let data18 = data17.permalink; +if(typeof data18 === "string"){ +if(!pattern19.test(data18)){ +const err63 = {instancePath:instancePath+"/collections/" + key3.replace(/~/g, "~0").replace(/\//g, "~1")+"/permalink",schemaPath:"#/$defs/CollectionSettings/properties/permalink/pattern",keyword:"pattern",params:{pattern: "^(/[a-z0-9]+(-[a-z0-9]+)*)+/\\{slug\\}$"},message:"must match pattern \""+"^(/[a-z0-9]+(-[a-z0-9]+)*)+/\\{slug\\}$"+"\""}; if(vErrors === null){ vErrors = [err63]; } @@ -5693,8 +5679,9 @@ vErrors.push(err63); } errors++; } -if(!pattern41.test(data21)){ -const err64 = {instancePath:instancePath+"/timeZone",schemaPath:"#/$defs/TimeZone/pattern",keyword:"pattern",params:{pattern: "^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$"},message:"must match pattern \""+"^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$"+"\""}; +} +else { +const err64 = {instancePath:instancePath+"/collections/" + key3.replace(/~/g, "~0").replace(/\//g, "~1")+"/permalink",schemaPath:"#/$defs/CollectionSettings/properties/permalink/type",keyword:"type",params:{type: "string"},message:"must be string"}; if(vErrors === null){ vErrors = [err64]; } @@ -5704,8 +5691,9 @@ vErrors.push(err64); errors++; } } +} else { -const err65 = {instancePath:instancePath+"/timeZone",schemaPath:"#/$defs/TimeZone/type",keyword:"type",params:{type: "string"},message:"must be string"}; +const err65 = {instancePath:instancePath+"/collections/" + key3.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/$defs/CollectionSettings/type",keyword:"type",params:{type: "object"},message:"must be object"}; if(vErrors === null){ vErrors = [err65]; } @@ -5715,11 +5703,9 @@ vErrors.push(err65); errors++; } } -if(data.publishedSnapshotId !== undefined){ -let data22 = data.publishedSnapshotId; -if(typeof data22 === "string"){ -if(func2(data22) > 48){ -const err66 = {instancePath:instancePath+"/publishedSnapshotId",schemaPath:"#/$defs/Id/maxLength",keyword:"maxLength",params:{limit: 48},message:"must NOT have more than 48 characters"}; +} +else { +const err66 = {instancePath:instancePath+"/collections",schemaPath:"#/properties/collections/type",keyword:"type",params:{type: "object"},message:"must be object"}; if(vErrors === null){ vErrors = [err66]; } @@ -5728,8 +5714,18 @@ vErrors.push(err66); } errors++; } -if(func2(data22) < 8){ -const err67 = {instancePath:instancePath+"/publishedSnapshotId",schemaPath:"#/$defs/Id/minLength",keyword:"minLength",params:{limit: 8},message:"must NOT have fewer than 8 characters"}; +} +if(data.theme !== undefined){ +if(!(validate24(data.theme, {instancePath:instancePath+"/theme",parentData:data,parentDataProperty:"theme",rootData,dynamicAnchors}))){ +vErrors = vErrors === null ? validate24.errors : vErrors.concat(validate24.errors); +errors = vErrors.length; +} +} +if(data.design !== undefined){ +let data20 = data.design; +if(data20 && typeof data20 == "object" && !Array.isArray(data20)){ +if(data20.markdown === undefined){ +const err67 = {instancePath:instancePath+"/design",schemaPath:"#/$defs/Design/required",keyword:"required",params:{missingProperty: "markdown"},message:"must have required property '"+"markdown"+"'"}; if(vErrors === null){ vErrors = [err67]; } @@ -5738,8 +5734,9 @@ vErrors.push(err67); } errors++; } -if(!pattern4.test(data22)){ -const err68 = {instancePath:instancePath+"/publishedSnapshotId",schemaPath:"#/$defs/Id/pattern",keyword:"pattern",params:{pattern: "^[a-z]+_[A-Za-z0-9]{6,32}$"},message:"must match pattern \""+"^[a-z]+_[A-Za-z0-9]{6,32}$"+"\""}; +for(const key5 in data20){ +if(!(key5 === "markdown")){ +const err68 = {instancePath:instancePath+"/design",schemaPath:"#/$defs/Design/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key5},message:"must NOT have additional properties"}; if(vErrors === null){ vErrors = [err68]; } @@ -5749,8 +5746,11 @@ vErrors.push(err68); errors++; } } -else { -const err69 = {instancePath:instancePath+"/publishedSnapshotId",schemaPath:"#/$defs/Id/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(data20.markdown !== undefined){ +let data21 = data20.markdown; +if(typeof data21 === "string"){ +if(func2(data21) > 24000){ +const err69 = {instancePath:instancePath+"/design/markdown",schemaPath:"#/$defs/Design/properties/markdown/maxLength",keyword:"maxLength",params:{limit: 24000},message:"must NOT have more than 24000 characters"}; if(vErrors === null){ vErrors = [err69]; } @@ -5759,11 +5759,8 @@ vErrors.push(err69); } errors++; } -} -if(data.rev !== undefined){ -let data23 = data.rev; -if(!(((typeof data23 == "number") && (!(data23 % 1) && !isNaN(data23))) && (isFinite(data23)))){ -const err70 = {instancePath:instancePath+"/rev",schemaPath:"#/$defs/Revision/type",keyword:"type",params:{type: "integer"},message:"must be integer"}; +if(func2(data21) < 1){ +const err70 = {instancePath:instancePath+"/design/markdown",schemaPath:"#/$defs/Design/properties/markdown/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"}; if(vErrors === null){ vErrors = [err70]; } @@ -5772,9 +5769,9 @@ vErrors.push(err70); } errors++; } -if((typeof data23 == "number") && (isFinite(data23))){ -if(data23 < 0 || isNaN(data23)){ -const err71 = {instancePath:instancePath+"/rev",schemaPath:"#/$defs/Revision/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"}; +} +else { +const err71 = {instancePath:instancePath+"/design/markdown",schemaPath:"#/$defs/Design/properties/markdown/type",keyword:"type",params:{type: "string"},message:"must be string"}; if(vErrors === null){ vErrors = [err71]; } @@ -5785,11 +5782,8 @@ errors++; } } } -if(data.createdAt !== undefined){ -let data24 = data.createdAt; -if(typeof data24 === "string"){ -if(!(formats0.validate(data24))){ -const err72 = {instancePath:instancePath+"/createdAt",schemaPath:"#/$defs/Timestamp/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""}; +else { +const err72 = {instancePath:instancePath+"/design",schemaPath:"#/$defs/Design/type",keyword:"type",params:{type: "object"},message:"must be object"}; if(vErrors === null){ vErrors = [err72]; } @@ -5799,8 +5793,23 @@ vErrors.push(err72); errors++; } } -else { -const err73 = {instancePath:instancePath+"/createdAt",schemaPath:"#/$defs/Timestamp/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(data.brand !== undefined){ +if(!(validate26(data.brand, {instancePath:instancePath+"/brand",parentData:data,parentDataProperty:"brand",rootData,dynamicAnchors}))){ +vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors); +errors = vErrors.length; +} +} +if(data.chrome !== undefined){ +if(!(validate31(data.chrome, {instancePath:instancePath+"/chrome",parentData:data,parentDataProperty:"chrome",rootData,dynamicAnchors}))){ +vErrors = vErrors === null ? validate31.errors : vErrors.concat(validate31.errors); +errors = vErrors.length; +} +} +if(data.timeZone !== undefined){ +let data24 = data.timeZone; +if(typeof data24 === "string"){ +if(func2(data24) > 64){ +const err73 = {instancePath:instancePath+"/timeZone",schemaPath:"#/$defs/TimeZone/maxLength",keyword:"maxLength",params:{limit: 64},message:"must NOT have more than 64 characters"}; if(vErrors === null){ vErrors = [err73]; } @@ -5809,12 +5818,8 @@ vErrors.push(err73); } errors++; } -} -if(data.updatedAt !== undefined){ -let data25 = data.updatedAt; -if(typeof data25 === "string"){ -if(!(formats0.validate(data25))){ -const err74 = {instancePath:instancePath+"/updatedAt",schemaPath:"#/$defs/Timestamp/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""}; +if(!pattern43.test(data24)){ +const err74 = {instancePath:instancePath+"/timeZone",schemaPath:"#/$defs/TimeZone/pattern",keyword:"pattern",params:{pattern: "^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$"},message:"must match pattern \""+"^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$"+"\""}; if(vErrors === null){ vErrors = [err74]; } @@ -5825,7 +5830,7 @@ errors++; } } else { -const err75 = {instancePath:instancePath+"/updatedAt",schemaPath:"#/$defs/Timestamp/type",keyword:"type",params:{type: "string"},message:"must be string"}; +const err75 = {instancePath:instancePath+"/timeZone",schemaPath:"#/$defs/TimeZone/type",keyword:"type",params:{type: "string"},message:"must be string"}; if(vErrors === null){ vErrors = [err75]; } @@ -5835,12 +5840,11 @@ vErrors.push(err75); errors++; } } -if(data.metadata !== undefined){ -let data26 = data.metadata; -if(data26 && typeof data26 == "object" && !Array.isArray(data26)){ -for(const key3 in data26){ -if(typeof data26[key3] !== "string"){ -const err76 = {instancePath:instancePath+"/metadata/" + key3.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/$defs/Metadata/additionalProperties/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(data.publishedSnapshotId !== undefined){ +let data25 = data.publishedSnapshotId; +if(typeof data25 === "string"){ +if(func2(data25) > 48){ +const err76 = {instancePath:instancePath+"/publishedSnapshotId",schemaPath:"#/$defs/Id/maxLength",keyword:"maxLength",params:{limit: 48},message:"must NOT have more than 48 characters"}; if(vErrors === null){ vErrors = [err76]; } @@ -5849,27 +5853,148 @@ vErrors.push(err76); } errors++; } +if(func2(data25) < 8){ +const err77 = {instancePath:instancePath+"/publishedSnapshotId",schemaPath:"#/$defs/Id/minLength",keyword:"minLength",params:{limit: 8},message:"must NOT have fewer than 8 characters"}; +if(vErrors === null){ +vErrors = [err77]; +} +else { +vErrors.push(err77); +} +errors++; +} +if(!pattern4.test(data25)){ +const err78 = {instancePath:instancePath+"/publishedSnapshotId",schemaPath:"#/$defs/Id/pattern",keyword:"pattern",params:{pattern: "^[a-z]+_[A-Za-z0-9]{6,32}$"},message:"must match pattern \""+"^[a-z]+_[A-Za-z0-9]{6,32}$"+"\""}; +if(vErrors === null){ +vErrors = [err78]; +} +else { +vErrors.push(err78); +} +errors++; +} } +else { +const err79 = {instancePath:instancePath+"/publishedSnapshotId",schemaPath:"#/$defs/Id/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err79]; } else { -const err77 = {instancePath:instancePath+"/metadata",schemaPath:"#/$defs/Metadata/type",keyword:"type",params:{type: "object"},message:"must be object"}; +vErrors.push(err79); +} +errors++; +} +} +if(data.rev !== undefined){ +let data26 = data.rev; +if(!(((typeof data26 == "number") && (!(data26 % 1) && !isNaN(data26))) && (isFinite(data26)))){ +const err80 = {instancePath:instancePath+"/rev",schemaPath:"#/$defs/Revision/type",keyword:"type",params:{type: "integer"},message:"must be integer"}; if(vErrors === null){ -vErrors = [err77]; +vErrors = [err80]; } else { -vErrors.push(err77); +vErrors.push(err80); +} +errors++; +} +if((typeof data26 == "number") && (isFinite(data26))){ +if(data26 < 0 || isNaN(data26)){ +const err81 = {instancePath:instancePath+"/rev",schemaPath:"#/$defs/Revision/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"}; +if(vErrors === null){ +vErrors = [err81]; +} +else { +vErrors.push(err81); } errors++; } } } +if(data.createdAt !== undefined){ +let data27 = data.createdAt; +if(typeof data27 === "string"){ +if(!(formats0.validate(data27))){ +const err82 = {instancePath:instancePath+"/createdAt",schemaPath:"#/$defs/Timestamp/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""}; +if(vErrors === null){ +vErrors = [err82]; +} else { -const err78 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}; +vErrors.push(err82); +} +errors++; +} +} +else { +const err83 = {instancePath:instancePath+"/createdAt",schemaPath:"#/$defs/Timestamp/type",keyword:"type",params:{type: "string"},message:"must be string"}; if(vErrors === null){ -vErrors = [err78]; +vErrors = [err83]; } else { -vErrors.push(err78); +vErrors.push(err83); +} +errors++; +} +} +if(data.updatedAt !== undefined){ +let data28 = data.updatedAt; +if(typeof data28 === "string"){ +if(!(formats0.validate(data28))){ +const err84 = {instancePath:instancePath+"/updatedAt",schemaPath:"#/$defs/Timestamp/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""}; +if(vErrors === null){ +vErrors = [err84]; +} +else { +vErrors.push(err84); +} +errors++; +} +} +else { +const err85 = {instancePath:instancePath+"/updatedAt",schemaPath:"#/$defs/Timestamp/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err85]; +} +else { +vErrors.push(err85); +} +errors++; +} +} +if(data.metadata !== undefined){ +let data29 = data.metadata; +if(data29 && typeof data29 == "object" && !Array.isArray(data29)){ +for(const key6 in data29){ +if(typeof data29[key6] !== "string"){ +const err86 = {instancePath:instancePath+"/metadata/" + key6.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/$defs/Metadata/additionalProperties/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err86]; +} +else { +vErrors.push(err86); +} +errors++; +} +} +} +else { +const err87 = {instancePath:instancePath+"/metadata",schemaPath:"#/$defs/Metadata/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err87]; +} +else { +vErrors.push(err87); +} +errors++; +} +} +} +else { +const err88 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err88]; +} +else { +vErrors.push(err88); } errors++; } @@ -5879,20 +6004,20 @@ return errors === 0; validate21.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; export const validatePage = validate53; -const schema97 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/page.schema.json","title":"Page","description":"A routable page of a site: a path, editorial status and an ordered list of sections. Sections are embedded (not separate nodes) so a page is the unit of locking, snapshotting and diffing.","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","path","title","locale","status","sections","rev","createdAt","updatedAt"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/Id"},"siteId":{"$ref":"#/$defs/Id"},"path":{"type":"string","description":"Absolute URL path, starting with '/', no trailing slash except root.","pattern":"^/(?:[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*)?$","maxLength":1024},"title":{"type":"string","minLength":1,"maxLength":300},"locale":{"$ref":"#/$defs/Locale"},"status":{"$ref":"#/$defs/PublishStatus"},"sections":{"type":"array","items":{"$ref":"#/$defs/Section"}},"seo":{"$ref":"#/$defs/Seo"},"availability":{"$ref":"#/$defs/Availability"},"chrome":{"type":"object","description":"This page's own header and footer (RFC-0025 §1): a landing page without the menu, a checkout without the footer.","additionalProperties":false,"properties":{"header":{"type":"string","enum":["default","transparent","hidden"]},"footer":{"type":"string","enum":["default","minimal","hidden"]}}},"motion":{"$ref":"#/$defs/MotionLevel","description":"Overrides the site's motion level for this page, up or down (RFC-0024 §1); absent follows the site."},"rev":{"$ref":"#/$defs/Revision"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"translation":{"$ref":"#/$defs/Translation"},"metadata":{"$ref":"#/$defs/Metadata"}},"$defs":{"Actor":{"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}},"ActorKind":{"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]},"Availability":{"type":"object","description":"Time gates of a page or content entry (RFC-0008): the published site shows the node only from `from` (inclusive: an embargo) until `until` (exclusive: an expiry), checked at render time, so the gates hold even when no scheduler runs. Before `from` and from `until` on, the node is absent from every public route, list, feed, sitemap and search output; previews still show it. At least one gate; writers check that `from` is before `until`.","additionalProperties":false,"minProperties":1,"properties":{"from":{"$ref":"#/$defs/Timestamp"},"until":{"$ref":"#/$defs/Timestamp"}}},"BodyBlock":{"type":"object","description":"One top-level block of a markdown body in the store's index (RFC-0014 §3). Written by the store only: a client's values are replaced on every write. `by`, `via` and `at` say who last changed the block's content, through which channel and when; they are absent for content written before the index existed.","additionalProperties":false,"required":["id","hash"],"properties":{"id":{"type":"string","pattern":"^blk_[0-9A-Za-z]{8,24}$","description":"Stable across writes that keep the block's content or its place among unchanged blocks."},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"SHA-256 (hex) of the block's markdown without trailing whitespace."},"by":{"$ref":"#/$defs/Actor"},"via":{"type":"string","enum":["studio","mcp","api","import","ai-command","proposal"],"description":"The channel of that change."},"at":{"$ref":"#/$defs/Timestamp"}}},"BodyBlocks":{"type":"array","description":"A markdown body's block index, in body order (RFC-0014 §3). Absent means not indexed (yet, or an HTML body), never empty.","maxItems":5000,"items":{"$ref":"#/$defs/BodyBlock"}},"BodyProfile":{"type":"integer","enum":[1],"description":"The markdown dialect a body follows (RFC-0014 §1): 1 = Cloudhal Markdown v1 (CommonMark + GFM + `asset:` + directives; raw HTML escaped). Set by the store on markdown bodies."},"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"ImageRef":{"type":"object","description":"Reference to an image. With `assetId` the image is an Asset node (RFC-0004): the store writes its media URL into `src`, and its size into `width` and `height`, as a fallback for readers that do not resolve assets. Without it, `src` is a relative path or an absolute URL, as before.","additionalProperties":false,"required":["src","alt"],"properties":{"assetId":{"$ref":"#/$defs/Id","description":"The Asset node shown (RFC-0004). Renderers that know assets resolve it and ignore `src`."},"src":{"type":"string","minLength":1,"maxLength":2048},"alt":{"type":"string","maxLength":500},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1}}},"Link":{"type":"object","additionalProperties":false,"required":["label","href"],"properties":{"label":{"type":"string","minLength":1,"maxLength":200},"href":{"type":"string","minLength":1,"maxLength":2048},"external":{"type":"boolean"}}},"Locale":{"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"},"Metadata":{"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}},"MotionLevel":{"type":"string","description":"How much a page may move (RFC-0024 §1); the reader's reduced-motion preference and pause control can only lower it.","enum":["none","subtle","standard","expressive"]},"PublishStatus":{"type":"string","description":"Editorial lifecycle of a page or content entry.","enum":["draft","review","published","archived"]},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Section":{"title":"Section","description":"A block inside a page, discriminated by `kind`. Variants are written inline (not via $ref) and `kind` is a single-value string enum so that TypeScript gets a literal union and Rust gets a serde internally-tagged enum.","oneOf":[{"type":"object","title":"HeroSection","additionalProperties":false,"required":["id","kind","heading"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["hero"]},"heading":{"type":"string","minLength":1,"maxLength":300},"subheading":{"type":"string","maxLength":1000},"image":{"$ref":"#/$defs/ImageRef"},"cta":{"$ref":"#/$defs/Link"}}},{"type":"object","title":"RichTextSection","additionalProperties":false,"required":["id","kind","format","body"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["richText"]},"format":{"type":"string","enum":["markdown","html"]},"body":{"type":"string","maxLength":200000},"profile":{"$ref":"#/$defs/BodyProfile"},"blocks":{"$ref":"#/$defs/BodyBlocks"}}},{"type":"object","title":"ImageSection","additionalProperties":false,"required":["id","kind","image"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["image"]},"image":{"$ref":"#/$defs/ImageRef"},"caption":{"type":"string","maxLength":1000}}},{"type":"object","title":"ContentListSection","description":"Renders the newest entries of a content collection (e.g. blog posts).","additionalProperties":false,"required":["id","kind","collection","limit","sort"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["contentList"]},"collection":{"$ref":"#/$defs/Slug"},"limit":{"type":"integer","minimum":1,"maximum":100},"sort":{"type":"string","enum":["newest","oldest","title"]},"filter":{"type":"string","description":"Filter expression over term assignments (RFC-0002), e.g. 'category:news+tag:ai' or 'category:news>*'.","minLength":1,"maxLength":512}}},{"type":"object","title":"ComponentSection","description":"A typed block from the block registry (RFC-0015): `type` names a registered block type (one global namespace of English names), `props` follow that type's props schema, which the store validates.","additionalProperties":false,"required":["id","kind","type","props"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["component"]},"type":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,40}$"},"props":{"type":"object","description":"Validated against the type's props schema by the store, not by this contract.","additionalProperties":true}}},{"type":"object","title":"SurfaceSection","description":"A generative UI surface (RFC-0020): a flat list of catalog components with a `root`, plus a data model their props may bind to. Written and streamed by agents as data, never code; the store checks the tree, the catalog and the limits.","additionalProperties":false,"required":["id","kind","catalog","components"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["surface"]},"catalog":{"type":"string","description":"The catalog the surface was written for: `@` (RFC-0018 manifest).","pattern":"^[a-z][a-z0-9-]{0,40}@[0-9]{1,4}$"},"components":{"type":"array","minItems":1,"maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponent"}},"data":{"type":"object","description":"The surface's data model: JSON its components bind to with `{path}` (at most 64 KB serialized; the store enforces the size).","additionalProperties":true}}}]},"Seo":{"type":"object","description":"Search and social metadata of a routable node (page, term archive).","additionalProperties":false,"properties":{"title":{"type":"string","maxLength":300},"description":{"type":"string","maxLength":500},"noindex":{"type":"boolean"},"canonical":{"type":"string","maxLength":2048},"ogImage":{"$ref":"#/$defs/ImageRef"}}},"Slug":{"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128},"SurfaceComponent":{"type":"object","description":"One element of a generative UI surface (RFC-0020 §1), in a flat list: `component` names a catalog entry (a PascalCase built-in primitive such as `Stack`, or a registry block type such as `callout`), `children` refer to other ids of the same surface, and `props` follow the entry's schema, which the store checks. Field names follow A2UI v0.9.","additionalProperties":false,"required":["id","component"],"properties":{"id":{"$ref":"#/$defs/SurfaceComponentId"},"component":{"type":"string","pattern":"^(?:[A-Z][A-Za-z0-9]{1,40}|[a-z][a-z0-9-]{1,40})$"},"children":{"type":"array","maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponentId"}},"props":{"type":"object","description":"Literal values, `{path}` bindings into the surface's data, or `{call}` named functions; validated by the store against the catalog, not by this contract.","additionalProperties":true},"visible":{"type":"object","description":"A visibility condition over the surface's data (`eq`, `ne`, `gt`, `lt`, `and`, `or`, `not`); validated by the store.","additionalProperties":true},"motion":{"type":"object","description":"Motion presets for this component (RFC-0024 §2): `reveal`, `hover`, `speed`, `delay`, `stagger`, `replay` and later `scroll`, `loop`; enums and clamped numbers only, validated by the store against the catalog. The renderer turns them into CSS; agents never write CSS.","additionalProperties":true}}},"SurfaceComponentId":{"type":"string","description":"A component's id, unique within its surface; `root` is the tree's root (RFC-0020 §1).","pattern":"^[a-z][a-z0-9-]{0,40}$"},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"},"Translation":{"type":"object","description":"Links a page or entry to the other language versions of the same work (RFC-0033 §2). Staleness is computed: the source's current hash differs from source.hash.","additionalProperties":false,"required":["group","method"],"properties":{"group":{"$ref":"#/$defs/Id","description":"The group's id: the original node's id, shared by every translation. May cross sites in one project."},"method":{"$ref":"#/$defs/TranslationMethod"},"layout":{"type":"string","description":"linked: the original's structural changes propagate to this translation as drafts (RFC-0033 §4a); independent: nothing propagates. Absent means linked.","enum":["linked","independent"]},"source":{"type":"object","description":"The node and revision this translation was made from. Absent on the original.","additionalProperties":false,"required":["id","locale","rev","hash"],"properties":{"id":{"$ref":"#/$defs/Id"},"siteId":{"$ref":"#/$defs/Id","description":"The source's site when it is another site of the same project (a language edition). Absent means this site."},"locale":{"$ref":"#/$defs/Locale"},"rev":{"$ref":"#/$defs/Revision"},"hash":{"type":"string","description":"Lower-case hex SHA-256 of the source node's canonical JSON at that revision.","pattern":"^[0-9a-f]{64}$"}}},"provenance":{"type":"object","description":"The machine-translation record: engine, glossary revision, checks run, quality score. Required when method is machine or machine-reviewed.","additionalProperties":false,"required":["engine","at"],"properties":{"engine":{"type":"string","minLength":1,"maxLength":200},"glossaryRev":{"$ref":"#/$defs/Revision"},"score":{"type":"number","minimum":0,"maximum":100},"checks":{"type":"array","items":{"type":"string","minLength":1,"maxLength":100},"maxItems":50},"at":{"$ref":"#/$defs/Timestamp"}}},"review":{"type":"object","description":"The person who reviewed this translation and when. Only humans set it.","additionalProperties":false,"required":["by","at"],"properties":{"by":{"$ref":"#/$defs/Actor"},"at":{"$ref":"#/$defs/Timestamp"}}}}},"TranslationMethod":{"type":"string","description":"How a page's or entry's text came to be (RFC-0033). 'original' marks the source of a translation group; 'machine-reviewed' is machine output a person has reviewed.","enum":["original","human","machine","machine-reviewed"]}}}; -const schema98 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; -const schema99 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; -const schema101 = {"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"}; -const schema102 = {"type":"string","description":"Editorial lifecycle of a page or content entry.","enum":["draft","review","published","archived"]}; -const schema128 = {"type":"string","description":"How much a page may move (RFC-0024 §1); the reader's reduced-motion preference and pause control can only lower it.","enum":["none","subtle","standard","expressive"]}; -const schema129 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; -const schema115 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; -const schema142 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}}; -const schema103 = {"title":"Section","description":"A block inside a page, discriminated by `kind`. Variants are written inline (not via $ref) and `kind` is a single-value string enum so that TypeScript gets a literal union and Rust gets a serde internally-tagged enum.","oneOf":[{"type":"object","title":"HeroSection","additionalProperties":false,"required":["id","kind","heading"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["hero"]},"heading":{"type":"string","minLength":1,"maxLength":300},"subheading":{"type":"string","maxLength":1000},"image":{"$ref":"#/$defs/ImageRef"},"cta":{"$ref":"#/$defs/Link"}}},{"type":"object","title":"RichTextSection","additionalProperties":false,"required":["id","kind","format","body"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["richText"]},"format":{"type":"string","enum":["markdown","html"]},"body":{"type":"string","maxLength":200000},"profile":{"$ref":"#/$defs/BodyProfile"},"blocks":{"$ref":"#/$defs/BodyBlocks"}}},{"type":"object","title":"ImageSection","additionalProperties":false,"required":["id","kind","image"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["image"]},"image":{"$ref":"#/$defs/ImageRef"},"caption":{"type":"string","maxLength":1000}}},{"type":"object","title":"ContentListSection","description":"Renders the newest entries of a content collection (e.g. blog posts).","additionalProperties":false,"required":["id","kind","collection","limit","sort"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["contentList"]},"collection":{"$ref":"#/$defs/Slug"},"limit":{"type":"integer","minimum":1,"maximum":100},"sort":{"type":"string","enum":["newest","oldest","title"]},"filter":{"type":"string","description":"Filter expression over term assignments (RFC-0002), e.g. 'category:news+tag:ai' or 'category:news>*'.","minLength":1,"maxLength":512}}},{"type":"object","title":"ComponentSection","description":"A typed block from the block registry (RFC-0015): `type` names a registered block type (one global namespace of English names), `props` follow that type's props schema, which the store validates.","additionalProperties":false,"required":["id","kind","type","props"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["component"]},"type":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,40}$"},"props":{"type":"object","description":"Validated against the type's props schema by the store, not by this contract.","additionalProperties":true}}},{"type":"object","title":"SurfaceSection","description":"A generative UI surface (RFC-0020): a flat list of catalog components with a `root`, plus a data model their props may bind to. Written and streamed by agents as data, never code; the store checks the tree, the catalog and the limits.","additionalProperties":false,"required":["id","kind","catalog","components"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["surface"]},"catalog":{"type":"string","description":"The catalog the surface was written for: `@` (RFC-0018 manifest).","pattern":"^[a-z][a-z0-9-]{0,40}@[0-9]{1,4}$"},"components":{"type":"array","minItems":1,"maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponent"}},"data":{"type":"object","description":"The surface's data model: JSON its components bind to with `{path}` (at most 64 KB serialized; the store enforces the size).","additionalProperties":true}}}]}; -const schema107 = {"type":"object","additionalProperties":false,"required":["label","href"],"properties":{"label":{"type":"string","minLength":1,"maxLength":200},"href":{"type":"string","minLength":1,"maxLength":2048},"external":{"type":"boolean"}}}; -const schema109 = {"type":"integer","enum":[1],"description":"The markdown dialect a body follows (RFC-0014 §1): 1 = Cloudhal Markdown v1 (CommonMark + GFM + `asset:` + directives; raw HTML escaped). Set by the store on markdown bodies."}; -const schema118 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128}; -const schema105 = {"type":"object","description":"Reference to an image. With `assetId` the image is an Asset node (RFC-0004): the store writes its media URL into `src`, and its size into `width` and `height`, as a fallback for readers that do not resolve assets. Without it, `src` is a relative path or an absolute URL, as before.","additionalProperties":false,"required":["src","alt"],"properties":{"assetId":{"$ref":"#/$defs/Id","description":"The Asset node shown (RFC-0004). Renderers that know assets resolve it and ignore `src`."},"src":{"type":"string","minLength":1,"maxLength":2048},"alt":{"type":"string","maxLength":500},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1}}}; +const schema99 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/page.schema.json","title":"Page","description":"A routable page of a site: a path, editorial status and an ordered list of sections. Sections are embedded (not separate nodes) so a page is the unit of locking, snapshotting and diffing.","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","path","title","locale","status","sections","rev","createdAt","updatedAt"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/Id"},"siteId":{"$ref":"#/$defs/Id"},"path":{"type":"string","description":"Absolute URL path, starting with '/', no trailing slash except root.","pattern":"^/(?:[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*)?$","maxLength":1024},"title":{"type":"string","minLength":1,"maxLength":300},"locale":{"$ref":"#/$defs/Locale"},"status":{"$ref":"#/$defs/PublishStatus"},"sections":{"type":"array","items":{"$ref":"#/$defs/Section"}},"seo":{"$ref":"#/$defs/Seo"},"availability":{"$ref":"#/$defs/Availability"},"chrome":{"type":"object","description":"This page's own header and footer (RFC-0025 §1): a landing page without the menu, a checkout without the footer.","additionalProperties":false,"properties":{"header":{"type":"string","enum":["default","transparent","hidden"]},"footer":{"type":"string","enum":["default","minimal","hidden"]}}},"motion":{"$ref":"#/$defs/MotionLevel","description":"Overrides the site's motion level for this page, up or down (RFC-0024 §1); absent follows the site."},"rev":{"$ref":"#/$defs/Revision"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"translation":{"$ref":"#/$defs/Translation"},"metadata":{"$ref":"#/$defs/Metadata"}},"$defs":{"Actor":{"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}},"ActorKind":{"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]},"Availability":{"type":"object","description":"Time gates of a page or content entry (RFC-0008): the published site shows the node only from `from` (inclusive: an embargo) until `until` (exclusive: an expiry), checked at render time, so the gates hold even when no scheduler runs. Before `from` and from `until` on, the node is absent from every public route, list, feed, sitemap and search output; previews still show it. At least one gate; writers check that `from` is before `until`.","additionalProperties":false,"minProperties":1,"properties":{"from":{"$ref":"#/$defs/Timestamp"},"until":{"$ref":"#/$defs/Timestamp"}}},"BodyBlock":{"type":"object","description":"One top-level block of a markdown body in the store's index (RFC-0014 §3). Written by the store only: a client's values are replaced on every write. `by`, `via` and `at` say who last changed the block's content, through which channel and when; they are absent for content written before the index existed.","additionalProperties":false,"required":["id","hash"],"properties":{"id":{"type":"string","pattern":"^blk_[0-9A-Za-z]{8,24}$","description":"Stable across writes that keep the block's content or its place among unchanged blocks."},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"SHA-256 (hex) of the block's markdown without trailing whitespace."},"by":{"$ref":"#/$defs/Actor"},"via":{"type":"string","enum":["studio","mcp","api","import","ai-command","proposal"],"description":"The channel of that change."},"at":{"$ref":"#/$defs/Timestamp"}}},"BodyBlocks":{"type":"array","description":"A markdown body's block index, in body order (RFC-0014 §3). Absent means not indexed (yet, or an HTML body), never empty.","maxItems":5000,"items":{"$ref":"#/$defs/BodyBlock"}},"BodyProfile":{"type":"integer","enum":[1],"description":"The markdown dialect a body follows (RFC-0014 §1): 1 = Cloudhal Markdown v1 (CommonMark + GFM + `asset:` + directives; raw HTML escaped). Set by the store on markdown bodies."},"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"ImageRef":{"type":"object","description":"Reference to an image. With `assetId` the image is an Asset node (RFC-0004): the store writes its media URL into `src`, and its size into `width` and `height`, as a fallback for readers that do not resolve assets. Without it, `src` is a relative path or an absolute URL, as before.","additionalProperties":false,"required":["src","alt"],"properties":{"assetId":{"$ref":"#/$defs/Id","description":"The Asset node shown (RFC-0004). Renderers that know assets resolve it and ignore `src`."},"src":{"type":"string","minLength":1,"maxLength":2048},"alt":{"type":"string","maxLength":500},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1}}},"Link":{"type":"object","additionalProperties":false,"required":["label","href"],"properties":{"label":{"type":"string","minLength":1,"maxLength":200},"href":{"type":"string","minLength":1,"maxLength":2048},"external":{"type":"boolean"}}},"Locale":{"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"},"Metadata":{"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}},"MotionLevel":{"type":"string","description":"How much a page may move (RFC-0024 §1); the reader's reduced-motion preference and pause control can only lower it.","enum":["none","subtle","standard","expressive"]},"PublishStatus":{"type":"string","description":"Editorial lifecycle of a page or content entry.","enum":["draft","review","published","archived"]},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Section":{"title":"Section","description":"A block inside a page, discriminated by `kind`. Variants are written inline (not via $ref) and `kind` is a single-value string enum so that TypeScript gets a literal union and Rust gets a serde internally-tagged enum.","oneOf":[{"type":"object","title":"HeroSection","additionalProperties":false,"required":["id","kind","heading"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["hero"]},"heading":{"type":"string","minLength":1,"maxLength":300},"subheading":{"type":"string","maxLength":1000},"image":{"$ref":"#/$defs/ImageRef"},"cta":{"$ref":"#/$defs/Link"}}},{"type":"object","title":"RichTextSection","additionalProperties":false,"required":["id","kind","format","body"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["richText"]},"format":{"type":"string","enum":["markdown","html"]},"body":{"type":"string","maxLength":200000},"profile":{"$ref":"#/$defs/BodyProfile"},"blocks":{"$ref":"#/$defs/BodyBlocks"}}},{"type":"object","title":"ImageSection","additionalProperties":false,"required":["id","kind","image"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["image"]},"image":{"$ref":"#/$defs/ImageRef"},"caption":{"type":"string","maxLength":1000}}},{"type":"object","title":"ContentListSection","description":"Renders the newest entries of a content collection (e.g. blog posts).","additionalProperties":false,"required":["id","kind","collection","limit","sort"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["contentList"]},"collection":{"$ref":"#/$defs/Slug"},"limit":{"type":"integer","minimum":1,"maximum":100},"sort":{"type":"string","enum":["newest","oldest","title"]},"filter":{"type":"string","description":"Filter expression over term assignments (RFC-0002), e.g. 'category:news+tag:ai' or 'category:news>*'.","minLength":1,"maxLength":512}}},{"type":"object","title":"ComponentSection","description":"A typed block from the block registry (RFC-0015): `type` names a registered block type (one global namespace of English names), `props` follow that type's props schema, which the store validates.","additionalProperties":false,"required":["id","kind","type","props"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["component"]},"type":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,40}$"},"props":{"type":"object","description":"Validated against the type's props schema by the store, not by this contract.","additionalProperties":true}}},{"type":"object","title":"SurfaceSection","description":"A generative UI surface (RFC-0020): a flat list of catalog components with a `root`, plus a data model their props may bind to. Written and streamed by agents as data, never code; the store checks the tree, the catalog and the limits.","additionalProperties":false,"required":["id","kind","catalog","components"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["surface"]},"catalog":{"type":"string","description":"The catalog the surface was written for: `@` (RFC-0018 manifest).","pattern":"^[a-z][a-z0-9-]{0,40}@[0-9]{1,4}$"},"components":{"type":"array","minItems":1,"maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponent"}},"data":{"type":"object","description":"The surface's data model: JSON its components bind to with `{path}` (at most 64 KB serialized; the store enforces the size).","additionalProperties":true}}}]},"Seo":{"type":"object","description":"Search and social metadata of a routable node (page, term archive).","additionalProperties":false,"properties":{"title":{"type":"string","maxLength":300},"description":{"type":"string","maxLength":500},"noindex":{"type":"boolean"},"canonical":{"type":"string","maxLength":2048},"ogImage":{"$ref":"#/$defs/ImageRef"}}},"Slug":{"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128},"SurfaceComponent":{"type":"object","description":"One element of a generative UI surface (RFC-0020 §1), in a flat list: `component` names a catalog entry (a PascalCase built-in primitive such as `Stack`, or a registry block type such as `callout`), `children` refer to other ids of the same surface, and `props` follow the entry's schema, which the store checks. Field names follow A2UI v0.9.","additionalProperties":false,"required":["id","component"],"properties":{"id":{"$ref":"#/$defs/SurfaceComponentId"},"component":{"type":"string","pattern":"^(?:[A-Z][A-Za-z0-9]{1,40}|[a-z][a-z0-9-]{1,40})$"},"children":{"type":"array","maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponentId"}},"props":{"type":"object","description":"Literal values, `{path}` bindings into the surface's data, or `{call}` named functions; validated by the store against the catalog, not by this contract.","additionalProperties":true},"visible":{"type":"object","description":"A visibility condition over the surface's data (`eq`, `ne`, `gt`, `lt`, `and`, `or`, `not`); validated by the store.","additionalProperties":true},"motion":{"type":"object","description":"Motion presets for this component (RFC-0024 §2): `reveal`, `hover`, `speed`, `delay`, `stagger`, `replay` and later `scroll`, `loop`; enums and clamped numbers only, validated by the store against the catalog. The renderer turns them into CSS; agents never write CSS.","additionalProperties":true}}},"SurfaceComponentId":{"type":"string","description":"A component's id, unique within its surface; `root` is the tree's root (RFC-0020 §1).","pattern":"^[a-z][a-z0-9-]{0,40}$"},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"},"Translation":{"type":"object","description":"Links a page or entry to the other language versions of the same work (RFC-0033 §2). Staleness is computed: the source's current hash differs from source.hash.","additionalProperties":false,"required":["group","method"],"properties":{"group":{"$ref":"#/$defs/Id","description":"The group's id: the original node's id, shared by every translation. May cross sites in one project."},"method":{"$ref":"#/$defs/TranslationMethod"},"layout":{"type":"string","description":"linked: the original's structural changes propagate to this translation as drafts (RFC-0033 §4a); independent: nothing propagates. Absent means linked.","enum":["linked","independent"]},"source":{"type":"object","description":"The node and revision this translation was made from. Absent on the original.","additionalProperties":false,"required":["id","locale","rev","hash"],"properties":{"id":{"$ref":"#/$defs/Id"},"siteId":{"$ref":"#/$defs/Id","description":"The source's site when it is another site of the same project (a language edition). Absent means this site."},"locale":{"$ref":"#/$defs/Locale"},"rev":{"$ref":"#/$defs/Revision"},"hash":{"type":"string","description":"Lower-case hex SHA-256 of the source node's canonical JSON at that revision.","pattern":"^[0-9a-f]{64}$"}}},"provenance":{"type":"object","description":"The machine-translation record: engine, glossary revision, checks run, quality score. Required when method is machine or machine-reviewed.","additionalProperties":false,"required":["engine","at"],"properties":{"engine":{"type":"string","minLength":1,"maxLength":200},"glossaryRev":{"$ref":"#/$defs/Revision"},"score":{"type":"number","minimum":0,"maximum":100},"checks":{"type":"array","items":{"type":"string","minLength":1,"maxLength":100},"maxItems":50},"at":{"$ref":"#/$defs/Timestamp"}}},"review":{"type":"object","description":"The person who reviewed this translation and when. Only humans set it.","additionalProperties":false,"required":["by","at"],"properties":{"by":{"$ref":"#/$defs/Actor"},"at":{"$ref":"#/$defs/Timestamp"}}}}},"TranslationMethod":{"type":"string","description":"How a page's or entry's text came to be (RFC-0033). 'original' marks the source of a translation group; 'machine-reviewed' is machine output a person has reviewed.","enum":["original","human","machine","machine-reviewed"]}}}; +const schema100 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; +const schema101 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; +const schema103 = {"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"}; +const schema104 = {"type":"string","description":"Editorial lifecycle of a page or content entry.","enum":["draft","review","published","archived"]}; +const schema130 = {"type":"string","description":"How much a page may move (RFC-0024 §1); the reader's reduced-motion preference and pause control can only lower it.","enum":["none","subtle","standard","expressive"]}; +const schema131 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; +const schema117 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; +const schema144 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}}; +const schema105 = {"title":"Section","description":"A block inside a page, discriminated by `kind`. Variants are written inline (not via $ref) and `kind` is a single-value string enum so that TypeScript gets a literal union and Rust gets a serde internally-tagged enum.","oneOf":[{"type":"object","title":"HeroSection","additionalProperties":false,"required":["id","kind","heading"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["hero"]},"heading":{"type":"string","minLength":1,"maxLength":300},"subheading":{"type":"string","maxLength":1000},"image":{"$ref":"#/$defs/ImageRef"},"cta":{"$ref":"#/$defs/Link"}}},{"type":"object","title":"RichTextSection","additionalProperties":false,"required":["id","kind","format","body"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["richText"]},"format":{"type":"string","enum":["markdown","html"]},"body":{"type":"string","maxLength":200000},"profile":{"$ref":"#/$defs/BodyProfile"},"blocks":{"$ref":"#/$defs/BodyBlocks"}}},{"type":"object","title":"ImageSection","additionalProperties":false,"required":["id","kind","image"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["image"]},"image":{"$ref":"#/$defs/ImageRef"},"caption":{"type":"string","maxLength":1000}}},{"type":"object","title":"ContentListSection","description":"Renders the newest entries of a content collection (e.g. blog posts).","additionalProperties":false,"required":["id","kind","collection","limit","sort"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["contentList"]},"collection":{"$ref":"#/$defs/Slug"},"limit":{"type":"integer","minimum":1,"maximum":100},"sort":{"type":"string","enum":["newest","oldest","title"]},"filter":{"type":"string","description":"Filter expression over term assignments (RFC-0002), e.g. 'category:news+tag:ai' or 'category:news>*'.","minLength":1,"maxLength":512}}},{"type":"object","title":"ComponentSection","description":"A typed block from the block registry (RFC-0015): `type` names a registered block type (one global namespace of English names), `props` follow that type's props schema, which the store validates.","additionalProperties":false,"required":["id","kind","type","props"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["component"]},"type":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,40}$"},"props":{"type":"object","description":"Validated against the type's props schema by the store, not by this contract.","additionalProperties":true}}},{"type":"object","title":"SurfaceSection","description":"A generative UI surface (RFC-0020): a flat list of catalog components with a `root`, plus a data model their props may bind to. Written and streamed by agents as data, never code; the store checks the tree, the catalog and the limits.","additionalProperties":false,"required":["id","kind","catalog","components"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["surface"]},"catalog":{"type":"string","description":"The catalog the surface was written for: `@` (RFC-0018 manifest).","pattern":"^[a-z][a-z0-9-]{0,40}@[0-9]{1,4}$"},"components":{"type":"array","minItems":1,"maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponent"}},"data":{"type":"object","description":"The surface's data model: JSON its components bind to with `{path}` (at most 64 KB serialized; the store enforces the size).","additionalProperties":true}}}]}; +const schema109 = {"type":"object","additionalProperties":false,"required":["label","href"],"properties":{"label":{"type":"string","minLength":1,"maxLength":200},"href":{"type":"string","minLength":1,"maxLength":2048},"external":{"type":"boolean"}}}; +const schema111 = {"type":"integer","enum":[1],"description":"The markdown dialect a body follows (RFC-0014 §1): 1 = Cloudhal Markdown v1 (CommonMark + GFM + `asset:` + directives; raw HTML escaped). Set by the store on markdown bodies."}; +const schema120 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128}; +const schema107 = {"type":"object","description":"Reference to an image. With `assetId` the image is an Asset node (RFC-0004): the store writes its media URL into `src`, and its size into `width` and `height`, as a fallback for readers that do not resolve assets. Without it, `src` is a relative path or an absolute URL, as before.","additionalProperties":false,"required":["src","alt"],"properties":{"assetId":{"$ref":"#/$defs/Id","description":"The Asset node shown (RFC-0004). Renderers that know assets resolve it and ignore `src`."},"src":{"type":"string","minLength":1,"maxLength":2048},"alt":{"type":"string","maxLength":500},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1}}}; function validate55(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -6108,10 +6233,10 @@ return errors === 0; } validate55.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; -const schema110 = {"type":"array","description":"A markdown body's block index, in body order (RFC-0014 §3). Absent means not indexed (yet, or an HTML body), never empty.","maxItems":5000,"items":{"$ref":"#/$defs/BodyBlock"}}; -const schema111 = {"type":"object","description":"One top-level block of a markdown body in the store's index (RFC-0014 §3). Written by the store only: a client's values are replaced on every write. `by`, `via` and `at` say who last changed the block's content, through which channel and when; they are absent for content written before the index existed.","additionalProperties":false,"required":["id","hash"],"properties":{"id":{"type":"string","pattern":"^blk_[0-9A-Za-z]{8,24}$","description":"Stable across writes that keep the block's content or its place among unchanged blocks."},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"SHA-256 (hex) of the block's markdown without trailing whitespace."},"by":{"$ref":"#/$defs/Actor"},"via":{"type":"string","enum":["studio","mcp","api","import","ai-command","proposal"],"description":"The channel of that change."},"at":{"$ref":"#/$defs/Timestamp"}}}; -const schema112 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}}; -const schema113 = {"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]}; +const schema112 = {"type":"array","description":"A markdown body's block index, in body order (RFC-0014 §3). Absent means not indexed (yet, or an HTML body), never empty.","maxItems":5000,"items":{"$ref":"#/$defs/BodyBlock"}}; +const schema113 = {"type":"object","description":"One top-level block of a markdown body in the store's index (RFC-0014 §3). Written by the store only: a client's values are replaced on every write. `by`, `via` and `at` say who last changed the block's content, through which channel and when; they are absent for content written before the index existed.","additionalProperties":false,"required":["id","hash"],"properties":{"id":{"type":"string","pattern":"^blk_[0-9A-Za-z]{8,24}$","description":"Stable across writes that keep the block's content or its place among unchanged blocks."},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"SHA-256 (hex) of the block's markdown without trailing whitespace."},"by":{"$ref":"#/$defs/Actor"},"via":{"type":"string","enum":["studio","mcp","api","import","ai-command","proposal"],"description":"The channel of that change."},"at":{"$ref":"#/$defs/Timestamp"}}}; +const schema114 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}}; +const schema115 = {"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]}; function validate59(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -6169,7 +6294,7 @@ vErrors.push(err3); errors++; } if(!(((data0 === "human") || (data0 === "agent")) || (data0 === "system"))){ -const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/$defs/ActorKind/enum",keyword:"enum",params:{allowedValues: schema113.enum},message:"must be equal to one of the allowed values"}; +const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/$defs/ActorKind/enum",keyword:"enum",params:{allowedValues: schema115.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err4]; } @@ -6312,7 +6437,7 @@ errors++; if(data.id !== undefined){ let data0 = data.id; if(typeof data0 === "string"){ -if(!pattern26.test(data0)){ +if(!pattern28.test(data0)){ const err3 = {instancePath:instancePath+"/id",schemaPath:"#/properties/id/pattern",keyword:"pattern",params:{pattern: "^blk_[0-9A-Za-z]{8,24}$"},message:"must match pattern \""+"^blk_[0-9A-Za-z]{8,24}$"+"\""}; if(vErrors === null){ vErrors = [err3]; @@ -6337,7 +6462,7 @@ errors++; if(data.hash !== undefined){ let data1 = data.hash; if(typeof data1 === "string"){ -if(!pattern27.test(data1)){ +if(!pattern29.test(data1)){ const err5 = {instancePath:instancePath+"/hash",schemaPath:"#/properties/hash/pattern",keyword:"pattern",params:{pattern: "^[0-9a-f]{64}$"},message:"must match pattern \""+"^[0-9a-f]{64}$"+"\""}; if(vErrors === null){ vErrors = [err5]; @@ -6378,7 +6503,7 @@ vErrors.push(err7); errors++; } if(!((((((data3 === "studio") || (data3 === "mcp")) || (data3 === "api")) || (data3 === "import")) || (data3 === "ai-command")) || (data3 === "proposal"))){ -const err8 = {instancePath:instancePath+"/via",schemaPath:"#/properties/via/enum",keyword:"enum",params:{allowedValues: schema111.properties.via.enum},message:"must be equal to one of the allowed values"}; +const err8 = {instancePath:instancePath+"/via",schemaPath:"#/properties/via/enum",keyword:"enum",params:{allowedValues: schema113.properties.via.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err8]; } @@ -6474,8 +6599,8 @@ return errors === 0; } validate57.evaluated = {"items":true,"dynamicProps":false,"dynamicItems":false}; -const schema121 = {"type":"object","description":"One element of a generative UI surface (RFC-0020 §1), in a flat list: `component` names a catalog entry (a PascalCase built-in primitive such as `Stack`, or a registry block type such as `callout`), `children` refer to other ids of the same surface, and `props` follow the entry's schema, which the store checks. Field names follow A2UI v0.9.","additionalProperties":false,"required":["id","component"],"properties":{"id":{"$ref":"#/$defs/SurfaceComponentId"},"component":{"type":"string","pattern":"^(?:[A-Z][A-Za-z0-9]{1,40}|[a-z][a-z0-9-]{1,40})$"},"children":{"type":"array","maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponentId"}},"props":{"type":"object","description":"Literal values, `{path}` bindings into the surface's data, or `{call}` named functions; validated by the store against the catalog, not by this contract.","additionalProperties":true},"visible":{"type":"object","description":"A visibility condition over the surface's data (`eq`, `ne`, `gt`, `lt`, `and`, `or`, `not`); validated by the store.","additionalProperties":true},"motion":{"type":"object","description":"Motion presets for this component (RFC-0024 §2): `reveal`, `hover`, `speed`, `delay`, `stagger`, `replay` and later `scroll`, `loop`; enums and clamped numbers only, validated by the store against the catalog. The renderer turns them into CSS; agents never write CSS.","additionalProperties":true}}}; -const schema122 = {"type":"string","description":"A component's id, unique within its surface; `root` is the tree's root (RFC-0020 §1).","pattern":"^[a-z][a-z0-9-]{0,40}$"}; +const schema123 = {"type":"object","description":"One element of a generative UI surface (RFC-0020 §1), in a flat list: `component` names a catalog entry (a PascalCase built-in primitive such as `Stack`, or a registry block type such as `callout`), `children` refer to other ids of the same surface, and `props` follow the entry's schema, which the store checks. Field names follow A2UI v0.9.","additionalProperties":false,"required":["id","component"],"properties":{"id":{"$ref":"#/$defs/SurfaceComponentId"},"component":{"type":"string","pattern":"^(?:[A-Z][A-Za-z0-9]{1,40}|[a-z][a-z0-9-]{1,40})$"},"children":{"type":"array","maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponentId"}},"props":{"type":"object","description":"Literal values, `{path}` bindings into the surface's data, or `{call}` named functions; validated by the store against the catalog, not by this contract.","additionalProperties":true},"visible":{"type":"object","description":"A visibility condition over the surface's data (`eq`, `ne`, `gt`, `lt`, `and`, `or`, `not`); validated by the store.","additionalProperties":true},"motion":{"type":"object","description":"Motion presets for this component (RFC-0024 §2): `reveal`, `hover`, `speed`, `delay`, `stagger`, `replay` and later `scroll`, `loop`; enums and clamped numbers only, validated by the store against the catalog. The renderer turns them into CSS; agents never write CSS.","additionalProperties":true}}}; +const schema124 = {"type":"string","description":"A component's id, unique within its surface; `root` is the tree's root (RFC-0020 §1).","pattern":"^[a-z][a-z0-9-]{0,40}$"}; function validate64(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -6523,7 +6648,7 @@ errors++; if(data.id !== undefined){ let data0 = data.id; if(typeof data0 === "string"){ -if(!pattern36.test(data0)){ +if(!pattern38.test(data0)){ const err3 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/SurfaceComponentId/pattern",keyword:"pattern",params:{pattern: "^[a-z][a-z0-9-]{0,40}$"},message:"must match pattern \""+"^[a-z][a-z0-9-]{0,40}$"+"\""}; if(vErrors === null){ vErrors = [err3]; @@ -6548,7 +6673,7 @@ errors++; if(data.component !== undefined){ let data1 = data.component; if(typeof data1 === "string"){ -if(!pattern37.test(data1)){ +if(!pattern39.test(data1)){ const err5 = {instancePath:instancePath+"/component",schemaPath:"#/properties/component/pattern",keyword:"pattern",params:{pattern: "^(?:[A-Z][A-Za-z0-9]{1,40}|[a-z][a-z0-9-]{1,40})$"},message:"must match pattern \""+"^(?:[A-Z][A-Za-z0-9]{1,40}|[a-z][a-z0-9-]{1,40})$"+"\""}; if(vErrors === null){ vErrors = [err5]; @@ -6587,7 +6712,7 @@ const len0 = data2.length; for(let i0=0; i0*'.","minLength":1,"maxLength":512}}},{"type":"object","title":"ComponentSection","description":"A typed block from the block registry (RFC-0015): `type` names a registered block type (one global namespace of English names), `props` follow that type's props schema, which the store validates.","additionalProperties":false,"required":["id","kind","type","props"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["component"]},"type":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,40}$"},"props":{"type":"object","description":"Validated against the type's props schema by the store, not by this contract.","additionalProperties":true}}},{"type":"object","title":"SurfaceSection","description":"A generative UI surface (RFC-0020): a flat list of catalog components with a `root`, plus a data model their props may bind to. Written and streamed by agents as data, never code; the store checks the tree, the catalog and the limits.","additionalProperties":false,"required":["id","kind","catalog","components"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["surface"]},"catalog":{"type":"string","description":"The catalog the surface was written for: `@` (RFC-0018 manifest).","pattern":"^[a-z][a-z0-9-]{0,40}@[0-9]{1,4}$"},"components":{"type":"array","minItems":1,"maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponent"}},"data":{"type":"object","description":"The surface's data model: JSON its components bind to with `{path}` (at most 64 KB serialized; the store enforces the size).","additionalProperties":true}}}],"$defs":{"Actor":{"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}},"ActorKind":{"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]},"BodyBlock":{"type":"object","description":"One top-level block of a markdown body in the store's index (RFC-0014 §3). Written by the store only: a client's values are replaced on every write. `by`, `via` and `at` say who last changed the block's content, through which channel and when; they are absent for content written before the index existed.","additionalProperties":false,"required":["id","hash"],"properties":{"id":{"type":"string","pattern":"^blk_[0-9A-Za-z]{8,24}$","description":"Stable across writes that keep the block's content or its place among unchanged blocks."},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"SHA-256 (hex) of the block's markdown without trailing whitespace."},"by":{"$ref":"#/$defs/Actor"},"via":{"type":"string","enum":["studio","mcp","api","import","ai-command","proposal"],"description":"The channel of that change."},"at":{"$ref":"#/$defs/Timestamp"}}},"BodyBlocks":{"type":"array","description":"A markdown body's block index, in body order (RFC-0014 §3). Absent means not indexed (yet, or an HTML body), never empty.","maxItems":5000,"items":{"$ref":"#/$defs/BodyBlock"}},"BodyProfile":{"type":"integer","enum":[1],"description":"The markdown dialect a body follows (RFC-0014 §1): 1 = Cloudhal Markdown v1 (CommonMark + GFM + `asset:` + directives; raw HTML escaped). Set by the store on markdown bodies."},"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"ImageRef":{"type":"object","description":"Reference to an image. With `assetId` the image is an Asset node (RFC-0004): the store writes its media URL into `src`, and its size into `width` and `height`, as a fallback for readers that do not resolve assets. Without it, `src` is a relative path or an absolute URL, as before.","additionalProperties":false,"required":["src","alt"],"properties":{"assetId":{"$ref":"#/$defs/Id","description":"The Asset node shown (RFC-0004). Renderers that know assets resolve it and ignore `src`."},"src":{"type":"string","minLength":1,"maxLength":2048},"alt":{"type":"string","maxLength":500},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1}}},"Link":{"type":"object","additionalProperties":false,"required":["label","href"],"properties":{"label":{"type":"string","minLength":1,"maxLength":200},"href":{"type":"string","minLength":1,"maxLength":2048},"external":{"type":"boolean"}}},"Slug":{"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128},"SurfaceComponent":{"type":"object","description":"One element of a generative UI surface (RFC-0020 §1), in a flat list: `component` names a catalog entry (a PascalCase built-in primitive such as `Stack`, or a registry block type such as `callout`), `children` refer to other ids of the same surface, and `props` follow the entry's schema, which the store checks. Field names follow A2UI v0.9.","additionalProperties":false,"required":["id","component"],"properties":{"id":{"$ref":"#/$defs/SurfaceComponentId"},"component":{"type":"string","pattern":"^(?:[A-Z][A-Za-z0-9]{1,40}|[a-z][a-z0-9-]{1,40})$"},"children":{"type":"array","maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponentId"}},"props":{"type":"object","description":"Literal values, `{path}` bindings into the surface's data, or `{call}` named functions; validated by the store against the catalog, not by this contract.","additionalProperties":true},"visible":{"type":"object","description":"A visibility condition over the surface's data (`eq`, `ne`, `gt`, `lt`, `and`, `or`, `not`); validated by the store.","additionalProperties":true},"motion":{"type":"object","description":"Motion presets for this component (RFC-0024 §2): `reveal`, `hover`, `speed`, `delay`, `stagger`, `replay` and later `scroll`, `loop`; enums and clamped numbers only, validated by the store against the catalog. The renderer turns them into CSS; agents never write CSS.","additionalProperties":true}}},"SurfaceComponentId":{"type":"string","description":"A component's id, unique within its surface; `root` is the tree's root (RFC-0020 §1).","pattern":"^[a-z][a-z0-9-]{0,40}$"},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}}}; -const schema144 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; -const schema147 = {"type":"object","additionalProperties":false,"required":["label","href"],"properties":{"label":{"type":"string","minLength":1,"maxLength":200},"href":{"type":"string","minLength":1,"maxLength":2048},"external":{"type":"boolean"}}}; -const schema149 = {"type":"integer","enum":[1],"description":"The markdown dialect a body follows (RFC-0014 §1): 1 = Cloudhal Markdown v1 (CommonMark + GFM + `asset:` + directives; raw HTML escaped). Set by the store on markdown bodies."}; -const schema158 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128}; -const schema145 = {"type":"object","description":"Reference to an image. With `assetId` the image is an Asset node (RFC-0004): the store writes its media URL into `src`, and its size into `width` and `height`, as a fallback for readers that do not resolve assets. Without it, `src` is a relative path or an absolute URL, as before.","additionalProperties":false,"required":["src","alt"],"properties":{"assetId":{"$ref":"#/$defs/Id","description":"The Asset node shown (RFC-0004). Renderers that know assets resolve it and ignore `src`."},"src":{"type":"string","minLength":1,"maxLength":2048},"alt":{"type":"string","maxLength":500},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1}}}; +const schema145 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/section.schema.json","title":"Section","description":"A block inside a page, discriminated by `kind`. Variants are written inline (not via $ref) and `kind` is a single-value string enum so that TypeScript gets a literal union and Rust gets a serde internally-tagged enum.","oneOf":[{"type":"object","title":"HeroSection","additionalProperties":false,"required":["id","kind","heading"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["hero"]},"heading":{"type":"string","minLength":1,"maxLength":300},"subheading":{"type":"string","maxLength":1000},"image":{"$ref":"#/$defs/ImageRef"},"cta":{"$ref":"#/$defs/Link"}}},{"type":"object","title":"RichTextSection","additionalProperties":false,"required":["id","kind","format","body"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["richText"]},"format":{"type":"string","enum":["markdown","html"]},"body":{"type":"string","maxLength":200000},"profile":{"$ref":"#/$defs/BodyProfile"},"blocks":{"$ref":"#/$defs/BodyBlocks"}}},{"type":"object","title":"ImageSection","additionalProperties":false,"required":["id","kind","image"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["image"]},"image":{"$ref":"#/$defs/ImageRef"},"caption":{"type":"string","maxLength":1000}}},{"type":"object","title":"ContentListSection","description":"Renders the newest entries of a content collection (e.g. blog posts).","additionalProperties":false,"required":["id","kind","collection","limit","sort"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["contentList"]},"collection":{"$ref":"#/$defs/Slug"},"limit":{"type":"integer","minimum":1,"maximum":100},"sort":{"type":"string","enum":["newest","oldest","title"]},"filter":{"type":"string","description":"Filter expression over term assignments (RFC-0002), e.g. 'category:news+tag:ai' or 'category:news>*'.","minLength":1,"maxLength":512}}},{"type":"object","title":"ComponentSection","description":"A typed block from the block registry (RFC-0015): `type` names a registered block type (one global namespace of English names), `props` follow that type's props schema, which the store validates.","additionalProperties":false,"required":["id","kind","type","props"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["component"]},"type":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,40}$"},"props":{"type":"object","description":"Validated against the type's props schema by the store, not by this contract.","additionalProperties":true}}},{"type":"object","title":"SurfaceSection","description":"A generative UI surface (RFC-0020): a flat list of catalog components with a `root`, plus a data model their props may bind to. Written and streamed by agents as data, never code; the store checks the tree, the catalog and the limits.","additionalProperties":false,"required":["id","kind","catalog","components"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["surface"]},"catalog":{"type":"string","description":"The catalog the surface was written for: `@` (RFC-0018 manifest).","pattern":"^[a-z][a-z0-9-]{0,40}@[0-9]{1,4}$"},"components":{"type":"array","minItems":1,"maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponent"}},"data":{"type":"object","description":"The surface's data model: JSON its components bind to with `{path}` (at most 64 KB serialized; the store enforces the size).","additionalProperties":true}}}],"$defs":{"Actor":{"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}},"ActorKind":{"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]},"BodyBlock":{"type":"object","description":"One top-level block of a markdown body in the store's index (RFC-0014 §3). Written by the store only: a client's values are replaced on every write. `by`, `via` and `at` say who last changed the block's content, through which channel and when; they are absent for content written before the index existed.","additionalProperties":false,"required":["id","hash"],"properties":{"id":{"type":"string","pattern":"^blk_[0-9A-Za-z]{8,24}$","description":"Stable across writes that keep the block's content or its place among unchanged blocks."},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"SHA-256 (hex) of the block's markdown without trailing whitespace."},"by":{"$ref":"#/$defs/Actor"},"via":{"type":"string","enum":["studio","mcp","api","import","ai-command","proposal"],"description":"The channel of that change."},"at":{"$ref":"#/$defs/Timestamp"}}},"BodyBlocks":{"type":"array","description":"A markdown body's block index, in body order (RFC-0014 §3). Absent means not indexed (yet, or an HTML body), never empty.","maxItems":5000,"items":{"$ref":"#/$defs/BodyBlock"}},"BodyProfile":{"type":"integer","enum":[1],"description":"The markdown dialect a body follows (RFC-0014 §1): 1 = Cloudhal Markdown v1 (CommonMark + GFM + `asset:` + directives; raw HTML escaped). Set by the store on markdown bodies."},"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"ImageRef":{"type":"object","description":"Reference to an image. With `assetId` the image is an Asset node (RFC-0004): the store writes its media URL into `src`, and its size into `width` and `height`, as a fallback for readers that do not resolve assets. Without it, `src` is a relative path or an absolute URL, as before.","additionalProperties":false,"required":["src","alt"],"properties":{"assetId":{"$ref":"#/$defs/Id","description":"The Asset node shown (RFC-0004). Renderers that know assets resolve it and ignore `src`."},"src":{"type":"string","minLength":1,"maxLength":2048},"alt":{"type":"string","maxLength":500},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1}}},"Link":{"type":"object","additionalProperties":false,"required":["label","href"],"properties":{"label":{"type":"string","minLength":1,"maxLength":200},"href":{"type":"string","minLength":1,"maxLength":2048},"external":{"type":"boolean"}}},"Slug":{"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128},"SurfaceComponent":{"type":"object","description":"One element of a generative UI surface (RFC-0020 §1), in a flat list: `component` names a catalog entry (a PascalCase built-in primitive such as `Stack`, or a registry block type such as `callout`), `children` refer to other ids of the same surface, and `props` follow the entry's schema, which the store checks. Field names follow A2UI v0.9.","additionalProperties":false,"required":["id","component"],"properties":{"id":{"$ref":"#/$defs/SurfaceComponentId"},"component":{"type":"string","pattern":"^(?:[A-Z][A-Za-z0-9]{1,40}|[a-z][a-z0-9-]{1,40})$"},"children":{"type":"array","maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponentId"}},"props":{"type":"object","description":"Literal values, `{path}` bindings into the surface's data, or `{call}` named functions; validated by the store against the catalog, not by this contract.","additionalProperties":true},"visible":{"type":"object","description":"A visibility condition over the surface's data (`eq`, `ne`, `gt`, `lt`, `and`, `or`, `not`); validated by the store.","additionalProperties":true},"motion":{"type":"object","description":"Motion presets for this component (RFC-0024 §2): `reveal`, `hover`, `speed`, `delay`, `stagger`, `replay` and later `scroll`, `loop`; enums and clamped numbers only, validated by the store against the catalog. The renderer turns them into CSS; agents never write CSS.","additionalProperties":true}}},"SurfaceComponentId":{"type":"string","description":"A component's id, unique within its surface; `root` is the tree's root (RFC-0020 §1).","pattern":"^[a-z][a-z0-9-]{0,40}$"},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}}}; +const schema146 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; +const schema149 = {"type":"object","additionalProperties":false,"required":["label","href"],"properties":{"label":{"type":"string","minLength":1,"maxLength":200},"href":{"type":"string","minLength":1,"maxLength":2048},"external":{"type":"boolean"}}}; +const schema151 = {"type":"integer","enum":[1],"description":"The markdown dialect a body follows (RFC-0014 §1): 1 = Cloudhal Markdown v1 (CommonMark + GFM + `asset:` + directives; raw HTML escaped). Set by the store on markdown bodies."}; +const schema160 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128}; +const schema147 = {"type":"object","description":"Reference to an image. With `assetId` the image is an Asset node (RFC-0004): the store writes its media URL into `src`, and its size into `width` and `height`, as a fallback for readers that do not resolve assets. Without it, `src` is a relative path or an absolute URL, as before.","additionalProperties":false,"required":["src","alt"],"properties":{"assetId":{"$ref":"#/$defs/Id","description":"The Asset node shown (RFC-0004). Renderers that know assets resolve it and ignore `src`."},"src":{"type":"string","minLength":1,"maxLength":2048},"alt":{"type":"string","maxLength":500},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1}}}; function validate76(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -9918,11 +10043,11 @@ return errors === 0; } validate76.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; -const schema150 = {"type":"array","description":"A markdown body's block index, in body order (RFC-0014 §3). Absent means not indexed (yet, or an HTML body), never empty.","maxItems":5000,"items":{"$ref":"#/$defs/BodyBlock"}}; -const schema151 = {"type":"object","description":"One top-level block of a markdown body in the store's index (RFC-0014 §3). Written by the store only: a client's values are replaced on every write. `by`, `via` and `at` say who last changed the block's content, through which channel and when; they are absent for content written before the index existed.","additionalProperties":false,"required":["id","hash"],"properties":{"id":{"type":"string","pattern":"^blk_[0-9A-Za-z]{8,24}$","description":"Stable across writes that keep the block's content or its place among unchanged blocks."},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"SHA-256 (hex) of the block's markdown without trailing whitespace."},"by":{"$ref":"#/$defs/Actor"},"via":{"type":"string","enum":["studio","mcp","api","import","ai-command","proposal"],"description":"The channel of that change."},"at":{"$ref":"#/$defs/Timestamp"}}}; -const schema155 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; -const schema152 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}}; -const schema153 = {"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]}; +const schema152 = {"type":"array","description":"A markdown body's block index, in body order (RFC-0014 §3). Absent means not indexed (yet, or an HTML body), never empty.","maxItems":5000,"items":{"$ref":"#/$defs/BodyBlock"}}; +const schema153 = {"type":"object","description":"One top-level block of a markdown body in the store's index (RFC-0014 §3). Written by the store only: a client's values are replaced on every write. `by`, `via` and `at` say who last changed the block's content, through which channel and when; they are absent for content written before the index existed.","additionalProperties":false,"required":["id","hash"],"properties":{"id":{"type":"string","pattern":"^blk_[0-9A-Za-z]{8,24}$","description":"Stable across writes that keep the block's content or its place among unchanged blocks."},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"SHA-256 (hex) of the block's markdown without trailing whitespace."},"by":{"$ref":"#/$defs/Actor"},"via":{"type":"string","enum":["studio","mcp","api","import","ai-command","proposal"],"description":"The channel of that change."},"at":{"$ref":"#/$defs/Timestamp"}}}; +const schema157 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; +const schema154 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}}; +const schema155 = {"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]}; function validate80(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -9980,7 +10105,7 @@ vErrors.push(err3); errors++; } if(!(((data0 === "human") || (data0 === "agent")) || (data0 === "system"))){ -const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/$defs/ActorKind/enum",keyword:"enum",params:{allowedValues: schema153.enum},message:"must be equal to one of the allowed values"}; +const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/$defs/ActorKind/enum",keyword:"enum",params:{allowedValues: schema155.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err4]; } @@ -10123,7 +10248,7 @@ errors++; if(data.id !== undefined){ let data0 = data.id; if(typeof data0 === "string"){ -if(!pattern26.test(data0)){ +if(!pattern28.test(data0)){ const err3 = {instancePath:instancePath+"/id",schemaPath:"#/properties/id/pattern",keyword:"pattern",params:{pattern: "^blk_[0-9A-Za-z]{8,24}$"},message:"must match pattern \""+"^blk_[0-9A-Za-z]{8,24}$"+"\""}; if(vErrors === null){ vErrors = [err3]; @@ -10148,7 +10273,7 @@ errors++; if(data.hash !== undefined){ let data1 = data.hash; if(typeof data1 === "string"){ -if(!pattern27.test(data1)){ +if(!pattern29.test(data1)){ const err5 = {instancePath:instancePath+"/hash",schemaPath:"#/properties/hash/pattern",keyword:"pattern",params:{pattern: "^[0-9a-f]{64}$"},message:"must match pattern \""+"^[0-9a-f]{64}$"+"\""}; if(vErrors === null){ vErrors = [err5]; @@ -10189,7 +10314,7 @@ vErrors.push(err7); errors++; } if(!((((((data3 === "studio") || (data3 === "mcp")) || (data3 === "api")) || (data3 === "import")) || (data3 === "ai-command")) || (data3 === "proposal"))){ -const err8 = {instancePath:instancePath+"/via",schemaPath:"#/properties/via/enum",keyword:"enum",params:{allowedValues: schema151.properties.via.enum},message:"must be equal to one of the allowed values"}; +const err8 = {instancePath:instancePath+"/via",schemaPath:"#/properties/via/enum",keyword:"enum",params:{allowedValues: schema153.properties.via.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err8]; } @@ -10285,8 +10410,8 @@ return errors === 0; } validate78.evaluated = {"items":true,"dynamicProps":false,"dynamicItems":false}; -const schema161 = {"type":"object","description":"One element of a generative UI surface (RFC-0020 §1), in a flat list: `component` names a catalog entry (a PascalCase built-in primitive such as `Stack`, or a registry block type such as `callout`), `children` refer to other ids of the same surface, and `props` follow the entry's schema, which the store checks. Field names follow A2UI v0.9.","additionalProperties":false,"required":["id","component"],"properties":{"id":{"$ref":"#/$defs/SurfaceComponentId"},"component":{"type":"string","pattern":"^(?:[A-Z][A-Za-z0-9]{1,40}|[a-z][a-z0-9-]{1,40})$"},"children":{"type":"array","maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponentId"}},"props":{"type":"object","description":"Literal values, `{path}` bindings into the surface's data, or `{call}` named functions; validated by the store against the catalog, not by this contract.","additionalProperties":true},"visible":{"type":"object","description":"A visibility condition over the surface's data (`eq`, `ne`, `gt`, `lt`, `and`, `or`, `not`); validated by the store.","additionalProperties":true},"motion":{"type":"object","description":"Motion presets for this component (RFC-0024 §2): `reveal`, `hover`, `speed`, `delay`, `stagger`, `replay` and later `scroll`, `loop`; enums and clamped numbers only, validated by the store against the catalog. The renderer turns them into CSS; agents never write CSS.","additionalProperties":true}}}; -const schema162 = {"type":"string","description":"A component's id, unique within its surface; `root` is the tree's root (RFC-0020 §1).","pattern":"^[a-z][a-z0-9-]{0,40}$"}; +const schema163 = {"type":"object","description":"One element of a generative UI surface (RFC-0020 §1), in a flat list: `component` names a catalog entry (a PascalCase built-in primitive such as `Stack`, or a registry block type such as `callout`), `children` refer to other ids of the same surface, and `props` follow the entry's schema, which the store checks. Field names follow A2UI v0.9.","additionalProperties":false,"required":["id","component"],"properties":{"id":{"$ref":"#/$defs/SurfaceComponentId"},"component":{"type":"string","pattern":"^(?:[A-Z][A-Za-z0-9]{1,40}|[a-z][a-z0-9-]{1,40})$"},"children":{"type":"array","maxItems":200,"items":{"$ref":"#/$defs/SurfaceComponentId"}},"props":{"type":"object","description":"Literal values, `{path}` bindings into the surface's data, or `{call}` named functions; validated by the store against the catalog, not by this contract.","additionalProperties":true},"visible":{"type":"object","description":"A visibility condition over the surface's data (`eq`, `ne`, `gt`, `lt`, `and`, `or`, `not`); validated by the store.","additionalProperties":true},"motion":{"type":"object","description":"Motion presets for this component (RFC-0024 §2): `reveal`, `hover`, `speed`, `delay`, `stagger`, `replay` and later `scroll`, `loop`; enums and clamped numbers only, validated by the store against the catalog. The renderer turns them into CSS; agents never write CSS.","additionalProperties":true}}}; +const schema164 = {"type":"string","description":"A component's id, unique within its surface; `root` is the tree's root (RFC-0020 §1).","pattern":"^[a-z][a-z0-9-]{0,40}$"}; function validate85(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -10334,7 +10459,7 @@ errors++; if(data.id !== undefined){ let data0 = data.id; if(typeof data0 === "string"){ -if(!pattern36.test(data0)){ +if(!pattern38.test(data0)){ const err3 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/SurfaceComponentId/pattern",keyword:"pattern",params:{pattern: "^[a-z][a-z0-9-]{0,40}$"},message:"must match pattern \""+"^[a-z][a-z0-9-]{0,40}$"+"\""}; if(vErrors === null){ vErrors = [err3]; @@ -10359,7 +10484,7 @@ errors++; if(data.component !== undefined){ let data1 = data.component; if(typeof data1 === "string"){ -if(!pattern37.test(data1)){ +if(!pattern39.test(data1)){ const err5 = {instancePath:instancePath+"/component",schemaPath:"#/properties/component/pattern",keyword:"pattern",params:{pattern: "^(?:[A-Z][A-Za-z0-9]{1,40}|[a-z][a-z0-9-]{1,40})$"},message:"must match pattern \""+"^(?:[A-Z][A-Za-z0-9]{1,40}|[a-z][a-z0-9-]{1,40})$"+"\""}; if(vErrors === null){ vErrors = [err5]; @@ -10398,7 +10523,7 @@ const len0 = data2.length; for(let i0=0; i0//rss.xml. Requires archive.enabled."},"minEntriesToIndex":{"type":"integer","description":"Archives listing fewer published entries than this are served with robots 'noindex, follow' and left out of the sitemap.","minimum":0,"maximum":1000},"rev":{"$ref":"#/$defs/Revision"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"metadata":{"$ref":"#/$defs/Metadata"}},"$defs":{"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"Locale":{"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"},"Metadata":{"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}},"Path":{"type":"string","description":"Absolute URL path of a route the site owns: starts with '/', lower-case slug segments, no trailing slash except root.","pattern":"^/(?:[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*)?$","maxLength":1024},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Slug":{"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128},"TaxonomyArchive":{"type":"object","description":"Public archive pages: / and //page/.","additionalProperties":false,"required":["enabled","basePath"],"properties":{"enabled":{"type":"boolean"},"basePath":{"$ref":"#/$defs/Path"}}},"TaxonomyLabel":{"type":"object","additionalProperties":false,"required":["singular","plural"],"properties":{"singular":{"type":"string","minLength":1,"maxLength":100},"plural":{"type":"string","minLength":1,"maxLength":100},"description":{"type":"string","maxLength":1000}}},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}}}; -const schema227 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; -const schema228 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; -const schema230 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128}; -const schema231 = {"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"}; -const schema232 = {"type":"object","additionalProperties":false,"required":["singular","plural"],"properties":{"singular":{"type":"string","minLength":1,"maxLength":100},"plural":{"type":"string","minLength":1,"maxLength":100},"description":{"type":"string","maxLength":1000}}}; -const schema236 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; -const schema237 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; -const schema239 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}}; -const schema234 = {"type":"object","description":"Public archive pages: / and //page/.","additionalProperties":false,"required":["enabled","basePath"],"properties":{"enabled":{"type":"boolean"},"basePath":{"$ref":"#/$defs/Path"}}}; -const schema235 = {"type":"string","description":"Absolute URL path of a route the site owns: starts with '/', lower-case slug segments, no trailing slash except root.","pattern":"^/(?:[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*)?$","maxLength":1024}; +const schema228 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/taxonomy.schema.json","title":"Taxonomy","description":"A classification scheme of a site (e.g. 'category', 'tag', 'series') and its rules: hierarchy, who may create terms, how many terms an entry takes, and how its archives are published. Terms are separate nodes (term.schema.json) that point here by id (RFC-0002).","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","key","labels","hierarchical","allowNewTerms","required","archive","feed","minEntriesToIndex","rev","createdAt","updatedAt"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/Id"},"siteId":{"$ref":"#/$defs/Id"},"key":{"$ref":"#/$defs/Slug","description":"Stable name of the taxonomy, unique per site and immutable once created. ContentEntry.terms and filter expressions use it (e.g. 'category:news')."},"labels":{"type":"object","description":"Display names per locale. Must contain the site's default locale.","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"$ref":"#/$defs/TaxonomyLabel"}},"hierarchical":{"type":"boolean","description":"Terms may have a parent term (categories). Flat when false (tags)."},"allowNewTerms":{"type":"string","description":"Who may add terms. none: people only. propose: agents may create terms with status 'proposed' that wait for a person. free: agents may create active terms.","enum":["none","propose","free"]},"required":{"type":"boolean","description":"An entry in review or published status must have at least one term of this taxonomy and an explicit primary term."},"maxPerEntry":{"type":"integer","description":"Most terms of this taxonomy one entry may carry. Absent: the contract limit of 50.","minimum":1,"maximum":50},"collections":{"type":"array","description":"Collections this taxonomy applies to. Absent: every collection.","items":{"$ref":"#/$defs/Slug"},"minItems":1,"maxItems":50,"uniqueItems":true},"archive":{"$ref":"#/$defs/TaxonomyArchive"},"feed":{"type":"boolean","description":"Publish an RSS feed per term at //rss.xml. Requires archive.enabled."},"minEntriesToIndex":{"type":"integer","description":"Archives listing fewer published entries than this are served with robots 'noindex, follow' and left out of the sitemap.","minimum":0,"maximum":1000},"rev":{"$ref":"#/$defs/Revision"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"metadata":{"$ref":"#/$defs/Metadata"}},"$defs":{"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"Locale":{"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"},"Metadata":{"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}},"Path":{"type":"string","description":"Absolute URL path of a route the site owns: starts with '/', lower-case slug segments, no trailing slash except root.","pattern":"^/(?:[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*)?$","maxLength":1024},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Slug":{"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128},"TaxonomyArchive":{"type":"object","description":"Public archive pages: / and //page/.","additionalProperties":false,"required":["enabled","basePath"],"properties":{"enabled":{"type":"boolean"},"basePath":{"$ref":"#/$defs/Path"}}},"TaxonomyLabel":{"type":"object","additionalProperties":false,"required":["singular","plural"],"properties":{"singular":{"type":"string","minLength":1,"maxLength":100},"plural":{"type":"string","minLength":1,"maxLength":100},"description":{"type":"string","maxLength":1000}}},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}}}; +const schema229 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; +const schema230 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; +const schema232 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128}; +const schema233 = {"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"}; +const schema234 = {"type":"object","additionalProperties":false,"required":["singular","plural"],"properties":{"singular":{"type":"string","minLength":1,"maxLength":100},"plural":{"type":"string","minLength":1,"maxLength":100},"description":{"type":"string","maxLength":1000}}}; +const schema238 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; +const schema239 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; +const schema241 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}}; +const schema236 = {"type":"object","description":"Public archive pages: / and //page/.","additionalProperties":false,"required":["enabled","basePath"],"properties":{"enabled":{"type":"boolean"},"basePath":{"$ref":"#/$defs/Path"}}}; +const schema237 = {"type":"string","description":"Absolute URL path of a route the site owns: starts with '/', lower-case slug segments, no trailing slash except root.","pattern":"^/(?:[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*)?$","maxLength":1024}; function validate115(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -16093,7 +16218,7 @@ vErrors.push(err4); } errors++; } -if(!pattern40.test(data1)){ +if(!pattern42.test(data1)){ const err5 = {instancePath:instancePath+"/basePath",schemaPath:"#/$defs/Path/pattern",keyword:"pattern",params:{pattern: "^/(?:[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*)?$"},message:"must match pattern \""+"^/(?:[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*)?$"+"\""}; if(vErrors === null){ vErrors = [err5]; @@ -16285,7 +16410,7 @@ vErrors.push(err13); errors++; } for(const key0 in data){ -if(!(func1.call(schema226.properties, key0))){ +if(!(func1.call(schema228.properties, key0))){ const err14 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}; if(vErrors === null){ vErrors = [err14]; @@ -16309,7 +16434,7 @@ vErrors.push(err15); errors++; } if(!(data0 === 1)){ -const err16 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema227.enum},message:"must be equal to one of the allowed values"}; +const err16 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema229.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err16]; } @@ -16682,7 +16807,7 @@ vErrors.push(err47); errors++; } if(!(((data10 === "none") || (data10 === "propose")) || (data10 === "free"))){ -const err48 = {instancePath:instancePath+"/allowNewTerms",schemaPath:"#/properties/allowNewTerms/enum",keyword:"enum",params:{allowedValues: schema226.properties.allowNewTerms.enum},message:"must be equal to one of the allowed values"}; +const err48 = {instancePath:instancePath+"/allowNewTerms",schemaPath:"#/properties/allowNewTerms/enum",keyword:"enum",params:{allowedValues: schema228.properties.allowNewTerms.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err48]; } @@ -17012,18 +17137,18 @@ return errors === 0; validate114.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; export const validateTerm = validate117; -const schema240 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/term.schema.json","title":"Term","description":"One concept in a taxonomy (a category, a tag, a series). Content refers to terms by id, independent of locale; names, synonyms, archive intro and SEO are per locale. Renames, merges and retirements keep the id and leave redirects behind; `proposedBy` records the actor that proposed it (RFC-0002).","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","taxonomyId","slug","status","visibility","labels","rev","createdAt","updatedAt"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/Id"},"siteId":{"$ref":"#/$defs/Id"},"taxonomyId":{"$ref":"#/$defs/Id"},"slug":{"$ref":"#/$defs/Slug","description":"URL segment of the term's archive; unique within its taxonomy, together with every slug in any term's slugHistory."},"status":{"type":"string","description":"active: usable and public (subject to visibility). proposed: created by an agent, waiting for a person; assignable but never public. retired: no new assignments, no archive.","enum":["active","proposed","retired"]},"visibility":{"type":"string","description":"internal terms have no archive, feed or sitemap entry and are not shown on entries; themes and filters may still use them.","enum":["public","internal"]},"labels":{"type":"object","description":"Names per locale. Must contain the site's default locale.","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"$ref":"#/$defs/TermLabel"}},"parentId":{"$ref":"#/$defs/Id","description":"Parent term in the same taxonomy; only in hierarchical taxonomies. No cycles."},"position":{"type":"integer","description":"Order among sibling terms (same taxonomy and parent), lowest first, for menus and sidebars. Terms without a position follow, by name.","minimum":0,"maximum":100000},"image":{"$ref":"#/$defs/ImageRef"},"links":{"type":"array","description":"Profile links shown with the term and given to search engines as sameAs, such as an author's pages elsewhere (RFC-0019 §1).","maxItems":10,"items":{"$ref":"#/$defs/Link"}},"slugHistory":{"type":"array","description":"Earlier slugs, oldest first. Old slugs stay reserved in the taxonomy and still resolve when an agent assigns by slug.","items":{"$ref":"#/$defs/Slug"},"minItems":1,"maxItems":50,"uniqueItems":true},"mergedInto":{"$ref":"#/$defs/Id","description":"Set when this term was merged into another active term of the same taxonomy; status is then 'retired'."},"proposedBy":{"$ref":"#/$defs/Actor"},"rev":{"$ref":"#/$defs/Revision"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"metadata":{"$ref":"#/$defs/Metadata"}},"$defs":{"Actor":{"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}},"ActorKind":{"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]},"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"ImageRef":{"type":"object","description":"Reference to an image. With `assetId` the image is an Asset node (RFC-0004): the store writes its media URL into `src`, and its size into `width` and `height`, as a fallback for readers that do not resolve assets. Without it, `src` is a relative path or an absolute URL, as before.","additionalProperties":false,"required":["src","alt"],"properties":{"assetId":{"$ref":"#/$defs/Id","description":"The Asset node shown (RFC-0004). Renderers that know assets resolve it and ignore `src`."},"src":{"type":"string","minLength":1,"maxLength":2048},"alt":{"type":"string","maxLength":500},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1}}},"Link":{"type":"object","additionalProperties":false,"required":["label","href"],"properties":{"label":{"type":"string","minLength":1,"maxLength":200},"href":{"type":"string","minLength":1,"maxLength":2048},"external":{"type":"boolean"}}},"Locale":{"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"},"Metadata":{"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Seo":{"type":"object","description":"Search and social metadata of a routable node (page, term archive).","additionalProperties":false,"properties":{"title":{"type":"string","maxLength":300},"description":{"type":"string","maxLength":500},"noindex":{"type":"boolean"},"canonical":{"type":"string","maxLength":2048},"ogImage":{"$ref":"#/$defs/ImageRef"}}},"Slug":{"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128},"TermLabel":{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200},"slug":{"$ref":"#/$defs/Slug","description":"This locale's archive slug (RFC-0033); absent uses the term's slug. Never set for the site's default locale, whose slug is the term's own; unique per taxonomy and locale."},"altNames":{"type":"array","description":"Synonyms (SKOS altLabel). Matched when an agent or editor assigns by name; never shown as separate terms.","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":50,"uniqueItems":true},"description":{"type":"string","description":"Markdown introduction shown at the top of the archive.","maxLength":20000},"role":{"type":"string","description":"A short role or title shown with the term, such as an author's \"Senior reporter\" (RFC-0019 §1).","minLength":1,"maxLength":120},"seo":{"$ref":"#/$defs/Seo"}}},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}}}; -const schema241 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; -const schema242 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; -const schema245 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128}; -const schema246 = {"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"}; -const schema253 = {"type":"object","additionalProperties":false,"required":["label","href"],"properties":{"label":{"type":"string","minLength":1,"maxLength":200},"href":{"type":"string","minLength":1,"maxLength":2048},"external":{"type":"boolean"}}}; -const schema259 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; -const schema260 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; -const schema262 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}}; -const schema247 = {"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200},"slug":{"$ref":"#/$defs/Slug","description":"This locale's archive slug (RFC-0033); absent uses the term's slug. Never set for the site's default locale, whose slug is the term's own; unique per taxonomy and locale."},"altNames":{"type":"array","description":"Synonyms (SKOS altLabel). Matched when an agent or editor assigns by name; never shown as separate terms.","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":50,"uniqueItems":true},"description":{"type":"string","description":"Markdown introduction shown at the top of the archive.","maxLength":20000},"role":{"type":"string","description":"A short role or title shown with the term, such as an author's \"Senior reporter\" (RFC-0019 §1).","minLength":1,"maxLength":120},"seo":{"$ref":"#/$defs/Seo"}}}; -const schema249 = {"type":"object","description":"Search and social metadata of a routable node (page, term archive).","additionalProperties":false,"properties":{"title":{"type":"string","maxLength":300},"description":{"type":"string","maxLength":500},"noindex":{"type":"boolean"},"canonical":{"type":"string","maxLength":2048},"ogImage":{"$ref":"#/$defs/ImageRef"}}}; -const schema250 = {"type":"object","description":"Reference to an image. With `assetId` the image is an Asset node (RFC-0004): the store writes its media URL into `src`, and its size into `width` and `height`, as a fallback for readers that do not resolve assets. Without it, `src` is a relative path or an absolute URL, as before.","additionalProperties":false,"required":["src","alt"],"properties":{"assetId":{"$ref":"#/$defs/Id","description":"The Asset node shown (RFC-0004). Renderers that know assets resolve it and ignore `src`."},"src":{"type":"string","minLength":1,"maxLength":2048},"alt":{"type":"string","maxLength":500},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1}}}; +const schema242 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/term.schema.json","title":"Term","description":"One concept in a taxonomy (a category, a tag, a series). Content refers to terms by id, independent of locale; names, synonyms, archive intro and SEO are per locale. Renames, merges and retirements keep the id and leave redirects behind; `proposedBy` records the actor that proposed it (RFC-0002).","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","taxonomyId","slug","status","visibility","labels","rev","createdAt","updatedAt"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/Id"},"siteId":{"$ref":"#/$defs/Id"},"taxonomyId":{"$ref":"#/$defs/Id"},"slug":{"$ref":"#/$defs/Slug","description":"URL segment of the term's archive; unique within its taxonomy, together with every slug in any term's slugHistory."},"status":{"type":"string","description":"active: usable and public (subject to visibility). proposed: created by an agent, waiting for a person; assignable but never public. retired: no new assignments, no archive.","enum":["active","proposed","retired"]},"visibility":{"type":"string","description":"internal terms have no archive, feed or sitemap entry and are not shown on entries; themes and filters may still use them.","enum":["public","internal"]},"labels":{"type":"object","description":"Names per locale. Must contain the site's default locale.","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"$ref":"#/$defs/TermLabel"}},"parentId":{"$ref":"#/$defs/Id","description":"Parent term in the same taxonomy; only in hierarchical taxonomies. No cycles."},"position":{"type":"integer","description":"Order among sibling terms (same taxonomy and parent), lowest first, for menus and sidebars. Terms without a position follow, by name.","minimum":0,"maximum":100000},"image":{"$ref":"#/$defs/ImageRef"},"links":{"type":"array","description":"Profile links shown with the term and given to search engines as sameAs, such as an author's pages elsewhere (RFC-0019 §1).","maxItems":10,"items":{"$ref":"#/$defs/Link"}},"slugHistory":{"type":"array","description":"Earlier slugs, oldest first. Old slugs stay reserved in the taxonomy and still resolve when an agent assigns by slug.","items":{"$ref":"#/$defs/Slug"},"minItems":1,"maxItems":50,"uniqueItems":true},"mergedInto":{"$ref":"#/$defs/Id","description":"Set when this term was merged into another active term of the same taxonomy; status is then 'retired'."},"proposedBy":{"$ref":"#/$defs/Actor"},"rev":{"$ref":"#/$defs/Revision"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"metadata":{"$ref":"#/$defs/Metadata"}},"$defs":{"Actor":{"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}},"ActorKind":{"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]},"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"ImageRef":{"type":"object","description":"Reference to an image. With `assetId` the image is an Asset node (RFC-0004): the store writes its media URL into `src`, and its size into `width` and `height`, as a fallback for readers that do not resolve assets. Without it, `src` is a relative path or an absolute URL, as before.","additionalProperties":false,"required":["src","alt"],"properties":{"assetId":{"$ref":"#/$defs/Id","description":"The Asset node shown (RFC-0004). Renderers that know assets resolve it and ignore `src`."},"src":{"type":"string","minLength":1,"maxLength":2048},"alt":{"type":"string","maxLength":500},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1}}},"Link":{"type":"object","additionalProperties":false,"required":["label","href"],"properties":{"label":{"type":"string","minLength":1,"maxLength":200},"href":{"type":"string","minLength":1,"maxLength":2048},"external":{"type":"boolean"}}},"Locale":{"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"},"Metadata":{"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Seo":{"type":"object","description":"Search and social metadata of a routable node (page, term archive).","additionalProperties":false,"properties":{"title":{"type":"string","maxLength":300},"description":{"type":"string","maxLength":500},"noindex":{"type":"boolean"},"canonical":{"type":"string","maxLength":2048},"ogImage":{"$ref":"#/$defs/ImageRef"}}},"Slug":{"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128},"TermLabel":{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200},"slug":{"$ref":"#/$defs/Slug","description":"This locale's archive slug (RFC-0033); absent uses the term's slug. Never set for the site's default locale, whose slug is the term's own; unique per taxonomy and locale."},"altNames":{"type":"array","description":"Synonyms (SKOS altLabel). Matched when an agent or editor assigns by name; never shown as separate terms.","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":50,"uniqueItems":true},"description":{"type":"string","description":"Markdown introduction shown at the top of the archive.","maxLength":20000},"role":{"type":"string","description":"A short role or title shown with the term, such as an author's \"Senior reporter\" (RFC-0019 §1).","minLength":1,"maxLength":120},"seo":{"$ref":"#/$defs/Seo"}}},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}}}; +const schema243 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; +const schema244 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; +const schema247 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128}; +const schema248 = {"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"}; +const schema255 = {"type":"object","additionalProperties":false,"required":["label","href"],"properties":{"label":{"type":"string","minLength":1,"maxLength":200},"href":{"type":"string","minLength":1,"maxLength":2048},"external":{"type":"boolean"}}}; +const schema261 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; +const schema262 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; +const schema264 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}}; +const schema249 = {"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200},"slug":{"$ref":"#/$defs/Slug","description":"This locale's archive slug (RFC-0033); absent uses the term's slug. Never set for the site's default locale, whose slug is the term's own; unique per taxonomy and locale."},"altNames":{"type":"array","description":"Synonyms (SKOS altLabel). Matched when an agent or editor assigns by name; never shown as separate terms.","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":50,"uniqueItems":true},"description":{"type":"string","description":"Markdown introduction shown at the top of the archive.","maxLength":20000},"role":{"type":"string","description":"A short role or title shown with the term, such as an author's \"Senior reporter\" (RFC-0019 §1).","minLength":1,"maxLength":120},"seo":{"$ref":"#/$defs/Seo"}}}; +const schema251 = {"type":"object","description":"Search and social metadata of a routable node (page, term archive).","additionalProperties":false,"properties":{"title":{"type":"string","maxLength":300},"description":{"type":"string","maxLength":500},"noindex":{"type":"boolean"},"canonical":{"type":"string","maxLength":2048},"ogImage":{"$ref":"#/$defs/ImageRef"}}}; +const schema252 = {"type":"object","description":"Reference to an image. With `assetId` the image is an Asset node (RFC-0004): the store writes its media URL into `src`, and its size into `width` and `height`, as a fallback for readers that do not resolve assets. Without it, `src` is a relative path or an absolute URL, as before.","additionalProperties":false,"required":["src","alt"],"properties":{"assetId":{"$ref":"#/$defs/Id","description":"The Asset node shown (RFC-0004). Renderers that know assets resolve it and ignore `src`."},"src":{"type":"string","minLength":1,"maxLength":2048},"alt":{"type":"string","maxLength":500},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1}}}; function validate120(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -17663,8 +17788,8 @@ return errors === 0; } validate118.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; -const schema256 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}}; -const schema257 = {"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]}; +const schema258 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}}; +const schema259 = {"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]}; function validate125(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -17722,7 +17847,7 @@ vErrors.push(err3); errors++; } if(!(((data0 === "human") || (data0 === "agent")) || (data0 === "system"))){ -const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/$defs/ActorKind/enum",keyword:"enum",params:{allowedValues: schema257.enum},message:"must be equal to one of the allowed values"}; +const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/$defs/ActorKind/enum",keyword:"enum",params:{allowedValues: schema259.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err4]; } @@ -17942,7 +18067,7 @@ vErrors.push(err10); errors++; } for(const key0 in data){ -if(!(func1.call(schema240.properties, key0))){ +if(!(func1.call(schema242.properties, key0))){ const err11 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}; if(vErrors === null){ vErrors = [err11]; @@ -17966,7 +18091,7 @@ vErrors.push(err12); errors++; } if(!(data0 === 1)){ -const err13 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema241.enum},message:"must be equal to one of the allowed values"}; +const err13 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema243.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err13]; } @@ -18169,7 +18294,7 @@ vErrors.push(err30); errors++; } if(!(((data5 === "active") || (data5 === "proposed")) || (data5 === "retired"))){ -const err31 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema240.properties.status.enum},message:"must be equal to one of the allowed values"}; +const err31 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema242.properties.status.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err31]; } @@ -18192,7 +18317,7 @@ vErrors.push(err32); errors++; } if(!((data6 === "public") || (data6 === "internal"))){ -const err33 = {instancePath:instancePath+"/visibility",schemaPath:"#/properties/visibility/enum",keyword:"enum",params:{allowedValues: schema240.properties.visibility.enum},message:"must be equal to one of the allowed values"}; +const err33 = {instancePath:instancePath+"/visibility",schemaPath:"#/properties/visibility/enum",keyword:"enum",params:{allowedValues: schema242.properties.visibility.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err33]; } @@ -18781,16 +18906,16 @@ return errors === 0; validate117.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; export const validateRedirect = validate127; -const schema263 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/redirect.schema.json","title":"Redirect","description":"An HTTP redirect from a path the site no longer serves. Created automatically when a published entry or a term changes slug, when terms are merged or retired, and by people. Snapshotted and published like every other node (RFC-0002).","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","from","to","status","match","reason","rev","createdAt","updatedAt"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/Id"},"siteId":{"$ref":"#/$defs/Id"},"from":{"type":"string","description":"Request path to redirect: starts with '/', no query, fragment, backslash or trailing slash (root excepted). Unique per site and locale; never a path the site currently serves.","pattern":"^/(?:[^\\s?#/\\\\]+(?:/[^\\s?#/\\\\]+)*)?$","maxLength":1024},"locale":{"$ref":"#/$defs/Locale","description":"The locale whose URLs `from` belongs to (RFC-0033); absent means the site's default locale. `from` is the path inside that locale, without its prefix or domain. Unique per site and locale."},"to":{"type":"string","description":"Target: a site path (may carry a query or fragment) or an absolute https URL. Protocol-relative ('//host') and backslash forms are rejected.","pattern":"^(?:/(?![/\\\\])[^\\s\\\\]*|https://[^\\s\\\\/?#]+(?:[/?#][^\\s\\\\]*)?)$","maxLength":2048},"status":{"type":"integer","description":"301 (permanent) for slug changes, merges and retirements; 302 (temporary) only for manual redirects.","enum":[301,302]},"match":{"type":"string","description":"exact: only `from` itself. prefix: `from` and every path below it; the rest of the path is appended to `to` (archives: /tag/old/page/2 → /tag/new/page/2).","enum":["exact","prefix"]},"reason":{"type":"string","enum":["slug-change","merge","retire","manual"]},"source":{"$ref":"#/$defs/RedirectSource"},"note":{"type":"string","maxLength":500},"rev":{"$ref":"#/$defs/Revision"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"metadata":{"$ref":"#/$defs/Metadata"}},"$defs":{"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"Locale":{"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"},"Metadata":{"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}},"RedirectSource":{"type":"object","description":"The node whose earlier URL this redirect preserves (automatic redirects). Used to collapse chains on later renames and to list a node's old URLs.","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"type":"string","enum":["page","contentEntry","term"]},"id":{"$ref":"#/$defs/Id"}}},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}}}; -const schema264 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; -const schema265 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; -const schema267 = {"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"}; -const schema270 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; -const schema271 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; -const schema273 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}}; -const pattern130 = new RegExp("^/(?:[^\\s?#/\\\\]+(?:/[^\\s?#/\\\\]+)*)?$", "u"); -const pattern132 = new RegExp("^(?:/(?![/\\\\])[^\\s\\\\]*|https://[^\\s\\\\/?#]+(?:[/?#][^\\s\\\\]*)?)$", "u"); -const schema268 = {"type":"object","description":"The node whose earlier URL this redirect preserves (automatic redirects). Used to collapse chains on later renames and to list a node's old URLs.","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"type":"string","enum":["page","contentEntry","term"]},"id":{"$ref":"#/$defs/Id"}}}; +const schema265 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/redirect.schema.json","title":"Redirect","description":"An HTTP redirect from a path the site no longer serves. Created automatically when a published entry or a term changes slug, when terms are merged or retired, and by people. Snapshotted and published like every other node (RFC-0002).","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","from","to","status","match","reason","rev","createdAt","updatedAt"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/Id"},"siteId":{"$ref":"#/$defs/Id"},"from":{"type":"string","description":"Request path to redirect: starts with '/', no query, fragment, backslash or trailing slash (root excepted). Unique per site and locale; never a path the site currently serves.","pattern":"^/(?:[^\\s?#/\\\\]+(?:/[^\\s?#/\\\\]+)*)?$","maxLength":1024},"locale":{"$ref":"#/$defs/Locale","description":"The locale whose URLs `from` belongs to (RFC-0033); absent means the site's default locale. `from` is the path inside that locale, without its prefix or domain. Unique per site and locale."},"to":{"type":"string","description":"Target: a site path (may carry a query or fragment) or an absolute https URL. Protocol-relative ('//host') and backslash forms are rejected.","pattern":"^(?:/(?![/\\\\])[^\\s\\\\]*|https://[^\\s\\\\/?#]+(?:[/?#][^\\s\\\\]*)?)$","maxLength":2048},"status":{"type":"integer","description":"301 (permanent) for slug changes, merges and retirements; 302 (temporary) only for manual redirects.","enum":[301,302]},"match":{"type":"string","description":"exact: only `from` itself. prefix: `from` and every path below it; the rest of the path is appended to `to` (archives: /tag/old/page/2 → /tag/new/page/2).","enum":["exact","prefix"]},"reason":{"type":"string","enum":["slug-change","merge","retire","manual"]},"source":{"$ref":"#/$defs/RedirectSource"},"note":{"type":"string","maxLength":500},"rev":{"$ref":"#/$defs/Revision"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"metadata":{"$ref":"#/$defs/Metadata"}},"$defs":{"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"Locale":{"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"},"Metadata":{"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}},"RedirectSource":{"type":"object","description":"The node whose earlier URL this redirect preserves (automatic redirects). Used to collapse chains on later renames and to list a node's old URLs.","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"type":"string","enum":["page","contentEntry","term"]},"id":{"$ref":"#/$defs/Id"}}},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}}}; +const schema266 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; +const schema267 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; +const schema269 = {"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"}; +const schema272 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; +const schema273 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; +const schema275 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}}; +const pattern132 = new RegExp("^/(?:[^\\s?#/\\\\]+(?:/[^\\s?#/\\\\]+)*)?$", "u"); +const pattern134 = new RegExp("^(?:/(?![/\\\\])[^\\s\\\\]*|https://[^\\s\\\\/?#]+(?:[/?#][^\\s\\\\]*)?)$", "u"); +const schema270 = {"type":"object","description":"The node whose earlier URL this redirect preserves (automatic redirects). Used to collapse chains on later renames and to list a node's old URLs.","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"type":"string","enum":["page","contentEntry","term"]},"id":{"$ref":"#/$defs/Id"}}}; function validate128(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -18848,7 +18973,7 @@ vErrors.push(err3); errors++; } if(!(((data0 === "page") || (data0 === "contentEntry")) || (data0 === "term"))){ -const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/properties/kind/enum",keyword:"enum",params:{allowedValues: schema268.properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/properties/kind/enum",keyword:"enum",params:{allowedValues: schema270.properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err4]; } @@ -19043,7 +19168,7 @@ vErrors.push(err10); errors++; } for(const key0 in data){ -if(!(func1.call(schema263.properties, key0))){ +if(!(func1.call(schema265.properties, key0))){ const err11 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}; if(vErrors === null){ vErrors = [err11]; @@ -19067,7 +19192,7 @@ vErrors.push(err12); errors++; } if(!(data0 === 1)){ -const err13 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema264.enum},message:"must be equal to one of the allowed values"}; +const err13 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema266.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err13]; } @@ -19180,7 +19305,7 @@ vErrors.push(err22); } errors++; } -if(!pattern130.test(data3)){ +if(!pattern132.test(data3)){ const err23 = {instancePath:instancePath+"/from",schemaPath:"#/properties/from/pattern",keyword:"pattern",params:{pattern: "^/(?:[^\\s?#/\\\\]+(?:/[^\\s?#/\\\\]+)*)?$"},message:"must match pattern \""+"^/(?:[^\\s?#/\\\\]+(?:/[^\\s?#/\\\\]+)*)?$"+"\""}; if(vErrors === null){ vErrors = [err23]; @@ -19240,7 +19365,7 @@ vErrors.push(err27); } errors++; } -if(!pattern132.test(data5)){ +if(!pattern134.test(data5)){ const err28 = {instancePath:instancePath+"/to",schemaPath:"#/properties/to/pattern",keyword:"pattern",params:{pattern: "^(?:/(?![/\\\\])[^\\s\\\\]*|https://[^\\s\\\\/?#]+(?:[/?#][^\\s\\\\]*)?)$"},message:"must match pattern \""+"^(?:/(?![/\\\\])[^\\s\\\\]*|https://[^\\s\\\\/?#]+(?:[/?#][^\\s\\\\]*)?)$"+"\""}; if(vErrors === null){ vErrors = [err28]; @@ -19275,7 +19400,7 @@ vErrors.push(err30); errors++; } if(!((data6 === 301) || (data6 === 302))){ -const err31 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema263.properties.status.enum},message:"must be equal to one of the allowed values"}; +const err31 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema265.properties.status.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err31]; } @@ -19298,7 +19423,7 @@ vErrors.push(err32); errors++; } if(!((data7 === "exact") || (data7 === "prefix"))){ -const err33 = {instancePath:instancePath+"/match",schemaPath:"#/properties/match/enum",keyword:"enum",params:{allowedValues: schema263.properties.match.enum},message:"must be equal to one of the allowed values"}; +const err33 = {instancePath:instancePath+"/match",schemaPath:"#/properties/match/enum",keyword:"enum",params:{allowedValues: schema265.properties.match.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err33]; } @@ -19321,7 +19446,7 @@ vErrors.push(err34); errors++; } if(!((((data8 === "slug-change") || (data8 === "merge")) || (data8 === "retire")) || (data8 === "manual"))){ -const err35 = {instancePath:instancePath+"/reason",schemaPath:"#/properties/reason/enum",keyword:"enum",params:{allowedValues: schema263.properties.reason.enum},message:"must be equal to one of the allowed values"}; +const err35 = {instancePath:instancePath+"/reason",schemaPath:"#/properties/reason/enum",keyword:"enum",params:{allowedValues: schema265.properties.reason.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err35]; } @@ -19481,23 +19606,23 @@ return errors === 0; validate127.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; export const validateAsset = validate130; -const schema274 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/asset.schema.json","title":"Asset","description":"A media file of a site (image, video, audio, document, data) as a graph node: its bytes are identified by SHA-256 and stored once per site in the blob store under a content-addressed key, or kept elsewhere (`storage.kind: external`, e.g. a Zenodo or Hugging Face file). Pages, entries and terms refer to it by id (`ImageRef.assetId`, markdown `asset:`). Snapshotted like every node, so a deleted asset's bytes stay while any snapshot references it (RFC-0004).","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","kind","mediaType","bytes","sha256","slug","storage","status","uploadedBy","source","rev","createdAt","updatedAt"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/Id"},"siteId":{"$ref":"#/$defs/Id"},"kind":{"type":"string","description":"What the file is, derived from the sniffed media type (never chosen by the client). `other` is allowed for external storage only.","enum":["image","video","audio","document","data","other"]},"mediaType":{"type":"string","description":"MIME type of the stored bytes as sniffed by the server (RFC 6838, lower case, no parameters), e.g. image/webp, text/csv. Served as Content-Type.","pattern":"^[a-z0-9][a-z0-9!#$&^_.+-]{0,126}/[a-z0-9][a-z0-9!#$&^_.+-]{0,126}$","maxLength":255},"bytes":{"type":"integer","description":"Size of the stored bytes. For external files: the size the source reports or the uploader declares.","minimum":0},"sha256":{"$ref":"#/$defs/Sha256","description":"SHA-256 of the stored bytes (lower-case hex): the blob key, the public URL prefix, the ETag and the checksum shown with downloads. Unique per site. For external files see `storage.checksum`."},"originalSha256":{"$ref":"#/$defs/Sha256","description":"SHA-256 of the bytes as uploaded, present only when the server changed them (image metadata stripping)."},"slug":{"$ref":"#/$defs/Slug","description":"File name in the public URL, without extension: /media//.. May change; the old URL then answers 301 to the new one."},"storage":{"$ref":"#/$defs/AssetStorage"},"image":{"$ref":"#/$defs/AssetImage"},"title":{"type":"object","description":"Display title per locale (download lists, library search).","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"type":"string","minLength":1,"maxLength":300}},"alt":{"type":"object","description":"Alternative text per locale. An `ImageRef.alt` that is not empty overrides it for one use.","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"type":"string","minLength":1,"maxLength":500}},"decorative":{"type":"boolean","description":"The image carries no information and renders with alt=\"\". Absent means false; the store never writes false."},"caption":{"type":"object","description":"Caption per locale (plain text), the default for figures that show this asset.","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"type":"string","minLength":1,"maxLength":1000}},"credit":{"type":"string","description":"Who made it, as shown next to it (e.g. 'Photo: Ayşe Yılmaz').","minLength":1,"maxLength":300},"license":{"$ref":"#/$defs/AssetLicense"},"status":{"type":"string","description":"active: listed and pickable. retired: hidden from the library and pickers and cannot be newly referenced; existing references keep rendering.","enum":["active","retired"]},"uploadedBy":{"$ref":"#/$defs/Actor"},"source":{"$ref":"#/$defs/AssetSource"},"rev":{"$ref":"#/$defs/Revision"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"metadata":{"$ref":"#/$defs/Metadata"}},"$defs":{"Actor":{"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}},"ActorKind":{"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]},"AssetImage":{"type":"object","description":"Intrinsic size and placeholders of an image. Present for every image stored as a blob.","additionalProperties":false,"required":["width","height"],"properties":{"width":{"type":"integer","minimum":1,"maximum":100000},"height":{"type":"integer","minimum":1,"maximum":100000},"animated":{"type":"boolean","description":"Animated GIF or WebP: never resized or re-encoded. Absent means false; the store never writes false."},"blurhash":{"type":"string","description":"BlurHash of the image for themes that decode it client-side.","pattern":"^[0-9A-Za-z#$%*+,.:;=?@^_{|}~\\[\\]-]{6,100}$"},"lqip":{"type":"string","description":"Low-quality placeholder: a raster data: URL of at most about 3 KB, shown while the image loads.","pattern":"^data:image/(?:webp|png|jpeg);base64,[A-Za-z0-9+/]+={0,2}$","maxLength":4096},"color":{"type":"string","description":"RFC-0028: the image's dominant colour, lower-case #rrggbb. Painted under the placeholder and in color-fill bars.","pattern":"^#[0-9a-f]{6}$"},"alpha":{"type":"boolean","enum":[true],"description":"RFC-0028: the image may have transparent pixels (read from its header at ingest). Absent means opaque; transparent images never show the LQIP."},"focal":{"type":"object","description":"RFC-0028: where the subject is, from the top-left corner as fractions of the width and height. Cover crops keep it in view.","additionalProperties":false,"required":["x","y"],"properties":{"x":{"type":"number","minimum":0,"maximum":1},"y":{"type":"number","minimum":0,"maximum":1}}}}},"AssetLicense":{"description":"Terms the asset may be reused under: an SPDX license expression or a license URL.","oneOf":[{"type":"object","title":"SpdxLicense","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"type":"string","enum":["spdx"]},"id":{"type":"string","description":"SPDX license identifier or simple expression, e.g. CC-BY-4.0, CC0-1.0, MIT OR Apache-2.0, LicenseRef-AllRightsReserved.","pattern":"^[A-Za-z0-9][A-Za-z0-9.+-]*(?: (?:AND|OR|WITH) [A-Za-z0-9][A-Za-z0-9.+-]*)*$","maxLength":200}}},{"type":"object","title":"UrlLicense","additionalProperties":false,"required":["kind","url"],"properties":{"kind":{"type":"string","enum":["url"]},"url":{"$ref":"#/$defs/HttpsUrl"},"name":{"type":"string","minLength":1,"maxLength":200}}}]},"AssetSource":{"description":"How the asset came in. Immutable after create.","oneOf":[{"type":"object","title":"UploadSource","description":"Bytes uploaded by a person (REST) or an agent (MCP upload_asset).","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["upload"]},"filename":{"type":"string","description":"Original file name as sent (never used as a storage key or served).","pattern":"^[^/\\\\\\u0000-\\u001f\\u007f]+$","minLength":1,"maxLength":255}}},{"type":"object","title":"FetchSource","description":"Bytes the server fetched from an https URL (MCP fetch_asset_url, Studio 'From URL') under the SSRF rules.","additionalProperties":false,"required":["kind","url","fetchedAt"],"properties":{"kind":{"type":"string","enum":["fetch"]},"url":{"$ref":"#/$defs/HttpsUrl"},"fetchedAt":{"$ref":"#/$defs/Timestamp"}}},{"type":"object","title":"ImportSource","description":"Created by a migration or importer (e.g. `cloudhal media import-urls` from an ImageRef.src).","additionalProperties":false,"required":["kind","from"],"properties":{"kind":{"type":"string","enum":["import"]},"from":{"type":"string","description":"The reference it was imported from: a URL or a site path.","minLength":1,"maxLength":2048},"importer":{"$ref":"#/$defs/Slug"}}},{"type":"object","title":"LinkSource","description":"An external file registered by URL without copying its bytes (storage.kind external).","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["link"]}}}]},"AssetStorage":{"description":"Where the bytes live. `blob`: in this deployment's blob store (R2 or the filesystem) under the content-addressed key media//, derived and never stored, so the node is portable between backends. `external`: elsewhere, linked rather than hosted (large research files on Zenodo or Hugging Face).","oneOf":[{"type":"object","title":"BlobStorage","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["blob"]}}},{"type":"object","title":"ExternalStorage","additionalProperties":false,"required":["kind","url","checksum"],"properties":{"kind":{"type":"string","enum":["external"]},"url":{"$ref":"#/$defs/HttpsUrl","description":"Direct download URL of the file."},"landingPage":{"$ref":"#/$defs/HttpsUrl","description":"Human page of the record the file belongs to (e.g. a Zenodo record or a Hugging Face dataset)."},"checksum":{"type":"string","description":"How `sha256` was obtained. computed: the server or `cloudhal media verify` streamed the URL and hashed it. declared: given by the person who linked it, not verified (shown as such).","enum":["computed","declared"]},"verifiedAt":{"$ref":"#/$defs/Timestamp","description":"When the URL last produced bytes with this SHA-256 (checksum `computed`)."}}}]},"HttpsUrl":{"type":"string","description":"Absolute https URL without credentials, whitespace or backslashes.","pattern":"^https://[^\\s\\\\/?#@]+(?:[/?#][^\\s\\\\]*)?$","maxLength":2048},"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"Locale":{"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"},"Metadata":{"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Sha256":{"type":"string","description":"Lower-case hex SHA-256 digest.","pattern":"^[0-9a-f]{64}$"},"Slug":{"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}}}; -const schema275 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; -const schema276 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; -const schema278 = {"type":"string","description":"Lower-case hex SHA-256 digest.","pattern":"^[0-9a-f]{64}$"}; -const schema280 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128}; -const schema285 = {"type":"object","description":"Intrinsic size and placeholders of an image. Present for every image stored as a blob.","additionalProperties":false,"required":["width","height"],"properties":{"width":{"type":"integer","minimum":1,"maximum":100000},"height":{"type":"integer","minimum":1,"maximum":100000},"animated":{"type":"boolean","description":"Animated GIF or WebP: never resized or re-encoded. Absent means false; the store never writes false."},"blurhash":{"type":"string","description":"BlurHash of the image for themes that decode it client-side.","pattern":"^[0-9A-Za-z#$%*+,.:;=?@^_{|}~\\[\\]-]{6,100}$"},"lqip":{"type":"string","description":"Low-quality placeholder: a raster data: URL of at most about 3 KB, shown while the image loads.","pattern":"^data:image/(?:webp|png|jpeg);base64,[A-Za-z0-9+/]+={0,2}$","maxLength":4096},"color":{"type":"string","description":"RFC-0028: the image's dominant colour, lower-case #rrggbb. Painted under the placeholder and in color-fill bars.","pattern":"^#[0-9a-f]{6}$"},"alpha":{"type":"boolean","enum":[true],"description":"RFC-0028: the image may have transparent pixels (read from its header at ingest). Absent means opaque; transparent images never show the LQIP."},"focal":{"type":"object","description":"RFC-0028: where the subject is, from the top-left corner as fractions of the width and height. Cover crops keep it in view.","additionalProperties":false,"required":["x","y"],"properties":{"x":{"type":"number","minimum":0,"maximum":1},"y":{"type":"number","minimum":0,"maximum":1}}}}}; -const schema286 = {"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"}; -const schema298 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; -const schema284 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; -const schema301 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}}; -const pattern136 = new RegExp("^[a-z0-9][a-z0-9!#$&^_.+-]{0,126}/[a-z0-9][a-z0-9!#$&^_.+-]{0,126}$", "u"); -const pattern142 = new RegExp("^[0-9A-Za-z#$%*+,.:;=?@^_{|}~\\[\\]-]{6,100}$", "u"); -const pattern143 = new RegExp("^data:image/(?:webp|png|jpeg);base64,[A-Za-z0-9+/]+={0,2}$", "u"); -const pattern144 = new RegExp("^#[0-9a-f]{6}$", "u"); -const schema281 = {"description":"Where the bytes live. `blob`: in this deployment's blob store (R2 or the filesystem) under the content-addressed key media//, derived and never stored, so the node is portable between backends. `external`: elsewhere, linked rather than hosted (large research files on Zenodo or Hugging Face).","oneOf":[{"type":"object","title":"BlobStorage","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["blob"]}}},{"type":"object","title":"ExternalStorage","additionalProperties":false,"required":["kind","url","checksum"],"properties":{"kind":{"type":"string","enum":["external"]},"url":{"$ref":"#/$defs/HttpsUrl","description":"Direct download URL of the file."},"landingPage":{"$ref":"#/$defs/HttpsUrl","description":"Human page of the record the file belongs to (e.g. a Zenodo record or a Hugging Face dataset)."},"checksum":{"type":"string","description":"How `sha256` was obtained. computed: the server or `cloudhal media verify` streamed the URL and hashed it. declared: given by the person who linked it, not verified (shown as such).","enum":["computed","declared"]},"verifiedAt":{"$ref":"#/$defs/Timestamp","description":"When the URL last produced bytes with this SHA-256 (checksum `computed`)."}}}]}; -const schema282 = {"type":"string","description":"Absolute https URL without credentials, whitespace or backslashes.","pattern":"^https://[^\\s\\\\/?#@]+(?:[/?#][^\\s\\\\]*)?$","maxLength":2048}; -const pattern140 = new RegExp("^https://[^\\s\\\\/?#@]+(?:[/?#][^\\s\\\\]*)?$", "u"); +const schema276 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/asset.schema.json","title":"Asset","description":"A media file of a site (image, video, audio, document, data) as a graph node: its bytes are identified by SHA-256 and stored once per site in the blob store under a content-addressed key, or kept elsewhere (`storage.kind: external`, e.g. a Zenodo or Hugging Face file). Pages, entries and terms refer to it by id (`ImageRef.assetId`, markdown `asset:`). Snapshotted like every node, so a deleted asset's bytes stay while any snapshot references it (RFC-0004).","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","kind","mediaType","bytes","sha256","slug","storage","status","uploadedBy","source","rev","createdAt","updatedAt"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/Id"},"siteId":{"$ref":"#/$defs/Id"},"kind":{"type":"string","description":"What the file is, derived from the sniffed media type (never chosen by the client). `other` is allowed for external storage only.","enum":["image","video","audio","document","data","other"]},"mediaType":{"type":"string","description":"MIME type of the stored bytes as sniffed by the server (RFC 6838, lower case, no parameters), e.g. image/webp, text/csv. Served as Content-Type.","pattern":"^[a-z0-9][a-z0-9!#$&^_.+-]{0,126}/[a-z0-9][a-z0-9!#$&^_.+-]{0,126}$","maxLength":255},"bytes":{"type":"integer","description":"Size of the stored bytes. For external files: the size the source reports or the uploader declares.","minimum":0},"sha256":{"$ref":"#/$defs/Sha256","description":"SHA-256 of the stored bytes (lower-case hex): the blob key, the public URL prefix, the ETag and the checksum shown with downloads. Unique per site. For external files see `storage.checksum`."},"originalSha256":{"$ref":"#/$defs/Sha256","description":"SHA-256 of the bytes as uploaded, present only when the server changed them (image metadata stripping)."},"slug":{"$ref":"#/$defs/Slug","description":"File name in the public URL, without extension: /media//.. May change; the old URL then answers 301 to the new one."},"storage":{"$ref":"#/$defs/AssetStorage"},"image":{"$ref":"#/$defs/AssetImage"},"title":{"type":"object","description":"Display title per locale (download lists, library search).","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"type":"string","minLength":1,"maxLength":300}},"alt":{"type":"object","description":"Alternative text per locale. An `ImageRef.alt` that is not empty overrides it for one use.","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"type":"string","minLength":1,"maxLength":500}},"decorative":{"type":"boolean","description":"The image carries no information and renders with alt=\"\". Absent means false; the store never writes false."},"caption":{"type":"object","description":"Caption per locale (plain text), the default for figures that show this asset.","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"type":"string","minLength":1,"maxLength":1000}},"credit":{"type":"string","description":"Who made it, as shown next to it (e.g. 'Photo: Ayşe Yılmaz').","minLength":1,"maxLength":300},"license":{"$ref":"#/$defs/AssetLicense"},"status":{"type":"string","description":"active: listed and pickable. retired: hidden from the library and pickers and cannot be newly referenced; existing references keep rendering.","enum":["active","retired"]},"uploadedBy":{"$ref":"#/$defs/Actor"},"source":{"$ref":"#/$defs/AssetSource"},"rev":{"$ref":"#/$defs/Revision"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"metadata":{"$ref":"#/$defs/Metadata"}},"$defs":{"Actor":{"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}},"ActorKind":{"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]},"AssetImage":{"type":"object","description":"Intrinsic size and placeholders of an image. Present for every image stored as a blob.","additionalProperties":false,"required":["width","height"],"properties":{"width":{"type":"integer","minimum":1,"maximum":100000},"height":{"type":"integer","minimum":1,"maximum":100000},"animated":{"type":"boolean","description":"Animated GIF or WebP: never resized or re-encoded. Absent means false; the store never writes false."},"blurhash":{"type":"string","description":"BlurHash of the image for themes that decode it client-side.","pattern":"^[0-9A-Za-z#$%*+,.:;=?@^_{|}~\\[\\]-]{6,100}$"},"lqip":{"type":"string","description":"Low-quality placeholder: a raster data: URL of at most about 3 KB, shown while the image loads.","pattern":"^data:image/(?:webp|png|jpeg);base64,[A-Za-z0-9+/]+={0,2}$","maxLength":4096},"color":{"type":"string","description":"RFC-0028: the image's dominant colour, lower-case #rrggbb. Painted under the placeholder and in color-fill bars.","pattern":"^#[0-9a-f]{6}$"},"alpha":{"type":"boolean","enum":[true],"description":"RFC-0028: the image may have transparent pixels (read from its header at ingest). Absent means opaque; transparent images never show the LQIP."},"focal":{"type":"object","description":"RFC-0028: where the subject is, from the top-left corner as fractions of the width and height. Cover crops keep it in view.","additionalProperties":false,"required":["x","y"],"properties":{"x":{"type":"number","minimum":0,"maximum":1},"y":{"type":"number","minimum":0,"maximum":1}}}}},"AssetLicense":{"description":"Terms the asset may be reused under: an SPDX license expression or a license URL.","oneOf":[{"type":"object","title":"SpdxLicense","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"type":"string","enum":["spdx"]},"id":{"type":"string","description":"SPDX license identifier or simple expression, e.g. CC-BY-4.0, CC0-1.0, MIT OR Apache-2.0, LicenseRef-AllRightsReserved.","pattern":"^[A-Za-z0-9][A-Za-z0-9.+-]*(?: (?:AND|OR|WITH) [A-Za-z0-9][A-Za-z0-9.+-]*)*$","maxLength":200}}},{"type":"object","title":"UrlLicense","additionalProperties":false,"required":["kind","url"],"properties":{"kind":{"type":"string","enum":["url"]},"url":{"$ref":"#/$defs/HttpsUrl"},"name":{"type":"string","minLength":1,"maxLength":200}}}]},"AssetSource":{"description":"How the asset came in. Immutable after create.","oneOf":[{"type":"object","title":"UploadSource","description":"Bytes uploaded by a person (REST) or an agent (MCP upload_asset).","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["upload"]},"filename":{"type":"string","description":"Original file name as sent (never used as a storage key or served).","pattern":"^[^/\\\\\\u0000-\\u001f\\u007f]+$","minLength":1,"maxLength":255}}},{"type":"object","title":"FetchSource","description":"Bytes the server fetched from an https URL (MCP fetch_asset_url, Studio 'From URL') under the SSRF rules.","additionalProperties":false,"required":["kind","url","fetchedAt"],"properties":{"kind":{"type":"string","enum":["fetch"]},"url":{"$ref":"#/$defs/HttpsUrl"},"fetchedAt":{"$ref":"#/$defs/Timestamp"}}},{"type":"object","title":"ImportSource","description":"Created by a migration or importer (e.g. `cloudhal media import-urls` from an ImageRef.src).","additionalProperties":false,"required":["kind","from"],"properties":{"kind":{"type":"string","enum":["import"]},"from":{"type":"string","description":"The reference it was imported from: a URL or a site path.","minLength":1,"maxLength":2048},"importer":{"$ref":"#/$defs/Slug"}}},{"type":"object","title":"LinkSource","description":"An external file registered by URL without copying its bytes (storage.kind external).","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["link"]}}}]},"AssetStorage":{"description":"Where the bytes live. `blob`: in this deployment's blob store (R2 or the filesystem) under the content-addressed key media//, derived and never stored, so the node is portable between backends. `external`: elsewhere, linked rather than hosted (large research files on Zenodo or Hugging Face).","oneOf":[{"type":"object","title":"BlobStorage","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["blob"]}}},{"type":"object","title":"ExternalStorage","additionalProperties":false,"required":["kind","url","checksum"],"properties":{"kind":{"type":"string","enum":["external"]},"url":{"$ref":"#/$defs/HttpsUrl","description":"Direct download URL of the file."},"landingPage":{"$ref":"#/$defs/HttpsUrl","description":"Human page of the record the file belongs to (e.g. a Zenodo record or a Hugging Face dataset)."},"checksum":{"type":"string","description":"How `sha256` was obtained. computed: the server or `cloudhal media verify` streamed the URL and hashed it. declared: given by the person who linked it, not verified (shown as such).","enum":["computed","declared"]},"verifiedAt":{"$ref":"#/$defs/Timestamp","description":"When the URL last produced bytes with this SHA-256 (checksum `computed`)."}}}]},"HttpsUrl":{"type":"string","description":"Absolute https URL without credentials, whitespace or backslashes.","pattern":"^https://[^\\s\\\\/?#@]+(?:[/?#][^\\s\\\\]*)?$","maxLength":2048},"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"Locale":{"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"},"Metadata":{"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Sha256":{"type":"string","description":"Lower-case hex SHA-256 digest.","pattern":"^[0-9a-f]{64}$"},"Slug":{"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}}}; +const schema277 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; +const schema278 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; +const schema280 = {"type":"string","description":"Lower-case hex SHA-256 digest.","pattern":"^[0-9a-f]{64}$"}; +const schema282 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128}; +const schema287 = {"type":"object","description":"Intrinsic size and placeholders of an image. Present for every image stored as a blob.","additionalProperties":false,"required":["width","height"],"properties":{"width":{"type":"integer","minimum":1,"maximum":100000},"height":{"type":"integer","minimum":1,"maximum":100000},"animated":{"type":"boolean","description":"Animated GIF or WebP: never resized or re-encoded. Absent means false; the store never writes false."},"blurhash":{"type":"string","description":"BlurHash of the image for themes that decode it client-side.","pattern":"^[0-9A-Za-z#$%*+,.:;=?@^_{|}~\\[\\]-]{6,100}$"},"lqip":{"type":"string","description":"Low-quality placeholder: a raster data: URL of at most about 3 KB, shown while the image loads.","pattern":"^data:image/(?:webp|png|jpeg);base64,[A-Za-z0-9+/]+={0,2}$","maxLength":4096},"color":{"type":"string","description":"RFC-0028: the image's dominant colour, lower-case #rrggbb. Painted under the placeholder and in color-fill bars.","pattern":"^#[0-9a-f]{6}$"},"alpha":{"type":"boolean","enum":[true],"description":"RFC-0028: the image may have transparent pixels (read from its header at ingest). Absent means opaque; transparent images never show the LQIP."},"focal":{"type":"object","description":"RFC-0028: where the subject is, from the top-left corner as fractions of the width and height. Cover crops keep it in view.","additionalProperties":false,"required":["x","y"],"properties":{"x":{"type":"number","minimum":0,"maximum":1},"y":{"type":"number","minimum":0,"maximum":1}}}}}; +const schema288 = {"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"}; +const schema300 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; +const schema286 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; +const schema303 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}}; +const pattern138 = new RegExp("^[a-z0-9][a-z0-9!#$&^_.+-]{0,126}/[a-z0-9][a-z0-9!#$&^_.+-]{0,126}$", "u"); +const pattern144 = new RegExp("^[0-9A-Za-z#$%*+,.:;=?@^_{|}~\\[\\]-]{6,100}$", "u"); +const pattern145 = new RegExp("^data:image/(?:webp|png|jpeg);base64,[A-Za-z0-9+/]+={0,2}$", "u"); +const pattern146 = new RegExp("^#[0-9a-f]{6}$", "u"); +const schema283 = {"description":"Where the bytes live. `blob`: in this deployment's blob store (R2 or the filesystem) under the content-addressed key media//, derived and never stored, so the node is portable between backends. `external`: elsewhere, linked rather than hosted (large research files on Zenodo or Hugging Face).","oneOf":[{"type":"object","title":"BlobStorage","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["blob"]}}},{"type":"object","title":"ExternalStorage","additionalProperties":false,"required":["kind","url","checksum"],"properties":{"kind":{"type":"string","enum":["external"]},"url":{"$ref":"#/$defs/HttpsUrl","description":"Direct download URL of the file."},"landingPage":{"$ref":"#/$defs/HttpsUrl","description":"Human page of the record the file belongs to (e.g. a Zenodo record or a Hugging Face dataset)."},"checksum":{"type":"string","description":"How `sha256` was obtained. computed: the server or `cloudhal media verify` streamed the URL and hashed it. declared: given by the person who linked it, not verified (shown as such).","enum":["computed","declared"]},"verifiedAt":{"$ref":"#/$defs/Timestamp","description":"When the URL last produced bytes with this SHA-256 (checksum `computed`)."}}}]}; +const schema284 = {"type":"string","description":"Absolute https URL without credentials, whitespace or backslashes.","pattern":"^https://[^\\s\\\\/?#@]+(?:[/?#][^\\s\\\\]*)?$","maxLength":2048}; +const pattern142 = new RegExp("^https://[^\\s\\\\/?#@]+(?:[/?#][^\\s\\\\]*)?$", "u"); function validate131(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -19549,7 +19674,7 @@ vErrors.push(err2); errors++; } if(!(data0 === "blob")){ -const err3 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema281.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err3 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema283.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err3]; } @@ -19633,7 +19758,7 @@ vErrors.push(err9); errors++; } if(!(data1 === "external")){ -const err10 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema281.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err10 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema283.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err10]; } @@ -19656,7 +19781,7 @@ vErrors.push(err11); } errors++; } -if(!pattern140.test(data2)){ +if(!pattern142.test(data2)){ const err12 = {instancePath:instancePath+"/url",schemaPath:"#/$defs/HttpsUrl/pattern",keyword:"pattern",params:{pattern: "^https://[^\\s\\\\/?#@]+(?:[/?#][^\\s\\\\]*)?$"},message:"must match pattern \""+"^https://[^\\s\\\\/?#@]+(?:[/?#][^\\s\\\\]*)?$"+"\""}; if(vErrors === null){ vErrors = [err12]; @@ -19691,7 +19816,7 @@ vErrors.push(err14); } errors++; } -if(!pattern140.test(data3)){ +if(!pattern142.test(data3)){ const err15 = {instancePath:instancePath+"/landingPage",schemaPath:"#/$defs/HttpsUrl/pattern",keyword:"pattern",params:{pattern: "^https://[^\\s\\\\/?#@]+(?:[/?#][^\\s\\\\]*)?$"},message:"must match pattern \""+"^https://[^\\s\\\\/?#@]+(?:[/?#][^\\s\\\\]*)?$"+"\""}; if(vErrors === null){ vErrors = [err15]; @@ -19726,7 +19851,7 @@ vErrors.push(err17); errors++; } if(!((data4 === "computed") || (data4 === "declared"))){ -const err18 = {instancePath:instancePath+"/checksum",schemaPath:"#/oneOf/1/properties/checksum/enum",keyword:"enum",params:{allowedValues: schema281.oneOf[1].properties.checksum.enum},message:"must be equal to one of the allowed values"}; +const err18 = {instancePath:instancePath+"/checksum",schemaPath:"#/oneOf/1/properties/checksum/enum",keyword:"enum",params:{allowedValues: schema283.oneOf[1].properties.checksum.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err18]; } @@ -19813,8 +19938,8 @@ return errors === 0; } validate131.evaluated = {"dynamicProps":true,"dynamicItems":false}; -const schema289 = {"description":"Terms the asset may be reused under: an SPDX license expression or a license URL.","oneOf":[{"type":"object","title":"SpdxLicense","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"type":"string","enum":["spdx"]},"id":{"type":"string","description":"SPDX license identifier or simple expression, e.g. CC-BY-4.0, CC0-1.0, MIT OR Apache-2.0, LicenseRef-AllRightsReserved.","pattern":"^[A-Za-z0-9][A-Za-z0-9.+-]*(?: (?:AND|OR|WITH) [A-Za-z0-9][A-Za-z0-9.+-]*)*$","maxLength":200}}},{"type":"object","title":"UrlLicense","additionalProperties":false,"required":["kind","url"],"properties":{"kind":{"type":"string","enum":["url"]},"url":{"$ref":"#/$defs/HttpsUrl"},"name":{"type":"string","minLength":1,"maxLength":200}}}]}; -const pattern148 = new RegExp("^[A-Za-z0-9][A-Za-z0-9.+-]*(?: (?:AND|OR|WITH) [A-Za-z0-9][A-Za-z0-9.+-]*)*$", "u"); +const schema291 = {"description":"Terms the asset may be reused under: an SPDX license expression or a license URL.","oneOf":[{"type":"object","title":"SpdxLicense","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"type":"string","enum":["spdx"]},"id":{"type":"string","description":"SPDX license identifier or simple expression, e.g. CC-BY-4.0, CC0-1.0, MIT OR Apache-2.0, LicenseRef-AllRightsReserved.","pattern":"^[A-Za-z0-9][A-Za-z0-9.+-]*(?: (?:AND|OR|WITH) [A-Za-z0-9][A-Za-z0-9.+-]*)*$","maxLength":200}}},{"type":"object","title":"UrlLicense","additionalProperties":false,"required":["kind","url"],"properties":{"kind":{"type":"string","enum":["url"]},"url":{"$ref":"#/$defs/HttpsUrl"},"name":{"type":"string","minLength":1,"maxLength":200}}}]}; +const pattern150 = new RegExp("^[A-Za-z0-9][A-Za-z0-9.+-]*(?: (?:AND|OR|WITH) [A-Za-z0-9][A-Za-z0-9.+-]*)*$", "u"); function validate133(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -19876,7 +20001,7 @@ vErrors.push(err3); errors++; } if(!(data0 === "spdx")){ -const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema289.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema291.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err4]; } @@ -19899,7 +20024,7 @@ vErrors.push(err5); } errors++; } -if(!pattern148.test(data1)){ +if(!pattern150.test(data1)){ const err6 = {instancePath:instancePath+"/id",schemaPath:"#/oneOf/0/properties/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9.+-]*(?: (?:AND|OR|WITH) [A-Za-z0-9][A-Za-z0-9.+-]*)*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9.+-]*(?: (?:AND|OR|WITH) [A-Za-z0-9][A-Za-z0-9.+-]*)*$"+"\""}; if(vErrors === null){ vErrors = [err6]; @@ -19985,7 +20110,7 @@ vErrors.push(err12); errors++; } if(!(data2 === "url")){ -const err13 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema289.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err13 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema291.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err13]; } @@ -20008,7 +20133,7 @@ vErrors.push(err14); } errors++; } -if(!pattern140.test(data3)){ +if(!pattern142.test(data3)){ const err15 = {instancePath:instancePath+"/url",schemaPath:"#/$defs/HttpsUrl/pattern",keyword:"pattern",params:{pattern: "^https://[^\\s\\\\/?#@]+(?:[/?#][^\\s\\\\]*)?$"},message:"must match pattern \""+"^https://[^\\s\\\\/?#@]+(?:[/?#][^\\s\\\\]*)?$"+"\""}; if(vErrors === null){ vErrors = [err15]; @@ -20117,8 +20242,8 @@ return errors === 0; } validate133.evaluated = {"dynamicProps":true,"dynamicItems":false}; -const schema291 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}}; -const schema292 = {"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]}; +const schema293 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}}; +const schema294 = {"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]}; function validate135(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -20176,7 +20301,7 @@ vErrors.push(err3); errors++; } if(!(((data0 === "human") || (data0 === "agent")) || (data0 === "system"))){ -const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/$defs/ActorKind/enum",keyword:"enum",params:{allowedValues: schema292.enum},message:"must be equal to one of the allowed values"}; +const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/$defs/ActorKind/enum",keyword:"enum",params:{allowedValues: schema294.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err4]; } @@ -20272,8 +20397,8 @@ return errors === 0; } validate135.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; -const schema294 = {"description":"How the asset came in. Immutable after create.","oneOf":[{"type":"object","title":"UploadSource","description":"Bytes uploaded by a person (REST) or an agent (MCP upload_asset).","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["upload"]},"filename":{"type":"string","description":"Original file name as sent (never used as a storage key or served).","pattern":"^[^/\\\\\\u0000-\\u001f\\u007f]+$","minLength":1,"maxLength":255}}},{"type":"object","title":"FetchSource","description":"Bytes the server fetched from an https URL (MCP fetch_asset_url, Studio 'From URL') under the SSRF rules.","additionalProperties":false,"required":["kind","url","fetchedAt"],"properties":{"kind":{"type":"string","enum":["fetch"]},"url":{"$ref":"#/$defs/HttpsUrl"},"fetchedAt":{"$ref":"#/$defs/Timestamp"}}},{"type":"object","title":"ImportSource","description":"Created by a migration or importer (e.g. `cloudhal media import-urls` from an ImageRef.src).","additionalProperties":false,"required":["kind","from"],"properties":{"kind":{"type":"string","enum":["import"]},"from":{"type":"string","description":"The reference it was imported from: a URL or a site path.","minLength":1,"maxLength":2048},"importer":{"$ref":"#/$defs/Slug"}}},{"type":"object","title":"LinkSource","description":"An external file registered by URL without copying its bytes (storage.kind external).","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["link"]}}}]}; -const pattern151 = new RegExp("^[^/\\\\\\u0000-\\u001f\\u007f]+$", "u"); +const schema296 = {"description":"How the asset came in. Immutable after create.","oneOf":[{"type":"object","title":"UploadSource","description":"Bytes uploaded by a person (REST) or an agent (MCP upload_asset).","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["upload"]},"filename":{"type":"string","description":"Original file name as sent (never used as a storage key or served).","pattern":"^[^/\\\\\\u0000-\\u001f\\u007f]+$","minLength":1,"maxLength":255}}},{"type":"object","title":"FetchSource","description":"Bytes the server fetched from an https URL (MCP fetch_asset_url, Studio 'From URL') under the SSRF rules.","additionalProperties":false,"required":["kind","url","fetchedAt"],"properties":{"kind":{"type":"string","enum":["fetch"]},"url":{"$ref":"#/$defs/HttpsUrl"},"fetchedAt":{"$ref":"#/$defs/Timestamp"}}},{"type":"object","title":"ImportSource","description":"Created by a migration or importer (e.g. `cloudhal media import-urls` from an ImageRef.src).","additionalProperties":false,"required":["kind","from"],"properties":{"kind":{"type":"string","enum":["import"]},"from":{"type":"string","description":"The reference it was imported from: a URL or a site path.","minLength":1,"maxLength":2048},"importer":{"$ref":"#/$defs/Slug"}}},{"type":"object","title":"LinkSource","description":"An external file registered by URL without copying its bytes (storage.kind external).","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["link"]}}}]}; +const pattern153 = new RegExp("^[^/\\\\\\u0000-\\u001f\\u007f]+$", "u"); function validate137(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -20325,7 +20450,7 @@ vErrors.push(err2); errors++; } if(!(data0 === "upload")){ -const err3 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema294.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err3 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema296.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err3]; } @@ -20358,7 +20483,7 @@ vErrors.push(err5); } errors++; } -if(!pattern151.test(data1)){ +if(!pattern153.test(data1)){ const err6 = {instancePath:instancePath+"/filename",schemaPath:"#/oneOf/0/properties/filename/pattern",keyword:"pattern",params:{pattern: "^[^/\\\\\\u0000-\\u001f\\u007f]+$"},message:"must match pattern \""+"^[^/\\\\\\u0000-\\u001f\\u007f]+$"+"\""}; if(vErrors === null){ vErrors = [err6]; @@ -20454,7 +20579,7 @@ vErrors.push(err13); errors++; } if(!(data2 === "fetch")){ -const err14 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema294.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err14 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema296.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err14]; } @@ -20477,7 +20602,7 @@ vErrors.push(err15); } errors++; } -if(!pattern140.test(data3)){ +if(!pattern142.test(data3)){ const err16 = {instancePath:instancePath+"/url",schemaPath:"#/$defs/HttpsUrl/pattern",keyword:"pattern",params:{pattern: "^https://[^\\s\\\\/?#@]+(?:[/?#][^\\s\\\\]*)?$"},message:"must match pattern \""+"^https://[^\\s\\\\/?#@]+(?:[/?#][^\\s\\\\]*)?$"+"\""}; if(vErrors === null){ vErrors = [err16]; @@ -20595,7 +20720,7 @@ vErrors.push(err24); errors++; } if(!(data5 === "import")){ -const err25 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/2/properties/kind/enum",keyword:"enum",params:{allowedValues: schema294.oneOf[2].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err25 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/2/properties/kind/enum",keyword:"enum",params:{allowedValues: schema296.oneOf[2].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err25]; } @@ -20746,7 +20871,7 @@ vErrors.push(err36); errors++; } if(!(data8 === "link")){ -const err37 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/3/properties/kind/enum",keyword:"enum",params:{allowedValues: schema294.oneOf[3].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err37 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/3/properties/kind/enum",keyword:"enum",params:{allowedValues: schema296.oneOf[3].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err37]; } @@ -20974,7 +21099,7 @@ vErrors.push(err14); errors++; } for(const key0 in data){ -if(!(func1.call(schema274.properties, key0))){ +if(!(func1.call(schema276.properties, key0))){ const err15 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}; if(vErrors === null){ vErrors = [err15]; @@ -20998,7 +21123,7 @@ vErrors.push(err16); errors++; } if(!(data0 === 1)){ -const err17 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema275.enum},message:"must be equal to one of the allowed values"}; +const err17 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema277.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err17]; } @@ -21111,7 +21236,7 @@ vErrors.push(err26); errors++; } if(!((((((data3 === "image") || (data3 === "video")) || (data3 === "audio")) || (data3 === "document")) || (data3 === "data")) || (data3 === "other"))){ -const err27 = {instancePath:instancePath+"/kind",schemaPath:"#/properties/kind/enum",keyword:"enum",params:{allowedValues: schema274.properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err27 = {instancePath:instancePath+"/kind",schemaPath:"#/properties/kind/enum",keyword:"enum",params:{allowedValues: schema276.properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err27]; } @@ -21134,7 +21259,7 @@ vErrors.push(err28); } errors++; } -if(!pattern136.test(data4)){ +if(!pattern138.test(data4)){ const err29 = {instancePath:instancePath+"/mediaType",schemaPath:"#/properties/mediaType/pattern",keyword:"pattern",params:{pattern: "^[a-z0-9][a-z0-9!#$&^_.+-]{0,126}/[a-z0-9][a-z0-9!#$&^_.+-]{0,126}$"},message:"must match pattern \""+"^[a-z0-9][a-z0-9!#$&^_.+-]{0,126}/[a-z0-9][a-z0-9!#$&^_.+-]{0,126}$"+"\""}; if(vErrors === null){ vErrors = [err29]; @@ -21184,7 +21309,7 @@ errors++; if(data.sha256 !== undefined){ let data6 = data.sha256; if(typeof data6 === "string"){ -if(!pattern27.test(data6)){ +if(!pattern29.test(data6)){ const err33 = {instancePath:instancePath+"/sha256",schemaPath:"#/$defs/Sha256/pattern",keyword:"pattern",params:{pattern: "^[0-9a-f]{64}$"},message:"must match pattern \""+"^[0-9a-f]{64}$"+"\""}; if(vErrors === null){ vErrors = [err33]; @@ -21209,7 +21334,7 @@ errors++; if(data.originalSha256 !== undefined){ let data7 = data.originalSha256; if(typeof data7 === "string"){ -if(!pattern27.test(data7)){ +if(!pattern29.test(data7)){ const err35 = {instancePath:instancePath+"/originalSha256",schemaPath:"#/$defs/Sha256/pattern",keyword:"pattern",params:{pattern: "^[0-9a-f]{64}$"},message:"must match pattern \""+"^[0-9a-f]{64}$"+"\""}; if(vErrors === null){ vErrors = [err35]; @@ -21402,7 +21527,7 @@ errors++; if(data10.blurhash !== undefined){ let data14 = data10.blurhash; if(typeof data14 === "string"){ -if(!pattern142.test(data14)){ +if(!pattern144.test(data14)){ const err51 = {instancePath:instancePath+"/image/blurhash",schemaPath:"#/$defs/AssetImage/properties/blurhash/pattern",keyword:"pattern",params:{pattern: "^[0-9A-Za-z#$%*+,.:;=?@^_{|}~\\[\\]-]{6,100}$"},message:"must match pattern \""+"^[0-9A-Za-z#$%*+,.:;=?@^_{|}~\\[\\]-]{6,100}$"+"\""}; if(vErrors === null){ vErrors = [err51]; @@ -21437,7 +21562,7 @@ vErrors.push(err53); } errors++; } -if(!pattern143.test(data15)){ +if(!pattern145.test(data15)){ const err54 = {instancePath:instancePath+"/image/lqip",schemaPath:"#/$defs/AssetImage/properties/lqip/pattern",keyword:"pattern",params:{pattern: "^data:image/(?:webp|png|jpeg);base64,[A-Za-z0-9+/]+={0,2}$"},message:"must match pattern \""+"^data:image/(?:webp|png|jpeg);base64,[A-Za-z0-9+/]+={0,2}$"+"\""}; if(vErrors === null){ vErrors = [err54]; @@ -21462,7 +21587,7 @@ errors++; if(data10.color !== undefined){ let data16 = data10.color; if(typeof data16 === "string"){ -if(!pattern144.test(data16)){ +if(!pattern146.test(data16)){ const err56 = {instancePath:instancePath+"/image/color",schemaPath:"#/$defs/AssetImage/properties/color/pattern",keyword:"pattern",params:{pattern: "^#[0-9a-f]{6}$"},message:"must match pattern \""+"^#[0-9a-f]{6}$"+"\""}; if(vErrors === null){ vErrors = [err56]; @@ -21497,7 +21622,7 @@ vErrors.push(err58); errors++; } if(!(data17 === true)){ -const err59 = {instancePath:instancePath+"/image/alpha",schemaPath:"#/$defs/AssetImage/properties/alpha/enum",keyword:"enum",params:{allowedValues: schema285.properties.alpha.enum},message:"must be equal to one of the allowed values"}; +const err59 = {instancePath:instancePath+"/image/alpha",schemaPath:"#/$defs/AssetImage/properties/alpha/enum",keyword:"enum",params:{allowedValues: schema287.properties.alpha.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err59]; } @@ -21990,7 +22115,7 @@ vErrors.push(err99); errors++; } if(!((data30 === "active") || (data30 === "retired"))){ -const err100 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema274.properties.status.enum},message:"must be equal to one of the allowed values"}; +const err100 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema276.properties.status.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err100]; } @@ -22131,19 +22256,19 @@ return errors === 0; validate130.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; export const validateMenu = validate139; -const schema302 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/menu.schema.json","title":"Menu","description":"A named navigation menu of a site (e.g. 'primary', 'footer', 'sidebar'): an ordered tree of items. Items point at pages, entries and terms by id, so renames and slug changes never break them; an item whose target is gone, retired or unpublished is hidden by the renderer, never rendered as a dead link (RFC-0003).","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","key","maxDepth","items","rev","createdAt","updatedAt"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/Id"},"siteId":{"$ref":"#/$defs/Id"},"key":{"$ref":"#/$defs/Slug","description":"Where themes place the menu ('primary', 'footer', 'sidebar', …). Unique per site and immutable once created."},"maxDepth":{"type":"integer","description":"Deepest level an item may sit at; top-level items are level 1. The store refuses deeper trees.","minimum":1,"maximum":6},"items":{"type":"array","description":"Top-level items in display order. Empty means the site deliberately shows no items here (a missing menu means the theme's derived default).","items":{"$ref":"#/$defs/MenuItem"},"maxItems":100},"rev":{"$ref":"#/$defs/Revision"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"metadata":{"$ref":"#/$defs/Metadata"}},"$defs":{"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"Locale":{"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"},"MenuDisplay":{"type":"string","description":"RFC-0025 25d (RFC-0003 amendment): how a top-level item with children opens in the header. `mega`: one wide panel, its children as columns (a group's children are the column's links).","enum":["mega"]},"MenuItem":{"description":"One item, discriminated by `kind` (what it links to). Children, in display order, form the tree; an item has exactly one parent by construction, so the tree has no cycles.","oneOf":[{"type":"object","title":"PageMenuItem","description":"Links to a page by id; the label falls back to the page title.","additionalProperties":false,"required":["id","kind","pageId"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["page"]},"pageId":{"$ref":"#/$defs/Id"},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}},{"type":"object","title":"EntryMenuItem","description":"Links to a content entry by id (at //); the label falls back to the entry title.","additionalProperties":false,"required":["id","kind","entryId"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["contentEntry"]},"entryId":{"$ref":"#/$defs/Id"},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}},{"type":"object","title":"TermMenuItem","description":"Links to a term's archive by term id; the label falls back to the term name in the locale.","additionalProperties":false,"required":["id","kind","termId"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["term"]},"termId":{"$ref":"#/$defs/Id"},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}},{"type":"object","title":"CollectionMenuItem","description":"Links to the index of a content collection, wherever the theme serves it. Collections have no node and no title, so labels are required.","additionalProperties":false,"required":["id","kind","collection","labels"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["collection"]},"collection":{"$ref":"#/$defs/Slug"},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}},{"type":"object","title":"UrlMenuItem","description":"Links to an external https URL. The host is ASCII (internationalised names in punycode) and carries no user-info, so a label cannot hide where the link goes.","additionalProperties":false,"required":["id","kind","href","labels"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["url"]},"href":{"type":"string","pattern":"^https://[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?(?::[0-9]{1,5})?(?:[/?#][^\\s\\\\\\x00-\\x1F\\x7F]*)?$","maxLength":2048},"newTab":{"type":"boolean","description":"Open in a new tab (rendered with rel=\"noopener noreferrer\")."},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}},{"type":"object","title":"GroupMenuItem","description":"A label without a link that holds other items: a dropdown parent or a sidebar section heading. Hidden when none of its children is shown.","additionalProperties":false,"required":["id","kind","labels","children"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["group"]},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}}]},"MenuLabels":{"type":"object","description":"Item label per locale; plain text, no control characters or surrounding whitespace. A locale without a label falls back to the default locale's label, then to the target's own title or name.","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[^\\s\\x00-\\x1F\\x7F](?:[^\\x00-\\x1F\\x7F]*[^\\s\\x00-\\x1F\\x7F])?$"}},"Metadata":{"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Slug":{"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}}}; -const schema303 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; -const schema304 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; -const schema306 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128}; -const schema326 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; -const schema327 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; -const schema329 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}}; -const schema307 = {"description":"One item, discriminated by `kind` (what it links to). Children, in display order, form the tree; an item has exactly one parent by construction, so the tree has no cycles.","oneOf":[{"type":"object","title":"PageMenuItem","description":"Links to a page by id; the label falls back to the page title.","additionalProperties":false,"required":["id","kind","pageId"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["page"]},"pageId":{"$ref":"#/$defs/Id"},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}},{"type":"object","title":"EntryMenuItem","description":"Links to a content entry by id (at //); the label falls back to the entry title.","additionalProperties":false,"required":["id","kind","entryId"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["contentEntry"]},"entryId":{"$ref":"#/$defs/Id"},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}},{"type":"object","title":"TermMenuItem","description":"Links to a term's archive by term id; the label falls back to the term name in the locale.","additionalProperties":false,"required":["id","kind","termId"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["term"]},"termId":{"$ref":"#/$defs/Id"},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}},{"type":"object","title":"CollectionMenuItem","description":"Links to the index of a content collection, wherever the theme serves it. Collections have no node and no title, so labels are required.","additionalProperties":false,"required":["id","kind","collection","labels"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["collection"]},"collection":{"$ref":"#/$defs/Slug"},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}},{"type":"object","title":"UrlMenuItem","description":"Links to an external https URL. The host is ASCII (internationalised names in punycode) and carries no user-info, so a label cannot hide where the link goes.","additionalProperties":false,"required":["id","kind","href","labels"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["url"]},"href":{"type":"string","pattern":"^https://[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?(?::[0-9]{1,5})?(?:[/?#][^\\s\\\\\\x00-\\x1F\\x7F]*)?$","maxLength":2048},"newTab":{"type":"boolean","description":"Open in a new tab (rendered with rel=\"noopener noreferrer\")."},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}},{"type":"object","title":"GroupMenuItem","description":"A label without a link that holds other items: a dropdown parent or a sidebar section heading. Hidden when none of its children is shown.","additionalProperties":false,"required":["id","kind","labels","children"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["group"]},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}}]}; -const schema312 = {"type":"string","description":"RFC-0025 25d (RFC-0003 amendment): how a top-level item with children opens in the header. `mega`: one wide panel, its children as columns (a group's children are the column's links).","enum":["mega"]}; -const pattern168 = new RegExp("^https://[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?(?::[0-9]{1,5})?(?:[/?#][^\\s\\\\\\x00-\\x1F\\x7F]*)?$", "u"); -const schema310 = {"type":"object","description":"Item label per locale; plain text, no control characters or surrounding whitespace. A locale without a label falls back to the default locale's label, then to the target's own title or name.","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[^\\s\\x00-\\x1F\\x7F](?:[^\\x00-\\x1F\\x7F]*[^\\s\\x00-\\x1F\\x7F])?$"}}; -const schema311 = {"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"}; -const pattern160 = new RegExp("^[^\\s\\x00-\\x1F\\x7F](?:[^\\x00-\\x1F\\x7F]*[^\\s\\x00-\\x1F\\x7F])?$", "u"); +const schema304 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/menu.schema.json","title":"Menu","description":"A named navigation menu of a site (e.g. 'primary', 'footer', 'sidebar'): an ordered tree of items. Items point at pages, entries and terms by id, so renames and slug changes never break them; an item whose target is gone, retired or unpublished is hidden by the renderer, never rendered as a dead link (RFC-0003).","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","key","maxDepth","items","rev","createdAt","updatedAt"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/Id"},"siteId":{"$ref":"#/$defs/Id"},"key":{"$ref":"#/$defs/Slug","description":"Where themes place the menu ('primary', 'footer', 'sidebar', …). Unique per site and immutable once created."},"maxDepth":{"type":"integer","description":"Deepest level an item may sit at; top-level items are level 1. The store refuses deeper trees.","minimum":1,"maximum":6},"items":{"type":"array","description":"Top-level items in display order. Empty means the site deliberately shows no items here (a missing menu means the theme's derived default).","items":{"$ref":"#/$defs/MenuItem"},"maxItems":100},"rev":{"$ref":"#/$defs/Revision"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"metadata":{"$ref":"#/$defs/Metadata"}},"$defs":{"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"Locale":{"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"},"MenuDisplay":{"type":"string","description":"RFC-0025 25d (RFC-0003 amendment): how a top-level item with children opens in the header. `mega`: one wide panel, its children as columns (a group's children are the column's links).","enum":["mega"]},"MenuItem":{"description":"One item, discriminated by `kind` (what it links to). Children, in display order, form the tree; an item has exactly one parent by construction, so the tree has no cycles.","oneOf":[{"type":"object","title":"PageMenuItem","description":"Links to a page by id; the label falls back to the page title.","additionalProperties":false,"required":["id","kind","pageId"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["page"]},"pageId":{"$ref":"#/$defs/Id"},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}},{"type":"object","title":"EntryMenuItem","description":"Links to a content entry by id (at //); the label falls back to the entry title.","additionalProperties":false,"required":["id","kind","entryId"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["contentEntry"]},"entryId":{"$ref":"#/$defs/Id"},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}},{"type":"object","title":"TermMenuItem","description":"Links to a term's archive by term id; the label falls back to the term name in the locale.","additionalProperties":false,"required":["id","kind","termId"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["term"]},"termId":{"$ref":"#/$defs/Id"},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}},{"type":"object","title":"CollectionMenuItem","description":"Links to the index of a content collection, wherever the theme serves it. Collections have no node and no title, so labels are required.","additionalProperties":false,"required":["id","kind","collection","labels"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["collection"]},"collection":{"$ref":"#/$defs/Slug"},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}},{"type":"object","title":"UrlMenuItem","description":"Links to an external https URL. The host is ASCII (internationalised names in punycode) and carries no user-info, so a label cannot hide where the link goes.","additionalProperties":false,"required":["id","kind","href","labels"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["url"]},"href":{"type":"string","pattern":"^https://[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?(?::[0-9]{1,5})?(?:[/?#][^\\s\\\\\\x00-\\x1F\\x7F]*)?$","maxLength":2048},"newTab":{"type":"boolean","description":"Open in a new tab (rendered with rel=\"noopener noreferrer\")."},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}},{"type":"object","title":"GroupMenuItem","description":"A label without a link that holds other items: a dropdown parent or a sidebar section heading. Hidden when none of its children is shown.","additionalProperties":false,"required":["id","kind","labels","children"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["group"]},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}}]},"MenuLabels":{"type":"object","description":"Item label per locale; plain text, no control characters or surrounding whitespace. A locale without a label falls back to the default locale's label, then to the target's own title or name.","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[^\\s\\x00-\\x1F\\x7F](?:[^\\x00-\\x1F\\x7F]*[^\\s\\x00-\\x1F\\x7F])?$"}},"Metadata":{"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Slug":{"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}}}; +const schema305 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; +const schema306 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; +const schema308 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128}; +const schema328 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; +const schema329 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; +const schema331 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}}; +const schema309 = {"description":"One item, discriminated by `kind` (what it links to). Children, in display order, form the tree; an item has exactly one parent by construction, so the tree has no cycles.","oneOf":[{"type":"object","title":"PageMenuItem","description":"Links to a page by id; the label falls back to the page title.","additionalProperties":false,"required":["id","kind","pageId"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["page"]},"pageId":{"$ref":"#/$defs/Id"},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}},{"type":"object","title":"EntryMenuItem","description":"Links to a content entry by id (at //); the label falls back to the entry title.","additionalProperties":false,"required":["id","kind","entryId"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["contentEntry"]},"entryId":{"$ref":"#/$defs/Id"},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}},{"type":"object","title":"TermMenuItem","description":"Links to a term's archive by term id; the label falls back to the term name in the locale.","additionalProperties":false,"required":["id","kind","termId"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["term"]},"termId":{"$ref":"#/$defs/Id"},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}},{"type":"object","title":"CollectionMenuItem","description":"Links to the index of a content collection, wherever the theme serves it. Collections have no node and no title, so labels are required.","additionalProperties":false,"required":["id","kind","collection","labels"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["collection"]},"collection":{"$ref":"#/$defs/Slug"},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}},{"type":"object","title":"UrlMenuItem","description":"Links to an external https URL. The host is ASCII (internationalised names in punycode) and carries no user-info, so a label cannot hide where the link goes.","additionalProperties":false,"required":["id","kind","href","labels"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["url"]},"href":{"type":"string","pattern":"^https://[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?(?::[0-9]{1,5})?(?:[/?#][^\\s\\\\\\x00-\\x1F\\x7F]*)?$","maxLength":2048},"newTab":{"type":"boolean","description":"Open in a new tab (rendered with rel=\"noopener noreferrer\")."},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}},{"type":"object","title":"GroupMenuItem","description":"A label without a link that holds other items: a dropdown parent or a sidebar section heading. Hidden when none of its children is shown.","additionalProperties":false,"required":["id","kind","labels","children"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["group"]},"labels":{"$ref":"#/$defs/MenuLabels"},"display":{"$ref":"#/$defs/MenuDisplay"},"children":{"type":"array","items":{"$ref":"#/$defs/MenuItem"},"minItems":1,"maxItems":100}}}]}; +const schema314 = {"type":"string","description":"RFC-0025 25d (RFC-0003 amendment): how a top-level item with children opens in the header. `mega`: one wide panel, its children as columns (a group's children are the column's links).","enum":["mega"]}; +const pattern170 = new RegExp("^https://[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?(?::[0-9]{1,5})?(?:[/?#][^\\s\\\\\\x00-\\x1F\\x7F]*)?$", "u"); +const schema312 = {"type":"object","description":"Item label per locale; plain text, no control characters or surrounding whitespace. A locale without a label falls back to the default locale's label, then to the target's own title or name.","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[^\\s\\x00-\\x1F\\x7F](?:[^\\x00-\\x1F\\x7F]*[^\\s\\x00-\\x1F\\x7F])?$"}}; +const schema313 = {"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"}; +const pattern162 = new RegExp("^[^\\s\\x00-\\x1F\\x7F](?:[^\\x00-\\x1F\\x7F]*[^\\s\\x00-\\x1F\\x7F])?$", "u"); function validate141(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -22225,7 +22350,7 @@ vErrors.push(err5); } errors++; } -if(!pattern160.test(data0)){ +if(!pattern162.test(data0)){ const err6 = {instancePath:instancePath+"/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/additionalProperties/pattern",keyword:"pattern",params:{pattern: "^[^\\s\\x00-\\x1F\\x7F](?:[^\\x00-\\x1F\\x7F]*[^\\s\\x00-\\x1F\\x7F])?$"},message:"must match pattern \""+"^[^\\s\\x00-\\x1F\\x7F](?:[^\\x00-\\x1F\\x7F]*[^\\s\\x00-\\x1F\\x7F])?$"+"\""}; if(vErrors === null){ vErrors = [err6]; @@ -22380,7 +22505,7 @@ vErrors.push(err8); errors++; } if(!(data1 === "page")){ -const err9 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema307.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err9 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema309.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err9]; } @@ -22454,7 +22579,7 @@ vErrors.push(err14); errors++; } if(!(data4 === "mega")){ -const err15 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema312.enum},message:"must be equal to one of the allowed values"}; +const err15 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema314.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err15]; } @@ -22625,7 +22750,7 @@ vErrors.push(err28); errors++; } if(!(data8 === "contentEntry")){ -const err29 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema307.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err29 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema309.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err29]; } @@ -22699,7 +22824,7 @@ vErrors.push(err34); errors++; } if(!(data11 === "mega")){ -const err35 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema312.enum},message:"must be equal to one of the allowed values"}; +const err35 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema314.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err35]; } @@ -22877,7 +23002,7 @@ vErrors.push(err48); errors++; } if(!(data15 === "term")){ -const err49 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/2/properties/kind/enum",keyword:"enum",params:{allowedValues: schema307.oneOf[2].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err49 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/2/properties/kind/enum",keyword:"enum",params:{allowedValues: schema309.oneOf[2].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err49]; } @@ -22951,7 +23076,7 @@ vErrors.push(err54); errors++; } if(!(data18 === "mega")){ -const err55 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema312.enum},message:"must be equal to one of the allowed values"}; +const err55 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema314.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err55]; } @@ -23139,7 +23264,7 @@ vErrors.push(err69); errors++; } if(!(data22 === "collection")){ -const err70 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/3/properties/kind/enum",keyword:"enum",params:{allowedValues: schema307.oneOf[3].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err70 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/3/properties/kind/enum",keyword:"enum",params:{allowedValues: schema309.oneOf[3].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err70]; } @@ -23213,7 +23338,7 @@ vErrors.push(err75); errors++; } if(!(data25 === "mega")){ -const err76 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema312.enum},message:"must be equal to one of the allowed values"}; +const err76 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema314.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err76]; } @@ -23401,7 +23526,7 @@ vErrors.push(err90); errors++; } if(!(data29 === "url")){ -const err91 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/4/properties/kind/enum",keyword:"enum",params:{allowedValues: schema307.oneOf[4].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err91 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/4/properties/kind/enum",keyword:"enum",params:{allowedValues: schema309.oneOf[4].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err91]; } @@ -23424,7 +23549,7 @@ vErrors.push(err92); } errors++; } -if(!pattern168.test(data30)){ +if(!pattern170.test(data30)){ const err93 = {instancePath:instancePath+"/href",schemaPath:"#/oneOf/4/properties/href/pattern",keyword:"pattern",params:{pattern: "^https://[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?(?::[0-9]{1,5})?(?:[/?#][^\\s\\\\\\x00-\\x1F\\x7F]*)?$"},message:"must match pattern \""+"^https://[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?(?::[0-9]{1,5})?(?:[/?#][^\\s\\\\\\x00-\\x1F\\x7F]*)?$"+"\""}; if(vErrors === null){ vErrors = [err93]; @@ -23477,7 +23602,7 @@ vErrors.push(err96); errors++; } if(!(data33 === "mega")){ -const err97 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema312.enum},message:"must be equal to one of the allowed values"}; +const err97 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema314.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err97]; } @@ -23665,7 +23790,7 @@ vErrors.push(err111); errors++; } if(!(data37 === "group")){ -const err112 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/5/properties/kind/enum",keyword:"enum",params:{allowedValues: schema307.oneOf[5].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err112 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/5/properties/kind/enum",keyword:"enum",params:{allowedValues: schema309.oneOf[5].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err112]; } @@ -23694,7 +23819,7 @@ vErrors.push(err113); errors++; } if(!(data39 === "mega")){ -const err114 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema312.enum},message:"must be equal to one of the allowed values"}; +const err114 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema314.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err114]; } @@ -23906,7 +24031,7 @@ vErrors.push(err8); errors++; } for(const key0 in data){ -if(!(func1.call(schema302.properties, key0))){ +if(!(func1.call(schema304.properties, key0))){ const err9 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}; if(vErrors === null){ vErrors = [err9]; @@ -23930,7 +24055,7 @@ vErrors.push(err10); errors++; } if(!(data0 === 1)){ -const err11 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema303.enum},message:"must be equal to one of the allowed values"}; +const err11 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema305.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err11]; } @@ -24261,32 +24386,32 @@ return errors === 0; validate139.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; export const validateAnalyticsEvent = validate149; -const schema330 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/analytics-event.schema.json","title":"AnalyticsEvent","description":"One site analytics event as stored (RFC-0006): a page view or a Core Web Vitals sample reported by the site's first-party script, or a response the site Worker rendered itself (cache miss, redirect, 404, 410, 5xx). Written by the site Worker after it validated and enriched a beacon. The named fields are the columns of the local/self-host SQLite store; each one documents its fixed slot in a Workers Analytics Engine data point (index1 = siteId, blob1 = kind, blob2 = schemaVersion). Slots never move and new fields are only appended. Never carries an IP address, a user agent, a full referrer URL, a query string or a cookie.","oneOf":[{"type":"object","title":"AnalyticsPageviewEvent","description":"A published page, article or archive was shown to a visitor (the site's script ran).","additionalProperties":false,"required":["schemaVersion","kind","siteId","timestamp","path","nodeId","nodeKind","channel","entrance","device","browser","os","navType"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"kind":{"type":"string","enum":["pageview"]},"siteId":{"$ref":"#/$defs/Id","description":"WAE index1."},"timestamp":{"$ref":"#/$defs/Timestamp"},"path":{"$ref":"#/$defs/Path","description":"WAE blob3. The published route the view resolved to; never a query string."},"nodeId":{"$ref":"#/$defs/Id","description":"WAE blob4. The page, entry or term the server resolved the path to, so stats survive slug changes."},"nodeKind":{"$ref":"#/$defs/AnalyticsNodeKind"},"refHost":{"$ref":"#/$defs/AnalyticsHostname"},"channel":{"$ref":"#/$defs/AnalyticsChannel"},"utmSource":{"$ref":"#/$defs/AnalyticsUtmValue","description":"WAE blob8."},"utmMedium":{"$ref":"#/$defs/AnalyticsUtmValue","description":"WAE blob9."},"utmCampaign":{"$ref":"#/$defs/AnalyticsUtmValue","description":"WAE blob10."},"country":{"$ref":"#/$defs/AnalyticsCountry"},"device":{"$ref":"#/$defs/AnalyticsDevice"},"browser":{"$ref":"#/$defs/AnalyticsBrowser"},"os":{"$ref":"#/$defs/AnalyticsOs"},"visitor":{"$ref":"#/$defs/AnalyticsVisitorHash"},"navType":{"$ref":"#/$defs/AnalyticsNavType"},"locale":{"$ref":"#/$defs/Locale","description":"WAE blob17. Locale of the resolved node, not the visitor's Accept-Language."},"entrance":{"type":"boolean","description":"WAE double1 (1 or 0). The view starts a visit: the referrer is empty or another site, and the navigation is not a reload, back/forward or restore."},"privacySignal":{"type":"boolean","description":"WAE double10 (1 or 0). The beacon request carried Global Privacy Control (Sec-GPC: 1) or Do Not Track (DNT: 1), so no visitor hash was formed. Only this flag is kept, never which signal or any header. Absent means no signal (and on events written before the field existed)."}}},{"type":"object","title":"AnalyticsVitalsEvent","description":"Core Web Vitals of one sampled page view, measured by the site's own script and sent when the page is hidden. Code checks that at least one metric is present (the schema subset has no cross-property rules).","additionalProperties":false,"required":["schemaVersion","kind","siteId","timestamp","path","nodeId","nodeKind","device","sampleOneIn"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"kind":{"type":"string","enum":["vitals"]},"siteId":{"$ref":"#/$defs/Id","description":"WAE index1."},"timestamp":{"$ref":"#/$defs/Timestamp"},"path":{"$ref":"#/$defs/Path","description":"WAE blob3."},"nodeId":{"$ref":"#/$defs/Id","description":"WAE blob4."},"nodeKind":{"$ref":"#/$defs/AnalyticsNodeKind"},"country":{"$ref":"#/$defs/AnalyticsCountry"},"device":{"$ref":"#/$defs/AnalyticsDevice"},"browser":{"$ref":"#/$defs/AnalyticsBrowser"},"lcp":{"$ref":"#/$defs/AnalyticsMillis","description":"WAE double3. Largest Contentful Paint."},"inp":{"$ref":"#/$defs/AnalyticsMillis","description":"WAE double4. Interaction to Next Paint."},"cls":{"type":"number","description":"WAE double5, stored multiplied by 1000. Cumulative Layout Shift (unitless).","minimum":0,"maximum":100},"ttfb":{"$ref":"#/$defs/AnalyticsMillis","description":"WAE double6. Time to First Byte."},"fcp":{"$ref":"#/$defs/AnalyticsMillis","description":"WAE double7. First Contentful Paint."},"sampleOneIn":{"$ref":"#/$defs/AnalyticsSampleOneIn"}}},{"type":"object","title":"AnalyticsRenderEvent","description":"The site Worker answered a request itself (a cache miss, redirect, 404, 410 or error). Cache hits never run the Worker, so these are not page views. The requested path is visitor-controlled text and is always shown as such.","additionalProperties":false,"required":["schemaVersion","kind","siteId","timestamp","path","status","device"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"kind":{"type":"string","enum":["render"]},"siteId":{"$ref":"#/$defs/Id","description":"WAE index1."},"timestamp":{"$ref":"#/$defs/Timestamp"},"path":{"$ref":"#/$defs/AnalyticsRequestPath"},"status":{"type":"integer","description":"WAE double2. HTTP status the Worker answered with.","minimum":100,"maximum":599},"nodeId":{"$ref":"#/$defs/Id","description":"WAE blob4. The node rendered or redirected from, when the path resolved to one."},"nodeKind":{"$ref":"#/$defs/AnalyticsNodeKind"},"redirectTo":{"type":"string","description":"WAE blob19. Target of a 301, 302 or 308: a site path or an https URL, without query string.","pattern":"^(?:/|https://)[!-~]*$","maxLength":512},"refHost":{"$ref":"#/$defs/AnalyticsHostname"},"refPath":{"$ref":"#/$defs/Path","description":"WAE blob20. Same-site referrers only: the page that linked here, which finds broken internal links."},"country":{"$ref":"#/$defs/AnalyticsCountry"},"device":{"$ref":"#/$defs/AnalyticsDevice"},"crawler":{"$ref":"#/$defs/AnalyticsCrawlerName"},"renderMs":{"$ref":"#/$defs/AnalyticsMillis","description":"WAE double8. Time the Worker spent answering."},"sampleOneIn":{"$ref":"#/$defs/AnalyticsSampleOneIn"}}}],"$defs":{"AnalyticsBrowser":{"type":"string","description":"WAE blob13. Browser family, without version.","enum":["chrome","edge","firefox","safari","samsung","opera","other"]},"AnalyticsChannel":{"type":"string","description":"WAE blob7. Where the view came from, classified by the server from the referrer host and utm_medium; internal = another page of this site.","enum":["direct","internal","search","social","ai","email","paid","referral"]},"AnalyticsCountry":{"type":"string","description":"WAE blob11. ISO 3166-1 alpha-2 country from Cloudflare's request.cf.country (XX unknown, T1 Tor). Never a city or region.","pattern":"^[A-Z][A-Z0-9]$"},"AnalyticsCrawlerName":{"type":"string","description":"WAE blob18. A known crawler identified from the user agent (e.g. googlebot, gptbot, claudebot, perplexitybot). The list and each crawler's class (search, AI, social, monitor) live in code.","pattern":"^[a-z0-9][a-z0-9-]{0,31}$"},"AnalyticsDevice":{"type":"string","description":"WAE blob12. Device class derived from the user agent and client hints, neither of which is stored.","enum":["desktop","mobile","tablet","bot","unknown"]},"AnalyticsHostname":{"type":"string","description":"WAE blob6. Host of an external referrer: lower case, without www., port, path or query.","pattern":"^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$","maxLength":253},"AnalyticsMillis":{"type":"integer","description":"Whole milliseconds (the server rounds), capped at ten minutes.","minimum":0,"maximum":600000},"AnalyticsNavType":{"type":"string","description":"WAE blob16. How the page was reached; restore = back/forward cache.","enum":["navigate","reload","back_forward","prerender","restore"]},"AnalyticsNodeKind":{"type":"string","description":"WAE blob5. The kind of node the path resolved to.","enum":["page","entry","archive"]},"AnalyticsOs":{"type":"string","description":"WAE blob14. Operating system family, without version.","enum":["windows","macos","ios","android","linux","chromeos","other"]},"AnalyticsRequestPath":{"type":"string","description":"WAE blob3. The requested path as received: percent-encoded printable ASCII, without query string or fragment, cut to 256 characters.","pattern":"^/[!-~]*$","maxLength":256},"AnalyticsSampleOneIn":{"type":"integer","description":"WAE double9. One in this many events of this kind was recorded (vitals sampling; render events under a flood); 1 = every event. Counts weight each event by this number. An integer, so a rate never suffers rounding.","minimum":1,"maximum":1000},"AnalyticsUtmValue":{"type":"string","description":"A normalised UTM value: lower case, a small alphabet, at most 64 characters. Values that do not fit are dropped, never stored as free text.","pattern":"^[a-z0-9][a-z0-9._+-]{0,63}$"},"AnalyticsVisitorHash":{"type":"string","description":"WAE blob15. Present only when the site owner turned on unique visitors and the request carried no GPC or DNT signal: the first 64 bits of SHA-256(dailySalt ‖ siteId ‖ ip ‖ userAgent) in hex. The salt is random, lives for one UTC day and is then deleted, so the hash can neither be recomputed nor linked across days.","pattern":"^[0-9a-f]{16}$"},"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"Locale":{"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"},"Path":{"type":"string","description":"Absolute URL path of a route the site owns: starts with '/', lower-case slug segments, no trailing slash except root.","pattern":"^/(?:[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*)?$","maxLength":1024},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}}}; -const schema331 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; -const schema332 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; -const schema333 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; -const schema334 = {"type":"string","description":"Absolute URL path of a route the site owns: starts with '/', lower-case slug segments, no trailing slash except root.","pattern":"^/(?:[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*)?$","maxLength":1024}; -const schema336 = {"type":"string","description":"WAE blob5. The kind of node the path resolved to.","enum":["page","entry","archive"]}; -const schema337 = {"type":"string","description":"WAE blob6. Host of an external referrer: lower case, without www., port, path or query.","pattern":"^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$","maxLength":253}; -const schema338 = {"type":"string","description":"WAE blob7. Where the view came from, classified by the server from the referrer host and utm_medium; internal = another page of this site.","enum":["direct","internal","search","social","ai","email","paid","referral"]}; -const schema339 = {"type":"string","description":"A normalised UTM value: lower case, a small alphabet, at most 64 characters. Values that do not fit are dropped, never stored as free text.","pattern":"^[a-z0-9][a-z0-9._+-]{0,63}$"}; -const schema342 = {"type":"string","description":"WAE blob11. ISO 3166-1 alpha-2 country from Cloudflare's request.cf.country (XX unknown, T1 Tor). Never a city or region.","pattern":"^[A-Z][A-Z0-9]$"}; -const schema343 = {"type":"string","description":"WAE blob12. Device class derived from the user agent and client hints, neither of which is stored.","enum":["desktop","mobile","tablet","bot","unknown"]}; -const schema344 = {"type":"string","description":"WAE blob13. Browser family, without version.","enum":["chrome","edge","firefox","safari","samsung","opera","other"]}; -const schema345 = {"type":"string","description":"WAE blob14. Operating system family, without version.","enum":["windows","macos","ios","android","linux","chromeos","other"]}; -const schema346 = {"type":"string","description":"WAE blob15. Present only when the site owner turned on unique visitors and the request carried no GPC or DNT signal: the first 64 bits of SHA-256(dailySalt ‖ siteId ‖ ip ‖ userAgent) in hex. The salt is random, lives for one UTC day and is then deleted, so the hash can neither be recomputed nor linked across days.","pattern":"^[0-9a-f]{16}$"}; -const schema347 = {"type":"string","description":"WAE blob16. How the page was reached; restore = back/forward cache.","enum":["navigate","reload","back_forward","prerender","restore"]}; -const schema348 = {"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"}; -const schema358 = {"type":"integer","description":"Whole milliseconds (the server rounds), capped at ten minutes.","minimum":0,"maximum":600000}; -const schema362 = {"type":"integer","description":"WAE double9. One in this many events of this kind was recorded (vitals sampling; render events under a flood); 1 = every event. Counts weight each event by this number. An integer, so a rate never suffers rounding.","minimum":1,"maximum":1000}; -const schema366 = {"type":"string","description":"WAE blob3. The requested path as received: percent-encoded printable ASCII, without query string or fragment, cut to 256 characters.","pattern":"^/[!-~]*$","maxLength":256}; -const schema373 = {"type":"string","description":"WAE blob18. A known crawler identified from the user agent (e.g. googlebot, gptbot, claudebot, perplexitybot). The list and each crawler's class (search, AI, social, monitor) live in code.","pattern":"^[a-z0-9][a-z0-9-]{0,31}$"}; -const pattern174 = new RegExp("^[a-z0-9][a-z0-9._+-]{0,63}$", "u"); -const pattern177 = new RegExp("^[A-Z][A-Z0-9]$", "u"); -const pattern178 = new RegExp("^[0-9a-f]{16}$", "u"); -const pattern185 = new RegExp("^/[!-~]*$", "u"); -const pattern187 = new RegExp("^(?:/|https://)[!-~]*$", "u"); -const pattern191 = new RegExp("^[a-z0-9][a-z0-9-]{0,31}$", "u"); +const schema332 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/analytics-event.schema.json","title":"AnalyticsEvent","description":"One site analytics event as stored (RFC-0006): a page view or a Core Web Vitals sample reported by the site's first-party script, or a response the site Worker rendered itself (cache miss, redirect, 404, 410, 5xx). Written by the site Worker after it validated and enriched a beacon. The named fields are the columns of the local/self-host SQLite store; each one documents its fixed slot in a Workers Analytics Engine data point (index1 = siteId, blob1 = kind, blob2 = schemaVersion). Slots never move and new fields are only appended. Never carries an IP address, a user agent, a full referrer URL, a query string or a cookie.","oneOf":[{"type":"object","title":"AnalyticsPageviewEvent","description":"A published page, article or archive was shown to a visitor (the site's script ran).","additionalProperties":false,"required":["schemaVersion","kind","siteId","timestamp","path","nodeId","nodeKind","channel","entrance","device","browser","os","navType"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"kind":{"type":"string","enum":["pageview"]},"siteId":{"$ref":"#/$defs/Id","description":"WAE index1."},"timestamp":{"$ref":"#/$defs/Timestamp"},"path":{"$ref":"#/$defs/Path","description":"WAE blob3. The published route the view resolved to; never a query string."},"nodeId":{"$ref":"#/$defs/Id","description":"WAE blob4. The page, entry or term the server resolved the path to, so stats survive slug changes."},"nodeKind":{"$ref":"#/$defs/AnalyticsNodeKind"},"refHost":{"$ref":"#/$defs/AnalyticsHostname"},"channel":{"$ref":"#/$defs/AnalyticsChannel"},"utmSource":{"$ref":"#/$defs/AnalyticsUtmValue","description":"WAE blob8."},"utmMedium":{"$ref":"#/$defs/AnalyticsUtmValue","description":"WAE blob9."},"utmCampaign":{"$ref":"#/$defs/AnalyticsUtmValue","description":"WAE blob10."},"country":{"$ref":"#/$defs/AnalyticsCountry"},"device":{"$ref":"#/$defs/AnalyticsDevice"},"browser":{"$ref":"#/$defs/AnalyticsBrowser"},"os":{"$ref":"#/$defs/AnalyticsOs"},"visitor":{"$ref":"#/$defs/AnalyticsVisitorHash"},"navType":{"$ref":"#/$defs/AnalyticsNavType"},"locale":{"$ref":"#/$defs/Locale","description":"WAE blob17. Locale of the resolved node, not the visitor's Accept-Language."},"entrance":{"type":"boolean","description":"WAE double1 (1 or 0). The view starts a visit: the referrer is empty or another site, and the navigation is not a reload, back/forward or restore."},"privacySignal":{"type":"boolean","description":"WAE double10 (1 or 0). The beacon request carried Global Privacy Control (Sec-GPC: 1) or Do Not Track (DNT: 1), so no visitor hash was formed. Only this flag is kept, never which signal or any header. Absent means no signal (and on events written before the field existed)."}}},{"type":"object","title":"AnalyticsVitalsEvent","description":"Core Web Vitals of one sampled page view, measured by the site's own script and sent when the page is hidden. Code checks that at least one metric is present (the schema subset has no cross-property rules).","additionalProperties":false,"required":["schemaVersion","kind","siteId","timestamp","path","nodeId","nodeKind","device","sampleOneIn"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"kind":{"type":"string","enum":["vitals"]},"siteId":{"$ref":"#/$defs/Id","description":"WAE index1."},"timestamp":{"$ref":"#/$defs/Timestamp"},"path":{"$ref":"#/$defs/Path","description":"WAE blob3."},"nodeId":{"$ref":"#/$defs/Id","description":"WAE blob4."},"nodeKind":{"$ref":"#/$defs/AnalyticsNodeKind"},"country":{"$ref":"#/$defs/AnalyticsCountry"},"device":{"$ref":"#/$defs/AnalyticsDevice"},"browser":{"$ref":"#/$defs/AnalyticsBrowser"},"lcp":{"$ref":"#/$defs/AnalyticsMillis","description":"WAE double3. Largest Contentful Paint."},"inp":{"$ref":"#/$defs/AnalyticsMillis","description":"WAE double4. Interaction to Next Paint."},"cls":{"type":"number","description":"WAE double5, stored multiplied by 1000. Cumulative Layout Shift (unitless).","minimum":0,"maximum":100},"ttfb":{"$ref":"#/$defs/AnalyticsMillis","description":"WAE double6. Time to First Byte."},"fcp":{"$ref":"#/$defs/AnalyticsMillis","description":"WAE double7. First Contentful Paint."},"sampleOneIn":{"$ref":"#/$defs/AnalyticsSampleOneIn"}}},{"type":"object","title":"AnalyticsRenderEvent","description":"The site Worker answered a request itself (a cache miss, redirect, 404, 410 or error). Cache hits never run the Worker, so these are not page views. The requested path is visitor-controlled text and is always shown as such.","additionalProperties":false,"required":["schemaVersion","kind","siteId","timestamp","path","status","device"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"kind":{"type":"string","enum":["render"]},"siteId":{"$ref":"#/$defs/Id","description":"WAE index1."},"timestamp":{"$ref":"#/$defs/Timestamp"},"path":{"$ref":"#/$defs/AnalyticsRequestPath"},"status":{"type":"integer","description":"WAE double2. HTTP status the Worker answered with.","minimum":100,"maximum":599},"nodeId":{"$ref":"#/$defs/Id","description":"WAE blob4. The node rendered or redirected from, when the path resolved to one."},"nodeKind":{"$ref":"#/$defs/AnalyticsNodeKind"},"redirectTo":{"type":"string","description":"WAE blob19. Target of a 301, 302 or 308: a site path or an https URL, without query string.","pattern":"^(?:/|https://)[!-~]*$","maxLength":512},"refHost":{"$ref":"#/$defs/AnalyticsHostname"},"refPath":{"$ref":"#/$defs/Path","description":"WAE blob20. Same-site referrers only: the page that linked here, which finds broken internal links."},"country":{"$ref":"#/$defs/AnalyticsCountry"},"device":{"$ref":"#/$defs/AnalyticsDevice"},"crawler":{"$ref":"#/$defs/AnalyticsCrawlerName"},"renderMs":{"$ref":"#/$defs/AnalyticsMillis","description":"WAE double8. Time the Worker spent answering."},"sampleOneIn":{"$ref":"#/$defs/AnalyticsSampleOneIn"}}}],"$defs":{"AnalyticsBrowser":{"type":"string","description":"WAE blob13. Browser family, without version.","enum":["chrome","edge","firefox","safari","samsung","opera","other"]},"AnalyticsChannel":{"type":"string","description":"WAE blob7. Where the view came from, classified by the server from the referrer host and utm_medium; internal = another page of this site.","enum":["direct","internal","search","social","ai","email","paid","referral"]},"AnalyticsCountry":{"type":"string","description":"WAE blob11. ISO 3166-1 alpha-2 country from Cloudflare's request.cf.country (XX unknown, T1 Tor). Never a city or region.","pattern":"^[A-Z][A-Z0-9]$"},"AnalyticsCrawlerName":{"type":"string","description":"WAE blob18. A known crawler identified from the user agent (e.g. googlebot, gptbot, claudebot, perplexitybot). The list and each crawler's class (search, AI, social, monitor) live in code.","pattern":"^[a-z0-9][a-z0-9-]{0,31}$"},"AnalyticsDevice":{"type":"string","description":"WAE blob12. Device class derived from the user agent and client hints, neither of which is stored.","enum":["desktop","mobile","tablet","bot","unknown"]},"AnalyticsHostname":{"type":"string","description":"WAE blob6. Host of an external referrer: lower case, without www., port, path or query.","pattern":"^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$","maxLength":253},"AnalyticsMillis":{"type":"integer","description":"Whole milliseconds (the server rounds), capped at ten minutes.","minimum":0,"maximum":600000},"AnalyticsNavType":{"type":"string","description":"WAE blob16. How the page was reached; restore = back/forward cache.","enum":["navigate","reload","back_forward","prerender","restore"]},"AnalyticsNodeKind":{"type":"string","description":"WAE blob5. The kind of node the path resolved to.","enum":["page","entry","archive"]},"AnalyticsOs":{"type":"string","description":"WAE blob14. Operating system family, without version.","enum":["windows","macos","ios","android","linux","chromeos","other"]},"AnalyticsRequestPath":{"type":"string","description":"WAE blob3. The requested path as received: percent-encoded printable ASCII, without query string or fragment, cut to 256 characters.","pattern":"^/[!-~]*$","maxLength":256},"AnalyticsSampleOneIn":{"type":"integer","description":"WAE double9. One in this many events of this kind was recorded (vitals sampling; render events under a flood); 1 = every event. Counts weight each event by this number. An integer, so a rate never suffers rounding.","minimum":1,"maximum":1000},"AnalyticsUtmValue":{"type":"string","description":"A normalised UTM value: lower case, a small alphabet, at most 64 characters. Values that do not fit are dropped, never stored as free text.","pattern":"^[a-z0-9][a-z0-9._+-]{0,63}$"},"AnalyticsVisitorHash":{"type":"string","description":"WAE blob15. Present only when the site owner turned on unique visitors and the request carried no GPC or DNT signal: the first 64 bits of SHA-256(dailySalt ‖ siteId ‖ ip ‖ userAgent) in hex. The salt is random, lives for one UTC day and is then deleted, so the hash can neither be recomputed nor linked across days.","pattern":"^[0-9a-f]{16}$"},"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"Locale":{"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"},"Path":{"type":"string","description":"Absolute URL path of a route the site owns: starts with '/', lower-case slug segments, no trailing slash except root.","pattern":"^/(?:[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*)?$","maxLength":1024},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}}}; +const schema333 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; +const schema334 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; +const schema335 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; +const schema336 = {"type":"string","description":"Absolute URL path of a route the site owns: starts with '/', lower-case slug segments, no trailing slash except root.","pattern":"^/(?:[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*)?$","maxLength":1024}; +const schema338 = {"type":"string","description":"WAE blob5. The kind of node the path resolved to.","enum":["page","entry","archive"]}; +const schema339 = {"type":"string","description":"WAE blob6. Host of an external referrer: lower case, without www., port, path or query.","pattern":"^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$","maxLength":253}; +const schema340 = {"type":"string","description":"WAE blob7. Where the view came from, classified by the server from the referrer host and utm_medium; internal = another page of this site.","enum":["direct","internal","search","social","ai","email","paid","referral"]}; +const schema341 = {"type":"string","description":"A normalised UTM value: lower case, a small alphabet, at most 64 characters. Values that do not fit are dropped, never stored as free text.","pattern":"^[a-z0-9][a-z0-9._+-]{0,63}$"}; +const schema344 = {"type":"string","description":"WAE blob11. ISO 3166-1 alpha-2 country from Cloudflare's request.cf.country (XX unknown, T1 Tor). Never a city or region.","pattern":"^[A-Z][A-Z0-9]$"}; +const schema345 = {"type":"string","description":"WAE blob12. Device class derived from the user agent and client hints, neither of which is stored.","enum":["desktop","mobile","tablet","bot","unknown"]}; +const schema346 = {"type":"string","description":"WAE blob13. Browser family, without version.","enum":["chrome","edge","firefox","safari","samsung","opera","other"]}; +const schema347 = {"type":"string","description":"WAE blob14. Operating system family, without version.","enum":["windows","macos","ios","android","linux","chromeos","other"]}; +const schema348 = {"type":"string","description":"WAE blob15. Present only when the site owner turned on unique visitors and the request carried no GPC or DNT signal: the first 64 bits of SHA-256(dailySalt ‖ siteId ‖ ip ‖ userAgent) in hex. The salt is random, lives for one UTC day and is then deleted, so the hash can neither be recomputed nor linked across days.","pattern":"^[0-9a-f]{16}$"}; +const schema349 = {"type":"string","description":"WAE blob16. How the page was reached; restore = back/forward cache.","enum":["navigate","reload","back_forward","prerender","restore"]}; +const schema350 = {"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"}; +const schema360 = {"type":"integer","description":"Whole milliseconds (the server rounds), capped at ten minutes.","minimum":0,"maximum":600000}; +const schema364 = {"type":"integer","description":"WAE double9. One in this many events of this kind was recorded (vitals sampling; render events under a flood); 1 = every event. Counts weight each event by this number. An integer, so a rate never suffers rounding.","minimum":1,"maximum":1000}; +const schema368 = {"type":"string","description":"WAE blob3. The requested path as received: percent-encoded printable ASCII, without query string or fragment, cut to 256 characters.","pattern":"^/[!-~]*$","maxLength":256}; +const schema375 = {"type":"string","description":"WAE blob18. A known crawler identified from the user agent (e.g. googlebot, gptbot, claudebot, perplexitybot). The list and each crawler's class (search, AI, social, monitor) live in code.","pattern":"^[a-z0-9][a-z0-9-]{0,31}$"}; +const pattern176 = new RegExp("^[a-z0-9][a-z0-9._+-]{0,63}$", "u"); +const pattern179 = new RegExp("^[A-Z][A-Z0-9]$", "u"); +const pattern180 = new RegExp("^[0-9a-f]{16}$", "u"); +const pattern187 = new RegExp("^/[!-~]*$", "u"); +const pattern189 = new RegExp("^(?:/|https://)[!-~]*$", "u"); +const pattern193 = new RegExp("^[a-z0-9][a-z0-9-]{0,31}$", "u"); function validate149(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ /*# sourceURL="https://cloudhal.dev/schemas/v1/analytics-event.schema.json" */; @@ -24435,7 +24560,7 @@ vErrors.push(err12); errors++; } for(const key0 in data){ -if(!(func1.call(schema330.oneOf[0].properties, key0))){ +if(!(func1.call(schema332.oneOf[0].properties, key0))){ const err13 = {instancePath,schemaPath:"#/oneOf/0/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}; if(vErrors === null){ vErrors = [err13]; @@ -24459,7 +24584,7 @@ vErrors.push(err14); errors++; } if(!(data0 === 1)){ -const err15 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema331.enum},message:"must be equal to one of the allowed values"}; +const err15 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema333.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err15]; } @@ -24482,7 +24607,7 @@ vErrors.push(err16); errors++; } if(!(data1 === "pageview")){ -const err17 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema330.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err17 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema332.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err17]; } @@ -24575,7 +24700,7 @@ vErrors.push(err24); } errors++; } -if(!pattern40.test(data4)){ +if(!pattern42.test(data4)){ const err25 = {instancePath:instancePath+"/path",schemaPath:"#/$defs/Path/pattern",keyword:"pattern",params:{pattern: "^/(?:[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*)?$"},message:"must match pattern \""+"^/(?:[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*)?$"+"\""}; if(vErrors === null){ vErrors = [err25]; @@ -24655,7 +24780,7 @@ vErrors.push(err31); errors++; } if(!(((data6 === "page") || (data6 === "entry")) || (data6 === "archive"))){ -const err32 = {instancePath:instancePath+"/nodeKind",schemaPath:"#/$defs/AnalyticsNodeKind/enum",keyword:"enum",params:{allowedValues: schema336.enum},message:"must be equal to one of the allowed values"}; +const err32 = {instancePath:instancePath+"/nodeKind",schemaPath:"#/$defs/AnalyticsNodeKind/enum",keyword:"enum",params:{allowedValues: schema338.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err32]; } @@ -24713,7 +24838,7 @@ vErrors.push(err36); errors++; } if(!((((((((data8 === "direct") || (data8 === "internal")) || (data8 === "search")) || (data8 === "social")) || (data8 === "ai")) || (data8 === "email")) || (data8 === "paid")) || (data8 === "referral"))){ -const err37 = {instancePath:instancePath+"/channel",schemaPath:"#/$defs/AnalyticsChannel/enum",keyword:"enum",params:{allowedValues: schema338.enum},message:"must be equal to one of the allowed values"}; +const err37 = {instancePath:instancePath+"/channel",schemaPath:"#/$defs/AnalyticsChannel/enum",keyword:"enum",params:{allowedValues: schema340.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err37]; } @@ -24726,7 +24851,7 @@ errors++; if(data.utmSource !== undefined){ let data9 = data.utmSource; if(typeof data9 === "string"){ -if(!pattern174.test(data9)){ +if(!pattern176.test(data9)){ const err38 = {instancePath:instancePath+"/utmSource",schemaPath:"#/$defs/AnalyticsUtmValue/pattern",keyword:"pattern",params:{pattern: "^[a-z0-9][a-z0-9._+-]{0,63}$"},message:"must match pattern \""+"^[a-z0-9][a-z0-9._+-]{0,63}$"+"\""}; if(vErrors === null){ vErrors = [err38]; @@ -24751,7 +24876,7 @@ errors++; if(data.utmMedium !== undefined){ let data10 = data.utmMedium; if(typeof data10 === "string"){ -if(!pattern174.test(data10)){ +if(!pattern176.test(data10)){ const err40 = {instancePath:instancePath+"/utmMedium",schemaPath:"#/$defs/AnalyticsUtmValue/pattern",keyword:"pattern",params:{pattern: "^[a-z0-9][a-z0-9._+-]{0,63}$"},message:"must match pattern \""+"^[a-z0-9][a-z0-9._+-]{0,63}$"+"\""}; if(vErrors === null){ vErrors = [err40]; @@ -24776,7 +24901,7 @@ errors++; if(data.utmCampaign !== undefined){ let data11 = data.utmCampaign; if(typeof data11 === "string"){ -if(!pattern174.test(data11)){ +if(!pattern176.test(data11)){ const err42 = {instancePath:instancePath+"/utmCampaign",schemaPath:"#/$defs/AnalyticsUtmValue/pattern",keyword:"pattern",params:{pattern: "^[a-z0-9][a-z0-9._+-]{0,63}$"},message:"must match pattern \""+"^[a-z0-9][a-z0-9._+-]{0,63}$"+"\""}; if(vErrors === null){ vErrors = [err42]; @@ -24801,7 +24926,7 @@ errors++; if(data.country !== undefined){ let data12 = data.country; if(typeof data12 === "string"){ -if(!pattern177.test(data12)){ +if(!pattern179.test(data12)){ const err44 = {instancePath:instancePath+"/country",schemaPath:"#/$defs/AnalyticsCountry/pattern",keyword:"pattern",params:{pattern: "^[A-Z][A-Z0-9]$"},message:"must match pattern \""+"^[A-Z][A-Z0-9]$"+"\""}; if(vErrors === null){ vErrors = [err44]; @@ -24836,7 +24961,7 @@ vErrors.push(err46); errors++; } if(!(((((data13 === "desktop") || (data13 === "mobile")) || (data13 === "tablet")) || (data13 === "bot")) || (data13 === "unknown"))){ -const err47 = {instancePath:instancePath+"/device",schemaPath:"#/$defs/AnalyticsDevice/enum",keyword:"enum",params:{allowedValues: schema343.enum},message:"must be equal to one of the allowed values"}; +const err47 = {instancePath:instancePath+"/device",schemaPath:"#/$defs/AnalyticsDevice/enum",keyword:"enum",params:{allowedValues: schema345.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err47]; } @@ -24859,7 +24984,7 @@ vErrors.push(err48); errors++; } if(!(((((((data14 === "chrome") || (data14 === "edge")) || (data14 === "firefox")) || (data14 === "safari")) || (data14 === "samsung")) || (data14 === "opera")) || (data14 === "other"))){ -const err49 = {instancePath:instancePath+"/browser",schemaPath:"#/$defs/AnalyticsBrowser/enum",keyword:"enum",params:{allowedValues: schema344.enum},message:"must be equal to one of the allowed values"}; +const err49 = {instancePath:instancePath+"/browser",schemaPath:"#/$defs/AnalyticsBrowser/enum",keyword:"enum",params:{allowedValues: schema346.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err49]; } @@ -24882,7 +25007,7 @@ vErrors.push(err50); errors++; } if(!(((((((data15 === "windows") || (data15 === "macos")) || (data15 === "ios")) || (data15 === "android")) || (data15 === "linux")) || (data15 === "chromeos")) || (data15 === "other"))){ -const err51 = {instancePath:instancePath+"/os",schemaPath:"#/$defs/AnalyticsOs/enum",keyword:"enum",params:{allowedValues: schema345.enum},message:"must be equal to one of the allowed values"}; +const err51 = {instancePath:instancePath+"/os",schemaPath:"#/$defs/AnalyticsOs/enum",keyword:"enum",params:{allowedValues: schema347.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err51]; } @@ -24895,7 +25020,7 @@ errors++; if(data.visitor !== undefined){ let data16 = data.visitor; if(typeof data16 === "string"){ -if(!pattern178.test(data16)){ +if(!pattern180.test(data16)){ const err52 = {instancePath:instancePath+"/visitor",schemaPath:"#/$defs/AnalyticsVisitorHash/pattern",keyword:"pattern",params:{pattern: "^[0-9a-f]{16}$"},message:"must match pattern \""+"^[0-9a-f]{16}$"+"\""}; if(vErrors === null){ vErrors = [err52]; @@ -24930,7 +25055,7 @@ vErrors.push(err54); errors++; } if(!(((((data17 === "navigate") || (data17 === "reload")) || (data17 === "back_forward")) || (data17 === "prerender")) || (data17 === "restore"))){ -const err55 = {instancePath:instancePath+"/navType",schemaPath:"#/$defs/AnalyticsNavType/enum",keyword:"enum",params:{allowedValues: schema347.enum},message:"must be equal to one of the allowed values"}; +const err55 = {instancePath:instancePath+"/navType",schemaPath:"#/$defs/AnalyticsNavType/enum",keyword:"enum",params:{allowedValues: schema349.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err55]; } @@ -25099,7 +25224,7 @@ vErrors.push(err69); errors++; } for(const key1 in data){ -if(!(func1.call(schema330.oneOf[1].properties, key1))){ +if(!(func1.call(schema332.oneOf[1].properties, key1))){ const err70 = {instancePath,schemaPath:"#/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"}; if(vErrors === null){ vErrors = [err70]; @@ -25123,7 +25248,7 @@ vErrors.push(err71); errors++; } if(!(data21 === 1)){ -const err72 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema331.enum},message:"must be equal to one of the allowed values"}; +const err72 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema333.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err72]; } @@ -25146,7 +25271,7 @@ vErrors.push(err73); errors++; } if(!(data22 === "vitals")){ -const err74 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema330.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err74 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema332.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err74]; } @@ -25239,7 +25364,7 @@ vErrors.push(err81); } errors++; } -if(!pattern40.test(data25)){ +if(!pattern42.test(data25)){ const err82 = {instancePath:instancePath+"/path",schemaPath:"#/$defs/Path/pattern",keyword:"pattern",params:{pattern: "^/(?:[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*)?$"},message:"must match pattern \""+"^/(?:[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*)?$"+"\""}; if(vErrors === null){ vErrors = [err82]; @@ -25319,7 +25444,7 @@ vErrors.push(err88); errors++; } if(!(((data27 === "page") || (data27 === "entry")) || (data27 === "archive"))){ -const err89 = {instancePath:instancePath+"/nodeKind",schemaPath:"#/$defs/AnalyticsNodeKind/enum",keyword:"enum",params:{allowedValues: schema336.enum},message:"must be equal to one of the allowed values"}; +const err89 = {instancePath:instancePath+"/nodeKind",schemaPath:"#/$defs/AnalyticsNodeKind/enum",keyword:"enum",params:{allowedValues: schema338.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err89]; } @@ -25332,7 +25457,7 @@ errors++; if(data.country !== undefined){ let data28 = data.country; if(typeof data28 === "string"){ -if(!pattern177.test(data28)){ +if(!pattern179.test(data28)){ const err90 = {instancePath:instancePath+"/country",schemaPath:"#/$defs/AnalyticsCountry/pattern",keyword:"pattern",params:{pattern: "^[A-Z][A-Z0-9]$"},message:"must match pattern \""+"^[A-Z][A-Z0-9]$"+"\""}; if(vErrors === null){ vErrors = [err90]; @@ -25367,7 +25492,7 @@ vErrors.push(err92); errors++; } if(!(((((data29 === "desktop") || (data29 === "mobile")) || (data29 === "tablet")) || (data29 === "bot")) || (data29 === "unknown"))){ -const err93 = {instancePath:instancePath+"/device",schemaPath:"#/$defs/AnalyticsDevice/enum",keyword:"enum",params:{allowedValues: schema343.enum},message:"must be equal to one of the allowed values"}; +const err93 = {instancePath:instancePath+"/device",schemaPath:"#/$defs/AnalyticsDevice/enum",keyword:"enum",params:{allowedValues: schema345.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err93]; } @@ -25390,7 +25515,7 @@ vErrors.push(err94); errors++; } if(!(((((((data30 === "chrome") || (data30 === "edge")) || (data30 === "firefox")) || (data30 === "safari")) || (data30 === "samsung")) || (data30 === "opera")) || (data30 === "other"))){ -const err95 = {instancePath:instancePath+"/browser",schemaPath:"#/$defs/AnalyticsBrowser/enum",keyword:"enum",params:{allowedValues: schema344.enum},message:"must be equal to one of the allowed values"}; +const err95 = {instancePath:instancePath+"/browser",schemaPath:"#/$defs/AnalyticsBrowser/enum",keyword:"enum",params:{allowedValues: schema346.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err95]; } @@ -25707,7 +25832,7 @@ vErrors.push(err121); errors++; } for(const key2 in data){ -if(!(func1.call(schema330.oneOf[2].properties, key2))){ +if(!(func1.call(schema332.oneOf[2].properties, key2))){ const err122 = {instancePath,schemaPath:"#/oneOf/2/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"}; if(vErrors === null){ vErrors = [err122]; @@ -25731,7 +25856,7 @@ vErrors.push(err123); errors++; } if(!(data37 === 1)){ -const err124 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema331.enum},message:"must be equal to one of the allowed values"}; +const err124 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema333.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err124]; } @@ -25754,7 +25879,7 @@ vErrors.push(err125); errors++; } if(!(data38 === "render")){ -const err126 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/2/properties/kind/enum",keyword:"enum",params:{allowedValues: schema330.oneOf[2].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err126 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/2/properties/kind/enum",keyword:"enum",params:{allowedValues: schema332.oneOf[2].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err126]; } @@ -25847,7 +25972,7 @@ vErrors.push(err133); } errors++; } -if(!pattern185.test(data41)){ +if(!pattern187.test(data41)){ const err134 = {instancePath:instancePath+"/path",schemaPath:"#/$defs/AnalyticsRequestPath/pattern",keyword:"pattern",params:{pattern: "^/[!-~]*$"},message:"must match pattern \""+"^/[!-~]*$"+"\""}; if(vErrors === null){ vErrors = [err134]; @@ -25962,7 +26087,7 @@ vErrors.push(err143); errors++; } if(!(((data44 === "page") || (data44 === "entry")) || (data44 === "archive"))){ -const err144 = {instancePath:instancePath+"/nodeKind",schemaPath:"#/$defs/AnalyticsNodeKind/enum",keyword:"enum",params:{allowedValues: schema336.enum},message:"must be equal to one of the allowed values"}; +const err144 = {instancePath:instancePath+"/nodeKind",schemaPath:"#/$defs/AnalyticsNodeKind/enum",keyword:"enum",params:{allowedValues: schema338.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err144]; } @@ -25985,7 +26110,7 @@ vErrors.push(err145); } errors++; } -if(!pattern187.test(data45)){ +if(!pattern189.test(data45)){ const err146 = {instancePath:instancePath+"/redirectTo",schemaPath:"#/oneOf/2/properties/redirectTo/pattern",keyword:"pattern",params:{pattern: "^(?:/|https://)[!-~]*$"},message:"must match pattern \""+"^(?:/|https://)[!-~]*$"+"\""}; if(vErrors === null){ vErrors = [err146]; @@ -26055,7 +26180,7 @@ vErrors.push(err151); } errors++; } -if(!pattern40.test(data47)){ +if(!pattern42.test(data47)){ const err152 = {instancePath:instancePath+"/refPath",schemaPath:"#/$defs/Path/pattern",keyword:"pattern",params:{pattern: "^/(?:[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*)?$"},message:"must match pattern \""+"^/(?:[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*)?$"+"\""}; if(vErrors === null){ vErrors = [err152]; @@ -26080,7 +26205,7 @@ errors++; if(data.country !== undefined){ let data48 = data.country; if(typeof data48 === "string"){ -if(!pattern177.test(data48)){ +if(!pattern179.test(data48)){ const err154 = {instancePath:instancePath+"/country",schemaPath:"#/$defs/AnalyticsCountry/pattern",keyword:"pattern",params:{pattern: "^[A-Z][A-Z0-9]$"},message:"must match pattern \""+"^[A-Z][A-Z0-9]$"+"\""}; if(vErrors === null){ vErrors = [err154]; @@ -26115,7 +26240,7 @@ vErrors.push(err156); errors++; } if(!(((((data49 === "desktop") || (data49 === "mobile")) || (data49 === "tablet")) || (data49 === "bot")) || (data49 === "unknown"))){ -const err157 = {instancePath:instancePath+"/device",schemaPath:"#/$defs/AnalyticsDevice/enum",keyword:"enum",params:{allowedValues: schema343.enum},message:"must be equal to one of the allowed values"}; +const err157 = {instancePath:instancePath+"/device",schemaPath:"#/$defs/AnalyticsDevice/enum",keyword:"enum",params:{allowedValues: schema345.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err157]; } @@ -26128,7 +26253,7 @@ errors++; if(data.crawler !== undefined){ let data50 = data.crawler; if(typeof data50 === "string"){ -if(!pattern191.test(data50)){ +if(!pattern193.test(data50)){ const err158 = {instancePath:instancePath+"/crawler",schemaPath:"#/$defs/AnalyticsCrawlerName/pattern",keyword:"pattern",params:{pattern: "^[a-z0-9][a-z0-9-]{0,31}$"},message:"must match pattern \""+"^[a-z0-9][a-z0-9-]{0,31}$"+"\""}; if(vErrors === null){ vErrors = [err158]; @@ -26274,12 +26399,12 @@ return errors === 0; validate149.evaluated = {"dynamicProps":true,"dynamicItems":false}; export const validateAnalyticsBeacon = validate150; -const schema376 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/analytics-beacon.schema.json","title":"AnalyticsBeacon","description":"What a site's first-party analytics script sends to POST /__cloudhal/e (RFC-0006): JSON as text/plain, at most 2 KB. Everything in it is untrusted. The server keeps a beacon only after the same-origin, bot and rate checks, normalises the path and resolves it to a published route, reduces the referrer to a host, normalises UTM values, and derives everything else (node, country, device, visitor) itself. The script never sends an identifier, a cookie or a full URL.","oneOf":[{"type":"object","title":"AnalyticsPageviewBeacon","description":"Sent once per page view, when the page is first visible (after prerendering, and again on a back/forward cache restore).","additionalProperties":false,"required":["schemaVersion","kind","path"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"kind":{"type":"string","enum":["pageview"]},"path":{"$ref":"#/$defs/BeaconPath"},"referrer":{"type":"string","description":"The host of document.referrer only (the script drops scheme, path and query); empty or absent for direct visits.","maxLength":253},"utmSource":{"$ref":"#/$defs/BeaconUtm"},"utmMedium":{"$ref":"#/$defs/BeaconUtm"},"utmCampaign":{"$ref":"#/$defs/BeaconUtm"},"navType":{"type":"string","enum":["navigate","reload","back_forward","prerender","restore"]}}},{"type":"object","title":"AnalyticsVitalsBeacon","description":"Sent once, when a sampled page view is hidden, with the Core Web Vitals measured so far.","additionalProperties":false,"required":["schemaVersion","kind","path","sampleOneIn"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"kind":{"type":"string","enum":["vitals"]},"path":{"$ref":"#/$defs/BeaconPath"},"lcp":{"$ref":"#/$defs/BeaconMillis"},"inp":{"$ref":"#/$defs/BeaconMillis"},"cls":{"type":"number","minimum":0,"maximum":100},"ttfb":{"$ref":"#/$defs/BeaconMillis"},"fcp":{"$ref":"#/$defs/BeaconMillis"},"sampleOneIn":{"type":"integer","description":"The vitals sampling the script was served with (one page view in N); the server checks it against the site's setting.","minimum":1,"maximum":1000}}}],"$defs":{"BeaconMillis":{"type":"number","minimum":0,"maximum":600000},"BeaconPath":{"type":"string","description":"location.pathname as the browser reports it (no query string, no fragment).","pattern":"^/[^?#\\s]*$","maxLength":1024},"BeaconUtm":{"type":"string","description":"A raw utm_* value from the page URL; the server lower-cases it and drops it unless it fits the stored alphabet.","minLength":1,"maxLength":128},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}}}; -const schema377 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; -const schema378 = {"type":"string","description":"location.pathname as the browser reports it (no query string, no fragment).","pattern":"^/[^?#\\s]*$","maxLength":1024}; -const schema379 = {"type":"string","description":"A raw utm_* value from the page URL; the server lower-cases it and drops it unless it fits the stored alphabet.","minLength":1,"maxLength":128}; -const schema384 = {"type":"number","minimum":0,"maximum":600000}; -const pattern192 = new RegExp("^/[^?#\\s]*$", "u"); +const schema378 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/analytics-beacon.schema.json","title":"AnalyticsBeacon","description":"What a site's first-party analytics script sends to POST /__cloudhal/e (RFC-0006): JSON as text/plain, at most 2 KB. Everything in it is untrusted. The server keeps a beacon only after the same-origin, bot and rate checks, normalises the path and resolves it to a published route, reduces the referrer to a host, normalises UTM values, and derives everything else (node, country, device, visitor) itself. The script never sends an identifier, a cookie or a full URL.","oneOf":[{"type":"object","title":"AnalyticsPageviewBeacon","description":"Sent once per page view, when the page is first visible (after prerendering, and again on a back/forward cache restore).","additionalProperties":false,"required":["schemaVersion","kind","path"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"kind":{"type":"string","enum":["pageview"]},"path":{"$ref":"#/$defs/BeaconPath"},"referrer":{"type":"string","description":"The host of document.referrer only (the script drops scheme, path and query); empty or absent for direct visits.","maxLength":253},"utmSource":{"$ref":"#/$defs/BeaconUtm"},"utmMedium":{"$ref":"#/$defs/BeaconUtm"},"utmCampaign":{"$ref":"#/$defs/BeaconUtm"},"navType":{"type":"string","enum":["navigate","reload","back_forward","prerender","restore"]}}},{"type":"object","title":"AnalyticsVitalsBeacon","description":"Sent once, when a sampled page view is hidden, with the Core Web Vitals measured so far.","additionalProperties":false,"required":["schemaVersion","kind","path","sampleOneIn"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"kind":{"type":"string","enum":["vitals"]},"path":{"$ref":"#/$defs/BeaconPath"},"lcp":{"$ref":"#/$defs/BeaconMillis"},"inp":{"$ref":"#/$defs/BeaconMillis"},"cls":{"type":"number","minimum":0,"maximum":100},"ttfb":{"$ref":"#/$defs/BeaconMillis"},"fcp":{"$ref":"#/$defs/BeaconMillis"},"sampleOneIn":{"type":"integer","description":"The vitals sampling the script was served with (one page view in N); the server checks it against the site's setting.","minimum":1,"maximum":1000}}}],"$defs":{"BeaconMillis":{"type":"number","minimum":0,"maximum":600000},"BeaconPath":{"type":"string","description":"location.pathname as the browser reports it (no query string, no fragment).","pattern":"^/[^?#\\s]*$","maxLength":1024},"BeaconUtm":{"type":"string","description":"A raw utm_* value from the page URL; the server lower-cases it and drops it unless it fits the stored alphabet.","minLength":1,"maxLength":128},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}}}; +const schema379 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; +const schema380 = {"type":"string","description":"location.pathname as the browser reports it (no query string, no fragment).","pattern":"^/[^?#\\s]*$","maxLength":1024}; +const schema381 = {"type":"string","description":"A raw utm_* value from the page URL; the server lower-cases it and drops it unless it fits the stored alphabet.","minLength":1,"maxLength":128}; +const schema386 = {"type":"number","minimum":0,"maximum":600000}; +const pattern194 = new RegExp("^/[^?#\\s]*$", "u"); function validate150(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ /*# sourceURL="https://cloudhal.dev/schemas/v1/analytics-beacon.schema.json" */; @@ -26352,7 +26477,7 @@ vErrors.push(err4); errors++; } if(!(data0 === 1)){ -const err5 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema377.enum},message:"must be equal to one of the allowed values"}; +const err5 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema379.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err5]; } @@ -26375,7 +26500,7 @@ vErrors.push(err6); errors++; } if(!(data1 === "pageview")){ -const err7 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema376.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err7 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema378.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err7]; } @@ -26398,7 +26523,7 @@ vErrors.push(err8); } errors++; } -if(!pattern192.test(data2)){ +if(!pattern194.test(data2)){ const err9 = {instancePath:instancePath+"/path",schemaPath:"#/$defs/BeaconPath/pattern",keyword:"pattern",params:{pattern: "^/[^?#\\s]*$"},message:"must match pattern \""+"^/[^?#\\s]*$"+"\""}; if(vErrors === null){ vErrors = [err9]; @@ -26563,7 +26688,7 @@ vErrors.push(err22); errors++; } if(!(((((data7 === "navigate") || (data7 === "reload")) || (data7 === "back_forward")) || (data7 === "prerender")) || (data7 === "restore"))){ -const err23 = {instancePath:instancePath+"/navType",schemaPath:"#/oneOf/0/properties/navType/enum",keyword:"enum",params:{allowedValues: schema376.oneOf[0].properties.navType.enum},message:"must be equal to one of the allowed values"}; +const err23 = {instancePath:instancePath+"/navType",schemaPath:"#/oneOf/0/properties/navType/enum",keyword:"enum",params:{allowedValues: schema378.oneOf[0].properties.navType.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err23]; } @@ -26633,7 +26758,7 @@ vErrors.push(err28); errors++; } for(const key1 in data){ -if(!(func1.call(schema376.oneOf[1].properties, key1))){ +if(!(func1.call(schema378.oneOf[1].properties, key1))){ const err29 = {instancePath,schemaPath:"#/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"}; if(vErrors === null){ vErrors = [err29]; @@ -26657,7 +26782,7 @@ vErrors.push(err30); errors++; } if(!(data8 === 1)){ -const err31 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema377.enum},message:"must be equal to one of the allowed values"}; +const err31 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema379.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err31]; } @@ -26680,7 +26805,7 @@ vErrors.push(err32); errors++; } if(!(data9 === "vitals")){ -const err33 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema376.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err33 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema378.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err33]; } @@ -26703,7 +26828,7 @@ vErrors.push(err34); } errors++; } -if(!pattern192.test(data10)){ +if(!pattern194.test(data10)){ const err35 = {instancePath:instancePath+"/path",schemaPath:"#/$defs/BeaconPath/pattern",keyword:"pattern",params:{pattern: "^/[^?#\\s]*$"},message:"must match pattern \""+"^/[^?#\\s]*$"+"\""}; if(vErrors === null){ vErrors = [err35]; @@ -26988,24 +27113,24 @@ return errors === 0; validate150.evaluated = {"dynamicProps":true,"dynamicItems":false}; export const validateComment = validate151; -const schema388 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/comment.schema.json","title":"Comment","description":"A reader comment on a published content entry, as stored in D1 (outside the site graph), returned to people and agents, and exported (RFC-0007). It is not a graph node: it never enters a snapshot, a diff or a site bundle, and a content rollback never changes it. The record carries no personal data beyond the display name the commenter chose: an email address, an IP address or its hash, a user agent and a proof-of-work challenge live in separate columns and tables that this contract cannot express (additionalProperties: false), so no reader of the contract — Studio lists, MCP tools, exports — can leak them. Text is plain text; renderers escape it and turn only https links into anchors with rel=\"ugc nofollow noopener noreferrer\".","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","nodeId","source","author","status","createdAt","rev"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/CommentId"},"siteId":{"$ref":"#/$defs/Id"},"nodeId":{"$ref":"#/$defs/Id","description":"The content entry the comment belongs to, by id, so threads survive slug changes (RFC-0002)."},"parentId":{"$ref":"#/$defs/CommentId","description":"The comment this one is nested under for display. Absent for a top-level comment. In v1 the tree is two levels deep: a parent is always top-level, and a reply to a reply is nested under the same top-level comment with `inReplyToId` pointing at the comment it answers."},"inReplyToId":{"$ref":"#/$defs/CommentId","description":"The comment this one answers when that is not `parentId` itself (a reply to a reply). Only with `parentId`, in the same thread."},"source":{"type":"string","description":"Where the comment came from: `native` (the site's own form or Studio), `import` (an importer; see `origin`). `webmention` and `activitypub` are reserved for later RFCs and are refused by v1 writers.","enum":["native","import","webmention","activitypub"]},"origin":{"$ref":"#/$defs/CommentOrigin"},"author":{"$ref":"#/$defs/CommentAuthor"},"body":{"$ref":"#/$defs/CommentBody"},"status":{"$ref":"#/$defs/CommentStatus"},"labels":{"type":"array","description":"Advisory labels from rules, classifiers, agents and people. Labels never change what readers see; only a status change does.","items":{"$ref":"#/$defs/CommentLabel"},"maxItems":32},"moderation":{"$ref":"#/$defs/CommentModeration"},"createdAt":{"$ref":"#/$defs/Timestamp"},"editedAt":{"$ref":"#/$defs/Timestamp","description":"Set when the text changed after it was first stored (v1.1 edit window; staff replies edited in Studio)."},"rev":{"$ref":"#/$defs/Revision"}},"$defs":{"Actor":{"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}},"ActorKind":{"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]},"CommentAuthor":{"description":"Who wrote the comment, discriminated by `kind`. There is deliberately no email, address or account handle here.","oneOf":[{"type":"object","title":"GuestCommentAuthor","description":"A guest: only the display name they typed. Guest comments are always held for moderation in v1.","additionalProperties":false,"required":["kind","name"],"properties":{"kind":{"type":"string","enum":["guest"]},"name":{"$ref":"#/$defs/CommentDisplayName"}}},{"type":"object","title":"ReaderCommentAuthor","description":"A verified reader (v1.1: email code or magic link on the site's own host). `readerId` is the site's reader account id, never the email.","additionalProperties":false,"required":["kind","readerId","name"],"properties":{"kind":{"type":"string","enum":["reader"]},"readerId":{"$ref":"#/$defs/Id"},"name":{"$ref":"#/$defs/CommentDisplayName"}}},{"type":"object","title":"StaffCommentAuthor","description":"A reply on the site's behalf. It is always attributed to a person (`personId` is a staff actor id, `user_…`): an agent may draft the text (`draftedBy`), but a person approved it. `draftedBy` names the agent that drafted the text, when one did (shown in Studio and the audit, not on the public page).","additionalProperties":false,"required":["kind","personId","name","badge"],"properties":{"kind":{"type":"string","enum":["staff"]},"personId":{"type":"string","description":"The person who wrote or approved the reply.","pattern":"^user_[A-Za-z0-9]{6,32}$"},"name":{"$ref":"#/$defs/CommentDisplayName"},"badge":{"type":"string","description":"What the public page shows next to the name: `author` when the person wrote the entry, otherwise `team`.","enum":["author","team"]},"draftedBy":{"$ref":"#/$defs/Actor"}}},{"type":"object","title":"ErasedCommentAuthor","description":"The commenter's identity was erased on request (RFC-0007 privacy model). Pages show a localised \"Anonymous\".","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["erased"]}}}]},"CommentBody":{"type":"object","description":"The comment text. v1 has only `text`: plain text with line breaks, no markup. A later RFC may add a restricted Markdown format. Absent only when the text was deleted (status `deleted`, or an erasure that removed the text); the record then stays as a tombstone while approved replies hang under it.","additionalProperties":false,"required":["format","text"],"properties":{"format":{"type":"string","enum":["text"]},"text":{"type":"string","description":"NFC-normalised, CRLF turned into LF, trimmed: 1 to 5,000 characters. Tabs and line feeds are the only control characters allowed; bidirectional overrides are refused so a comment cannot disguise itself in Studio.","minLength":1,"maxLength":5000,"pattern":"^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$"}}},"CommentDisplayName":{"type":"string","description":"Plain text chosen by the commenter (or the staff member's name), NFC-normalised and trimmed by the server: 1 to 80 characters, no control characters, no bidirectional overrides, no surrounding whitespace.","minLength":1,"maxLength":80,"pattern":"^[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069](?:[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069])?$"},"CommentId":{"type":"string","description":"A comment id: `cmt_` and 6 to 32 letters or digits.","pattern":"^cmt_[A-Za-z0-9]{6,32}$"},"CommentLabel":{"type":"object","description":"An advisory label. `rule` labels come from the submission pipeline (word lists, link count, instruction-like text); `classifier` from an opt-in classifier (Workers AI, Akismet); `agent` from `label_comments`; `person` from Studio. `by` is the agent or person that set it (absent for rules and classifiers).","additionalProperties":false,"required":["label","source","at"],"properties":{"label":{"type":"string","enum":["spam-suspect","toxic","off-topic","question","needs-reply","praise","personal-data","links","watch-word","instruction-like"]},"source":{"type":"string","enum":["rule","classifier","agent","person"]},"by":{"$ref":"#/$defs/Actor"},"confidence":{"type":"integer","description":"0 to 100, when the source reports one.","minimum":0,"maximum":100},"at":{"$ref":"#/$defs/Timestamp"}}},"CommentModeration":{"type":"object","description":"The latest moderation decision, kept as a statement of reasons (EU DSA Art. 17). The full history is in `comment_events`. Approvals are always a person's decision; agents may only hide, mark as spam or hold.","additionalProperties":false,"required":["action","actor","at","automated"],"properties":{"action":{"type":"string","enum":["approve","hide","spam","hold","restore","delete","erase"]},"actor":{"$ref":"#/$defs/Actor"},"at":{"$ref":"#/$defs/Timestamp"},"reasonCode":{"type":"string","description":"Required by writers for hide, spam, delete and erase.","enum":["spam","abuse","off-topic","personal-data","illegal-content","legal-order","author-request","erasure-request","duplicate","other"]},"note":{"type":"string","description":"Free text from the moderator, plain text, at most 500 characters.","maxLength":500,"pattern":"^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"},"automated":{"type":"boolean","description":"True when the decision was taken without a person (an agent, a rule or a ban), as a statement of reasons must say."}}},"CommentOrigin":{"type":"object","description":"For imported comments: the system and its id, so importers are idempotent.","additionalProperties":false,"required":["system","id"],"properties":{"system":{"type":"string","enum":["cloudhal","disqus","wordpress"]},"id":{"type":"string","description":"The comment's id in that system: printable ASCII, at most 200 characters.","minLength":1,"maxLength":200,"pattern":"^[!-~]+$"}}},"CommentStatus":{"type":"string","description":"Lifecycle. `pending` waits for a person (every guest comment starts here); `approved` is public; `hidden` is withdrawn and reversible; `spam` is never shown and is deleted after the retention period; `deleted` is a soft delete, hard-deleted after the retention period unless approved replies keep it as a tombstone.","enum":["pending","approved","hidden","spam","deleted"]},"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}}}; -const schema389 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; -const schema390 = {"type":"string","description":"A comment id: `cmt_` and 6 to 32 letters or digits.","pattern":"^cmt_[A-Za-z0-9]{6,32}$"}; -const schema391 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; -const schema395 = {"type":"object","description":"For imported comments: the system and its id, so importers are idempotent.","additionalProperties":false,"required":["system","id"],"properties":{"system":{"type":"string","enum":["cloudhal","disqus","wordpress"]},"id":{"type":"string","description":"The comment's id in that system: printable ASCII, at most 200 characters.","minLength":1,"maxLength":200,"pattern":"^[!-~]+$"}}}; -const schema404 = {"type":"object","description":"The comment text. v1 has only `text`: plain text with line breaks, no markup. A later RFC may add a restricted Markdown format. Absent only when the text was deleted (status `deleted`, or an erasure that removed the text); the record then stays as a tombstone while approved replies hang under it.","additionalProperties":false,"required":["format","text"],"properties":{"format":{"type":"string","enum":["text"]},"text":{"type":"string","description":"NFC-normalised, CRLF turned into LF, trimmed: 1 to 5,000 characters. Tabs and line feeds are the only control characters allowed; bidirectional overrides are refused so a comment cannot disguise itself in Studio.","minLength":1,"maxLength":5000,"pattern":"^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$"}}}; -const schema405 = {"type":"string","description":"Lifecycle. `pending` waits for a person (every guest comment starts here); `approved` is public; `hidden` is withdrawn and reversible; `spam` is never shown and is deleted after the retention period; `deleted` is a soft delete, hard-deleted after the retention period unless approved replies keep it as a tombstone.","enum":["pending","approved","hidden","spam","deleted"]}; -const schema407 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; -const schema412 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; -const pattern194 = new RegExp("^cmt_[A-Za-z0-9]{6,32}$", "u"); -const pattern199 = new RegExp("^[!-~]+$", "u"); -const pattern206 = new RegExp("^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$", "u"); -const schema396 = {"description":"Who wrote the comment, discriminated by `kind`. There is deliberately no email, address or account handle here.","oneOf":[{"type":"object","title":"GuestCommentAuthor","description":"A guest: only the display name they typed. Guest comments are always held for moderation in v1.","additionalProperties":false,"required":["kind","name"],"properties":{"kind":{"type":"string","enum":["guest"]},"name":{"$ref":"#/$defs/CommentDisplayName"}}},{"type":"object","title":"ReaderCommentAuthor","description":"A verified reader (v1.1: email code or magic link on the site's own host). `readerId` is the site's reader account id, never the email.","additionalProperties":false,"required":["kind","readerId","name"],"properties":{"kind":{"type":"string","enum":["reader"]},"readerId":{"$ref":"#/$defs/Id"},"name":{"$ref":"#/$defs/CommentDisplayName"}}},{"type":"object","title":"StaffCommentAuthor","description":"A reply on the site's behalf. It is always attributed to a person (`personId` is a staff actor id, `user_…`): an agent may draft the text (`draftedBy`), but a person approved it. `draftedBy` names the agent that drafted the text, when one did (shown in Studio and the audit, not on the public page).","additionalProperties":false,"required":["kind","personId","name","badge"],"properties":{"kind":{"type":"string","enum":["staff"]},"personId":{"type":"string","description":"The person who wrote or approved the reply.","pattern":"^user_[A-Za-z0-9]{6,32}$"},"name":{"$ref":"#/$defs/CommentDisplayName"},"badge":{"type":"string","description":"What the public page shows next to the name: `author` when the person wrote the entry, otherwise `team`.","enum":["author","team"]},"draftedBy":{"$ref":"#/$defs/Actor"}}},{"type":"object","title":"ErasedCommentAuthor","description":"The commenter's identity was erased on request (RFC-0007 privacy model). Pages show a localised \"Anonymous\".","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["erased"]}}}]}; -const schema397 = {"type":"string","description":"Plain text chosen by the commenter (or the staff member's name), NFC-normalised and trimmed by the server: 1 to 80 characters, no control characters, no bidirectional overrides, no surrounding whitespace.","minLength":1,"maxLength":80,"pattern":"^[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069](?:[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069])?$"}; -const pattern200 = new RegExp("^[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069](?:[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069])?$", "u"); -const pattern203 = new RegExp("^user_[A-Za-z0-9]{6,32}$", "u"); -const schema401 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}}; -const schema402 = {"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]}; +const schema390 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/comment.schema.json","title":"Comment","description":"A reader comment on a published content entry, as stored in D1 (outside the site graph), returned to people and agents, and exported (RFC-0007). It is not a graph node: it never enters a snapshot, a diff or a site bundle, and a content rollback never changes it. The record carries no personal data beyond the display name the commenter chose: an email address, an IP address or its hash, a user agent and a proof-of-work challenge live in separate columns and tables that this contract cannot express (additionalProperties: false), so no reader of the contract — Studio lists, MCP tools, exports — can leak them. Text is plain text; renderers escape it and turn only https links into anchors with rel=\"ugc nofollow noopener noreferrer\".","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","nodeId","source","author","status","createdAt","rev"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/CommentId"},"siteId":{"$ref":"#/$defs/Id"},"nodeId":{"$ref":"#/$defs/Id","description":"The content entry the comment belongs to, by id, so threads survive slug changes (RFC-0002)."},"parentId":{"$ref":"#/$defs/CommentId","description":"The comment this one is nested under for display. Absent for a top-level comment. In v1 the tree is two levels deep: a parent is always top-level, and a reply to a reply is nested under the same top-level comment with `inReplyToId` pointing at the comment it answers."},"inReplyToId":{"$ref":"#/$defs/CommentId","description":"The comment this one answers when that is not `parentId` itself (a reply to a reply). Only with `parentId`, in the same thread."},"source":{"type":"string","description":"Where the comment came from: `native` (the site's own form or Studio), `import` (an importer; see `origin`). `webmention` and `activitypub` are reserved for later RFCs and are refused by v1 writers.","enum":["native","import","webmention","activitypub"]},"origin":{"$ref":"#/$defs/CommentOrigin"},"author":{"$ref":"#/$defs/CommentAuthor"},"body":{"$ref":"#/$defs/CommentBody"},"status":{"$ref":"#/$defs/CommentStatus"},"labels":{"type":"array","description":"Advisory labels from rules, classifiers, agents and people. Labels never change what readers see; only a status change does.","items":{"$ref":"#/$defs/CommentLabel"},"maxItems":32},"moderation":{"$ref":"#/$defs/CommentModeration"},"createdAt":{"$ref":"#/$defs/Timestamp"},"editedAt":{"$ref":"#/$defs/Timestamp","description":"Set when the text changed after it was first stored (v1.1 edit window; staff replies edited in Studio)."},"rev":{"$ref":"#/$defs/Revision"}},"$defs":{"Actor":{"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}},"ActorKind":{"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]},"CommentAuthor":{"description":"Who wrote the comment, discriminated by `kind`. There is deliberately no email, address or account handle here.","oneOf":[{"type":"object","title":"GuestCommentAuthor","description":"A guest: only the display name they typed. Guest comments are always held for moderation in v1.","additionalProperties":false,"required":["kind","name"],"properties":{"kind":{"type":"string","enum":["guest"]},"name":{"$ref":"#/$defs/CommentDisplayName"}}},{"type":"object","title":"ReaderCommentAuthor","description":"A verified reader (v1.1: email code or magic link on the site's own host). `readerId` is the site's reader account id, never the email.","additionalProperties":false,"required":["kind","readerId","name"],"properties":{"kind":{"type":"string","enum":["reader"]},"readerId":{"$ref":"#/$defs/Id"},"name":{"$ref":"#/$defs/CommentDisplayName"}}},{"type":"object","title":"StaffCommentAuthor","description":"A reply on the site's behalf. It is always attributed to a person (`personId` is a staff actor id, `user_…`): an agent may draft the text (`draftedBy`), but a person approved it. `draftedBy` names the agent that drafted the text, when one did (shown in Studio and the audit, not on the public page).","additionalProperties":false,"required":["kind","personId","name","badge"],"properties":{"kind":{"type":"string","enum":["staff"]},"personId":{"type":"string","description":"The person who wrote or approved the reply.","pattern":"^user_[A-Za-z0-9]{6,32}$"},"name":{"$ref":"#/$defs/CommentDisplayName"},"badge":{"type":"string","description":"What the public page shows next to the name: `author` when the person wrote the entry, otherwise `team`.","enum":["author","team"]},"draftedBy":{"$ref":"#/$defs/Actor"}}},{"type":"object","title":"ErasedCommentAuthor","description":"The commenter's identity was erased on request (RFC-0007 privacy model). Pages show a localised \"Anonymous\".","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["erased"]}}}]},"CommentBody":{"type":"object","description":"The comment text. v1 has only `text`: plain text with line breaks, no markup. A later RFC may add a restricted Markdown format. Absent only when the text was deleted (status `deleted`, or an erasure that removed the text); the record then stays as a tombstone while approved replies hang under it.","additionalProperties":false,"required":["format","text"],"properties":{"format":{"type":"string","enum":["text"]},"text":{"type":"string","description":"NFC-normalised, CRLF turned into LF, trimmed: 1 to 5,000 characters. Tabs and line feeds are the only control characters allowed; bidirectional overrides are refused so a comment cannot disguise itself in Studio.","minLength":1,"maxLength":5000,"pattern":"^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$"}}},"CommentDisplayName":{"type":"string","description":"Plain text chosen by the commenter (or the staff member's name), NFC-normalised and trimmed by the server: 1 to 80 characters, no control characters, no bidirectional overrides, no surrounding whitespace.","minLength":1,"maxLength":80,"pattern":"^[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069](?:[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069])?$"},"CommentId":{"type":"string","description":"A comment id: `cmt_` and 6 to 32 letters or digits.","pattern":"^cmt_[A-Za-z0-9]{6,32}$"},"CommentLabel":{"type":"object","description":"An advisory label. `rule` labels come from the submission pipeline (word lists, link count, instruction-like text); `classifier` from an opt-in classifier (Workers AI, Akismet); `agent` from `label_comments`; `person` from Studio. `by` is the agent or person that set it (absent for rules and classifiers).","additionalProperties":false,"required":["label","source","at"],"properties":{"label":{"type":"string","enum":["spam-suspect","toxic","off-topic","question","needs-reply","praise","personal-data","links","watch-word","instruction-like"]},"source":{"type":"string","enum":["rule","classifier","agent","person"]},"by":{"$ref":"#/$defs/Actor"},"confidence":{"type":"integer","description":"0 to 100, when the source reports one.","minimum":0,"maximum":100},"at":{"$ref":"#/$defs/Timestamp"}}},"CommentModeration":{"type":"object","description":"The latest moderation decision, kept as a statement of reasons (EU DSA Art. 17). The full history is in `comment_events`. Approvals are always a person's decision; agents may only hide, mark as spam or hold.","additionalProperties":false,"required":["action","actor","at","automated"],"properties":{"action":{"type":"string","enum":["approve","hide","spam","hold","restore","delete","erase"]},"actor":{"$ref":"#/$defs/Actor"},"at":{"$ref":"#/$defs/Timestamp"},"reasonCode":{"type":"string","description":"Required by writers for hide, spam, delete and erase.","enum":["spam","abuse","off-topic","personal-data","illegal-content","legal-order","author-request","erasure-request","duplicate","other"]},"note":{"type":"string","description":"Free text from the moderator, plain text, at most 500 characters.","maxLength":500,"pattern":"^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"},"automated":{"type":"boolean","description":"True when the decision was taken without a person (an agent, a rule or a ban), as a statement of reasons must say."}}},"CommentOrigin":{"type":"object","description":"For imported comments: the system and its id, so importers are idempotent.","additionalProperties":false,"required":["system","id"],"properties":{"system":{"type":"string","enum":["cloudhal","disqus","wordpress"]},"id":{"type":"string","description":"The comment's id in that system: printable ASCII, at most 200 characters.","minLength":1,"maxLength":200,"pattern":"^[!-~]+$"}}},"CommentStatus":{"type":"string","description":"Lifecycle. `pending` waits for a person (every guest comment starts here); `approved` is public; `hidden` is withdrawn and reversible; `spam` is never shown and is deleted after the retention period; `deleted` is a soft delete, hard-deleted after the retention period unless approved replies keep it as a tombstone.","enum":["pending","approved","hidden","spam","deleted"]},"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}}}; +const schema391 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; +const schema392 = {"type":"string","description":"A comment id: `cmt_` and 6 to 32 letters or digits.","pattern":"^cmt_[A-Za-z0-9]{6,32}$"}; +const schema393 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; +const schema397 = {"type":"object","description":"For imported comments: the system and its id, so importers are idempotent.","additionalProperties":false,"required":["system","id"],"properties":{"system":{"type":"string","enum":["cloudhal","disqus","wordpress"]},"id":{"type":"string","description":"The comment's id in that system: printable ASCII, at most 200 characters.","minLength":1,"maxLength":200,"pattern":"^[!-~]+$"}}}; +const schema406 = {"type":"object","description":"The comment text. v1 has only `text`: plain text with line breaks, no markup. A later RFC may add a restricted Markdown format. Absent only when the text was deleted (status `deleted`, or an erasure that removed the text); the record then stays as a tombstone while approved replies hang under it.","additionalProperties":false,"required":["format","text"],"properties":{"format":{"type":"string","enum":["text"]},"text":{"type":"string","description":"NFC-normalised, CRLF turned into LF, trimmed: 1 to 5,000 characters. Tabs and line feeds are the only control characters allowed; bidirectional overrides are refused so a comment cannot disguise itself in Studio.","minLength":1,"maxLength":5000,"pattern":"^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$"}}}; +const schema407 = {"type":"string","description":"Lifecycle. `pending` waits for a person (every guest comment starts here); `approved` is public; `hidden` is withdrawn and reversible; `spam` is never shown and is deleted after the retention period; `deleted` is a soft delete, hard-deleted after the retention period unless approved replies keep it as a tombstone.","enum":["pending","approved","hidden","spam","deleted"]}; +const schema409 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; +const schema414 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; +const pattern196 = new RegExp("^cmt_[A-Za-z0-9]{6,32}$", "u"); +const pattern201 = new RegExp("^[!-~]+$", "u"); +const pattern208 = new RegExp("^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$", "u"); +const schema398 = {"description":"Who wrote the comment, discriminated by `kind`. There is deliberately no email, address or account handle here.","oneOf":[{"type":"object","title":"GuestCommentAuthor","description":"A guest: only the display name they typed. Guest comments are always held for moderation in v1.","additionalProperties":false,"required":["kind","name"],"properties":{"kind":{"type":"string","enum":["guest"]},"name":{"$ref":"#/$defs/CommentDisplayName"}}},{"type":"object","title":"ReaderCommentAuthor","description":"A verified reader (v1.1: email code or magic link on the site's own host). `readerId` is the site's reader account id, never the email.","additionalProperties":false,"required":["kind","readerId","name"],"properties":{"kind":{"type":"string","enum":["reader"]},"readerId":{"$ref":"#/$defs/Id"},"name":{"$ref":"#/$defs/CommentDisplayName"}}},{"type":"object","title":"StaffCommentAuthor","description":"A reply on the site's behalf. It is always attributed to a person (`personId` is a staff actor id, `user_…`): an agent may draft the text (`draftedBy`), but a person approved it. `draftedBy` names the agent that drafted the text, when one did (shown in Studio and the audit, not on the public page).","additionalProperties":false,"required":["kind","personId","name","badge"],"properties":{"kind":{"type":"string","enum":["staff"]},"personId":{"type":"string","description":"The person who wrote or approved the reply.","pattern":"^user_[A-Za-z0-9]{6,32}$"},"name":{"$ref":"#/$defs/CommentDisplayName"},"badge":{"type":"string","description":"What the public page shows next to the name: `author` when the person wrote the entry, otherwise `team`.","enum":["author","team"]},"draftedBy":{"$ref":"#/$defs/Actor"}}},{"type":"object","title":"ErasedCommentAuthor","description":"The commenter's identity was erased on request (RFC-0007 privacy model). Pages show a localised \"Anonymous\".","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["erased"]}}}]}; +const schema399 = {"type":"string","description":"Plain text chosen by the commenter (or the staff member's name), NFC-normalised and trimmed by the server: 1 to 80 characters, no control characters, no bidirectional overrides, no surrounding whitespace.","minLength":1,"maxLength":80,"pattern":"^[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069](?:[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069])?$"}; +const pattern202 = new RegExp("^[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069](?:[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069])?$", "u"); +const pattern205 = new RegExp("^user_[A-Za-z0-9]{6,32}$", "u"); +const schema403 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}}; +const schema404 = {"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]}; function validate153(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -27063,7 +27188,7 @@ vErrors.push(err3); errors++; } if(!(((data0 === "human") || (data0 === "agent")) || (data0 === "system"))){ -const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/$defs/ActorKind/enum",keyword:"enum",params:{allowedValues: schema402.enum},message:"must be equal to one of the allowed values"}; +const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/$defs/ActorKind/enum",keyword:"enum",params:{allowedValues: schema404.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err4]; } @@ -27220,7 +27345,7 @@ vErrors.push(err3); errors++; } if(!(data0 === "guest")){ -const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema396.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema398.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err4]; } @@ -27253,7 +27378,7 @@ vErrors.push(err6); } errors++; } -if(!pattern200.test(data1)){ +if(!pattern202.test(data1)){ const err7 = {instancePath:instancePath+"/name",schemaPath:"#/$defs/CommentDisplayName/pattern",keyword:"pattern",params:{pattern: "^[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069](?:[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069])?$"},message:"must match pattern \""+"^[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069](?:[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069])?$"+"\""}; if(vErrors === null){ vErrors = [err7]; @@ -27349,7 +27474,7 @@ vErrors.push(err14); errors++; } if(!(data2 === "reader")){ -const err15 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema396.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err15 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema398.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err15]; } @@ -27427,7 +27552,7 @@ vErrors.push(err21); } errors++; } -if(!pattern200.test(data4)){ +if(!pattern202.test(data4)){ const err22 = {instancePath:instancePath+"/name",schemaPath:"#/$defs/CommentDisplayName/pattern",keyword:"pattern",params:{pattern: "^[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069](?:[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069])?$"},message:"must match pattern \""+"^[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069](?:[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069])?$"+"\""}; if(vErrors === null){ vErrors = [err22]; @@ -27540,7 +27665,7 @@ vErrors.push(err30); errors++; } if(!(data5 === "staff")){ -const err31 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/2/properties/kind/enum",keyword:"enum",params:{allowedValues: schema396.oneOf[2].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err31 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/2/properties/kind/enum",keyword:"enum",params:{allowedValues: schema398.oneOf[2].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err31]; } @@ -27553,7 +27678,7 @@ errors++; if(data.personId !== undefined){ let data6 = data.personId; if(typeof data6 === "string"){ -if(!pattern203.test(data6)){ +if(!pattern205.test(data6)){ const err32 = {instancePath:instancePath+"/personId",schemaPath:"#/oneOf/2/properties/personId/pattern",keyword:"pattern",params:{pattern: "^user_[A-Za-z0-9]{6,32}$"},message:"must match pattern \""+"^user_[A-Za-z0-9]{6,32}$"+"\""}; if(vErrors === null){ vErrors = [err32]; @@ -27598,7 +27723,7 @@ vErrors.push(err35); } errors++; } -if(!pattern200.test(data7)){ +if(!pattern202.test(data7)){ const err36 = {instancePath:instancePath+"/name",schemaPath:"#/$defs/CommentDisplayName/pattern",keyword:"pattern",params:{pattern: "^[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069](?:[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069])?$"},message:"must match pattern \""+"^[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069](?:[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*[^\\s\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069])?$"+"\""}; if(vErrors === null){ vErrors = [err36]; @@ -27633,7 +27758,7 @@ vErrors.push(err38); errors++; } if(!((data8 === "author") || (data8 === "team"))){ -const err39 = {instancePath:instancePath+"/badge",schemaPath:"#/oneOf/2/properties/badge/enum",keyword:"enum",params:{allowedValues: schema396.oneOf[2].properties.badge.enum},message:"must be equal to one of the allowed values"}; +const err39 = {instancePath:instancePath+"/badge",schemaPath:"#/oneOf/2/properties/badge/enum",keyword:"enum",params:{allowedValues: schema398.oneOf[2].properties.badge.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err39]; } @@ -27710,7 +27835,7 @@ vErrors.push(err43); errors++; } if(!(data10 === "erased")){ -const err44 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/3/properties/kind/enum",keyword:"enum",params:{allowedValues: schema396.oneOf[3].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err44 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/3/properties/kind/enum",keyword:"enum",params:{allowedValues: schema398.oneOf[3].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err44]; } @@ -27774,7 +27899,7 @@ return errors === 0; } validate152.evaluated = {"dynamicProps":true,"dynamicItems":false}; -const schema406 = {"type":"object","description":"An advisory label. `rule` labels come from the submission pipeline (word lists, link count, instruction-like text); `classifier` from an opt-in classifier (Workers AI, Akismet); `agent` from `label_comments`; `person` from Studio. `by` is the agent or person that set it (absent for rules and classifiers).","additionalProperties":false,"required":["label","source","at"],"properties":{"label":{"type":"string","enum":["spam-suspect","toxic","off-topic","question","needs-reply","praise","personal-data","links","watch-word","instruction-like"]},"source":{"type":"string","enum":["rule","classifier","agent","person"]},"by":{"$ref":"#/$defs/Actor"},"confidence":{"type":"integer","description":"0 to 100, when the source reports one.","minimum":0,"maximum":100},"at":{"$ref":"#/$defs/Timestamp"}}}; +const schema408 = {"type":"object","description":"An advisory label. `rule` labels come from the submission pipeline (word lists, link count, instruction-like text); `classifier` from an opt-in classifier (Workers AI, Akismet); `agent` from `label_comments`; `person` from Studio. `by` is the agent or person that set it (absent for rules and classifiers).","additionalProperties":false,"required":["label","source","at"],"properties":{"label":{"type":"string","enum":["spam-suspect","toxic","off-topic","question","needs-reply","praise","personal-data","links","watch-word","instruction-like"]},"source":{"type":"string","enum":["rule","classifier","agent","person"]},"by":{"$ref":"#/$defs/Actor"},"confidence":{"type":"integer","description":"0 to 100, when the source reports one.","minimum":0,"maximum":100},"at":{"$ref":"#/$defs/Timestamp"}}}; function validate156(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -27842,7 +27967,7 @@ vErrors.push(err4); errors++; } if(!((((((((((data0 === "spam-suspect") || (data0 === "toxic")) || (data0 === "off-topic")) || (data0 === "question")) || (data0 === "needs-reply")) || (data0 === "praise")) || (data0 === "personal-data")) || (data0 === "links")) || (data0 === "watch-word")) || (data0 === "instruction-like"))){ -const err5 = {instancePath:instancePath+"/label",schemaPath:"#/properties/label/enum",keyword:"enum",params:{allowedValues: schema406.properties.label.enum},message:"must be equal to one of the allowed values"}; +const err5 = {instancePath:instancePath+"/label",schemaPath:"#/properties/label/enum",keyword:"enum",params:{allowedValues: schema408.properties.label.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err5]; } @@ -27865,7 +27990,7 @@ vErrors.push(err6); errors++; } if(!((((data1 === "rule") || (data1 === "classifier")) || (data1 === "agent")) || (data1 === "person"))){ -const err7 = {instancePath:instancePath+"/source",schemaPath:"#/properties/source/enum",keyword:"enum",params:{allowedValues: schema406.properties.source.enum},message:"must be equal to one of the allowed values"}; +const err7 = {instancePath:instancePath+"/source",schemaPath:"#/properties/source/enum",keyword:"enum",params:{allowedValues: schema408.properties.source.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err7]; } @@ -27957,8 +28082,8 @@ return errors === 0; } validate156.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; -const schema408 = {"type":"object","description":"The latest moderation decision, kept as a statement of reasons (EU DSA Art. 17). The full history is in `comment_events`. Approvals are always a person's decision; agents may only hide, mark as spam or hold.","additionalProperties":false,"required":["action","actor","at","automated"],"properties":{"action":{"type":"string","enum":["approve","hide","spam","hold","restore","delete","erase"]},"actor":{"$ref":"#/$defs/Actor"},"at":{"$ref":"#/$defs/Timestamp"},"reasonCode":{"type":"string","description":"Required by writers for hide, spam, delete and erase.","enum":["spam","abuse","off-topic","personal-data","illegal-content","legal-order","author-request","erasure-request","duplicate","other"]},"note":{"type":"string","description":"Free text from the moderator, plain text, at most 500 characters.","maxLength":500,"pattern":"^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"},"automated":{"type":"boolean","description":"True when the decision was taken without a person (an agent, a rule or a ban), as a statement of reasons must say."}}}; -const pattern207 = new RegExp("^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$", "u"); +const schema410 = {"type":"object","description":"The latest moderation decision, kept as a statement of reasons (EU DSA Art. 17). The full history is in `comment_events`. Approvals are always a person's decision; agents may only hide, mark as spam or hold.","additionalProperties":false,"required":["action","actor","at","automated"],"properties":{"action":{"type":"string","enum":["approve","hide","spam","hold","restore","delete","erase"]},"actor":{"$ref":"#/$defs/Actor"},"at":{"$ref":"#/$defs/Timestamp"},"reasonCode":{"type":"string","description":"Required by writers for hide, spam, delete and erase.","enum":["spam","abuse","off-topic","personal-data","illegal-content","legal-order","author-request","erasure-request","duplicate","other"]},"note":{"type":"string","description":"Free text from the moderator, plain text, at most 500 characters.","maxLength":500,"pattern":"^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"},"automated":{"type":"boolean","description":"True when the decision was taken without a person (an agent, a rule or a ban), as a statement of reasons must say."}}}; +const pattern209 = new RegExp("^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$", "u"); function validate159(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -28036,7 +28161,7 @@ vErrors.push(err5); errors++; } if(!(((((((data0 === "approve") || (data0 === "hide")) || (data0 === "spam")) || (data0 === "hold")) || (data0 === "restore")) || (data0 === "delete")) || (data0 === "erase"))){ -const err6 = {instancePath:instancePath+"/action",schemaPath:"#/properties/action/enum",keyword:"enum",params:{allowedValues: schema408.properties.action.enum},message:"must be equal to one of the allowed values"}; +const err6 = {instancePath:instancePath+"/action",schemaPath:"#/properties/action/enum",keyword:"enum",params:{allowedValues: schema410.properties.action.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err6]; } @@ -28090,7 +28215,7 @@ vErrors.push(err9); errors++; } if(!((((((((((data3 === "spam") || (data3 === "abuse")) || (data3 === "off-topic")) || (data3 === "personal-data")) || (data3 === "illegal-content")) || (data3 === "legal-order")) || (data3 === "author-request")) || (data3 === "erasure-request")) || (data3 === "duplicate")) || (data3 === "other"))){ -const err10 = {instancePath:instancePath+"/reasonCode",schemaPath:"#/properties/reasonCode/enum",keyword:"enum",params:{allowedValues: schema408.properties.reasonCode.enum},message:"must be equal to one of the allowed values"}; +const err10 = {instancePath:instancePath+"/reasonCode",schemaPath:"#/properties/reasonCode/enum",keyword:"enum",params:{allowedValues: schema410.properties.reasonCode.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err10]; } @@ -28113,7 +28238,7 @@ vErrors.push(err11); } errors++; } -if(!pattern207.test(data4)){ +if(!pattern209.test(data4)){ const err12 = {instancePath:instancePath+"/note",schemaPath:"#/properties/note/pattern",keyword:"pattern",params:{pattern: "^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"},message:"must match pattern \""+"^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"+"\""}; if(vErrors === null){ vErrors = [err12]; @@ -28267,7 +28392,7 @@ vErrors.push(err8); errors++; } for(const key0 in data){ -if(!(func1.call(schema388.properties, key0))){ +if(!(func1.call(schema390.properties, key0))){ const err9 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}; if(vErrors === null){ vErrors = [err9]; @@ -28291,7 +28416,7 @@ vErrors.push(err10); errors++; } if(!(data0 === 1)){ -const err11 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema389.enum},message:"must be equal to one of the allowed values"}; +const err11 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema391.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err11]; } @@ -28304,7 +28429,7 @@ errors++; if(data.id !== undefined){ let data1 = data.id; if(typeof data1 === "string"){ -if(!pattern194.test(data1)){ +if(!pattern196.test(data1)){ const err12 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/CommentId/pattern",keyword:"pattern",params:{pattern: "^cmt_[A-Za-z0-9]{6,32}$"},message:"must match pattern \""+"^cmt_[A-Za-z0-9]{6,32}$"+"\""}; if(vErrors === null){ vErrors = [err12]; @@ -28419,7 +28544,7 @@ errors++; if(data.parentId !== undefined){ let data4 = data.parentId; if(typeof data4 === "string"){ -if(!pattern194.test(data4)){ +if(!pattern196.test(data4)){ const err22 = {instancePath:instancePath+"/parentId",schemaPath:"#/$defs/CommentId/pattern",keyword:"pattern",params:{pattern: "^cmt_[A-Za-z0-9]{6,32}$"},message:"must match pattern \""+"^cmt_[A-Za-z0-9]{6,32}$"+"\""}; if(vErrors === null){ vErrors = [err22]; @@ -28444,7 +28569,7 @@ errors++; if(data.inReplyToId !== undefined){ let data5 = data.inReplyToId; if(typeof data5 === "string"){ -if(!pattern194.test(data5)){ +if(!pattern196.test(data5)){ const err24 = {instancePath:instancePath+"/inReplyToId",schemaPath:"#/$defs/CommentId/pattern",keyword:"pattern",params:{pattern: "^cmt_[A-Za-z0-9]{6,32}$"},message:"must match pattern \""+"^cmt_[A-Za-z0-9]{6,32}$"+"\""}; if(vErrors === null){ vErrors = [err24]; @@ -28479,7 +28604,7 @@ vErrors.push(err26); errors++; } if(!((((data6 === "native") || (data6 === "import")) || (data6 === "webmention")) || (data6 === "activitypub"))){ -const err27 = {instancePath:instancePath+"/source",schemaPath:"#/properties/source/enum",keyword:"enum",params:{allowedValues: schema388.properties.source.enum},message:"must be equal to one of the allowed values"}; +const err27 = {instancePath:instancePath+"/source",schemaPath:"#/properties/source/enum",keyword:"enum",params:{allowedValues: schema390.properties.source.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err27]; } @@ -28537,7 +28662,7 @@ vErrors.push(err31); errors++; } if(!(((data8 === "cloudhal") || (data8 === "disqus")) || (data8 === "wordpress"))){ -const err32 = {instancePath:instancePath+"/origin/system",schemaPath:"#/$defs/CommentOrigin/properties/system/enum",keyword:"enum",params:{allowedValues: schema395.properties.system.enum},message:"must be equal to one of the allowed values"}; +const err32 = {instancePath:instancePath+"/origin/system",schemaPath:"#/$defs/CommentOrigin/properties/system/enum",keyword:"enum",params:{allowedValues: schema397.properties.system.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err32]; } @@ -28570,7 +28695,7 @@ vErrors.push(err34); } errors++; } -if(!pattern199.test(data9)){ +if(!pattern201.test(data9)){ const err35 = {instancePath:instancePath+"/origin/id",schemaPath:"#/$defs/CommentOrigin/properties/id/pattern",keyword:"pattern",params:{pattern: "^[!-~]+$"},message:"must match pattern \""+"^[!-~]+$"+"\""}; if(vErrors === null){ vErrors = [err35]; @@ -28658,7 +28783,7 @@ vErrors.push(err41); errors++; } if(!(data12 === "text")){ -const err42 = {instancePath:instancePath+"/body/format",schemaPath:"#/$defs/CommentBody/properties/format/enum",keyword:"enum",params:{allowedValues: schema404.properties.format.enum},message:"must be equal to one of the allowed values"}; +const err42 = {instancePath:instancePath+"/body/format",schemaPath:"#/$defs/CommentBody/properties/format/enum",keyword:"enum",params:{allowedValues: schema406.properties.format.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err42]; } @@ -28691,7 +28816,7 @@ vErrors.push(err44); } errors++; } -if(!pattern206.test(data13)){ +if(!pattern208.test(data13)){ const err45 = {instancePath:instancePath+"/body/text",schemaPath:"#/$defs/CommentBody/properties/text/pattern",keyword:"pattern",params:{pattern: "^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$"},message:"must match pattern \""+"^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$"+"\""}; if(vErrors === null){ vErrors = [err45]; @@ -28738,7 +28863,7 @@ vErrors.push(err48); errors++; } if(!(((((data14 === "pending") || (data14 === "approved")) || (data14 === "hidden")) || (data14 === "spam")) || (data14 === "deleted"))){ -const err49 = {instancePath:instancePath+"/status",schemaPath:"#/$defs/CommentStatus/enum",keyword:"enum",params:{allowedValues: schema405.enum},message:"must be equal to one of the allowed values"}; +const err49 = {instancePath:instancePath+"/status",schemaPath:"#/$defs/CommentStatus/enum",keyword:"enum",params:{allowedValues: schema407.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err49]; } @@ -28878,17 +29003,17 @@ return errors === 0; validate151.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; export const validateCommentSubmission = validate162; -const schema413 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/comment-submission.schema.json","title":"CommentSubmission","description":"What a browser may send to the site Worker's comment endpoints (RFC-0007): `POST /__cloudhal/comments` (a comment) and `POST /__cloudhal/comments/report` (a report). Forms post application/x-www-form-urlencoded and the comments module posts JSON; the server builds this object from either (the endpoint sets `kind`, the server sets `schemaVersion`) and checks it with the precompiled validator before anything else reads it. Everything in it is untrusted. It is loose where the server normalises (it trims, NFC-normalises and re-checks text against the stored Comment contract) and strict where a browser has no business sending something: there is no field for a status, a site, an author kind, a label, an IP address or anything the server derives itself.","oneOf":[{"type":"object","title":"CommentPostSubmission","description":"A new comment or a reply.","additionalProperties":false,"required":["schemaVersion","kind","nodeId","name","body"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"kind":{"type":"string","enum":["comment"]},"nodeId":{"$ref":"#/$defs/Id","description":"The entry commented on. The server accepts it only for a published entry with an open thread, and works out the page to return to itself."},"parentId":{"$ref":"#/$defs/SubmissionCommentId","description":"The comment replied to; it must be approved and in the same thread. The server decides `parentId`/`inReplyToId` of the stored comment."},"name":{"type":"string","description":"The display name as typed (the form says it is shown publicly).","minLength":1,"maxLength":200},"email":{"type":"string","description":"Optional and never shown. Kept only for moderation, erasure and export requests (v1.1: reply notifications after double opt-in).","maxLength":254,"pattern":"^[^\\s@\\x00-\\x1F\\x7F]{1,64}@[^\\s@\\x00-\\x1F\\x7F]{1,189}$"},"body":{"type":"string","description":"The comment as typed; the server trims and normalises it, then requires 2 to 5,000 characters.","minLength":1,"maxLength":20000},"challenge":{"$ref":"#/$defs/SubmissionChallenge"},"solution":{"$ref":"#/$defs/SubmissionSolution"},"turnstileToken":{"$ref":"#/$defs/SubmissionTurnstileToken"},"website":{"$ref":"#/$defs/SubmissionHoneypot"}}},{"type":"object","title":"CommentReportSubmission","description":"A reader's notice that a comment breaks the site's rules or the law (EU DSA Art. 16). A report never hides anything by itself: it puts the comment in front of a person.","additionalProperties":false,"required":["schemaVersion","kind","commentId","reason"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"kind":{"type":"string","enum":["report"]},"commentId":{"$ref":"#/$defs/SubmissionCommentId"},"reason":{"type":"string","enum":["spam","abuse","personal-data","illegal-content","other"]},"details":{"type":"string","description":"Why, as typed; at most 1,000 characters after normalising.","maxLength":4000},"email":{"type":"string","description":"Optional, never shown: where the site may answer the notice.","maxLength":254,"pattern":"^[^\\s@\\x00-\\x1F\\x7F]{1,64}@[^\\s@\\x00-\\x1F\\x7F]{1,189}$"},"challenge":{"$ref":"#/$defs/SubmissionChallenge"},"solution":{"$ref":"#/$defs/SubmissionSolution"},"turnstileToken":{"$ref":"#/$defs/SubmissionTurnstileToken"},"website":{"$ref":"#/$defs/SubmissionHoneypot"}}}],"$defs":{"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"SubmissionChallenge":{"type":"string","description":"The signed proof-of-work challenge from `GET /__cloudhal/comments/challenge`, as issued (base64url segments joined by dots).","minLength":16,"maxLength":512,"pattern":"^[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*$"},"SubmissionCommentId":{"type":"string","pattern":"^cmt_[A-Za-z0-9]{6,32}$"},"SubmissionHoneypot":{"type":"string","description":"A field people never see. The schema accepts any short value so that a filled honeypot gets the same answer as a real submission (\"held for moderation\") and nothing is stored.","maxLength":2048},"SubmissionSolution":{"type":"string","description":"The counter that solves the challenge, in decimal.","pattern":"^(?:0|[1-9][0-9]{0,15})$"},"SubmissionTurnstileToken":{"type":"string","description":"Cloudflare Turnstile's response token, when the site turned Turnstile on.","minLength":1,"maxLength":2048}}}; -const schema414 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; -const schema415 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; -const schema416 = {"type":"string","pattern":"^cmt_[A-Za-z0-9]{6,32}$"}; -const schema417 = {"type":"string","description":"The signed proof-of-work challenge from `GET /__cloudhal/comments/challenge`, as issued (base64url segments joined by dots).","minLength":16,"maxLength":512,"pattern":"^[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*$"}; -const schema418 = {"type":"string","description":"The counter that solves the challenge, in decimal.","pattern":"^(?:0|[1-9][0-9]{0,15})$"}; -const schema419 = {"type":"string","description":"Cloudflare Turnstile's response token, when the site turned Turnstile on.","minLength":1,"maxLength":2048}; -const schema420 = {"type":"string","description":"A field people never see. The schema accepts any short value so that a filled honeypot gets the same answer as a real submission (\"held for moderation\") and nothing is stored.","maxLength":2048}; -const pattern210 = new RegExp("^[^\\s@\\x00-\\x1F\\x7F]{1,64}@[^\\s@\\x00-\\x1F\\x7F]{1,189}$", "u"); -const pattern211 = new RegExp("^[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*$", "u"); -const pattern212 = new RegExp("^(?:0|[1-9][0-9]{0,15})$", "u"); +const schema415 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/comment-submission.schema.json","title":"CommentSubmission","description":"What a browser may send to the site Worker's comment endpoints (RFC-0007): `POST /__cloudhal/comments` (a comment) and `POST /__cloudhal/comments/report` (a report). Forms post application/x-www-form-urlencoded and the comments module posts JSON; the server builds this object from either (the endpoint sets `kind`, the server sets `schemaVersion`) and checks it with the precompiled validator before anything else reads it. Everything in it is untrusted. It is loose where the server normalises (it trims, NFC-normalises and re-checks text against the stored Comment contract) and strict where a browser has no business sending something: there is no field for a status, a site, an author kind, a label, an IP address or anything the server derives itself.","oneOf":[{"type":"object","title":"CommentPostSubmission","description":"A new comment or a reply.","additionalProperties":false,"required":["schemaVersion","kind","nodeId","name","body"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"kind":{"type":"string","enum":["comment"]},"nodeId":{"$ref":"#/$defs/Id","description":"The entry commented on. The server accepts it only for a published entry with an open thread, and works out the page to return to itself."},"parentId":{"$ref":"#/$defs/SubmissionCommentId","description":"The comment replied to; it must be approved and in the same thread. The server decides `parentId`/`inReplyToId` of the stored comment."},"name":{"type":"string","description":"The display name as typed (the form says it is shown publicly).","minLength":1,"maxLength":200},"email":{"type":"string","description":"Optional and never shown. Kept only for moderation, erasure and export requests (v1.1: reply notifications after double opt-in).","maxLength":254,"pattern":"^[^\\s@\\x00-\\x1F\\x7F]{1,64}@[^\\s@\\x00-\\x1F\\x7F]{1,189}$"},"body":{"type":"string","description":"The comment as typed; the server trims and normalises it, then requires 2 to 5,000 characters.","minLength":1,"maxLength":20000},"challenge":{"$ref":"#/$defs/SubmissionChallenge"},"solution":{"$ref":"#/$defs/SubmissionSolution"},"turnstileToken":{"$ref":"#/$defs/SubmissionTurnstileToken"},"website":{"$ref":"#/$defs/SubmissionHoneypot"}}},{"type":"object","title":"CommentReportSubmission","description":"A reader's notice that a comment breaks the site's rules or the law (EU DSA Art. 16). A report never hides anything by itself: it puts the comment in front of a person.","additionalProperties":false,"required":["schemaVersion","kind","commentId","reason"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"kind":{"type":"string","enum":["report"]},"commentId":{"$ref":"#/$defs/SubmissionCommentId"},"reason":{"type":"string","enum":["spam","abuse","personal-data","illegal-content","other"]},"details":{"type":"string","description":"Why, as typed; at most 1,000 characters after normalising.","maxLength":4000},"email":{"type":"string","description":"Optional, never shown: where the site may answer the notice.","maxLength":254,"pattern":"^[^\\s@\\x00-\\x1F\\x7F]{1,64}@[^\\s@\\x00-\\x1F\\x7F]{1,189}$"},"challenge":{"$ref":"#/$defs/SubmissionChallenge"},"solution":{"$ref":"#/$defs/SubmissionSolution"},"turnstileToken":{"$ref":"#/$defs/SubmissionTurnstileToken"},"website":{"$ref":"#/$defs/SubmissionHoneypot"}}}],"$defs":{"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"SubmissionChallenge":{"type":"string","description":"The signed proof-of-work challenge from `GET /__cloudhal/comments/challenge`, as issued (base64url segments joined by dots).","minLength":16,"maxLength":512,"pattern":"^[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*$"},"SubmissionCommentId":{"type":"string","pattern":"^cmt_[A-Za-z0-9]{6,32}$"},"SubmissionHoneypot":{"type":"string","description":"A field people never see. The schema accepts any short value so that a filled honeypot gets the same answer as a real submission (\"held for moderation\") and nothing is stored.","maxLength":2048},"SubmissionSolution":{"type":"string","description":"The counter that solves the challenge, in decimal.","pattern":"^(?:0|[1-9][0-9]{0,15})$"},"SubmissionTurnstileToken":{"type":"string","description":"Cloudflare Turnstile's response token, when the site turned Turnstile on.","minLength":1,"maxLength":2048}}}; +const schema416 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; +const schema417 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; +const schema418 = {"type":"string","pattern":"^cmt_[A-Za-z0-9]{6,32}$"}; +const schema419 = {"type":"string","description":"The signed proof-of-work challenge from `GET /__cloudhal/comments/challenge`, as issued (base64url segments joined by dots).","minLength":16,"maxLength":512,"pattern":"^[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*$"}; +const schema420 = {"type":"string","description":"The counter that solves the challenge, in decimal.","pattern":"^(?:0|[1-9][0-9]{0,15})$"}; +const schema421 = {"type":"string","description":"Cloudflare Turnstile's response token, when the site turned Turnstile on.","minLength":1,"maxLength":2048}; +const schema422 = {"type":"string","description":"A field people never see. The schema accepts any short value so that a filled honeypot gets the same answer as a real submission (\"held for moderation\") and nothing is stored.","maxLength":2048}; +const pattern212 = new RegExp("^[^\\s@\\x00-\\x1F\\x7F]{1,64}@[^\\s@\\x00-\\x1F\\x7F]{1,189}$", "u"); +const pattern213 = new RegExp("^[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*$", "u"); +const pattern214 = new RegExp("^(?:0|[1-9][0-9]{0,15})$", "u"); function validate162(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ /*# sourceURL="https://cloudhal.dev/schemas/v1/comment-submission.schema.json" */; @@ -28957,7 +29082,7 @@ vErrors.push(err4); errors++; } for(const key0 in data){ -if(!(func1.call(schema413.oneOf[0].properties, key0))){ +if(!(func1.call(schema415.oneOf[0].properties, key0))){ const err5 = {instancePath,schemaPath:"#/oneOf/0/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}; if(vErrors === null){ vErrors = [err5]; @@ -28981,7 +29106,7 @@ vErrors.push(err6); errors++; } if(!(data0 === 1)){ -const err7 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema414.enum},message:"must be equal to one of the allowed values"}; +const err7 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema416.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err7]; } @@ -29004,7 +29129,7 @@ vErrors.push(err8); errors++; } if(!(data1 === "comment")){ -const err9 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema413.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err9 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema415.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err9]; } @@ -29062,7 +29187,7 @@ errors++; if(data.parentId !== undefined){ let data3 = data.parentId; if(typeof data3 === "string"){ -if(!pattern194.test(data3)){ +if(!pattern196.test(data3)){ const err14 = {instancePath:instancePath+"/parentId",schemaPath:"#/$defs/SubmissionCommentId/pattern",keyword:"pattern",params:{pattern: "^cmt_[A-Za-z0-9]{6,32}$"},message:"must match pattern \""+"^cmt_[A-Za-z0-9]{6,32}$"+"\""}; if(vErrors === null){ vErrors = [err14]; @@ -29132,7 +29257,7 @@ vErrors.push(err19); } errors++; } -if(!pattern210.test(data5)){ +if(!pattern212.test(data5)){ const err20 = {instancePath:instancePath+"/email",schemaPath:"#/oneOf/0/properties/email/pattern",keyword:"pattern",params:{pattern: "^[^\\s@\\x00-\\x1F\\x7F]{1,64}@[^\\s@\\x00-\\x1F\\x7F]{1,189}$"},message:"must match pattern \""+"^[^\\s@\\x00-\\x1F\\x7F]{1,64}@[^\\s@\\x00-\\x1F\\x7F]{1,189}$"+"\""}; if(vErrors === null){ vErrors = [err20]; @@ -29212,7 +29337,7 @@ vErrors.push(err26); } errors++; } -if(!pattern211.test(data7)){ +if(!pattern213.test(data7)){ const err27 = {instancePath:instancePath+"/challenge",schemaPath:"#/$defs/SubmissionChallenge/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*$"},message:"must match pattern \""+"^[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*$"+"\""}; if(vErrors === null){ vErrors = [err27]; @@ -29237,7 +29362,7 @@ errors++; if(data.solution !== undefined){ let data8 = data.solution; if(typeof data8 === "string"){ -if(!pattern212.test(data8)){ +if(!pattern214.test(data8)){ const err29 = {instancePath:instancePath+"/solution",schemaPath:"#/$defs/SubmissionSolution/pattern",keyword:"pattern",params:{pattern: "^(?:0|[1-9][0-9]{0,15})$"},message:"must match pattern \""+"^(?:0|[1-9][0-9]{0,15})$"+"\""}; if(vErrors === null){ vErrors = [err29]; @@ -29379,7 +29504,7 @@ vErrors.push(err40); errors++; } for(const key1 in data){ -if(!(func1.call(schema413.oneOf[1].properties, key1))){ +if(!(func1.call(schema415.oneOf[1].properties, key1))){ const err41 = {instancePath,schemaPath:"#/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"}; if(vErrors === null){ vErrors = [err41]; @@ -29403,7 +29528,7 @@ vErrors.push(err42); errors++; } if(!(data11 === 1)){ -const err43 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema414.enum},message:"must be equal to one of the allowed values"}; +const err43 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema416.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err43]; } @@ -29426,7 +29551,7 @@ vErrors.push(err44); errors++; } if(!(data12 === "report")){ -const err45 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema413.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err45 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema415.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err45]; } @@ -29439,7 +29564,7 @@ errors++; if(data.commentId !== undefined){ let data13 = data.commentId; if(typeof data13 === "string"){ -if(!pattern194.test(data13)){ +if(!pattern196.test(data13)){ const err46 = {instancePath:instancePath+"/commentId",schemaPath:"#/$defs/SubmissionCommentId/pattern",keyword:"pattern",params:{pattern: "^cmt_[A-Za-z0-9]{6,32}$"},message:"must match pattern \""+"^cmt_[A-Za-z0-9]{6,32}$"+"\""}; if(vErrors === null){ vErrors = [err46]; @@ -29474,7 +29599,7 @@ vErrors.push(err48); errors++; } if(!(((((data14 === "spam") || (data14 === "abuse")) || (data14 === "personal-data")) || (data14 === "illegal-content")) || (data14 === "other"))){ -const err49 = {instancePath:instancePath+"/reason",schemaPath:"#/oneOf/1/properties/reason/enum",keyword:"enum",params:{allowedValues: schema413.oneOf[1].properties.reason.enum},message:"must be equal to one of the allowed values"}; +const err49 = {instancePath:instancePath+"/reason",schemaPath:"#/oneOf/1/properties/reason/enum",keyword:"enum",params:{allowedValues: schema415.oneOf[1].properties.reason.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err49]; } @@ -29522,7 +29647,7 @@ vErrors.push(err52); } errors++; } -if(!pattern210.test(data16)){ +if(!pattern212.test(data16)){ const err53 = {instancePath:instancePath+"/email",schemaPath:"#/oneOf/1/properties/email/pattern",keyword:"pattern",params:{pattern: "^[^\\s@\\x00-\\x1F\\x7F]{1,64}@[^\\s@\\x00-\\x1F\\x7F]{1,189}$"},message:"must match pattern \""+"^[^\\s@\\x00-\\x1F\\x7F]{1,64}@[^\\s@\\x00-\\x1F\\x7F]{1,189}$"+"\""}; if(vErrors === null){ vErrors = [err53]; @@ -29567,7 +29692,7 @@ vErrors.push(err56); } errors++; } -if(!pattern211.test(data17)){ +if(!pattern213.test(data17)){ const err57 = {instancePath:instancePath+"/challenge",schemaPath:"#/$defs/SubmissionChallenge/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*$"},message:"must match pattern \""+"^[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*$"+"\""}; if(vErrors === null){ vErrors = [err57]; @@ -29592,7 +29717,7 @@ errors++; if(data.solution !== undefined){ let data18 = data.solution; if(typeof data18 === "string"){ -if(!pattern212.test(data18)){ +if(!pattern214.test(data18)){ const err59 = {instancePath:instancePath+"/solution",schemaPath:"#/$defs/SubmissionSolution/pattern",keyword:"pattern",params:{pattern: "^(?:0|[1-9][0-9]{0,15})$"},message:"must match pattern \""+"^(?:0|[1-9][0-9]{0,15})$"+"\""}; if(vErrors === null){ vErrors = [err59]; @@ -29727,21 +29852,21 @@ return errors === 0; validate162.evaluated = {"dynamicProps":true,"dynamicItems":false}; export const validateRelease = validate163; -const schema427 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/release.schema.json","title":"Release","description":"A scheduled content change set (RFC-0008): node versions pinned by id, rev and content hash, applied on top of whatever is live at the release's time. A single scheduled publish is a one-item release. Each item records the version that was live when the release was pinned (`base`); if a pinned node or its live base changed before the release runs, the release is held as a conflict instead of overwriting anything. A person's approval is bound to `hash` (`rl1:`), which digests the items only, so a person may move an approved release without re-approving it while an agent's move or any content change voids the approval. Releases live in D1 next to the graph; they are not graph nodes, never enter a snapshot and are not part of site bundles.","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","items","state","hash","createdBy","createdAt","updatedAt","rev"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/ReleaseId"},"siteId":{"$ref":"#/$defs/Id"},"title":{"type":"string","description":"Optional name shown in Studio (\"Election night\"). A one-item release without a title is shown under its entry's or page's title.","minLength":1,"maxLength":200,"pattern":"^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$"},"items":{"type":"array","description":"The pinned changes, at most one per node. A release never contains the site node: site settings, domains and structure are published the ordinary way.","items":{"$ref":"#/$defs/ReleaseItem"},"minItems":1,"maxItems":100},"when":{"$ref":"#/$defs/ReleaseWhen"},"state":{"$ref":"#/$defs/ReleaseState"},"hash":{"$ref":"#/$defs/ReleaseHash"},"approval":{"$ref":"#/$defs/ReleaseApproval"},"outcome":{"$ref":"#/$defs/ReleaseOutcome"},"createdBy":{"$ref":"#/$defs/Actor"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"rev":{"$ref":"#/$defs/Revision"},"metadata":{"$ref":"#/$defs/Metadata"}},"$defs":{"Actor":{"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}},"ActorKind":{"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]},"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"LocalDateTime":{"type":"string","description":"A wall-clock date and time without an offset, to the minute (YYYY-MM-DDTHH:mm), as a person entered it; always paired with a TimeZone (RFC-0008).","pattern":"^[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])T(?:[01][0-9]|2[0-3]):[0-5][0-9]$"},"Metadata":{"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}},"ReleaseApproval":{"description":"Why the release may run, discriminated by `kind`.","oneOf":[{"type":"object","title":"ReleasePersonApproval","description":"A person approved these items (bound to `hash`) for `executeAt`, either by deciding an agent's request (`approvalId`) or by scheduling it themselves. The person's rights are checked again when the release runs.","additionalProperties":false,"required":["kind","by","at"],"properties":{"kind":{"type":"string","enum":["person"]},"by":{"$ref":"#/$defs/ReleasePerson"},"at":{"$ref":"#/$defs/Timestamp"},"executeAt":{"$ref":"#/$defs/Timestamp"},"approvalId":{"$ref":"#/$defs/Id"}}},{"type":"object","title":"ReleaseAutoApproval","description":"An agent key in `auto` mode scheduled a low-risk release without a person (ADR-0015). Valid only while the release runs before `windowEnds`: the end of the key's elevation, or 24 hours after scheduling, whichever comes first.","additionalProperties":false,"required":["kind","by","keyId","at","windowEnds"],"properties":{"kind":{"type":"string","enum":["auto"]},"by":{"$ref":"#/$defs/Actor"},"keyId":{"type":"string","pattern":"^[A-Za-z0-9_-]{6,64}$"},"at":{"$ref":"#/$defs/Timestamp"},"windowEnds":{"$ref":"#/$defs/Timestamp"}}}]},"ReleaseConflict":{"type":"object","description":"A node whose live version was not the pinned base when the release ran. `expected` absent: the node was not expected to be live; `found` absent: it was not live.","additionalProperties":false,"required":["nodeId"],"properties":{"nodeId":{"$ref":"#/$defs/Id"},"expected":{"$ref":"#/$defs/ReleaseNodeVersion"},"found":{"$ref":"#/$defs/ReleaseNodeVersion"}}},"ReleaseHash":{"type":"string","description":"`rl1:` and the lower-case hex SHA-256 of the release's canonical item lines (site id, then one line per item sorted by node id: kind, node id, target and base versions). The prefix keeps it apart from the graph state hash and from comment bindings (`cm1:`); approvals store it as their state.","pattern":"^rl1:[0-9a-f]{64}$"},"ReleaseId":{"type":"string","description":"A release id: `rel_` and 6 to 32 letters or digits.","pattern":"^rel_[A-Za-z0-9]{6,32}$"},"ReleaseItem":{"description":"One pinned change, discriminated by `kind`.","oneOf":[{"type":"object","title":"ReleaseSetItem","description":"Make `target` the live version of the node. `base` is the version that was live when the item was pinned; absent when the node was not live (a new entry or page).","additionalProperties":false,"required":["kind","nodeId","nodeKind","target"],"properties":{"kind":{"type":"string","enum":["set"]},"nodeId":{"$ref":"#/$defs/Id"},"nodeKind":{"$ref":"#/$defs/ReleaseNodeKind"},"target":{"$ref":"#/$defs/ReleaseNodeVersion"},"base":{"$ref":"#/$defs/ReleaseNodeVersion"}}},{"type":"object","title":"ReleaseRemoveItem","description":"Take the node out of the live snapshot. `base` is the live version that is removed.","additionalProperties":false,"required":["kind","nodeId","nodeKind","base"],"properties":{"kind":{"type":"string","enum":["remove"]},"nodeId":{"$ref":"#/$defs/Id"},"nodeKind":{"$ref":"#/$defs/ReleaseNodeKind"},"base":{"$ref":"#/$defs/ReleaseNodeVersion"}}}]},"ReleaseNodeKind":{"type":"string","description":"Kinds of graph node a release may carry. The site node is never one of them.","enum":["page","contentEntry","taxonomy","term","redirect","menu","asset"]},"ReleaseNodeVersion":{"type":"object","description":"One version of a node: its revision and the SHA-256 of its canonical JSON, whose body the blob store keeps under `nodes/.json`.","additionalProperties":false,"required":["rev","hash"],"properties":{"rev":{"$ref":"#/$defs/Revision"},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$"}}},"ReleaseOutcome":{"description":"What happened when the release was due, discriminated by `kind` (it matches the terminal `state`).","oneOf":[{"type":"object","title":"ReleasePublishedOutcome","description":"Applied: `snapshotId` is the new live snapshot (reason `scheduled`), composed from `baseSnapshotId` and the items. `purge` says how the edge cache followed.","additionalProperties":false,"required":["kind","runId","at","snapshotId","baseSnapshotId","latenessSeconds","purge"],"properties":{"kind":{"type":"string","enum":["published"]},"runId":{"$ref":"#/$defs/Id"},"at":{"$ref":"#/$defs/Timestamp"},"snapshotId":{"$ref":"#/$defs/Id"},"baseSnapshotId":{"$ref":"#/$defs/Id"},"latenessSeconds":{"type":"integer","minimum":0},"purge":{"type":"string","enum":["tags","everything","deferred","failed","none"]}}},{"type":"object","title":"ReleaseConflictOutcome","description":"Held: these nodes changed since the release was pinned. Nothing was published.","additionalProperties":false,"required":["kind","at","conflicts"],"properties":{"kind":{"type":"string","enum":["conflict"]},"runId":{"$ref":"#/$defs/Id"},"at":{"$ref":"#/$defs/Timestamp"},"conflicts":{"type":"array","items":{"$ref":"#/$defs/ReleaseConflict"},"minItems":1,"maxItems":100}}},{"type":"object","title":"ReleaseFailedOutcome","description":"Refused at run time; nothing was published.","additionalProperties":false,"required":["kind","at","code"],"properties":{"kind":{"type":"string","enum":["failed"]},"runId":{"$ref":"#/$defs/Id"},"at":{"$ref":"#/$defs/Timestamp"},"code":{"type":"string","enum":["dependency_missing","invalid_snapshot","site_missing","node_body_missing","store_error"]},"message":{"type":"string","maxLength":500,"pattern":"^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"}}},{"type":"object","title":"ReleaseMissedOutcome","description":"The scheduler reached the release more than an hour after its time; nothing was published. A person may publish it now.","additionalProperties":false,"required":["kind","at","latenessSeconds"],"properties":{"kind":{"type":"string","enum":["missed"]},"at":{"$ref":"#/$defs/Timestamp"},"latenessSeconds":{"type":"integer","minimum":0}}},{"type":"object","title":"ReleaseExpiredOutcome","description":"No valid approval when the release was due: never approved, the approver can no longer publish it, the agent's `auto` window ended, or its creator left.","additionalProperties":false,"required":["kind","at","reason"],"properties":{"kind":{"type":"string","enum":["expired"]},"at":{"$ref":"#/$defs/Timestamp"},"reason":{"type":"string","enum":["unapproved","approval_lost","auto_window_ended","creator_removed"]}}},{"type":"object","title":"ReleaseCanceledOutcome","description":"Withdrawn by a person or an agent; its nodes are unlocked.","additionalProperties":false,"required":["kind","at","by"],"properties":{"kind":{"type":"string","enum":["canceled"]},"at":{"$ref":"#/$defs/Timestamp"},"by":{"$ref":"#/$defs/Actor"}}}]},"ReleasePerson":{"type":"object","description":"A staff person (`user_…`). Agents never appear here.","additionalProperties":false,"required":["id"],"properties":{"id":{"type":"string","pattern":"^user_[A-Za-z0-9]{6,32}$"},"name":{"type":"string","maxLength":200}}},"ReleaseState":{"type":"string","description":"Lifecycle. `draft`: proposed or being assembled, nothing locked. `awaiting_approval`: waiting for a person; its nodes are locked. `scheduled`: approved (or allowed by `auto` mode) and locked until it runs. `running`: claimed by the scheduler. `published`: applied; `outcome` names the snapshot. `conflict`: a pinned node or its live base changed; held, nothing published. `missed`: the scheduler reached it more than an hour late; nothing published. `expired`: no valid approval at its time. `failed`: refused at run time (e.g. a dependency is missing). `canceled`: withdrawn.","enum":["draft","awaiting_approval","scheduled","running","published","conflict","missed","expired","failed","canceled"]},"ReleaseWhen":{"description":"When the release runs, discriminated by `kind`. Absent while a draft has no time yet.","oneOf":[{"type":"object","title":"ReleaseAt","description":"At an instant. `at` is what the scheduler compares with the clock; when a person entered a wall-clock time, `localTime` keeps it and `at` is recomputed from it and `timeZone` before the release runs, never earlier than the wall-clock time.","additionalProperties":false,"required":["kind","at","timeZone"],"properties":{"kind":{"type":"string","enum":["at"]},"at":{"$ref":"#/$defs/Timestamp"},"timeZone":{"$ref":"#/$defs/TimeZone"},"localTime":{"$ref":"#/$defs/LocalDateTime"}}},{"type":"object","title":"ReleaseOnApproval","description":"As soon as it is approved (a gate-only change such as setting an expiry on live content, or \"publish when approved\").","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["on-approval"]}}}]},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"},"TimeZone":{"type":"string","description":"IANA time zone name from the tz database (RFC-0008), e.g. Europe/Istanbul, America/Argentina/Buenos_Aires, Etc/GMT+3 or UTC. Wall-clock times a person enters are kept with their zone, so the instant is recomputed when governments change the rules. The pattern bounds the shape only; writers also check the name against the runtime's Intl zone list.","pattern":"^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$","maxLength":64}}}; -const schema428 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; -const schema429 = {"type":"string","description":"A release id: `rel_` and 6 to 32 letters or digits.","pattern":"^rel_[A-Za-z0-9]{6,32}$"}; -const schema430 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; -const schema442 = {"type":"string","description":"Lifecycle. `draft`: proposed or being assembled, nothing locked. `awaiting_approval`: waiting for a person; its nodes are locked. `scheduled`: approved (or allowed by `auto` mode) and locked until it runs. `running`: claimed by the scheduler. `published`: applied; `outcome` names the snapshot. `conflict`: a pinned node or its live base changed; held, nothing published. `missed`: the scheduler reached it more than an hour late; nothing published. `expired`: no valid approval at its time. `failed`: refused at run time (e.g. a dependency is missing). `canceled`: withdrawn.","enum":["draft","awaiting_approval","scheduled","running","published","conflict","missed","expired","failed","canceled"]}; -const schema443 = {"type":"string","description":"`rl1:` and the lower-case hex SHA-256 of the release's canonical item lines (site id, then one line per item sorted by node id: kind, node id, target and base versions). The prefix keeps it apart from the graph state hash and from comment bindings (`cm1:`); approvals store it as their state.","pattern":"^rl1:[0-9a-f]{64}$"}; -const schema439 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; -const schema435 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; -const schema471 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}}; -const pattern217 = new RegExp("^rel_[A-Za-z0-9]{6,32}$", "u"); -const pattern219 = new RegExp("^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$", "u"); -const pattern225 = new RegExp("^rl1:[0-9a-f]{64}$", "u"); -const schema431 = {"description":"One pinned change, discriminated by `kind`.","oneOf":[{"type":"object","title":"ReleaseSetItem","description":"Make `target` the live version of the node. `base` is the version that was live when the item was pinned; absent when the node was not live (a new entry or page).","additionalProperties":false,"required":["kind","nodeId","nodeKind","target"],"properties":{"kind":{"type":"string","enum":["set"]},"nodeId":{"$ref":"#/$defs/Id"},"nodeKind":{"$ref":"#/$defs/ReleaseNodeKind"},"target":{"$ref":"#/$defs/ReleaseNodeVersion"},"base":{"$ref":"#/$defs/ReleaseNodeVersion"}}},{"type":"object","title":"ReleaseRemoveItem","description":"Take the node out of the live snapshot. `base` is the live version that is removed.","additionalProperties":false,"required":["kind","nodeId","nodeKind","base"],"properties":{"kind":{"type":"string","enum":["remove"]},"nodeId":{"$ref":"#/$defs/Id"},"nodeKind":{"$ref":"#/$defs/ReleaseNodeKind"},"base":{"$ref":"#/$defs/ReleaseNodeVersion"}}}]}; -const schema433 = {"type":"string","description":"Kinds of graph node a release may carry. The site node is never one of them.","enum":["page","contentEntry","taxonomy","term","redirect","menu","asset"]}; -const schema434 = {"type":"object","description":"One version of a node: its revision and the SHA-256 of its canonical JSON, whose body the blob store keeps under `nodes/.json`.","additionalProperties":false,"required":["rev","hash"],"properties":{"rev":{"$ref":"#/$defs/Revision"},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$"}}}; +const schema429 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/release.schema.json","title":"Release","description":"A scheduled content change set (RFC-0008): node versions pinned by id, rev and content hash, applied on top of whatever is live at the release's time. A single scheduled publish is a one-item release. Each item records the version that was live when the release was pinned (`base`); if a pinned node or its live base changed before the release runs, the release is held as a conflict instead of overwriting anything. A person's approval is bound to `hash` (`rl1:`), which digests the items only, so a person may move an approved release without re-approving it while an agent's move or any content change voids the approval. Releases live in D1 next to the graph; they are not graph nodes, never enter a snapshot and are not part of site bundles.","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","items","state","hash","createdBy","createdAt","updatedAt","rev"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/ReleaseId"},"siteId":{"$ref":"#/$defs/Id"},"title":{"type":"string","description":"Optional name shown in Studio (\"Election night\"). A one-item release without a title is shown under its entry's or page's title.","minLength":1,"maxLength":200,"pattern":"^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$"},"items":{"type":"array","description":"The pinned changes, at most one per node. A release never contains the site node: site settings, domains and structure are published the ordinary way.","items":{"$ref":"#/$defs/ReleaseItem"},"minItems":1,"maxItems":100},"when":{"$ref":"#/$defs/ReleaseWhen"},"state":{"$ref":"#/$defs/ReleaseState"},"hash":{"$ref":"#/$defs/ReleaseHash"},"approval":{"$ref":"#/$defs/ReleaseApproval"},"outcome":{"$ref":"#/$defs/ReleaseOutcome"},"createdBy":{"$ref":"#/$defs/Actor"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"rev":{"$ref":"#/$defs/Revision"},"metadata":{"$ref":"#/$defs/Metadata"}},"$defs":{"Actor":{"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}},"ActorKind":{"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]},"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"LocalDateTime":{"type":"string","description":"A wall-clock date and time without an offset, to the minute (YYYY-MM-DDTHH:mm), as a person entered it; always paired with a TimeZone (RFC-0008).","pattern":"^[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])T(?:[01][0-9]|2[0-3]):[0-5][0-9]$"},"Metadata":{"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}},"ReleaseApproval":{"description":"Why the release may run, discriminated by `kind`.","oneOf":[{"type":"object","title":"ReleasePersonApproval","description":"A person approved these items (bound to `hash`) for `executeAt`, either by deciding an agent's request (`approvalId`) or by scheduling it themselves. The person's rights are checked again when the release runs.","additionalProperties":false,"required":["kind","by","at"],"properties":{"kind":{"type":"string","enum":["person"]},"by":{"$ref":"#/$defs/ReleasePerson"},"at":{"$ref":"#/$defs/Timestamp"},"executeAt":{"$ref":"#/$defs/Timestamp"},"approvalId":{"$ref":"#/$defs/Id"}}},{"type":"object","title":"ReleaseAutoApproval","description":"An agent key in `auto` mode scheduled a low-risk release without a person (ADR-0015). Valid only while the release runs before `windowEnds`: the end of the key's elevation, or 24 hours after scheduling, whichever comes first.","additionalProperties":false,"required":["kind","by","keyId","at","windowEnds"],"properties":{"kind":{"type":"string","enum":["auto"]},"by":{"$ref":"#/$defs/Actor"},"keyId":{"type":"string","pattern":"^[A-Za-z0-9_-]{6,64}$"},"at":{"$ref":"#/$defs/Timestamp"},"windowEnds":{"$ref":"#/$defs/Timestamp"}}}]},"ReleaseConflict":{"type":"object","description":"A node whose live version was not the pinned base when the release ran. `expected` absent: the node was not expected to be live; `found` absent: it was not live.","additionalProperties":false,"required":["nodeId"],"properties":{"nodeId":{"$ref":"#/$defs/Id"},"expected":{"$ref":"#/$defs/ReleaseNodeVersion"},"found":{"$ref":"#/$defs/ReleaseNodeVersion"}}},"ReleaseHash":{"type":"string","description":"`rl1:` and the lower-case hex SHA-256 of the release's canonical item lines (site id, then one line per item sorted by node id: kind, node id, target and base versions). The prefix keeps it apart from the graph state hash and from comment bindings (`cm1:`); approvals store it as their state.","pattern":"^rl1:[0-9a-f]{64}$"},"ReleaseId":{"type":"string","description":"A release id: `rel_` and 6 to 32 letters or digits.","pattern":"^rel_[A-Za-z0-9]{6,32}$"},"ReleaseItem":{"description":"One pinned change, discriminated by `kind`.","oneOf":[{"type":"object","title":"ReleaseSetItem","description":"Make `target` the live version of the node. `base` is the version that was live when the item was pinned; absent when the node was not live (a new entry or page).","additionalProperties":false,"required":["kind","nodeId","nodeKind","target"],"properties":{"kind":{"type":"string","enum":["set"]},"nodeId":{"$ref":"#/$defs/Id"},"nodeKind":{"$ref":"#/$defs/ReleaseNodeKind"},"target":{"$ref":"#/$defs/ReleaseNodeVersion"},"base":{"$ref":"#/$defs/ReleaseNodeVersion"}}},{"type":"object","title":"ReleaseRemoveItem","description":"Take the node out of the live snapshot. `base` is the live version that is removed.","additionalProperties":false,"required":["kind","nodeId","nodeKind","base"],"properties":{"kind":{"type":"string","enum":["remove"]},"nodeId":{"$ref":"#/$defs/Id"},"nodeKind":{"$ref":"#/$defs/ReleaseNodeKind"},"base":{"$ref":"#/$defs/ReleaseNodeVersion"}}}]},"ReleaseNodeKind":{"type":"string","description":"Kinds of graph node a release may carry. The site node is never one of them.","enum":["page","contentEntry","taxonomy","term","redirect","menu","asset"]},"ReleaseNodeVersion":{"type":"object","description":"One version of a node: its revision and the SHA-256 of its canonical JSON, whose body the blob store keeps under `nodes/.json`.","additionalProperties":false,"required":["rev","hash"],"properties":{"rev":{"$ref":"#/$defs/Revision"},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$"}}},"ReleaseOutcome":{"description":"What happened when the release was due, discriminated by `kind` (it matches the terminal `state`).","oneOf":[{"type":"object","title":"ReleasePublishedOutcome","description":"Applied: `snapshotId` is the new live snapshot (reason `scheduled`), composed from `baseSnapshotId` and the items. `purge` says how the edge cache followed.","additionalProperties":false,"required":["kind","runId","at","snapshotId","baseSnapshotId","latenessSeconds","purge"],"properties":{"kind":{"type":"string","enum":["published"]},"runId":{"$ref":"#/$defs/Id"},"at":{"$ref":"#/$defs/Timestamp"},"snapshotId":{"$ref":"#/$defs/Id"},"baseSnapshotId":{"$ref":"#/$defs/Id"},"latenessSeconds":{"type":"integer","minimum":0},"purge":{"type":"string","enum":["tags","everything","deferred","failed","none"]}}},{"type":"object","title":"ReleaseConflictOutcome","description":"Held: these nodes changed since the release was pinned. Nothing was published.","additionalProperties":false,"required":["kind","at","conflicts"],"properties":{"kind":{"type":"string","enum":["conflict"]},"runId":{"$ref":"#/$defs/Id"},"at":{"$ref":"#/$defs/Timestamp"},"conflicts":{"type":"array","items":{"$ref":"#/$defs/ReleaseConflict"},"minItems":1,"maxItems":100}}},{"type":"object","title":"ReleaseFailedOutcome","description":"Refused at run time; nothing was published.","additionalProperties":false,"required":["kind","at","code"],"properties":{"kind":{"type":"string","enum":["failed"]},"runId":{"$ref":"#/$defs/Id"},"at":{"$ref":"#/$defs/Timestamp"},"code":{"type":"string","enum":["dependency_missing","invalid_snapshot","site_missing","node_body_missing","store_error"]},"message":{"type":"string","maxLength":500,"pattern":"^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"}}},{"type":"object","title":"ReleaseMissedOutcome","description":"The scheduler reached the release more than an hour after its time; nothing was published. A person may publish it now.","additionalProperties":false,"required":["kind","at","latenessSeconds"],"properties":{"kind":{"type":"string","enum":["missed"]},"at":{"$ref":"#/$defs/Timestamp"},"latenessSeconds":{"type":"integer","minimum":0}}},{"type":"object","title":"ReleaseExpiredOutcome","description":"No valid approval when the release was due: never approved, the approver can no longer publish it, the agent's `auto` window ended, or its creator left.","additionalProperties":false,"required":["kind","at","reason"],"properties":{"kind":{"type":"string","enum":["expired"]},"at":{"$ref":"#/$defs/Timestamp"},"reason":{"type":"string","enum":["unapproved","approval_lost","auto_window_ended","creator_removed"]}}},{"type":"object","title":"ReleaseCanceledOutcome","description":"Withdrawn by a person or an agent; its nodes are unlocked.","additionalProperties":false,"required":["kind","at","by"],"properties":{"kind":{"type":"string","enum":["canceled"]},"at":{"$ref":"#/$defs/Timestamp"},"by":{"$ref":"#/$defs/Actor"}}}]},"ReleasePerson":{"type":"object","description":"A staff person (`user_…`). Agents never appear here.","additionalProperties":false,"required":["id"],"properties":{"id":{"type":"string","pattern":"^user_[A-Za-z0-9]{6,32}$"},"name":{"type":"string","maxLength":200}}},"ReleaseState":{"type":"string","description":"Lifecycle. `draft`: proposed or being assembled, nothing locked. `awaiting_approval`: waiting for a person; its nodes are locked. `scheduled`: approved (or allowed by `auto` mode) and locked until it runs. `running`: claimed by the scheduler. `published`: applied; `outcome` names the snapshot. `conflict`: a pinned node or its live base changed; held, nothing published. `missed`: the scheduler reached it more than an hour late; nothing published. `expired`: no valid approval at its time. `failed`: refused at run time (e.g. a dependency is missing). `canceled`: withdrawn.","enum":["draft","awaiting_approval","scheduled","running","published","conflict","missed","expired","failed","canceled"]},"ReleaseWhen":{"description":"When the release runs, discriminated by `kind`. Absent while a draft has no time yet.","oneOf":[{"type":"object","title":"ReleaseAt","description":"At an instant. `at` is what the scheduler compares with the clock; when a person entered a wall-clock time, `localTime` keeps it and `at` is recomputed from it and `timeZone` before the release runs, never earlier than the wall-clock time.","additionalProperties":false,"required":["kind","at","timeZone"],"properties":{"kind":{"type":"string","enum":["at"]},"at":{"$ref":"#/$defs/Timestamp"},"timeZone":{"$ref":"#/$defs/TimeZone"},"localTime":{"$ref":"#/$defs/LocalDateTime"}}},{"type":"object","title":"ReleaseOnApproval","description":"As soon as it is approved (a gate-only change such as setting an expiry on live content, or \"publish when approved\").","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["on-approval"]}}}]},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"},"TimeZone":{"type":"string","description":"IANA time zone name from the tz database (RFC-0008), e.g. Europe/Istanbul, America/Argentina/Buenos_Aires, Etc/GMT+3 or UTC. Wall-clock times a person enters are kept with their zone, so the instant is recomputed when governments change the rules. The pattern bounds the shape only; writers also check the name against the runtime's Intl zone list.","pattern":"^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$","maxLength":64}}}; +const schema430 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; +const schema431 = {"type":"string","description":"A release id: `rel_` and 6 to 32 letters or digits.","pattern":"^rel_[A-Za-z0-9]{6,32}$"}; +const schema432 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; +const schema444 = {"type":"string","description":"Lifecycle. `draft`: proposed or being assembled, nothing locked. `awaiting_approval`: waiting for a person; its nodes are locked. `scheduled`: approved (or allowed by `auto` mode) and locked until it runs. `running`: claimed by the scheduler. `published`: applied; `outcome` names the snapshot. `conflict`: a pinned node or its live base changed; held, nothing published. `missed`: the scheduler reached it more than an hour late; nothing published. `expired`: no valid approval at its time. `failed`: refused at run time (e.g. a dependency is missing). `canceled`: withdrawn.","enum":["draft","awaiting_approval","scheduled","running","published","conflict","missed","expired","failed","canceled"]}; +const schema445 = {"type":"string","description":"`rl1:` and the lower-case hex SHA-256 of the release's canonical item lines (site id, then one line per item sorted by node id: kind, node id, target and base versions). The prefix keeps it apart from the graph state hash and from comment bindings (`cm1:`); approvals store it as their state.","pattern":"^rl1:[0-9a-f]{64}$"}; +const schema441 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; +const schema437 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; +const schema473 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}}; +const pattern219 = new RegExp("^rel_[A-Za-z0-9]{6,32}$", "u"); +const pattern221 = new RegExp("^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$", "u"); +const pattern227 = new RegExp("^rl1:[0-9a-f]{64}$", "u"); +const schema433 = {"description":"One pinned change, discriminated by `kind`.","oneOf":[{"type":"object","title":"ReleaseSetItem","description":"Make `target` the live version of the node. `base` is the version that was live when the item was pinned; absent when the node was not live (a new entry or page).","additionalProperties":false,"required":["kind","nodeId","nodeKind","target"],"properties":{"kind":{"type":"string","enum":["set"]},"nodeId":{"$ref":"#/$defs/Id"},"nodeKind":{"$ref":"#/$defs/ReleaseNodeKind"},"target":{"$ref":"#/$defs/ReleaseNodeVersion"},"base":{"$ref":"#/$defs/ReleaseNodeVersion"}}},{"type":"object","title":"ReleaseRemoveItem","description":"Take the node out of the live snapshot. `base` is the live version that is removed.","additionalProperties":false,"required":["kind","nodeId","nodeKind","base"],"properties":{"kind":{"type":"string","enum":["remove"]},"nodeId":{"$ref":"#/$defs/Id"},"nodeKind":{"$ref":"#/$defs/ReleaseNodeKind"},"base":{"$ref":"#/$defs/ReleaseNodeVersion"}}}]}; +const schema435 = {"type":"string","description":"Kinds of graph node a release may carry. The site node is never one of them.","enum":["page","contentEntry","taxonomy","term","redirect","menu","asset"]}; +const schema436 = {"type":"object","description":"One version of a node: its revision and the SHA-256 of its canonical JSON, whose body the blob store keeps under `nodes/.json`.","additionalProperties":false,"required":["rev","hash"],"properties":{"rev":{"$ref":"#/$defs/Revision"},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$"}}}; function validate165(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -29814,7 +29939,7 @@ errors++; if(data.hash !== undefined){ let data1 = data.hash; if(typeof data1 === "string"){ -if(!pattern27.test(data1)){ +if(!pattern29.test(data1)){ const err5 = {instancePath:instancePath+"/hash",schemaPath:"#/properties/hash/pattern",keyword:"pattern",params:{pattern: "^[0-9a-f]{64}$"},message:"must match pattern \""+"^[0-9a-f]{64}$"+"\""}; if(vErrors === null){ vErrors = [err5]; @@ -29933,7 +30058,7 @@ vErrors.push(err5); errors++; } if(!(data0 === "set")){ -const err6 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema431.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err6 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema433.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err6]; } @@ -30001,7 +30126,7 @@ vErrors.push(err11); errors++; } if(!(((((((data2 === "page") || (data2 === "contentEntry")) || (data2 === "taxonomy")) || (data2 === "term")) || (data2 === "redirect")) || (data2 === "menu")) || (data2 === "asset"))){ -const err12 = {instancePath:instancePath+"/nodeKind",schemaPath:"#/$defs/ReleaseNodeKind/enum",keyword:"enum",params:{allowedValues: schema433.enum},message:"must be equal to one of the allowed values"}; +const err12 = {instancePath:instancePath+"/nodeKind",schemaPath:"#/$defs/ReleaseNodeKind/enum",keyword:"enum",params:{allowedValues: schema435.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err12]; } @@ -30107,7 +30232,7 @@ vErrors.push(err19); errors++; } if(!(data5 === "remove")){ -const err20 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema431.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err20 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema433.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err20]; } @@ -30175,7 +30300,7 @@ vErrors.push(err25); errors++; } if(!(((((((data7 === "page") || (data7 === "contentEntry")) || (data7 === "taxonomy")) || (data7 === "term")) || (data7 === "redirect")) || (data7 === "menu")) || (data7 === "asset"))){ -const err26 = {instancePath:instancePath+"/nodeKind",schemaPath:"#/$defs/ReleaseNodeKind/enum",keyword:"enum",params:{allowedValues: schema433.enum},message:"must be equal to one of the allowed values"}; +const err26 = {instancePath:instancePath+"/nodeKind",schemaPath:"#/$defs/ReleaseNodeKind/enum",keyword:"enum",params:{allowedValues: schema435.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err26]; } @@ -30243,10 +30368,10 @@ return errors === 0; } validate164.evaluated = {"dynamicProps":true,"dynamicItems":false}; -const schema438 = {"description":"When the release runs, discriminated by `kind`. Absent while a draft has no time yet.","oneOf":[{"type":"object","title":"ReleaseAt","description":"At an instant. `at` is what the scheduler compares with the clock; when a person entered a wall-clock time, `localTime` keeps it and `at` is recomputed from it and `timeZone` before the release runs, never earlier than the wall-clock time.","additionalProperties":false,"required":["kind","at","timeZone"],"properties":{"kind":{"type":"string","enum":["at"]},"at":{"$ref":"#/$defs/Timestamp"},"timeZone":{"$ref":"#/$defs/TimeZone"},"localTime":{"$ref":"#/$defs/LocalDateTime"}}},{"type":"object","title":"ReleaseOnApproval","description":"As soon as it is approved (a gate-only change such as setting an expiry on live content, or \"publish when approved\").","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["on-approval"]}}}]}; -const schema440 = {"type":"string","description":"IANA time zone name from the tz database (RFC-0008), e.g. Europe/Istanbul, America/Argentina/Buenos_Aires, Etc/GMT+3 or UTC. Wall-clock times a person enters are kept with their zone, so the instant is recomputed when governments change the rules. The pattern bounds the shape only; writers also check the name against the runtime's Intl zone list.","pattern":"^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$","maxLength":64}; -const schema441 = {"type":"string","description":"A wall-clock date and time without an offset, to the minute (YYYY-MM-DDTHH:mm), as a person entered it; always paired with a TimeZone (RFC-0008).","pattern":"^[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])T(?:[01][0-9]|2[0-3]):[0-5][0-9]$"}; -const pattern224 = new RegExp("^[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])T(?:[01][0-9]|2[0-3]):[0-5][0-9]$", "u"); +const schema440 = {"description":"When the release runs, discriminated by `kind`. Absent while a draft has no time yet.","oneOf":[{"type":"object","title":"ReleaseAt","description":"At an instant. `at` is what the scheduler compares with the clock; when a person entered a wall-clock time, `localTime` keeps it and `at` is recomputed from it and `timeZone` before the release runs, never earlier than the wall-clock time.","additionalProperties":false,"required":["kind","at","timeZone"],"properties":{"kind":{"type":"string","enum":["at"]},"at":{"$ref":"#/$defs/Timestamp"},"timeZone":{"$ref":"#/$defs/TimeZone"},"localTime":{"$ref":"#/$defs/LocalDateTime"}}},{"type":"object","title":"ReleaseOnApproval","description":"As soon as it is approved (a gate-only change such as setting an expiry on live content, or \"publish when approved\").","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["on-approval"]}}}]}; +const schema442 = {"type":"string","description":"IANA time zone name from the tz database (RFC-0008), e.g. Europe/Istanbul, America/Argentina/Buenos_Aires, Etc/GMT+3 or UTC. Wall-clock times a person enters are kept with their zone, so the instant is recomputed when governments change the rules. The pattern bounds the shape only; writers also check the name against the runtime's Intl zone list.","pattern":"^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$","maxLength":64}; +const schema443 = {"type":"string","description":"A wall-clock date and time without an offset, to the minute (YYYY-MM-DDTHH:mm), as a person entered it; always paired with a TimeZone (RFC-0008).","pattern":"^[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])T(?:[01][0-9]|2[0-3]):[0-5][0-9]$"}; +const pattern226 = new RegExp("^[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])T(?:[01][0-9]|2[0-3]):[0-5][0-9]$", "u"); function validate170(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -30318,7 +30443,7 @@ vErrors.push(err4); errors++; } if(!(data0 === "at")){ -const err5 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema438.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err5 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema440.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err5]; } @@ -30366,7 +30491,7 @@ vErrors.push(err8); } errors++; } -if(!pattern41.test(data2)){ +if(!pattern43.test(data2)){ const err9 = {instancePath:instancePath+"/timeZone",schemaPath:"#/$defs/TimeZone/pattern",keyword:"pattern",params:{pattern: "^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$"},message:"must match pattern \""+"^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$"+"\""}; if(vErrors === null){ vErrors = [err9]; @@ -30391,7 +30516,7 @@ errors++; if(data.localTime !== undefined){ let data3 = data.localTime; if(typeof data3 === "string"){ -if(!pattern224.test(data3)){ +if(!pattern226.test(data3)){ const err11 = {instancePath:instancePath+"/localTime",schemaPath:"#/$defs/LocalDateTime/pattern",keyword:"pattern",params:{pattern: "^[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])T(?:[01][0-9]|2[0-3]):[0-5][0-9]$"},message:"must match pattern \""+"^[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])T(?:[01][0-9]|2[0-3]):[0-5][0-9]$"+"\""}; if(vErrors === null){ vErrors = [err11]; @@ -30467,7 +30592,7 @@ vErrors.push(err16); errors++; } if(!(data4 === "on-approval")){ -const err17 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema438.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err17 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema440.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err17]; } @@ -30529,11 +30654,11 @@ return errors === 0; } validate170.evaluated = {"dynamicProps":true,"dynamicItems":false}; -const schema444 = {"description":"Why the release may run, discriminated by `kind`.","oneOf":[{"type":"object","title":"ReleasePersonApproval","description":"A person approved these items (bound to `hash`) for `executeAt`, either by deciding an agent's request (`approvalId`) or by scheduling it themselves. The person's rights are checked again when the release runs.","additionalProperties":false,"required":["kind","by","at"],"properties":{"kind":{"type":"string","enum":["person"]},"by":{"$ref":"#/$defs/ReleasePerson"},"at":{"$ref":"#/$defs/Timestamp"},"executeAt":{"$ref":"#/$defs/Timestamp"},"approvalId":{"$ref":"#/$defs/Id"}}},{"type":"object","title":"ReleaseAutoApproval","description":"An agent key in `auto` mode scheduled a low-risk release without a person (ADR-0015). Valid only while the release runs before `windowEnds`: the end of the key's elevation, or 24 hours after scheduling, whichever comes first.","additionalProperties":false,"required":["kind","by","keyId","at","windowEnds"],"properties":{"kind":{"type":"string","enum":["auto"]},"by":{"$ref":"#/$defs/Actor"},"keyId":{"type":"string","pattern":"^[A-Za-z0-9_-]{6,64}$"},"at":{"$ref":"#/$defs/Timestamp"},"windowEnds":{"$ref":"#/$defs/Timestamp"}}}]}; -const schema445 = {"type":"object","description":"A staff person (`user_…`). Agents never appear here.","additionalProperties":false,"required":["id"],"properties":{"id":{"type":"string","pattern":"^user_[A-Za-z0-9]{6,32}$"},"name":{"type":"string","maxLength":200}}}; -const pattern229 = new RegExp("^[A-Za-z0-9_-]{6,64}$", "u"); -const schema449 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}}; -const schema450 = {"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]}; +const schema446 = {"description":"Why the release may run, discriminated by `kind`.","oneOf":[{"type":"object","title":"ReleasePersonApproval","description":"A person approved these items (bound to `hash`) for `executeAt`, either by deciding an agent's request (`approvalId`) or by scheduling it themselves. The person's rights are checked again when the release runs.","additionalProperties":false,"required":["kind","by","at"],"properties":{"kind":{"type":"string","enum":["person"]},"by":{"$ref":"#/$defs/ReleasePerson"},"at":{"$ref":"#/$defs/Timestamp"},"executeAt":{"$ref":"#/$defs/Timestamp"},"approvalId":{"$ref":"#/$defs/Id"}}},{"type":"object","title":"ReleaseAutoApproval","description":"An agent key in `auto` mode scheduled a low-risk release without a person (ADR-0015). Valid only while the release runs before `windowEnds`: the end of the key's elevation, or 24 hours after scheduling, whichever comes first.","additionalProperties":false,"required":["kind","by","keyId","at","windowEnds"],"properties":{"kind":{"type":"string","enum":["auto"]},"by":{"$ref":"#/$defs/Actor"},"keyId":{"type":"string","pattern":"^[A-Za-z0-9_-]{6,64}$"},"at":{"$ref":"#/$defs/Timestamp"},"windowEnds":{"$ref":"#/$defs/Timestamp"}}}]}; +const schema447 = {"type":"object","description":"A staff person (`user_…`). Agents never appear here.","additionalProperties":false,"required":["id"],"properties":{"id":{"type":"string","pattern":"^user_[A-Za-z0-9]{6,32}$"},"name":{"type":"string","maxLength":200}}}; +const pattern231 = new RegExp("^[A-Za-z0-9_-]{6,64}$", "u"); +const schema451 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}}; +const schema452 = {"type":"string","description":"Who performed an action. Humans and agents share one actor model so the policy engine treats them uniformly.","enum":["human","agent","system"]}; function validate173(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -30591,7 +30716,7 @@ vErrors.push(err3); errors++; } if(!(((data0 === "human") || (data0 === "agent")) || (data0 === "system"))){ -const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/$defs/ActorKind/enum",keyword:"enum",params:{allowedValues: schema450.enum},message:"must be equal to one of the allowed values"}; +const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/$defs/ActorKind/enum",keyword:"enum",params:{allowedValues: schema452.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err4]; } @@ -30758,7 +30883,7 @@ vErrors.push(err4); errors++; } if(!(data0 === "person")){ -const err5 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema444.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err5 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema446.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err5]; } @@ -30796,7 +30921,7 @@ errors++; if(data1.id !== undefined){ let data2 = data1.id; if(typeof data2 === "string"){ -if(!pattern203.test(data2)){ +if(!pattern205.test(data2)){ const err8 = {instancePath:instancePath+"/by/id",schemaPath:"#/$defs/ReleasePerson/properties/id/pattern",keyword:"pattern",params:{pattern: "^user_[A-Za-z0-9]{6,32}$"},message:"must match pattern \""+"^user_[A-Za-z0-9]{6,32}$"+"\""}; if(vErrors === null){ vErrors = [err8]; @@ -31044,7 +31169,7 @@ vErrors.push(err28); errors++; } if(!(data7 === "auto")){ -const err29 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema444.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err29 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema446.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err29]; } @@ -31063,7 +31188,7 @@ errors = vErrors.length; if(data.keyId !== undefined){ let data9 = data.keyId; if(typeof data9 === "string"){ -if(!pattern229.test(data9)){ +if(!pattern231.test(data9)){ const err30 = {instancePath:instancePath+"/keyId",schemaPath:"#/oneOf/1/properties/keyId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9_-]{6,64}$"},message:"must match pattern \""+"^[A-Za-z0-9_-]{6,64}$"+"\""}; if(vErrors === null){ vErrors = [err30]; @@ -31187,9 +31312,9 @@ return errors === 0; } validate172.evaluated = {"dynamicProps":true,"dynamicItems":false}; -const schema454 = {"description":"What happened when the release was due, discriminated by `kind` (it matches the terminal `state`).","oneOf":[{"type":"object","title":"ReleasePublishedOutcome","description":"Applied: `snapshotId` is the new live snapshot (reason `scheduled`), composed from `baseSnapshotId` and the items. `purge` says how the edge cache followed.","additionalProperties":false,"required":["kind","runId","at","snapshotId","baseSnapshotId","latenessSeconds","purge"],"properties":{"kind":{"type":"string","enum":["published"]},"runId":{"$ref":"#/$defs/Id"},"at":{"$ref":"#/$defs/Timestamp"},"snapshotId":{"$ref":"#/$defs/Id"},"baseSnapshotId":{"$ref":"#/$defs/Id"},"latenessSeconds":{"type":"integer","minimum":0},"purge":{"type":"string","enum":["tags","everything","deferred","failed","none"]}}},{"type":"object","title":"ReleaseConflictOutcome","description":"Held: these nodes changed since the release was pinned. Nothing was published.","additionalProperties":false,"required":["kind","at","conflicts"],"properties":{"kind":{"type":"string","enum":["conflict"]},"runId":{"$ref":"#/$defs/Id"},"at":{"$ref":"#/$defs/Timestamp"},"conflicts":{"type":"array","items":{"$ref":"#/$defs/ReleaseConflict"},"minItems":1,"maxItems":100}}},{"type":"object","title":"ReleaseFailedOutcome","description":"Refused at run time; nothing was published.","additionalProperties":false,"required":["kind","at","code"],"properties":{"kind":{"type":"string","enum":["failed"]},"runId":{"$ref":"#/$defs/Id"},"at":{"$ref":"#/$defs/Timestamp"},"code":{"type":"string","enum":["dependency_missing","invalid_snapshot","site_missing","node_body_missing","store_error"]},"message":{"type":"string","maxLength":500,"pattern":"^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"}}},{"type":"object","title":"ReleaseMissedOutcome","description":"The scheduler reached the release more than an hour after its time; nothing was published. A person may publish it now.","additionalProperties":false,"required":["kind","at","latenessSeconds"],"properties":{"kind":{"type":"string","enum":["missed"]},"at":{"$ref":"#/$defs/Timestamp"},"latenessSeconds":{"type":"integer","minimum":0}}},{"type":"object","title":"ReleaseExpiredOutcome","description":"No valid approval when the release was due: never approved, the approver can no longer publish it, the agent's `auto` window ended, or its creator left.","additionalProperties":false,"required":["kind","at","reason"],"properties":{"kind":{"type":"string","enum":["expired"]},"at":{"$ref":"#/$defs/Timestamp"},"reason":{"type":"string","enum":["unapproved","approval_lost","auto_window_ended","creator_removed"]}}},{"type":"object","title":"ReleaseCanceledOutcome","description":"Withdrawn by a person or an agent; its nodes are unlocked.","additionalProperties":false,"required":["kind","at","by"],"properties":{"kind":{"type":"string","enum":["canceled"]},"at":{"$ref":"#/$defs/Timestamp"},"by":{"$ref":"#/$defs/Actor"}}}]}; -const pattern236 = new RegExp("^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$", "u"); -const schema461 = {"type":"object","description":"A node whose live version was not the pinned base when the release ran. `expected` absent: the node was not expected to be live; `found` absent: it was not live.","additionalProperties":false,"required":["nodeId"],"properties":{"nodeId":{"$ref":"#/$defs/Id"},"expected":{"$ref":"#/$defs/ReleaseNodeVersion"},"found":{"$ref":"#/$defs/ReleaseNodeVersion"}}}; +const schema456 = {"description":"What happened when the release was due, discriminated by `kind` (it matches the terminal `state`).","oneOf":[{"type":"object","title":"ReleasePublishedOutcome","description":"Applied: `snapshotId` is the new live snapshot (reason `scheduled`), composed from `baseSnapshotId` and the items. `purge` says how the edge cache followed.","additionalProperties":false,"required":["kind","runId","at","snapshotId","baseSnapshotId","latenessSeconds","purge"],"properties":{"kind":{"type":"string","enum":["published"]},"runId":{"$ref":"#/$defs/Id"},"at":{"$ref":"#/$defs/Timestamp"},"snapshotId":{"$ref":"#/$defs/Id"},"baseSnapshotId":{"$ref":"#/$defs/Id"},"latenessSeconds":{"type":"integer","minimum":0},"purge":{"type":"string","enum":["tags","everything","deferred","failed","none"]}}},{"type":"object","title":"ReleaseConflictOutcome","description":"Held: these nodes changed since the release was pinned. Nothing was published.","additionalProperties":false,"required":["kind","at","conflicts"],"properties":{"kind":{"type":"string","enum":["conflict"]},"runId":{"$ref":"#/$defs/Id"},"at":{"$ref":"#/$defs/Timestamp"},"conflicts":{"type":"array","items":{"$ref":"#/$defs/ReleaseConflict"},"minItems":1,"maxItems":100}}},{"type":"object","title":"ReleaseFailedOutcome","description":"Refused at run time; nothing was published.","additionalProperties":false,"required":["kind","at","code"],"properties":{"kind":{"type":"string","enum":["failed"]},"runId":{"$ref":"#/$defs/Id"},"at":{"$ref":"#/$defs/Timestamp"},"code":{"type":"string","enum":["dependency_missing","invalid_snapshot","site_missing","node_body_missing","store_error"]},"message":{"type":"string","maxLength":500,"pattern":"^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"}}},{"type":"object","title":"ReleaseMissedOutcome","description":"The scheduler reached the release more than an hour after its time; nothing was published. A person may publish it now.","additionalProperties":false,"required":["kind","at","latenessSeconds"],"properties":{"kind":{"type":"string","enum":["missed"]},"at":{"$ref":"#/$defs/Timestamp"},"latenessSeconds":{"type":"integer","minimum":0}}},{"type":"object","title":"ReleaseExpiredOutcome","description":"No valid approval when the release was due: never approved, the approver can no longer publish it, the agent's `auto` window ended, or its creator left.","additionalProperties":false,"required":["kind","at","reason"],"properties":{"kind":{"type":"string","enum":["expired"]},"at":{"$ref":"#/$defs/Timestamp"},"reason":{"type":"string","enum":["unapproved","approval_lost","auto_window_ended","creator_removed"]}}},{"type":"object","title":"ReleaseCanceledOutcome","description":"Withdrawn by a person or an agent; its nodes are unlocked.","additionalProperties":false,"required":["kind","at","by"],"properties":{"kind":{"type":"string","enum":["canceled"]},"at":{"$ref":"#/$defs/Timestamp"},"by":{"$ref":"#/$defs/Actor"}}}]}; +const pattern238 = new RegExp("^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$", "u"); +const schema463 = {"type":"object","description":"A node whose live version was not the pinned base when the release ran. `expected` absent: the node was not expected to be live; `found` absent: it was not live.","additionalProperties":false,"required":["nodeId"],"properties":{"nodeId":{"$ref":"#/$defs/Id"},"expected":{"$ref":"#/$defs/ReleaseNodeVersion"},"found":{"$ref":"#/$defs/ReleaseNodeVersion"}}}; function validate177(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -31408,7 +31533,7 @@ vErrors.push(err8); errors++; } if(!(data0 === "published")){ -const err9 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema454.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err9 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema456.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err9]; } @@ -31616,7 +31741,7 @@ vErrors.push(err26); errors++; } if(!(((((data6 === "tags") || (data6 === "everything")) || (data6 === "deferred")) || (data6 === "failed")) || (data6 === "none"))){ -const err27 = {instancePath:instancePath+"/purge",schemaPath:"#/oneOf/0/properties/purge/enum",keyword:"enum",params:{allowedValues: schema454.oneOf[0].properties.purge.enum},message:"must be equal to one of the allowed values"}; +const err27 = {instancePath:instancePath+"/purge",schemaPath:"#/oneOf/0/properties/purge/enum",keyword:"enum",params:{allowedValues: schema456.oneOf[0].properties.purge.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err27]; } @@ -31700,7 +31825,7 @@ vErrors.push(err33); errors++; } if(!(data7 === "conflict")){ -const err34 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema454.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err34 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema456.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err34]; } @@ -31903,7 +32028,7 @@ vErrors.push(err49); errors++; } if(!(data12 === "failed")){ -const err50 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/2/properties/kind/enum",keyword:"enum",params:{allowedValues: schema454.oneOf[2].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err50 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/2/properties/kind/enum",keyword:"enum",params:{allowedValues: schema456.oneOf[2].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err50]; } @@ -31996,7 +32121,7 @@ vErrors.push(err57); errors++; } if(!(((((data15 === "dependency_missing") || (data15 === "invalid_snapshot")) || (data15 === "site_missing")) || (data15 === "node_body_missing")) || (data15 === "store_error"))){ -const err58 = {instancePath:instancePath+"/code",schemaPath:"#/oneOf/2/properties/code/enum",keyword:"enum",params:{allowedValues: schema454.oneOf[2].properties.code.enum},message:"must be equal to one of the allowed values"}; +const err58 = {instancePath:instancePath+"/code",schemaPath:"#/oneOf/2/properties/code/enum",keyword:"enum",params:{allowedValues: schema456.oneOf[2].properties.code.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err58]; } @@ -32019,7 +32144,7 @@ vErrors.push(err59); } errors++; } -if(!pattern236.test(data16)){ +if(!pattern238.test(data16)){ const err60 = {instancePath:instancePath+"/message",schemaPath:"#/oneOf/2/properties/message/pattern",keyword:"pattern",params:{pattern: "^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"},message:"must match pattern \""+"^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"+"\""}; if(vErrors === null){ vErrors = [err60]; @@ -32122,7 +32247,7 @@ vErrors.push(err67); errors++; } if(!(data17 === "missed")){ -const err68 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/3/properties/kind/enum",keyword:"enum",params:{allowedValues: schema454.oneOf[3].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err68 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/3/properties/kind/enum",keyword:"enum",params:{allowedValues: schema456.oneOf[3].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err68]; } @@ -32263,7 +32388,7 @@ vErrors.push(err78); errors++; } if(!(data20 === "expired")){ -const err79 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/4/properties/kind/enum",keyword:"enum",params:{allowedValues: schema454.oneOf[4].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err79 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/4/properties/kind/enum",keyword:"enum",params:{allowedValues: schema456.oneOf[4].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err79]; } @@ -32311,7 +32436,7 @@ vErrors.push(err82); errors++; } if(!((((data22 === "unapproved") || (data22 === "approval_lost")) || (data22 === "auto_window_ended")) || (data22 === "creator_removed"))){ -const err83 = {instancePath:instancePath+"/reason",schemaPath:"#/oneOf/4/properties/reason/enum",keyword:"enum",params:{allowedValues: schema454.oneOf[4].properties.reason.enum},message:"must be equal to one of the allowed values"}; +const err83 = {instancePath:instancePath+"/reason",schemaPath:"#/oneOf/4/properties/reason/enum",keyword:"enum",params:{allowedValues: schema456.oneOf[4].properties.reason.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err83]; } @@ -32402,7 +32527,7 @@ vErrors.push(err89); errors++; } if(!(data23 === "canceled")){ -const err90 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/5/properties/kind/enum",keyword:"enum",params:{allowedValues: schema454.oneOf[5].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err90 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/5/properties/kind/enum",keyword:"enum",params:{allowedValues: schema456.oneOf[5].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err90]; } @@ -32613,7 +32738,7 @@ vErrors.push(err9); errors++; } for(const key0 in data){ -if(!(func1.call(schema427.properties, key0))){ +if(!(func1.call(schema429.properties, key0))){ const err10 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}; if(vErrors === null){ vErrors = [err10]; @@ -32637,7 +32762,7 @@ vErrors.push(err11); errors++; } if(!(data0 === 1)){ -const err12 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema428.enum},message:"must be equal to one of the allowed values"}; +const err12 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema430.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err12]; } @@ -32650,7 +32775,7 @@ errors++; if(data.id !== undefined){ let data1 = data.id; if(typeof data1 === "string"){ -if(!pattern217.test(data1)){ +if(!pattern219.test(data1)){ const err13 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/ReleaseId/pattern",keyword:"pattern",params:{pattern: "^rel_[A-Za-z0-9]{6,32}$"},message:"must match pattern \""+"^rel_[A-Za-z0-9]{6,32}$"+"\""}; if(vErrors === null){ vErrors = [err13]; @@ -32740,7 +32865,7 @@ vErrors.push(err20); } errors++; } -if(!pattern219.test(data3)){ +if(!pattern221.test(data3)){ const err21 = {instancePath:instancePath+"/title",schemaPath:"#/properties/title/pattern",keyword:"pattern",params:{pattern: "^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$"},message:"must match pattern \""+"^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$"+"\""}; if(vErrors === null){ vErrors = [err21]; @@ -32823,7 +32948,7 @@ vErrors.push(err26); errors++; } if(!((((((((((data7 === "draft") || (data7 === "awaiting_approval")) || (data7 === "scheduled")) || (data7 === "running")) || (data7 === "published")) || (data7 === "conflict")) || (data7 === "missed")) || (data7 === "expired")) || (data7 === "failed")) || (data7 === "canceled"))){ -const err27 = {instancePath:instancePath+"/state",schemaPath:"#/$defs/ReleaseState/enum",keyword:"enum",params:{allowedValues: schema442.enum},message:"must be equal to one of the allowed values"}; +const err27 = {instancePath:instancePath+"/state",schemaPath:"#/$defs/ReleaseState/enum",keyword:"enum",params:{allowedValues: schema444.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err27]; } @@ -32836,7 +32961,7 @@ errors++; if(data.hash !== undefined){ let data8 = data.hash; if(typeof data8 === "string"){ -if(!pattern225.test(data8)){ +if(!pattern227.test(data8)){ const err28 = {instancePath:instancePath+"/hash",schemaPath:"#/$defs/ReleaseHash/pattern",keyword:"pattern",params:{pattern: "^rl1:[0-9a-f]{64}$"},message:"must match pattern \""+"^rl1:[0-9a-f]{64}$"+"\""}; if(vErrors === null){ vErrors = [err28]; @@ -32995,18 +33120,18 @@ return errors === 0; validate163.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; export const validateAutomation = validate184; -const schema472 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/automation.schema.json","title":"Automation","description":"A recurring job on a site (RFC-0008), created by a person in Studio: either a built-in deterministic job that Cloudhal runs itself (reports, findings and notifications; it never publishes), or a scheduled agent task that an external agent picks up over MCP with the automation's own key, inside a run window, under a mode cap and budgets. No language model runs inside Cloudhal. Scheduling never grants rights: every call of a task runs with the key's scopes narrowed to its owner's current role. Automations live in D1 next to the graph; they are not graph nodes and are not part of site bundles.","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","name","job","recurrence","state","consecutiveFailures","owner","createdAt","updatedAt","rev"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/AutomationId"},"siteId":{"$ref":"#/$defs/Id"},"name":{"type":"string","minLength":1,"maxLength":120,"pattern":"^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$"},"job":{"$ref":"#/$defs/AutomationJob"},"recurrence":{"$ref":"#/$defs/AutomationRecurrence"},"state":{"$ref":"#/$defs/AutomationState"},"pausedReason":{"type":"string","description":"Why the automation is not `active`: a person paused it; its agent paused it; three consecutive failures; a run exceeded its budget; the site's kill switch; its key was revoked; its owner left or no longer holds the scopes it needs.","enum":["person","agent","failures","budget","kill-switch","key-revoked","owner-removed","role-changed"]},"consecutiveFailures":{"type":"integer","minimum":0},"owner":{"$ref":"#/$defs/AutomationOwner"},"nextRunAt":{"$ref":"#/$defs/Timestamp"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"rev":{"$ref":"#/$defs/Revision"},"metadata":{"$ref":"#/$defs/Metadata"}},"$defs":{"AutomationBudget":{"type":"object","description":"Per-run limits Cloudhal counts and enforces for an agent task: tool calls, mutations, approval requests filed and minutes since the run began. Exceeding one ends the run (`budget-exceeded`) and pauses the automation.","additionalProperties":false,"required":["maxToolCalls","maxWrites","maxApprovalRequests","maxMinutes"],"properties":{"maxToolCalls":{"type":"integer","minimum":1,"maximum":500},"maxWrites":{"type":"integer","minimum":0,"maximum":200},"maxApprovalRequests":{"type":"integer","minimum":0,"maximum":20},"maxMinutes":{"type":"integer","minimum":1,"maximum":240}}},"AutomationId":{"type":"string","description":"An automation id: `aut_` and 6 to 32 letters or digits.","pattern":"^aut_[A-Za-z0-9]{6,32}$"},"AutomationJob":{"description":"What runs, discriminated by `kind`.","oneOf":[{"type":"object","title":"BuiltinAutomationJob","description":"A deterministic job Cloudhal runs itself, in bounded steps. Its outputs are findings, reports and Studio notifications; it never changes or publishes content.","additionalProperties":false,"required":["kind","builtin"],"properties":{"kind":{"type":"string","enum":["builtin"]},"builtin":{"type":"string","enum":["broken-links","stale-content","feed-check","weekly-digest","cleanup"]},"staleAfterDays":{"type":"integer","description":"`stale-content` only: entries not updated for this many days are listed for review.","minimum":30,"maximum":1095}}},{"type":"object","title":"AgentTaskAutomationJob","description":"A task an external agent performs over MCP. At each occurrence a run window opens for `windowMinutes`; the automation's key (`keyId`, owned by the automation's owner) is valid only inside an open run, which the agent starts with `begin_run` and ends with `end_run`. The run's mode is the key's effective mode capped by `modeCap`; it never waits for a person: calls that need one leave an approval request and the run ends. `readsUntrustedText` declares that the task reads visitor-supplied text (comments, 404 paths, fetched pages); such an automation's key cannot hold write scopes, and any run that reads such text is limited to reading, labelling and hiding from then on.","additionalProperties":false,"required":["kind","keyId","instructions","modeCap","readsUntrustedText","windowMinutes","budget"],"properties":{"kind":{"type":"string","enum":["agent-task"]},"keyId":{"type":"string","pattern":"^[A-Za-z0-9_-]{6,64}$"},"instructions":{"type":"string","description":"What the owner asks the agent to do, shown to the agent by `begin_run`. Plain text.","minLength":1,"maxLength":2000,"pattern":"^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$"},"skill":{"type":"string","description":"A skill the run declares (its scopes and approval list narrow the run further).","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","maxLength":64},"modeCap":{"type":"string","description":"The most autonomous mode a run may use; never `bypass`.","enum":["plan","manual","edits","auto"]},"readsUntrustedText":{"type":"boolean"},"windowMinutes":{"type":"integer","minimum":5,"maximum":240},"budget":{"$ref":"#/$defs/AutomationBudget"}}}]},"AutomationOwner":{"type":"object","description":"The person who created the automation and owns its key (`user_…`). Agents never own automations.","additionalProperties":false,"required":["id"],"properties":{"id":{"type":"string","pattern":"^user_[A-Za-z0-9]{6,32}$"},"name":{"type":"string","maxLength":200}}},"AutomationRecurrence":{"type":"object","description":"When the automation runs: the first occurrence as a wall-clock time in `timeZone`, repeated by `rule` (absent: once). Occurrences are computed in the zone, so \"Mondays 09:07\" stays 09:07 across daylight-saving changes; a time that does not exist on a given day (a spring-forward gap) runs at the next valid minute, and a time that occurs twice runs once, at the first (a documented deviation from RFC 5545 §3.3.10, which drops such occurrences). `preset` records which Studio choice produced the rule.","additionalProperties":false,"required":["start","timeZone","preset"],"properties":{"start":{"$ref":"#/$defs/LocalDateTime"},"timeZone":{"$ref":"#/$defs/TimeZone"},"rule":{"$ref":"#/$defs/AutomationRule"},"preset":{"type":"string","enum":["once","hourly","daily","weekdays","weekly","monthly","custom"]}}},"AutomationRule":{"type":"string","description":"The recurrence as an RFC 5545 RRULE value in Cloudhal's subset, without the `RRULE:` prefix: FREQ DAILY, WEEKLY or MONTHLY; INTERVAL; BYDAY (ordinals such as 2TU or -1FR only with MONTHLY); BYMONTHDAY (1–31 or -1); BYHOUR; BYMINUTE; COUNT or UNTIL. The pattern bounds the vocabulary; the parser refuses repeated parts, COUNT with UNTIL, ordinals outside MONTHLY and runs closer than the job's minimum interval.","maxLength":300,"pattern":"^FREQ=(?:DAILY|WEEKLY|MONTHLY)(?:;(?:INTERVAL=[1-9][0-9]{0,2}|BYDAY=(?:[+-]?[1-5])?(?:MO|TU|WE|TH|FR|SA|SU)(?:,(?:[+-]?[1-5])?(?:MO|TU|WE|TH|FR|SA|SU)){0,6}|BYMONTHDAY=(?:-1|[1-9]|[12][0-9]|3[01])(?:,(?:-1|[1-9]|[12][0-9]|3[01])){0,30}|BYHOUR=(?:[01]?[0-9]|2[0-3])(?:,(?:[01]?[0-9]|2[0-3])){0,23}|BYMINUTE=[0-5]?[0-9](?:,[0-5]?[0-9]){0,3}|COUNT=[1-9][0-9]{0,3}|UNTIL=[0-9]{8}T[0-9]{6}Z))*$"},"AutomationState":{"type":"string","description":"`active` runs on its recurrence; `paused` and `auto-paused` (by failures or a budget) wait for a person to resume; `broken` needs a person to repair it (key revoked, owner gone); `ended` reached its COUNT or UNTIL.","enum":["active","paused","auto-paused","broken","ended"]},"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"LocalDateTime":{"type":"string","description":"A wall-clock date and time without an offset, to the minute (YYYY-MM-DDTHH:mm), as a person entered it; always paired with a TimeZone (RFC-0008).","pattern":"^[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])T(?:[01][0-9]|2[0-3]):[0-5][0-9]$"},"Metadata":{"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"},"TimeZone":{"type":"string","description":"IANA time zone name from the tz database (RFC-0008), e.g. Europe/Istanbul, America/Argentina/Buenos_Aires, Etc/GMT+3 or UTC. Wall-clock times a person enters are kept with their zone, so the instant is recomputed when governments change the rules. The pattern bounds the shape only; writers also check the name against the runtime's Intl zone list.","pattern":"^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$","maxLength":64}}}; -const schema473 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; -const schema474 = {"type":"string","description":"An automation id: `aut_` and 6 to 32 letters or digits.","pattern":"^aut_[A-Za-z0-9]{6,32}$"}; -const schema475 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; -const schema482 = {"type":"string","description":"`active` runs on its recurrence; `paused` and `auto-paused` (by failures or a budget) wait for a person to resume; `broken` needs a person to repair it (key revoked, owner gone); `ended` reached its COUNT or UNTIL.","enum":["active","paused","auto-paused","broken","ended"]}; -const schema483 = {"type":"object","description":"The person who created the automation and owns its key (`user_…`). Agents never own automations.","additionalProperties":false,"required":["id"],"properties":{"id":{"type":"string","pattern":"^user_[A-Za-z0-9]{6,32}$"},"name":{"type":"string","maxLength":200}}}; -const schema484 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; -const schema487 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; -const schema488 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}}; -const pattern237 = new RegExp("^aut_[A-Za-z0-9]{6,32}$", "u"); -const schema476 = {"description":"What runs, discriminated by `kind`.","oneOf":[{"type":"object","title":"BuiltinAutomationJob","description":"A deterministic job Cloudhal runs itself, in bounded steps. Its outputs are findings, reports and Studio notifications; it never changes or publishes content.","additionalProperties":false,"required":["kind","builtin"],"properties":{"kind":{"type":"string","enum":["builtin"]},"builtin":{"type":"string","enum":["broken-links","stale-content","feed-check","weekly-digest","cleanup"]},"staleAfterDays":{"type":"integer","description":"`stale-content` only: entries not updated for this many days are listed for review.","minimum":30,"maximum":1095}}},{"type":"object","title":"AgentTaskAutomationJob","description":"A task an external agent performs over MCP. At each occurrence a run window opens for `windowMinutes`; the automation's key (`keyId`, owned by the automation's owner) is valid only inside an open run, which the agent starts with `begin_run` and ends with `end_run`. The run's mode is the key's effective mode capped by `modeCap`; it never waits for a person: calls that need one leave an approval request and the run ends. `readsUntrustedText` declares that the task reads visitor-supplied text (comments, 404 paths, fetched pages); such an automation's key cannot hold write scopes, and any run that reads such text is limited to reading, labelling and hiding from then on.","additionalProperties":false,"required":["kind","keyId","instructions","modeCap","readsUntrustedText","windowMinutes","budget"],"properties":{"kind":{"type":"string","enum":["agent-task"]},"keyId":{"type":"string","pattern":"^[A-Za-z0-9_-]{6,64}$"},"instructions":{"type":"string","description":"What the owner asks the agent to do, shown to the agent by `begin_run`. Plain text.","minLength":1,"maxLength":2000,"pattern":"^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$"},"skill":{"type":"string","description":"A skill the run declares (its scopes and approval list narrow the run further).","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","maxLength":64},"modeCap":{"type":"string","description":"The most autonomous mode a run may use; never `bypass`.","enum":["plan","manual","edits","auto"]},"readsUntrustedText":{"type":"boolean"},"windowMinutes":{"type":"integer","minimum":5,"maximum":240},"budget":{"$ref":"#/$defs/AutomationBudget"}}}]}; -const schema477 = {"type":"object","description":"Per-run limits Cloudhal counts and enforces for an agent task: tool calls, mutations, approval requests filed and minutes since the run began. Exceeding one ends the run (`budget-exceeded`) and pauses the automation.","additionalProperties":false,"required":["maxToolCalls","maxWrites","maxApprovalRequests","maxMinutes"],"properties":{"maxToolCalls":{"type":"integer","minimum":1,"maximum":500},"maxWrites":{"type":"integer","minimum":0,"maximum":200},"maxApprovalRequests":{"type":"integer","minimum":0,"maximum":20},"maxMinutes":{"type":"integer","minimum":1,"maximum":240}}}; +const schema474 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/automation.schema.json","title":"Automation","description":"A recurring job on a site (RFC-0008), created by a person in Studio: either a built-in deterministic job that Cloudhal runs itself (reports, findings and notifications; it never publishes), or a scheduled agent task that an external agent picks up over MCP with the automation's own key, inside a run window, under a mode cap and budgets. No language model runs inside Cloudhal. Scheduling never grants rights: every call of a task runs with the key's scopes narrowed to its owner's current role. Automations live in D1 next to the graph; they are not graph nodes and are not part of site bundles.","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","name","job","recurrence","state","consecutiveFailures","owner","createdAt","updatedAt","rev"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/AutomationId"},"siteId":{"$ref":"#/$defs/Id"},"name":{"type":"string","minLength":1,"maxLength":120,"pattern":"^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$"},"job":{"$ref":"#/$defs/AutomationJob"},"recurrence":{"$ref":"#/$defs/AutomationRecurrence"},"state":{"$ref":"#/$defs/AutomationState"},"pausedReason":{"type":"string","description":"Why the automation is not `active`: a person paused it; its agent paused it; three consecutive failures; a run exceeded its budget; the site's kill switch; its key was revoked; its owner left or no longer holds the scopes it needs.","enum":["person","agent","failures","budget","kill-switch","key-revoked","owner-removed","role-changed"]},"consecutiveFailures":{"type":"integer","minimum":0},"owner":{"$ref":"#/$defs/AutomationOwner"},"nextRunAt":{"$ref":"#/$defs/Timestamp"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"rev":{"$ref":"#/$defs/Revision"},"metadata":{"$ref":"#/$defs/Metadata"}},"$defs":{"AutomationBudget":{"type":"object","description":"Per-run limits Cloudhal counts and enforces for an agent task: tool calls, mutations, approval requests filed and minutes since the run began. Exceeding one ends the run (`budget-exceeded`) and pauses the automation.","additionalProperties":false,"required":["maxToolCalls","maxWrites","maxApprovalRequests","maxMinutes"],"properties":{"maxToolCalls":{"type":"integer","minimum":1,"maximum":500},"maxWrites":{"type":"integer","minimum":0,"maximum":200},"maxApprovalRequests":{"type":"integer","minimum":0,"maximum":20},"maxMinutes":{"type":"integer","minimum":1,"maximum":240}}},"AutomationId":{"type":"string","description":"An automation id: `aut_` and 6 to 32 letters or digits.","pattern":"^aut_[A-Za-z0-9]{6,32}$"},"AutomationJob":{"description":"What runs, discriminated by `kind`.","oneOf":[{"type":"object","title":"BuiltinAutomationJob","description":"A deterministic job Cloudhal runs itself, in bounded steps. Its outputs are findings, reports and Studio notifications; it never changes or publishes content.","additionalProperties":false,"required":["kind","builtin"],"properties":{"kind":{"type":"string","enum":["builtin"]},"builtin":{"type":"string","enum":["broken-links","stale-content","feed-check","weekly-digest","cleanup"]},"staleAfterDays":{"type":"integer","description":"`stale-content` only: entries not updated for this many days are listed for review.","minimum":30,"maximum":1095}}},{"type":"object","title":"AgentTaskAutomationJob","description":"A task an external agent performs over MCP. At each occurrence a run window opens for `windowMinutes`; the automation's key (`keyId`, owned by the automation's owner) is valid only inside an open run, which the agent starts with `begin_run` and ends with `end_run`. The run's mode is the key's effective mode capped by `modeCap`; it never waits for a person: calls that need one leave an approval request and the run ends. `readsUntrustedText` declares that the task reads visitor-supplied text (comments, 404 paths, fetched pages); such an automation's key cannot hold write scopes, and any run that reads such text is limited to reading, labelling and hiding from then on.","additionalProperties":false,"required":["kind","keyId","instructions","modeCap","readsUntrustedText","windowMinutes","budget"],"properties":{"kind":{"type":"string","enum":["agent-task"]},"keyId":{"type":"string","pattern":"^[A-Za-z0-9_-]{6,64}$"},"instructions":{"type":"string","description":"What the owner asks the agent to do, shown to the agent by `begin_run`. Plain text.","minLength":1,"maxLength":2000,"pattern":"^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$"},"skill":{"type":"string","description":"A skill the run declares (its scopes and approval list narrow the run further).","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","maxLength":64},"modeCap":{"type":"string","description":"The most autonomous mode a run may use; never `bypass`.","enum":["plan","manual","edits","auto"]},"readsUntrustedText":{"type":"boolean"},"windowMinutes":{"type":"integer","minimum":5,"maximum":240},"budget":{"$ref":"#/$defs/AutomationBudget"}}}]},"AutomationOwner":{"type":"object","description":"The person who created the automation and owns its key (`user_…`). Agents never own automations.","additionalProperties":false,"required":["id"],"properties":{"id":{"type":"string","pattern":"^user_[A-Za-z0-9]{6,32}$"},"name":{"type":"string","maxLength":200}}},"AutomationRecurrence":{"type":"object","description":"When the automation runs: the first occurrence as a wall-clock time in `timeZone`, repeated by `rule` (absent: once). Occurrences are computed in the zone, so \"Mondays 09:07\" stays 09:07 across daylight-saving changes; a time that does not exist on a given day (a spring-forward gap) runs at the next valid minute, and a time that occurs twice runs once, at the first (a documented deviation from RFC 5545 §3.3.10, which drops such occurrences). `preset` records which Studio choice produced the rule.","additionalProperties":false,"required":["start","timeZone","preset"],"properties":{"start":{"$ref":"#/$defs/LocalDateTime"},"timeZone":{"$ref":"#/$defs/TimeZone"},"rule":{"$ref":"#/$defs/AutomationRule"},"preset":{"type":"string","enum":["once","hourly","daily","weekdays","weekly","monthly","custom"]}}},"AutomationRule":{"type":"string","description":"The recurrence as an RFC 5545 RRULE value in Cloudhal's subset, without the `RRULE:` prefix: FREQ DAILY, WEEKLY or MONTHLY; INTERVAL; BYDAY (ordinals such as 2TU or -1FR only with MONTHLY); BYMONTHDAY (1–31 or -1); BYHOUR; BYMINUTE; COUNT or UNTIL. The pattern bounds the vocabulary; the parser refuses repeated parts, COUNT with UNTIL, ordinals outside MONTHLY and runs closer than the job's minimum interval.","maxLength":300,"pattern":"^FREQ=(?:DAILY|WEEKLY|MONTHLY)(?:;(?:INTERVAL=[1-9][0-9]{0,2}|BYDAY=(?:[+-]?[1-5])?(?:MO|TU|WE|TH|FR|SA|SU)(?:,(?:[+-]?[1-5])?(?:MO|TU|WE|TH|FR|SA|SU)){0,6}|BYMONTHDAY=(?:-1|[1-9]|[12][0-9]|3[01])(?:,(?:-1|[1-9]|[12][0-9]|3[01])){0,30}|BYHOUR=(?:[01]?[0-9]|2[0-3])(?:,(?:[01]?[0-9]|2[0-3])){0,23}|BYMINUTE=[0-5]?[0-9](?:,[0-5]?[0-9]){0,3}|COUNT=[1-9][0-9]{0,3}|UNTIL=[0-9]{8}T[0-9]{6}Z))*$"},"AutomationState":{"type":"string","description":"`active` runs on its recurrence; `paused` and `auto-paused` (by failures or a budget) wait for a person to resume; `broken` needs a person to repair it (key revoked, owner gone); `ended` reached its COUNT or UNTIL.","enum":["active","paused","auto-paused","broken","ended"]},"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"LocalDateTime":{"type":"string","description":"A wall-clock date and time without an offset, to the minute (YYYY-MM-DDTHH:mm), as a person entered it; always paired with a TimeZone (RFC-0008).","pattern":"^[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])T(?:[01][0-9]|2[0-3]):[0-5][0-9]$"},"Metadata":{"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"},"TimeZone":{"type":"string","description":"IANA time zone name from the tz database (RFC-0008), e.g. Europe/Istanbul, America/Argentina/Buenos_Aires, Etc/GMT+3 or UTC. Wall-clock times a person enters are kept with their zone, so the instant is recomputed when governments change the rules. The pattern bounds the shape only; writers also check the name against the runtime's Intl zone list.","pattern":"^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$","maxLength":64}}}; +const schema475 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; +const schema476 = {"type":"string","description":"An automation id: `aut_` and 6 to 32 letters or digits.","pattern":"^aut_[A-Za-z0-9]{6,32}$"}; +const schema477 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; +const schema484 = {"type":"string","description":"`active` runs on its recurrence; `paused` and `auto-paused` (by failures or a budget) wait for a person to resume; `broken` needs a person to repair it (key revoked, owner gone); `ended` reached its COUNT or UNTIL.","enum":["active","paused","auto-paused","broken","ended"]}; +const schema485 = {"type":"object","description":"The person who created the automation and owns its key (`user_…`). Agents never own automations.","additionalProperties":false,"required":["id"],"properties":{"id":{"type":"string","pattern":"^user_[A-Za-z0-9]{6,32}$"},"name":{"type":"string","maxLength":200}}}; +const schema486 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; +const schema489 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; +const schema490 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}}; +const pattern239 = new RegExp("^aut_[A-Za-z0-9]{6,32}$", "u"); +const schema478 = {"description":"What runs, discriminated by `kind`.","oneOf":[{"type":"object","title":"BuiltinAutomationJob","description":"A deterministic job Cloudhal runs itself, in bounded steps. Its outputs are findings, reports and Studio notifications; it never changes or publishes content.","additionalProperties":false,"required":["kind","builtin"],"properties":{"kind":{"type":"string","enum":["builtin"]},"builtin":{"type":"string","enum":["broken-links","stale-content","feed-check","weekly-digest","cleanup"]},"staleAfterDays":{"type":"integer","description":"`stale-content` only: entries not updated for this many days are listed for review.","minimum":30,"maximum":1095}}},{"type":"object","title":"AgentTaskAutomationJob","description":"A task an external agent performs over MCP. At each occurrence a run window opens for `windowMinutes`; the automation's key (`keyId`, owned by the automation's owner) is valid only inside an open run, which the agent starts with `begin_run` and ends with `end_run`. The run's mode is the key's effective mode capped by `modeCap`; it never waits for a person: calls that need one leave an approval request and the run ends. `readsUntrustedText` declares that the task reads visitor-supplied text (comments, 404 paths, fetched pages); such an automation's key cannot hold write scopes, and any run that reads such text is limited to reading, labelling and hiding from then on.","additionalProperties":false,"required":["kind","keyId","instructions","modeCap","readsUntrustedText","windowMinutes","budget"],"properties":{"kind":{"type":"string","enum":["agent-task"]},"keyId":{"type":"string","pattern":"^[A-Za-z0-9_-]{6,64}$"},"instructions":{"type":"string","description":"What the owner asks the agent to do, shown to the agent by `begin_run`. Plain text.","minLength":1,"maxLength":2000,"pattern":"^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$"},"skill":{"type":"string","description":"A skill the run declares (its scopes and approval list narrow the run further).","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","maxLength":64},"modeCap":{"type":"string","description":"The most autonomous mode a run may use; never `bypass`.","enum":["plan","manual","edits","auto"]},"readsUntrustedText":{"type":"boolean"},"windowMinutes":{"type":"integer","minimum":5,"maximum":240},"budget":{"$ref":"#/$defs/AutomationBudget"}}}]}; +const schema479 = {"type":"object","description":"Per-run limits Cloudhal counts and enforces for an agent task: tool calls, mutations, approval requests filed and minutes since the run began. Exceeding one ends the run (`budget-exceeded`) and pauses the automation.","additionalProperties":false,"required":["maxToolCalls","maxWrites","maxApprovalRequests","maxMinutes"],"properties":{"maxToolCalls":{"type":"integer","minimum":1,"maximum":500},"maxWrites":{"type":"integer","minimum":0,"maximum":200},"maxApprovalRequests":{"type":"integer","minimum":0,"maximum":20},"maxMinutes":{"type":"integer","minimum":1,"maximum":240}}}; function validate185(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -33068,7 +33193,7 @@ vErrors.push(err3); errors++; } if(!(data0 === "builtin")){ -const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema476.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema478.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err4]; } @@ -33091,7 +33216,7 @@ vErrors.push(err5); errors++; } if(!(((((data1 === "broken-links") || (data1 === "stale-content")) || (data1 === "feed-check")) || (data1 === "weekly-digest")) || (data1 === "cleanup"))){ -const err6 = {instancePath:instancePath+"/builtin",schemaPath:"#/oneOf/0/properties/builtin/enum",keyword:"enum",params:{allowedValues: schema476.oneOf[0].properties.builtin.enum},message:"must be equal to one of the allowed values"}; +const err6 = {instancePath:instancePath+"/builtin",schemaPath:"#/oneOf/0/properties/builtin/enum",keyword:"enum",params:{allowedValues: schema478.oneOf[0].properties.builtin.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err6]; } @@ -33250,7 +33375,7 @@ vErrors.push(err19); errors++; } if(!(data3 === "agent-task")){ -const err20 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema476.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err20 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema478.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err20]; } @@ -33263,7 +33388,7 @@ errors++; if(data.keyId !== undefined){ let data4 = data.keyId; if(typeof data4 === "string"){ -if(!pattern229.test(data4)){ +if(!pattern231.test(data4)){ const err21 = {instancePath:instancePath+"/keyId",schemaPath:"#/oneOf/1/properties/keyId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9_-]{6,64}$"},message:"must match pattern \""+"^[A-Za-z0-9_-]{6,64}$"+"\""}; if(vErrors === null){ vErrors = [err21]; @@ -33308,7 +33433,7 @@ vErrors.push(err24); } errors++; } -if(!pattern206.test(data5)){ +if(!pattern208.test(data5)){ const err25 = {instancePath:instancePath+"/instructions",schemaPath:"#/oneOf/1/properties/instructions/pattern",keyword:"pattern",params:{pattern: "^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$"},message:"must match pattern \""+"^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$"+"\""}; if(vErrors === null){ vErrors = [err25]; @@ -33378,7 +33503,7 @@ vErrors.push(err30); errors++; } if(!((((data7 === "plan") || (data7 === "manual")) || (data7 === "edits")) || (data7 === "auto"))){ -const err31 = {instancePath:instancePath+"/modeCap",schemaPath:"#/oneOf/1/properties/modeCap/enum",keyword:"enum",params:{allowedValues: schema476.oneOf[1].properties.modeCap.enum},message:"must be equal to one of the allowed values"}; +const err31 = {instancePath:instancePath+"/modeCap",schemaPath:"#/oneOf/1/properties/modeCap/enum",keyword:"enum",params:{allowedValues: schema478.oneOf[1].properties.modeCap.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err31]; } @@ -33694,11 +33819,11 @@ return errors === 0; } validate185.evaluated = {"dynamicProps":true,"dynamicItems":false}; -const schema478 = {"type":"object","description":"When the automation runs: the first occurrence as a wall-clock time in `timeZone`, repeated by `rule` (absent: once). Occurrences are computed in the zone, so \"Mondays 09:07\" stays 09:07 across daylight-saving changes; a time that does not exist on a given day (a spring-forward gap) runs at the next valid minute, and a time that occurs twice runs once, at the first (a documented deviation from RFC 5545 §3.3.10, which drops such occurrences). `preset` records which Studio choice produced the rule.","additionalProperties":false,"required":["start","timeZone","preset"],"properties":{"start":{"$ref":"#/$defs/LocalDateTime"},"timeZone":{"$ref":"#/$defs/TimeZone"},"rule":{"$ref":"#/$defs/AutomationRule"},"preset":{"type":"string","enum":["once","hourly","daily","weekdays","weekly","monthly","custom"]}}}; -const schema479 = {"type":"string","description":"A wall-clock date and time without an offset, to the minute (YYYY-MM-DDTHH:mm), as a person entered it; always paired with a TimeZone (RFC-0008).","pattern":"^[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])T(?:[01][0-9]|2[0-3]):[0-5][0-9]$"}; -const schema480 = {"type":"string","description":"IANA time zone name from the tz database (RFC-0008), e.g. Europe/Istanbul, America/Argentina/Buenos_Aires, Etc/GMT+3 or UTC. Wall-clock times a person enters are kept with their zone, so the instant is recomputed when governments change the rules. The pattern bounds the shape only; writers also check the name against the runtime's Intl zone list.","pattern":"^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$","maxLength":64}; -const schema481 = {"type":"string","description":"The recurrence as an RFC 5545 RRULE value in Cloudhal's subset, without the `RRULE:` prefix: FREQ DAILY, WEEKLY or MONTHLY; INTERVAL; BYDAY (ordinals such as 2TU or -1FR only with MONTHLY); BYMONTHDAY (1–31 or -1); BYHOUR; BYMINUTE; COUNT or UNTIL. The pattern bounds the vocabulary; the parser refuses repeated parts, COUNT with UNTIL, ordinals outside MONTHLY and runs closer than the job's minimum interval.","maxLength":300,"pattern":"^FREQ=(?:DAILY|WEEKLY|MONTHLY)(?:;(?:INTERVAL=[1-9][0-9]{0,2}|BYDAY=(?:[+-]?[1-5])?(?:MO|TU|WE|TH|FR|SA|SU)(?:,(?:[+-]?[1-5])?(?:MO|TU|WE|TH|FR|SA|SU)){0,6}|BYMONTHDAY=(?:-1|[1-9]|[12][0-9]|3[01])(?:,(?:-1|[1-9]|[12][0-9]|3[01])){0,30}|BYHOUR=(?:[01]?[0-9]|2[0-3])(?:,(?:[01]?[0-9]|2[0-3])){0,23}|BYMINUTE=[0-5]?[0-9](?:,[0-5]?[0-9]){0,3}|COUNT=[1-9][0-9]{0,3}|UNTIL=[0-9]{8}T[0-9]{6}Z))*$"}; -const pattern245 = new RegExp("^FREQ=(?:DAILY|WEEKLY|MONTHLY)(?:;(?:INTERVAL=[1-9][0-9]{0,2}|BYDAY=(?:[+-]?[1-5])?(?:MO|TU|WE|TH|FR|SA|SU)(?:,(?:[+-]?[1-5])?(?:MO|TU|WE|TH|FR|SA|SU)){0,6}|BYMONTHDAY=(?:-1|[1-9]|[12][0-9]|3[01])(?:,(?:-1|[1-9]|[12][0-9]|3[01])){0,30}|BYHOUR=(?:[01]?[0-9]|2[0-3])(?:,(?:[01]?[0-9]|2[0-3])){0,23}|BYMINUTE=[0-5]?[0-9](?:,[0-5]?[0-9]){0,3}|COUNT=[1-9][0-9]{0,3}|UNTIL=[0-9]{8}T[0-9]{6}Z))*$", "u"); +const schema480 = {"type":"object","description":"When the automation runs: the first occurrence as a wall-clock time in `timeZone`, repeated by `rule` (absent: once). Occurrences are computed in the zone, so \"Mondays 09:07\" stays 09:07 across daylight-saving changes; a time that does not exist on a given day (a spring-forward gap) runs at the next valid minute, and a time that occurs twice runs once, at the first (a documented deviation from RFC 5545 §3.3.10, which drops such occurrences). `preset` records which Studio choice produced the rule.","additionalProperties":false,"required":["start","timeZone","preset"],"properties":{"start":{"$ref":"#/$defs/LocalDateTime"},"timeZone":{"$ref":"#/$defs/TimeZone"},"rule":{"$ref":"#/$defs/AutomationRule"},"preset":{"type":"string","enum":["once","hourly","daily","weekdays","weekly","monthly","custom"]}}}; +const schema481 = {"type":"string","description":"A wall-clock date and time without an offset, to the minute (YYYY-MM-DDTHH:mm), as a person entered it; always paired with a TimeZone (RFC-0008).","pattern":"^[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])T(?:[01][0-9]|2[0-3]):[0-5][0-9]$"}; +const schema482 = {"type":"string","description":"IANA time zone name from the tz database (RFC-0008), e.g. Europe/Istanbul, America/Argentina/Buenos_Aires, Etc/GMT+3 or UTC. Wall-clock times a person enters are kept with their zone, so the instant is recomputed when governments change the rules. The pattern bounds the shape only; writers also check the name against the runtime's Intl zone list.","pattern":"^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$","maxLength":64}; +const schema483 = {"type":"string","description":"The recurrence as an RFC 5545 RRULE value in Cloudhal's subset, without the `RRULE:` prefix: FREQ DAILY, WEEKLY or MONTHLY; INTERVAL; BYDAY (ordinals such as 2TU or -1FR only with MONTHLY); BYMONTHDAY (1–31 or -1); BYHOUR; BYMINUTE; COUNT or UNTIL. The pattern bounds the vocabulary; the parser refuses repeated parts, COUNT with UNTIL, ordinals outside MONTHLY and runs closer than the job's minimum interval.","maxLength":300,"pattern":"^FREQ=(?:DAILY|WEEKLY|MONTHLY)(?:;(?:INTERVAL=[1-9][0-9]{0,2}|BYDAY=(?:[+-]?[1-5])?(?:MO|TU|WE|TH|FR|SA|SU)(?:,(?:[+-]?[1-5])?(?:MO|TU|WE|TH|FR|SA|SU)){0,6}|BYMONTHDAY=(?:-1|[1-9]|[12][0-9]|3[01])(?:,(?:-1|[1-9]|[12][0-9]|3[01])){0,30}|BYHOUR=(?:[01]?[0-9]|2[0-3])(?:,(?:[01]?[0-9]|2[0-3])){0,23}|BYMINUTE=[0-5]?[0-9](?:,[0-5]?[0-9]){0,3}|COUNT=[1-9][0-9]{0,3}|UNTIL=[0-9]{8}T[0-9]{6}Z))*$"}; +const pattern247 = new RegExp("^FREQ=(?:DAILY|WEEKLY|MONTHLY)(?:;(?:INTERVAL=[1-9][0-9]{0,2}|BYDAY=(?:[+-]?[1-5])?(?:MO|TU|WE|TH|FR|SA|SU)(?:,(?:[+-]?[1-5])?(?:MO|TU|WE|TH|FR|SA|SU)){0,6}|BYMONTHDAY=(?:-1|[1-9]|[12][0-9]|3[01])(?:,(?:-1|[1-9]|[12][0-9]|3[01])){0,30}|BYHOUR=(?:[01]?[0-9]|2[0-3])(?:,(?:[01]?[0-9]|2[0-3])){0,23}|BYMINUTE=[0-5]?[0-9](?:,[0-5]?[0-9]){0,3}|COUNT=[1-9][0-9]{0,3}|UNTIL=[0-9]{8}T[0-9]{6}Z))*$", "u"); function validate187(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -33756,7 +33881,7 @@ errors++; if(data.start !== undefined){ let data0 = data.start; if(typeof data0 === "string"){ -if(!pattern224.test(data0)){ +if(!pattern226.test(data0)){ const err4 = {instancePath:instancePath+"/start",schemaPath:"#/$defs/LocalDateTime/pattern",keyword:"pattern",params:{pattern: "^[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])T(?:[01][0-9]|2[0-3]):[0-5][0-9]$"},message:"must match pattern \""+"^[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])T(?:[01][0-9]|2[0-3]):[0-5][0-9]$"+"\""}; if(vErrors === null){ vErrors = [err4]; @@ -33791,7 +33916,7 @@ vErrors.push(err6); } errors++; } -if(!pattern41.test(data1)){ +if(!pattern43.test(data1)){ const err7 = {instancePath:instancePath+"/timeZone",schemaPath:"#/$defs/TimeZone/pattern",keyword:"pattern",params:{pattern: "^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$"},message:"must match pattern \""+"^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$"+"\""}; if(vErrors === null){ vErrors = [err7]; @@ -33826,7 +33951,7 @@ vErrors.push(err9); } errors++; } -if(!pattern245.test(data2)){ +if(!pattern247.test(data2)){ const err10 = {instancePath:instancePath+"/rule",schemaPath:"#/$defs/AutomationRule/pattern",keyword:"pattern",params:{pattern: "^FREQ=(?:DAILY|WEEKLY|MONTHLY)(?:;(?:INTERVAL=[1-9][0-9]{0,2}|BYDAY=(?:[+-]?[1-5])?(?:MO|TU|WE|TH|FR|SA|SU)(?:,(?:[+-]?[1-5])?(?:MO|TU|WE|TH|FR|SA|SU)){0,6}|BYMONTHDAY=(?:-1|[1-9]|[12][0-9]|3[01])(?:,(?:-1|[1-9]|[12][0-9]|3[01])){0,30}|BYHOUR=(?:[01]?[0-9]|2[0-3])(?:,(?:[01]?[0-9]|2[0-3])){0,23}|BYMINUTE=[0-5]?[0-9](?:,[0-5]?[0-9]){0,3}|COUNT=[1-9][0-9]{0,3}|UNTIL=[0-9]{8}T[0-9]{6}Z))*$"},message:"must match pattern \""+"^FREQ=(?:DAILY|WEEKLY|MONTHLY)(?:;(?:INTERVAL=[1-9][0-9]{0,2}|BYDAY=(?:[+-]?[1-5])?(?:MO|TU|WE|TH|FR|SA|SU)(?:,(?:[+-]?[1-5])?(?:MO|TU|WE|TH|FR|SA|SU)){0,6}|BYMONTHDAY=(?:-1|[1-9]|[12][0-9]|3[01])(?:,(?:-1|[1-9]|[12][0-9]|3[01])){0,30}|BYHOUR=(?:[01]?[0-9]|2[0-3])(?:,(?:[01]?[0-9]|2[0-3])){0,23}|BYMINUTE=[0-5]?[0-9](?:,[0-5]?[0-9]){0,3}|COUNT=[1-9][0-9]{0,3}|UNTIL=[0-9]{8}T[0-9]{6}Z))*$"+"\""}; if(vErrors === null){ vErrors = [err10]; @@ -33861,7 +33986,7 @@ vErrors.push(err12); errors++; } if(!(((((((data3 === "once") || (data3 === "hourly")) || (data3 === "daily")) || (data3 === "weekdays")) || (data3 === "weekly")) || (data3 === "monthly")) || (data3 === "custom"))){ -const err13 = {instancePath:instancePath+"/preset",schemaPath:"#/properties/preset/enum",keyword:"enum",params:{allowedValues: schema478.properties.preset.enum},message:"must be equal to one of the allowed values"}; +const err13 = {instancePath:instancePath+"/preset",schemaPath:"#/properties/preset/enum",keyword:"enum",params:{allowedValues: schema480.properties.preset.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err13]; } @@ -34021,7 +34146,7 @@ vErrors.push(err11); errors++; } for(const key0 in data){ -if(!(func1.call(schema472.properties, key0))){ +if(!(func1.call(schema474.properties, key0))){ const err12 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}; if(vErrors === null){ vErrors = [err12]; @@ -34045,7 +34170,7 @@ vErrors.push(err13); errors++; } if(!(data0 === 1)){ -const err14 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema473.enum},message:"must be equal to one of the allowed values"}; +const err14 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema475.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err14]; } @@ -34058,7 +34183,7 @@ errors++; if(data.id !== undefined){ let data1 = data.id; if(typeof data1 === "string"){ -if(!pattern237.test(data1)){ +if(!pattern239.test(data1)){ const err15 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/AutomationId/pattern",keyword:"pattern",params:{pattern: "^aut_[A-Za-z0-9]{6,32}$"},message:"must match pattern \""+"^aut_[A-Za-z0-9]{6,32}$"+"\""}; if(vErrors === null){ vErrors = [err15]; @@ -34148,7 +34273,7 @@ vErrors.push(err22); } errors++; } -if(!pattern219.test(data3)){ +if(!pattern221.test(data3)){ const err23 = {instancePath:instancePath+"/name",schemaPath:"#/properties/name/pattern",keyword:"pattern",params:{pattern: "^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$"},message:"must match pattern \""+"^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$"+"\""}; if(vErrors === null){ vErrors = [err23]; @@ -34195,7 +34320,7 @@ vErrors.push(err25); errors++; } if(!(((((data6 === "active") || (data6 === "paused")) || (data6 === "auto-paused")) || (data6 === "broken")) || (data6 === "ended"))){ -const err26 = {instancePath:instancePath+"/state",schemaPath:"#/$defs/AutomationState/enum",keyword:"enum",params:{allowedValues: schema482.enum},message:"must be equal to one of the allowed values"}; +const err26 = {instancePath:instancePath+"/state",schemaPath:"#/$defs/AutomationState/enum",keyword:"enum",params:{allowedValues: schema484.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err26]; } @@ -34218,7 +34343,7 @@ vErrors.push(err27); errors++; } if(!((((((((data7 === "person") || (data7 === "agent")) || (data7 === "failures")) || (data7 === "budget")) || (data7 === "kill-switch")) || (data7 === "key-revoked")) || (data7 === "owner-removed")) || (data7 === "role-changed"))){ -const err28 = {instancePath:instancePath+"/pausedReason",schemaPath:"#/properties/pausedReason/enum",keyword:"enum",params:{allowedValues: schema472.properties.pausedReason.enum},message:"must be equal to one of the allowed values"}; +const err28 = {instancePath:instancePath+"/pausedReason",schemaPath:"#/properties/pausedReason/enum",keyword:"enum",params:{allowedValues: schema474.properties.pausedReason.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err28]; } @@ -34281,7 +34406,7 @@ errors++; if(data9.id !== undefined){ let data10 = data9.id; if(typeof data10 === "string"){ -if(!pattern203.test(data10)){ +if(!pattern205.test(data10)){ const err33 = {instancePath:instancePath+"/owner/id",schemaPath:"#/$defs/AutomationOwner/properties/id/pattern",keyword:"pattern",params:{pattern: "^user_[A-Za-z0-9]{6,32}$"},message:"must match pattern \""+"^user_[A-Za-z0-9]{6,32}$"+"\""}; if(vErrors === null){ vErrors = [err33]; @@ -34484,19 +34609,19 @@ return errors === 0; validate184.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; export const validateScheduleRun = validate189; -const schema489 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/schedule-run.schema.json","title":"ScheduleRun","description":"One occurrence of something scheduled (RFC-0008): a release's execution or an automation's run. Its id is derived from the subject and `occurrenceAt`, so a second scheduler tick that reaches the same occurrence finds the same row and the effect happens at most once. `state` says whether the run itself completed; `outcome` says whether it did any work, because a run that ended cleanly may still have done nothing. Runs live in D1 and are kept 90 days; the audit log keeps what each call did.","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","subject","occurrenceAt","state","attempt","createdAt","updatedAt","rev"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/ScheduleRunId"},"siteId":{"$ref":"#/$defs/Id"},"subject":{"$ref":"#/$defs/ScheduleRunSubject"},"occurrenceAt":{"$ref":"#/$defs/Timestamp"},"state":{"$ref":"#/$defs/ScheduleRunState"},"outcome":{"type":"string","description":"What the run achieved, separately from whether it completed: `did-work` changed something or produced findings; `nothing-to-do`; `proposed` left drafts or proposals for people; `waiting-approval` filed requests a person must decide.","enum":["did-work","nothing-to-do","proposed","waiting-approval"]},"trigger":{"type":"string","description":"What woke the scheduler: the every-minute cron trigger, the Durable Object clock's alarm, an admin request (backstop), the Node timer, or a person (\"Run now\", \"Publish now\").","enum":["cron","alarm","request","node","manual"]},"attempt":{"type":"integer","minimum":1},"startedAt":{"$ref":"#/$defs/Timestamp"},"endedAt":{"$ref":"#/$defs/Timestamp"},"latenessSeconds":{"type":"integer","minimum":0},"adjusted":{"type":"string","description":"The occurrence was moved: `dst-gap` (the wall-clock time did not exist that day, so it ran at the next valid minute) or `tzdata` (the zone's rules changed after it was planned).","enum":["dst-gap","tzdata"]},"window":{"$ref":"#/$defs/ScheduleRunWindow"},"usage":{"$ref":"#/$defs/ScheduleRunUsage"},"taint":{"$ref":"#/$defs/ScheduleRunTaint"},"summary":{"type":"string","description":"The agent's own account of the run (`end_run`) or the built-in job's report line. Plain text written by an agent: Studio renders it as text and never as an instruction.","maxLength":2000,"pattern":"^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"},"counts":{"type":"object","description":"Findings by name (e.g. `broken-links`, `stale-entries`, `proposals`).","propertyNames":{"type":"string","pattern":"^[a-z][a-z0-9-]{0,40}$"},"maxProperties":20,"additionalProperties":{"type":"integer","minimum":0}},"approvalIds":{"type":"array","description":"Approval requests the run filed.","items":{"$ref":"#/$defs/Id"},"uniqueItems":true,"maxItems":50},"error":{"$ref":"#/$defs/ScheduleRunError"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"rev":{"$ref":"#/$defs/Revision"}},"$defs":{"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"ScheduleRunError":{"type":"object","additionalProperties":false,"required":["code"],"properties":{"code":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}$"},"message":{"type":"string","maxLength":500,"pattern":"^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"}}},"ScheduleRunId":{"type":"string","description":"A run id: `run_` and 6 to 32 letters or digits, derived from the subject id and `occurrenceAt` (the occurrence's idempotency key).","pattern":"^run_[A-Za-z0-9]{6,32}$"},"ScheduleRunState":{"type":"string","description":"`due`: claimed occurrence not started yet; `ready`: an agent task's window is open and waits for `begin_run`; `running`; `succeeded`; `failed`; `missed`: the window closed or the scheduler came too late; `skipped`: a missed occurrence coalesced into a later one; `budget-exceeded`; `killed`: stopped by a pause or a kill switch.","enum":["due","ready","running","succeeded","failed","missed","skipped","budget-exceeded","killed"]},"ScheduleRunSubject":{"description":"What ran, discriminated by `kind`.","oneOf":[{"type":"object","title":"ReleaseRunSubject","additionalProperties":false,"required":["kind","releaseId"],"properties":{"kind":{"type":"string","enum":["release"]},"releaseId":{"type":"string","pattern":"^rel_[A-Za-z0-9]{6,32}$"}}},{"type":"object","title":"AutomationRunSubject","additionalProperties":false,"required":["kind","automationId","job"],"properties":{"kind":{"type":"string","enum":["automation"]},"automationId":{"type":"string","pattern":"^aut_[A-Za-z0-9]{6,32}$"},"job":{"type":"string","enum":["builtin","agent-task"]}}}]},"ScheduleRunTaint":{"type":"object","description":"The run read visitor-supplied text at `at` through `tool`; from then on it may only read, label, hide and pause.","additionalProperties":false,"required":["at","tool"],"properties":{"at":{"$ref":"#/$defs/Timestamp"},"tool":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}$"}}},"ScheduleRunUsage":{"type":"object","description":"What the run used against its budget, counted by the server.","additionalProperties":false,"required":["toolCalls","writes","approvalRequests"],"properties":{"toolCalls":{"type":"integer","minimum":0},"writes":{"type":"integer","minimum":0},"approvalRequests":{"type":"integer","minimum":0}}},"ScheduleRunWindow":{"type":"object","description":"An agent task's run window: the automation's key is valid only between `opensAt` and `closesAt`, and only inside a run it began.","additionalProperties":false,"required":["opensAt","closesAt"],"properties":{"opensAt":{"$ref":"#/$defs/Timestamp"},"closesAt":{"$ref":"#/$defs/Timestamp"}}},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}}}; -const schema490 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; -const schema491 = {"type":"string","description":"A run id: `run_` and 6 to 32 letters or digits, derived from the subject id and `occurrenceAt` (the occurrence's idempotency key).","pattern":"^run_[A-Za-z0-9]{6,32}$"}; -const schema492 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; -const schema493 = {"description":"What ran, discriminated by `kind`.","oneOf":[{"type":"object","title":"ReleaseRunSubject","additionalProperties":false,"required":["kind","releaseId"],"properties":{"kind":{"type":"string","enum":["release"]},"releaseId":{"type":"string","pattern":"^rel_[A-Za-z0-9]{6,32}$"}}},{"type":"object","title":"AutomationRunSubject","additionalProperties":false,"required":["kind","automationId","job"],"properties":{"kind":{"type":"string","enum":["automation"]},"automationId":{"type":"string","pattern":"^aut_[A-Za-z0-9]{6,32}$"},"job":{"type":"string","enum":["builtin","agent-task"]}}}]}; -const schema494 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; -const schema495 = {"type":"string","description":"`due`: claimed occurrence not started yet; `ready`: an agent task's window is open and waits for `begin_run`; `running`; `succeeded`; `failed`; `missed`: the window closed or the scheduler came too late; `skipped`: a missed occurrence coalesced into a later one; `budget-exceeded`; `killed`: stopped by a pause or a kill switch.","enum":["due","ready","running","succeeded","failed","missed","skipped","budget-exceeded","killed"]}; -const schema501 = {"type":"object","description":"What the run used against its budget, counted by the server.","additionalProperties":false,"required":["toolCalls","writes","approvalRequests"],"properties":{"toolCalls":{"type":"integer","minimum":0},"writes":{"type":"integer","minimum":0},"approvalRequests":{"type":"integer","minimum":0}}}; -const schema505 = {"type":"object","additionalProperties":false,"required":["code"],"properties":{"code":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}$"},"message":{"type":"string","maxLength":500,"pattern":"^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"}}}; -const schema508 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; -const pattern247 = new RegExp("^run_[A-Za-z0-9]{6,32}$", "u"); -const pattern251 = new RegExp("^[a-z][a-z0-9_]{0,63}$", "u"); -const schema498 = {"type":"object","description":"An agent task's run window: the automation's key is valid only between `opensAt` and `closesAt`, and only inside a run it began.","additionalProperties":false,"required":["opensAt","closesAt"],"properties":{"opensAt":{"$ref":"#/$defs/Timestamp"},"closesAt":{"$ref":"#/$defs/Timestamp"}}}; +const schema491 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/schedule-run.schema.json","title":"ScheduleRun","description":"One occurrence of something scheduled (RFC-0008): a release's execution or an automation's run. Its id is derived from the subject and `occurrenceAt`, so a second scheduler tick that reaches the same occurrence finds the same row and the effect happens at most once. `state` says whether the run itself completed; `outcome` says whether it did any work, because a run that ended cleanly may still have done nothing. Runs live in D1 and are kept 90 days; the audit log keeps what each call did.","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","subject","occurrenceAt","state","attempt","createdAt","updatedAt","rev"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/ScheduleRunId"},"siteId":{"$ref":"#/$defs/Id"},"subject":{"$ref":"#/$defs/ScheduleRunSubject"},"occurrenceAt":{"$ref":"#/$defs/Timestamp"},"state":{"$ref":"#/$defs/ScheduleRunState"},"outcome":{"type":"string","description":"What the run achieved, separately from whether it completed: `did-work` changed something or produced findings; `nothing-to-do`; `proposed` left drafts or proposals for people; `waiting-approval` filed requests a person must decide.","enum":["did-work","nothing-to-do","proposed","waiting-approval"]},"trigger":{"type":"string","description":"What woke the scheduler: the every-minute cron trigger, the Durable Object clock's alarm, an admin request (backstop), the Node timer, or a person (\"Run now\", \"Publish now\").","enum":["cron","alarm","request","node","manual"]},"attempt":{"type":"integer","minimum":1},"startedAt":{"$ref":"#/$defs/Timestamp"},"endedAt":{"$ref":"#/$defs/Timestamp"},"latenessSeconds":{"type":"integer","minimum":0},"adjusted":{"type":"string","description":"The occurrence was moved: `dst-gap` (the wall-clock time did not exist that day, so it ran at the next valid minute) or `tzdata` (the zone's rules changed after it was planned).","enum":["dst-gap","tzdata"]},"window":{"$ref":"#/$defs/ScheduleRunWindow"},"usage":{"$ref":"#/$defs/ScheduleRunUsage"},"taint":{"$ref":"#/$defs/ScheduleRunTaint"},"summary":{"type":"string","description":"The agent's own account of the run (`end_run`) or the built-in job's report line. Plain text written by an agent: Studio renders it as text and never as an instruction.","maxLength":2000,"pattern":"^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"},"counts":{"type":"object","description":"Findings by name (e.g. `broken-links`, `stale-entries`, `proposals`).","propertyNames":{"type":"string","pattern":"^[a-z][a-z0-9-]{0,40}$"},"maxProperties":20,"additionalProperties":{"type":"integer","minimum":0}},"approvalIds":{"type":"array","description":"Approval requests the run filed.","items":{"$ref":"#/$defs/Id"},"uniqueItems":true,"maxItems":50},"error":{"$ref":"#/$defs/ScheduleRunError"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"rev":{"$ref":"#/$defs/Revision"}},"$defs":{"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"ScheduleRunError":{"type":"object","additionalProperties":false,"required":["code"],"properties":{"code":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}$"},"message":{"type":"string","maxLength":500,"pattern":"^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"}}},"ScheduleRunId":{"type":"string","description":"A run id: `run_` and 6 to 32 letters or digits, derived from the subject id and `occurrenceAt` (the occurrence's idempotency key).","pattern":"^run_[A-Za-z0-9]{6,32}$"},"ScheduleRunState":{"type":"string","description":"`due`: claimed occurrence not started yet; `ready`: an agent task's window is open and waits for `begin_run`; `running`; `succeeded`; `failed`; `missed`: the window closed or the scheduler came too late; `skipped`: a missed occurrence coalesced into a later one; `budget-exceeded`; `killed`: stopped by a pause or a kill switch.","enum":["due","ready","running","succeeded","failed","missed","skipped","budget-exceeded","killed"]},"ScheduleRunSubject":{"description":"What ran, discriminated by `kind`.","oneOf":[{"type":"object","title":"ReleaseRunSubject","additionalProperties":false,"required":["kind","releaseId"],"properties":{"kind":{"type":"string","enum":["release"]},"releaseId":{"type":"string","pattern":"^rel_[A-Za-z0-9]{6,32}$"}}},{"type":"object","title":"AutomationRunSubject","additionalProperties":false,"required":["kind","automationId","job"],"properties":{"kind":{"type":"string","enum":["automation"]},"automationId":{"type":"string","pattern":"^aut_[A-Za-z0-9]{6,32}$"},"job":{"type":"string","enum":["builtin","agent-task"]}}}]},"ScheduleRunTaint":{"type":"object","description":"The run read visitor-supplied text at `at` through `tool`; from then on it may only read, label, hide and pause.","additionalProperties":false,"required":["at","tool"],"properties":{"at":{"$ref":"#/$defs/Timestamp"},"tool":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}$"}}},"ScheduleRunUsage":{"type":"object","description":"What the run used against its budget, counted by the server.","additionalProperties":false,"required":["toolCalls","writes","approvalRequests"],"properties":{"toolCalls":{"type":"integer","minimum":0},"writes":{"type":"integer","minimum":0},"approvalRequests":{"type":"integer","minimum":0}}},"ScheduleRunWindow":{"type":"object","description":"An agent task's run window: the automation's key is valid only between `opensAt` and `closesAt`, and only inside a run it began.","additionalProperties":false,"required":["opensAt","closesAt"],"properties":{"opensAt":{"$ref":"#/$defs/Timestamp"},"closesAt":{"$ref":"#/$defs/Timestamp"}}},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}}}; +const schema492 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; +const schema493 = {"type":"string","description":"A run id: `run_` and 6 to 32 letters or digits, derived from the subject id and `occurrenceAt` (the occurrence's idempotency key).","pattern":"^run_[A-Za-z0-9]{6,32}$"}; +const schema494 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; +const schema495 = {"description":"What ran, discriminated by `kind`.","oneOf":[{"type":"object","title":"ReleaseRunSubject","additionalProperties":false,"required":["kind","releaseId"],"properties":{"kind":{"type":"string","enum":["release"]},"releaseId":{"type":"string","pattern":"^rel_[A-Za-z0-9]{6,32}$"}}},{"type":"object","title":"AutomationRunSubject","additionalProperties":false,"required":["kind","automationId","job"],"properties":{"kind":{"type":"string","enum":["automation"]},"automationId":{"type":"string","pattern":"^aut_[A-Za-z0-9]{6,32}$"},"job":{"type":"string","enum":["builtin","agent-task"]}}}]}; +const schema496 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; +const schema497 = {"type":"string","description":"`due`: claimed occurrence not started yet; `ready`: an agent task's window is open and waits for `begin_run`; `running`; `succeeded`; `failed`; `missed`: the window closed or the scheduler came too late; `skipped`: a missed occurrence coalesced into a later one; `budget-exceeded`; `killed`: stopped by a pause or a kill switch.","enum":["due","ready","running","succeeded","failed","missed","skipped","budget-exceeded","killed"]}; +const schema503 = {"type":"object","description":"What the run used against its budget, counted by the server.","additionalProperties":false,"required":["toolCalls","writes","approvalRequests"],"properties":{"toolCalls":{"type":"integer","minimum":0},"writes":{"type":"integer","minimum":0},"approvalRequests":{"type":"integer","minimum":0}}}; +const schema507 = {"type":"object","additionalProperties":false,"required":["code"],"properties":{"code":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}$"},"message":{"type":"string","maxLength":500,"pattern":"^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"}}}; +const schema510 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; +const pattern249 = new RegExp("^run_[A-Za-z0-9]{6,32}$", "u"); +const pattern253 = new RegExp("^[a-z][a-z0-9_]{0,63}$", "u"); +const schema500 = {"type":"object","description":"An agent task's run window: the automation's key is valid only between `opensAt` and `closesAt`, and only inside a run it began.","additionalProperties":false,"required":["opensAt","closesAt"],"properties":{"opensAt":{"$ref":"#/$defs/Timestamp"},"closesAt":{"$ref":"#/$defs/Timestamp"}}}; function validate190(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -34607,7 +34732,7 @@ return errors === 0; } validate190.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; -const schema502 = {"type":"object","description":"The run read visitor-supplied text at `at` through `tool`; from then on it may only read, label, hide and pause.","additionalProperties":false,"required":["at","tool"],"properties":{"at":{"$ref":"#/$defs/Timestamp"},"tool":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}$"}}}; +const schema504 = {"type":"object","description":"The run read visitor-supplied text at `at` through `tool`; from then on it may only read, label, hide and pause.","additionalProperties":false,"required":["at","tool"],"properties":{"at":{"$ref":"#/$defs/Timestamp"},"tool":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}$"}}}; function validate192(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -34680,7 +34805,7 @@ errors++; if(data.tool !== undefined){ let data1 = data.tool; if(typeof data1 === "string"){ -if(!pattern251.test(data1)){ +if(!pattern253.test(data1)){ const err5 = {instancePath:instancePath+"/tool",schemaPath:"#/properties/tool/pattern",keyword:"pattern",params:{pattern: "^[a-z][a-z0-9_]{0,63}$"},message:"must match pattern \""+"^[a-z][a-z0-9_]{0,63}$"+"\""}; if(vErrors === null){ vErrors = [err5]; @@ -34832,7 +34957,7 @@ vErrors.push(err9); errors++; } for(const key0 in data){ -if(!(func1.call(schema489.properties, key0))){ +if(!(func1.call(schema491.properties, key0))){ const err10 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}; if(vErrors === null){ vErrors = [err10]; @@ -34856,7 +34981,7 @@ vErrors.push(err11); errors++; } if(!(data0 === 1)){ -const err12 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema490.enum},message:"must be equal to one of the allowed values"}; +const err12 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema492.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err12]; } @@ -34869,7 +34994,7 @@ errors++; if(data.id !== undefined){ let data1 = data.id; if(typeof data1 === "string"){ -if(!pattern247.test(data1)){ +if(!pattern249.test(data1)){ const err13 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/ScheduleRunId/pattern",keyword:"pattern",params:{pattern: "^run_[A-Za-z0-9]{6,32}$"},message:"must match pattern \""+"^run_[A-Za-z0-9]{6,32}$"+"\""}; if(vErrors === null){ vErrors = [err13]; @@ -34988,7 +35113,7 @@ vErrors.push(err22); errors++; } if(!(data4 === "release")){ -const err23 = {instancePath:instancePath+"/subject/kind",schemaPath:"#/$defs/ScheduleRunSubject/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema493.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err23 = {instancePath:instancePath+"/subject/kind",schemaPath:"#/$defs/ScheduleRunSubject/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema495.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err23]; } @@ -35001,7 +35126,7 @@ errors++; if(data3.releaseId !== undefined){ let data5 = data3.releaseId; if(typeof data5 === "string"){ -if(!pattern217.test(data5)){ +if(!pattern219.test(data5)){ const err24 = {instancePath:instancePath+"/subject/releaseId",schemaPath:"#/$defs/ScheduleRunSubject/oneOf/0/properties/releaseId/pattern",keyword:"pattern",params:{pattern: "^rel_[A-Za-z0-9]{6,32}$"},message:"must match pattern \""+"^rel_[A-Za-z0-9]{6,32}$"+"\""}; if(vErrors === null){ vErrors = [err24]; @@ -35097,7 +35222,7 @@ vErrors.push(err31); errors++; } if(!(data6 === "automation")){ -const err32 = {instancePath:instancePath+"/subject/kind",schemaPath:"#/$defs/ScheduleRunSubject/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema493.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; +const err32 = {instancePath:instancePath+"/subject/kind",schemaPath:"#/$defs/ScheduleRunSubject/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema495.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err32]; } @@ -35110,7 +35235,7 @@ errors++; if(data3.automationId !== undefined){ let data7 = data3.automationId; if(typeof data7 === "string"){ -if(!pattern237.test(data7)){ +if(!pattern239.test(data7)){ const err33 = {instancePath:instancePath+"/subject/automationId",schemaPath:"#/$defs/ScheduleRunSubject/oneOf/1/properties/automationId/pattern",keyword:"pattern",params:{pattern: "^aut_[A-Za-z0-9]{6,32}$"},message:"must match pattern \""+"^aut_[A-Za-z0-9]{6,32}$"+"\""}; if(vErrors === null){ vErrors = [err33]; @@ -35145,7 +35270,7 @@ vErrors.push(err35); errors++; } if(!((data8 === "builtin") || (data8 === "agent-task"))){ -const err36 = {instancePath:instancePath+"/subject/job",schemaPath:"#/$defs/ScheduleRunSubject/oneOf/1/properties/job/enum",keyword:"enum",params:{allowedValues: schema493.oneOf[1].properties.job.enum},message:"must be equal to one of the allowed values"}; +const err36 = {instancePath:instancePath+"/subject/job",schemaPath:"#/$defs/ScheduleRunSubject/oneOf/1/properties/job/enum",keyword:"enum",params:{allowedValues: schema495.oneOf[1].properties.job.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err36]; } @@ -35240,7 +35365,7 @@ vErrors.push(err41); errors++; } if(!(((((((((data10 === "due") || (data10 === "ready")) || (data10 === "running")) || (data10 === "succeeded")) || (data10 === "failed")) || (data10 === "missed")) || (data10 === "skipped")) || (data10 === "budget-exceeded")) || (data10 === "killed"))){ -const err42 = {instancePath:instancePath+"/state",schemaPath:"#/$defs/ScheduleRunState/enum",keyword:"enum",params:{allowedValues: schema495.enum},message:"must be equal to one of the allowed values"}; +const err42 = {instancePath:instancePath+"/state",schemaPath:"#/$defs/ScheduleRunState/enum",keyword:"enum",params:{allowedValues: schema497.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err42]; } @@ -35263,7 +35388,7 @@ vErrors.push(err43); errors++; } if(!((((data11 === "did-work") || (data11 === "nothing-to-do")) || (data11 === "proposed")) || (data11 === "waiting-approval"))){ -const err44 = {instancePath:instancePath+"/outcome",schemaPath:"#/properties/outcome/enum",keyword:"enum",params:{allowedValues: schema489.properties.outcome.enum},message:"must be equal to one of the allowed values"}; +const err44 = {instancePath:instancePath+"/outcome",schemaPath:"#/properties/outcome/enum",keyword:"enum",params:{allowedValues: schema491.properties.outcome.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err44]; } @@ -35286,7 +35411,7 @@ vErrors.push(err45); errors++; } if(!(((((data12 === "cron") || (data12 === "alarm")) || (data12 === "request")) || (data12 === "node")) || (data12 === "manual"))){ -const err46 = {instancePath:instancePath+"/trigger",schemaPath:"#/properties/trigger/enum",keyword:"enum",params:{allowedValues: schema489.properties.trigger.enum},message:"must be equal to one of the allowed values"}; +const err46 = {instancePath:instancePath+"/trigger",schemaPath:"#/properties/trigger/enum",keyword:"enum",params:{allowedValues: schema491.properties.trigger.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err46]; } @@ -35409,7 +35534,7 @@ vErrors.push(err55); errors++; } if(!((data17 === "dst-gap") || (data17 === "tzdata"))){ -const err56 = {instancePath:instancePath+"/adjusted",schemaPath:"#/properties/adjusted/enum",keyword:"enum",params:{allowedValues: schema489.properties.adjusted.enum},message:"must be equal to one of the allowed values"}; +const err56 = {instancePath:instancePath+"/adjusted",schemaPath:"#/properties/adjusted/enum",keyword:"enum",params:{allowedValues: schema491.properties.adjusted.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err56]; } @@ -35576,7 +35701,7 @@ vErrors.push(err68); } errors++; } -if(!pattern207.test(data24)){ +if(!pattern209.test(data24)){ const err69 = {instancePath:instancePath+"/summary",schemaPath:"#/properties/summary/pattern",keyword:"pattern",params:{pattern: "^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"},message:"must match pattern \""+"^[^\\x00-\\x08\\x0B-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"+"\""}; if(vErrors === null){ vErrors = [err69]; @@ -35614,7 +35739,7 @@ errors++; for(const key4 in data25){ const _errs68 = errors; if(typeof key4 === "string"){ -if(!pattern36.test(key4)){ +if(!pattern38.test(key4)){ const err72 = {instancePath:instancePath+"/counts",schemaPath:"#/properties/counts/propertyNames/pattern",keyword:"pattern",params:{pattern: "^[a-z][a-z0-9-]{0,40}$"},message:"must match pattern \""+"^[a-z][a-z0-9-]{0,40}$"+"\"",propertyName:key4}; if(vErrors === null){ vErrors = [err72]; @@ -35803,7 +35928,7 @@ errors++; if(data29.code !== undefined){ let data30 = data29.code; if(typeof data30 === "string"){ -if(!pattern251.test(data30)){ +if(!pattern253.test(data30)){ const err87 = {instancePath:instancePath+"/error/code",schemaPath:"#/$defs/ScheduleRunError/properties/code/pattern",keyword:"pattern",params:{pattern: "^[a-z][a-z0-9_]{0,63}$"},message:"must match pattern \""+"^[a-z][a-z0-9_]{0,63}$"+"\""}; if(vErrors === null){ vErrors = [err87]; @@ -35838,7 +35963,7 @@ vErrors.push(err89); } errors++; } -if(!pattern236.test(data31)){ +if(!pattern238.test(data31)){ const err90 = {instancePath:instancePath+"/error/message",schemaPath:"#/$defs/ScheduleRunError/properties/message/pattern",keyword:"pattern",params:{pattern: "^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"},message:"must match pattern \""+"^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$"+"\""}; if(vErrors === null){ vErrors = [err90]; @@ -35964,14 +36089,14 @@ return errors === 0; validate189.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; export const validateGlossary = validate194; -const schema509 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/glossary.schema.json","title":"Glossary","description":"A site's translation glossary (RFC-0033 §2): terms with their approved translation per locale, and terms never to translate (brand and product names). Agents translating content read it as hard constraints; the checks flag drafts that break it. One per site, snapshotted like every node.","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","entries","rev","createdAt","updatedAt"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/Id"},"siteId":{"$ref":"#/$defs/Id"},"entries":{"type":"array","description":"Unique by term (case-insensitive); order is the editor's.","items":{"$ref":"#/$defs/GlossaryEntry"},"maxItems":2000},"rev":{"$ref":"#/$defs/Revision"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"metadata":{"$ref":"#/$defs/Metadata"}},"$defs":{"GlossaryEntry":{"type":"object","additionalProperties":false,"required":["term"],"properties":{"term":{"type":"string","description":"The term as written in the site's default locale.","minLength":1,"maxLength":200},"note":{"type":"string","description":"Context for translators and agents: meaning, part of speech, where it appears.","maxLength":500},"doNotTranslate":{"type":"boolean","description":"Keep the term as written in every locale (brand, product or proper names). Such an entry has no translations."},"caseSensitive":{"type":"boolean","description":"Match the term only with this exact capitalisation."},"translations":{"type":"object","description":"The approved translation per locale (never the default locale).","propertyNames":{"$ref":"#/$defs/Locale"},"additionalProperties":{"type":"object","additionalProperties":false,"required":["text"],"properties":{"text":{"type":"string","minLength":1,"maxLength":200},"note":{"type":"string","maxLength":500}}}}}},"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"Locale":{"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"},"Metadata":{"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}}}; -const schema510 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; -const schema511 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; -const schema515 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; -const schema516 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; -const schema518 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}}; -const schema513 = {"type":"object","additionalProperties":false,"required":["term"],"properties":{"term":{"type":"string","description":"The term as written in the site's default locale.","minLength":1,"maxLength":200},"note":{"type":"string","description":"Context for translators and agents: meaning, part of speech, where it appears.","maxLength":500},"doNotTranslate":{"type":"boolean","description":"Keep the term as written in every locale (brand, product or proper names). Such an entry has no translations."},"caseSensitive":{"type":"boolean","description":"Match the term only with this exact capitalisation."},"translations":{"type":"object","description":"The approved translation per locale (never the default locale).","propertyNames":{"$ref":"#/$defs/Locale"},"additionalProperties":{"type":"object","additionalProperties":false,"required":["text"],"properties":{"text":{"type":"string","minLength":1,"maxLength":200},"note":{"type":"string","maxLength":500}}}}}}; -const schema514 = {"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"}; +const schema511 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/glossary.schema.json","title":"Glossary","description":"A site's translation glossary (RFC-0033 §2): terms with their approved translation per locale, and terms never to translate (brand and product names). Agents translating content read it as hard constraints; the checks flag drafts that break it. One per site, snapshotted like every node.","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","entries","rev","createdAt","updatedAt"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/Id"},"siteId":{"$ref":"#/$defs/Id"},"entries":{"type":"array","description":"Unique by term (case-insensitive); order is the editor's.","items":{"$ref":"#/$defs/GlossaryEntry"},"maxItems":2000},"rev":{"$ref":"#/$defs/Revision"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"metadata":{"$ref":"#/$defs/Metadata"}},"$defs":{"GlossaryEntry":{"type":"object","additionalProperties":false,"required":["term"],"properties":{"term":{"type":"string","description":"The term as written in the site's default locale.","minLength":1,"maxLength":200},"note":{"type":"string","description":"Context for translators and agents: meaning, part of speech, where it appears.","maxLength":500},"doNotTranslate":{"type":"boolean","description":"Keep the term as written in every locale (brand, product or proper names). Such an entry has no translations."},"caseSensitive":{"type":"boolean","description":"Match the term only with this exact capitalisation."},"translations":{"type":"object","description":"The approved translation per locale (never the default locale).","propertyNames":{"$ref":"#/$defs/Locale"},"additionalProperties":{"type":"object","additionalProperties":false,"required":["text"],"properties":{"text":{"type":"string","minLength":1,"maxLength":200},"note":{"type":"string","maxLength":500}}}}}},"Id":{"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48},"Locale":{"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"},"Metadata":{"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}},"Revision":{"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}}}; +const schema512 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; +const schema513 = {"type":"string","description":"Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.","pattern":"^[a-z]+_[A-Za-z0-9]{6,32}$","minLength":8,"maxLength":48}; +const schema517 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0}; +const schema518 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; +const schema520 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}}; +const schema515 = {"type":"object","additionalProperties":false,"required":["term"],"properties":{"term":{"type":"string","description":"The term as written in the site's default locale.","minLength":1,"maxLength":200},"note":{"type":"string","description":"Context for translators and agents: meaning, part of speech, where it appears.","maxLength":500},"doNotTranslate":{"type":"boolean","description":"Keep the term as written in every locale (brand, product or proper names). Such an entry has no translations."},"caseSensitive":{"type":"boolean","description":"Match the term only with this exact capitalisation."},"translations":{"type":"object","description":"The approved translation per locale (never the default locale).","propertyNames":{"$ref":"#/$defs/Locale"},"additionalProperties":{"type":"object","additionalProperties":false,"required":["text"],"properties":{"text":{"type":"string","minLength":1,"maxLength":200},"note":{"type":"string","maxLength":500}}}}}}; +const schema516 = {"type":"string","description":"BCP 47 language tag, e.g. en, tr, en-US.","pattern":"^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"}; function validate195(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ let vErrors = null; @@ -36362,7 +36487,7 @@ vErrors.push(err8); errors++; } if(!(data0 === 1)){ -const err9 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema510.enum},message:"must be equal to one of the allowed values"}; +const err9 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema512.enum},message:"must be equal to one of the allowed values"}; if(vErrors === null){ vErrors = [err9]; } diff --git a/packages/core-ts/package.json b/packages/core-ts/package.json index 456df004..bd82216f 100644 --- a/packages/core-ts/package.json +++ b/packages/core-ts/package.json @@ -18,6 +18,7 @@ "./local": "./src/local.ts", "./filter": "./src/filter.ts", "./names": "./src/names.ts", + "./permalinks": "./src/permalinks.ts", "./taxonomy-report": "./src/taxonomy-report.ts", "./menu": "./src/menu.ts", "./cache-tags": "./src/cache-tags.ts", @@ -94,6 +95,10 @@ "types": "./dist/src/names.d.ts", "default": "./dist/src/names.js" }, + "./permalinks": { + "types": "./dist/src/permalinks.d.ts", + "default": "./dist/src/permalinks.js" + }, "./taxonomy-report": { "types": "./dist/src/taxonomy-report.d.ts", "default": "./dist/src/taxonomy-report.js" diff --git a/packages/core-ts/src/automations.ts b/packages/core-ts/src/automations.ts index d25bed6f..55c99a94 100644 --- a/packages/core-ts/src/automations.ts +++ b/packages/core-ts/src/automations.ts @@ -53,6 +53,7 @@ import { Lexer, type Token, type Tokens } from "marked"; import { gateState } from "./availability.ts"; import { ASSET_HREF, imageSlots, markdownSlots } from "./media/refs.ts"; import { MEDIA_ROUTE, mediaPrefix } from "./media/types.ts"; +import { entryAt, entryPath } from "./permalinks.ts"; import { matchRedirect, normalisePath } from "./redirects.ts"; // ───────────────────────────── inputs from the deployment ───────────────────────────── @@ -288,7 +289,6 @@ type Resolution = | { readonly ok: false; readonly gated?: { id: string; state: "embargoed" | "expired" } }; const WELL_KNOWN = new Set(["/", "/rss.xml", "/sitemap.xml", "/robots.txt"]); -const ENTRY_PATH = /^\/([a-z0-9]+(?:-[a-z0-9]+)*)\/([a-z0-9]+(?:-[a-z0-9]+)*)$/; /** The live site's routes, gates and assets at `now` (RFC-0002 §7's order: page, entry, archive, redirect). */ class LiveIndex { @@ -313,7 +313,7 @@ class LiveIndex { list.push(i); this.#pages.set(i.path, list); } else if (i.kind === "contentEntry" && i.collection && i.slug) - this.#entries.set(`/${i.collection}/${i.slug}`, i); + this.#entries.set(entryPathOf(live.site, i), i); else if (i.kind === "term" && i.taxonomyId && i.slug) this.#terms.set(`${i.taxonomyId}\n${i.slug}`, i); else if (i.kind === "taxonomy" && i.archiveOn && i.archiveBase) this.#taxonomies.push(i); @@ -359,9 +359,8 @@ class LiveIndex { if (WELL_KNOWN.has(path)) return { ok: true }; const page = this.#node(this.#pages.get(path)); if (page?.ok) return page; - const entry = ENTRY_PATH.test(path) - ? this.#node(this.#entries.has(path) ? [this.#entries.get(path) as RouteInfo] : undefined) - : undefined; + const hit = entryAt(this.live.site, path) && this.#entries.get(path); + const entry = hit ? this.#node([hit]) : undefined; if (entry?.ok) return entry; for (const tax of this.#taxonomies) { const base = tax.archiveBase as string; @@ -391,7 +390,8 @@ class LiveIndex { } } -const entryPathOf = (i: RouteInfo) => `/${i.collection}/${i.slug}`; +const entryPathOf = (site: Site, i: { collection?: unknown; slug?: unknown }) => + entryPath(site, { collection: `${i.collection}`, slug: `${i.slug}` }); /** The live index: one read of the routes, plus live bodies of nodes changed since publishing. */ async function liveIndex( @@ -745,7 +745,7 @@ export function graphBuiltinSources(o: GraphBuiltinSourcesOptions): BuiltinSourc segment: lastSegment(i.path as string), })), ...index.entries().map((i) => ({ - path: entryPathOf(i), + path: entryPathOf(index.live.site, i), nodeId: i.id, segment: i.slug as string, })), @@ -823,7 +823,7 @@ export function graphBuiltinSources(o: GraphBuiltinSourcesOptions): BuiltinSourc severity: "info", nodeId: r.id, title: r.title ?? r.id, - path: `/${r.collection}/${r.slug}`, + path: entryPathOf(live.site, r), updatedAt: r.updated_at, ageDays: Math.floor((t - Date.parse(r.updated_at)) / DAY_MS), }); @@ -853,14 +853,14 @@ export function graphBuiltinSources(o: GraphBuiltinSourcesOptions): BuiltinSourc if (i.kind !== "page" && i.kind !== "contentEntry") continue; const v = index.visibility(i); if (v !== "embargoed" && v !== "expired") continue; - const path = i.kind === "page" ? i.path : entryPathOf(i); + const path = i.kind === "page" ? i.path : entryPathOf(index.live.site, i); if (path) gated.push({ nodeId: i.id, path, state: v }); } return { now: canonicalInstant(now), newest: entries.map((e) => ({ nodeId: e.id, - path: entryPathOf(e), + path: entryPathOf(index.live.site, e), title: e.title ?? e.id, })), gated, diff --git a/packages/core-ts/src/index.ts b/packages/core-ts/src/index.ts index f910d031..e31a1ca4 100644 --- a/packages/core-ts/src/index.ts +++ b/packages/core-ts/src/index.ts @@ -48,6 +48,7 @@ export { } from "./media-import.ts"; export * from "./menu.ts"; export * from "./names.ts"; +export * from "./permalinks.ts"; export * from "./preview-token.ts"; export * from "./proposals.ts"; export { diff --git a/packages/core-ts/src/permalinks.ts b/packages/core-ts/src/permalinks.ts new file mode 100644 index 00000000..ed595d7b --- /dev/null +++ b/packages/core-ts/src/permalinks.ts @@ -0,0 +1,59 @@ +/** + * RFC-0038 / ADR-0040: where a content entry lives. A collection's `permalink` template + * (`Site.collections..permalink`) names the URL, `//{slug}` by default. Version 1 + * templates are fixed segments followed by `/{slug}` (the schema's pattern), so the template is a + * prefix and one slug segment. Every place that links to an entry, and every place that reads a + * path back, goes through these two functions. + * + * Worker-safe: no imports beyond types. + */ +import type { ContentEntry, Site } from "@cloudhal/core-contracts"; + +/** The part of the site these functions read. */ +export type PermalinkSite = Pick; + +const SLUG_TOKEN = "/{slug}"; + +/** The collection's template: its own `permalink`, else `//{slug}`. */ +export function collectionPermalink(site: PermalinkSite | undefined, collection: string): string { + return site?.collections?.[collection]?.permalink ?? `/${collection}${SLUG_TOKEN}`; +} + +/** The fixed part before `/{slug}`: `/blog` for `/blog/{slug}`. */ +export function permalinkPrefix(template: string): string { + return template.endsWith(SLUG_TOKEN) ? template.slice(0, -SLUG_TOKEN.length) : template; +} + +/** `/blog/hello-world`: the collection's template with the entry's slug filled in. */ +export function entryPath( + site: PermalinkSite | undefined, + entry: Pick, +): string { + return `${permalinkPrefix(collectionPermalink(site, entry.collection))}/${entry.slug}`; +} + +/** + * The collection and slug a path names, or undefined. A configured template wins over the + * default, the longest prefix first; a collection that has its own template is no longer at + * `//…` (its old URLs answer through redirects). Whether such an entry exists is + * the caller's question. + */ +export function entryAt( + site: PermalinkSite | undefined, + path: string, +): { collection: string; slug: string } | undefined { + const configured = Object.entries(site?.collections ?? {}) + .filter(([, s]) => s?.permalink) + .map(([collection, s]) => ({ collection, prefix: permalinkPrefix(s?.permalink as string) })) + .sort((a, b) => b.prefix.length - a.prefix.length); + for (const { collection, prefix } of configured) { + if (!path.startsWith(`${prefix}/`)) continue; + const slug = path.slice(prefix.length + 1); + if (slug && !slug.includes("/")) return { collection, slug }; + } + const m = /^\/([^/]+)\/([^/]+)$/.exec(path); + if (!m) return undefined; + const [, collection, slug] = m as unknown as [string, string, string]; + if (site?.collections?.[collection]?.permalink) return undefined; + return { collection, slug }; +} diff --git a/packages/core-ts/src/search-rows.ts b/packages/core-ts/src/search-rows.ts index 5519b30d..0b978faa 100644 --- a/packages/core-ts/src/search-rows.ts +++ b/packages/core-ts/src/search-rows.ts @@ -5,6 +5,7 @@ */ import type { ContentEntry, Page, Taxonomy, Term } from "@cloudhal/core-contracts"; import { termName } from "./names.ts"; +import { entryPath, type PermalinkSite } from "./permalinks.ts"; import { MARK_CLOSE, MARK_OPEN, SEARCH_TEXT_MAX, type SearchRow } from "./search-common.ts"; import { termSlug } from "./term-locales.ts"; @@ -72,7 +73,7 @@ function entryTermIds(entry: ContentEntry): string[] { */ export function searchRowOf( entry: ContentEntry, - options: { drafts?: boolean } = {}, + options: { drafts?: boolean; site?: PermalinkSite | undefined } = {}, ): SearchRow | undefined { if (!shown(entry.status, options.drafts)) return undefined; const terms = entryTermIds(entry); @@ -83,7 +84,7 @@ export function searchRowOf( title: entry.title, summary: entry.summary ?? "", body: searchText(entry.body), - href: `/${entry.collection}/${entry.slug}`, + href: entryPath(options.site, entry), publishedAt: iso(entry.publishedAt), gateFrom: iso(entry.availability?.from), gateUntil: iso(entry.availability?.until), @@ -241,11 +242,14 @@ export function searchDocuments(input: { drafts?: boolean; /** The site's default locale, whose archive slugs are the terms' own (default: `locales[0]`). */ defaultLocale?: string; + /** The site's collection permalinks (RFC-0038), for article links. */ + site?: PermalinkSite; }): SearchRow[] { const options = { drafts: input.drafts ?? false }; + const entryOptions = { ...options, site: input.site }; const byId = new Map(input.taxonomies.map((t) => [t.id, t])); const rows: (SearchRow | undefined)[] = [ - ...input.entries.map((e) => searchRowOf(e, options)), + ...input.entries.map((e) => searchRowOf(e, entryOptions)), ...input.pages.map((p) => pageSearchRow(p, options)), ]; for (const term of input.terms) { diff --git a/packages/core-ts/src/search.ts b/packages/core-ts/src/search.ts index 3c135fd7..9ad2f3b1 100644 --- a/packages/core-ts/src/search.ts +++ b/packages/core-ts/src/search.ts @@ -186,12 +186,14 @@ export async function planSearch(input: { (n) => INDEXED.has(n.kind) && (changed(n) || (n.kind === "term" && archivesChanged)), ); const rows: SearchRow[] = []; + const siteRef = target.nodes.find((n) => n.kind === "site"); + const site = siteRef && rewrite.length > 0 ? ((await input.read(siteRef)) as Site) : undefined; const archives = rewrite.some((n) => n.kind === "term") - ? await archiveContext(target, input.read) + ? await archiveContext(target, site, input.read) : undefined; for (const ref of rewrite) { const node = await input.read(ref); - if (ref.kind === "contentEntry") pushRow(rows, searchRowOf(node as ContentEntry)); + if (ref.kind === "contentEntry") pushRow(rows, searchRowOf(node as ContentEntry, { site })); else if (ref.kind === "page") pushRow(rows, pageSearchRow(node as Page)); else if (archives) { const term = node as Term; @@ -223,10 +225,9 @@ function pushRow(rows: SearchRow[], row: SearchRow | undefined): void { /** The site's locales and taxonomies, which term rows need. */ async function archiveContext( target: Snap, + site: Site | undefined, read: (ref: NodeRef) => Promise, ): Promise<{ locales: string[]; taxonomies: Map }> { - const siteRef = target.nodes.find((n) => n.kind === "site"); - const site = siteRef ? ((await read(siteRef)) as Site) : undefined; const taxonomies = new Map(); for (const ref of target.nodes.filter((n) => n.kind === "taxonomy")) taxonomies.set(ref.id, (await read(ref)) as Taxonomy); diff --git a/packages/core-ts/src/site-search.ts b/packages/core-ts/src/site-search.ts index ea531b6f..e13f4470 100644 --- a/packages/core-ts/src/site-search.ts +++ b/packages/core-ts/src/site-search.ts @@ -16,6 +16,7 @@ import type { Term, } from "@cloudhal/core-contracts"; import { foldName, termName } from "./names.ts"; +import { entryPath, type PermalinkSite } from "./permalinks.ts"; import { SEARCH_QUERY_MAX, SEARCH_TERMS_MAX } from "./search-common.ts"; export type SiteSearchKind = "entry" | "page" | "term" | "asset" | "menu" | "redirect"; @@ -117,16 +118,21 @@ function menuItems(menu: Menu): Candidate[] { return out; } -type Reader = (src: SiteSearchSource, siteId: string, query: string) => Promise; +type Reader = ( + src: SiteSearchSource, + siteId: string, + query: string, + site: PermalinkSite | undefined, +) => Promise; /** Where each kind's candidates come from (a dispatch table, one reader per kind). */ const READERS: Record = { - entry: async (src, siteId) => + entry: async (src, siteId, _query, site) => (await src.listContentEntries(siteId)).map((e) => ({ kind: "entry", id: e.id, title: e.title, - detail: `/${e.collection}/${e.slug}`, + detail: entryPath(site, e), context: e.collection, status: e.status, locale: e.locale, @@ -195,7 +201,12 @@ export async function searchSiteNodes( src: SiteSearchSource, siteId: string, query: string, - options: { kinds?: readonly SiteSearchKind[]; limit?: number } = {}, + options: { + kinds?: readonly SiteSearchKind[]; + limit?: number; + /** The site's collection permalinks (RFC-0038), for entries' paths. */ + site?: PermalinkSite; + } = {}, ): Promise { const q = query.slice(0, SEARCH_QUERY_MAX).trim(); const words = siteSearchWords(q); @@ -204,7 +215,7 @@ export async function searchSiteNodes( const wanted = SITE_SEARCH_KINDS.filter((k) => !options.kinds || options.kinds.includes(k)); const groups = await Promise.all( wanted.map(async (kind) => { - const candidates = await READERS[kind](src, siteId, q); + const candidates = await READERS[kind](src, siteId, q, options.site); return candidates .map((c) => ({ c, diff --git a/packages/core-ts/src/store/entries.ts b/packages/core-ts/src/store/entries.ts index c71fbbf9..bfc426a8 100644 --- a/packages/core-ts/src/store/entries.ts +++ b/packages/core-ts/src/store/entries.ts @@ -9,6 +9,7 @@ import { ID_PREFIX } from "../ids.ts"; import { fillMissingSrc } from "../media/refs.ts"; import { and, casUpdateStatement, insertNodeStatement, OpBatch, runOpBatch } from "../node-sql.ts"; import { uniquenessKey } from "../nodes.ts"; +import { entryPath } from "../permalinks.ts"; import { canonicalTerms } from "../taxonomy.ts"; import type { StoreContext } from "./context.ts"; import { checkLocalizedNode, checkTranslationLinks } from "./translations.ts"; @@ -98,8 +99,9 @@ export class EntryEngine { wasPublished: !!stored && sameLocale && (stored.status === "published" || !!stored.publishedAt), isPublished: entry.status === "published", - oldPath: stored && sameLocale ? `/${stored.collection}/${stored.slug}` : undefined, - newPath: `/${entry.collection}/${entry.slug}`, + // Both under the site's current templates (RFC-0038). + oldPath: stored && sameLocale ? entryPath(site, stored) : undefined, + newPath: entryPath(site, entry), source: { kind: "contentEntry", id: entry.id }, }); if (newTerms.length === 0 && !plan) { diff --git a/packages/core-ts/test/permalinks.test.ts b/packages/core-ts/test/permalinks.test.ts new file mode 100644 index 00000000..603f2cb0 --- /dev/null +++ b/packages/core-ts/test/permalinks.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it } from "vitest"; +import { collectionPermalink, entryAt, entryPath } from "../src/permalinks.ts"; + +const plain = {}; +const site = { + collections: { + posts: { permalink: "/blog/{slug}" }, + guides: { permalink: "/docs/guides/{slug}" }, + docs: { permalink: "/docs/{slug}" }, + news: {}, + }, +}; +const e = (collection: string, slug: string) => ({ collection, slug }); + +describe("permalinks (RFC-0038)", () => { + it("defaults to //{slug}, as before", () => { + expect(entryPath(plain, e("posts", "hello"))).toBe("/posts/hello"); + expect(entryPath(undefined, e("posts", "hello"))).toBe("/posts/hello"); + expect(entryAt(plain, "/posts/hello")).toEqual(e("posts", "hello")); + expect(collectionPermalink(site, "news")).toBe("/news/{slug}"); + }); + + it("uses a collection's template both ways", () => { + expect(entryPath(site, e("posts", "hello"))).toBe("/blog/hello"); + expect(entryPath(site, e("guides", "setup"))).toBe("/docs/guides/setup"); + expect(entryAt(site, "/blog/hello")).toEqual(e("posts", "hello")); + expect(entryAt(site, "/news/today")).toEqual(e("news", "today")); + }); + + it("prefers the longest prefix, and a slug is one segment", () => { + expect(entryAt(site, "/docs/guides/setup")).toEqual(e("guides", "setup")); + expect(entryAt(site, "/docs/intro")).toEqual(e("docs", "intro")); + expect(entryAt(site, "/docs/guides/setup/more")).toBeUndefined(); + expect(entryAt(site, "/")).toBeUndefined(); + expect(entryAt(site, "/about")).toBeUndefined(); + }); + + it("does not answer at a moved collection's old prefix", () => { + expect(entryAt(site, "/posts/hello")).toBeUndefined(); + }); + + it("round-trips every entry", () => { + for (const x of [ + e("posts", "a"), + e("guides", "b"), + e("docs", "c"), + e("news", "d"), + e("misc", "e"), + ]) + expect(entryAt(site, entryPath(site, x))).toEqual(x); + }); +}); diff --git a/packages/mcp-server/src/analytics-reports.ts b/packages/mcp-server/src/analytics-reports.ts index 4fe7b118..0fe336ab 100644 --- a/packages/mcp-server/src/analytics-reports.ts +++ b/packages/mcp-server/src/analytics-reports.ts @@ -21,10 +21,9 @@ import type { } from "@cloudhal/analytics"; import { ID_RE, parseRange, privacySignalShare } from "@cloudhal/analytics"; import { type AnalyticsService, previousRange } from "@cloudhal/analytics/service"; -import { entryPath } from "@cloudhal/astro-renderer/routing"; import { archivePath } from "@cloudhal/astro-renderer/taxonomy"; import type { ContentEntry, Page, Site, Taxonomy, Term } from "@cloudhal/core-contracts"; -import { type SiteGraphStore, termName } from "@cloudhal/core-ts"; +import { entryPath, type SiteGraphStore, termName } from "@cloudhal/core-ts"; export type AnalyticsNodeKind = "page" | "entry" | "archive"; @@ -143,7 +142,7 @@ export async function analyticsNodes( kind: "entry", exists: true, title: e.title, - path: entryPath(e), + path: entryPath(site, e), status: e.status, } : gone(id), @@ -581,7 +580,7 @@ export async function brokenLinksReport( ...pages .filter((p) => p.status === "published") .map((p) => ({ path: p.path, nodeId: p.id, segment: lastSegment(p.path) })), - ...entries.map((e) => ({ path: entryPath(e), nodeId: e.id, segment: e.slug })), + ...entries.map((e) => ({ path: entryPath(site, e), nodeId: e.id, segment: e.slug })), ]; return candidates; }; diff --git a/packages/mcp-server/src/comment-tools.ts b/packages/mcp-server/src/comment-tools.ts index 2848ac43..b5245ad0 100644 --- a/packages/mcp-server/src/comment-tools.ts +++ b/packages/mcp-server/src/comment-tools.ts @@ -251,7 +251,7 @@ export function commentTools(): ToolDefinition[] { d.store.counts(site.id, a.nodeId !== undefined ? { nodeId: a.nodeId as string } : {}), commentEntries( ctx.store, - site.id, + site, page.comments.map((c) => c.nodeId), ), d.store.listReports(site.id, { open: true, limit: 200 }), @@ -320,7 +320,7 @@ export function commentTools(): ToolDefinition[] { const d = comments(ctx); const site = await requireSite(ctx, a.siteId as string); const nodeId = a.nodeId as string; - const entry = (await commentEntries(ctx.store, site.id, [nodeId])).get(nodeId); + const entry = (await commentEntries(ctx.store, site, [nodeId])).get(nodeId); if (!entry) throw new ToolError("not_found", `article ${nodeId} not found`); const all = await d.store.threadComments(site.id, nodeId, { includePending: a.includePending !== false, @@ -410,7 +410,7 @@ export function commentTools(): ToolDefinition[] { ]); const entries = await commentEntries( ctx.store, - site.id, + site, stats.busiest.map((b) => b.nodeId), ); const age = (at: string | null) => @@ -720,7 +720,7 @@ export function commentTools(): ToolDefinition[] { const d = comments(ctx); const site = await requireSite(ctx, a.siteId as string); const nodeId = a.nodeId as string; - const entry = (await commentEntries(ctx.store, site.id, [nodeId])).get(nodeId); + const entry = (await commentEntries(ctx.store, site, [nodeId])).get(nodeId); if (!entry) throw new ToolError("not_found", `article ${nodeId} not found`); await assertOwn(ctx, [entry]); const actor = threadClass(a) === "person" ? personActor(ctx) : ctx.principal.actor; diff --git a/packages/mcp-server/src/comments.ts b/packages/mcp-server/src/comments.ts index 11ed6f64..167a3045 100644 --- a/packages/mcp-server/src/comments.ts +++ b/packages/mcp-server/src/comments.ts @@ -8,7 +8,6 @@ * for them (invariant C1), and commenter data stays in the person-only REST routes. */ import { type OwnershipResolver, ownsEntry } from "@cloudhal/agent-runtime"; -import { entryPath } from "@cloudhal/astro-renderer/routing"; import { type CommentKeys, type CommentReport, @@ -20,8 +19,8 @@ import { threadState, } from "@cloudhal/comments"; import type { SqlCommentStore } from "@cloudhal/comments/sql"; -import type { Actor, Comment, ContentEntry } from "@cloudhal/core-contracts"; -import type { SiteGraphStore } from "@cloudhal/core-ts"; +import type { Actor, Comment, ContentEntry, Site } from "@cloudhal/core-contracts"; +import { entryPath, type PermalinkSite, type SiteGraphStore } from "@cloudhal/core-ts"; /** What a deployment provides for reader comments (RFC-0007 §15.3). */ export interface CommentsDeps { @@ -69,11 +68,11 @@ export interface CommentEntry { const ENTRY_GETS = 20; -export function toCommentEntry(e: ContentEntry): CommentEntry { +export function toCommentEntry(site: PermalinkSite | undefined, e: ContentEntry): CommentEntry { return { nodeId: e.id, title: e.title, - path: entryPath(e), + path: entryPath(site, e), collection: e.collection, status: e.status, publishedAt: e.publishedAt ?? null, @@ -81,20 +80,26 @@ export function toCommentEntry(e: ContentEntry): CommentEntry { }; } -/** The entries of `nodeIds` on `siteId` (unknown ids are left out). */ +/** + * The entries of `nodeIds` on the site (unknown ids are left out). Pass the site when the caller + * has it; with only its id, it is read for the entries' paths (RFC-0038). + */ export async function commentEntries( graph: SiteGraphStore, - siteId: string, + site: string | Site, nodeIds: Iterable, ): Promise> { const ids = [...new Set(nodeIds)]; const out = new Map(); if (ids.length === 0) return out; - const found = + const siteId = typeof site === "string" ? site : site.id; + const [found, permalinks] = await Promise.all([ ids.length <= ENTRY_GETS - ? await Promise.all(ids.map((id) => graph.getContentEntry(id))) - : (await graph.listContentEntries(siteId)).filter((e) => ids.includes(e.id)); - for (const e of found) if (e && e.siteId === siteId) out.set(e.id, toCommentEntry(e)); + ? Promise.all(ids.map((id) => graph.getContentEntry(id))) + : graph.listContentEntries(siteId).then((all) => all.filter((e) => ids.includes(e.id))), + typeof site === "string" ? graph.getSite(site) : site, + ]); + for (const e of found) if (e && e.siteId === siteId) out.set(e.id, toCommentEntry(permalinks, e)); return out; } diff --git a/packages/mcp-server/src/release-titles.ts b/packages/mcp-server/src/release-titles.ts new file mode 100644 index 00000000..e3fa6e38 --- /dev/null +++ b/packages/mcp-server/src/release-titles.ts @@ -0,0 +1,34 @@ +/** + * The title and path a release item shows for a node version (§12 `get_release`): whatever name + * the node kind has, and its page path, entry permalink (RFC-0038) or redirect source. + */ +import { entryPath, type PermalinkSite } from "@cloudhal/core-ts"; + +export function titleOf( + site: PermalinkSite | undefined, + node: unknown, +): { title: string | null; path: string | null } { + const n = node as { + title?: string; + path?: string; + name?: unknown; + key?: string; + from?: string; + to?: string; + filename?: string; + slug?: string; + collection?: string; + }; + if (!n) return { title: null, path: null }; + const name = + typeof n.name === "string" + ? n.name + : n.name && typeof n.name === "object" + ? (Object.values(n.name as Record)[0] ?? null) + : null; + const title = + n.title ?? name ?? n.key ?? (n.from ? `${n.from} → ${n.to}` : null) ?? n.filename ?? null; + const entry = n.collection && n.slug ? { collection: n.collection, slug: n.slug } : undefined; + const path = n.path ?? (entry ? entryPath(site, entry) : null) ?? n.from ?? null; + return { title: title ? String(title).slice(0, 200) : null, path }; +} diff --git a/packages/mcp-server/src/schedules.ts b/packages/mcp-server/src/schedules.ts index a5ad51dc..50987f33 100644 --- a/packages/mcp-server/src/schedules.ts +++ b/packages/mcp-server/src/schedules.ts @@ -58,6 +58,7 @@ import { untrustedEffectiveScopes, } from "@cloudhal/schedules"; import type { ReleaseRecord, SqlScheduleStore } from "@cloudhal/schedules/sql"; +import { titleOf } from "./release-titles.ts"; /** What a deployment provides for schedules (RFC-0008 §16). */ export interface SchedulesDeps { @@ -518,32 +519,6 @@ export interface ReleaseView extends Release { }; } -function titleOf(node: unknown): { title: string | null; path: string | null } { - const n = node as { - title?: string; - path?: string; - name?: unknown; - key?: string; - from?: string; - to?: string; - filename?: string; - slug?: string; - collection?: string; - }; - if (!n) return { title: null, path: null }; - const name = - typeof n.name === "string" - ? n.name - : n.name && typeof n.name === "object" - ? (Object.values(n.name as Record)[0] ?? null) - : null; - const title = - n.title ?? name ?? n.key ?? (n.from ? `${n.from} → ${n.to}` : null) ?? n.filename ?? null; - const path = - n.path ?? (n.collection && n.slug ? `/${n.collection}/${n.slug}` : null) ?? n.from ?? null; - return { title: title ? String(title).slice(0, 200) : null, path }; -} - /** A release with its items' titles and paths, overlaps, locks and the request (§12 `get_release`). */ export async function releaseView( deps: SchedulesDeps, @@ -553,6 +528,7 @@ export async function releaseView( ): Promise { const r = record.release; const overlaps = await deps.store.overlaps(r.siteId); + const site = await graph.getSite(r.siteId); // What the dependency closure added (§6.1 step 2): new nodes another item's target references. const referenced = new Set(record.items.flatMap((p) => (p.item.kind === "set" ? p.deps : []))); const closure = new Set( @@ -569,7 +545,7 @@ export async function releaseView( nodeId: i.nodeId, nodeKind: i.nodeKind, kind: i.kind, - ...titleOf(node), + ...titleOf(site, node), pinnedRev: i.kind === "set" ? i.target.rev : null, liveRev: i.base?.rev ?? null, closure: closure.has(i.nodeId), diff --git a/packages/mcp-server/src/search-tools.ts b/packages/mcp-server/src/search-tools.ts index ffda7309..e0c06320 100644 --- a/packages/mcp-server/src/search-tools.ts +++ b/packages/mcp-server/src/search-tools.ts @@ -98,6 +98,7 @@ const searchSiteTool: ToolDefinition = { }; } return await searchSiteNodes(ctx.store, site.id, query, { + site, ...(kinds ? { kinds: kinds as SiteSearchKind[] } : {}), ...(limit !== undefined ? { limit } : {}), }); diff --git a/scripts/size-baseline.json b/scripts/size-baseline.json index 74665703..04e70749 100644 --- a/scripts/size-baseline.json +++ b/scripts/size-baseline.json @@ -1,11 +1,11 @@ { "$comment": "Oversized files and functions as of their last tightening. Entries may only shrink: run `node scripts/size-check.ts --update` after a refactor. See docs/engineering/code-structure.md.", "files": { - "apps/studio/src/components/AnalyticsSections.tsx": 1350, + "apps/studio/src/components/AnalyticsSections.tsx": 1323, "apps/studio/src/components/CommentSettings.tsx": 837, - "apps/studio/src/lib/menus.ts": 831, + "apps/studio/src/lib/menus.ts": 829, "apps/studio/src/routes/AssetPage.tsx": 855, - "apps/studio/src/routes/CommentThreadPage.tsx": 814, + "apps/studio/src/routes/CommentThreadPage.tsx": 813, "apps/studio/src/routes/NavigationPage.tsx": 1171, "apps/studio/src/routes/SchedulesPage.tsx": 1296, "apps/studio/src/routes/SiteSettingsPage.tsx": 881, @@ -26,7 +26,7 @@ "packages/core-ts/src/taxonomy.ts": 1723, "packages/mcp-server/src/menu-tools.ts": 1058, "packages/mcp-server/src/schedule-tools.ts": 1596, - "packages/mcp-server/src/schedules.ts": 890, + "packages/mcp-server/src/schedules.ts": 866, "packages/mcp-server/src/taxonomy-tools.ts": 1433, "packages/schedules/src/recurrence.ts": 939, "packages/schedules/src/scheduler.ts": 1343, @@ -100,7 +100,7 @@ "lines": 273 }, "apps/studio/src/components/GovernanceDialog.tsx::PreviewSection": { - "lines": 167 + "lines": 165 }, "apps/studio/src/components/InviteDialog.tsx::InviteDialog": { "lines": 229 @@ -217,7 +217,7 @@ "lines": 156 }, "apps/studio/src/routes/CommentThreadPage.tsx::CommentThreadPage": { - "lines": 427 + "lines": 425 }, "apps/studio/src/routes/InvitePage.tsx::InvitePage": { "lines": 252 @@ -235,7 +235,7 @@ "lines": 1042 }, "apps/studio/src/routes/RedirectsPage.tsx::RedirectsPage": { - "lines": 296 + "lines": 290 }, "apps/studio/src/routes/ReleasePage.tsx::ReleasePage": { "lines": 505 diff --git a/starters/blog/src/components/EntryList.astro b/starters/blog/src/components/EntryList.astro index fde201e1..702fa453 100644 --- a/starters/blog/src/components/EntryList.astro +++ b/starters/blog/src/components/EntryList.astro @@ -41,7 +41,7 @@ const cover = (e: ContentEntry) => { )}
      - {e.title} + {e.title} {e.summary &&

      {e.summary}

      }