diff --git a/.changeset/reference-repeater-contract.md b/.changeset/reference-repeater-contract.md
new file mode 100644
index 00000000..e0ce785c
--- /dev/null
+++ b/.changeset/reference-repeater-contract.md
@@ -0,0 +1,26 @@
+---
+"@cloudhal/core-contracts": minor
+"@cloudhal/core-ts": patch
+"@cloudhal/mcp-server": patch
+"@cloudhal/api": patch
+---
+
+The contract gains two field types for collections, `reference` and `repeater` (RFC-0045 §1, rollout step 1, #114). Every write path still refuses them, so existing sites behave exactly as before.
+
+- **Contract.** `FieldDefinition` gains `ReferenceField` and `RepeaterField`. A new `SubFieldDefinition` union holds a repeater's sub-fields: scalar values only, so a repeater cannot contain a repeater. `FieldMigration.fromType` gains both types, and `FieldMigration` gains an optional `sub`. There is no `schemaVersion` bump, and existing documents stay valid.
+- **The gate.** Until rollout step 7 enables them, a reference or repeater definition, or a migration marker that names one, is refused with `fields_unsupported`: API status 409, with an MCP hint. The refusal covers:
+ - site creates and updates, so Studio, MCP and the API;
+ - field operations;
+ - every bundle `importSite` takes: import, pull, push, `cloudhal import --replace` and `--revert`, snapshot restores and edition copies.
+
+ `checkBundle` gained `assertFieldTypes`. It checks the working site row, and `prepareImport` checks every snapshot site body the import writes once it has been verified (bodies the target already holds are its own), and refuses the types when no capability is given. A push answers `409 fields_unsupported` with its issues, not `400 bad_request`.
+- **Pure core** (`@cloudhal/core-ts/fields`), with no effect on stored data yet:
+ - the definition rules: `reference_target_unknown`, `where`, `itemLabel`, and caps on sub-fields and leaves;
+ - value checks for both types, the per-item `requiredToPublish`, and `too_many_references`;
+ - `resolveFields` skips types it does not know;
+ - `fieldsJsonSchema(…, { strict: true })` and `strictSchemaCost`;
+ - `pickMember`, `referenceIssues`, and `FIELD_QUERY_OPS` and structured-data entries that make neither type listable or mappable.
+
+ `FIELD_TYPES` keeps the nine enabled types (`EnabledFieldType`). `ALL_FIELD_TYPES` lists all eleven.
+- **MCP.** `update_collection`'s input now builds its `$defs` with `defsClosure` and offers only the enabled types. A test checks that every `$ref` in every tool's input and output schema resolves. Before this change, `pnpm gen` alone would have left the input pointing at `$defs` it did not carry.
+- **Fix, existing sites:** block props and typed-field objects no longer accept keys named after `Object.prototype` members (`constructor`, `toString`, `__proto__`, …) as known props. The props interpreter (`validateProps`) now checks and reads own keys only. Such keys are refused as unknown, and a prop the value leaves out is absent, not the prototype's function. `toLocaleString`, `isPrototypeOf` and `propertyIsEnumerable` join the reserved field keys, beside `toString`, `valueOf` and `hasOwnProperty`.
diff --git a/apps/studio/src/components/fields/FieldRow.tsx b/apps/studio/src/components/fields/FieldRow.tsx
index 669910ef..dc8a4e2e 100644
--- a/apps/studio/src/components/fields/FieldRow.tsx
+++ b/apps/studio/src/components/fields/FieldRow.tsx
@@ -1,4 +1,5 @@
import type { FieldDefinition } from "@cloudhal/core-contracts";
+import { isReferenceFieldType } from "@cloudhal/core-ts/fields";
import { Badge, Button, DropdownMenu, Text, Tooltip } from "@cloudhal/ui";
import {
ArrowDown,
@@ -59,7 +60,10 @@ export function FieldRow({
{def.key}
- {t(`fields.types.${def.type}`)}
+ {/* RFC-0045: reference and repeater get their words with their blocks (rollout step 6). */}
+ {!isReferenceFieldType(def.type) && (
+ {t(`fields.types.${def.type}`)}
+ )}
{def.localized && {t("fields.builder.localized")}}
{def.type !== "boolean" && def.requiredToPublish && (
{t("fields.editor.neededToPublish")}
diff --git a/apps/studio/src/components/listings/ListFieldsSection.tsx b/apps/studio/src/components/listings/ListFieldsSection.tsx
index 449c60a9..974af2ce 100644
--- a/apps/studio/src/components/listings/ListFieldsSection.tsx
+++ b/apps/studio/src/components/listings/ListFieldsSection.tsx
@@ -1,5 +1,5 @@
import type { CollectionSettings, FieldDefinition, Site } from "@cloudhal/core-contracts";
-import { fieldLabel } from "@cloudhal/core-ts/fields";
+import { fieldLabel, isReferenceFieldType } from "@cloudhal/core-ts/fields";
import { Badge, Button, Link, SectionEmpty, Select, Text } from "@cloudhal/ui";
import { ArrowDown, ArrowUp, Columns, Trash } from "@phosphor-icons/react";
import { type ReactNode, useEffect, useState } from "react";
@@ -181,7 +181,9 @@ function ListFieldRow({
{fieldKey}
- {def && {t(`fields.types.${def.type}`)}}
+ {def && !isReferenceFieldType(def.type) && (
+ {t(`fields.types.${def.type}`)}
+ )}
{def && sortable(def) && (
{t("listings.listFields.sortable")}
)}
diff --git a/crates/cloudhal-schema/src/generated/content_entry.rs b/crates/cloudhal-schema/src/generated/content_entry.rs
index cd081298..be42c657 100644
--- a/crates/cloudhal-schema/src/generated/content_entry.rs
+++ b/crates/cloudhal-schema/src/generated/content_entry.rs
@@ -502,7 +502,7 @@ pub struct ContentEntry {
pub cover_image: ::std::option::Option,
#[serde(rename = "createdAt")]
pub created_at: Timestamp,
- #[doc = "Collection-specific values, keyed by field. A collection with typed fields (RFC-0041, Site.collections..fields) checks each value against its definition on write; otherwise any JSON. An absent key means no value."]
+ #[doc = "Collection-specific values, keyed by field. A collection with typed fields (RFC-0041, Site.collections..fields) checks each value against its definition on write; otherwise any JSON. An absent key means no value. A reference value is a translation group id or a list of them, a repeater value a list of flat objects (RFC-0045)."]
pub fields: ::serde_json::Map<::std::string::String, ::serde_json::Value>,
pub id: Id,
pub locale: Locale,
diff --git a/crates/cloudhal-schema/src/generated/site.rs b/crates/cloudhal-schema/src/generated/site.rs
index 6b5f8aef..dc6c0179 100644
--- a/crates/cloudhal-schema/src/generated/site.rs
+++ b/crates/cloudhal-schema/src/generated/site.rs
@@ -2772,6 +2772,70 @@ pub enum FieldDefinition {
)]
required_to_publish: ::std::option::Option,
},
+ #[doc = "ReferenceField\n\nRFC-0045: entries of other (or this) collections of the same site, by translation group id (entry_…): the id of the group's original, which every language version and edition carries in translation.group."]
+ #[serde(rename = "reference")]
+ Reference {
+ #[doc = "The collections a target may belong to: declared collections of this site (checked in code)."]
+ collections: ::std::vec::Vec,
+ key: FieldKey,
+ labels: FieldLabels,
+ #[doc = "true: each language version picks its own targets; false: only the translation group's original holds them, and every version resolves them in its own language."]
+ localized: bool,
+ #[doc = "With multiple: at most this many targets. Absent: 20."]
+ #[serde(
+ rename = "maxItems",
+ skip_serializing_if = "::std::option::Option::is_none"
+ )]
+ max_items: ::std::option::Option<::std::num::NonZeroU64>,
+ #[serde(skip_serializing_if = "::std::option::Option::is_none")]
+ migration: ::std::option::Option,
+ #[doc = "The value is an ordered list of targets. Absent: false."]
+ #[serde(skip_serializing_if = "::std::option::Option::is_none")]
+ multiple: ::std::option::Option,
+ #[doc = "An entry in review or published needs at least one target. Absent: false."]
+ #[serde(
+ rename = "requiredToPublish",
+ skip_serializing_if = "::std::option::Option::is_none"
+ )]
+ required_to_publish: ::std::option::Option,
+ #[doc = "Only with one target collection: conditions on its list fields that a newly chosen target must meet (no `now`; checked in code). Narrows the picker; not re-checked when the target changes later."]
+ #[serde(
+ rename = "where",
+ default,
+ skip_serializing_if = "::std::vec::Vec::is_empty"
+ )]
+ where_: ::std::vec::Vec,
+ },
+ #[doc = "RepeaterField\n\nRFC-0045: an ordered list of items, each a flat object of sub-fields. A repeater cannot contain a repeater."]
+ #[serde(rename = "repeater")]
+ Repeater {
+ #[doc = "The sub-fields of every item, in form order. Unique keys (checked in code)."]
+ fields: ::std::vec::Vec,
+ #[doc = "The text, select or reference sub-field that names an item in Studio and approval prompts. Absent: the first such sub-field."]
+ #[serde(
+ rename = "itemLabel",
+ skip_serializing_if = "::std::option::Option::is_none"
+ )]
+ item_label: ::std::option::Option,
+ key: FieldKey,
+ labels: FieldLabels,
+ #[doc = "true: each language version has its own list (missing reads the original's); false: only the original holds the list."]
+ localized: bool,
+ #[doc = "At most this many items. Absent: 20."]
+ #[serde(
+ rename = "maxItems",
+ skip_serializing_if = "::std::option::Option::is_none"
+ )]
+ max_items: ::std::option::Option<::std::num::NonZeroU64>,
+ #[serde(skip_serializing_if = "::std::option::Option::is_none")]
+ migration: ::std::option::Option,
+ #[doc = "An entry in review or published needs at least one item. Absent: false."]
+ #[serde(
+ rename = "requiredToPublish",
+ skip_serializing_if = "::std::option::Option::is_none"
+ )]
+ required_to_publish: ::std::option::Option,
+ },
}
#[doc = "The field's key in ContentEntry.fields: lower camelCase, a JavaScript identifier. Unique in its collection; built-in entry property names are reserved (checked in code)."]
#[derive(:: serde :: Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
@@ -2979,6 +3043,9 @@ pub struct FieldMigration {
pub from_type: ::std::option::Option,
#[doc = "The operation's \"before\" snapshot."]
pub id: Id,
+ #[doc = "RFC-0045: the repeater's sub-field the operation rewrites (its key once the operation is done); fromKey and fromType then describe the sub-field."]
+ #[serde(skip_serializing_if = "::std::option::Option::is_none")]
+ pub sub: ::std::option::Option,
}
#[doc = "`FieldMigrationFromType`"]
#[derive(
@@ -3012,6 +3079,10 @@ pub enum FieldMigrationFromType {
Image,
#[serde(rename = "select")]
Select,
+ #[serde(rename = "reference")]
+ Reference,
+ #[serde(rename = "repeater")]
+ Repeater,
}
impl ::std::fmt::Display for FieldMigrationFromType {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
@@ -3025,6 +3096,8 @@ impl ::std::fmt::Display for FieldMigrationFromType {
Self::Url => f.write_str("url"),
Self::Image => f.write_str("image"),
Self::Select => f.write_str("select"),
+ Self::Reference => f.write_str("reference"),
+ Self::Repeater => f.write_str("repeater"),
}
}
}
@@ -3041,6 +3114,8 @@ impl ::std::str::FromStr for FieldMigrationFromType {
"url" => Ok(Self::Url),
"image" => Ok(Self::Image),
"select" => Ok(Self::Select),
+ "reference" => Ok(Self::Reference),
+ "repeater" => Ok(Self::Repeater),
_ => Err("invalid value".into()),
}
}
@@ -5452,6 +5527,129 @@ impl<'de> ::serde::Deserialize<'de> for StructuredDataSourceValue {
})
}
}
+#[doc = "RFC-0045: one sub-field of a repeater item, discriminated by `type`. Only types whose value is a scalar: no datetime, no multiple select or reference, no repeater. No localized and no migration: the repeater's apply."]
+#[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug, PartialEq)]
+#[serde(tag = "type", deny_unknown_fields)]
+pub enum SubFieldDefinition {
+ #[doc = "TextSubField"]
+ #[serde(rename = "text")]
+ Text {
+ key: FieldKey,
+ labels: FieldLabels,
+ #[doc = "Absent: 5000."]
+ #[serde(
+ rename = "maxLength",
+ skip_serializing_if = "::std::option::Option::is_none"
+ )]
+ max_length: ::std::option::Option<::std::num::NonZeroU64>,
+ #[doc = "Line breaks allowed. Absent: false."]
+ #[serde(skip_serializing_if = "::std::option::Option::is_none")]
+ multiline: ::std::option::Option,
+ #[doc = "Every item of an entry in review or published needs a value. Absent: false."]
+ #[serde(
+ rename = "requiredToPublish",
+ skip_serializing_if = "::std::option::Option::is_none"
+ )]
+ required_to_publish: ::std::option::Option,
+ },
+ #[doc = "RichTextSubField"]
+ #[serde(rename = "richText")]
+ RichText {
+ key: FieldKey,
+ labels: FieldLabels,
+ #[doc = "Every item of an entry in review or published needs a value. Absent: false."]
+ #[serde(
+ rename = "requiredToPublish",
+ skip_serializing_if = "::std::option::Option::is_none"
+ )]
+ required_to_publish: ::std::option::Option,
+ },
+ #[doc = "NumberSubField"]
+ #[serde(rename = "number")]
+ Number {
+ #[doc = "Whole numbers only. Absent: false."]
+ #[serde(skip_serializing_if = "::std::option::Option::is_none")]
+ integer: ::std::option::Option,
+ key: FieldKey,
+ labels: FieldLabels,
+ #[serde(skip_serializing_if = "::std::option::Option::is_none")]
+ maximum: ::std::option::Option,
+ #[serde(skip_serializing_if = "::std::option::Option::is_none")]
+ minimum: ::std::option::Option,
+ #[doc = "Every item of an entry in review or published needs a value. Absent: false."]
+ #[serde(
+ rename = "requiredToPublish",
+ skip_serializing_if = "::std::option::Option::is_none"
+ )]
+ required_to_publish: ::std::option::Option,
+ },
+ #[doc = "BooleanSubField"]
+ #[serde(rename = "boolean")]
+ Boolean { key: FieldKey, labels: FieldLabels },
+ #[doc = "DateSubField"]
+ #[serde(rename = "date")]
+ Date {
+ key: FieldKey,
+ labels: FieldLabels,
+ #[doc = "Every item of an entry in review or published needs a value. Absent: false."]
+ #[serde(
+ rename = "requiredToPublish",
+ skip_serializing_if = "::std::option::Option::is_none"
+ )]
+ required_to_publish: ::std::option::Option,
+ },
+ #[doc = "UrlSubField"]
+ #[serde(rename = "url")]
+ Url {
+ key: FieldKey,
+ labels: FieldLabels,
+ #[doc = "Every item of an entry in review or published needs a value. Absent: false."]
+ #[serde(
+ rename = "requiredToPublish",
+ skip_serializing_if = "::std::option::Option::is_none"
+ )]
+ required_to_publish: ::std::option::Option,
+ },
+ #[doc = "ImageSubField"]
+ #[serde(rename = "image")]
+ Image {
+ key: FieldKey,
+ labels: FieldLabels,
+ #[doc = "Every item of an entry in review or published needs a value. Absent: false."]
+ #[serde(
+ rename = "requiredToPublish",
+ skip_serializing_if = "::std::option::Option::is_none"
+ )]
+ required_to_publish: ::std::option::Option,
+ },
+ #[doc = "SelectSubField"]
+ #[serde(rename = "select")]
+ Select {
+ key: FieldKey,
+ labels: FieldLabels,
+ options: ::std::vec::Vec,
+ #[doc = "Every item of an entry in review or published needs a value. Absent: false."]
+ #[serde(
+ rename = "requiredToPublish",
+ skip_serializing_if = "::std::option::Option::is_none"
+ )]
+ required_to_publish: ::std::option::Option,
+ },
+ #[doc = "ReferenceSubField"]
+ #[serde(rename = "reference")]
+ Reference {
+ #[doc = "The collections a target may belong to (checked in code)."]
+ collections: ::std::vec::Vec,
+ key: FieldKey,
+ labels: FieldLabels,
+ #[doc = "Every item of an entry in review or published needs a value. Absent: false."]
+ #[serde(
+ rename = "requiredToPublish",
+ skip_serializing_if = "::std::option::Option::is_none"
+ )]
+ required_to_publish: ::std::option::Option,
+ },
+}
#[doc = "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."]
#[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug, PartialEq)]
#[serde(deny_unknown_fields)]
diff --git a/packages/api/src/errors.ts b/packages/api/src/errors.ts
index 8f040fe2..1e99617e 100644
--- a/packages/api/src/errors.ts
+++ b/packages/api/src/errors.ts
@@ -91,6 +91,11 @@ export const INVARIANT_STATUS: Record = new Set([
+ "gates_unsupported",
+ "invalid_availability",
+ "fields_unsupported",
+]);
+
export function syncAdminRoutes(deps: AdminDeps): Hono {
const { storage } = deps;
return new Hono()
@@ -144,12 +154,7 @@ export function syncAdminRoutes(deps: AdminDeps): Hono {
{ error: "conflict", message: e.message, details: { differing: e.differing } },
409,
);
- // RFC-0008: refused gates keep their code (409 gates_unsupported, 400 invalid_availability).
- if (
- e instanceof InvariantError &&
- (e.code === "gates_unsupported" || e.code === "invalid_availability")
- )
- throw e;
+ if (e instanceof InvariantError && KEPT_CODES.has(e.code)) throw e;
if (e instanceof Error) throw badRequest(e.message);
throw e;
}
diff --git a/packages/api/src/testing-fields.ts b/packages/api/src/testing-fields.ts
index d3a0890c..93d0c437 100644
--- a/packages/api/src/testing-fields.ts
+++ b/packages/api/src/testing-fields.ts
@@ -161,6 +161,38 @@ export function describeFieldsApi(label: string, factory: ApiStoreFactory): void
expect(made.body.fields).toEqual(loose);
});
+ it("refuses reference and repeater definitions until they are enabled (RFC-0045, 409)", async () => {
+ const venue = {
+ key: "venue",
+ type: "reference",
+ labels: { en: { label: "Venue" } },
+ localized: false,
+ collections: ["posts"],
+ };
+ const speakers = {
+ key: "speakers",
+ type: "repeater",
+ labels: { en: { label: "Speakers" } },
+ localized: false,
+ fields: [{ key: "name", type: "text", labels: { en: { label: "Name" } } }],
+ };
+ for (const def of [venue, speakers])
+ for (const query of ["?dryRun=1", ""]) {
+ const refused = await put({ fields: [def] }, query);
+ expect(refused.status, JSON.stringify(refused.body)).toBe(409);
+ expect(refused.body.error).toBe("fields_unsupported");
+ }
+ expect((await site()).collections?.events).toBeUndefined();
+ // and no field operation turns a field into one (the API takes the nine types only)
+ expect((await put(EVENTS)).status).toBe(200);
+ const ops = `/api/sites/${siteId}/collections/events/fields/operations`;
+ const retype = await call(ops, {
+ method: "POST",
+ body: JSON.stringify({ op: "change_field_type", key: "venue", type: "reference" }),
+ });
+ expect(retype.status).toBe(400);
+ });
+
it("field operations: dry run, run, status, continue and revert (RFC-0041 §6)", async () => {
expect((await put(EVENTS)).status).toBe(200);
const made = await create({ venue: "Hall", level: "expert" });
diff --git a/packages/api/test/admin.test.ts b/packages/api/test/admin.test.ts
index 2979a89a..9cbedb2a 100644
--- a/packages/api/test/admin.test.ts
+++ b/packages/api/test/admin.test.ts
@@ -1,5 +1,12 @@
/** `cloudhal push`/`pull` endpoints and the /mcp origin guard. */
-import { SiteGraphStore, seedStarterSite } from "@cloudhal/core-ts";
+import type { Site } from "@cloudhal/core-contracts";
+import {
+ canonicalJson,
+ exportSite,
+ nodeHash,
+ SiteGraphStore,
+ seedStarterSite,
+} from "@cloudhal/core-ts";
import { makePng } from "@cloudhal/core-ts/media/fixtures";
import { MemoryBlobStore } from "@cloudhal/storage";
import { BetterSqlite3Driver } from "@cloudhal/storage/sqlite";
@@ -269,6 +276,53 @@ describe("admin bundle endpoints", () => {
expect((await put(bundle.replace(site.id, "site_other0000001"))).status).toBe(400);
expect((await put(bundle, "?mode=yolo")).status).toBe(400);
});
+
+ it("refuses a pushed reference or repeater definition with 409 fields_unsupported (RFC-0045)", async () => {
+ const a = await backend();
+ const b = await backend();
+ const site = await seedStarterSite(a.store, { name: "Pushed", slug: "pushed" });
+ const app = createApi({ store: b.store, admin: { deployToken: TOKEN, storage: b } });
+ const bundle = await exportSite(a, site.id);
+ const row = bundle.nodes.find((n) => n.kind === "site");
+ if (!row) throw new Error("no site row");
+ const label = { en: { label: "Venue" } };
+ const body = {
+ ...(JSON.parse(row.body) as Site),
+ collections: {
+ places: {},
+ events: {
+ fields: [
+ {
+ key: "venue",
+ type: "reference",
+ labels: label,
+ localized: false,
+ collections: ["places"],
+ },
+ ],
+ },
+ },
+ } as Site;
+ row.body = canonicalJson(body);
+ row.hash = await nodeHash(body);
+ for (const query of ["", "?mode=replace"]) {
+ const res = await app.request(`/api/admin/sites/${site.id}/bundle${query}`, {
+ method: "PUT",
+ headers: { authorization: `Bearer ${TOKEN}`, "content-type": "application/json" },
+ body: JSON.stringify(bundle),
+ });
+ expect(res.status, query).toBe(409);
+ const answer = (await res.json()) as {
+ error: string;
+ details: { issues: { path: string }[] };
+ };
+ expect(answer.error).toBe("fields_unsupported");
+ expect(answer.details.issues.map((i) => i.path)).toEqual([
+ "collections/events/fields/0/type",
+ ]);
+ }
+ expect(await b.store.getSite(site.id)).toBeUndefined();
+ });
});
describe("short deploy token", () => {
diff --git a/packages/cli/src/commands/fields.ts b/packages/cli/src/commands/fields.ts
index 8910069c..74e8574f 100644
--- a/packages/cli/src/commands/fields.ts
+++ b/packages/cli/src/commands/fields.ts
@@ -17,7 +17,7 @@ import {
revertFieldOperation,
runFieldOperation,
} from "@cloudhal/core-ts";
-import { FIELD_TYPES, type FieldType } from "@cloudhal/core-ts/fields";
+import { type EnabledFieldType, FIELD_TYPES, type FieldType } from "@cloudhal/core-ts/fields";
import { openLocalStore } from "@cloudhal/core-ts/local";
import { humanizeSlug } from "@cloudhal/core-ts/names";
import type { CliCommand } from "../cli/types.ts";
@@ -90,7 +90,7 @@ export function parseFieldsArgs(
};
case "retype": {
need(a, b);
- if (!FIELD_TYPES.includes(b as FieldType))
+ if (!FIELD_TYPES.includes(b as EnabledFieldType))
throw new Error(`the type is one of ${FIELD_TYPES.join(", ")}`);
const op: FieldOperation = {
op: "change_field_type",
diff --git a/packages/core-contracts/fixtures/invalid/site/field-migration-from-blocks.json b/packages/core-contracts/fixtures/invalid/site/field-migration-from-blocks.json
new file mode 100644
index 00000000..5eee2472
--- /dev/null
+++ b/packages/core-contracts/fixtures/invalid/site/field-migration-from-blocks.json
@@ -0,0 +1,97 @@
+{
+ "reason": "a migration's fromType is one of the eleven types",
+ "data": {
+ "schemaVersion": 1,
+ "id": "site_01HZX8K3Q9T1",
+ "projectId": "proj_01HZX8K3Q9AB",
+ "name": "Events",
+ "slug": "events",
+ "defaultLocale": "en",
+ "locales": ["en", "tr"],
+ "domains": [],
+ "pageIds": [],
+ "theme": {
+ "name": "blog"
+ },
+ "rev": 0,
+ "createdAt": "2026-11-11T09:00:00Z",
+ "updatedAt": "2026-11-11T09:00:00Z",
+ "collections": {
+ "events": {
+ "fields": [
+ {
+ "key": "speakers",
+ "type": "repeater",
+ "labels": {
+ "en": {
+ "label": "Speakers"
+ }
+ },
+ "localized": false,
+ "maxItems": 20,
+ "itemLabel": "person",
+ "fields": [
+ {
+ "key": "person",
+ "type": "reference",
+ "labels": {
+ "en": {
+ "label": "Person"
+ }
+ },
+ "requiredToPublish": true,
+ "collections": ["people"]
+ },
+ {
+ "key": "role",
+ "type": "select",
+ "labels": {
+ "en": {
+ "label": "Role"
+ }
+ },
+ "options": [
+ {
+ "value": "host",
+ "labels": {
+ "en": "Host"
+ }
+ },
+ {
+ "value": "speaker",
+ "labels": {
+ "en": "Speaker"
+ }
+ }
+ ]
+ },
+ {
+ "key": "photo",
+ "type": "image",
+ "labels": {
+ "en": {
+ "label": "Photo"
+ }
+ }
+ },
+ {
+ "key": "talk",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "Talk"
+ }
+ },
+ "maxLength": 200
+ }
+ ],
+ "migration": {
+ "id": "snap_01HZX8K3Q9MG",
+ "fromType": "blocks"
+ }
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/packages/core-contracts/fixtures/invalid/site/field-migration-unknown-type.json b/packages/core-contracts/fixtures/invalid/site/field-migration-unknown-type.json
index be54012c..cf110278 100644
--- a/packages/core-contracts/fixtures/invalid/site/field-migration-unknown-type.json
+++ b/packages/core-contracts/fixtures/invalid/site/field-migration-unknown-type.json
@@ -1,5 +1,5 @@
{
- "reason": "a migration's fromType is one of the nine types",
+ "reason": "a migration's fromType is one of the eleven types",
"data": {
"schemaVersion": 1,
"id": "site_01HZX8K3Q9T1",
diff --git a/packages/core-contracts/fixtures/invalid/site/field-reference-duplicate-collection.json b/packages/core-contracts/fixtures/invalid/site/field-reference-duplicate-collection.json
new file mode 100644
index 00000000..79258952
--- /dev/null
+++ b/packages/core-contracts/fixtures/invalid/site/field-reference-duplicate-collection.json
@@ -0,0 +1,37 @@
+{
+ "reason": "a reference field names each target collection once",
+ "data": {
+ "schemaVersion": 1,
+ "id": "site_01HZX8K3Q9T1",
+ "projectId": "proj_01HZX8K3Q9AB",
+ "name": "Events",
+ "slug": "events",
+ "defaultLocale": "en",
+ "locales": ["en", "tr"],
+ "domains": [],
+ "pageIds": [],
+ "theme": {
+ "name": "blog"
+ },
+ "rev": 0,
+ "createdAt": "2026-11-11T09:00:00Z",
+ "updatedAt": "2026-11-11T09:00:00Z",
+ "collections": {
+ "events": {
+ "fields": [
+ {
+ "key": "venue",
+ "type": "reference",
+ "labels": {
+ "en": {
+ "label": "Venue"
+ }
+ },
+ "localized": false,
+ "collections": ["places", "places"]
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/packages/core-contracts/fixtures/invalid/site/field-reference-max-items-51.json b/packages/core-contracts/fixtures/invalid/site/field-reference-max-items-51.json
new file mode 100644
index 00000000..4ae9a3af
--- /dev/null
+++ b/packages/core-contracts/fixtures/invalid/site/field-reference-max-items-51.json
@@ -0,0 +1,39 @@
+{
+ "reason": "a reference field holds at most fifty targets",
+ "data": {
+ "schemaVersion": 1,
+ "id": "site_01HZX8K3Q9T1",
+ "projectId": "proj_01HZX8K3Q9AB",
+ "name": "Events",
+ "slug": "events",
+ "defaultLocale": "en",
+ "locales": ["en", "tr"],
+ "domains": [],
+ "pageIds": [],
+ "theme": {
+ "name": "blog"
+ },
+ "rev": 0,
+ "createdAt": "2026-11-11T09:00:00Z",
+ "updatedAt": "2026-11-11T09:00:00Z",
+ "collections": {
+ "events": {
+ "fields": [
+ {
+ "key": "venue",
+ "type": "reference",
+ "labels": {
+ "en": {
+ "label": "Venue"
+ }
+ },
+ "localized": false,
+ "collections": ["places"],
+ "multiple": true,
+ "maxItems": 51
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/packages/core-contracts/fixtures/invalid/site/field-reference-six-collections.json b/packages/core-contracts/fixtures/invalid/site/field-reference-six-collections.json
new file mode 100644
index 00000000..82a285c0
--- /dev/null
+++ b/packages/core-contracts/fixtures/invalid/site/field-reference-six-collections.json
@@ -0,0 +1,37 @@
+{
+ "reason": "a reference field allows at most five target collections",
+ "data": {
+ "schemaVersion": 1,
+ "id": "site_01HZX8K3Q9T1",
+ "projectId": "proj_01HZX8K3Q9AB",
+ "name": "Events",
+ "slug": "events",
+ "defaultLocale": "en",
+ "locales": ["en", "tr"],
+ "domains": [],
+ "pageIds": [],
+ "theme": {
+ "name": "blog"
+ },
+ "rev": 0,
+ "createdAt": "2026-11-11T09:00:00Z",
+ "updatedAt": "2026-11-11T09:00:00Z",
+ "collections": {
+ "events": {
+ "fields": [
+ {
+ "key": "venue",
+ "type": "reference",
+ "labels": {
+ "en": {
+ "label": "Venue"
+ }
+ },
+ "localized": false,
+ "collections": ["a", "b", "c", "d", "e", "f"]
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/packages/core-contracts/fixtures/invalid/site/field-reference-without-collections.json b/packages/core-contracts/fixtures/invalid/site/field-reference-without-collections.json
new file mode 100644
index 00000000..08659281
--- /dev/null
+++ b/packages/core-contracts/fixtures/invalid/site/field-reference-without-collections.json
@@ -0,0 +1,36 @@
+{
+ "reason": "a reference field names its target collections",
+ "data": {
+ "schemaVersion": 1,
+ "id": "site_01HZX8K3Q9T1",
+ "projectId": "proj_01HZX8K3Q9AB",
+ "name": "Events",
+ "slug": "events",
+ "defaultLocale": "en",
+ "locales": ["en", "tr"],
+ "domains": [],
+ "pageIds": [],
+ "theme": {
+ "name": "blog"
+ },
+ "rev": 0,
+ "createdAt": "2026-11-11T09:00:00Z",
+ "updatedAt": "2026-11-11T09:00:00Z",
+ "collections": {
+ "events": {
+ "fields": [
+ {
+ "key": "venue",
+ "type": "reference",
+ "labels": {
+ "en": {
+ "label": "Venue"
+ }
+ },
+ "localized": false
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/packages/core-contracts/fixtures/invalid/site/field-repeater-datetime-sub-field.json b/packages/core-contracts/fixtures/invalid/site/field-repeater-datetime-sub-field.json
new file mode 100644
index 00000000..48fcfe2d
--- /dev/null
+++ b/packages/core-contracts/fixtures/invalid/site/field-repeater-datetime-sub-field.json
@@ -0,0 +1,102 @@
+{
+ "reason": "a repeater sub-field is a scalar: no datetime",
+ "data": {
+ "schemaVersion": 1,
+ "id": "site_01HZX8K3Q9T1",
+ "projectId": "proj_01HZX8K3Q9AB",
+ "name": "Events",
+ "slug": "events",
+ "defaultLocale": "en",
+ "locales": ["en", "tr"],
+ "domains": [],
+ "pageIds": [],
+ "theme": {
+ "name": "blog"
+ },
+ "rev": 0,
+ "createdAt": "2026-11-11T09:00:00Z",
+ "updatedAt": "2026-11-11T09:00:00Z",
+ "collections": {
+ "events": {
+ "fields": [
+ {
+ "key": "speakers",
+ "type": "repeater",
+ "labels": {
+ "en": {
+ "label": "Speakers"
+ }
+ },
+ "localized": false,
+ "maxItems": 20,
+ "itemLabel": "person",
+ "fields": [
+ {
+ "key": "person",
+ "type": "reference",
+ "labels": {
+ "en": {
+ "label": "Person"
+ }
+ },
+ "requiredToPublish": true,
+ "collections": ["people"]
+ },
+ {
+ "key": "role",
+ "type": "select",
+ "labels": {
+ "en": {
+ "label": "Role"
+ }
+ },
+ "options": [
+ {
+ "value": "host",
+ "labels": {
+ "en": "Host"
+ }
+ },
+ {
+ "value": "speaker",
+ "labels": {
+ "en": "Speaker"
+ }
+ }
+ ]
+ },
+ {
+ "key": "photo",
+ "type": "image",
+ "labels": {
+ "en": {
+ "label": "Photo"
+ }
+ }
+ },
+ {
+ "key": "talk",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "Talk"
+ }
+ },
+ "maxLength": 200
+ },
+ {
+ "key": "at",
+ "type": "datetime",
+ "labels": {
+ "en": {
+ "label": "At"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/packages/core-contracts/fixtures/invalid/site/field-repeater-eleven-sub-fields.json b/packages/core-contracts/fixtures/invalid/site/field-repeater-eleven-sub-fields.json
new file mode 100644
index 00000000..e9e548e9
--- /dev/null
+++ b/packages/core-contracts/fixtures/invalid/site/field-repeater-eleven-sub-fields.json
@@ -0,0 +1,139 @@
+{
+ "reason": "a repeater has at most ten sub-fields",
+ "data": {
+ "schemaVersion": 1,
+ "id": "site_01HZX8K3Q9T1",
+ "projectId": "proj_01HZX8K3Q9AB",
+ "name": "Events",
+ "slug": "events",
+ "defaultLocale": "en",
+ "locales": ["en", "tr"],
+ "domains": [],
+ "pageIds": [],
+ "theme": {
+ "name": "blog"
+ },
+ "rev": 0,
+ "createdAt": "2026-11-11T09:00:00Z",
+ "updatedAt": "2026-11-11T09:00:00Z",
+ "collections": {
+ "events": {
+ "fields": [
+ {
+ "key": "speakers",
+ "type": "repeater",
+ "labels": {
+ "en": {
+ "label": "Speakers"
+ }
+ },
+ "localized": false,
+ "maxItems": 20,
+ "itemLabel": "person",
+ "fields": [
+ {
+ "key": "sub0",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "Sub 0"
+ }
+ }
+ },
+ {
+ "key": "sub1",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "Sub 1"
+ }
+ }
+ },
+ {
+ "key": "sub2",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "Sub 2"
+ }
+ }
+ },
+ {
+ "key": "sub3",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "Sub 3"
+ }
+ }
+ },
+ {
+ "key": "sub4",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "Sub 4"
+ }
+ }
+ },
+ {
+ "key": "sub5",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "Sub 5"
+ }
+ }
+ },
+ {
+ "key": "sub6",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "Sub 6"
+ }
+ }
+ },
+ {
+ "key": "sub7",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "Sub 7"
+ }
+ }
+ },
+ {
+ "key": "sub8",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "Sub 8"
+ }
+ }
+ },
+ {
+ "key": "sub9",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "Sub 9"
+ }
+ }
+ },
+ {
+ "key": "sub10",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "Sub 10"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/packages/core-contracts/fixtures/invalid/site/field-repeater-max-items-51.json b/packages/core-contracts/fixtures/invalid/site/field-repeater-max-items-51.json
new file mode 100644
index 00000000..d5716257
--- /dev/null
+++ b/packages/core-contracts/fixtures/invalid/site/field-repeater-max-items-51.json
@@ -0,0 +1,93 @@
+{
+ "reason": "a repeater holds at most fifty items",
+ "data": {
+ "schemaVersion": 1,
+ "id": "site_01HZX8K3Q9T1",
+ "projectId": "proj_01HZX8K3Q9AB",
+ "name": "Events",
+ "slug": "events",
+ "defaultLocale": "en",
+ "locales": ["en", "tr"],
+ "domains": [],
+ "pageIds": [],
+ "theme": {
+ "name": "blog"
+ },
+ "rev": 0,
+ "createdAt": "2026-11-11T09:00:00Z",
+ "updatedAt": "2026-11-11T09:00:00Z",
+ "collections": {
+ "events": {
+ "fields": [
+ {
+ "key": "speakers",
+ "type": "repeater",
+ "labels": {
+ "en": {
+ "label": "Speakers"
+ }
+ },
+ "localized": false,
+ "maxItems": 51,
+ "itemLabel": "person",
+ "fields": [
+ {
+ "key": "person",
+ "type": "reference",
+ "labels": {
+ "en": {
+ "label": "Person"
+ }
+ },
+ "requiredToPublish": true,
+ "collections": ["people"]
+ },
+ {
+ "key": "role",
+ "type": "select",
+ "labels": {
+ "en": {
+ "label": "Role"
+ }
+ },
+ "options": [
+ {
+ "value": "host",
+ "labels": {
+ "en": "Host"
+ }
+ },
+ {
+ "value": "speaker",
+ "labels": {
+ "en": "Speaker"
+ }
+ }
+ ]
+ },
+ {
+ "key": "photo",
+ "type": "image",
+ "labels": {
+ "en": {
+ "label": "Photo"
+ }
+ }
+ },
+ {
+ "key": "talk",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "Talk"
+ }
+ },
+ "maxLength": 200
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/packages/core-contracts/fixtures/invalid/site/field-repeater-nested-repeater.json b/packages/core-contracts/fixtures/invalid/site/field-repeater-nested-repeater.json
new file mode 100644
index 00000000..fb2feb9a
--- /dev/null
+++ b/packages/core-contracts/fixtures/invalid/site/field-repeater-nested-repeater.json
@@ -0,0 +1,113 @@
+{
+ "reason": "a repeater cannot contain a repeater",
+ "data": {
+ "schemaVersion": 1,
+ "id": "site_01HZX8K3Q9T1",
+ "projectId": "proj_01HZX8K3Q9AB",
+ "name": "Events",
+ "slug": "events",
+ "defaultLocale": "en",
+ "locales": ["en", "tr"],
+ "domains": [],
+ "pageIds": [],
+ "theme": {
+ "name": "blog"
+ },
+ "rev": 0,
+ "createdAt": "2026-11-11T09:00:00Z",
+ "updatedAt": "2026-11-11T09:00:00Z",
+ "collections": {
+ "events": {
+ "fields": [
+ {
+ "key": "speakers",
+ "type": "repeater",
+ "labels": {
+ "en": {
+ "label": "Speakers"
+ }
+ },
+ "localized": false,
+ "maxItems": 20,
+ "itemLabel": "person",
+ "fields": [
+ {
+ "key": "person",
+ "type": "reference",
+ "labels": {
+ "en": {
+ "label": "Person"
+ }
+ },
+ "requiredToPublish": true,
+ "collections": ["people"]
+ },
+ {
+ "key": "role",
+ "type": "select",
+ "labels": {
+ "en": {
+ "label": "Role"
+ }
+ },
+ "options": [
+ {
+ "value": "host",
+ "labels": {
+ "en": "Host"
+ }
+ },
+ {
+ "value": "speaker",
+ "labels": {
+ "en": "Speaker"
+ }
+ }
+ ]
+ },
+ {
+ "key": "photo",
+ "type": "image",
+ "labels": {
+ "en": {
+ "label": "Photo"
+ }
+ }
+ },
+ {
+ "key": "talk",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "Talk"
+ }
+ },
+ "maxLength": 200
+ },
+ {
+ "key": "inner",
+ "type": "repeater",
+ "labels": {
+ "en": {
+ "label": "Inner"
+ }
+ },
+ "fields": [
+ {
+ "key": "x",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "X"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/packages/core-contracts/fixtures/invalid/site/field-repeater-sub-field-localized.json b/packages/core-contracts/fixtures/invalid/site/field-repeater-sub-field-localized.json
new file mode 100644
index 00000000..1dc7f096
--- /dev/null
+++ b/packages/core-contracts/fixtures/invalid/site/field-repeater-sub-field-localized.json
@@ -0,0 +1,103 @@
+{
+ "reason": "a sub-field has no localized of its own: the repeater's applies",
+ "data": {
+ "schemaVersion": 1,
+ "id": "site_01HZX8K3Q9T1",
+ "projectId": "proj_01HZX8K3Q9AB",
+ "name": "Events",
+ "slug": "events",
+ "defaultLocale": "en",
+ "locales": ["en", "tr"],
+ "domains": [],
+ "pageIds": [],
+ "theme": {
+ "name": "blog"
+ },
+ "rev": 0,
+ "createdAt": "2026-11-11T09:00:00Z",
+ "updatedAt": "2026-11-11T09:00:00Z",
+ "collections": {
+ "events": {
+ "fields": [
+ {
+ "key": "speakers",
+ "type": "repeater",
+ "labels": {
+ "en": {
+ "label": "Speakers"
+ }
+ },
+ "localized": false,
+ "maxItems": 20,
+ "itemLabel": "person",
+ "fields": [
+ {
+ "key": "person",
+ "type": "reference",
+ "labels": {
+ "en": {
+ "label": "Person"
+ }
+ },
+ "requiredToPublish": true,
+ "collections": ["people"]
+ },
+ {
+ "key": "role",
+ "type": "select",
+ "labels": {
+ "en": {
+ "label": "Role"
+ }
+ },
+ "options": [
+ {
+ "value": "host",
+ "labels": {
+ "en": "Host"
+ }
+ },
+ {
+ "value": "speaker",
+ "labels": {
+ "en": "Speaker"
+ }
+ }
+ ]
+ },
+ {
+ "key": "photo",
+ "type": "image",
+ "labels": {
+ "en": {
+ "label": "Photo"
+ }
+ }
+ },
+ {
+ "key": "talk",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "Talk"
+ }
+ },
+ "maxLength": 200
+ },
+ {
+ "key": "bio",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "Bio"
+ }
+ },
+ "localized": true
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/packages/core-contracts/fixtures/invalid/site/field-repeater-sub-field-multiple.json b/packages/core-contracts/fixtures/invalid/site/field-repeater-sub-field-multiple.json
new file mode 100644
index 00000000..fad2aa60
--- /dev/null
+++ b/packages/core-contracts/fixtures/invalid/site/field-repeater-sub-field-multiple.json
@@ -0,0 +1,111 @@
+{
+ "reason": "a sub-field cannot be a multiple select",
+ "data": {
+ "schemaVersion": 1,
+ "id": "site_01HZX8K3Q9T1",
+ "projectId": "proj_01HZX8K3Q9AB",
+ "name": "Events",
+ "slug": "events",
+ "defaultLocale": "en",
+ "locales": ["en", "tr"],
+ "domains": [],
+ "pageIds": [],
+ "theme": {
+ "name": "blog"
+ },
+ "rev": 0,
+ "createdAt": "2026-11-11T09:00:00Z",
+ "updatedAt": "2026-11-11T09:00:00Z",
+ "collections": {
+ "events": {
+ "fields": [
+ {
+ "key": "speakers",
+ "type": "repeater",
+ "labels": {
+ "en": {
+ "label": "Speakers"
+ }
+ },
+ "localized": false,
+ "maxItems": 20,
+ "itemLabel": "person",
+ "fields": [
+ {
+ "key": "person",
+ "type": "reference",
+ "labels": {
+ "en": {
+ "label": "Person"
+ }
+ },
+ "requiredToPublish": true,
+ "collections": ["people"]
+ },
+ {
+ "key": "role",
+ "type": "select",
+ "labels": {
+ "en": {
+ "label": "Role"
+ }
+ },
+ "options": [
+ {
+ "value": "host",
+ "labels": {
+ "en": "Host"
+ }
+ },
+ {
+ "value": "speaker",
+ "labels": {
+ "en": "Speaker"
+ }
+ }
+ ]
+ },
+ {
+ "key": "photo",
+ "type": "image",
+ "labels": {
+ "en": {
+ "label": "Photo"
+ }
+ }
+ },
+ {
+ "key": "talk",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "Talk"
+ }
+ },
+ "maxLength": 200
+ },
+ {
+ "key": "tags",
+ "type": "select",
+ "labels": {
+ "en": {
+ "label": "Tags"
+ }
+ },
+ "multiple": true,
+ "options": [
+ {
+ "value": "a",
+ "labels": {
+ "en": "A"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/packages/core-contracts/fixtures/invalid/site/field-repeater-without-fields.json b/packages/core-contracts/fixtures/invalid/site/field-repeater-without-fields.json
new file mode 100644
index 00000000..06e51887
--- /dev/null
+++ b/packages/core-contracts/fixtures/invalid/site/field-repeater-without-fields.json
@@ -0,0 +1,39 @@
+{
+ "reason": "a repeater has at least one sub-field",
+ "data": {
+ "schemaVersion": 1,
+ "id": "site_01HZX8K3Q9T1",
+ "projectId": "proj_01HZX8K3Q9AB",
+ "name": "Events",
+ "slug": "events",
+ "defaultLocale": "en",
+ "locales": ["en", "tr"],
+ "domains": [],
+ "pageIds": [],
+ "theme": {
+ "name": "blog"
+ },
+ "rev": 0,
+ "createdAt": "2026-11-11T09:00:00Z",
+ "updatedAt": "2026-11-11T09:00:00Z",
+ "collections": {
+ "events": {
+ "fields": [
+ {
+ "key": "speakers",
+ "type": "repeater",
+ "labels": {
+ "en": {
+ "label": "Speakers"
+ }
+ },
+ "localized": false,
+ "maxItems": 20,
+ "itemLabel": "person",
+ "fields": []
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/packages/core-contracts/fixtures/valid/site/collection-stage4.json b/packages/core-contracts/fixtures/valid/site/collection-stage4.json
new file mode 100644
index 00000000..f2252a57
--- /dev/null
+++ b/packages/core-contracts/fixtures/valid/site/collection-stage4.json
@@ -0,0 +1,253 @@
+{
+ "schemaVersion": 1,
+ "id": "site_01HZX8K3Q9T1",
+ "projectId": "proj_01HZX8K3Q9AB",
+ "name": "Events",
+ "slug": "events",
+ "defaultLocale": "en",
+ "locales": ["en", "tr"],
+ "domains": [],
+ "pageIds": [],
+ "theme": {
+ "name": "blog"
+ },
+ "rev": 0,
+ "createdAt": "2026-11-11T09:00:00Z",
+ "updatedAt": "2026-11-11T09:00:00Z",
+ "collections": {
+ "events": {
+ "fields": [
+ {
+ "key": "venue",
+ "type": "reference",
+ "labels": {
+ "en": {
+ "label": "Venue"
+ }
+ },
+ "localized": false,
+ "collections": ["places"]
+ },
+ {
+ "key": "partners",
+ "type": "reference",
+ "labels": {
+ "en": {
+ "label": "Partners"
+ }
+ },
+ "localized": true,
+ "collections": ["places", "people"],
+ "multiple": true,
+ "maxItems": 50,
+ "requiredToPublish": true
+ },
+ {
+ "key": "host",
+ "type": "reference",
+ "labels": {
+ "en": {
+ "label": "Host"
+ }
+ },
+ "localized": false,
+ "collections": ["people"],
+ "where": [
+ {
+ "field": "role",
+ "op": "eq",
+ "value": "speaker"
+ }
+ ]
+ },
+ {
+ "key": "speakers",
+ "type": "repeater",
+ "labels": {
+ "en": {
+ "label": "Speakers"
+ }
+ },
+ "localized": false,
+ "maxItems": 20,
+ "itemLabel": "person",
+ "fields": [
+ {
+ "key": "person",
+ "type": "reference",
+ "labels": {
+ "en": {
+ "label": "Person"
+ }
+ },
+ "requiredToPublish": true,
+ "collections": ["people"]
+ },
+ {
+ "key": "role",
+ "type": "select",
+ "labels": {
+ "en": {
+ "label": "Role"
+ }
+ },
+ "options": [
+ {
+ "value": "host",
+ "labels": {
+ "en": "Host"
+ }
+ },
+ {
+ "value": "speaker",
+ "labels": {
+ "en": "Speaker"
+ }
+ }
+ ]
+ },
+ {
+ "key": "photo",
+ "type": "image",
+ "labels": {
+ "en": {
+ "label": "Photo"
+ }
+ }
+ },
+ {
+ "key": "talk",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "Talk"
+ }
+ },
+ "maxLength": 200
+ }
+ ]
+ },
+ {
+ "key": "agenda",
+ "type": "repeater",
+ "labels": {
+ "en": {
+ "label": "Agenda"
+ }
+ },
+ "localized": true,
+ "fields": [
+ {
+ "key": "heading",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "Title"
+ }
+ },
+ "multiline": true,
+ "requiredToPublish": true
+ },
+ {
+ "key": "notes",
+ "type": "richText",
+ "labels": {
+ "en": {
+ "label": "Notes"
+ }
+ }
+ },
+ {
+ "key": "minutes",
+ "type": "number",
+ "labels": {
+ "en": {
+ "label": "Minutes"
+ }
+ },
+ "integer": true,
+ "minimum": 0.5,
+ "maximum": 240.5
+ },
+ {
+ "key": "break",
+ "type": "boolean",
+ "labels": {
+ "en": {
+ "label": "Break"
+ }
+ }
+ },
+ {
+ "key": "day",
+ "type": "date",
+ "labels": {
+ "en": {
+ "label": "Day"
+ }
+ }
+ },
+ {
+ "key": "slides",
+ "type": "url",
+ "labels": {
+ "en": {
+ "label": "Slides"
+ }
+ }
+ }
+ ],
+ "migration": {
+ "id": "snap_01HZX8K3Q9MG",
+ "fromKey": "program",
+ "fromType": "text",
+ "sub": "heading"
+ }
+ }
+ ]
+ },
+ "places": {
+ "fields": [
+ {
+ "key": "street",
+ "type": "text",
+ "labels": {
+ "en": {
+ "label": "Street"
+ }
+ },
+ "localized": false
+ }
+ ]
+ },
+ "people": {
+ "fields": [
+ {
+ "key": "role",
+ "type": "select",
+ "labels": {
+ "en": {
+ "label": "Role"
+ }
+ },
+ "localized": false,
+ "options": [
+ {
+ "value": "speaker",
+ "labels": {
+ "en": "Speaker"
+ }
+ },
+ {
+ "value": "staff",
+ "labels": {
+ "en": "Staff"
+ }
+ }
+ ]
+ }
+ ],
+ "listFields": ["role"]
+ }
+ }
+}
diff --git a/packages/core-contracts/schemas/content-entry.schema.json b/packages/core-contracts/schemas/content-entry.schema.json
index 09035ea4..c4b7e782 100644
--- a/packages/core-contracts/schemas/content-entry.schema.json
+++ b/packages/core-contracts/schemas/content-entry.schema.json
@@ -35,7 +35,7 @@
"body": { "$ref": "#/$defs/Body" },
"fields": {
"type": "object",
- "description": "Collection-specific values, keyed by field. A collection with typed fields (RFC-0041, Site.collections..fields) checks each value against its definition on write; otherwise any JSON. An absent key means no value.",
+ "description": "Collection-specific values, keyed by field. A collection with typed fields (RFC-0041, Site.collections..fields) checks each value against its definition on write; otherwise any JSON. An absent key means no value. A reference value is a translation group id or a list of them, a repeater value a list of flat objects (RFC-0045).",
"additionalProperties": true
},
"tags": {
diff --git a/packages/core-contracts/schemas/site.schema.json b/packages/core-contracts/schemas/site.schema.json
index c58400bc..8f156565 100644
--- a/packages/core-contracts/schemas/site.schema.json
+++ b/packages/core-contracts/schemas/site.schema.json
@@ -579,6 +579,351 @@
"description": "The value is a list of option values. Absent: false."
}
}
+ },
+ {
+ "title": "ReferenceField",
+ "type": "object",
+ "description": "RFC-0045: entries of other (or this) collections of the same site, by translation group id (entry_…): the id of the group's original, which every language version and edition carries in translation.group.",
+ "additionalProperties": false,
+ "required": ["key", "type", "labels", "localized", "collections"],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": ["reference"]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ },
+ "localized": {
+ "type": "boolean",
+ "description": "true: each language version picks its own targets; false: only the translation group's original holds them, and every version resolves them in its own language."
+ },
+ "requiredToPublish": {
+ "type": "boolean",
+ "description": "An entry in review or published needs at least one target. Absent: false."
+ },
+ "migration": {
+ "$ref": "#/$defs/FieldMigration"
+ },
+ "collections": {
+ "type": "array",
+ "description": "The collections a target may belong to: declared collections of this site (checked in code).",
+ "minItems": 1,
+ "maxItems": 5,
+ "uniqueItems": true,
+ "items": {
+ "$ref": "common.schema.json#/$defs/Slug"
+ }
+ },
+ "multiple": {
+ "type": "boolean",
+ "description": "The value is an ordered list of targets. Absent: false."
+ },
+ "maxItems": {
+ "type": "integer",
+ "description": "With multiple: at most this many targets. Absent: 20.",
+ "minimum": 1,
+ "maximum": 50
+ },
+ "where": {
+ "type": "array",
+ "description": "Only with one target collection: conditions on its list fields that a newly chosen target must meet (no `now`; checked in code). Narrows the picker; not re-checked when the target changes later.",
+ "minItems": 1,
+ "maxItems": 4,
+ "items": {
+ "$ref": "common.schema.json#/$defs/FieldCondition"
+ }
+ }
+ }
+ },
+ {
+ "title": "RepeaterField",
+ "type": "object",
+ "description": "RFC-0045: an ordered list of items, each a flat object of sub-fields. A repeater cannot contain a repeater.",
+ "additionalProperties": false,
+ "required": ["key", "type", "labels", "localized", "fields"],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": ["repeater"]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ },
+ "localized": {
+ "type": "boolean",
+ "description": "true: each language version has its own list (missing reads the original's); false: only the original holds the list."
+ },
+ "requiredToPublish": {
+ "type": "boolean",
+ "description": "An entry in review or published needs at least one item. Absent: false."
+ },
+ "migration": {
+ "$ref": "#/$defs/FieldMigration"
+ },
+ "fields": {
+ "type": "array",
+ "description": "The sub-fields of every item, in form order. Unique keys (checked in code).",
+ "minItems": 1,
+ "maxItems": 10,
+ "items": {
+ "$ref": "#/$defs/SubFieldDefinition"
+ }
+ },
+ "maxItems": {
+ "type": "integer",
+ "description": "At most this many items. Absent: 20.",
+ "minimum": 1,
+ "maximum": 50
+ },
+ "itemLabel": {
+ "$ref": "#/$defs/FieldKey",
+ "description": "The text, select or reference sub-field that names an item in Studio and approval prompts. Absent: the first such sub-field."
+ }
+ }
+ }
+ ]
+ },
+ "SubFieldDefinition": {
+ "description": "RFC-0045: one sub-field of a repeater item, discriminated by `type`. Only types whose value is a scalar: no datetime, no multiple select or reference, no repeater. No localized and no migration: the repeater's apply.",
+ "oneOf": [
+ {
+ "title": "TextSubField",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["key", "type", "labels"],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": ["text"]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ },
+ "requiredToPublish": {
+ "type": "boolean",
+ "description": "Every item of an entry in review or published needs a value. Absent: false."
+ },
+ "maxLength": {
+ "type": "integer",
+ "description": "Absent: 5000.",
+ "minimum": 1,
+ "maximum": 5000
+ },
+ "multiline": {
+ "type": "boolean",
+ "description": "Line breaks allowed. Absent: false."
+ }
+ }
+ },
+ {
+ "title": "RichTextSubField",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["key", "type", "labels"],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": ["richText"]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ },
+ "requiredToPublish": {
+ "type": "boolean",
+ "description": "Every item of an entry in review or published needs a value. Absent: false."
+ }
+ }
+ },
+ {
+ "title": "NumberSubField",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["key", "type", "labels"],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": ["number"]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ },
+ "requiredToPublish": {
+ "type": "boolean",
+ "description": "Every item of an entry in review or published needs a value. Absent: false."
+ },
+ "integer": {
+ "type": "boolean",
+ "description": "Whole numbers only. Absent: false."
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "maximum": {
+ "type": "number"
+ }
+ }
+ },
+ {
+ "title": "BooleanSubField",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["key", "type", "labels"],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": ["boolean"]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ }
+ }
+ },
+ {
+ "title": "DateSubField",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["key", "type", "labels"],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": ["date"]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ },
+ "requiredToPublish": {
+ "type": "boolean",
+ "description": "Every item of an entry in review or published needs a value. Absent: false."
+ }
+ }
+ },
+ {
+ "title": "UrlSubField",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["key", "type", "labels"],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": ["url"]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ },
+ "requiredToPublish": {
+ "type": "boolean",
+ "description": "Every item of an entry in review or published needs a value. Absent: false."
+ }
+ }
+ },
+ {
+ "title": "ImageSubField",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["key", "type", "labels"],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": ["image"]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ },
+ "requiredToPublish": {
+ "type": "boolean",
+ "description": "Every item of an entry in review or published needs a value. Absent: false."
+ }
+ }
+ },
+ {
+ "title": "SelectSubField",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["key", "type", "labels", "options"],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": ["select"]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ },
+ "requiredToPublish": {
+ "type": "boolean",
+ "description": "Every item of an entry in review or published needs a value. Absent: false."
+ },
+ "options": {
+ "type": "array",
+ "minItems": 1,
+ "maxItems": 50,
+ "items": {
+ "$ref": "#/$defs/FieldOption"
+ }
+ }
+ }
+ },
+ {
+ "title": "ReferenceSubField",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["key", "type", "labels", "collections"],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": ["reference"]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ },
+ "requiredToPublish": {
+ "type": "boolean",
+ "description": "Every item of an entry in review or published needs a value. Absent: false."
+ },
+ "collections": {
+ "type": "array",
+ "description": "The collections a target may belong to (checked in code).",
+ "minItems": 1,
+ "maxItems": 5,
+ "uniqueItems": true,
+ "items": {
+ "$ref": "common.schema.json#/$defs/Slug"
+ }
+ }
+ }
}
]
},
@@ -662,11 +1007,17 @@
"datetime",
"url",
"image",
- "select"
+ "select",
+ "reference",
+ "repeater"
]
},
"fromLocalized": {
"type": "boolean"
+ },
+ "sub": {
+ "$ref": "#/$defs/FieldKey",
+ "description": "RFC-0045: the repeater's sub-field the operation rewrites (its key once the operation is done); fromKey and fromType then describe the sub-field."
}
}
},
diff --git a/packages/core-contracts/src/generated/content-entry.ts b/packages/core-contracts/src/generated/content-entry.ts
index bae213b8..87d45511 100644
--- a/packages/core-contracts/src/generated/content-entry.ts
+++ b/packages/core-contracts/src/generated/content-entry.ts
@@ -100,7 +100,7 @@ export interface ContentEntry {
summary?: string;
body: Body;
/**
- * Collection-specific values, keyed by field. A collection with typed fields (RFC-0041, Site.collections..fields) checks each value against its definition on write; otherwise any JSON. An absent key means no value.
+ * Collection-specific values, keyed by field. A collection with typed fields (RFC-0041, Site.collections..fields) checks each value against its definition on write; otherwise any JSON. An absent key means no value. A reference value is a translation group id or a list of them, a repeater value a list of flat objects (RFC-0045).
*/
fields: {
[k: string]: unknown | undefined;
diff --git a/packages/core-contracts/src/generated/index.ts b/packages/core-contracts/src/generated/index.ts
index df939360..b8e8a2a4 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, FieldDefinition, FieldKey, StructuredDataSource, EntryValueProperty, SiteValueProperty, Section, BodyProfile, ActorKind, BodyBlocks, SurfaceComponentId, Path, Section1, TimeZone, Site, Domain, Localization, CollectionSettings, CollectionLabel, TextField, FieldLabels, FieldLabel, FieldMigration, RichTextField, NumberField, BooleanField, DateField, DateTimeField, UrlField, ImageField, SelectField, FieldOption, CollectionExpiry, StructuredDataPreset, FieldValueSource, EntryValueSource, SiteValueSource, ConstantValueSource, ThemeRef, Design, Brand, ImageRef, SocialLink, Chrome, ChromeHeader, HeaderAction, ChromeFooter, FooterColumn, HeroSection, Link, RichTextSection, BodyBlock, Actor, ImageSection, ContentListSection, FieldCondition, FieldOrder, ComponentSection, SurfaceSection, SurfaceComponent, Announcement, Consent } from "./site.ts";
+export type { Locale, FieldDefinition, FieldKey, SubFieldDefinition, StructuredDataSource, EntryValueProperty, SiteValueProperty, Section, BodyProfile, ActorKind, BodyBlocks, SurfaceComponentId, Path, Section1, TimeZone, Site, Domain, Localization, CollectionSettings, CollectionLabel, TextField, FieldLabels, FieldLabel, FieldMigration, RichTextField, NumberField, BooleanField, DateField, DateTimeField, UrlField, ImageField, SelectField, FieldOption, ReferenceField, FieldCondition, RepeaterField, TextSubField, RichTextSubField, NumberSubField, BooleanSubField, DateSubField, UrlSubField, ImageSubField, SelectSubField, ReferenceSubField, CollectionExpiry, StructuredDataPreset, FieldValueSource, EntryValueSource, SiteValueSource, ConstantValueSource, ThemeRef, Design, Brand, ImageRef, SocialLink, Chrome, ChromeHeader, HeaderAction, ChromeFooter, FooterColumn, HeroSection, Link, RichTextSection, BodyBlock, Actor, ImageSection, ContentListSection, FieldOrder, 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 dad84ff9..36527b44 100644
--- a/packages/core-contracts/src/generated/schemas.ts
+++ b/packages/core-contracts/src/generated/schemas.ts
@@ -1165,6 +1165,130 @@ export const SiteSchema = {
"description": "The value is a list of option values. Absent: false."
}
}
+ },
+ {
+ "title": "ReferenceField",
+ "type": "object",
+ "description": "RFC-0045: entries of other (or this) collections of the same site, by translation group id (entry_…): the id of the group's original, which every language version and edition carries in translation.group.",
+ "additionalProperties": false,
+ "required": [
+ "key",
+ "type",
+ "labels",
+ "localized",
+ "collections"
+ ],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "reference"
+ ]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ },
+ "localized": {
+ "type": "boolean",
+ "description": "true: each language version picks its own targets; false: only the translation group's original holds them, and every version resolves them in its own language."
+ },
+ "requiredToPublish": {
+ "type": "boolean",
+ "description": "An entry in review or published needs at least one target. Absent: false."
+ },
+ "migration": {
+ "$ref": "#/$defs/FieldMigration"
+ },
+ "collections": {
+ "type": "array",
+ "description": "The collections a target may belong to: declared collections of this site (checked in code).",
+ "minItems": 1,
+ "maxItems": 5,
+ "uniqueItems": true,
+ "items": {
+ "$ref": "#/$defs/Slug"
+ }
+ },
+ "multiple": {
+ "type": "boolean",
+ "description": "The value is an ordered list of targets. Absent: false."
+ },
+ "maxItems": {
+ "type": "integer",
+ "description": "With multiple: at most this many targets. Absent: 20.",
+ "minimum": 1,
+ "maximum": 50
+ },
+ "where": {
+ "type": "array",
+ "description": "Only with one target collection: conditions on its list fields that a newly chosen target must meet (no `now`; checked in code). Narrows the picker; not re-checked when the target changes later.",
+ "minItems": 1,
+ "maxItems": 4,
+ "items": {
+ "$ref": "#/$defs/FieldCondition"
+ }
+ }
+ }
+ },
+ {
+ "title": "RepeaterField",
+ "type": "object",
+ "description": "RFC-0045: an ordered list of items, each a flat object of sub-fields. A repeater cannot contain a repeater.",
+ "additionalProperties": false,
+ "required": [
+ "key",
+ "type",
+ "labels",
+ "localized",
+ "fields"
+ ],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "repeater"
+ ]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ },
+ "localized": {
+ "type": "boolean",
+ "description": "true: each language version has its own list (missing reads the original's); false: only the original holds the list."
+ },
+ "requiredToPublish": {
+ "type": "boolean",
+ "description": "An entry in review or published needs at least one item. Absent: false."
+ },
+ "migration": {
+ "$ref": "#/$defs/FieldMigration"
+ },
+ "fields": {
+ "type": "array",
+ "description": "The sub-fields of every item, in form order. Unique keys (checked in code).",
+ "minItems": 1,
+ "maxItems": 10,
+ "items": {
+ "$ref": "#/$defs/SubFieldDefinition"
+ }
+ },
+ "maxItems": {
+ "type": "integer",
+ "description": "At most this many items. Absent: 20.",
+ "minimum": 1,
+ "maximum": 50
+ },
+ "itemLabel": {
+ "$ref": "#/$defs/FieldKey",
+ "description": "The text, select or reference sub-field that names an item in Studio and approval prompts. Absent: the first such sub-field."
+ }
+ }
}
]
},
@@ -1228,11 +1352,17 @@ export const SiteSchema = {
"datetime",
"url",
"image",
- "select"
+ "select",
+ "reference",
+ "repeater"
]
},
"fromLocalized": {
"type": "boolean"
+ },
+ "sub": {
+ "$ref": "#/$defs/FieldKey",
+ "description": "RFC-0045: the repeater's sub-field the operation rewrites (its key once the operation is done); fromKey and fromType then describe the sub-field."
}
}
},
@@ -1905,6 +2035,299 @@ export const SiteSchema = {
}
]
},
+ "SubFieldDefinition": {
+ "description": "RFC-0045: one sub-field of a repeater item, discriminated by `type`. Only types whose value is a scalar: no datetime, no multiple select or reference, no repeater. No localized and no migration: the repeater's apply.",
+ "oneOf": [
+ {
+ "title": "TextSubField",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "key",
+ "type",
+ "labels"
+ ],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "text"
+ ]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ },
+ "requiredToPublish": {
+ "type": "boolean",
+ "description": "Every item of an entry in review or published needs a value. Absent: false."
+ },
+ "maxLength": {
+ "type": "integer",
+ "description": "Absent: 5000.",
+ "minimum": 1,
+ "maximum": 5000
+ },
+ "multiline": {
+ "type": "boolean",
+ "description": "Line breaks allowed. Absent: false."
+ }
+ }
+ },
+ {
+ "title": "RichTextSubField",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "key",
+ "type",
+ "labels"
+ ],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "richText"
+ ]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ },
+ "requiredToPublish": {
+ "type": "boolean",
+ "description": "Every item of an entry in review or published needs a value. Absent: false."
+ }
+ }
+ },
+ {
+ "title": "NumberSubField",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "key",
+ "type",
+ "labels"
+ ],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "number"
+ ]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ },
+ "requiredToPublish": {
+ "type": "boolean",
+ "description": "Every item of an entry in review or published needs a value. Absent: false."
+ },
+ "integer": {
+ "type": "boolean",
+ "description": "Whole numbers only. Absent: false."
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "maximum": {
+ "type": "number"
+ }
+ }
+ },
+ {
+ "title": "BooleanSubField",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "key",
+ "type",
+ "labels"
+ ],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "boolean"
+ ]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ }
+ }
+ },
+ {
+ "title": "DateSubField",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "key",
+ "type",
+ "labels"
+ ],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "date"
+ ]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ },
+ "requiredToPublish": {
+ "type": "boolean",
+ "description": "Every item of an entry in review or published needs a value. Absent: false."
+ }
+ }
+ },
+ {
+ "title": "UrlSubField",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "key",
+ "type",
+ "labels"
+ ],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "url"
+ ]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ },
+ "requiredToPublish": {
+ "type": "boolean",
+ "description": "Every item of an entry in review or published needs a value. Absent: false."
+ }
+ }
+ },
+ {
+ "title": "ImageSubField",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "key",
+ "type",
+ "labels"
+ ],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "image"
+ ]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ },
+ "requiredToPublish": {
+ "type": "boolean",
+ "description": "Every item of an entry in review or published needs a value. Absent: false."
+ }
+ }
+ },
+ {
+ "title": "SelectSubField",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "key",
+ "type",
+ "labels",
+ "options"
+ ],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "select"
+ ]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ },
+ "requiredToPublish": {
+ "type": "boolean",
+ "description": "Every item of an entry in review or published needs a value. Absent: false."
+ },
+ "options": {
+ "type": "array",
+ "minItems": 1,
+ "maxItems": 50,
+ "items": {
+ "$ref": "#/$defs/FieldOption"
+ }
+ }
+ }
+ },
+ {
+ "title": "ReferenceSubField",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "key",
+ "type",
+ "labels",
+ "collections"
+ ],
+ "properties": {
+ "key": {
+ "$ref": "#/$defs/FieldKey"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "reference"
+ ]
+ },
+ "labels": {
+ "$ref": "#/$defs/FieldLabels"
+ },
+ "requiredToPublish": {
+ "type": "boolean",
+ "description": "Every item of an entry in review or published needs a value. Absent: false."
+ },
+ "collections": {
+ "type": "array",
+ "description": "The collections a target may belong to (checked in code).",
+ "minItems": 1,
+ "maxItems": 5,
+ "uniqueItems": true,
+ "items": {
+ "$ref": "#/$defs/Slug"
+ }
+ }
+ }
+ }
+ ]
+ },
"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.",
@@ -3381,7 +3804,7 @@ export const ContentEntrySchema = {
},
"fields": {
"type": "object",
- "description": "Collection-specific values, keyed by field. A collection with typed fields (RFC-0041, Site.collections..fields) checks each value against its definition on write; otherwise any JSON. An absent key means no value.",
+ "description": "Collection-specific values, keyed by field. A collection with typed fields (RFC-0041, Site.collections..fields) checks each value against its definition on write; otherwise any JSON. An absent key means no value. A reference value is a translation group id or a list of them, a repeater value a list of flat objects (RFC-0045).",
"additionalProperties": true
},
"tags": {
diff --git a/packages/core-contracts/src/generated/site.ts b/packages/core-contracts/src/generated/site.ts
index a0df6956..93fd9eed 100644
--- a/packages/core-contracts/src/generated/site.ts
+++ b/packages/core-contracts/src/generated/site.ts
@@ -48,7 +48,9 @@ export type FieldDefinition =
| DateTimeField
| UrlField
| ImageField
- | SelectField;
+ | SelectField
+ | ReferenceField
+ | RepeaterField;
/**
* The field's key in ContentEntry.fields: lower camelCase, a JavaScript identifier. Unique in its collection; built-in entry property names are reserved (checked in code).
*
@@ -56,6 +58,22 @@ export type FieldDefinition =
* via the `definition` "FieldKey".
*/
export type FieldKey = string;
+/**
+ * RFC-0045: one sub-field of a repeater item, discriminated by `type`. Only types whose value is a scalar: no datetime, no multiple select or reference, no repeater. No localized and no migration: the repeater's apply.
+ *
+ * This interface was referenced by `Site`'s JSON-Schema
+ * via the `definition` "SubFieldDefinition".
+ */
+export type SubFieldDefinition =
+ | TextSubField
+ | RichTextSubField
+ | NumberSubField
+ | BooleanSubField
+ | DateSubField
+ | UrlSubField
+ | ImageSubField
+ | SelectSubField
+ | ReferenceSubField;
/**
* RFC-0042: where one JSON-LD property's value comes from, discriminated by `kind`.
*
@@ -365,8 +383,22 @@ export interface FieldMigration {
id: string;
fromKey?: FieldKey;
fromType?:
- "text" | "richText" | "number" | "boolean" | "date" | "datetime" | "url" | "image" | "select";
+ | "text"
+ | "richText"
+ | "number"
+ | "boolean"
+ | "date"
+ | "datetime"
+ | "url"
+ | "image"
+ | "select"
+ | "reference"
+ | "repeater";
fromLocalized?: boolean;
+ /**
+ * The field's key in ContentEntry.fields: lower camelCase, a JavaScript identifier. Unique in its collection; built-in entry property names are reserved (checked in code).
+ */
+ sub?: string;
}
/**
* Cloudhal Markdown v1 (ADR-0028), at most 50 000 characters.
@@ -535,365 +567,69 @@ export interface FieldOption {
};
}
/**
- * RFC-0042: a time gate every entry derives from one of its date or datetime fields, checked at render time like Availability.until. A date ends at the start of the next day in the site's time zone.
- *
- * This interface was referenced by `Site`'s JSON-Schema
- * via the `definition` "CollectionExpiry".
- */
-export interface CollectionExpiry {
- field: FieldKey;
- /**
- * unlist: the page stays, the entry leaves listings, feeds, the sitemap and search. hide: every public route answers 404.
- */
- action: "unlist" | "hide";
- /**
- * Grace after the field's instant. Absent: 0.
- */
- afterMinutes?: number;
-}
-/**
- * RFC-0042: the JSON-LD type an entry page emits in place of BlogPosting, with its properties mapped to value sources. Paths, fitting field types and required properties are a closed table in code (@cloudhal/core-ts/structured-data).
- *
- * This interface was referenced by `Site`'s JSON-Schema
- * via the `definition` "StructuredDataPreset".
+ * RFC-0045: entries of other (or this) collections of the same site, by translation group id (entry_…): the id of the group's original, which every language version and edition carries in translation.group.
*/
-export interface StructuredDataPreset {
- type: "Event" | "JobPosting" | "Product" | "Recipe";
+export interface ReferenceField {
+ key: FieldKey;
+ type: "reference";
+ labels: FieldLabels;
/**
- * Keyed by property path, e.g. startDate or location.address.addressLocality.
+ * true: each language version picks its own targets; false: only the translation group's original holds them, and every version resolves them in its own language.
*/
- properties: {
- [k: string]: StructuredDataSource | undefined;
- };
-}
-/**
- * A typed field of this collection, converted by its type.
- */
-export interface FieldValueSource {
- kind: "field";
- field: FieldKey;
-}
-/**
- * A built-in property of the entry.
- */
-export interface EntryValueSource {
- kind: "entry";
- property: EntryValueProperty;
-}
-/**
- * A property of the site.
- */
-export interface SiteValueSource {
- kind: "site";
- property: SiteValueProperty;
-}
-/**
- * A plain-text constant: no control characters, no markup meaning; vocabulary-checked on enum paths (in code).
- */
-export interface ConstantValueSource {
- kind: "constant";
- value: string;
-}
-/**
- * Theme (a.k.a. Experience) applied by the renderer. Config is theme-defined and validated by the theme's own schema, not here.
- *
- * This interface was referenced by `Site`'s JSON-Schema
- * via the `definition` "ThemeRef".
- */
-export interface ThemeRef {
- name: Slug;
- version?: string;
- config?: {
- [k: string]: unknown | undefined;
- };
-}
-/**
- * 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.
- *
- * This interface was referenced by `Site`'s JSON-Schema
- * via the `definition` "Design".
- */
-export interface Design {
- markdown: string;
-}
-/**
- * The site's logo and social links (RFC-0025 §1), shared by the header, the footer and SEO.
- *
- * This interface was referenced by `Site`'s JSON-Schema
- * via the `definition` "Brand".
- */
-export interface Brand {
- logo?: ImageRef;
- logoDark?: ImageRef;
- logoHeight?: "s" | "m" | "l";
+ localized: boolean;
/**
- * @maxItems 8
+ * An entry in review or published needs at least one target. Absent: false.
*/
- social?:
- | []
- | [SocialLink]
- | [SocialLink, SocialLink]
- | [SocialLink, SocialLink, SocialLink]
- | [SocialLink, SocialLink, SocialLink, SocialLink]
- | [SocialLink, SocialLink, SocialLink, SocialLink, SocialLink]
- | [SocialLink, SocialLink, SocialLink, SocialLink, SocialLink, SocialLink]
- | [SocialLink, SocialLink, SocialLink, SocialLink, SocialLink, SocialLink, SocialLink]
- | [
- SocialLink,
- SocialLink,
- SocialLink,
- SocialLink,
- SocialLink,
- SocialLink,
- SocialLink,
- SocialLink,
- ];
-}
-/**
- * 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.
- *
- * This interface was referenced by `Site`'s JSON-Schema
- * via the `definition` "ImageRef".
- */
-export interface ImageRef {
+ requiredToPublish?: boolean;
+ migration?: FieldMigration;
/**
- * Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.
+ * The collections a target may belong to: declared collections of this site (checked in code).
+ *
+ * @minItems 1
+ * @maxItems 5
*/
- assetId?: string;
- src: string;
- alt: string;
- width?: number;
- height?: number;
-}
-/**
- * This interface was referenced by `Site`'s JSON-Schema
- * via the `definition` "SocialLink".
- */
-export interface SocialLink {
- network:
- | "x"
- | "bluesky"
- | "mastodon"
- | "github"
- | "linkedin"
- | "instagram"
- | "facebook"
- | "youtube"
- | "tiktok"
- | "threads"
- | "rss"
- | "email";
- href: string;
-}
-/**
- * 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.
- *
- * This interface was referenced by `Site`'s JSON-Schema
- * via the `definition` "Chrome".
- */
-export interface Chrome {
- header?: ChromeHeader;
- footer?: ChromeFooter;
- announcement?: Announcement;
- consent?: Consent;
-}
-/**
- * This interface was referenced by `Site`'s JSON-Schema
- * via the `definition` "ChromeHeader".
- */
-export interface ChromeHeader {
- layout?: "logo-left" | "logo-left-nav-center" | "centered" | "split" | "minimal";
- behavior?: "static" | "sticky" | "hide-on-scroll" | "fixed";
- shrink?: boolean;
- overHero?: "solid" | "transparent";
- width?: "content" | "wide" | "full";
- surface?: "plain" | "muted" | "raised" | "inverse" | "brand";
- mobileStyle?: "dropdown" | "drawer-start" | "drawer-end" | "fullscreen";
- collapseAt?: "sm" | "md" | "lg" | "always";
+ collections:
+ | [Slug]
+ | [Slug, Slug]
+ | [Slug, Slug, Slug]
+ | [Slug, Slug, Slug, Slug]
+ | [Slug, Slug, Slug, Slug, Slug];
/**
- * Lower-case URL segment.
+ * The value is an ordered list of targets. Absent: false.
*/
- nav?: string;
+ multiple?: boolean;
/**
- * Lower-case URL segment.
+ * With multiple: at most this many targets. Absent: 20.
*/
- utility?: string;
+ maxItems?: number;
/**
- * @maxItems 3
+ * Only with one target collection: conditions on its list fields that a newly chosen target must meet (no `now`; checked in code). Narrows the picker; not re-checked when the target changes later.
+ *
+ * @minItems 1
+ * @maxItems 4
*/
- actions?:
- [] | [HeaderAction] | [HeaderAction, HeaderAction] | [HeaderAction, HeaderAction, HeaderAction];
-}
-/**
- * This interface was referenced by `Site`'s JSON-Schema
- * via the `definition` "HeaderAction".
- */
-export interface HeaderAction {
- kind: "button" | "search" | "locale" | "theme-toggle" | "motion-toggle";
- label?: string;
- href?: string;
- variant?: "primary" | "secondary";
+ where?:
+ | [FieldCondition]
+ | [FieldCondition, FieldCondition]
+ | [FieldCondition, FieldCondition, FieldCondition]
+ | [FieldCondition, FieldCondition, FieldCondition, FieldCondition];
}
/**
+ * RFC-0042: one condition on a typed field of the listed collection. `value` is read by the field's type: a number, YYYY-MM-DD, an RFC 3339 instant, true/false, an option value, or `now`. `values` only with `in`.
+ *
* This interface was referenced by `Site`'s JSON-Schema
- * via the `definition` "ChromeFooter".
+ * via the `definition` "FieldCondition".
*/
-export interface ChromeFooter {
- layout?: "simple" | "columns" | "cta-band" | "minimal";
- surface?: "plain" | "muted" | "inverse";
- tagline?: string;
- /**
- * @maxItems 5
- */
- columns?:
- | []
- | [FooterColumn]
- | [FooterColumn, FooterColumn]
- | [FooterColumn, FooterColumn, FooterColumn]
- | [FooterColumn, FooterColumn, FooterColumn, FooterColumn]
- | [FooterColumn, FooterColumn, FooterColumn, FooterColumn, FooterColumn];
- ctaHeading?: string;
- ctaLabel?: string;
- ctaHref?: string;
- social?: boolean;
- /**
- * The legal line; {year} and {site} are the only placeholders.
- */
- copyright?: string;
+export interface FieldCondition {
/**
- * Lower-case URL segment.
+ * A field key (site.schema.json FieldKey's pattern, repeated).
*/
- legal?: string;
+ field: string;
+ op: "eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "in" | "set" | "unset";
+ value?: string;
/**
- * @maxItems 3
- */
- extras?:
- | []
- | ["locale" | "theme-toggle" | "motion-toggle"]
- | ["locale" | "theme-toggle" | "motion-toggle", "locale" | "theme-toggle" | "motion-toggle"]
- | [
- "locale" | "theme-toggle" | "motion-toggle",
- "locale" | "theme-toggle" | "motion-toggle",
- "locale" | "theme-toggle" | "motion-toggle",
- ];
- credit?: boolean;
- region?: Section;
-}
-/**
- * This interface was referenced by `Site`'s JSON-Schema
- * via the `definition` "FooterColumn".
- */
-export interface FooterColumn {
- heading: string;
- /**
- * Lower-case URL segment.
- */
- menu: string;
-}
-export interface HeroSection {
- id: Id;
- kind: "hero";
- heading: string;
- subheading?: string;
- image?: ImageRef;
- cta?: Link;
-}
-/**
- * This interface was referenced by `Site`'s JSON-Schema
- * via the `definition` "Link".
- */
-export interface Link {
- label: string;
- href: string;
- external?: boolean;
-}
-export interface RichTextSection {
- id: Id;
- kind: "richText";
- format: "markdown" | "html";
- body: string;
- profile?: BodyProfile;
- blocks?: BodyBlocks;
-}
-/**
- * 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.
- *
- * This interface was referenced by `Site`'s JSON-Schema
- * via the `definition` "BodyBlock".
- */
-export interface BodyBlock {
- /**
- * Stable across writes that keep the block's content or its place among unchanged blocks.
- */
- id: string;
- /**
- * SHA-256 (hex) of the block's markdown without trailing whitespace.
- */
- hash: string;
- by?: Actor;
- /**
- * The channel of that change.
- */
- via?: "studio" | "mcp" | "api" | "import" | "ai-command" | "proposal";
- at?: Timestamp;
-}
-/**
- * This interface was referenced by `Site`'s JSON-Schema
- * via the `definition` "Actor".
- */
-export interface Actor {
- kind: ActorKind;
- id: Id;
- name?: string;
-}
-export interface ImageSection {
- id: Id;
- kind: "image";
- image: ImageRef;
- caption?: string;
-}
-/**
- * Renders the newest entries of a content collection (e.g. blog posts).
- */
-export interface ContentListSection {
- id: Id;
- kind: "contentList";
- collection: Slug;
- limit: number;
- sort: "newest" | "oldest" | "title";
- /**
- * Filter expression over term assignments (RFC-0002), e.g. 'category:news+tag:ai' or 'category:news>*'.
- */
- filter?: string;
- /**
- * RFC-0042: conditions on the collection's list fields, ANDed.
- *
- * @minItems 1
- * @maxItems 4
- */
- where?:
- | [FieldCondition]
- | [FieldCondition, FieldCondition]
- | [FieldCondition, FieldCondition, FieldCondition]
- | [FieldCondition, FieldCondition, FieldCondition, FieldCondition];
- orderBy?: FieldOrder;
-}
-/**
- * RFC-0042: one condition on a typed field of the listed collection. `value` is read by the field's type: a number, YYYY-MM-DD, an RFC 3339 instant, true/false, an option value, or `now`. `values` only with `in`.
- *
- * This interface was referenced by `Site`'s JSON-Schema
- * via the `definition` "FieldCondition".
- */
-export interface FieldCondition {
- /**
- * A field key (site.schema.json FieldKey's pattern, repeated).
- */
- field: string;
- op: "eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "in" | "set" | "unset";
- value?: string;
- /**
- * @minItems 1
- * @maxItems 20
+ * @minItems 1
+ * @maxItems 20
*/
values?:
| [string]
@@ -1070,6 +806,551 @@ export interface FieldCondition {
string,
];
}
+/**
+ * RFC-0045: an ordered list of items, each a flat object of sub-fields. A repeater cannot contain a repeater.
+ */
+export interface RepeaterField {
+ key: FieldKey;
+ type: "repeater";
+ labels: FieldLabels;
+ /**
+ * true: each language version has its own list (missing reads the original's); false: only the original holds the list.
+ */
+ localized: boolean;
+ /**
+ * An entry in review or published needs at least one item. Absent: false.
+ */
+ requiredToPublish?: boolean;
+ migration?: FieldMigration;
+ /**
+ * The sub-fields of every item, in form order. Unique keys (checked in code).
+ *
+ * @minItems 1
+ * @maxItems 10
+ */
+ fields:
+ | [SubFieldDefinition]
+ | [SubFieldDefinition, SubFieldDefinition]
+ | [SubFieldDefinition, SubFieldDefinition, SubFieldDefinition]
+ | [SubFieldDefinition, SubFieldDefinition, SubFieldDefinition, SubFieldDefinition]
+ | [
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ ]
+ | [
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ ]
+ | [
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ ]
+ | [
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ ]
+ | [
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ ]
+ | [
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ SubFieldDefinition,
+ ];
+ /**
+ * At most this many items. Absent: 20.
+ */
+ maxItems?: number;
+ /**
+ * The field's key in ContentEntry.fields: lower camelCase, a JavaScript identifier. Unique in its collection; built-in entry property names are reserved (checked in code).
+ */
+ itemLabel?: string;
+}
+export interface TextSubField {
+ key: FieldKey;
+ type: "text";
+ labels: FieldLabels;
+ /**
+ * Every item of an entry in review or published needs a value. Absent: false.
+ */
+ requiredToPublish?: boolean;
+ /**
+ * Absent: 5000.
+ */
+ maxLength?: number;
+ /**
+ * Line breaks allowed. Absent: false.
+ */
+ multiline?: boolean;
+}
+export interface RichTextSubField {
+ key: FieldKey;
+ type: "richText";
+ labels: FieldLabels;
+ /**
+ * Every item of an entry in review or published needs a value. Absent: false.
+ */
+ requiredToPublish?: boolean;
+}
+export interface NumberSubField {
+ key: FieldKey;
+ type: "number";
+ labels: FieldLabels;
+ /**
+ * Every item of an entry in review or published needs a value. Absent: false.
+ */
+ requiredToPublish?: boolean;
+ /**
+ * Whole numbers only. Absent: false.
+ */
+ integer?: boolean;
+ minimum?: number;
+ maximum?: number;
+}
+export interface BooleanSubField {
+ key: FieldKey;
+ type: "boolean";
+ labels: FieldLabels;
+}
+export interface DateSubField {
+ key: FieldKey;
+ type: "date";
+ labels: FieldLabels;
+ /**
+ * Every item of an entry in review or published needs a value. Absent: false.
+ */
+ requiredToPublish?: boolean;
+}
+export interface UrlSubField {
+ key: FieldKey;
+ type: "url";
+ labels: FieldLabels;
+ /**
+ * Every item of an entry in review or published needs a value. Absent: false.
+ */
+ requiredToPublish?: boolean;
+}
+export interface ImageSubField {
+ key: FieldKey;
+ type: "image";
+ labels: FieldLabels;
+ /**
+ * Every item of an entry in review or published needs a value. Absent: false.
+ */
+ requiredToPublish?: boolean;
+}
+export interface SelectSubField {
+ key: FieldKey;
+ type: "select";
+ labels: FieldLabels;
+ /**
+ * Every item of an entry in review or published needs a value. Absent: false.
+ */
+ requiredToPublish?: boolean;
+ /**
+ * @minItems 1
+ * @maxItems 50
+ */
+ options: [FieldOption, ...FieldOption[]];
+}
+export interface ReferenceSubField {
+ key: FieldKey;
+ type: "reference";
+ labels: FieldLabels;
+ /**
+ * Every item of an entry in review or published needs a value. Absent: false.
+ */
+ requiredToPublish?: boolean;
+ /**
+ * The collections a target may belong to (checked in code).
+ *
+ * @minItems 1
+ * @maxItems 5
+ */
+ collections:
+ | [Slug]
+ | [Slug, Slug]
+ | [Slug, Slug, Slug]
+ | [Slug, Slug, Slug, Slug]
+ | [Slug, Slug, Slug, Slug, Slug];
+}
+/**
+ * RFC-0042: a time gate every entry derives from one of its date or datetime fields, checked at render time like Availability.until. A date ends at the start of the next day in the site's time zone.
+ *
+ * This interface was referenced by `Site`'s JSON-Schema
+ * via the `definition` "CollectionExpiry".
+ */
+export interface CollectionExpiry {
+ field: FieldKey;
+ /**
+ * unlist: the page stays, the entry leaves listings, feeds, the sitemap and search. hide: every public route answers 404.
+ */
+ action: "unlist" | "hide";
+ /**
+ * Grace after the field's instant. Absent: 0.
+ */
+ afterMinutes?: number;
+}
+/**
+ * RFC-0042: the JSON-LD type an entry page emits in place of BlogPosting, with its properties mapped to value sources. Paths, fitting field types and required properties are a closed table in code (@cloudhal/core-ts/structured-data).
+ *
+ * This interface was referenced by `Site`'s JSON-Schema
+ * via the `definition` "StructuredDataPreset".
+ */
+export interface StructuredDataPreset {
+ type: "Event" | "JobPosting" | "Product" | "Recipe";
+ /**
+ * Keyed by property path, e.g. startDate or location.address.addressLocality.
+ */
+ properties: {
+ [k: string]: StructuredDataSource | undefined;
+ };
+}
+/**
+ * A typed field of this collection, converted by its type.
+ */
+export interface FieldValueSource {
+ kind: "field";
+ field: FieldKey;
+}
+/**
+ * A built-in property of the entry.
+ */
+export interface EntryValueSource {
+ kind: "entry";
+ property: EntryValueProperty;
+}
+/**
+ * A property of the site.
+ */
+export interface SiteValueSource {
+ kind: "site";
+ property: SiteValueProperty;
+}
+/**
+ * A plain-text constant: no control characters, no markup meaning; vocabulary-checked on enum paths (in code).
+ */
+export interface ConstantValueSource {
+ kind: "constant";
+ value: string;
+}
+/**
+ * Theme (a.k.a. Experience) applied by the renderer. Config is theme-defined and validated by the theme's own schema, not here.
+ *
+ * This interface was referenced by `Site`'s JSON-Schema
+ * via the `definition` "ThemeRef".
+ */
+export interface ThemeRef {
+ name: Slug;
+ version?: string;
+ config?: {
+ [k: string]: unknown | undefined;
+ };
+}
+/**
+ * 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.
+ *
+ * This interface was referenced by `Site`'s JSON-Schema
+ * via the `definition` "Design".
+ */
+export interface Design {
+ markdown: string;
+}
+/**
+ * The site's logo and social links (RFC-0025 §1), shared by the header, the footer and SEO.
+ *
+ * This interface was referenced by `Site`'s JSON-Schema
+ * via the `definition` "Brand".
+ */
+export interface Brand {
+ logo?: ImageRef;
+ logoDark?: ImageRef;
+ logoHeight?: "s" | "m" | "l";
+ /**
+ * @maxItems 8
+ */
+ social?:
+ | []
+ | [SocialLink]
+ | [SocialLink, SocialLink]
+ | [SocialLink, SocialLink, SocialLink]
+ | [SocialLink, SocialLink, SocialLink, SocialLink]
+ | [SocialLink, SocialLink, SocialLink, SocialLink, SocialLink]
+ | [SocialLink, SocialLink, SocialLink, SocialLink, SocialLink, SocialLink]
+ | [SocialLink, SocialLink, SocialLink, SocialLink, SocialLink, SocialLink, SocialLink]
+ | [
+ SocialLink,
+ SocialLink,
+ SocialLink,
+ SocialLink,
+ SocialLink,
+ SocialLink,
+ SocialLink,
+ SocialLink,
+ ];
+}
+/**
+ * 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.
+ *
+ * This interface was referenced by `Site`'s JSON-Schema
+ * via the `definition` "ImageRef".
+ */
+export interface ImageRef {
+ /**
+ * Prefixed, URL-safe identifier, e.g. site_01HZX8K3Q9. The prefix names the entity kind; agents and the MCP server pass these as opaque handles.
+ */
+ assetId?: string;
+ src: string;
+ alt: string;
+ width?: number;
+ height?: number;
+}
+/**
+ * This interface was referenced by `Site`'s JSON-Schema
+ * via the `definition` "SocialLink".
+ */
+export interface SocialLink {
+ network:
+ | "x"
+ | "bluesky"
+ | "mastodon"
+ | "github"
+ | "linkedin"
+ | "instagram"
+ | "facebook"
+ | "youtube"
+ | "tiktok"
+ | "threads"
+ | "rss"
+ | "email";
+ href: string;
+}
+/**
+ * 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.
+ *
+ * This interface was referenced by `Site`'s JSON-Schema
+ * via the `definition` "Chrome".
+ */
+export interface Chrome {
+ header?: ChromeHeader;
+ footer?: ChromeFooter;
+ announcement?: Announcement;
+ consent?: Consent;
+}
+/**
+ * This interface was referenced by `Site`'s JSON-Schema
+ * via the `definition` "ChromeHeader".
+ */
+export interface ChromeHeader {
+ layout?: "logo-left" | "logo-left-nav-center" | "centered" | "split" | "minimal";
+ behavior?: "static" | "sticky" | "hide-on-scroll" | "fixed";
+ shrink?: boolean;
+ overHero?: "solid" | "transparent";
+ width?: "content" | "wide" | "full";
+ surface?: "plain" | "muted" | "raised" | "inverse" | "brand";
+ mobileStyle?: "dropdown" | "drawer-start" | "drawer-end" | "fullscreen";
+ collapseAt?: "sm" | "md" | "lg" | "always";
+ /**
+ * Lower-case URL segment.
+ */
+ nav?: string;
+ /**
+ * Lower-case URL segment.
+ */
+ utility?: string;
+ /**
+ * @maxItems 3
+ */
+ actions?:
+ [] | [HeaderAction] | [HeaderAction, HeaderAction] | [HeaderAction, HeaderAction, HeaderAction];
+}
+/**
+ * This interface was referenced by `Site`'s JSON-Schema
+ * via the `definition` "HeaderAction".
+ */
+export interface HeaderAction {
+ kind: "button" | "search" | "locale" | "theme-toggle" | "motion-toggle";
+ label?: string;
+ href?: string;
+ variant?: "primary" | "secondary";
+}
+/**
+ * This interface was referenced by `Site`'s JSON-Schema
+ * via the `definition` "ChromeFooter".
+ */
+export interface ChromeFooter {
+ layout?: "simple" | "columns" | "cta-band" | "minimal";
+ surface?: "plain" | "muted" | "inverse";
+ tagline?: string;
+ /**
+ * @maxItems 5
+ */
+ columns?:
+ | []
+ | [FooterColumn]
+ | [FooterColumn, FooterColumn]
+ | [FooterColumn, FooterColumn, FooterColumn]
+ | [FooterColumn, FooterColumn, FooterColumn, FooterColumn]
+ | [FooterColumn, FooterColumn, FooterColumn, FooterColumn, FooterColumn];
+ ctaHeading?: string;
+ ctaLabel?: string;
+ ctaHref?: string;
+ social?: boolean;
+ /**
+ * The legal line; {year} and {site} are the only placeholders.
+ */
+ copyright?: string;
+ /**
+ * Lower-case URL segment.
+ */
+ legal?: string;
+ /**
+ * @maxItems 3
+ */
+ extras?:
+ | []
+ | ["locale" | "theme-toggle" | "motion-toggle"]
+ | ["locale" | "theme-toggle" | "motion-toggle", "locale" | "theme-toggle" | "motion-toggle"]
+ | [
+ "locale" | "theme-toggle" | "motion-toggle",
+ "locale" | "theme-toggle" | "motion-toggle",
+ "locale" | "theme-toggle" | "motion-toggle",
+ ];
+ credit?: boolean;
+ region?: Section;
+}
+/**
+ * This interface was referenced by `Site`'s JSON-Schema
+ * via the `definition` "FooterColumn".
+ */
+export interface FooterColumn {
+ heading: string;
+ /**
+ * Lower-case URL segment.
+ */
+ menu: string;
+}
+export interface HeroSection {
+ id: Id;
+ kind: "hero";
+ heading: string;
+ subheading?: string;
+ image?: ImageRef;
+ cta?: Link;
+}
+/**
+ * This interface was referenced by `Site`'s JSON-Schema
+ * via the `definition` "Link".
+ */
+export interface Link {
+ label: string;
+ href: string;
+ external?: boolean;
+}
+export interface RichTextSection {
+ id: Id;
+ kind: "richText";
+ format: "markdown" | "html";
+ body: string;
+ profile?: BodyProfile;
+ blocks?: BodyBlocks;
+}
+/**
+ * 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.
+ *
+ * This interface was referenced by `Site`'s JSON-Schema
+ * via the `definition` "BodyBlock".
+ */
+export interface BodyBlock {
+ /**
+ * Stable across writes that keep the block's content or its place among unchanged blocks.
+ */
+ id: string;
+ /**
+ * SHA-256 (hex) of the block's markdown without trailing whitespace.
+ */
+ hash: string;
+ by?: Actor;
+ /**
+ * The channel of that change.
+ */
+ via?: "studio" | "mcp" | "api" | "import" | "ai-command" | "proposal";
+ at?: Timestamp;
+}
+/**
+ * This interface was referenced by `Site`'s JSON-Schema
+ * via the `definition` "Actor".
+ */
+export interface Actor {
+ kind: ActorKind;
+ id: Id;
+ name?: string;
+}
+export interface ImageSection {
+ id: Id;
+ kind: "image";
+ image: ImageRef;
+ caption?: string;
+}
+/**
+ * Renders the newest entries of a content collection (e.g. blog posts).
+ */
+export interface ContentListSection {
+ id: Id;
+ kind: "contentList";
+ collection: Slug;
+ limit: number;
+ sort: "newest" | "oldest" | "title";
+ /**
+ * Filter expression over term assignments (RFC-0002), e.g. 'category:news+tag:ai' or 'category:news>*'.
+ */
+ filter?: string;
+ /**
+ * RFC-0042: conditions on the collection's list fields, ANDed.
+ *
+ * @minItems 1
+ * @maxItems 4
+ */
+ where?:
+ | [FieldCondition]
+ | [FieldCondition, FieldCondition]
+ | [FieldCondition, FieldCondition, FieldCondition]
+ | [FieldCondition, FieldCondition, FieldCondition, FieldCondition];
+ orderBy?: FieldOrder;
+}
/**
* RFC-0042: order a listing by a number, date or datetime field. Entries without a value sort last.
*
diff --git a/packages/core-contracts/src/generated/validators.js b/packages/core-contracts/src/generated/validators.js
index 0d017158..346dfa2a 100644
--- a/packages/core-contracts/src/generated/validators.js
+++ b/packages/core-contracts/src/generated/validators.js
@@ -530,20 +530,20 @@ 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"},"collections":{"type":"object","description":"RFC-0038, RFC-0040: the site's declared collections and their settings, keyed by collection (ContentEntry.collection). A key here declares the collection, even with no settings ({}). A collection with entries but no key is inferred and uses the defaults.","propertyNames":{"$ref":"#/$defs/Slug"},"maxProperties":50,"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"}}}},"CollectionExpiry":{"type":"object","description":"RFC-0042: a time gate every entry derives from one of its date or datetime fields, checked at render time like Availability.until. A date ends at the start of the next day in the site's time zone.","additionalProperties":false,"required":["field","action"],"properties":{"field":{"$ref":"#/$defs/FieldKey"},"action":{"type":"string","description":"unlist: the page stays, the entry leaves listings, feeds, the sitemap and search. hide: every public route answers 404.","enum":["unlist","hide"]},"afterMinutes":{"type":"integer","description":"Grace after the field's instant. Absent: 0.","minimum":0,"maximum":10080}}},"CollectionLabel":{"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}}},"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\\}$"},"labels":{"type":"object","description":"RFC-0040: display names per locale (Studio, agents, themes). Absent or missing a locale: the default locale's, then the key.","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"$ref":"#/$defs/CollectionLabel"}},"icon":{"type":"string","description":"RFC-0040: a name from Studio's closed icon set (e.g. calendar, briefcase, users). Unknown names show the default icon. Never a URL or markup.","pattern":"^[a-z0-9]+(-[a-z0-9]+)*$","maxLength":40},"feed":{"type":"boolean","description":"RFC-0040: entries appear in the site's RSS feeds. Absent: true."},"sitemap":{"type":"boolean","description":"RFC-0040: published entries appear in sitemap.xml. Absent: true."},"sort":{"type":"string","description":"RFC-0040: default order where none is given (Studio's list, entry queries without sort, MCP list_entries). Absent: newest.","enum":["newest","oldest","title"]},"fields":{"type":"array","description":"RFC-0041: the collection's typed custom fields, in form order. Values live in ContentEntry.fields under each key. At most 200 per site and unique keys (checked in code).","minItems":1,"maxItems":30,"items":{"$ref":"#/$defs/FieldDefinition"}},"listFields":{"type":"array","description":"RFC-0042: fields that listings, queries, Studio columns and MCP may filter and sort by, in column order. Keys of this collection's fields (checked in code).","minItems":1,"maxItems":4,"uniqueItems":true,"items":{"$ref":"#/$defs/FieldKey"}},"expiry":{"$ref":"#/$defs/CollectionExpiry"},"structuredData":{"$ref":"#/$defs/StructuredDataPreset"}}},"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"}}},"EntryValueProperty":{"type":"string","description":"RFC-0042: entry properties a structured-data property may read.","enum":["title","summary","body","coverImage","url","publishedAt","updatedAt"]},"FieldCondition":{"type":"object","description":"RFC-0042: one condition on a typed field of the listed collection. `value` is read by the field's type: a number, YYYY-MM-DD, an RFC 3339 instant, true/false, an option value, or `now`. `values` only with `in`.","additionalProperties":false,"required":["field","op"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"op":{"type":"string","enum":["eq","ne","lt","lte","gt","gte","in","set","unset"]},"value":{"type":"string","minLength":1,"maxLength":100},"values":{"type":"array","minItems":1,"maxItems":20,"uniqueItems":true,"items":{"type":"string","minLength":1,"maxLength":100}}}},"FieldDefinition":{"description":"RFC-0041: one typed custom field of a collection, discriminated by `type`.","oneOf":[{"title":"TextField","type":"object","description":"Plain text: a string of at most maxLength characters.","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["text"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"},"maxLength":{"type":"integer","description":"Absent: 5000.","minimum":1,"maximum":5000},"multiline":{"type":"boolean","description":"Line breaks allowed. Absent: false."},"searchable":{"type":"boolean","description":"Its words go into site search. Absent: false."}}},{"title":"RichTextField","type":"object","description":"Cloudhal Markdown v1 (ADR-0028), at most 50 000 characters.","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["richText"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"},"searchable":{"type":"boolean","description":"Its words go into site search. Absent: false."}}},{"title":"NumberField","type":"object","description":"A finite number.","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["number"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"},"integer":{"type":"boolean","description":"Whole numbers only. Absent: false."},"minimum":{"type":"number"},"maximum":{"type":"number"}}},{"title":"BooleanField","type":"object","description":"true or false; absent reads as false, so it cannot be required to publish.","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["boolean"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"migration":{"$ref":"#/$defs/FieldMigration"}}},{"title":"DateField","type":"object","description":"A calendar day without a zone (YYYY-MM-DD).","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["date"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"}}},{"title":"DateTimeField","type":"object","description":"An instant with the zone it was entered in: {at, timeZone} (RFC-0008 §9.1).","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["datetime"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"}}},{"title":"UrlField","type":"object","description":"A link: https, http, mailto, tel, a site-relative path or a #fragment; at most 2048 characters.","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["url"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"}}},{"title":"ImageField","type":"object","description":"An image Asset of the same site, by id (asset_…).","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["image"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"}}},{"title":"SelectField","type":"object","description":"One of a closed list of options, or several when multiple.","additionalProperties":false,"required":["key","type","labels","localized","options"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["select"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"},"options":{"type":"array","minItems":1,"maxItems":50,"items":{"$ref":"#/$defs/FieldOption"}},"multiple":{"type":"boolean","description":"The value is a list of option values. Absent: false."}}}]},"FieldKey":{"type":"string","description":"The field's key in ContentEntry.fields: lower camelCase, a JavaScript identifier. Unique in its collection; built-in entry property names are reserved (checked in code).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"FieldLabel":{"type":"object","additionalProperties":false,"required":["label"],"properties":{"label":{"type":"string","minLength":1,"maxLength":100},"help":{"type":"string","maxLength":300}}},"FieldLabels":{"type":"object","description":"The field's label and help per locale. Missing a locale: the default locale's, then the key.","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"$ref":"#/$defs/FieldLabel"}},"FieldMigration":{"type":"object","description":"RFC-0041 §6: present only while a field operation rewrites entries.","additionalProperties":false,"required":["id"],"properties":{"id":{"$ref":"#/$defs/Id","description":"The operation's \"before\" snapshot."},"fromKey":{"$ref":"#/$defs/FieldKey"},"fromType":{"type":"string","enum":["text","richText","number","boolean","date","datetime","url","image","select"]},"fromLocalized":{"type":"boolean"}}},"FieldOption":{"type":"object","description":"One option of a select field. Values are unique in the field (checked in code).","additionalProperties":false,"required":["value","labels"],"properties":{"value":{"$ref":"#/$defs/Slug","description":"Stored in entries, never shown."},"labels":{"type":"object","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"type":"string","minLength":1,"maxLength":100}}}},"FieldOrder":{"type":"object","description":"RFC-0042: order a listing by a number, date or datetime field. Entries without a value sort last.","additionalProperties":false,"required":["field","direction"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"direction":{"type":"string","enum":["asc","desc"]}}},"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},"where":{"type":"array","description":"RFC-0042: conditions on the collection's list fields, ANDed.","minItems":1,"maxItems":4,"items":{"$ref":"#/$defs/FieldCondition"}},"orderBy":{"$ref":"#/$defs/FieldOrder"}}},{"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}}}]},"SiteValueProperty":{"type":"string","description":"RFC-0042: site properties a structured-data property may read.","enum":["name","url","logo"]},"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}}},"StructuredDataPreset":{"type":"object","description":"RFC-0042: the JSON-LD type an entry page emits in place of BlogPosting, with its properties mapped to value sources. Paths, fitting field types and required properties are a closed table in code (@cloudhal/core-ts/structured-data).","additionalProperties":false,"required":["type","properties"],"properties":{"type":{"type":"string","enum":["Event","JobPosting","Product","Recipe"]},"properties":{"type":"object","description":"Keyed by property path, e.g. startDate or location.address.addressLocality.","propertyNames":{"type":"string","pattern":"^[a-z][A-Za-z]{0,39}(\\.[a-z][A-Za-z]{0,39}){0,2}$"},"minProperties":1,"maxProperties":40,"additionalProperties":{"$ref":"#/$defs/StructuredDataSource"}}}},"StructuredDataSource":{"description":"RFC-0042: where one JSON-LD property's value comes from, discriminated by `kind`.","oneOf":[{"title":"FieldValueSource","type":"object","description":"A typed field of this collection, converted by its type.","additionalProperties":false,"required":["kind","field"],"properties":{"kind":{"type":"string","enum":["field"]},"field":{"$ref":"#/$defs/FieldKey"}}},{"title":"EntryValueSource","type":"object","description":"A built-in property of the entry.","additionalProperties":false,"required":["kind","property"],"properties":{"kind":{"type":"string","enum":["entry"]},"property":{"$ref":"#/$defs/EntryValueProperty"}}},{"title":"SiteValueSource","type":"object","description":"A property of the site.","additionalProperties":false,"required":["kind","property"],"properties":{"kind":{"type":"string","enum":["site"]},"property":{"$ref":"#/$defs/SiteValueProperty"}}},{"title":"ConstantValueSource","type":"object","description":"A plain-text constant: no control characters, no markup meaning; vocabulary-checked on enum paths (in code).","additionalProperties":false,"required":["kind","value"],"properties":{"kind":{"type":"string","enum":["constant"]},"value":{"type":"string","minLength":1,"maxLength":200,"pattern":"^[^\\x00-\\x1F\\x7F]+$"}}}]},"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, RFC-0040: the site's declared collections and their settings, keyed by collection (ContentEntry.collection). A key here declares the collection, even with no settings ({}). A collection with entries but no key is inferred and uses the defaults.","propertyNames":{"$ref":"#/$defs/Slug"},"maxProperties":50,"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"}}}},"CollectionExpiry":{"type":"object","description":"RFC-0042: a time gate every entry derives from one of its date or datetime fields, checked at render time like Availability.until. A date ends at the start of the next day in the site's time zone.","additionalProperties":false,"required":["field","action"],"properties":{"field":{"$ref":"#/$defs/FieldKey"},"action":{"type":"string","description":"unlist: the page stays, the entry leaves listings, feeds, the sitemap and search. hide: every public route answers 404.","enum":["unlist","hide"]},"afterMinutes":{"type":"integer","description":"Grace after the field's instant. Absent: 0.","minimum":0,"maximum":10080}}},"CollectionLabel":{"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}}},"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\\}$"},"labels":{"type":"object","description":"RFC-0040: display names per locale (Studio, agents, themes). Absent or missing a locale: the default locale's, then the key.","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"$ref":"#/$defs/CollectionLabel"}},"icon":{"type":"string","description":"RFC-0040: a name from Studio's closed icon set (e.g. calendar, briefcase, users). Unknown names show the default icon. Never a URL or markup.","pattern":"^[a-z0-9]+(-[a-z0-9]+)*$","maxLength":40},"feed":{"type":"boolean","description":"RFC-0040: entries appear in the site's RSS feeds. Absent: true."},"sitemap":{"type":"boolean","description":"RFC-0040: published entries appear in sitemap.xml. Absent: true."},"sort":{"type":"string","description":"RFC-0040: default order where none is given (Studio's list, entry queries without sort, MCP list_entries). Absent: newest.","enum":["newest","oldest","title"]},"fields":{"type":"array","description":"RFC-0041: the collection's typed custom fields, in form order. Values live in ContentEntry.fields under each key. At most 200 per site and unique keys (checked in code).","minItems":1,"maxItems":30,"items":{"$ref":"#/$defs/FieldDefinition"}},"listFields":{"type":"array","description":"RFC-0042: fields that listings, queries, Studio columns and MCP may filter and sort by, in column order. Keys of this collection's fields (checked in code).","minItems":1,"maxItems":4,"uniqueItems":true,"items":{"$ref":"#/$defs/FieldKey"}},"expiry":{"$ref":"#/$defs/CollectionExpiry"},"structuredData":{"$ref":"#/$defs/StructuredDataPreset"}}},"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"}}},"EntryValueProperty":{"type":"string","description":"RFC-0042: entry properties a structured-data property may read.","enum":["title","summary","body","coverImage","url","publishedAt","updatedAt"]},"FieldCondition":{"type":"object","description":"RFC-0042: one condition on a typed field of the listed collection. `value` is read by the field's type: a number, YYYY-MM-DD, an RFC 3339 instant, true/false, an option value, or `now`. `values` only with `in`.","additionalProperties":false,"required":["field","op"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"op":{"type":"string","enum":["eq","ne","lt","lte","gt","gte","in","set","unset"]},"value":{"type":"string","minLength":1,"maxLength":100},"values":{"type":"array","minItems":1,"maxItems":20,"uniqueItems":true,"items":{"type":"string","minLength":1,"maxLength":100}}}},"FieldDefinition":{"description":"RFC-0041: one typed custom field of a collection, discriminated by `type`.","oneOf":[{"title":"TextField","type":"object","description":"Plain text: a string of at most maxLength characters.","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["text"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"},"maxLength":{"type":"integer","description":"Absent: 5000.","minimum":1,"maximum":5000},"multiline":{"type":"boolean","description":"Line breaks allowed. Absent: false."},"searchable":{"type":"boolean","description":"Its words go into site search. Absent: false."}}},{"title":"RichTextField","type":"object","description":"Cloudhal Markdown v1 (ADR-0028), at most 50 000 characters.","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["richText"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"},"searchable":{"type":"boolean","description":"Its words go into site search. Absent: false."}}},{"title":"NumberField","type":"object","description":"A finite number.","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["number"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"},"integer":{"type":"boolean","description":"Whole numbers only. Absent: false."},"minimum":{"type":"number"},"maximum":{"type":"number"}}},{"title":"BooleanField","type":"object","description":"true or false; absent reads as false, so it cannot be required to publish.","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["boolean"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"migration":{"$ref":"#/$defs/FieldMigration"}}},{"title":"DateField","type":"object","description":"A calendar day without a zone (YYYY-MM-DD).","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["date"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"}}},{"title":"DateTimeField","type":"object","description":"An instant with the zone it was entered in: {at, timeZone} (RFC-0008 §9.1).","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["datetime"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"}}},{"title":"UrlField","type":"object","description":"A link: https, http, mailto, tel, a site-relative path or a #fragment; at most 2048 characters.","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["url"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"}}},{"title":"ImageField","type":"object","description":"An image Asset of the same site, by id (asset_…).","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["image"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"}}},{"title":"SelectField","type":"object","description":"One of a closed list of options, or several when multiple.","additionalProperties":false,"required":["key","type","labels","localized","options"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["select"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"},"options":{"type":"array","minItems":1,"maxItems":50,"items":{"$ref":"#/$defs/FieldOption"}},"multiple":{"type":"boolean","description":"The value is a list of option values. Absent: false."}}},{"title":"ReferenceField","type":"object","description":"RFC-0045: entries of other (or this) collections of the same site, by translation group id (entry_…): the id of the group's original, which every language version and edition carries in translation.group.","additionalProperties":false,"required":["key","type","labels","localized","collections"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["reference"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version picks its own targets; false: only the translation group's original holds them, and every version resolves them in its own language."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs at least one target. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"},"collections":{"type":"array","description":"The collections a target may belong to: declared collections of this site (checked in code).","minItems":1,"maxItems":5,"uniqueItems":true,"items":{"$ref":"#/$defs/Slug"}},"multiple":{"type":"boolean","description":"The value is an ordered list of targets. Absent: false."},"maxItems":{"type":"integer","description":"With multiple: at most this many targets. Absent: 20.","minimum":1,"maximum":50},"where":{"type":"array","description":"Only with one target collection: conditions on its list fields that a newly chosen target must meet (no `now`; checked in code). Narrows the picker; not re-checked when the target changes later.","minItems":1,"maxItems":4,"items":{"$ref":"#/$defs/FieldCondition"}}}},{"title":"RepeaterField","type":"object","description":"RFC-0045: an ordered list of items, each a flat object of sub-fields. A repeater cannot contain a repeater.","additionalProperties":false,"required":["key","type","labels","localized","fields"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["repeater"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own list (missing reads the original's); false: only the original holds the list."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs at least one item. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"},"fields":{"type":"array","description":"The sub-fields of every item, in form order. Unique keys (checked in code).","minItems":1,"maxItems":10,"items":{"$ref":"#/$defs/SubFieldDefinition"}},"maxItems":{"type":"integer","description":"At most this many items. Absent: 20.","minimum":1,"maximum":50},"itemLabel":{"$ref":"#/$defs/FieldKey","description":"The text, select or reference sub-field that names an item in Studio and approval prompts. Absent: the first such sub-field."}}}]},"FieldKey":{"type":"string","description":"The field's key in ContentEntry.fields: lower camelCase, a JavaScript identifier. Unique in its collection; built-in entry property names are reserved (checked in code).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"FieldLabel":{"type":"object","additionalProperties":false,"required":["label"],"properties":{"label":{"type":"string","minLength":1,"maxLength":100},"help":{"type":"string","maxLength":300}}},"FieldLabels":{"type":"object","description":"The field's label and help per locale. Missing a locale: the default locale's, then the key.","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"$ref":"#/$defs/FieldLabel"}},"FieldMigration":{"type":"object","description":"RFC-0041 §6: present only while a field operation rewrites entries.","additionalProperties":false,"required":["id"],"properties":{"id":{"$ref":"#/$defs/Id","description":"The operation's \"before\" snapshot."},"fromKey":{"$ref":"#/$defs/FieldKey"},"fromType":{"type":"string","enum":["text","richText","number","boolean","date","datetime","url","image","select","reference","repeater"]},"fromLocalized":{"type":"boolean"},"sub":{"$ref":"#/$defs/FieldKey","description":"RFC-0045: the repeater's sub-field the operation rewrites (its key once the operation is done); fromKey and fromType then describe the sub-field."}}},"FieldOption":{"type":"object","description":"One option of a select field. Values are unique in the field (checked in code).","additionalProperties":false,"required":["value","labels"],"properties":{"value":{"$ref":"#/$defs/Slug","description":"Stored in entries, never shown."},"labels":{"type":"object","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"type":"string","minLength":1,"maxLength":100}}}},"FieldOrder":{"type":"object","description":"RFC-0042: order a listing by a number, date or datetime field. Entries without a value sort last.","additionalProperties":false,"required":["field","direction"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"direction":{"type":"string","enum":["asc","desc"]}}},"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},"where":{"type":"array","description":"RFC-0042: conditions on the collection's list fields, ANDed.","minItems":1,"maxItems":4,"items":{"$ref":"#/$defs/FieldCondition"}},"orderBy":{"$ref":"#/$defs/FieldOrder"}}},{"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}}}]},"SiteValueProperty":{"type":"string","description":"RFC-0042: site properties a structured-data property may read.","enum":["name","url","logo"]},"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}}},"StructuredDataPreset":{"type":"object","description":"RFC-0042: the JSON-LD type an entry page emits in place of BlogPosting, with its properties mapped to value sources. Paths, fitting field types and required properties are a closed table in code (@cloudhal/core-ts/structured-data).","additionalProperties":false,"required":["type","properties"],"properties":{"type":{"type":"string","enum":["Event","JobPosting","Product","Recipe"]},"properties":{"type":"object","description":"Keyed by property path, e.g. startDate or location.address.addressLocality.","propertyNames":{"type":"string","pattern":"^[a-z][A-Za-z]{0,39}(\\.[a-z][A-Za-z]{0,39}){0,2}$"},"minProperties":1,"maxProperties":40,"additionalProperties":{"$ref":"#/$defs/StructuredDataSource"}}}},"StructuredDataSource":{"description":"RFC-0042: where one JSON-LD property's value comes from, discriminated by `kind`.","oneOf":[{"title":"FieldValueSource","type":"object","description":"A typed field of this collection, converted by its type.","additionalProperties":false,"required":["kind","field"],"properties":{"kind":{"type":"string","enum":["field"]},"field":{"$ref":"#/$defs/FieldKey"}}},{"title":"EntryValueSource","type":"object","description":"A built-in property of the entry.","additionalProperties":false,"required":["kind","property"],"properties":{"kind":{"type":"string","enum":["entry"]},"property":{"$ref":"#/$defs/EntryValueProperty"}}},{"title":"SiteValueSource","type":"object","description":"A property of the site.","additionalProperties":false,"required":["kind","property"],"properties":{"kind":{"type":"string","enum":["site"]},"property":{"$ref":"#/$defs/SiteValueProperty"}}},{"title":"ConstantValueSource","type":"object","description":"A plain-text constant: no control characters, no markup meaning; vocabulary-checked on enum paths (in code).","additionalProperties":false,"required":["kind","value"],"properties":{"kind":{"type":"string","enum":["constant"]},"value":{"type":"string","minLength":1,"maxLength":200,"pattern":"^[^\\x00-\\x1F\\x7F]+$"}}}]},"SubFieldDefinition":{"description":"RFC-0045: one sub-field of a repeater item, discriminated by `type`. Only types whose value is a scalar: no datetime, no multiple select or reference, no repeater. No localized and no migration: the repeater's apply.","oneOf":[{"title":"TextSubField","type":"object","additionalProperties":false,"required":["key","type","labels"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["text"]},"labels":{"$ref":"#/$defs/FieldLabels"},"requiredToPublish":{"type":"boolean","description":"Every item of an entry in review or published needs a value. Absent: false."},"maxLength":{"type":"integer","description":"Absent: 5000.","minimum":1,"maximum":5000},"multiline":{"type":"boolean","description":"Line breaks allowed. Absent: false."}}},{"title":"RichTextSubField","type":"object","additionalProperties":false,"required":["key","type","labels"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["richText"]},"labels":{"$ref":"#/$defs/FieldLabels"},"requiredToPublish":{"type":"boolean","description":"Every item of an entry in review or published needs a value. Absent: false."}}},{"title":"NumberSubField","type":"object","additionalProperties":false,"required":["key","type","labels"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["number"]},"labels":{"$ref":"#/$defs/FieldLabels"},"requiredToPublish":{"type":"boolean","description":"Every item of an entry in review or published needs a value. Absent: false."},"integer":{"type":"boolean","description":"Whole numbers only. Absent: false."},"minimum":{"type":"number"},"maximum":{"type":"number"}}},{"title":"BooleanSubField","type":"object","additionalProperties":false,"required":["key","type","labels"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["boolean"]},"labels":{"$ref":"#/$defs/FieldLabels"}}},{"title":"DateSubField","type":"object","additionalProperties":false,"required":["key","type","labels"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["date"]},"labels":{"$ref":"#/$defs/FieldLabels"},"requiredToPublish":{"type":"boolean","description":"Every item of an entry in review or published needs a value. Absent: false."}}},{"title":"UrlSubField","type":"object","additionalProperties":false,"required":["key","type","labels"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["url"]},"labels":{"$ref":"#/$defs/FieldLabels"},"requiredToPublish":{"type":"boolean","description":"Every item of an entry in review or published needs a value. Absent: false."}}},{"title":"ImageSubField","type":"object","additionalProperties":false,"required":["key","type","labels"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["image"]},"labels":{"$ref":"#/$defs/FieldLabels"},"requiredToPublish":{"type":"boolean","description":"Every item of an entry in review or published needs a value. Absent: false."}}},{"title":"SelectSubField","type":"object","additionalProperties":false,"required":["key","type","labels","options"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["select"]},"labels":{"$ref":"#/$defs/FieldLabels"},"requiredToPublish":{"type":"boolean","description":"Every item of an entry in review or published needs a value. Absent: false."},"options":{"type":"array","minItems":1,"maxItems":50,"items":{"$ref":"#/$defs/FieldOption"}}}},{"title":"ReferenceSubField","type":"object","additionalProperties":false,"required":["key","type","labels","collections"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["reference"]},"labels":{"$ref":"#/$defs/FieldLabels"},"requiredToPublish":{"type":"boolean","description":"Every item of an entry in review or published needs a value. Absent: false."},"collections":{"type":"array","description":"The collections a target may belong to (checked in code).","minItems":1,"maxItems":5,"uniqueItems":true,"items":{"$ref":"#/$defs/Slug"}}}}]},"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 schema86 = {"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 schema124 = {"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 schema126 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0};
-const schema110 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"};
-const schema129 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}};
+const schema103 = {"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 schema141 = {"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 schema143 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0};
+const schema127 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"};
+const schema146 = {"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 pattern66 = new RegExp("^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$", "u");
+const pattern82 = 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={}}={}){
@@ -848,7 +848,8 @@ const schema58 = {"type":"string","description":"The field's key in ContentEntry
const pattern19 = new RegExp("^(/[a-z0-9]+(-[a-z0-9]+)*)+/\\{slug\\}$", "u");
const pattern21 = new RegExp("^[a-z0-9]+(-[a-z0-9]+)*$", "u");
const pattern22 = new RegExp("^[a-z][A-Za-z0-9]{0,39}$", "u");
-const schema57 = {"description":"RFC-0041: one typed custom field of a collection, discriminated by `type`.","oneOf":[{"title":"TextField","type":"object","description":"Plain text: a string of at most maxLength characters.","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["text"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"},"maxLength":{"type":"integer","description":"Absent: 5000.","minimum":1,"maximum":5000},"multiline":{"type":"boolean","description":"Line breaks allowed. Absent: false."},"searchable":{"type":"boolean","description":"Its words go into site search. Absent: false."}}},{"title":"RichTextField","type":"object","description":"Cloudhal Markdown v1 (ADR-0028), at most 50 000 characters.","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["richText"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"},"searchable":{"type":"boolean","description":"Its words go into site search. Absent: false."}}},{"title":"NumberField","type":"object","description":"A finite number.","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["number"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"},"integer":{"type":"boolean","description":"Whole numbers only. Absent: false."},"minimum":{"type":"number"},"maximum":{"type":"number"}}},{"title":"BooleanField","type":"object","description":"true or false; absent reads as false, so it cannot be required to publish.","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["boolean"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"migration":{"$ref":"#/$defs/FieldMigration"}}},{"title":"DateField","type":"object","description":"A calendar day without a zone (YYYY-MM-DD).","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["date"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"}}},{"title":"DateTimeField","type":"object","description":"An instant with the zone it was entered in: {at, timeZone} (RFC-0008 §9.1).","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["datetime"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"}}},{"title":"UrlField","type":"object","description":"A link: https, http, mailto, tel, a site-relative path or a #fragment; at most 2048 characters.","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["url"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"}}},{"title":"ImageField","type":"object","description":"An image Asset of the same site, by id (asset_…).","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["image"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"}}},{"title":"SelectField","type":"object","description":"One of a closed list of options, or several when multiple.","additionalProperties":false,"required":["key","type","labels","localized","options"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["select"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"},"options":{"type":"array","minItems":1,"maxItems":50,"items":{"$ref":"#/$defs/FieldOption"}},"multiple":{"type":"boolean","description":"The value is a list of option values. Absent: false."}}}]};
+const schema57 = {"description":"RFC-0041: one typed custom field of a collection, discriminated by `type`.","oneOf":[{"title":"TextField","type":"object","description":"Plain text: a string of at most maxLength characters.","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["text"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"},"maxLength":{"type":"integer","description":"Absent: 5000.","minimum":1,"maximum":5000},"multiline":{"type":"boolean","description":"Line breaks allowed. Absent: false."},"searchable":{"type":"boolean","description":"Its words go into site search. Absent: false."}}},{"title":"RichTextField","type":"object","description":"Cloudhal Markdown v1 (ADR-0028), at most 50 000 characters.","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["richText"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"},"searchable":{"type":"boolean","description":"Its words go into site search. Absent: false."}}},{"title":"NumberField","type":"object","description":"A finite number.","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["number"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"},"integer":{"type":"boolean","description":"Whole numbers only. Absent: false."},"minimum":{"type":"number"},"maximum":{"type":"number"}}},{"title":"BooleanField","type":"object","description":"true or false; absent reads as false, so it cannot be required to publish.","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["boolean"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"migration":{"$ref":"#/$defs/FieldMigration"}}},{"title":"DateField","type":"object","description":"A calendar day without a zone (YYYY-MM-DD).","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["date"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"}}},{"title":"DateTimeField","type":"object","description":"An instant with the zone it was entered in: {at, timeZone} (RFC-0008 §9.1).","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["datetime"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"}}},{"title":"UrlField","type":"object","description":"A link: https, http, mailto, tel, a site-relative path or a #fragment; at most 2048 characters.","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["url"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"}}},{"title":"ImageField","type":"object","description":"An image Asset of the same site, by id (asset_…).","additionalProperties":false,"required":["key","type","labels","localized"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["image"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"}}},{"title":"SelectField","type":"object","description":"One of a closed list of options, or several when multiple.","additionalProperties":false,"required":["key","type","labels","localized","options"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["select"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own value (missing reads the original's); false: only the translation group's original holds it."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs a value. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"},"options":{"type":"array","minItems":1,"maxItems":50,"items":{"$ref":"#/$defs/FieldOption"}},"multiple":{"type":"boolean","description":"The value is a list of option values. Absent: false."}}},{"title":"ReferenceField","type":"object","description":"RFC-0045: entries of other (or this) collections of the same site, by translation group id (entry_…): the id of the group's original, which every language version and edition carries in translation.group.","additionalProperties":false,"required":["key","type","labels","localized","collections"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["reference"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version picks its own targets; false: only the translation group's original holds them, and every version resolves them in its own language."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs at least one target. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"},"collections":{"type":"array","description":"The collections a target may belong to: declared collections of this site (checked in code).","minItems":1,"maxItems":5,"uniqueItems":true,"items":{"$ref":"#/$defs/Slug"}},"multiple":{"type":"boolean","description":"The value is an ordered list of targets. Absent: false."},"maxItems":{"type":"integer","description":"With multiple: at most this many targets. Absent: 20.","minimum":1,"maximum":50},"where":{"type":"array","description":"Only with one target collection: conditions on its list fields that a newly chosen target must meet (no `now`; checked in code). Narrows the picker; not re-checked when the target changes later.","minItems":1,"maxItems":4,"items":{"$ref":"#/$defs/FieldCondition"}}}},{"title":"RepeaterField","type":"object","description":"RFC-0045: an ordered list of items, each a flat object of sub-fields. A repeater cannot contain a repeater.","additionalProperties":false,"required":["key","type","labels","localized","fields"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["repeater"]},"labels":{"$ref":"#/$defs/FieldLabels"},"localized":{"type":"boolean","description":"true: each language version has its own list (missing reads the original's); false: only the original holds the list."},"requiredToPublish":{"type":"boolean","description":"An entry in review or published needs at least one item. Absent: false."},"migration":{"$ref":"#/$defs/FieldMigration"},"fields":{"type":"array","description":"The sub-fields of every item, in form order. Unique keys (checked in code).","minItems":1,"maxItems":10,"items":{"$ref":"#/$defs/SubFieldDefinition"}},"maxItems":{"type":"integer","description":"At most this many items. Absent: 20.","minimum":1,"maximum":50},"itemLabel":{"$ref":"#/$defs/FieldKey","description":"The text, select or reference sub-field that names an item in Studio and approval prompts. Absent: the first such sub-field."}}}]};
+const schema79 = {"type":"object","description":"RFC-0042: one condition on a typed field of the listed collection. `value` is read by the field's type: a number, YYYY-MM-DD, an RFC 3339 instant, true/false, an option value, or `now`. `values` only with `in`.","additionalProperties":false,"required":["field","op"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"op":{"type":"string","enum":["eq","ne","lt","lte","gt","gte","in","set","unset"]},"value":{"type":"string","minLength":1,"maxLength":100},"values":{"type":"array","minItems":1,"maxItems":20,"uniqueItems":true,"items":{"type":"string","minLength":1,"maxLength":100}}}};
const schema59 = {"type":"object","description":"The field's label and help per locale. Missing a locale: the default locale's, then the key.","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"$ref":"#/$defs/FieldLabel"}};
const schema61 = {"type":"object","additionalProperties":false,"required":["label"],"properties":{"label":{"type":"string","minLength":1,"maxLength":100},"help":{"type":"string","maxLength":300}}};
@@ -1022,7 +1023,7 @@ return errors === 0;
}
validate26.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-const schema62 = {"type":"object","description":"RFC-0041 §6: present only while a field operation rewrites entries.","additionalProperties":false,"required":["id"],"properties":{"id":{"$ref":"#/$defs/Id","description":"The operation's \"before\" snapshot."},"fromKey":{"$ref":"#/$defs/FieldKey"},"fromType":{"type":"string","enum":["text","richText","number","boolean","date","datetime","url","image","select"]},"fromLocalized":{"type":"boolean"}}};
+const schema62 = {"type":"object","description":"RFC-0041 §6: present only while a field operation rewrites entries.","additionalProperties":false,"required":["id"],"properties":{"id":{"$ref":"#/$defs/Id","description":"The operation's \"before\" snapshot."},"fromKey":{"$ref":"#/$defs/FieldKey"},"fromType":{"type":"string","enum":["text","richText","number","boolean","date","datetime","url","image","select","reference","repeater"]},"fromLocalized":{"type":"boolean"},"sub":{"$ref":"#/$defs/FieldKey","description":"RFC-0045: the repeater's sub-field the operation rewrites (its key once the operation is done); fromKey and fromType then describe the sub-field."}}};
function validate28(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
@@ -1046,7 +1047,7 @@ vErrors.push(err0);
errors++;
}
for(const key0 in data){
-if(!((((key0 === "id") || (key0 === "fromKey")) || (key0 === "fromType")) || (key0 === "fromLocalized"))){
+if(!(((((key0 === "id") || (key0 === "fromKey")) || (key0 === "fromType")) || (key0 === "fromLocalized")) || (key0 === "sub"))){
const err1 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};
if(vErrors === null){
vErrors = [err1];
@@ -1139,7 +1140,7 @@ vErrors.push(err8);
}
errors++;
}
-if(!(((((((((data2 === "text") || (data2 === "richText")) || (data2 === "number")) || (data2 === "boolean")) || (data2 === "date")) || (data2 === "datetime")) || (data2 === "url")) || (data2 === "image")) || (data2 === "select"))){
+if(!(((((((((((data2 === "text") || (data2 === "richText")) || (data2 === "number")) || (data2 === "boolean")) || (data2 === "date")) || (data2 === "datetime")) || (data2 === "url")) || (data2 === "image")) || (data2 === "select")) || (data2 === "reference")) || (data2 === "repeater"))){
const err9 = {instancePath:instancePath+"/fromType",schemaPath:"#/properties/fromType/enum",keyword:"enum",params:{allowedValues: schema62.properties.fromType.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err9];
@@ -1162,9 +1163,11 @@ vErrors.push(err10);
errors++;
}
}
-}
-else {
-const err11 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};
+if(data.sub !== undefined){
+let data4 = data.sub;
+if(typeof data4 === "string"){
+if(!pattern22.test(data4)){
+const err11 = {instancePath:instancePath+"/sub",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
if(vErrors === null){
vErrors = [err11];
}
@@ -1173,12 +1176,35 @@ vErrors.push(err11);
}
errors++;
}
+}
+else {
+const err12 = {instancePath:instancePath+"/sub",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err12];
+}
+else {
+vErrors.push(err12);
+}
+errors++;
+}
+}
+}
+else {
+const err13 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};
+if(vErrors === null){
+vErrors = [err13];
+}
+else {
+vErrors.push(err13);
+}
+errors++;
+}
validate28.errors = vErrors;
return errors === 0;
}
validate28.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-const schema73 = {"type":"object","description":"One option of a select field. Values are unique in the field (checked in code).","additionalProperties":false,"required":["value","labels"],"properties":{"value":{"$ref":"#/$defs/Slug","description":"Stored in entries, never shown."},"labels":{"type":"object","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"type":"string","minLength":1,"maxLength":100}}}};
+const schema74 = {"type":"object","description":"One option of a select field. Values are unique in the field (checked in code).","additionalProperties":false,"required":["value","labels"],"properties":{"value":{"$ref":"#/$defs/Slug","description":"Stored in entries, never shown."},"labels":{"type":"object","propertyNames":{"$ref":"#/$defs/Locale"},"minProperties":1,"additionalProperties":{"type":"string","minLength":1,"maxLength":100}}}};
function validate46(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
@@ -1380,11 +1406,12 @@ return errors === 0;
}
validate46.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+const schema81 = {"description":"RFC-0045: one sub-field of a repeater item, discriminated by `type`. Only types whose value is a scalar: no datetime, no multiple select or reference, no repeater. No localized and no migration: the repeater's apply.","oneOf":[{"title":"TextSubField","type":"object","additionalProperties":false,"required":["key","type","labels"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["text"]},"labels":{"$ref":"#/$defs/FieldLabels"},"requiredToPublish":{"type":"boolean","description":"Every item of an entry in review or published needs a value. Absent: false."},"maxLength":{"type":"integer","description":"Absent: 5000.","minimum":1,"maximum":5000},"multiline":{"type":"boolean","description":"Line breaks allowed. Absent: false."}}},{"title":"RichTextSubField","type":"object","additionalProperties":false,"required":["key","type","labels"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["richText"]},"labels":{"$ref":"#/$defs/FieldLabels"},"requiredToPublish":{"type":"boolean","description":"Every item of an entry in review or published needs a value. Absent: false."}}},{"title":"NumberSubField","type":"object","additionalProperties":false,"required":["key","type","labels"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["number"]},"labels":{"$ref":"#/$defs/FieldLabels"},"requiredToPublish":{"type":"boolean","description":"Every item of an entry in review or published needs a value. Absent: false."},"integer":{"type":"boolean","description":"Whole numbers only. Absent: false."},"minimum":{"type":"number"},"maximum":{"type":"number"}}},{"title":"BooleanSubField","type":"object","additionalProperties":false,"required":["key","type","labels"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["boolean"]},"labels":{"$ref":"#/$defs/FieldLabels"}}},{"title":"DateSubField","type":"object","additionalProperties":false,"required":["key","type","labels"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["date"]},"labels":{"$ref":"#/$defs/FieldLabels"},"requiredToPublish":{"type":"boolean","description":"Every item of an entry in review or published needs a value. Absent: false."}}},{"title":"UrlSubField","type":"object","additionalProperties":false,"required":["key","type","labels"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["url"]},"labels":{"$ref":"#/$defs/FieldLabels"},"requiredToPublish":{"type":"boolean","description":"Every item of an entry in review or published needs a value. Absent: false."}}},{"title":"ImageSubField","type":"object","additionalProperties":false,"required":["key","type","labels"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["image"]},"labels":{"$ref":"#/$defs/FieldLabels"},"requiredToPublish":{"type":"boolean","description":"Every item of an entry in review or published needs a value. Absent: false."}}},{"title":"SelectSubField","type":"object","additionalProperties":false,"required":["key","type","labels","options"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["select"]},"labels":{"$ref":"#/$defs/FieldLabels"},"requiredToPublish":{"type":"boolean","description":"Every item of an entry in review or published needs a value. Absent: false."},"options":{"type":"array","minItems":1,"maxItems":50,"items":{"$ref":"#/$defs/FieldOption"}}}},{"title":"ReferenceSubField","type":"object","additionalProperties":false,"required":["key","type","labels","collections"],"properties":{"key":{"$ref":"#/$defs/FieldKey"},"type":{"type":"string","enum":["reference"]},"labels":{"$ref":"#/$defs/FieldLabels"},"requiredToPublish":{"type":"boolean","description":"Every item of an entry in review or published needs a value. Absent: false."},"collections":{"type":"array","description":"The collections a target may belong to (checked in code).","minItems":1,"maxItems":5,"uniqueItems":true,"items":{"$ref":"#/$defs/Slug"}}}}]};
-function validate25(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate52(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate25.evaluated;
+const evaluated0 = validate52.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -1426,8 +1453,9 @@ vErrors.push(err2);
}
errors++;
}
-if(data.localized === undefined){
-const err3 = {instancePath,schemaPath:"#/oneOf/0/required",keyword:"required",params:{missingProperty: "localized"},message:"must have required property '"+"localized"+"'"};
+for(const key0 in data){
+if(!((((((key0 === "key") || (key0 === "type")) || (key0 === "labels")) || (key0 === "requiredToPublish")) || (key0 === "maxLength")) || (key0 === "multiline"))){
+const err3 = {instancePath,schemaPath:"#/oneOf/0/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};
if(vErrors === null){
vErrors = [err3];
}
@@ -1436,39 +1464,28 @@ vErrors.push(err3);
}
errors++;
}
-for(const key0 in data){
-if(!(func1.call(schema57.oneOf[0].properties, key0))){
-const err4 = {instancePath,schemaPath:"#/oneOf/0/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};
-if(vErrors === null){
-vErrors = [err4];
-}
-else {
-vErrors.push(err4);
-}
-errors++;
-}
}
if(data.key !== undefined){
let data0 = data.key;
if(typeof data0 === "string"){
if(!pattern22.test(data0)){
-const err5 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
+const err4 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
if(vErrors === null){
-vErrors = [err5];
+vErrors = [err4];
}
else {
-vErrors.push(err5);
+vErrors.push(err4);
}
errors++;
}
}
else {
-const err6 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
+const err5 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
if(vErrors === null){
-vErrors = [err6];
+vErrors = [err5];
}
else {
-vErrors.push(err6);
+vErrors.push(err5);
}
errors++;
}
@@ -1476,22 +1493,22 @@ errors++;
if(data.type !== undefined){
let data1 = data.type;
if(typeof data1 !== "string"){
-const err7 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/0/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
+const err6 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/0/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
if(vErrors === null){
-vErrors = [err7];
+vErrors = [err6];
}
else {
-vErrors.push(err7);
+vErrors.push(err6);
}
errors++;
}
if(!(data1 === "text")){
-const err8 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/0/properties/type/enum",keyword:"enum",params:{allowedValues: schema57.oneOf[0].properties.type.enum},message:"must be equal to one of the allowed values"};
+const err7 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/0/properties/type/enum",keyword:"enum",params:{allowedValues: schema81.oneOf[0].properties.type.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
-vErrors = [err8];
+vErrors = [err7];
}
else {
-vErrors.push(err8);
+vErrors.push(err7);
}
errors++;
}
@@ -1502,40 +1519,43 @@ vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.error
errors = vErrors.length;
}
}
-if(data.localized !== undefined){
-if(typeof data.localized !== "boolean"){
-const err9 = {instancePath:instancePath+"/localized",schemaPath:"#/oneOf/0/properties/localized/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(data.requiredToPublish !== undefined){
+if(typeof data.requiredToPublish !== "boolean"){
+const err8 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/0/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
if(vErrors === null){
-vErrors = [err9];
+vErrors = [err8];
}
else {
-vErrors.push(err9);
+vErrors.push(err8);
}
errors++;
}
}
-if(data.requiredToPublish !== undefined){
-if(typeof data.requiredToPublish !== "boolean"){
-const err10 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/0/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(data.maxLength !== undefined){
+let data4 = data.maxLength;
+if(!(((typeof data4 == "number") && (!(data4 % 1) && !isNaN(data4))) && (isFinite(data4)))){
+const err9 = {instancePath:instancePath+"/maxLength",schemaPath:"#/oneOf/0/properties/maxLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"};
if(vErrors === null){
-vErrors = [err10];
+vErrors = [err9];
}
else {
-vErrors.push(err10);
+vErrors.push(err9);
}
errors++;
}
+if((typeof data4 == "number") && (isFinite(data4))){
+if(data4 > 5000 || isNaN(data4)){
+const err10 = {instancePath:instancePath+"/maxLength",schemaPath:"#/oneOf/0/properties/maxLength/maximum",keyword:"maximum",params:{comparison: "<=", limit: 5000},message:"must be <= 5000"};
+if(vErrors === null){
+vErrors = [err10];
}
-if(data.migration !== undefined){
-if(!(validate28(data.migration, {instancePath:instancePath+"/migration",parentData:data,parentDataProperty:"migration",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate28.errors : vErrors.concat(validate28.errors);
-errors = vErrors.length;
+else {
+vErrors.push(err10);
}
+errors++;
}
-if(data.maxLength !== undefined){
-let data6 = data.maxLength;
-if(!(((typeof data6 == "number") && (!(data6 % 1) && !isNaN(data6))) && (isFinite(data6)))){
-const err11 = {instancePath:instancePath+"/maxLength",schemaPath:"#/oneOf/0/properties/maxLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"};
+if(data4 < 1 || isNaN(data4)){
+const err11 = {instancePath:instancePath+"/maxLength",schemaPath:"#/oneOf/0/properties/maxLength/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};
if(vErrors === null){
vErrors = [err11];
}
@@ -1544,9 +1564,11 @@ vErrors.push(err11);
}
errors++;
}
-if((typeof data6 == "number") && (isFinite(data6))){
-if(data6 > 5000 || isNaN(data6)){
-const err12 = {instancePath:instancePath+"/maxLength",schemaPath:"#/oneOf/0/properties/maxLength/maximum",keyword:"maximum",params:{comparison: "<=", limit: 5000},message:"must be <= 5000"};
+}
+}
+if(data.multiline !== undefined){
+if(typeof data.multiline !== "boolean"){
+const err12 = {instancePath:instancePath+"/multiline",schemaPath:"#/oneOf/0/properties/multiline/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
if(vErrors === null){
vErrors = [err12];
}
@@ -1555,8 +1577,10 @@ vErrors.push(err12);
}
errors++;
}
-if(data6 < 1 || isNaN(data6)){
-const err13 = {instancePath:instancePath+"/maxLength",schemaPath:"#/oneOf/0/properties/maxLength/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};
+}
+}
+else {
+const err13 = {instancePath,schemaPath:"#/oneOf/0/type",keyword:"type",params:{type: "object"},message:"must be object"};
if(vErrors === null){
vErrors = [err13];
}
@@ -1565,11 +1589,16 @@ vErrors.push(err13);
}
errors++;
}
+var _valid0 = _errs1 === errors;
+if(_valid0){
+valid0 = true;
+passing0 = 0;
+var props0 = true;
}
-}
-if(data.multiline !== undefined){
-if(typeof data.multiline !== "boolean"){
-const err14 = {instancePath:instancePath+"/multiline",schemaPath:"#/oneOf/0/properties/multiline/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+const _errs16 = errors;
+if(data && typeof data == "object" && !Array.isArray(data)){
+if(data.key === undefined){
+const err14 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
if(vErrors === null){
vErrors = [err14];
}
@@ -1578,10 +1607,8 @@ vErrors.push(err14);
}
errors++;
}
-}
-if(data.searchable !== undefined){
-if(typeof data.searchable !== "boolean"){
-const err15 = {instancePath:instancePath+"/searchable",schemaPath:"#/oneOf/0/properties/searchable/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(data.type === undefined){
+const err15 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
if(vErrors === null){
vErrors = [err15];
}
@@ -1590,10 +1617,8 @@ vErrors.push(err15);
}
errors++;
}
-}
-}
-else {
-const err16 = {instancePath,schemaPath:"#/oneOf/0/type",keyword:"type",params:{type: "object"},message:"must be object"};
+if(data.labels === undefined){
+const err16 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
if(vErrors === null){
vErrors = [err16];
}
@@ -1602,16 +1627,9 @@ vErrors.push(err16);
}
errors++;
}
-var _valid0 = _errs1 === errors;
-if(_valid0){
-valid0 = true;
-passing0 = 0;
-var props0 = true;
-}
-const _errs21 = errors;
-if(data && typeof data == "object" && !Array.isArray(data)){
-if(data.key === undefined){
-const err17 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
+for(const key1 in data){
+if(!((((key1 === "key") || (key1 === "type")) || (key1 === "labels")) || (key1 === "requiredToPublish"))){
+const err17 = {instancePath,schemaPath:"#/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};
if(vErrors === null){
vErrors = [err17];
}
@@ -1620,8 +1638,12 @@ vErrors.push(err17);
}
errors++;
}
-if(data.type === undefined){
-const err18 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
+}
+if(data.key !== undefined){
+let data6 = data.key;
+if(typeof data6 === "string"){
+if(!pattern22.test(data6)){
+const err18 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
if(vErrors === null){
vErrors = [err18];
}
@@ -1630,8 +1652,9 @@ vErrors.push(err18);
}
errors++;
}
-if(data.labels === undefined){
-const err19 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
+}
+else {
+const err19 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
if(vErrors === null){
vErrors = [err19];
}
@@ -1640,8 +1663,11 @@ vErrors.push(err19);
}
errors++;
}
-if(data.localized === undefined){
-const err20 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "localized"},message:"must have required property '"+"localized"+"'"};
+}
+if(data.type !== undefined){
+let data7 = data.type;
+if(typeof data7 !== "string"){
+const err20 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/1/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
if(vErrors === null){
vErrors = [err20];
}
@@ -1650,9 +1676,8 @@ vErrors.push(err20);
}
errors++;
}
-for(const key1 in data){
-if(!(((((((key1 === "key") || (key1 === "type")) || (key1 === "labels")) || (key1 === "localized")) || (key1 === "requiredToPublish")) || (key1 === "migration")) || (key1 === "searchable"))){
-const err21 = {instancePath,schemaPath:"#/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};
+if(!(data7 === "richText")){
+const err21 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/1/properties/type/enum",keyword:"enum",params:{allowedValues: schema81.oneOf[1].properties.type.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err21];
}
@@ -1662,11 +1687,15 @@ vErrors.push(err21);
errors++;
}
}
-if(data.key !== undefined){
-let data9 = data.key;
-if(typeof data9 === "string"){
-if(!pattern22.test(data9)){
-const err22 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
+if(data.labels !== undefined){
+if(!(validate26(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors);
+errors = vErrors.length;
+}
+}
+if(data.requiredToPublish !== undefined){
+if(typeof data.requiredToPublish !== "boolean"){
+const err22 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/1/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
if(vErrors === null){
vErrors = [err22];
}
@@ -1676,8 +1705,9 @@ vErrors.push(err22);
errors++;
}
}
+}
else {
-const err23 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
+const err23 = {instancePath,schemaPath:"#/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};
if(vErrors === null){
vErrors = [err23];
}
@@ -1686,11 +1716,23 @@ vErrors.push(err23);
}
errors++;
}
+var _valid0 = _errs16 === errors;
+if(_valid0 && valid0){
+valid0 = false;
+passing0 = [passing0, 1];
}
-if(data.type !== undefined){
-let data10 = data.type;
-if(typeof data10 !== "string"){
-const err24 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/1/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
+else {
+if(_valid0){
+valid0 = true;
+passing0 = 1;
+if(props0 !== true){
+props0 = true;
+}
+}
+const _errs27 = errors;
+if(data && typeof data == "object" && !Array.isArray(data)){
+if(data.key === undefined){
+const err24 = {instancePath,schemaPath:"#/oneOf/2/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
if(vErrors === null){
vErrors = [err24];
}
@@ -1699,8 +1741,8 @@ vErrors.push(err24);
}
errors++;
}
-if(!(data10 === "richText")){
-const err25 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/1/properties/type/enum",keyword:"enum",params:{allowedValues: schema57.oneOf[1].properties.type.enum},message:"must be equal to one of the allowed values"};
+if(data.type === undefined){
+const err25 = {instancePath,schemaPath:"#/oneOf/2/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
if(vErrors === null){
vErrors = [err25];
}
@@ -1709,16 +1751,8 @@ vErrors.push(err25);
}
errors++;
}
-}
-if(data.labels !== undefined){
-if(!(validate26(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors);
-errors = vErrors.length;
-}
-}
-if(data.localized !== undefined){
-if(typeof data.localized !== "boolean"){
-const err26 = {instancePath:instancePath+"/localized",schemaPath:"#/oneOf/1/properties/localized/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(data.labels === undefined){
+const err26 = {instancePath,schemaPath:"#/oneOf/2/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
if(vErrors === null){
vErrors = [err26];
}
@@ -1727,10 +1761,9 @@ vErrors.push(err26);
}
errors++;
}
-}
-if(data.requiredToPublish !== undefined){
-if(typeof data.requiredToPublish !== "boolean"){
-const err27 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/1/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+for(const key2 in data){
+if(!(((((((key2 === "key") || (key2 === "type")) || (key2 === "labels")) || (key2 === "requiredToPublish")) || (key2 === "integer")) || (key2 === "minimum")) || (key2 === "maximum"))){
+const err27 = {instancePath,schemaPath:"#/oneOf/2/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};
if(vErrors === null){
vErrors = [err27];
}
@@ -1740,15 +1773,11 @@ vErrors.push(err27);
errors++;
}
}
-if(data.migration !== undefined){
-if(!(validate28(data.migration, {instancePath:instancePath+"/migration",parentData:data,parentDataProperty:"migration",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate28.errors : vErrors.concat(validate28.errors);
-errors = vErrors.length;
-}
-}
-if(data.searchable !== undefined){
-if(typeof data.searchable !== "boolean"){
-const err28 = {instancePath:instancePath+"/searchable",schemaPath:"#/oneOf/1/properties/searchable/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(data.key !== undefined){
+let data10 = data.key;
+if(typeof data10 === "string"){
+if(!pattern22.test(data10)){
+const err28 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
if(vErrors === null){
vErrors = [err28];
}
@@ -1758,9 +1787,8 @@ vErrors.push(err28);
errors++;
}
}
-}
else {
-const err29 = {instancePath,schemaPath:"#/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};
+const err29 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
if(vErrors === null){
vErrors = [err29];
}
@@ -1769,23 +1797,11 @@ vErrors.push(err29);
}
errors++;
}
-var _valid0 = _errs21 === errors;
-if(_valid0 && valid0){
-valid0 = false;
-passing0 = [passing0, 1];
-}
-else {
-if(_valid0){
-valid0 = true;
-passing0 = 1;
-if(props0 !== true){
-props0 = true;
-}
}
-const _errs37 = errors;
-if(data && typeof data == "object" && !Array.isArray(data)){
-if(data.key === undefined){
-const err30 = {instancePath,schemaPath:"#/oneOf/2/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
+if(data.type !== undefined){
+let data11 = data.type;
+if(typeof data11 !== "string"){
+const err30 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/2/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
if(vErrors === null){
vErrors = [err30];
}
@@ -1794,8 +1810,8 @@ vErrors.push(err30);
}
errors++;
}
-if(data.type === undefined){
-const err31 = {instancePath,schemaPath:"#/oneOf/2/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
+if(!(data11 === "number")){
+const err31 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/2/properties/type/enum",keyword:"enum",params:{allowedValues: schema81.oneOf[2].properties.type.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err31];
}
@@ -1804,8 +1820,16 @@ vErrors.push(err31);
}
errors++;
}
-if(data.labels === undefined){
-const err32 = {instancePath,schemaPath:"#/oneOf/2/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
+}
+if(data.labels !== undefined){
+if(!(validate26(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors);
+errors = vErrors.length;
+}
+}
+if(data.requiredToPublish !== undefined){
+if(typeof data.requiredToPublish !== "boolean"){
+const err32 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/2/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
if(vErrors === null){
vErrors = [err32];
}
@@ -1814,8 +1838,10 @@ vErrors.push(err32);
}
errors++;
}
-if(data.localized === undefined){
-const err33 = {instancePath,schemaPath:"#/oneOf/2/required",keyword:"required",params:{missingProperty: "localized"},message:"must have required property '"+"localized"+"'"};
+}
+if(data.integer !== undefined){
+if(typeof data.integer !== "boolean"){
+const err33 = {instancePath:instancePath+"/integer",schemaPath:"#/oneOf/2/properties/integer/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
if(vErrors === null){
vErrors = [err33];
}
@@ -1824,9 +1850,11 @@ vErrors.push(err33);
}
errors++;
}
-for(const key2 in data){
-if(!(func1.call(schema57.oneOf[2].properties, key2))){
-const err34 = {instancePath,schemaPath:"#/oneOf/2/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};
+}
+if(data.minimum !== undefined){
+let data15 = data.minimum;
+if(!((typeof data15 == "number") && (isFinite(data15)))){
+const err34 = {instancePath:instancePath+"/minimum",schemaPath:"#/oneOf/2/properties/minimum/type",keyword:"type",params:{type: "number"},message:"must be number"};
if(vErrors === null){
vErrors = [err34];
}
@@ -1836,11 +1864,10 @@ vErrors.push(err34);
errors++;
}
}
-if(data.key !== undefined){
-let data16 = data.key;
-if(typeof data16 === "string"){
-if(!pattern22.test(data16)){
-const err35 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
+if(data.maximum !== undefined){
+let data16 = data.maximum;
+if(!((typeof data16 == "number") && (isFinite(data16)))){
+const err35 = {instancePath:instancePath+"/maximum",schemaPath:"#/oneOf/2/properties/maximum/type",keyword:"type",params:{type: "number"},message:"must be number"};
if(vErrors === null){
vErrors = [err35];
}
@@ -1850,8 +1877,9 @@ vErrors.push(err35);
errors++;
}
}
+}
else {
-const err36 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
+const err36 = {instancePath,schemaPath:"#/oneOf/2/type",keyword:"type",params:{type: "object"},message:"must be object"};
if(vErrors === null){
vErrors = [err36];
}
@@ -1860,11 +1888,23 @@ vErrors.push(err36);
}
errors++;
}
+var _valid0 = _errs27 === errors;
+if(_valid0 && valid0){
+valid0 = false;
+passing0 = [passing0, 2];
+}
+else {
+if(_valid0){
+valid0 = true;
+passing0 = 2;
+if(props0 !== true){
+props0 = true;
}
-if(data.type !== undefined){
-let data17 = data.type;
-if(typeof data17 !== "string"){
-const err37 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/2/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
+}
+const _errs44 = errors;
+if(data && typeof data == "object" && !Array.isArray(data)){
+if(data.key === undefined){
+const err37 = {instancePath,schemaPath:"#/oneOf/3/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
if(vErrors === null){
vErrors = [err37];
}
@@ -1873,8 +1913,8 @@ vErrors.push(err37);
}
errors++;
}
-if(!(data17 === "number")){
-const err38 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/2/properties/type/enum",keyword:"enum",params:{allowedValues: schema57.oneOf[2].properties.type.enum},message:"must be equal to one of the allowed values"};
+if(data.type === undefined){
+const err38 = {instancePath,schemaPath:"#/oneOf/3/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
if(vErrors === null){
vErrors = [err38];
}
@@ -1883,16 +1923,8 @@ vErrors.push(err38);
}
errors++;
}
-}
-if(data.labels !== undefined){
-if(!(validate26(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors);
-errors = vErrors.length;
-}
-}
-if(data.localized !== undefined){
-if(typeof data.localized !== "boolean"){
-const err39 = {instancePath:instancePath+"/localized",schemaPath:"#/oneOf/2/properties/localized/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(data.labels === undefined){
+const err39 = {instancePath,schemaPath:"#/oneOf/3/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
if(vErrors === null){
vErrors = [err39];
}
@@ -1901,10 +1933,9 @@ vErrors.push(err39);
}
errors++;
}
-}
-if(data.requiredToPublish !== undefined){
-if(typeof data.requiredToPublish !== "boolean"){
-const err40 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/2/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+for(const key3 in data){
+if(!(((key3 === "key") || (key3 === "type")) || (key3 === "labels"))){
+const err40 = {instancePath,schemaPath:"#/oneOf/3/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};
if(vErrors === null){
vErrors = [err40];
}
@@ -1914,15 +1945,11 @@ vErrors.push(err40);
errors++;
}
}
-if(data.migration !== undefined){
-if(!(validate28(data.migration, {instancePath:instancePath+"/migration",parentData:data,parentDataProperty:"migration",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate28.errors : vErrors.concat(validate28.errors);
-errors = vErrors.length;
-}
-}
-if(data.integer !== undefined){
-if(typeof data.integer !== "boolean"){
-const err41 = {instancePath:instancePath+"/integer",schemaPath:"#/oneOf/2/properties/integer/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(data.key !== undefined){
+let data17 = data.key;
+if(typeof data17 === "string"){
+if(!pattern22.test(data17)){
+const err41 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
if(vErrors === null){
vErrors = [err41];
}
@@ -1932,10 +1959,8 @@ vErrors.push(err41);
errors++;
}
}
-if(data.minimum !== undefined){
-let data23 = data.minimum;
-if(!((typeof data23 == "number") && (isFinite(data23)))){
-const err42 = {instancePath:instancePath+"/minimum",schemaPath:"#/oneOf/2/properties/minimum/type",keyword:"type",params:{type: "number"},message:"must be number"};
+else {
+const err42 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
if(vErrors === null){
vErrors = [err42];
}
@@ -1945,10 +1970,10 @@ vErrors.push(err42);
errors++;
}
}
-if(data.maximum !== undefined){
-let data24 = data.maximum;
-if(!((typeof data24 == "number") && (isFinite(data24)))){
-const err43 = {instancePath:instancePath+"/maximum",schemaPath:"#/oneOf/2/properties/maximum/type",keyword:"type",params:{type: "number"},message:"must be number"};
+if(data.type !== undefined){
+let data18 = data.type;
+if(typeof data18 !== "string"){
+const err43 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/3/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
if(vErrors === null){
vErrors = [err43];
}
@@ -1957,45 +1982,51 @@ vErrors.push(err43);
}
errors++;
}
+if(!(data18 === "boolean")){
+const err44 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/3/properties/type/enum",keyword:"enum",params:{allowedValues: schema81.oneOf[3].properties.type.enum},message:"must be equal to one of the allowed values"};
+if(vErrors === null){
+vErrors = [err44];
+}
+else {
+vErrors.push(err44);
+}
+errors++;
+}
+}
+if(data.labels !== undefined){
+if(!(validate26(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors);
+errors = vErrors.length;
+}
}
}
else {
-const err44 = {instancePath,schemaPath:"#/oneOf/2/type",keyword:"type",params:{type: "object"},message:"must be object"};
+const err45 = {instancePath,schemaPath:"#/oneOf/3/type",keyword:"type",params:{type: "object"},message:"must be object"};
if(vErrors === null){
-vErrors = [err44];
+vErrors = [err45];
}
else {
-vErrors.push(err44);
+vErrors.push(err45);
}
errors++;
}
-var _valid0 = _errs37 === errors;
+var _valid0 = _errs44 === errors;
if(_valid0 && valid0){
valid0 = false;
-passing0 = [passing0, 2];
+passing0 = [passing0, 3];
}
else {
if(_valid0){
valid0 = true;
-passing0 = 2;
+passing0 = 3;
if(props0 !== true){
props0 = true;
}
}
-const _errs57 = errors;
+const _errs53 = errors;
if(data && typeof data == "object" && !Array.isArray(data)){
if(data.key === undefined){
-const err45 = {instancePath,schemaPath:"#/oneOf/3/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
-if(vErrors === null){
-vErrors = [err45];
-}
-else {
-vErrors.push(err45);
-}
-errors++;
-}
-if(data.type === undefined){
-const err46 = {instancePath,schemaPath:"#/oneOf/3/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
+const err46 = {instancePath,schemaPath:"#/oneOf/4/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
if(vErrors === null){
vErrors = [err46];
}
@@ -2004,8 +2035,8 @@ vErrors.push(err46);
}
errors++;
}
-if(data.labels === undefined){
-const err47 = {instancePath,schemaPath:"#/oneOf/3/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
+if(data.type === undefined){
+const err47 = {instancePath,schemaPath:"#/oneOf/4/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
if(vErrors === null){
vErrors = [err47];
}
@@ -2014,8 +2045,8 @@ vErrors.push(err47);
}
errors++;
}
-if(data.localized === undefined){
-const err48 = {instancePath,schemaPath:"#/oneOf/3/required",keyword:"required",params:{missingProperty: "localized"},message:"must have required property '"+"localized"+"'"};
+if(data.labels === undefined){
+const err48 = {instancePath,schemaPath:"#/oneOf/4/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
if(vErrors === null){
vErrors = [err48];
}
@@ -2024,9 +2055,9 @@ vErrors.push(err48);
}
errors++;
}
-for(const key3 in data){
-if(!(((((key3 === "key") || (key3 === "type")) || (key3 === "labels")) || (key3 === "localized")) || (key3 === "migration"))){
-const err49 = {instancePath,schemaPath:"#/oneOf/3/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};
+for(const key4 in data){
+if(!((((key4 === "key") || (key4 === "type")) || (key4 === "labels")) || (key4 === "requiredToPublish"))){
+const err49 = {instancePath,schemaPath:"#/oneOf/4/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"};
if(vErrors === null){
vErrors = [err49];
}
@@ -2037,9 +2068,9 @@ errors++;
}
}
if(data.key !== undefined){
-let data25 = data.key;
-if(typeof data25 === "string"){
-if(!pattern22.test(data25)){
+let data20 = data.key;
+if(typeof data20 === "string"){
+if(!pattern22.test(data20)){
const err50 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
if(vErrors === null){
vErrors = [err50];
@@ -2062,9 +2093,9 @@ errors++;
}
}
if(data.type !== undefined){
-let data26 = data.type;
-if(typeof data26 !== "string"){
-const err52 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/3/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
+let data21 = data.type;
+if(typeof data21 !== "string"){
+const err52 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/4/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
if(vErrors === null){
vErrors = [err52];
}
@@ -2073,8 +2104,8 @@ vErrors.push(err52);
}
errors++;
}
-if(!(data26 === "boolean")){
-const err53 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/3/properties/type/enum",keyword:"enum",params:{allowedValues: schema57.oneOf[3].properties.type.enum},message:"must be equal to one of the allowed values"};
+if(!(data21 === "date")){
+const err53 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/4/properties/type/enum",keyword:"enum",params:{allowedValues: schema81.oneOf[4].properties.type.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err53];
}
@@ -2090,9 +2121,9 @@ vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.error
errors = vErrors.length;
}
}
-if(data.localized !== undefined){
-if(typeof data.localized !== "boolean"){
-const err54 = {instancePath:instancePath+"/localized",schemaPath:"#/oneOf/3/properties/localized/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(data.requiredToPublish !== undefined){
+if(typeof data.requiredToPublish !== "boolean"){
+const err54 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/4/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
if(vErrors === null){
vErrors = [err54];
}
@@ -2102,15 +2133,9 @@ vErrors.push(err54);
errors++;
}
}
-if(data.migration !== undefined){
-if(!(validate28(data.migration, {instancePath:instancePath+"/migration",parentData:data,parentDataProperty:"migration",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate28.errors : vErrors.concat(validate28.errors);
-errors = vErrors.length;
-}
-}
}
else {
-const err55 = {instancePath,schemaPath:"#/oneOf/3/type",keyword:"type",params:{type: "object"},message:"must be object"};
+const err55 = {instancePath,schemaPath:"#/oneOf/4/type",keyword:"type",params:{type: "object"},message:"must be object"};
if(vErrors === null){
vErrors = [err55];
}
@@ -2119,23 +2144,23 @@ vErrors.push(err55);
}
errors++;
}
-var _valid0 = _errs57 === errors;
+var _valid0 = _errs53 === errors;
if(_valid0 && valid0){
valid0 = false;
-passing0 = [passing0, 3];
+passing0 = [passing0, 4];
}
else {
if(_valid0){
valid0 = true;
-passing0 = 3;
+passing0 = 4;
if(props0 !== true){
props0 = true;
}
}
-const _errs69 = errors;
+const _errs64 = errors;
if(data && typeof data == "object" && !Array.isArray(data)){
if(data.key === undefined){
-const err56 = {instancePath,schemaPath:"#/oneOf/4/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
+const err56 = {instancePath,schemaPath:"#/oneOf/5/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
if(vErrors === null){
vErrors = [err56];
}
@@ -2145,7 +2170,7 @@ vErrors.push(err56);
errors++;
}
if(data.type === undefined){
-const err57 = {instancePath,schemaPath:"#/oneOf/4/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
+const err57 = {instancePath,schemaPath:"#/oneOf/5/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
if(vErrors === null){
vErrors = [err57];
}
@@ -2155,7 +2180,7 @@ vErrors.push(err57);
errors++;
}
if(data.labels === undefined){
-const err58 = {instancePath,schemaPath:"#/oneOf/4/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
+const err58 = {instancePath,schemaPath:"#/oneOf/5/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
if(vErrors === null){
vErrors = [err58];
}
@@ -2164,8 +2189,9 @@ vErrors.push(err58);
}
errors++;
}
-if(data.localized === undefined){
-const err59 = {instancePath,schemaPath:"#/oneOf/4/required",keyword:"required",params:{missingProperty: "localized"},message:"must have required property '"+"localized"+"'"};
+for(const key5 in data){
+if(!((((key5 === "key") || (key5 === "type")) || (key5 === "labels")) || (key5 === "requiredToPublish"))){
+const err59 = {instancePath,schemaPath:"#/oneOf/5/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key5},message:"must NOT have additional properties"};
if(vErrors === null){
vErrors = [err59];
}
@@ -2174,9 +2200,12 @@ vErrors.push(err59);
}
errors++;
}
-for(const key4 in data){
-if(!((((((key4 === "key") || (key4 === "type")) || (key4 === "labels")) || (key4 === "localized")) || (key4 === "requiredToPublish")) || (key4 === "migration"))){
-const err60 = {instancePath,schemaPath:"#/oneOf/4/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"};
+}
+if(data.key !== undefined){
+let data24 = data.key;
+if(typeof data24 === "string"){
+if(!pattern22.test(data24)){
+const err60 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
if(vErrors === null){
vErrors = [err60];
}
@@ -2186,11 +2215,8 @@ vErrors.push(err60);
errors++;
}
}
-if(data.key !== undefined){
-let data30 = data.key;
-if(typeof data30 === "string"){
-if(!pattern22.test(data30)){
-const err61 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
+else {
+const err61 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
if(vErrors === null){
vErrors = [err61];
}
@@ -2200,8 +2226,10 @@ vErrors.push(err61);
errors++;
}
}
-else {
-const err62 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(data.type !== undefined){
+let data25 = data.type;
+if(typeof data25 !== "string"){
+const err62 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/5/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
if(vErrors === null){
vErrors = [err62];
}
@@ -2210,11 +2238,8 @@ vErrors.push(err62);
}
errors++;
}
-}
-if(data.type !== undefined){
-let data31 = data.type;
-if(typeof data31 !== "string"){
-const err63 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/4/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(!(data25 === "url")){
+const err63 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/5/properties/type/enum",keyword:"enum",params:{allowedValues: schema81.oneOf[5].properties.type.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err63];
}
@@ -2223,8 +2248,16 @@ vErrors.push(err63);
}
errors++;
}
-if(!(data31 === "date")){
-const err64 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/4/properties/type/enum",keyword:"enum",params:{allowedValues: schema57.oneOf[4].properties.type.enum},message:"must be equal to one of the allowed values"};
+}
+if(data.labels !== undefined){
+if(!(validate26(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors);
+errors = vErrors.length;
+}
+}
+if(data.requiredToPublish !== undefined){
+if(typeof data.requiredToPublish !== "boolean"){
+const err64 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/5/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
if(vErrors === null){
vErrors = [err64];
}
@@ -2234,164 +2267,2364 @@ vErrors.push(err64);
errors++;
}
}
+}
+else {
+const err65 = {instancePath,schemaPath:"#/oneOf/5/type",keyword:"type",params:{type: "object"},message:"must be object"};
+if(vErrors === null){
+vErrors = [err65];
+}
+else {
+vErrors.push(err65);
+}
+errors++;
+}
+var _valid0 = _errs64 === errors;
+if(_valid0 && valid0){
+valid0 = false;
+passing0 = [passing0, 5];
+}
+else {
+if(_valid0){
+valid0 = true;
+passing0 = 5;
+if(props0 !== true){
+props0 = true;
+}
+}
+const _errs75 = errors;
+if(data && typeof data == "object" && !Array.isArray(data)){
+if(data.key === undefined){
+const err66 = {instancePath,schemaPath:"#/oneOf/6/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
+if(vErrors === null){
+vErrors = [err66];
+}
+else {
+vErrors.push(err66);
+}
+errors++;
+}
+if(data.type === undefined){
+const err67 = {instancePath,schemaPath:"#/oneOf/6/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
+if(vErrors === null){
+vErrors = [err67];
+}
+else {
+vErrors.push(err67);
+}
+errors++;
+}
+if(data.labels === undefined){
+const err68 = {instancePath,schemaPath:"#/oneOf/6/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
+if(vErrors === null){
+vErrors = [err68];
+}
+else {
+vErrors.push(err68);
+}
+errors++;
+}
+for(const key6 in data){
+if(!((((key6 === "key") || (key6 === "type")) || (key6 === "labels")) || (key6 === "requiredToPublish"))){
+const err69 = {instancePath,schemaPath:"#/oneOf/6/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key6},message:"must NOT have additional properties"};
+if(vErrors === null){
+vErrors = [err69];
+}
+else {
+vErrors.push(err69);
+}
+errors++;
+}
+}
+if(data.key !== undefined){
+let data28 = data.key;
+if(typeof data28 === "string"){
+if(!pattern22.test(data28)){
+const err70 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
+if(vErrors === null){
+vErrors = [err70];
+}
+else {
+vErrors.push(err70);
+}
+errors++;
+}
+}
+else {
+const err71 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err71];
+}
+else {
+vErrors.push(err71);
+}
+errors++;
+}
+}
+if(data.type !== undefined){
+let data29 = data.type;
+if(typeof data29 !== "string"){
+const err72 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/6/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err72];
+}
+else {
+vErrors.push(err72);
+}
+errors++;
+}
+if(!(data29 === "image")){
+const err73 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/6/properties/type/enum",keyword:"enum",params:{allowedValues: schema81.oneOf[6].properties.type.enum},message:"must be equal to one of the allowed values"};
+if(vErrors === null){
+vErrors = [err73];
+}
+else {
+vErrors.push(err73);
+}
+errors++;
+}
+}
if(data.labels !== undefined){
if(!(validate26(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors);
errors = vErrors.length;
}
}
-if(data.localized !== undefined){
-if(typeof data.localized !== "boolean"){
-const err65 = {instancePath:instancePath+"/localized",schemaPath:"#/oneOf/4/properties/localized/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(data.requiredToPublish !== undefined){
+if(typeof data.requiredToPublish !== "boolean"){
+const err74 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/6/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
if(vErrors === null){
-vErrors = [err65];
+vErrors = [err74];
}
else {
-vErrors.push(err65);
+vErrors.push(err74);
+}
+errors++;
+}
+}
+}
+else {
+const err75 = {instancePath,schemaPath:"#/oneOf/6/type",keyword:"type",params:{type: "object"},message:"must be object"};
+if(vErrors === null){
+vErrors = [err75];
+}
+else {
+vErrors.push(err75);
+}
+errors++;
+}
+var _valid0 = _errs75 === errors;
+if(_valid0 && valid0){
+valid0 = false;
+passing0 = [passing0, 6];
+}
+else {
+if(_valid0){
+valid0 = true;
+passing0 = 6;
+if(props0 !== true){
+props0 = true;
+}
+}
+const _errs86 = errors;
+if(data && typeof data == "object" && !Array.isArray(data)){
+if(data.key === undefined){
+const err76 = {instancePath,schemaPath:"#/oneOf/7/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
+if(vErrors === null){
+vErrors = [err76];
+}
+else {
+vErrors.push(err76);
+}
+errors++;
+}
+if(data.type === undefined){
+const err77 = {instancePath,schemaPath:"#/oneOf/7/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
+if(vErrors === null){
+vErrors = [err77];
+}
+else {
+vErrors.push(err77);
+}
+errors++;
+}
+if(data.labels === undefined){
+const err78 = {instancePath,schemaPath:"#/oneOf/7/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
+if(vErrors === null){
+vErrors = [err78];
+}
+else {
+vErrors.push(err78);
+}
+errors++;
+}
+if(data.options === undefined){
+const err79 = {instancePath,schemaPath:"#/oneOf/7/required",keyword:"required",params:{missingProperty: "options"},message:"must have required property '"+"options"+"'"};
+if(vErrors === null){
+vErrors = [err79];
+}
+else {
+vErrors.push(err79);
+}
+errors++;
+}
+for(const key7 in data){
+if(!(((((key7 === "key") || (key7 === "type")) || (key7 === "labels")) || (key7 === "requiredToPublish")) || (key7 === "options"))){
+const err80 = {instancePath,schemaPath:"#/oneOf/7/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key7},message:"must NOT have additional properties"};
+if(vErrors === null){
+vErrors = [err80];
+}
+else {
+vErrors.push(err80);
+}
+errors++;
+}
+}
+if(data.key !== undefined){
+let data32 = data.key;
+if(typeof data32 === "string"){
+if(!pattern22.test(data32)){
+const err81 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
+if(vErrors === null){
+vErrors = [err81];
+}
+else {
+vErrors.push(err81);
+}
+errors++;
+}
+}
+else {
+const err82 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err82];
+}
+else {
+vErrors.push(err82);
+}
+errors++;
+}
+}
+if(data.type !== undefined){
+let data33 = data.type;
+if(typeof data33 !== "string"){
+const err83 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/7/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err83];
+}
+else {
+vErrors.push(err83);
+}
+errors++;
+}
+if(!(data33 === "select")){
+const err84 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/7/properties/type/enum",keyword:"enum",params:{allowedValues: schema81.oneOf[7].properties.type.enum},message:"must be equal to one of the allowed values"};
+if(vErrors === null){
+vErrors = [err84];
+}
+else {
+vErrors.push(err84);
}
errors++;
}
}
+if(data.labels !== undefined){
+if(!(validate26(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors);
+errors = vErrors.length;
+}
+}
if(data.requiredToPublish !== undefined){
if(typeof data.requiredToPublish !== "boolean"){
-const err66 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/4/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+const err85 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/7/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
if(vErrors === null){
-vErrors = [err66];
+vErrors = [err85];
}
else {
-vErrors.push(err66);
+vErrors.push(err85);
}
errors++;
}
}
-if(data.migration !== undefined){
-if(!(validate28(data.migration, {instancePath:instancePath+"/migration",parentData:data,parentDataProperty:"migration",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate28.errors : vErrors.concat(validate28.errors);
+if(data.options !== undefined){
+let data36 = data.options;
+if(Array.isArray(data36)){
+if(data36.length > 50){
+const err86 = {instancePath:instancePath+"/options",schemaPath:"#/oneOf/7/properties/options/maxItems",keyword:"maxItems",params:{limit: 50},message:"must NOT have more than 50 items"};
+if(vErrors === null){
+vErrors = [err86];
+}
+else {
+vErrors.push(err86);
+}
+errors++;
+}
+if(data36.length < 1){
+const err87 = {instancePath:instancePath+"/options",schemaPath:"#/oneOf/7/properties/options/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};
+if(vErrors === null){
+vErrors = [err87];
+}
+else {
+vErrors.push(err87);
+}
+errors++;
+}
+const len0 = data36.length;
+for(let i0=0; i0 5){
+const err100 = {instancePath:instancePath+"/collections",schemaPath:"#/oneOf/8/properties/collections/maxItems",keyword:"maxItems",params:{limit: 5},message:"must NOT have more than 5 items"};
+if(vErrors === null){
+vErrors = [err100];
+}
+else {
+vErrors.push(err100);
+}
+errors++;
+}
+if(data42.length < 1){
+const err101 = {instancePath:instancePath+"/collections",schemaPath:"#/oneOf/8/properties/collections/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};
+if(vErrors === null){
+vErrors = [err101];
+}
+else {
+vErrors.push(err101);
+}
+errors++;
+}
+const len1 = data42.length;
+for(let i1=0; i1 128){
+const err102 = {instancePath:instancePath+"/collections/" + i1,schemaPath:"#/$defs/Slug/maxLength",keyword:"maxLength",params:{limit: 128},message:"must NOT have more than 128 characters"};
+if(vErrors === null){
+vErrors = [err102];
+}
+else {
+vErrors.push(err102);
+}
+errors++;
+}
+if(func2(data43) < 1){
+const err103 = {instancePath:instancePath+"/collections/" + i1,schemaPath:"#/$defs/Slug/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};
+if(vErrors === null){
+vErrors = [err103];
+}
+else {
+vErrors.push(err103);
+}
+errors++;
+}
+if(!pattern5.test(data43)){
+const err104 = {instancePath:instancePath+"/collections/" + i1,schemaPath:"#/$defs/Slug/pattern",keyword:"pattern",params:{pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$"},message:"must match pattern \""+"^[a-z0-9]+(?:-[a-z0-9]+)*$"+"\""};
+if(vErrors === null){
+vErrors = [err104];
+}
+else {
+vErrors.push(err104);
+}
+errors++;
+}
+}
+else {
+const err105 = {instancePath:instancePath+"/collections/" + i1,schemaPath:"#/$defs/Slug/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err105];
+}
+else {
+vErrors.push(err105);
+}
+errors++;
+}
+}
+let i2 = data42.length;
+let j0;
+if(i2 > 1){
+outer0:
+for(;i2--;){
+for(j0 = i2; j0--;){
+if(func0(data42[i2], data42[j0])){
+const err106 = {instancePath:instancePath+"/collections",schemaPath:"#/oneOf/8/properties/collections/uniqueItems",keyword:"uniqueItems",params:{i: i2, j: j0},message:"must NOT have duplicate items (items ## "+j0+" and "+i2+" are identical)"};
+if(vErrors === null){
+vErrors = [err106];
+}
+else {
+vErrors.push(err106);
+}
+errors++;
+break outer0;
+}
+}
+}
+}
+}
+else {
+const err107 = {instancePath:instancePath+"/collections",schemaPath:"#/oneOf/8/properties/collections/type",keyword:"type",params:{type: "array"},message:"must be array"};
+if(vErrors === null){
+vErrors = [err107];
+}
+else {
+vErrors.push(err107);
+}
+errors++;
+}
+}
+}
+else {
+const err108 = {instancePath,schemaPath:"#/oneOf/8/type",keyword:"type",params:{type: "object"},message:"must be object"};
+if(vErrors === null){
+vErrors = [err108];
+}
+else {
+vErrors.push(err108);
+}
+errors++;
+}
+var _valid0 = _errs100 === errors;
+if(_valid0 && valid0){
+valid0 = false;
+passing0 = [passing0, 8];
+}
+else {
+if(_valid0){
+valid0 = true;
+passing0 = 8;
+if(props0 !== true){
+props0 = true;
+}
+}
+}
+}
+}
+}
+}
+}
+}
+}
+if(!valid0){
+const err109 = {instancePath,schemaPath:"#/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};
+if(vErrors === null){
+vErrors = [err109];
+}
+else {
+vErrors.push(err109);
+}
+errors++;
+}
+else {
+errors = _errs0;
+if(vErrors !== null){
+if(_errs0){
+vErrors.length = _errs0;
+}
+else {
+vErrors = null;
+}
+}
+}
+validate52.errors = vErrors;
+evaluated0.props = props0;
+return errors === 0;
+}
+validate52.evaluated = {"dynamicProps":true,"dynamicItems":false};
+
+
+function validate25(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+let vErrors = null;
+let errors = 0;
+const evaluated0 = validate25.evaluated;
+if(evaluated0.dynamicProps){
+evaluated0.props = undefined;
+}
+if(evaluated0.dynamicItems){
+evaluated0.items = undefined;
+}
+const _errs0 = errors;
+let valid0 = false;
+let passing0 = null;
+const _errs1 = errors;
+if(data && typeof data == "object" && !Array.isArray(data)){
+if(data.key === undefined){
+const err0 = {instancePath,schemaPath:"#/oneOf/0/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
+if(vErrors === null){
+vErrors = [err0];
+}
+else {
+vErrors.push(err0);
+}
+errors++;
+}
+if(data.type === undefined){
+const err1 = {instancePath,schemaPath:"#/oneOf/0/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
+if(vErrors === null){
+vErrors = [err1];
+}
+else {
+vErrors.push(err1);
+}
+errors++;
+}
+if(data.labels === undefined){
+const err2 = {instancePath,schemaPath:"#/oneOf/0/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
+if(vErrors === null){
+vErrors = [err2];
+}
+else {
+vErrors.push(err2);
+}
+errors++;
+}
+if(data.localized === undefined){
+const err3 = {instancePath,schemaPath:"#/oneOf/0/required",keyword:"required",params:{missingProperty: "localized"},message:"must have required property '"+"localized"+"'"};
+if(vErrors === null){
+vErrors = [err3];
+}
+else {
+vErrors.push(err3);
+}
+errors++;
+}
+for(const key0 in data){
+if(!(func1.call(schema57.oneOf[0].properties, key0))){
+const err4 = {instancePath,schemaPath:"#/oneOf/0/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};
+if(vErrors === null){
+vErrors = [err4];
+}
+else {
+vErrors.push(err4);
+}
+errors++;
+}
+}
+if(data.key !== undefined){
+let data0 = data.key;
+if(typeof data0 === "string"){
+if(!pattern22.test(data0)){
+const err5 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
+if(vErrors === null){
+vErrors = [err5];
+}
+else {
+vErrors.push(err5);
+}
+errors++;
+}
+}
+else {
+const err6 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err6];
+}
+else {
+vErrors.push(err6);
+}
+errors++;
+}
+}
+if(data.type !== undefined){
+let data1 = data.type;
+if(typeof data1 !== "string"){
+const err7 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/0/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err7];
+}
+else {
+vErrors.push(err7);
+}
+errors++;
+}
+if(!(data1 === "text")){
+const err8 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/0/properties/type/enum",keyword:"enum",params:{allowedValues: schema57.oneOf[0].properties.type.enum},message:"must be equal to one of the allowed values"};
+if(vErrors === null){
+vErrors = [err8];
+}
+else {
+vErrors.push(err8);
+}
+errors++;
+}
+}
+if(data.labels !== undefined){
+if(!(validate26(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors);
+errors = vErrors.length;
+}
+}
+if(data.localized !== undefined){
+if(typeof data.localized !== "boolean"){
+const err9 = {instancePath:instancePath+"/localized",schemaPath:"#/oneOf/0/properties/localized/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(vErrors === null){
+vErrors = [err9];
+}
+else {
+vErrors.push(err9);
+}
+errors++;
+}
+}
+if(data.requiredToPublish !== undefined){
+if(typeof data.requiredToPublish !== "boolean"){
+const err10 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/0/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(vErrors === null){
+vErrors = [err10];
+}
+else {
+vErrors.push(err10);
+}
+errors++;
+}
+}
+if(data.migration !== undefined){
+if(!(validate28(data.migration, {instancePath:instancePath+"/migration",parentData:data,parentDataProperty:"migration",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate28.errors : vErrors.concat(validate28.errors);
+errors = vErrors.length;
+}
+}
+if(data.maxLength !== undefined){
+let data6 = data.maxLength;
+if(!(((typeof data6 == "number") && (!(data6 % 1) && !isNaN(data6))) && (isFinite(data6)))){
+const err11 = {instancePath:instancePath+"/maxLength",schemaPath:"#/oneOf/0/properties/maxLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"};
+if(vErrors === null){
+vErrors = [err11];
+}
+else {
+vErrors.push(err11);
+}
+errors++;
+}
+if((typeof data6 == "number") && (isFinite(data6))){
+if(data6 > 5000 || isNaN(data6)){
+const err12 = {instancePath:instancePath+"/maxLength",schemaPath:"#/oneOf/0/properties/maxLength/maximum",keyword:"maximum",params:{comparison: "<=", limit: 5000},message:"must be <= 5000"};
+if(vErrors === null){
+vErrors = [err12];
+}
+else {
+vErrors.push(err12);
+}
+errors++;
+}
+if(data6 < 1 || isNaN(data6)){
+const err13 = {instancePath:instancePath+"/maxLength",schemaPath:"#/oneOf/0/properties/maxLength/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};
+if(vErrors === null){
+vErrors = [err13];
+}
+else {
+vErrors.push(err13);
+}
+errors++;
+}
+}
+}
+if(data.multiline !== undefined){
+if(typeof data.multiline !== "boolean"){
+const err14 = {instancePath:instancePath+"/multiline",schemaPath:"#/oneOf/0/properties/multiline/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(vErrors === null){
+vErrors = [err14];
+}
+else {
+vErrors.push(err14);
+}
+errors++;
+}
+}
+if(data.searchable !== undefined){
+if(typeof data.searchable !== "boolean"){
+const err15 = {instancePath:instancePath+"/searchable",schemaPath:"#/oneOf/0/properties/searchable/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(vErrors === null){
+vErrors = [err15];
+}
+else {
+vErrors.push(err15);
+}
+errors++;
+}
+}
+}
+else {
+const err16 = {instancePath,schemaPath:"#/oneOf/0/type",keyword:"type",params:{type: "object"},message:"must be object"};
+if(vErrors === null){
+vErrors = [err16];
+}
+else {
+vErrors.push(err16);
+}
+errors++;
+}
+var _valid0 = _errs1 === errors;
+if(_valid0){
+valid0 = true;
+passing0 = 0;
+var props0 = true;
+}
+const _errs21 = errors;
+if(data && typeof data == "object" && !Array.isArray(data)){
+if(data.key === undefined){
+const err17 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
+if(vErrors === null){
+vErrors = [err17];
+}
+else {
+vErrors.push(err17);
+}
+errors++;
+}
+if(data.type === undefined){
+const err18 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
+if(vErrors === null){
+vErrors = [err18];
+}
+else {
+vErrors.push(err18);
+}
+errors++;
+}
+if(data.labels === undefined){
+const err19 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
+if(vErrors === null){
+vErrors = [err19];
+}
+else {
+vErrors.push(err19);
+}
+errors++;
+}
+if(data.localized === undefined){
+const err20 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "localized"},message:"must have required property '"+"localized"+"'"};
+if(vErrors === null){
+vErrors = [err20];
+}
+else {
+vErrors.push(err20);
+}
+errors++;
+}
+for(const key1 in data){
+if(!(((((((key1 === "key") || (key1 === "type")) || (key1 === "labels")) || (key1 === "localized")) || (key1 === "requiredToPublish")) || (key1 === "migration")) || (key1 === "searchable"))){
+const err21 = {instancePath,schemaPath:"#/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};
+if(vErrors === null){
+vErrors = [err21];
+}
+else {
+vErrors.push(err21);
+}
+errors++;
+}
+}
+if(data.key !== undefined){
+let data9 = data.key;
+if(typeof data9 === "string"){
+if(!pattern22.test(data9)){
+const err22 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
+if(vErrors === null){
+vErrors = [err22];
+}
+else {
+vErrors.push(err22);
+}
+errors++;
+}
+}
+else {
+const err23 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err23];
+}
+else {
+vErrors.push(err23);
+}
+errors++;
+}
+}
+if(data.type !== undefined){
+let data10 = data.type;
+if(typeof data10 !== "string"){
+const err24 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/1/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err24];
+}
+else {
+vErrors.push(err24);
+}
+errors++;
+}
+if(!(data10 === "richText")){
+const err25 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/1/properties/type/enum",keyword:"enum",params:{allowedValues: schema57.oneOf[1].properties.type.enum},message:"must be equal to one of the allowed values"};
+if(vErrors === null){
+vErrors = [err25];
+}
+else {
+vErrors.push(err25);
+}
+errors++;
+}
+}
+if(data.labels !== undefined){
+if(!(validate26(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors);
+errors = vErrors.length;
+}
+}
+if(data.localized !== undefined){
+if(typeof data.localized !== "boolean"){
+const err26 = {instancePath:instancePath+"/localized",schemaPath:"#/oneOf/1/properties/localized/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(vErrors === null){
+vErrors = [err26];
+}
+else {
+vErrors.push(err26);
+}
+errors++;
+}
+}
+if(data.requiredToPublish !== undefined){
+if(typeof data.requiredToPublish !== "boolean"){
+const err27 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/1/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(vErrors === null){
+vErrors = [err27];
+}
+else {
+vErrors.push(err27);
+}
+errors++;
+}
+}
+if(data.migration !== undefined){
+if(!(validate28(data.migration, {instancePath:instancePath+"/migration",parentData:data,parentDataProperty:"migration",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate28.errors : vErrors.concat(validate28.errors);
+errors = vErrors.length;
+}
+}
+if(data.searchable !== undefined){
+if(typeof data.searchable !== "boolean"){
+const err28 = {instancePath:instancePath+"/searchable",schemaPath:"#/oneOf/1/properties/searchable/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(vErrors === null){
+vErrors = [err28];
+}
+else {
+vErrors.push(err28);
+}
+errors++;
+}
+}
+}
+else {
+const err29 = {instancePath,schemaPath:"#/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};
+if(vErrors === null){
+vErrors = [err29];
+}
+else {
+vErrors.push(err29);
+}
+errors++;
+}
+var _valid0 = _errs21 === errors;
+if(_valid0 && valid0){
+valid0 = false;
+passing0 = [passing0, 1];
+}
+else {
+if(_valid0){
+valid0 = true;
+passing0 = 1;
+if(props0 !== true){
+props0 = true;
+}
+}
+const _errs37 = errors;
+if(data && typeof data == "object" && !Array.isArray(data)){
+if(data.key === undefined){
+const err30 = {instancePath,schemaPath:"#/oneOf/2/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
+if(vErrors === null){
+vErrors = [err30];
+}
+else {
+vErrors.push(err30);
+}
+errors++;
+}
+if(data.type === undefined){
+const err31 = {instancePath,schemaPath:"#/oneOf/2/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
+if(vErrors === null){
+vErrors = [err31];
+}
+else {
+vErrors.push(err31);
+}
+errors++;
+}
+if(data.labels === undefined){
+const err32 = {instancePath,schemaPath:"#/oneOf/2/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
+if(vErrors === null){
+vErrors = [err32];
+}
+else {
+vErrors.push(err32);
+}
+errors++;
+}
+if(data.localized === undefined){
+const err33 = {instancePath,schemaPath:"#/oneOf/2/required",keyword:"required",params:{missingProperty: "localized"},message:"must have required property '"+"localized"+"'"};
+if(vErrors === null){
+vErrors = [err33];
+}
+else {
+vErrors.push(err33);
+}
+errors++;
+}
+for(const key2 in data){
+if(!(func1.call(schema57.oneOf[2].properties, key2))){
+const err34 = {instancePath,schemaPath:"#/oneOf/2/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};
+if(vErrors === null){
+vErrors = [err34];
+}
+else {
+vErrors.push(err34);
+}
+errors++;
+}
+}
+if(data.key !== undefined){
+let data16 = data.key;
+if(typeof data16 === "string"){
+if(!pattern22.test(data16)){
+const err35 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
+if(vErrors === null){
+vErrors = [err35];
+}
+else {
+vErrors.push(err35);
+}
+errors++;
+}
+}
+else {
+const err36 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err36];
+}
+else {
+vErrors.push(err36);
+}
+errors++;
+}
+}
+if(data.type !== undefined){
+let data17 = data.type;
+if(typeof data17 !== "string"){
+const err37 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/2/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err37];
+}
+else {
+vErrors.push(err37);
+}
+errors++;
+}
+if(!(data17 === "number")){
+const err38 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/2/properties/type/enum",keyword:"enum",params:{allowedValues: schema57.oneOf[2].properties.type.enum},message:"must be equal to one of the allowed values"};
+if(vErrors === null){
+vErrors = [err38];
+}
+else {
+vErrors.push(err38);
+}
+errors++;
+}
+}
+if(data.labels !== undefined){
+if(!(validate26(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors);
+errors = vErrors.length;
+}
+}
+if(data.localized !== undefined){
+if(typeof data.localized !== "boolean"){
+const err39 = {instancePath:instancePath+"/localized",schemaPath:"#/oneOf/2/properties/localized/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(vErrors === null){
+vErrors = [err39];
+}
+else {
+vErrors.push(err39);
+}
+errors++;
+}
+}
+if(data.requiredToPublish !== undefined){
+if(typeof data.requiredToPublish !== "boolean"){
+const err40 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/2/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(vErrors === null){
+vErrors = [err40];
+}
+else {
+vErrors.push(err40);
+}
+errors++;
+}
+}
+if(data.migration !== undefined){
+if(!(validate28(data.migration, {instancePath:instancePath+"/migration",parentData:data,parentDataProperty:"migration",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate28.errors : vErrors.concat(validate28.errors);
+errors = vErrors.length;
+}
+}
+if(data.integer !== undefined){
+if(typeof data.integer !== "boolean"){
+const err41 = {instancePath:instancePath+"/integer",schemaPath:"#/oneOf/2/properties/integer/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(vErrors === null){
+vErrors = [err41];
+}
+else {
+vErrors.push(err41);
+}
+errors++;
+}
+}
+if(data.minimum !== undefined){
+let data23 = data.minimum;
+if(!((typeof data23 == "number") && (isFinite(data23)))){
+const err42 = {instancePath:instancePath+"/minimum",schemaPath:"#/oneOf/2/properties/minimum/type",keyword:"type",params:{type: "number"},message:"must be number"};
+if(vErrors === null){
+vErrors = [err42];
+}
+else {
+vErrors.push(err42);
+}
+errors++;
+}
+}
+if(data.maximum !== undefined){
+let data24 = data.maximum;
+if(!((typeof data24 == "number") && (isFinite(data24)))){
+const err43 = {instancePath:instancePath+"/maximum",schemaPath:"#/oneOf/2/properties/maximum/type",keyword:"type",params:{type: "number"},message:"must be number"};
+if(vErrors === null){
+vErrors = [err43];
+}
+else {
+vErrors.push(err43);
+}
+errors++;
+}
+}
+}
+else {
+const err44 = {instancePath,schemaPath:"#/oneOf/2/type",keyword:"type",params:{type: "object"},message:"must be object"};
+if(vErrors === null){
+vErrors = [err44];
+}
+else {
+vErrors.push(err44);
+}
+errors++;
+}
+var _valid0 = _errs37 === errors;
+if(_valid0 && valid0){
+valid0 = false;
+passing0 = [passing0, 2];
+}
+else {
+if(_valid0){
+valid0 = true;
+passing0 = 2;
+if(props0 !== true){
+props0 = true;
+}
+}
+const _errs57 = errors;
+if(data && typeof data == "object" && !Array.isArray(data)){
+if(data.key === undefined){
+const err45 = {instancePath,schemaPath:"#/oneOf/3/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
+if(vErrors === null){
+vErrors = [err45];
+}
+else {
+vErrors.push(err45);
+}
+errors++;
+}
+if(data.type === undefined){
+const err46 = {instancePath,schemaPath:"#/oneOf/3/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
+if(vErrors === null){
+vErrors = [err46];
+}
+else {
+vErrors.push(err46);
+}
+errors++;
+}
+if(data.labels === undefined){
+const err47 = {instancePath,schemaPath:"#/oneOf/3/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
+if(vErrors === null){
+vErrors = [err47];
+}
+else {
+vErrors.push(err47);
+}
+errors++;
+}
+if(data.localized === undefined){
+const err48 = {instancePath,schemaPath:"#/oneOf/3/required",keyword:"required",params:{missingProperty: "localized"},message:"must have required property '"+"localized"+"'"};
+if(vErrors === null){
+vErrors = [err48];
+}
+else {
+vErrors.push(err48);
+}
+errors++;
+}
+for(const key3 in data){
+if(!(((((key3 === "key") || (key3 === "type")) || (key3 === "labels")) || (key3 === "localized")) || (key3 === "migration"))){
+const err49 = {instancePath,schemaPath:"#/oneOf/3/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};
+if(vErrors === null){
+vErrors = [err49];
+}
+else {
+vErrors.push(err49);
+}
+errors++;
+}
+}
+if(data.key !== undefined){
+let data25 = data.key;
+if(typeof data25 === "string"){
+if(!pattern22.test(data25)){
+const err50 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
+if(vErrors === null){
+vErrors = [err50];
+}
+else {
+vErrors.push(err50);
+}
+errors++;
+}
+}
+else {
+const err51 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err51];
+}
+else {
+vErrors.push(err51);
+}
+errors++;
+}
+}
+if(data.type !== undefined){
+let data26 = data.type;
+if(typeof data26 !== "string"){
+const err52 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/3/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err52];
+}
+else {
+vErrors.push(err52);
+}
+errors++;
+}
+if(!(data26 === "boolean")){
+const err53 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/3/properties/type/enum",keyword:"enum",params:{allowedValues: schema57.oneOf[3].properties.type.enum},message:"must be equal to one of the allowed values"};
+if(vErrors === null){
+vErrors = [err53];
+}
+else {
+vErrors.push(err53);
+}
+errors++;
+}
+}
+if(data.labels !== undefined){
+if(!(validate26(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors);
+errors = vErrors.length;
+}
+}
+if(data.localized !== undefined){
+if(typeof data.localized !== "boolean"){
+const err54 = {instancePath:instancePath+"/localized",schemaPath:"#/oneOf/3/properties/localized/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(vErrors === null){
+vErrors = [err54];
+}
+else {
+vErrors.push(err54);
+}
+errors++;
+}
+}
+if(data.migration !== undefined){
+if(!(validate28(data.migration, {instancePath:instancePath+"/migration",parentData:data,parentDataProperty:"migration",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate28.errors : vErrors.concat(validate28.errors);
+errors = vErrors.length;
+}
+}
+}
+else {
+const err55 = {instancePath,schemaPath:"#/oneOf/3/type",keyword:"type",params:{type: "object"},message:"must be object"};
+if(vErrors === null){
+vErrors = [err55];
+}
+else {
+vErrors.push(err55);
+}
+errors++;
+}
+var _valid0 = _errs57 === errors;
+if(_valid0 && valid0){
+valid0 = false;
+passing0 = [passing0, 3];
+}
+else {
+if(_valid0){
+valid0 = true;
+passing0 = 3;
+if(props0 !== true){
+props0 = true;
+}
+}
+const _errs69 = errors;
+if(data && typeof data == "object" && !Array.isArray(data)){
+if(data.key === undefined){
+const err56 = {instancePath,schemaPath:"#/oneOf/4/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
+if(vErrors === null){
+vErrors = [err56];
+}
+else {
+vErrors.push(err56);
+}
+errors++;
+}
+if(data.type === undefined){
+const err57 = {instancePath,schemaPath:"#/oneOf/4/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
+if(vErrors === null){
+vErrors = [err57];
+}
+else {
+vErrors.push(err57);
+}
+errors++;
+}
+if(data.labels === undefined){
+const err58 = {instancePath,schemaPath:"#/oneOf/4/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
+if(vErrors === null){
+vErrors = [err58];
+}
+else {
+vErrors.push(err58);
+}
+errors++;
+}
+if(data.localized === undefined){
+const err59 = {instancePath,schemaPath:"#/oneOf/4/required",keyword:"required",params:{missingProperty: "localized"},message:"must have required property '"+"localized"+"'"};
+if(vErrors === null){
+vErrors = [err59];
+}
+else {
+vErrors.push(err59);
+}
+errors++;
+}
+for(const key4 in data){
+if(!((((((key4 === "key") || (key4 === "type")) || (key4 === "labels")) || (key4 === "localized")) || (key4 === "requiredToPublish")) || (key4 === "migration"))){
+const err60 = {instancePath,schemaPath:"#/oneOf/4/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"};
+if(vErrors === null){
+vErrors = [err60];
+}
+else {
+vErrors.push(err60);
+}
+errors++;
+}
+}
+if(data.key !== undefined){
+let data30 = data.key;
+if(typeof data30 === "string"){
+if(!pattern22.test(data30)){
+const err61 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
+if(vErrors === null){
+vErrors = [err61];
+}
+else {
+vErrors.push(err61);
+}
+errors++;
+}
+}
+else {
+const err62 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err62];
+}
+else {
+vErrors.push(err62);
+}
+errors++;
+}
+}
+if(data.type !== undefined){
+let data31 = data.type;
+if(typeof data31 !== "string"){
+const err63 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/4/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err63];
+}
+else {
+vErrors.push(err63);
+}
+errors++;
+}
+if(!(data31 === "date")){
+const err64 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/4/properties/type/enum",keyword:"enum",params:{allowedValues: schema57.oneOf[4].properties.type.enum},message:"must be equal to one of the allowed values"};
+if(vErrors === null){
+vErrors = [err64];
+}
+else {
+vErrors.push(err64);
+}
+errors++;
+}
+}
+if(data.labels !== undefined){
+if(!(validate26(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors);
+errors = vErrors.length;
+}
+}
+if(data.localized !== undefined){
+if(typeof data.localized !== "boolean"){
+const err65 = {instancePath:instancePath+"/localized",schemaPath:"#/oneOf/4/properties/localized/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(vErrors === null){
+vErrors = [err65];
+}
+else {
+vErrors.push(err65);
+}
+errors++;
+}
+}
+if(data.requiredToPublish !== undefined){
+if(typeof data.requiredToPublish !== "boolean"){
+const err66 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/4/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(vErrors === null){
+vErrors = [err66];
+}
+else {
+vErrors.push(err66);
+}
+errors++;
+}
+}
+if(data.migration !== undefined){
+if(!(validate28(data.migration, {instancePath:instancePath+"/migration",parentData:data,parentDataProperty:"migration",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate28.errors : vErrors.concat(validate28.errors);
+errors = vErrors.length;
+}
+}
+}
+else {
+const err67 = {instancePath,schemaPath:"#/oneOf/4/type",keyword:"type",params:{type: "object"},message:"must be object"};
+if(vErrors === null){
+vErrors = [err67];
+}
+else {
+vErrors.push(err67);
+}
+errors++;
+}
+var _valid0 = _errs69 === errors;
+if(_valid0 && valid0){
+valid0 = false;
+passing0 = [passing0, 4];
+}
+else {
+if(_valid0){
+valid0 = true;
+passing0 = 4;
+if(props0 !== true){
+props0 = true;
+}
+}
+const _errs83 = errors;
+if(data && typeof data == "object" && !Array.isArray(data)){
+if(data.key === undefined){
+const err68 = {instancePath,schemaPath:"#/oneOf/5/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
+if(vErrors === null){
+vErrors = [err68];
+}
+else {
+vErrors.push(err68);
+}
+errors++;
+}
+if(data.type === undefined){
+const err69 = {instancePath,schemaPath:"#/oneOf/5/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
+if(vErrors === null){
+vErrors = [err69];
+}
+else {
+vErrors.push(err69);
+}
+errors++;
+}
+if(data.labels === undefined){
+const err70 = {instancePath,schemaPath:"#/oneOf/5/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
+if(vErrors === null){
+vErrors = [err70];
+}
+else {
+vErrors.push(err70);
+}
+errors++;
+}
+if(data.localized === undefined){
+const err71 = {instancePath,schemaPath:"#/oneOf/5/required",keyword:"required",params:{missingProperty: "localized"},message:"must have required property '"+"localized"+"'"};
+if(vErrors === null){
+vErrors = [err71];
+}
+else {
+vErrors.push(err71);
+}
+errors++;
+}
+for(const key5 in data){
+if(!((((((key5 === "key") || (key5 === "type")) || (key5 === "labels")) || (key5 === "localized")) || (key5 === "requiredToPublish")) || (key5 === "migration"))){
+const err72 = {instancePath,schemaPath:"#/oneOf/5/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key5},message:"must NOT have additional properties"};
+if(vErrors === null){
+vErrors = [err72];
+}
+else {
+vErrors.push(err72);
+}
+errors++;
+}
+}
+if(data.key !== undefined){
+let data36 = data.key;
+if(typeof data36 === "string"){
+if(!pattern22.test(data36)){
+const err73 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
+if(vErrors === null){
+vErrors = [err73];
+}
+else {
+vErrors.push(err73);
+}
+errors++;
+}
+}
+else {
+const err74 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err74];
+}
+else {
+vErrors.push(err74);
+}
+errors++;
+}
+}
+if(data.type !== undefined){
+let data37 = data.type;
+if(typeof data37 !== "string"){
+const err75 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/5/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err75];
+}
+else {
+vErrors.push(err75);
+}
+errors++;
+}
+if(!(data37 === "datetime")){
+const err76 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/5/properties/type/enum",keyword:"enum",params:{allowedValues: schema57.oneOf[5].properties.type.enum},message:"must be equal to one of the allowed values"};
+if(vErrors === null){
+vErrors = [err76];
+}
+else {
+vErrors.push(err76);
+}
+errors++;
+}
+}
+if(data.labels !== undefined){
+if(!(validate26(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors);
+errors = vErrors.length;
+}
+}
+if(data.localized !== undefined){
+if(typeof data.localized !== "boolean"){
+const err77 = {instancePath:instancePath+"/localized",schemaPath:"#/oneOf/5/properties/localized/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(vErrors === null){
+vErrors = [err77];
+}
+else {
+vErrors.push(err77);
+}
+errors++;
+}
+}
+if(data.requiredToPublish !== undefined){
+if(typeof data.requiredToPublish !== "boolean"){
+const err78 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/5/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(vErrors === null){
+vErrors = [err78];
+}
+else {
+vErrors.push(err78);
+}
+errors++;
+}
+}
+if(data.migration !== undefined){
+if(!(validate28(data.migration, {instancePath:instancePath+"/migration",parentData:data,parentDataProperty:"migration",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate28.errors : vErrors.concat(validate28.errors);
+errors = vErrors.length;
+}
+}
+}
+else {
+const err79 = {instancePath,schemaPath:"#/oneOf/5/type",keyword:"type",params:{type: "object"},message:"must be object"};
+if(vErrors === null){
+vErrors = [err79];
+}
+else {
+vErrors.push(err79);
+}
+errors++;
+}
+var _valid0 = _errs83 === errors;
+if(_valid0 && valid0){
+valid0 = false;
+passing0 = [passing0, 5];
+}
+else {
+if(_valid0){
+valid0 = true;
+passing0 = 5;
+if(props0 !== true){
+props0 = true;
+}
+}
+const _errs97 = errors;
+if(data && typeof data == "object" && !Array.isArray(data)){
+if(data.key === undefined){
+const err80 = {instancePath,schemaPath:"#/oneOf/6/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
+if(vErrors === null){
+vErrors = [err80];
+}
+else {
+vErrors.push(err80);
+}
+errors++;
+}
+if(data.type === undefined){
+const err81 = {instancePath,schemaPath:"#/oneOf/6/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
+if(vErrors === null){
+vErrors = [err81];
+}
+else {
+vErrors.push(err81);
+}
+errors++;
+}
+if(data.labels === undefined){
+const err82 = {instancePath,schemaPath:"#/oneOf/6/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
+if(vErrors === null){
+vErrors = [err82];
+}
+else {
+vErrors.push(err82);
+}
+errors++;
+}
+if(data.localized === undefined){
+const err83 = {instancePath,schemaPath:"#/oneOf/6/required",keyword:"required",params:{missingProperty: "localized"},message:"must have required property '"+"localized"+"'"};
+if(vErrors === null){
+vErrors = [err83];
+}
+else {
+vErrors.push(err83);
+}
+errors++;
+}
+for(const key6 in data){
+if(!((((((key6 === "key") || (key6 === "type")) || (key6 === "labels")) || (key6 === "localized")) || (key6 === "requiredToPublish")) || (key6 === "migration"))){
+const err84 = {instancePath,schemaPath:"#/oneOf/6/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key6},message:"must NOT have additional properties"};
+if(vErrors === null){
+vErrors = [err84];
+}
+else {
+vErrors.push(err84);
+}
+errors++;
+}
+}
+if(data.key !== undefined){
+let data42 = data.key;
+if(typeof data42 === "string"){
+if(!pattern22.test(data42)){
+const err85 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
+if(vErrors === null){
+vErrors = [err85];
+}
+else {
+vErrors.push(err85);
+}
+errors++;
+}
+}
+else {
+const err86 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err86];
+}
+else {
+vErrors.push(err86);
+}
+errors++;
+}
+}
+if(data.type !== undefined){
+let data43 = data.type;
+if(typeof data43 !== "string"){
+const err87 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/6/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err87];
+}
+else {
+vErrors.push(err87);
+}
+errors++;
+}
+if(!(data43 === "url")){
+const err88 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/6/properties/type/enum",keyword:"enum",params:{allowedValues: schema57.oneOf[6].properties.type.enum},message:"must be equal to one of the allowed values"};
+if(vErrors === null){
+vErrors = [err88];
+}
+else {
+vErrors.push(err88);
+}
+errors++;
+}
+}
+if(data.labels !== undefined){
+if(!(validate26(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors);
+errors = vErrors.length;
+}
+}
+if(data.localized !== undefined){
+if(typeof data.localized !== "boolean"){
+const err89 = {instancePath:instancePath+"/localized",schemaPath:"#/oneOf/6/properties/localized/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(vErrors === null){
+vErrors = [err89];
+}
+else {
+vErrors.push(err89);
+}
+errors++;
+}
+}
+if(data.requiredToPublish !== undefined){
+if(typeof data.requiredToPublish !== "boolean"){
+const err90 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/6/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(vErrors === null){
+vErrors = [err90];
+}
+else {
+vErrors.push(err90);
+}
+errors++;
+}
+}
+if(data.migration !== undefined){
+if(!(validate28(data.migration, {instancePath:instancePath+"/migration",parentData:data,parentDataProperty:"migration",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate28.errors : vErrors.concat(validate28.errors);
+errors = vErrors.length;
+}
+}
+}
+else {
+const err91 = {instancePath,schemaPath:"#/oneOf/6/type",keyword:"type",params:{type: "object"},message:"must be object"};
+if(vErrors === null){
+vErrors = [err91];
+}
+else {
+vErrors.push(err91);
+}
+errors++;
+}
+var _valid0 = _errs97 === errors;
+if(_valid0 && valid0){
+valid0 = false;
+passing0 = [passing0, 6];
+}
+else {
+if(_valid0){
+valid0 = true;
+passing0 = 6;
+if(props0 !== true){
+props0 = true;
+}
+}
+const _errs111 = errors;
+if(data && typeof data == "object" && !Array.isArray(data)){
+if(data.key === undefined){
+const err92 = {instancePath,schemaPath:"#/oneOf/7/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
+if(vErrors === null){
+vErrors = [err92];
+}
+else {
+vErrors.push(err92);
+}
+errors++;
+}
+if(data.type === undefined){
+const err93 = {instancePath,schemaPath:"#/oneOf/7/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
+if(vErrors === null){
+vErrors = [err93];
+}
+else {
+vErrors.push(err93);
+}
+errors++;
+}
+if(data.labels === undefined){
+const err94 = {instancePath,schemaPath:"#/oneOf/7/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
+if(vErrors === null){
+vErrors = [err94];
+}
+else {
+vErrors.push(err94);
+}
+errors++;
+}
+if(data.localized === undefined){
+const err95 = {instancePath,schemaPath:"#/oneOf/7/required",keyword:"required",params:{missingProperty: "localized"},message:"must have required property '"+"localized"+"'"};
+if(vErrors === null){
+vErrors = [err95];
+}
+else {
+vErrors.push(err95);
+}
+errors++;
+}
+for(const key7 in data){
+if(!((((((key7 === "key") || (key7 === "type")) || (key7 === "labels")) || (key7 === "localized")) || (key7 === "requiredToPublish")) || (key7 === "migration"))){
+const err96 = {instancePath,schemaPath:"#/oneOf/7/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key7},message:"must NOT have additional properties"};
+if(vErrors === null){
+vErrors = [err96];
+}
+else {
+vErrors.push(err96);
+}
+errors++;
+}
+}
+if(data.key !== undefined){
+let data48 = data.key;
+if(typeof data48 === "string"){
+if(!pattern22.test(data48)){
+const err97 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
+if(vErrors === null){
+vErrors = [err97];
+}
+else {
+vErrors.push(err97);
+}
+errors++;
+}
+}
+else {
+const err98 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err98];
+}
+else {
+vErrors.push(err98);
+}
+errors++;
+}
+}
+if(data.type !== undefined){
+let data49 = data.type;
+if(typeof data49 !== "string"){
+const err99 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/7/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err99];
+}
+else {
+vErrors.push(err99);
+}
+errors++;
+}
+if(!(data49 === "image")){
+const err100 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/7/properties/type/enum",keyword:"enum",params:{allowedValues: schema57.oneOf[7].properties.type.enum},message:"must be equal to one of the allowed values"};
+if(vErrors === null){
+vErrors = [err100];
+}
+else {
+vErrors.push(err100);
+}
+errors++;
+}
+}
+if(data.labels !== undefined){
+if(!(validate26(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors);
+errors = vErrors.length;
+}
+}
+if(data.localized !== undefined){
+if(typeof data.localized !== "boolean"){
+const err101 = {instancePath:instancePath+"/localized",schemaPath:"#/oneOf/7/properties/localized/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(vErrors === null){
+vErrors = [err101];
+}
+else {
+vErrors.push(err101);
+}
+errors++;
+}
+}
+if(data.requiredToPublish !== undefined){
+if(typeof data.requiredToPublish !== "boolean"){
+const err102 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/7/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(vErrors === null){
+vErrors = [err102];
+}
+else {
+vErrors.push(err102);
+}
+errors++;
+}
+}
+if(data.migration !== undefined){
+if(!(validate28(data.migration, {instancePath:instancePath+"/migration",parentData:data,parentDataProperty:"migration",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate28.errors : vErrors.concat(validate28.errors);
+errors = vErrors.length;
+}
+}
+}
+else {
+const err103 = {instancePath,schemaPath:"#/oneOf/7/type",keyword:"type",params:{type: "object"},message:"must be object"};
+if(vErrors === null){
+vErrors = [err103];
+}
+else {
+vErrors.push(err103);
+}
+errors++;
+}
+var _valid0 = _errs111 === errors;
+if(_valid0 && valid0){
+valid0 = false;
+passing0 = [passing0, 7];
+}
+else {
+if(_valid0){
+valid0 = true;
+passing0 = 7;
+if(props0 !== true){
+props0 = true;
+}
+}
+const _errs125 = errors;
+if(data && typeof data == "object" && !Array.isArray(data)){
+if(data.key === undefined){
+const err104 = {instancePath,schemaPath:"#/oneOf/8/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
+if(vErrors === null){
+vErrors = [err104];
+}
+else {
+vErrors.push(err104);
+}
+errors++;
+}
+if(data.type === undefined){
+const err105 = {instancePath,schemaPath:"#/oneOf/8/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
+if(vErrors === null){
+vErrors = [err105];
+}
+else {
+vErrors.push(err105);
+}
+errors++;
+}
+if(data.labels === undefined){
+const err106 = {instancePath,schemaPath:"#/oneOf/8/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
+if(vErrors === null){
+vErrors = [err106];
+}
+else {
+vErrors.push(err106);
+}
+errors++;
+}
+if(data.localized === undefined){
+const err107 = {instancePath,schemaPath:"#/oneOf/8/required",keyword:"required",params:{missingProperty: "localized"},message:"must have required property '"+"localized"+"'"};
+if(vErrors === null){
+vErrors = [err107];
+}
+else {
+vErrors.push(err107);
+}
+errors++;
+}
+if(data.options === undefined){
+const err108 = {instancePath,schemaPath:"#/oneOf/8/required",keyword:"required",params:{missingProperty: "options"},message:"must have required property '"+"options"+"'"};
+if(vErrors === null){
+vErrors = [err108];
+}
+else {
+vErrors.push(err108);
+}
+errors++;
+}
+for(const key8 in data){
+if(!((((((((key8 === "key") || (key8 === "type")) || (key8 === "labels")) || (key8 === "localized")) || (key8 === "requiredToPublish")) || (key8 === "migration")) || (key8 === "options")) || (key8 === "multiple"))){
+const err109 = {instancePath,schemaPath:"#/oneOf/8/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key8},message:"must NOT have additional properties"};
+if(vErrors === null){
+vErrors = [err109];
+}
+else {
+vErrors.push(err109);
+}
+errors++;
+}
+}
+if(data.key !== undefined){
+let data54 = data.key;
+if(typeof data54 === "string"){
+if(!pattern22.test(data54)){
+const err110 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
+if(vErrors === null){
+vErrors = [err110];
+}
+else {
+vErrors.push(err110);
+}
+errors++;
+}
+}
+else {
+const err111 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err111];
+}
+else {
+vErrors.push(err111);
+}
+errors++;
+}
+}
+if(data.type !== undefined){
+let data55 = data.type;
+if(typeof data55 !== "string"){
+const err112 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/8/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(vErrors === null){
+vErrors = [err112];
+}
+else {
+vErrors.push(err112);
+}
+errors++;
+}
+if(!(data55 === "select")){
+const err113 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/8/properties/type/enum",keyword:"enum",params:{allowedValues: schema57.oneOf[8].properties.type.enum},message:"must be equal to one of the allowed values"};
+if(vErrors === null){
+vErrors = [err113];
+}
+else {
+vErrors.push(err113);
+}
+errors++;
+}
+}
+if(data.labels !== undefined){
+if(!(validate26(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors);
+errors = vErrors.length;
+}
+}
+if(data.localized !== undefined){
+if(typeof data.localized !== "boolean"){
+const err114 = {instancePath:instancePath+"/localized",schemaPath:"#/oneOf/8/properties/localized/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(vErrors === null){
+vErrors = [err114];
+}
+else {
+vErrors.push(err114);
+}
+errors++;
+}
+}
+if(data.requiredToPublish !== undefined){
+if(typeof data.requiredToPublish !== "boolean"){
+const err115 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/8/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+if(vErrors === null){
+vErrors = [err115];
+}
+else {
+vErrors.push(err115);
+}
+errors++;
+}
+}
+if(data.migration !== undefined){
+if(!(validate28(data.migration, {instancePath:instancePath+"/migration",parentData:data,parentDataProperty:"migration",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate28.errors : vErrors.concat(validate28.errors);
+errors = vErrors.length;
+}
+}
+if(data.options !== undefined){
+let data60 = data.options;
+if(Array.isArray(data60)){
+if(data60.length > 50){
+const err116 = {instancePath:instancePath+"/options",schemaPath:"#/oneOf/8/properties/options/maxItems",keyword:"maxItems",params:{limit: 50},message:"must NOT have more than 50 items"};
+if(vErrors === null){
+vErrors = [err116];
+}
+else {
+vErrors.push(err116);
+}
+errors++;
+}
+if(data60.length < 1){
+const err117 = {instancePath:instancePath+"/options",schemaPath:"#/oneOf/8/properties/options/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};
+if(vErrors === null){
+vErrors = [err117];
+}
+else {
+vErrors.push(err117);
+}
+errors++;
+}
+const len0 = data60.length;
+for(let i0=0; i0 5){
+const err133 = {instancePath:instancePath+"/collections",schemaPath:"#/oneOf/9/properties/collections/maxItems",keyword:"maxItems",params:{limit: 5},message:"must NOT have more than 5 items"};
+if(vErrors === null){
+vErrors = [err133];
}
else {
-const err79 = {instancePath,schemaPath:"#/oneOf/5/type",keyword:"type",params:{type: "object"},message:"must be object"};
+vErrors.push(err133);
+}
+errors++;
+}
+if(data69.length < 1){
+const err134 = {instancePath:instancePath+"/collections",schemaPath:"#/oneOf/9/properties/collections/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};
if(vErrors === null){
-vErrors = [err79];
+vErrors = [err134];
}
else {
-vErrors.push(err79);
+vErrors.push(err134);
}
errors++;
}
-var _valid0 = _errs83 === errors;
-if(_valid0 && valid0){
-valid0 = false;
-passing0 = [passing0, 5];
+const len1 = data69.length;
+for(let i1=0; i1 128){
+const err135 = {instancePath:instancePath+"/collections/" + i1,schemaPath:"#/$defs/Slug/maxLength",keyword:"maxLength",params:{limit: 128},message:"must NOT have more than 128 characters"};
+if(vErrors === null){
+vErrors = [err135];
}
else {
-if(_valid0){
-valid0 = true;
-passing0 = 5;
-if(props0 !== true){
-props0 = true;
+vErrors.push(err135);
}
+errors++;
}
-const _errs97 = errors;
-if(data && typeof data == "object" && !Array.isArray(data)){
-if(data.key === undefined){
-const err80 = {instancePath,schemaPath:"#/oneOf/6/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
+if(func2(data70) < 1){
+const err136 = {instancePath:instancePath+"/collections/" + i1,schemaPath:"#/$defs/Slug/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};
if(vErrors === null){
-vErrors = [err80];
+vErrors = [err136];
}
else {
-vErrors.push(err80);
+vErrors.push(err136);
}
errors++;
}
-if(data.type === undefined){
-const err81 = {instancePath,schemaPath:"#/oneOf/6/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
+if(!pattern5.test(data70)){
+const err137 = {instancePath:instancePath+"/collections/" + i1,schemaPath:"#/$defs/Slug/pattern",keyword:"pattern",params:{pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$"},message:"must match pattern \""+"^[a-z0-9]+(?:-[a-z0-9]+)*$"+"\""};
if(vErrors === null){
-vErrors = [err81];
+vErrors = [err137];
}
else {
-vErrors.push(err81);
+vErrors.push(err137);
}
errors++;
}
-if(data.labels === undefined){
-const err82 = {instancePath,schemaPath:"#/oneOf/6/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
+}
+else {
+const err138 = {instancePath:instancePath+"/collections/" + i1,schemaPath:"#/$defs/Slug/type",keyword:"type",params:{type: "string"},message:"must be string"};
if(vErrors === null){
-vErrors = [err82];
+vErrors = [err138];
}
else {
-vErrors.push(err82);
+vErrors.push(err138);
}
errors++;
}
-if(data.localized === undefined){
-const err83 = {instancePath,schemaPath:"#/oneOf/6/required",keyword:"required",params:{missingProperty: "localized"},message:"must have required property '"+"localized"+"'"};
+}
+let i2 = data69.length;
+let j0;
+if(i2 > 1){
+outer0:
+for(;i2--;){
+for(j0 = i2; j0--;){
+if(func0(data69[i2], data69[j0])){
+const err139 = {instancePath:instancePath+"/collections",schemaPath:"#/oneOf/9/properties/collections/uniqueItems",keyword:"uniqueItems",params:{i: i2, j: j0},message:"must NOT have duplicate items (items ## "+j0+" and "+i2+" are identical)"};
if(vErrors === null){
-vErrors = [err83];
+vErrors = [err139];
}
else {
-vErrors.push(err83);
+vErrors.push(err139);
}
errors++;
+break outer0;
}
-for(const key6 in data){
-if(!((((((key6 === "key") || (key6 === "type")) || (key6 === "labels")) || (key6 === "localized")) || (key6 === "requiredToPublish")) || (key6 === "migration"))){
-const err84 = {instancePath,schemaPath:"#/oneOf/6/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key6},message:"must NOT have additional properties"};
+}
+}
+}
+}
+else {
+const err140 = {instancePath:instancePath+"/collections",schemaPath:"#/oneOf/9/properties/collections/type",keyword:"type",params:{type: "array"},message:"must be array"};
if(vErrors === null){
-vErrors = [err84];
+vErrors = [err140];
}
else {
-vErrors.push(err84);
+vErrors.push(err140);
}
errors++;
}
}
-if(data.key !== undefined){
-let data42 = data.key;
-if(typeof data42 === "string"){
-if(!pattern22.test(data42)){
-const err85 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
+if(data.multiple !== undefined){
+if(typeof data.multiple !== "boolean"){
+const err141 = {instancePath:instancePath+"/multiple",schemaPath:"#/oneOf/9/properties/multiple/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
if(vErrors === null){
-vErrors = [err85];
+vErrors = [err141];
}
else {
-vErrors.push(err85);
+vErrors.push(err141);
}
errors++;
}
}
-else {
-const err86 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
+if(data.maxItems !== undefined){
+let data72 = data.maxItems;
+if(!(((typeof data72 == "number") && (!(data72 % 1) && !isNaN(data72))) && (isFinite(data72)))){
+const err142 = {instancePath:instancePath+"/maxItems",schemaPath:"#/oneOf/9/properties/maxItems/type",keyword:"type",params:{type: "integer"},message:"must be integer"};
if(vErrors === null){
-vErrors = [err86];
+vErrors = [err142];
}
else {
-vErrors.push(err86);
+vErrors.push(err142);
}
errors++;
}
+if((typeof data72 == "number") && (isFinite(data72))){
+if(data72 > 50 || isNaN(data72)){
+const err143 = {instancePath:instancePath+"/maxItems",schemaPath:"#/oneOf/9/properties/maxItems/maximum",keyword:"maximum",params:{comparison: "<=", limit: 50},message:"must be <= 50"};
+if(vErrors === null){
+vErrors = [err143];
+}
+else {
+vErrors.push(err143);
}
-if(data.type !== undefined){
-let data43 = data.type;
-if(typeof data43 !== "string"){
-const err87 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/6/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
+errors++;
+}
+if(data72 < 1 || isNaN(data72)){
+const err144 = {instancePath:instancePath+"/maxItems",schemaPath:"#/oneOf/9/properties/maxItems/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};
if(vErrors === null){
-vErrors = [err87];
+vErrors = [err144];
}
else {
-vErrors.push(err87);
+vErrors.push(err144);
}
errors++;
}
-if(!(data43 === "url")){
-const err88 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/6/properties/type/enum",keyword:"enum",params:{allowedValues: schema57.oneOf[6].properties.type.enum},message:"must be equal to one of the allowed values"};
+}
+}
+if(data.where !== undefined){
+let data73 = data.where;
+if(Array.isArray(data73)){
+if(data73.length > 4){
+const err145 = {instancePath:instancePath+"/where",schemaPath:"#/oneOf/9/properties/where/maxItems",keyword:"maxItems",params:{limit: 4},message:"must NOT have more than 4 items"};
if(vErrors === null){
-vErrors = [err88];
+vErrors = [err145];
}
else {
-vErrors.push(err88);
+vErrors.push(err145);
}
errors++;
}
+if(data73.length < 1){
+const err146 = {instancePath:instancePath+"/where",schemaPath:"#/oneOf/9/properties/where/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};
+if(vErrors === null){
+vErrors = [err146];
}
-if(data.labels !== undefined){
-if(!(validate26(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors);
-errors = vErrors.length;
+else {
+vErrors.push(err146);
}
+errors++;
}
-if(data.localized !== undefined){
-if(typeof data.localized !== "boolean"){
-const err89 = {instancePath:instancePath+"/localized",schemaPath:"#/oneOf/6/properties/localized/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+const len2 = data73.length;
+for(let i3=0; i3 100){
+const err154 = {instancePath:instancePath+"/where/" + i3+"/value",schemaPath:"#/$defs/FieldCondition/properties/value/maxLength",keyword:"maxLength",params:{limit: 100},message:"must NOT have more than 100 characters"};
if(vErrors === null){
-vErrors = [err93];
+vErrors = [err154];
}
else {
-vErrors.push(err93);
+vErrors.push(err154);
}
errors++;
}
-if(data.labels === undefined){
-const err94 = {instancePath,schemaPath:"#/oneOf/7/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
+if(func2(data77) < 1){
+const err155 = {instancePath:instancePath+"/where/" + i3+"/value",schemaPath:"#/$defs/FieldCondition/properties/value/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};
if(vErrors === null){
-vErrors = [err94];
+vErrors = [err155];
}
else {
-vErrors.push(err94);
+vErrors.push(err155);
}
errors++;
}
-if(data.localized === undefined){
-const err95 = {instancePath,schemaPath:"#/oneOf/7/required",keyword:"required",params:{missingProperty: "localized"},message:"must have required property '"+"localized"+"'"};
+}
+else {
+const err156 = {instancePath:instancePath+"/where/" + i3+"/value",schemaPath:"#/$defs/FieldCondition/properties/value/type",keyword:"type",params:{type: "string"},message:"must be string"};
if(vErrors === null){
-vErrors = [err95];
+vErrors = [err156];
}
else {
-vErrors.push(err95);
+vErrors.push(err156);
}
errors++;
}
-for(const key7 in data){
-if(!((((((key7 === "key") || (key7 === "type")) || (key7 === "labels")) || (key7 === "localized")) || (key7 === "requiredToPublish")) || (key7 === "migration"))){
-const err96 = {instancePath,schemaPath:"#/oneOf/7/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key7},message:"must NOT have additional properties"};
+}
+if(data74.values !== undefined){
+let data78 = data74.values;
+if(Array.isArray(data78)){
+if(data78.length > 20){
+const err157 = {instancePath:instancePath+"/where/" + i3+"/values",schemaPath:"#/$defs/FieldCondition/properties/values/maxItems",keyword:"maxItems",params:{limit: 20},message:"must NOT have more than 20 items"};
if(vErrors === null){
-vErrors = [err96];
+vErrors = [err157];
}
else {
-vErrors.push(err96);
+vErrors.push(err157);
}
errors++;
}
-}
-if(data.key !== undefined){
-let data48 = data.key;
-if(typeof data48 === "string"){
-if(!pattern22.test(data48)){
-const err97 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
+if(data78.length < 1){
+const err158 = {instancePath:instancePath+"/where/" + i3+"/values",schemaPath:"#/$defs/FieldCondition/properties/values/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};
if(vErrors === null){
-vErrors = [err97];
+vErrors = [err158];
}
else {
-vErrors.push(err97);
+vErrors.push(err158);
}
errors++;
}
+const len3 = data78.length;
+for(let i4=0; i4 100){
+const err159 = {instancePath:instancePath+"/where/" + i3+"/values/" + i4,schemaPath:"#/$defs/FieldCondition/properties/values/items/maxLength",keyword:"maxLength",params:{limit: 100},message:"must NOT have more than 100 characters"};
+if(vErrors === null){
+vErrors = [err159];
}
else {
-const err98 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
+vErrors.push(err159);
+}
+errors++;
+}
+if(func2(data79) < 1){
+const err160 = {instancePath:instancePath+"/where/" + i3+"/values/" + i4,schemaPath:"#/$defs/FieldCondition/properties/values/items/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};
if(vErrors === null){
-vErrors = [err98];
+vErrors = [err160];
}
else {
-vErrors.push(err98);
+vErrors.push(err160);
}
errors++;
}
}
-if(data.type !== undefined){
-let data49 = data.type;
-if(typeof data49 !== "string"){
-const err99 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/7/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
+else {
+const err161 = {instancePath:instancePath+"/where/" + i3+"/values/" + i4,schemaPath:"#/$defs/FieldCondition/properties/values/items/type",keyword:"type",params:{type: "string"},message:"must be string"};
if(vErrors === null){
-vErrors = [err99];
+vErrors = [err161];
}
else {
-vErrors.push(err99);
+vErrors.push(err161);
}
errors++;
}
-if(!(data49 === "image")){
-const err100 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/7/properties/type/enum",keyword:"enum",params:{allowedValues: schema57.oneOf[7].properties.type.enum},message:"must be equal to one of the allowed values"};
+}
+let i5 = data78.length;
+let j1;
+if(i5 > 1){
+const indices0 = {};
+for(;i5--;){
+let item0 = data78[i5];
+if(typeof item0 !== "string"){
+continue;
+}
+if(typeof indices0[item0] == "number"){
+j1 = indices0[item0];
+const err162 = {instancePath:instancePath+"/where/" + i3+"/values",schemaPath:"#/$defs/FieldCondition/properties/values/uniqueItems",keyword:"uniqueItems",params:{i: i5, j: j1},message:"must NOT have duplicate items (items ## "+j1+" and "+i5+" are identical)"};
if(vErrors === null){
-vErrors = [err100];
+vErrors = [err162];
}
else {
-vErrors.push(err100);
+vErrors.push(err162);
}
errors++;
+break;
}
+indices0[item0] = i5;
}
-if(data.labels !== undefined){
-if(!(validate26(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors);
-errors = vErrors.length;
}
}
-if(data.localized !== undefined){
-if(typeof data.localized !== "boolean"){
-const err101 = {instancePath:instancePath+"/localized",schemaPath:"#/oneOf/7/properties/localized/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+else {
+const err163 = {instancePath:instancePath+"/where/" + i3+"/values",schemaPath:"#/$defs/FieldCondition/properties/values/type",keyword:"type",params:{type: "array"},message:"must be array"};
if(vErrors === null){
-vErrors = [err101];
+vErrors = [err163];
}
else {
-vErrors.push(err101);
+vErrors.push(err163);
}
errors++;
}
}
-if(data.requiredToPublish !== undefined){
-if(typeof data.requiredToPublish !== "boolean"){
-const err102 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/7/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+}
+else {
+const err164 = {instancePath:instancePath+"/where/" + i3,schemaPath:"#/$defs/FieldCondition/type",keyword:"type",params:{type: "object"},message:"must be object"};
if(vErrors === null){
-vErrors = [err102];
+vErrors = [err164];
}
else {
-vErrors.push(err102);
+vErrors.push(err164);
}
errors++;
}
}
-if(data.migration !== undefined){
-if(!(validate28(data.migration, {instancePath:instancePath+"/migration",parentData:data,parentDataProperty:"migration",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate28.errors : vErrors.concat(validate28.errors);
-errors = vErrors.length;
+}
+else {
+const err165 = {instancePath:instancePath+"/where",schemaPath:"#/oneOf/9/properties/where/type",keyword:"type",params:{type: "array"},message:"must be array"};
+if(vErrors === null){
+vErrors = [err165];
+}
+else {
+vErrors.push(err165);
+}
+errors++;
}
}
}
else {
-const err103 = {instancePath,schemaPath:"#/oneOf/7/type",keyword:"type",params:{type: "object"},message:"must be object"};
+const err166 = {instancePath,schemaPath:"#/oneOf/9/type",keyword:"type",params:{type: "object"},message:"must be object"};
if(vErrors === null){
-vErrors = [err103];
+vErrors = [err166];
}
else {
-vErrors.push(err103);
+vErrors.push(err166);
}
errors++;
}
-var _valid0 = _errs111 === errors;
+var _valid0 = _errs144 === errors;
if(_valid0 && valid0){
valid0 = false;
-passing0 = [passing0, 7];
+passing0 = [passing0, 9];
}
else {
if(_valid0){
valid0 = true;
-passing0 = 7;
+passing0 = 9;
if(props0 !== true){
props0 = true;
}
}
-const _errs125 = errors;
+const _errs183 = errors;
if(data && typeof data == "object" && !Array.isArray(data)){
if(data.key === undefined){
-const err104 = {instancePath,schemaPath:"#/oneOf/8/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
+const err167 = {instancePath,schemaPath:"#/oneOf/10/required",keyword:"required",params:{missingProperty: "key"},message:"must have required property '"+"key"+"'"};
if(vErrors === null){
-vErrors = [err104];
+vErrors = [err167];
}
else {
-vErrors.push(err104);
+vErrors.push(err167);
}
errors++;
}
if(data.type === undefined){
-const err105 = {instancePath,schemaPath:"#/oneOf/8/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
+const err168 = {instancePath,schemaPath:"#/oneOf/10/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};
if(vErrors === null){
-vErrors = [err105];
+vErrors = [err168];
}
else {
-vErrors.push(err105);
+vErrors.push(err168);
}
errors++;
}
if(data.labels === undefined){
-const err106 = {instancePath,schemaPath:"#/oneOf/8/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
+const err169 = {instancePath,schemaPath:"#/oneOf/10/required",keyword:"required",params:{missingProperty: "labels"},message:"must have required property '"+"labels"+"'"};
if(vErrors === null){
-vErrors = [err106];
+vErrors = [err169];
}
else {
-vErrors.push(err106);
+vErrors.push(err169);
}
errors++;
}
if(data.localized === undefined){
-const err107 = {instancePath,schemaPath:"#/oneOf/8/required",keyword:"required",params:{missingProperty: "localized"},message:"must have required property '"+"localized"+"'"};
+const err170 = {instancePath,schemaPath:"#/oneOf/10/required",keyword:"required",params:{missingProperty: "localized"},message:"must have required property '"+"localized"+"'"};
if(vErrors === null){
-vErrors = [err107];
+vErrors = [err170];
}
else {
-vErrors.push(err107);
+vErrors.push(err170);
}
errors++;
}
-if(data.options === undefined){
-const err108 = {instancePath,schemaPath:"#/oneOf/8/required",keyword:"required",params:{missingProperty: "options"},message:"must have required property '"+"options"+"'"};
+if(data.fields === undefined){
+const err171 = {instancePath,schemaPath:"#/oneOf/10/required",keyword:"required",params:{missingProperty: "fields"},message:"must have required property '"+"fields"+"'"};
if(vErrors === null){
-vErrors = [err108];
+vErrors = [err171];
}
else {
-vErrors.push(err108);
+vErrors.push(err171);
}
errors++;
}
-for(const key8 in data){
-if(!((((((((key8 === "key") || (key8 === "type")) || (key8 === "labels")) || (key8 === "localized")) || (key8 === "requiredToPublish")) || (key8 === "migration")) || (key8 === "options")) || (key8 === "multiple"))){
-const err109 = {instancePath,schemaPath:"#/oneOf/8/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key8},message:"must NOT have additional properties"};
+for(const key11 in data){
+if(!(func1.call(schema57.oneOf[10].properties, key11))){
+const err172 = {instancePath,schemaPath:"#/oneOf/10/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key11},message:"must NOT have additional properties"};
if(vErrors === null){
-vErrors = [err109];
+vErrors = [err172];
}
else {
-vErrors.push(err109);
+vErrors.push(err172);
}
errors++;
}
}
if(data.key !== undefined){
-let data54 = data.key;
-if(typeof data54 === "string"){
-if(!pattern22.test(data54)){
-const err110 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
+let data80 = data.key;
+if(typeof data80 === "string"){
+if(!pattern22.test(data80)){
+const err173 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
if(vErrors === null){
-vErrors = [err110];
+vErrors = [err173];
}
else {
-vErrors.push(err110);
+vErrors.push(err173);
}
errors++;
}
}
else {
-const err111 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
+const err174 = {instancePath:instancePath+"/key",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
if(vErrors === null){
-vErrors = [err111];
+vErrors = [err174];
}
else {
-vErrors.push(err111);
+vErrors.push(err174);
}
errors++;
}
}
if(data.type !== undefined){
-let data55 = data.type;
-if(typeof data55 !== "string"){
-const err112 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/8/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
+let data81 = data.type;
+if(typeof data81 !== "string"){
+const err175 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/10/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};
if(vErrors === null){
-vErrors = [err112];
+vErrors = [err175];
}
else {
-vErrors.push(err112);
+vErrors.push(err175);
}
errors++;
}
-if(!(data55 === "select")){
-const err113 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/8/properties/type/enum",keyword:"enum",params:{allowedValues: schema57.oneOf[8].properties.type.enum},message:"must be equal to one of the allowed values"};
+if(!(data81 === "repeater")){
+const err176 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/10/properties/type/enum",keyword:"enum",params:{allowedValues: schema57.oneOf[10].properties.type.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
-vErrors = [err113];
+vErrors = [err176];
}
else {
-vErrors.push(err113);
+vErrors.push(err176);
}
errors++;
}
@@ -2900,24 +5218,24 @@ errors = vErrors.length;
}
if(data.localized !== undefined){
if(typeof data.localized !== "boolean"){
-const err114 = {instancePath:instancePath+"/localized",schemaPath:"#/oneOf/8/properties/localized/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+const err177 = {instancePath:instancePath+"/localized",schemaPath:"#/oneOf/10/properties/localized/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
if(vErrors === null){
-vErrors = [err114];
+vErrors = [err177];
}
else {
-vErrors.push(err114);
+vErrors.push(err177);
}
errors++;
}
}
if(data.requiredToPublish !== undefined){
if(typeof data.requiredToPublish !== "boolean"){
-const err115 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/8/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
+const err178 = {instancePath:instancePath+"/requiredToPublish",schemaPath:"#/oneOf/10/properties/requiredToPublish/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
if(vErrors === null){
-vErrors = [err115];
+vErrors = [err178];
}
else {
-vErrors.push(err115);
+vErrors.push(err178);
}
errors++;
}
@@ -2928,80 +5246,128 @@ vErrors = vErrors === null ? validate28.errors : vErrors.concat(validate28.error
errors = vErrors.length;
}
}
-if(data.options !== undefined){
-let data60 = data.options;
-if(Array.isArray(data60)){
-if(data60.length > 50){
-const err116 = {instancePath:instancePath+"/options",schemaPath:"#/oneOf/8/properties/options/maxItems",keyword:"maxItems",params:{limit: 50},message:"must NOT have more than 50 items"};
+if(data.fields !== undefined){
+let data86 = data.fields;
+if(Array.isArray(data86)){
+if(data86.length > 10){
+const err179 = {instancePath:instancePath+"/fields",schemaPath:"#/oneOf/10/properties/fields/maxItems",keyword:"maxItems",params:{limit: 10},message:"must NOT have more than 10 items"};
if(vErrors === null){
-vErrors = [err116];
+vErrors = [err179];
}
else {
-vErrors.push(err116);
+vErrors.push(err179);
}
errors++;
}
-if(data60.length < 1){
-const err117 = {instancePath:instancePath+"/options",schemaPath:"#/oneOf/8/properties/options/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};
+if(data86.length < 1){
+const err180 = {instancePath:instancePath+"/fields",schemaPath:"#/oneOf/10/properties/fields/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};
if(vErrors === null){
-vErrors = [err117];
+vErrors = [err180];
}
else {
-vErrors.push(err117);
+vErrors.push(err180);
}
errors++;
}
-const len0 = data60.length;
-for(let i0=0; i0 50 || isNaN(data88)){
+const err183 = {instancePath:instancePath+"/maxItems",schemaPath:"#/oneOf/10/properties/maxItems/maximum",keyword:"maximum",params:{comparison: "<=", limit: 50},message:"must be <= 50"};
+if(vErrors === null){
+vErrors = [err183];
+}
+else {
+vErrors.push(err183);
+}
+errors++;
+}
+if(data88 < 1 || isNaN(data88)){
+const err184 = {instancePath:instancePath+"/maxItems",schemaPath:"#/oneOf/10/properties/maxItems/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};
+if(vErrors === null){
+vErrors = [err184];
+}
+else {
+vErrors.push(err184);
}
errors++;
}
}
}
+if(data.itemLabel !== undefined){
+let data89 = data.itemLabel;
+if(typeof data89 === "string"){
+if(!pattern22.test(data89)){
+const err185 = {instancePath:instancePath+"/itemLabel",schemaPath:"#/$defs/FieldKey/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z0-9]{0,39}$"},message:"must match pattern \""+"^[a-z][A-Za-z0-9]{0,39}$"+"\""};
+if(vErrors === null){
+vErrors = [err185];
+}
else {
-const err120 = {instancePath,schemaPath:"#/oneOf/8/type",keyword:"type",params:{type: "object"},message:"must be object"};
+vErrors.push(err185);
+}
+errors++;
+}
+}
+else {
+const err186 = {instancePath:instancePath+"/itemLabel",schemaPath:"#/$defs/FieldKey/type",keyword:"type",params:{type: "string"},message:"must be string"};
if(vErrors === null){
-vErrors = [err120];
+vErrors = [err186];
}
else {
-vErrors.push(err120);
+vErrors.push(err186);
}
errors++;
}
-var _valid0 = _errs125 === errors;
+}
+}
+else {
+const err187 = {instancePath,schemaPath:"#/oneOf/10/type",keyword:"type",params:{type: "object"},message:"must be object"};
+if(vErrors === null){
+vErrors = [err187];
+}
+else {
+vErrors.push(err187);
+}
+errors++;
+}
+var _valid0 = _errs183 === errors;
if(_valid0 && valid0){
valid0 = false;
-passing0 = [passing0, 8];
+passing0 = [passing0, 10];
}
else {
if(_valid0){
valid0 = true;
-passing0 = 8;
+passing0 = 10;
if(props0 !== true){
props0 = true;
}
@@ -3014,13 +5380,15 @@ props0 = true;
}
}
}
+}
+}
if(!valid0){
-const err121 = {instancePath,schemaPath:"#/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};
+const err188 = {instancePath,schemaPath:"#/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};
if(vErrors === null){
-vErrors = [err121];
+vErrors = [err188];
}
else {
-vErrors.push(err121);
+vErrors.push(err188);
}
errors++;
}
@@ -3041,12 +5409,12 @@ return errors === 0;
}
validate25.evaluated = {"dynamicProps":true,"dynamicItems":false};
-const schema77 = {"type":"object","description":"RFC-0042: a time gate every entry derives from one of its date or datetime fields, checked at render time like Availability.until. A date ends at the start of the next day in the site's time zone.","additionalProperties":false,"required":["field","action"],"properties":{"field":{"$ref":"#/$defs/FieldKey"},"action":{"type":"string","description":"unlist: the page stays, the entry leaves listings, feeds, the sitemap and search. hide: every public route answers 404.","enum":["unlist","hide"]},"afterMinutes":{"type":"integer","description":"Grace after the field's instant. Absent: 0.","minimum":0,"maximum":10080}}};
+const schema94 = {"type":"object","description":"RFC-0042: a time gate every entry derives from one of its date or datetime fields, checked at render time like Availability.until. A date ends at the start of the next day in the site's time zone.","additionalProperties":false,"required":["field","action"],"properties":{"field":{"$ref":"#/$defs/FieldKey"},"action":{"type":"string","description":"unlist: the page stays, the entry leaves listings, feeds, the sitemap and search. hide: every public route answers 404.","enum":["unlist","hide"]},"afterMinutes":{"type":"integer","description":"Grace after the field's instant. Absent: 0.","minimum":0,"maximum":10080}}};
-function validate49(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate65(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate49.evaluated;
+const evaluated0 = validate65.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -3124,7 +5492,7 @@ vErrors.push(err5);
errors++;
}
if(!((data1 === "unlist") || (data1 === "hide"))){
-const err6 = {instancePath:instancePath+"/action",schemaPath:"#/properties/action/enum",keyword:"enum",params:{allowedValues: schema77.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: schema94.properties.action.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err6];
}
@@ -3180,22 +5548,22 @@ vErrors.push(err10);
}
errors++;
}
-validate49.errors = vErrors;
+validate65.errors = vErrors;
return errors === 0;
}
-validate49.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate65.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-const schema79 = {"type":"object","description":"RFC-0042: the JSON-LD type an entry page emits in place of BlogPosting, with its properties mapped to value sources. Paths, fitting field types and required properties are a closed table in code (@cloudhal/core-ts/structured-data).","additionalProperties":false,"required":["type","properties"],"properties":{"type":{"type":"string","enum":["Event","JobPosting","Product","Recipe"]},"properties":{"type":"object","description":"Keyed by property path, e.g. startDate or location.address.addressLocality.","propertyNames":{"type":"string","pattern":"^[a-z][A-Za-z]{0,39}(\\.[a-z][A-Za-z]{0,39}){0,2}$"},"minProperties":1,"maxProperties":40,"additionalProperties":{"$ref":"#/$defs/StructuredDataSource"}}}};
-const pattern38 = new RegExp("^[a-z][A-Za-z]{0,39}(\\.[a-z][A-Za-z]{0,39}){0,2}$", "u");
-const schema80 = {"description":"RFC-0042: where one JSON-LD property's value comes from, discriminated by `kind`.","oneOf":[{"title":"FieldValueSource","type":"object","description":"A typed field of this collection, converted by its type.","additionalProperties":false,"required":["kind","field"],"properties":{"kind":{"type":"string","enum":["field"]},"field":{"$ref":"#/$defs/FieldKey"}}},{"title":"EntryValueSource","type":"object","description":"A built-in property of the entry.","additionalProperties":false,"required":["kind","property"],"properties":{"kind":{"type":"string","enum":["entry"]},"property":{"$ref":"#/$defs/EntryValueProperty"}}},{"title":"SiteValueSource","type":"object","description":"A property of the site.","additionalProperties":false,"required":["kind","property"],"properties":{"kind":{"type":"string","enum":["site"]},"property":{"$ref":"#/$defs/SiteValueProperty"}}},{"title":"ConstantValueSource","type":"object","description":"A plain-text constant: no control characters, no markup meaning; vocabulary-checked on enum paths (in code).","additionalProperties":false,"required":["kind","value"],"properties":{"kind":{"type":"string","enum":["constant"]},"value":{"type":"string","minLength":1,"maxLength":200,"pattern":"^[^\\x00-\\x1F\\x7F]+$"}}}]};
-const schema82 = {"type":"string","description":"RFC-0042: entry properties a structured-data property may read.","enum":["title","summary","body","coverImage","url","publishedAt","updatedAt"]};
-const schema83 = {"type":"string","description":"RFC-0042: site properties a structured-data property may read.","enum":["name","url","logo"]};
-const pattern40 = new RegExp("^[^\\x00-\\x1F\\x7F]+$", "u");
+const schema96 = {"type":"object","description":"RFC-0042: the JSON-LD type an entry page emits in place of BlogPosting, with its properties mapped to value sources. Paths, fitting field types and required properties are a closed table in code (@cloudhal/core-ts/structured-data).","additionalProperties":false,"required":["type","properties"],"properties":{"type":{"type":"string","enum":["Event","JobPosting","Product","Recipe"]},"properties":{"type":"object","description":"Keyed by property path, e.g. startDate or location.address.addressLocality.","propertyNames":{"type":"string","pattern":"^[a-z][A-Za-z]{0,39}(\\.[a-z][A-Za-z]{0,39}){0,2}$"},"minProperties":1,"maxProperties":40,"additionalProperties":{"$ref":"#/$defs/StructuredDataSource"}}}};
+const pattern54 = new RegExp("^[a-z][A-Za-z]{0,39}(\\.[a-z][A-Za-z]{0,39}){0,2}$", "u");
+const schema97 = {"description":"RFC-0042: where one JSON-LD property's value comes from, discriminated by `kind`.","oneOf":[{"title":"FieldValueSource","type":"object","description":"A typed field of this collection, converted by its type.","additionalProperties":false,"required":["kind","field"],"properties":{"kind":{"type":"string","enum":["field"]},"field":{"$ref":"#/$defs/FieldKey"}}},{"title":"EntryValueSource","type":"object","description":"A built-in property of the entry.","additionalProperties":false,"required":["kind","property"],"properties":{"kind":{"type":"string","enum":["entry"]},"property":{"$ref":"#/$defs/EntryValueProperty"}}},{"title":"SiteValueSource","type":"object","description":"A property of the site.","additionalProperties":false,"required":["kind","property"],"properties":{"kind":{"type":"string","enum":["site"]},"property":{"$ref":"#/$defs/SiteValueProperty"}}},{"title":"ConstantValueSource","type":"object","description":"A plain-text constant: no control characters, no markup meaning; vocabulary-checked on enum paths (in code).","additionalProperties":false,"required":["kind","value"],"properties":{"kind":{"type":"string","enum":["constant"]},"value":{"type":"string","minLength":1,"maxLength":200,"pattern":"^[^\\x00-\\x1F\\x7F]+$"}}}]};
+const schema99 = {"type":"string","description":"RFC-0042: entry properties a structured-data property may read.","enum":["title","summary","body","coverImage","url","publishedAt","updatedAt"]};
+const schema100 = {"type":"string","description":"RFC-0042: site properties a structured-data property may read.","enum":["name","url","logo"]};
+const pattern56 = new RegExp("^[^\\x00-\\x1F\\x7F]+$", "u");
-function validate52(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate68(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate52.evaluated;
+const evaluated0 = validate68.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -3252,7 +5620,7 @@ vErrors.push(err3);
errors++;
}
if(!(data0 === "field")){
-const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema80.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: schema97.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err4];
}
@@ -3351,7 +5719,7 @@ vErrors.push(err11);
errors++;
}
if(!(data2 === "entry")){
-const err12 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema80.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"};
+const err12 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema97.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err12];
}
@@ -3374,7 +5742,7 @@ vErrors.push(err13);
errors++;
}
if(!(((((((data3 === "title") || (data3 === "summary")) || (data3 === "body")) || (data3 === "coverImage")) || (data3 === "url")) || (data3 === "publishedAt")) || (data3 === "updatedAt"))){
-const err14 = {instancePath:instancePath+"/property",schemaPath:"#/$defs/EntryValueProperty/enum",keyword:"enum",params:{allowedValues: schema82.enum},message:"must be equal to one of the allowed values"};
+const err14 = {instancePath:instancePath+"/property",schemaPath:"#/$defs/EntryValueProperty/enum",keyword:"enum",params:{allowedValues: schema99.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err14];
}
@@ -3455,7 +5823,7 @@ vErrors.push(err19);
errors++;
}
if(!(data4 === "site")){
-const err20 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/2/properties/kind/enum",keyword:"enum",params:{allowedValues: schema80.oneOf[2].properties.kind.enum},message:"must be equal to one of the allowed values"};
+const err20 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/2/properties/kind/enum",keyword:"enum",params:{allowedValues: schema97.oneOf[2].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err20];
}
@@ -3478,7 +5846,7 @@ vErrors.push(err21);
errors++;
}
if(!(((data5 === "name") || (data5 === "url")) || (data5 === "logo"))){
-const err22 = {instancePath:instancePath+"/property",schemaPath:"#/$defs/SiteValueProperty/enum",keyword:"enum",params:{allowedValues: schema83.enum},message:"must be equal to one of the allowed values"};
+const err22 = {instancePath:instancePath+"/property",schemaPath:"#/$defs/SiteValueProperty/enum",keyword:"enum",params:{allowedValues: schema100.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err22];
}
@@ -3559,7 +5927,7 @@ vErrors.push(err27);
errors++;
}
if(!(data6 === "constant")){
-const err28 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/3/properties/kind/enum",keyword:"enum",params:{allowedValues: schema80.oneOf[3].properties.kind.enum},message:"must be equal to one of the allowed values"};
+const err28 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/3/properties/kind/enum",keyword:"enum",params:{allowedValues: schema97.oneOf[3].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err28];
}
@@ -3592,7 +5960,7 @@ vErrors.push(err30);
}
errors++;
}
-if(!pattern40.test(data7)){
+if(!pattern56.test(data7)){
const err31 = {instancePath:instancePath+"/value",schemaPath:"#/oneOf/3/properties/value/pattern",keyword:"pattern",params:{pattern: "^[^\\x00-\\x1F\\x7F]+$"},message:"must match pattern \""+"^[^\\x00-\\x1F\\x7F]+$"+"\""};
if(vErrors === null){
vErrors = [err31];
@@ -3662,17 +6030,17 @@ vErrors = null;
}
}
}
-validate52.errors = vErrors;
+validate68.errors = vErrors;
evaluated0.props = props0;
return errors === 0;
}
-validate52.evaluated = {"dynamicProps":true,"dynamicItems":false};
+validate68.evaluated = {"dynamicProps":true,"dynamicItems":false};
-function validate51(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate67(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate51.evaluated;
+const evaluated0 = validate67.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -3725,7 +6093,7 @@ vErrors.push(err3);
errors++;
}
if(!((((data0 === "Event") || (data0 === "JobPosting")) || (data0 === "Product")) || (data0 === "Recipe"))){
-const err4 = {instancePath:instancePath+"/type",schemaPath:"#/properties/type/enum",keyword:"enum",params:{allowedValues: schema79.properties.type.enum},message:"must be equal to one of the allowed values"};
+const err4 = {instancePath:instancePath+"/type",schemaPath:"#/properties/type/enum",keyword:"enum",params:{allowedValues: schema96.properties.type.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err4];
}
@@ -3761,7 +6129,7 @@ errors++;
for(const key1 in data1){
const _errs6 = errors;
if(typeof key1 === "string"){
-if(!pattern38.test(key1)){
+if(!pattern54.test(key1)){
const err7 = {instancePath:instancePath+"/properties",schemaPath:"#/properties/properties/propertyNames/pattern",keyword:"pattern",params:{pattern: "^[a-z][A-Za-z]{0,39}(\\.[a-z][A-Za-z]{0,39}){0,2}$"},message:"must match pattern \""+"^[a-z][A-Za-z]{0,39}(\\.[a-z][A-Za-z]{0,39}){0,2}$"+"\"",propertyName:key1};
if(vErrors === null){
vErrors = [err7];
@@ -3795,8 +6163,8 @@ errors++;
}
}
for(const key2 in data1){
-if(!(validate52(data1[key2], {instancePath:instancePath+"/properties/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),parentData:data1,parentDataProperty:key2,rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate52.errors : vErrors.concat(validate52.errors);
+if(!(validate68(data1[key2], {instancePath:instancePath+"/properties/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),parentData:data1,parentDataProperty:key2,rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate68.errors : vErrors.concat(validate68.errors);
errors = vErrors.length;
}
}
@@ -3823,10 +6191,10 @@ vErrors.push(err11);
}
errors++;
}
-validate51.errors = vErrors;
+validate67.errors = vErrors;
return errors === 0;
}
-validate51.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate67.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
function validate24(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
@@ -4286,14 +6654,14 @@ errors++;
}
}
if(data.expiry !== undefined){
-if(!(validate49(data.expiry, {instancePath:instancePath+"/expiry",parentData:data,parentDataProperty:"expiry",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate49.errors : vErrors.concat(validate49.errors);
+if(!(validate65(data.expiry, {instancePath:instancePath+"/expiry",parentData:data,parentDataProperty:"expiry",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate65.errors : vErrors.concat(validate65.errors);
errors = vErrors.length;
}
}
if(data.structuredData !== undefined){
-if(!(validate51(data.structuredData, {instancePath:instancePath+"/structuredData",parentData:data,parentDataProperty:"structuredData",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate51.errors : vErrors.concat(validate51.errors);
+if(!(validate67(data.structuredData, {instancePath:instancePath+"/structuredData",parentData:data,parentDataProperty:"structuredData",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate67.errors : vErrors.concat(validate67.errors);
errors = vErrors.length;
}
}
@@ -4313,12 +6681,12 @@ return errors === 0;
}
validate24.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-const schema84 = {"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 schema101 = {"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 validate56(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate72(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate56.evaluated;
+const evaluated0 = validate72.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -4444,19 +6812,19 @@ vErrors.push(err9);
}
errors++;
}
-validate56.errors = vErrors;
+validate72.errors = vErrors;
return errors === 0;
}
-validate56.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate72.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-const schema87 = {"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 schema90 = {"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 schema88 = {"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 schema104 = {"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 schema107 = {"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 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}}};
-function validate59(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate75(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate59.evaluated;
+const evaluated0 = validate75.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -4662,16 +7030,16 @@ vErrors.push(err16);
}
errors++;
}
-validate59.errors = vErrors;
+validate75.errors = vErrors;
return errors === 0;
}
-validate59.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate75.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-function validate58(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate74(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate58.evaluated;
+const evaluated0 = validate74.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -4692,14 +7060,14 @@ errors++;
}
}
if(data.logo !== undefined){
-if(!(validate59(data.logo, {instancePath:instancePath+"/logo",parentData:data,parentDataProperty:"logo",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate59.errors : vErrors.concat(validate59.errors);
+if(!(validate75(data.logo, {instancePath:instancePath+"/logo",parentData:data,parentDataProperty:"logo",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate75.errors : vErrors.concat(validate75.errors);
errors = vErrors.length;
}
}
if(data.logoDark !== undefined){
-if(!(validate59(data.logoDark, {instancePath:instancePath+"/logoDark",parentData:data,parentDataProperty:"logoDark",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate59.errors : vErrors.concat(validate59.errors);
+if(!(validate75(data.logoDark, {instancePath:instancePath+"/logoDark",parentData:data,parentDataProperty:"logoDark",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate75.errors : vErrors.concat(validate75.errors);
errors = vErrors.length;
}
}
@@ -4716,7 +7084,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: schema87.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: schema104.properties.logoHeight.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err2];
}
@@ -4788,7 +7156,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: schema90.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: schema107.properties.network.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err8];
}
@@ -4868,20 +7236,20 @@ vErrors.push(err14);
}
errors++;
}
-validate58.errors = vErrors;
+validate74.errors = vErrors;
return errors === 0;
}
-validate58.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate74.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-const schema91 = {"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 schema121 = {"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 schema92 = {"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 schema95 = {"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 schema108 = {"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 schema138 = {"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 schema109 = {"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 schema112 = {"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 validate64(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate80(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate64.evaluated;
+const evaluated0 = validate80.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -4890,7 +7258,7 @@ evaluated0.items = undefined;
}
if(data && typeof data == "object" && !Array.isArray(data)){
for(const key0 in data){
-if(!(func1.call(schema92.properties, key0))){
+if(!(func1.call(schema109.properties, key0))){
const err0 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};
if(vErrors === null){
vErrors = [err0];
@@ -4914,7 +7282,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: schema92.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: schema109.properties.layout.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err2];
}
@@ -4937,7 +7305,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: schema92.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: schema109.properties.behavior.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err4];
}
@@ -4972,7 +7340,7 @@ vErrors.push(err6);
errors++;
}
if(!((data3 === "solid") || (data3 === "transparent"))){
-const err7 = {instancePath:instancePath+"/overHero",schemaPath:"#/properties/overHero/enum",keyword:"enum",params:{allowedValues: schema92.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: schema109.properties.overHero.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err7];
}
@@ -4995,7 +7363,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: schema92.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: schema109.properties.width.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err9];
}
@@ -5018,7 +7386,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: schema92.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: schema109.properties.surface.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err11];
}
@@ -5041,7 +7409,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: schema92.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: schema109.properties.mobileStyle.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err13];
}
@@ -5064,7 +7432,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: schema92.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: schema109.properties.collapseAt.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err15];
}
@@ -5216,7 +7584,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: schema95.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: schema112.properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err28];
}
@@ -5309,7 +7677,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: schema95.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: schema112.properties.variant.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err36];
}
@@ -5354,18 +7722,18 @@ vErrors.push(err39);
}
errors++;
}
-validate64.errors = vErrors;
+validate80.errors = vErrors;
return errors === 0;
}
-validate64.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate80.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-const schema96 = {"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 schema97 = {"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 schema113 = {"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 schema114 = {"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 validate67(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate83(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate67.evaluated;
+const evaluated0 = validate83.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -5496,29 +7864,28 @@ vErrors.push(err10);
}
errors++;
}
-validate67.errors = vErrors;
+validate83.errors = vErrors;
return errors === 0;
}
-validate67.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate83.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-const schema100 = {"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},"where":{"type":"array","description":"RFC-0042: conditions on the collection's list fields, ANDed.","minItems":1,"maxItems":4,"items":{"$ref":"#/$defs/FieldCondition"}},"orderBy":{"$ref":"#/$defs/FieldOrder"}}},{"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 schema102 = {"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 schema104 = {"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 schema114 = {"type":"object","description":"RFC-0042: one condition on a typed field of the listed collection. `value` is read by the field's type: a number, YYYY-MM-DD, an RFC 3339 instant, true/false, an option value, or `now`. `values` only with `in`.","additionalProperties":false,"required":["field","op"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"op":{"type":"string","enum":["eq","ne","lt","lte","gt","gte","in","set","unset"]},"value":{"type":"string","minLength":1,"maxLength":100},"values":{"type":"array","minItems":1,"maxItems":20,"uniqueItems":true,"items":{"type":"string","minLength":1,"maxLength":100}}}};
-const schema115 = {"type":"object","description":"RFC-0042: order a listing by a number, date or datetime field. Entries without a value sort last.","additionalProperties":false,"required":["field","direction"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"direction":{"type":"string","enum":["asc","desc"]}}};
-const pattern58 = new RegExp("^[a-z][a-z0-9-]{1,40}$", "u");
-const pattern60 = new RegExp("^[a-z][a-z0-9-]{0,40}@[0-9]{1,4}$", "u");
-const schema105 = {"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 schema106 = {"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 pattern49 = new RegExp("^blk_[0-9A-Za-z]{8,24}$", "u");
-const pattern50 = new RegExp("^[0-9a-f]{64}$", "u");
-const schema107 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}};
-const schema108 = {"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 schema117 = {"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},"where":{"type":"array","description":"RFC-0042: conditions on the collection's list fields, ANDed.","minItems":1,"maxItems":4,"items":{"$ref":"#/$defs/FieldCondition"}},"orderBy":{"$ref":"#/$defs/FieldOrder"}}},{"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 schema119 = {"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 schema121 = {"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 schema132 = {"type":"object","description":"RFC-0042: order a listing by a number, date or datetime field. Entries without a value sort last.","additionalProperties":false,"required":["field","direction"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"direction":{"type":"string","enum":["asc","desc"]}}};
+const pattern74 = new RegExp("^[a-z][a-z0-9-]{1,40}$", "u");
+const pattern76 = new RegExp("^[a-z][a-z0-9-]{0,40}@[0-9]{1,4}$", "u");
+const schema122 = {"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 schema123 = {"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 pattern65 = new RegExp("^blk_[0-9A-Za-z]{8,24}$", "u");
+const pattern66 = new RegExp("^[0-9a-f]{64}$", "u");
+const schema124 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}};
+const schema125 = {"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 validate73(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate89(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate73.evaluated;
+const evaluated0 = validate89.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -5571,7 +7938,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: schema108.enum},message:"must be equal to one of the allowed values"};
+const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/$defs/ActorKind/enum",keyword:"enum",params:{allowedValues: schema125.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err4];
}
@@ -5662,16 +8029,16 @@ vErrors.push(err11);
}
errors++;
}
-validate73.errors = vErrors;
+validate89.errors = vErrors;
return errors === 0;
}
-validate73.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate89.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-function validate72(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate88(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate72.evaluated;
+const evaluated0 = validate88.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -5714,7 +8081,7 @@ errors++;
if(data.id !== undefined){
let data0 = data.id;
if(typeof data0 === "string"){
-if(!pattern49.test(data0)){
+if(!pattern65.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];
@@ -5739,7 +8106,7 @@ errors++;
if(data.hash !== undefined){
let data1 = data.hash;
if(typeof data1 === "string"){
-if(!pattern50.test(data1)){
+if(!pattern66.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];
@@ -5762,8 +8129,8 @@ errors++;
}
}
if(data.by !== undefined){
-if(!(validate73(data.by, {instancePath:instancePath+"/by",parentData:data,parentDataProperty:"by",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate73.errors : vErrors.concat(validate73.errors);
+if(!(validate89(data.by, {instancePath:instancePath+"/by",parentData:data,parentDataProperty:"by",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate89.errors : vErrors.concat(validate89.errors);
errors = vErrors.length;
}
}
@@ -5780,7 +8147,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: schema106.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: schema123.properties.via.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err8];
}
@@ -5826,16 +8193,16 @@ vErrors.push(err11);
}
errors++;
}
-validate72.errors = vErrors;
+validate88.errors = vErrors;
return errors === 0;
}
-validate72.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate88.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-function validate71(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate87(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate71.evaluated;
+const evaluated0 = validate87.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -5855,8 +8222,8 @@ errors++;
}
const len0 = data.length;
for(let i0=0; i0*'.","minLength":1,"maxLength":512},"where":{"type":"array","description":"RFC-0042: conditions on the collection's list fields, ANDed.","minItems":1,"maxItems":4,"items":{"$ref":"#/$defs/FieldCondition"}},"orderBy":{"$ref":"#/$defs/FieldOrder"}}},{"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 schema131 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]};
-const schema132 = {"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 schema134 = {"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 schema135 = {"type":"string","description":"Editorial lifecycle of a page or content entry.","enum":["draft","review","published","archived"]};
-const schema163 = {"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 schema164 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0};
-const schema148 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"};
-const schema177 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}};
-const schema136 = {"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},"where":{"type":"array","description":"RFC-0042: conditions on the collection's list fields, ANDed.","minItems":1,"maxItems":4,"items":{"$ref":"#/$defs/FieldCondition"}},"orderBy":{"$ref":"#/$defs/FieldOrder"}}},{"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 schema140 = {"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 schema142 = {"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 schema151 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128};
-const schema152 = {"type":"object","description":"RFC-0042: one condition on a typed field of the listed collection. `value` is read by the field's type: a number, YYYY-MM-DD, an RFC 3339 instant, true/false, an option value, or `now`. `values` only with `in`.","additionalProperties":false,"required":["field","op"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"op":{"type":"string","enum":["eq","ne","lt","lte","gt","gte","in","set","unset"]},"value":{"type":"string","minLength":1,"maxLength":100},"values":{"type":"array","minItems":1,"maxItems":20,"uniqueItems":true,"items":{"type":"string","minLength":1,"maxLength":100}}}};
-const schema153 = {"type":"object","description":"RFC-0042: order a listing by a number, date or datetime field. Entries without a value sort last.","additionalProperties":false,"required":["field","direction"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"direction":{"type":"string","enum":["asc","desc"]}}};
-const schema138 = {"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}}};
+export const validatePage = validate101;
+const schema147 = {"$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."},"FieldCondition":{"type":"object","description":"RFC-0042: one condition on a typed field of the listed collection. `value` is read by the field's type: a number, YYYY-MM-DD, an RFC 3339 instant, true/false, an option value, or `now`. `values` only with `in`.","additionalProperties":false,"required":["field","op"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"op":{"type":"string","enum":["eq","ne","lt","lte","gt","gte","in","set","unset"]},"value":{"type":"string","minLength":1,"maxLength":100},"values":{"type":"array","minItems":1,"maxItems":20,"uniqueItems":true,"items":{"type":"string","minLength":1,"maxLength":100}}}},"FieldOrder":{"type":"object","description":"RFC-0042: order a listing by a number, date or datetime field. Entries without a value sort last.","additionalProperties":false,"required":["field","direction"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"direction":{"type":"string","enum":["asc","desc"]}}},"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},"where":{"type":"array","description":"RFC-0042: conditions on the collection's list fields, ANDed.","minItems":1,"maxItems":4,"items":{"$ref":"#/$defs/FieldCondition"}},"orderBy":{"$ref":"#/$defs/FieldOrder"}}},{"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 schema148 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]};
+const schema149 = {"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 schema151 = {"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 schema152 = {"type":"string","description":"Editorial lifecycle of a page or content entry.","enum":["draft","review","published","archived"]};
+const schema180 = {"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 schema181 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0};
+const schema165 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"};
+const schema194 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}};
+const schema153 = {"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},"where":{"type":"array","description":"RFC-0042: conditions on the collection's list fields, ANDed.","minItems":1,"maxItems":4,"items":{"$ref":"#/$defs/FieldCondition"}},"orderBy":{"$ref":"#/$defs/FieldOrder"}}},{"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 schema157 = {"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 schema159 = {"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 schema168 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128};
+const schema169 = {"type":"object","description":"RFC-0042: one condition on a typed field of the listed collection. `value` is read by the field's type: a number, YYYY-MM-DD, an RFC 3339 instant, true/false, an option value, or `now`. `values` only with `in`.","additionalProperties":false,"required":["field","op"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"op":{"type":"string","enum":["eq","ne","lt","lte","gt","gte","in","set","unset"]},"value":{"type":"string","minLength":1,"maxLength":100},"values":{"type":"array","minItems":1,"maxItems":20,"uniqueItems":true,"items":{"type":"string","minLength":1,"maxLength":100}}}};
+const schema170 = {"type":"object","description":"RFC-0042: order a listing by a number, date or datetime field. Entries without a value sort last.","additionalProperties":false,"required":["field","direction"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"direction":{"type":"string","enum":["asc","desc"]}}};
+const schema155 = {"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 validate87(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate103(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate87.evaluated;
+const evaluated0 = validate103.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -10021,20 +12388,20 @@ vErrors.push(err16);
}
errors++;
}
-validate87.errors = vErrors;
+validate103.errors = vErrors;
return errors === 0;
}
-validate87.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate103.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-const schema143 = {"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 schema144 = {"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 schema145 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}};
-const schema146 = {"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 schema160 = {"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 schema161 = {"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 schema162 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}};
+const schema163 = {"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 validate91(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate107(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate91.evaluated;
+const evaluated0 = validate107.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -10087,7 +12454,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: schema146.enum},message:"must be equal to one of the allowed values"};
+const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/$defs/ActorKind/enum",keyword:"enum",params:{allowedValues: schema163.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err4];
}
@@ -10178,16 +12545,16 @@ vErrors.push(err11);
}
errors++;
}
-validate91.errors = vErrors;
+validate107.errors = vErrors;
return errors === 0;
}
-validate91.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate107.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-function validate90(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate106(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate90.evaluated;
+const evaluated0 = validate106.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -10230,7 +12597,7 @@ errors++;
if(data.id !== undefined){
let data0 = data.id;
if(typeof data0 === "string"){
-if(!pattern49.test(data0)){
+if(!pattern65.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];
@@ -10255,7 +12622,7 @@ errors++;
if(data.hash !== undefined){
let data1 = data.hash;
if(typeof data1 === "string"){
-if(!pattern50.test(data1)){
+if(!pattern66.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];
@@ -10278,8 +12645,8 @@ errors++;
}
}
if(data.by !== undefined){
-if(!(validate91(data.by, {instancePath:instancePath+"/by",parentData:data,parentDataProperty:"by",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate91.errors : vErrors.concat(validate91.errors);
+if(!(validate107(data.by, {instancePath:instancePath+"/by",parentData:data,parentDataProperty:"by",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate107.errors : vErrors.concat(validate107.errors);
errors = vErrors.length;
}
}
@@ -10296,7 +12663,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: schema144.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: schema161.properties.via.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err8];
}
@@ -10342,16 +12709,16 @@ vErrors.push(err11);
}
errors++;
}
-validate90.errors = vErrors;
+validate106.errors = vErrors;
return errors === 0;
}
-validate90.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate106.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-function validate89(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate105(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate89.evaluated;
+const evaluated0 = validate105.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -10371,8 +12738,8 @@ errors++;
}
const len0 = data.length;
for(let i0=0; i0*'.","minLength":1,"maxLength":512},"where":{"type":"array","description":"RFC-0042: conditions on the collection's list fields, ANDed.","minItems":1,"maxItems":4,"items":{"$ref":"#/$defs/FieldCondition"}},"orderBy":{"$ref":"#/$defs/FieldOrder"}}},{"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."},"FieldCondition":{"type":"object","description":"RFC-0042: one condition on a typed field of the listed collection. `value` is read by the field's type: a number, YYYY-MM-DD, an RFC 3339 instant, true/false, an option value, or `now`. `values` only with `in`.","additionalProperties":false,"required":["field","op"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"op":{"type":"string","enum":["eq","ne","lt","lte","gt","gte","in","set","unset"]},"value":{"type":"string","minLength":1,"maxLength":100},"values":{"type":"array","minItems":1,"maxItems":20,"uniqueItems":true,"items":{"type":"string","minLength":1,"maxLength":100}}}},"FieldOrder":{"type":"object","description":"RFC-0042: order a listing by a number, date or datetime field. Entries without a value sort last.","additionalProperties":false,"required":["field","direction"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"direction":{"type":"string","enum":["asc","desc"]}}},"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 schema179 = {"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 schema182 = {"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 schema184 = {"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 schema193 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128};
-const schema194 = {"type":"object","description":"RFC-0042: one condition on a typed field of the listed collection. `value` is read by the field's type: a number, YYYY-MM-DD, an RFC 3339 instant, true/false, an option value, or `now`. `values` only with `in`.","additionalProperties":false,"required":["field","op"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"op":{"type":"string","enum":["eq","ne","lt","lte","gt","gte","in","set","unset"]},"value":{"type":"string","minLength":1,"maxLength":100},"values":{"type":"array","minItems":1,"maxItems":20,"uniqueItems":true,"items":{"type":"string","minLength":1,"maxLength":100}}}};
-const schema195 = {"type":"object","description":"RFC-0042: order a listing by a number, date or datetime field. Entries without a value sort last.","additionalProperties":false,"required":["field","direction"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"direction":{"type":"string","enum":["asc","desc"]}}};
-const schema180 = {"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}}};
+export const validateSection = validate123;
+const schema195 = {"$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},"where":{"type":"array","description":"RFC-0042: conditions on the collection's list fields, ANDed.","minItems":1,"maxItems":4,"items":{"$ref":"#/$defs/FieldCondition"}},"orderBy":{"$ref":"#/$defs/FieldOrder"}}},{"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."},"FieldCondition":{"type":"object","description":"RFC-0042: one condition on a typed field of the listed collection. `value` is read by the field's type: a number, YYYY-MM-DD, an RFC 3339 instant, true/false, an option value, or `now`. `values` only with `in`.","additionalProperties":false,"required":["field","op"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"op":{"type":"string","enum":["eq","ne","lt","lte","gt","gte","in","set","unset"]},"value":{"type":"string","minLength":1,"maxLength":100},"values":{"type":"array","minItems":1,"maxItems":20,"uniqueItems":true,"items":{"type":"string","minLength":1,"maxLength":100}}}},"FieldOrder":{"type":"object","description":"RFC-0042: order a listing by a number, date or datetime field. Entries without a value sort last.","additionalProperties":false,"required":["field","direction"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"direction":{"type":"string","enum":["asc","desc"]}}},"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 schema196 = {"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 schema199 = {"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 schema201 = {"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 schema210 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128};
+const schema211 = {"type":"object","description":"RFC-0042: one condition on a typed field of the listed collection. `value` is read by the field's type: a number, YYYY-MM-DD, an RFC 3339 instant, true/false, an option value, or `now`. `values` only with `in`.","additionalProperties":false,"required":["field","op"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"op":{"type":"string","enum":["eq","ne","lt","lte","gt","gte","in","set","unset"]},"value":{"type":"string","minLength":1,"maxLength":100},"values":{"type":"array","minItems":1,"maxItems":20,"uniqueItems":true,"items":{"type":"string","minLength":1,"maxLength":100}}}};
+const schema212 = {"type":"object","description":"RFC-0042: order a listing by a number, date or datetime field. Entries without a value sort last.","additionalProperties":false,"required":["field","direction"],"properties":{"field":{"type":"string","description":"A field key (site.schema.json FieldKey's pattern, repeated).","pattern":"^[a-z][A-Za-z0-9]{0,39}$"},"direction":{"type":"string","enum":["asc","desc"]}}};
+const schema197 = {"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 validate108(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate124(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate108.evaluated;
+const evaluated0 = validate124.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -14189,21 +16556,21 @@ vErrors.push(err16);
}
errors++;
}
-validate108.errors = vErrors;
+validate124.errors = vErrors;
return errors === 0;
}
-validate108.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate124.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-const schema185 = {"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 schema186 = {"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 schema190 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"};
-const schema187 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}};
-const schema188 = {"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 schema202 = {"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 schema203 = {"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 schema207 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"};
+const schema204 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}};
+const schema205 = {"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 validate112(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate128(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate112.evaluated;
+const evaluated0 = validate128.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -14256,7 +16623,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: schema188.enum},message:"must be equal to one of the allowed values"};
+const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/$defs/ActorKind/enum",keyword:"enum",params:{allowedValues: schema205.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err4];
}
@@ -14347,16 +16714,16 @@ vErrors.push(err11);
}
errors++;
}
-validate112.errors = vErrors;
+validate128.errors = vErrors;
return errors === 0;
}
-validate112.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate128.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-function validate111(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate127(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate111.evaluated;
+const evaluated0 = validate127.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -14399,7 +16766,7 @@ errors++;
if(data.id !== undefined){
let data0 = data.id;
if(typeof data0 === "string"){
-if(!pattern49.test(data0)){
+if(!pattern65.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];
@@ -14424,7 +16791,7 @@ errors++;
if(data.hash !== undefined){
let data1 = data.hash;
if(typeof data1 === "string"){
-if(!pattern50.test(data1)){
+if(!pattern66.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];
@@ -14447,8 +16814,8 @@ errors++;
}
}
if(data.by !== undefined){
-if(!(validate112(data.by, {instancePath:instancePath+"/by",parentData:data,parentDataProperty:"by",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate112.errors : vErrors.concat(validate112.errors);
+if(!(validate128(data.by, {instancePath:instancePath+"/by",parentData:data,parentDataProperty:"by",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate128.errors : vErrors.concat(validate128.errors);
errors = vErrors.length;
}
}
@@ -14465,7 +16832,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: schema186.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: schema203.properties.via.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err8];
}
@@ -14511,16 +16878,16 @@ vErrors.push(err11);
}
errors++;
}
-validate111.errors = vErrors;
+validate127.errors = vErrors;
return errors === 0;
}
-validate111.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate127.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-function validate110(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate126(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate110.evaluated;
+const evaluated0 = validate126.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -14540,8 +16907,8 @@ errors++;
}
const len0 = data.length;
for(let i0=0; i0.fields) checks each value against its definition on write; otherwise any JSON. An absent key means no value.","additionalProperties":true},"tags":{"type":"array","description":"Deprecated by RFC-0002: use `terms.tag`. Still accepted on input for one release and translated into terms; the store then writes []. Removed in schemaVersion 2.","deprecated":true,"items":{"$ref":"#/$defs/Slug"},"uniqueItems":true,"maxItems":50},"terms":{"type":"object","description":"Term assignments by taxonomy key (e.g. 'category', 'tag'). Terms are referenced by id, independent of locale. A taxonomy without terms is omitted, never an empty object.","propertyNames":{"$ref":"#/$defs/Slug"},"minProperties":1,"maxProperties":10,"additionalProperties":{"$ref":"#/$defs/TermAssignment"}},"author":{"$ref":"#/$defs/Actor"},"coverImage":{"$ref":"#/$defs/ImageRef"},"publishedAt":{"$ref":"#/$defs/Timestamp"},"availability":{"$ref":"#/$defs/Availability"},"rev":{"$ref":"#/$defs/Revision"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"translation":{"$ref":"#/$defs/Translation"},"metadata":{"$ref":"#/$defs/Metadata"},"corrections":{"type":"array","description":"Corrections, clarifications and updates appended to the entry, oldest first (RFC-0019 §2). The store stamps `at`; an item that has been live can no longer be removed, and only a person may edit its note.","items":{"$ref":"#/$defs/Correction"},"maxItems":100}},"$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"}}},"Body":{"type":"object","additionalProperties":false,"required":["format","value"],"properties":{"format":{"type":"string","enum":["markdown","html"]},"value":{"type":"string","maxLength":1000000},"profile":{"$ref":"#/$defs/BodyProfile"},"blocks":{"$ref":"#/$defs/BodyBlocks"}}},"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."},"Correction":{"type":"object","description":"One correction notice (RFC-0019 §2).","additionalProperties":false,"required":["id","kind","at","note"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["correction","clarification","update"]},"at":{"$ref":"#/$defs/Timestamp","description":"When it was written; set by the store, never by the caller."},"note":{"type":"string","description":"What changed, in markdown (inline).","minLength":1,"maxLength":2000},"snapshotId":{"$ref":"#/$defs/Id","description":"The snapshot it was first published in."}}},"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}}},"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"}},"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]},"Slug":{"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128},"TermAssignment":{"type":"object","description":"Terms of one taxonomy on one entry, in display order. The primary term is explicit and never derived from the order.","additionalProperties":false,"required":["ids"],"properties":{"ids":{"type":"array","items":{"$ref":"#/$defs/Id"},"minItems":1,"maxItems":50,"uniqueItems":true},"primary":{"$ref":"#/$defs/Id","description":"The entry's main term in this taxonomy (breadcrumbs, articleSection, later permalinks). Must be one of `ids`."}}},"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 schema202 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]};
-const schema203 = {"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 schema205 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128};
-const schema207 = {"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 schema208 = {"type":"string","description":"Editorial lifecycle of a page or content entry.","enum":["draft","review","published","archived"]};
-const schema216 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"};
-const schema228 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0};
-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 schema209 = {"type":"object","additionalProperties":false,"required":["format","value"],"properties":{"format":{"type":"string","enum":["markdown","html"]},"value":{"type":"string","maxLength":1000000},"profile":{"$ref":"#/$defs/BodyProfile"},"blocks":{"$ref":"#/$defs/BodyBlocks"}}};
-const schema210 = {"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 schema211 = {"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 schema212 = {"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 schema213 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}};
-const schema214 = {"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"]};
+export const validateContentEntry = validate135;
+const schema218 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/content-entry.schema.json","title":"ContentEntry","description":"A structured content item in a named collection (e.g. collection 'posts'). Core fields are typed; collection-specific fields live in `fields` and are validated by the collection's own schema in a later phase.","type":"object","additionalProperties":false,"required":["schemaVersion","id","siteId","collection","slug","locale","status","title","body","fields","tags","author","rev","createdAt","updatedAt"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"id":{"$ref":"#/$defs/Id"},"siteId":{"$ref":"#/$defs/Id"},"collection":{"$ref":"#/$defs/Slug"},"slug":{"$ref":"#/$defs/Slug"},"locale":{"$ref":"#/$defs/Locale"},"status":{"$ref":"#/$defs/PublishStatus"},"title":{"type":"string","minLength":1,"maxLength":300},"summary":{"type":"string","maxLength":2000},"body":{"$ref":"#/$defs/Body"},"fields":{"type":"object","description":"Collection-specific values, keyed by field. A collection with typed fields (RFC-0041, Site.collections..fields) checks each value against its definition on write; otherwise any JSON. An absent key means no value. A reference value is a translation group id or a list of them, a repeater value a list of flat objects (RFC-0045).","additionalProperties":true},"tags":{"type":"array","description":"Deprecated by RFC-0002: use `terms.tag`. Still accepted on input for one release and translated into terms; the store then writes []. Removed in schemaVersion 2.","deprecated":true,"items":{"$ref":"#/$defs/Slug"},"uniqueItems":true,"maxItems":50},"terms":{"type":"object","description":"Term assignments by taxonomy key (e.g. 'category', 'tag'). Terms are referenced by id, independent of locale. A taxonomy without terms is omitted, never an empty object.","propertyNames":{"$ref":"#/$defs/Slug"},"minProperties":1,"maxProperties":10,"additionalProperties":{"$ref":"#/$defs/TermAssignment"}},"author":{"$ref":"#/$defs/Actor"},"coverImage":{"$ref":"#/$defs/ImageRef"},"publishedAt":{"$ref":"#/$defs/Timestamp"},"availability":{"$ref":"#/$defs/Availability"},"rev":{"$ref":"#/$defs/Revision"},"createdAt":{"$ref":"#/$defs/Timestamp"},"updatedAt":{"$ref":"#/$defs/Timestamp"},"translation":{"$ref":"#/$defs/Translation"},"metadata":{"$ref":"#/$defs/Metadata"},"corrections":{"type":"array","description":"Corrections, clarifications and updates appended to the entry, oldest first (RFC-0019 §2). The store stamps `at`; an item that has been live can no longer be removed, and only a person may edit its note.","items":{"$ref":"#/$defs/Correction"},"maxItems":100}},"$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"}}},"Body":{"type":"object","additionalProperties":false,"required":["format","value"],"properties":{"format":{"type":"string","enum":["markdown","html"]},"value":{"type":"string","maxLength":1000000},"profile":{"$ref":"#/$defs/BodyProfile"},"blocks":{"$ref":"#/$defs/BodyBlocks"}}},"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."},"Correction":{"type":"object","description":"One correction notice (RFC-0019 §2).","additionalProperties":false,"required":["id","kind","at","note"],"properties":{"id":{"$ref":"#/$defs/Id"},"kind":{"type":"string","enum":["correction","clarification","update"]},"at":{"$ref":"#/$defs/Timestamp","description":"When it was written; set by the store, never by the caller."},"note":{"type":"string","description":"What changed, in markdown (inline).","minLength":1,"maxLength":2000},"snapshotId":{"$ref":"#/$defs/Id","description":"The snapshot it was first published in."}}},"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}}},"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"}},"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]},"Slug":{"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128},"TermAssignment":{"type":"object","description":"Terms of one taxonomy on one entry, in display order. The primary term is explicit and never derived from the order.","additionalProperties":false,"required":["ids"],"properties":{"ids":{"type":"array","items":{"$ref":"#/$defs/Id"},"minItems":1,"maxItems":50,"uniqueItems":true},"primary":{"$ref":"#/$defs/Id","description":"The entry's main term in this taxonomy (breadcrumbs, articleSection, later permalinks). Must be one of `ids`."}}},"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 schema219 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]};
+const schema220 = {"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 schema222 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128};
+const schema224 = {"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 schema225 = {"type":"string","description":"Editorial lifecycle of a page or content entry.","enum":["draft","review","published","archived"]};
+const schema233 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"};
+const schema245 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0};
+const schema258 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}};
+const schema226 = {"type":"object","additionalProperties":false,"required":["format","value"],"properties":{"format":{"type":"string","enum":["markdown","html"]},"value":{"type":"string","maxLength":1000000},"profile":{"$ref":"#/$defs/BodyProfile"},"blocks":{"$ref":"#/$defs/BodyBlocks"}}};
+const schema227 = {"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 schema228 = {"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 schema229 = {"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 schema230 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}};
+const schema231 = {"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 validate123(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate139(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate123.evaluated;
+const evaluated0 = validate139.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -16667,7 +19034,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: schema214.enum},message:"must be equal to one of the allowed values"};
+const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/$defs/ActorKind/enum",keyword:"enum",params:{allowedValues: schema231.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err4];
}
@@ -16758,16 +19125,16 @@ vErrors.push(err11);
}
errors++;
}
-validate123.errors = vErrors;
+validate139.errors = vErrors;
return errors === 0;
}
-validate123.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate139.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-function validate122(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate138(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate122.evaluated;
+const evaluated0 = validate138.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -16810,7 +19177,7 @@ errors++;
if(data.id !== undefined){
let data0 = data.id;
if(typeof data0 === "string"){
-if(!pattern49.test(data0)){
+if(!pattern65.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];
@@ -16835,7 +19202,7 @@ errors++;
if(data.hash !== undefined){
let data1 = data.hash;
if(typeof data1 === "string"){
-if(!pattern50.test(data1)){
+if(!pattern66.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];
@@ -16858,8 +19225,8 @@ errors++;
}
}
if(data.by !== undefined){
-if(!(validate123(data.by, {instancePath:instancePath+"/by",parentData:data,parentDataProperty:"by",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate123.errors : vErrors.concat(validate123.errors);
+if(!(validate139(data.by, {instancePath:instancePath+"/by",parentData:data,parentDataProperty:"by",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate139.errors : vErrors.concat(validate139.errors);
errors = vErrors.length;
}
}
@@ -16876,7 +19243,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: schema212.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: schema229.properties.via.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err8];
}
@@ -16922,16 +19289,16 @@ vErrors.push(err11);
}
errors++;
}
-validate122.errors = vErrors;
+validate138.errors = vErrors;
return errors === 0;
}
-validate122.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate138.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-function validate121(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate137(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate121.evaluated;
+const evaluated0 = validate137.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -16951,8 +19318,8 @@ errors++;
}
const len0 = data.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 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":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128};
-const schema268 = {"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 schema269 = {"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 schema273 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0};
-const schema274 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"};
-const schema276 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}};
-const schema271 = {"type":"object","description":"Public archive pages: / and //page/.","additionalProperties":false,"required":["enabled","basePath"],"properties":{"enabled":{"type":"boolean"},"basePath":{"$ref":"#/$defs/Path"}}};
-const schema272 = {"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};
+export const validateTaxonomy = validate162;
+const schema280 = {"$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 schema281 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]};
+const schema282 = {"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 schema284 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128};
+const schema285 = {"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 schema286 = {"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 schema290 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0};
+const schema291 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"};
+const schema293 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}};
+const schema288 = {"type":"object","description":"Public archive pages: / and //page/.","additionalProperties":false,"required":["enabled","basePath"],"properties":{"enabled":{"type":"boolean"},"basePath":{"$ref":"#/$defs/Path"}}};
+const schema289 = {"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 validate147(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate163(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate147.evaluated;
+const evaluated0 = validate163.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -20725,7 +23092,7 @@ vErrors.push(err4);
}
errors++;
}
-if(!pattern65.test(data1)){
+if(!pattern81.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];
@@ -20758,17 +23125,17 @@ vErrors.push(err7);
}
errors++;
}
-validate147.errors = vErrors;
+validate163.errors = vErrors;
return errors === 0;
}
-validate147.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate163.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-function validate146(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate162(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
/*# sourceURL="https://cloudhal.dev/schemas/v1/taxonomy.schema.json" */;
let vErrors = null;
let errors = 0;
-const evaluated0 = validate146.evaluated;
+const evaluated0 = validate162.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -20917,7 +23284,7 @@ vErrors.push(err13);
errors++;
}
for(const key0 in data){
-if(!(func1.call(schema263.properties, key0))){
+if(!(func1.call(schema280.properties, key0))){
const err14 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};
if(vErrors === null){
vErrors = [err14];
@@ -20941,7 +23308,7 @@ vErrors.push(err15);
errors++;
}
if(!(data0 === 1)){
-const err16 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema264.enum},message:"must be equal to one of the allowed values"};
+const err16 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema281.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err16];
}
@@ -21314,7 +23681,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: schema263.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: schema280.properties.allowNewTerms.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err48];
}
@@ -21473,8 +23840,8 @@ errors++;
}
}
if(data.archive !== undefined){
-if(!(validate147(data.archive, {instancePath:instancePath+"/archive",parentData:data,parentDataProperty:"archive",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate147.errors : vErrors.concat(validate147.errors);
+if(!(validate163(data.archive, {instancePath:instancePath+"/archive",parentData:data,parentDataProperty:"archive",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate163.errors : vErrors.concat(validate163.errors);
errors = vErrors.length;
}
}
@@ -21638,29 +24005,29 @@ vErrors.push(err73);
}
errors++;
}
-validate146.errors = vErrors;
+validate162.errors = vErrors;
return errors === 0;
}
-validate146.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate162.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-export const validateTerm = validate149;
-const schema277 = {"$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 schema278 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]};
-const schema279 = {"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 schema282 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128};
-const schema283 = {"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 schema290 = {"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 schema296 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0};
-const schema297 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"};
-const schema299 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}};
-const schema284 = {"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 schema286 = {"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 schema287 = {"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}}};
+export const validateTerm = validate165;
+const schema294 = {"$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 schema295 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]};
+const schema296 = {"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 schema299 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128};
+const schema300 = {"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 schema307 = {"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 schema313 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0};
+const schema314 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"};
+const schema316 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}};
+const schema301 = {"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 schema303 = {"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 schema304 = {"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 validate152(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate168(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate152.evaluated;
+const evaluated0 = validate168.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -21866,16 +24233,16 @@ vErrors.push(err16);
}
errors++;
}
-validate152.errors = vErrors;
+validate168.errors = vErrors;
return errors === 0;
}
-validate152.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate168.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-function validate151(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate167(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate151.evaluated;
+const evaluated0 = validate167.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -21983,8 +24350,8 @@ errors++;
}
}
if(data.ogImage !== undefined){
-if(!(validate152(data.ogImage, {instancePath:instancePath+"/ogImage",parentData:data,parentDataProperty:"ogImage",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate152.errors : vErrors.concat(validate152.errors);
+if(!(validate168(data.ogImage, {instancePath:instancePath+"/ogImage",parentData:data,parentDataProperty:"ogImage",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate168.errors : vErrors.concat(validate168.errors);
errors = vErrors.length;
}
}
@@ -21999,16 +24366,16 @@ vErrors.push(err8);
}
errors++;
}
-validate151.errors = vErrors;
+validate167.errors = vErrors;
return errors === 0;
}
-validate151.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate167.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-function validate150(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate166(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate150.evaluated;
+const evaluated0 = validate166.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -22274,8 +24641,8 @@ errors++;
}
}
if(data.seo !== undefined){
-if(!(validate151(data.seo, {instancePath:instancePath+"/seo",parentData:data,parentDataProperty:"seo",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate151.errors : vErrors.concat(validate151.errors);
+if(!(validate167(data.seo, {instancePath:instancePath+"/seo",parentData:data,parentDataProperty:"seo",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate167.errors : vErrors.concat(validate167.errors);
errors = vErrors.length;
}
}
@@ -22290,18 +24657,18 @@ vErrors.push(err21);
}
errors++;
}
-validate150.errors = vErrors;
+validate166.errors = vErrors;
return errors === 0;
}
-validate150.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate166.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-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"]};
+const schema310 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}};
+const schema311 = {"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 validate157(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate173(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate157.evaluated;
+const evaluated0 = validate173.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -22354,7 +24721,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: schema294.enum},message:"must be equal to one of the allowed values"};
+const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/$defs/ActorKind/enum",keyword:"enum",params:{allowedValues: schema311.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err4];
}
@@ -22445,17 +24812,17 @@ vErrors.push(err11);
}
errors++;
}
-validate157.errors = vErrors;
+validate173.errors = vErrors;
return errors === 0;
}
-validate157.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate173.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-function validate149(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate165(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
/*# sourceURL="https://cloudhal.dev/schemas/v1/term.schema.json" */;
let vErrors = null;
let errors = 0;
-const evaluated0 = validate149.evaluated;
+const evaluated0 = validate165.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -22574,7 +24941,7 @@ vErrors.push(err10);
errors++;
}
for(const key0 in data){
-if(!(func1.call(schema277.properties, key0))){
+if(!(func1.call(schema294.properties, key0))){
const err11 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};
if(vErrors === null){
vErrors = [err11];
@@ -22598,7 +24965,7 @@ vErrors.push(err12);
errors++;
}
if(!(data0 === 1)){
-const err13 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema278.enum},message:"must be equal to one of the allowed values"};
+const err13 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema295.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err13];
}
@@ -22801,7 +25168,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: schema277.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: schema294.properties.status.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err31];
}
@@ -22824,7 +25191,7 @@ vErrors.push(err32);
errors++;
}
if(!((data6 === "public") || (data6 === "internal"))){
-const err33 = {instancePath:instancePath+"/visibility",schemaPath:"#/properties/visibility/enum",keyword:"enum",params:{allowedValues: schema277.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: schema294.properties.visibility.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err33];
}
@@ -22884,8 +25251,8 @@ errors++;
}
}
for(const key2 in data7){
-if(!(validate150(data7[key2], {instancePath:instancePath+"/labels/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),parentData:data7,parentDataProperty:key2,rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate150.errors : vErrors.concat(validate150.errors);
+if(!(validate166(data7[key2], {instancePath:instancePath+"/labels/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),parentData:data7,parentDataProperty:key2,rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate166.errors : vErrors.concat(validate166.errors);
errors = vErrors.length;
}
}
@@ -22982,8 +25349,8 @@ errors++;
}
}
if(data.image !== undefined){
-if(!(validate152(data.image, {instancePath:instancePath+"/image",parentData:data,parentDataProperty:"image",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate152.errors : vErrors.concat(validate152.errors);
+if(!(validate168(data.image, {instancePath:instancePath+"/image",parentData:data,parentDataProperty:"image",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate168.errors : vErrors.concat(validate168.errors);
errors = vErrors.length;
}
}
@@ -23289,8 +25656,8 @@ errors++;
}
}
if(data.proposedBy !== undefined){
-if(!(validate157(data.proposedBy, {instancePath:instancePath+"/proposedBy",parentData:data,parentDataProperty:"proposedBy",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate157.errors : vErrors.concat(validate157.errors);
+if(!(validate173(data.proposedBy, {instancePath:instancePath+"/proposedBy",parentData:data,parentDataProperty:"proposedBy",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate173.errors : vErrors.concat(validate173.errors);
errors = vErrors.length;
}
}
@@ -23407,27 +25774,27 @@ vErrors.push(err79);
}
errors++;
}
-validate149.errors = vErrors;
+validate165.errors = vErrors;
return errors === 0;
}
-validate149.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate165.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-export const validateRedirect = validate159;
-const schema300 = {"$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 schema301 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]};
-const schema302 = {"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 schema304 = {"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 schema307 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0};
-const schema308 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"};
-const schema310 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}};
-const pattern159 = new RegExp("^/(?:[^\\s?#/\\\\]+(?:/[^\\s?#/\\\\]+)*)?$", "u");
-const pattern161 = new RegExp("^(?:/(?![/\\\\])[^\\s\\\\]*|https://[^\\s\\\\/?#]+(?:[/?#][^\\s\\\\]*)?)$", "u");
-const schema305 = {"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"}}};
+export const validateRedirect = validate175;
+const schema317 = {"$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 schema318 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]};
+const schema319 = {"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 schema321 = {"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 schema324 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0};
+const schema325 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"};
+const schema327 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}};
+const pattern175 = new RegExp("^/(?:[^\\s?#/\\\\]+(?:/[^\\s?#/\\\\]+)*)?$", "u");
+const pattern177 = new RegExp("^(?:/(?![/\\\\])[^\\s\\\\]*|https://[^\\s\\\\/?#]+(?:[/?#][^\\s\\\\]*)?)$", "u");
+const schema322 = {"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 validate160(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate176(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate160.evaluated;
+const evaluated0 = validate176.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -23480,7 +25847,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: schema305.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: schema322.properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err4];
}
@@ -23546,17 +25913,17 @@ vErrors.push(err9);
}
errors++;
}
-validate160.errors = vErrors;
+validate176.errors = vErrors;
return errors === 0;
}
-validate160.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate176.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-function validate159(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate175(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
/*# sourceURL="https://cloudhal.dev/schemas/v1/redirect.schema.json" */;
let vErrors = null;
let errors = 0;
-const evaluated0 = validate159.evaluated;
+const evaluated0 = validate175.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -23675,7 +26042,7 @@ vErrors.push(err10);
errors++;
}
for(const key0 in data){
-if(!(func1.call(schema300.properties, key0))){
+if(!(func1.call(schema317.properties, key0))){
const err11 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};
if(vErrors === null){
vErrors = [err11];
@@ -23699,7 +26066,7 @@ vErrors.push(err12);
errors++;
}
if(!(data0 === 1)){
-const err13 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema301.enum},message:"must be equal to one of the allowed values"};
+const err13 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema318.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err13];
}
@@ -23812,7 +26179,7 @@ vErrors.push(err22);
}
errors++;
}
-if(!pattern159.test(data3)){
+if(!pattern175.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];
@@ -23872,7 +26239,7 @@ vErrors.push(err27);
}
errors++;
}
-if(!pattern161.test(data5)){
+if(!pattern177.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];
@@ -23907,7 +26274,7 @@ vErrors.push(err30);
errors++;
}
if(!((data6 === 301) || (data6 === 302))){
-const err31 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema300.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: schema317.properties.status.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err31];
}
@@ -23930,7 +26297,7 @@ vErrors.push(err32);
errors++;
}
if(!((data7 === "exact") || (data7 === "prefix"))){
-const err33 = {instancePath:instancePath+"/match",schemaPath:"#/properties/match/enum",keyword:"enum",params:{allowedValues: schema300.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: schema317.properties.match.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err33];
}
@@ -23953,7 +26320,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: schema300.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: schema317.properties.reason.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err35];
}
@@ -23964,8 +26331,8 @@ errors++;
}
}
if(data.source !== undefined){
-if(!(validate160(data.source, {instancePath:instancePath+"/source",parentData:data,parentDataProperty:"source",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate160.errors : vErrors.concat(validate160.errors);
+if(!(validate176(data.source, {instancePath:instancePath+"/source",parentData:data,parentDataProperty:"source",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate176.errors : vErrors.concat(validate176.errors);
errors = vErrors.length;
}
}
@@ -24107,34 +26474,34 @@ vErrors.push(err46);
}
errors++;
}
-validate159.errors = vErrors;
+validate175.errors = vErrors;
return errors === 0;
}
-validate159.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate175.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-export const validateAsset = validate162;
-const schema311 = {"$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 schema312 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]};
-const schema313 = {"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 schema315 = {"type":"string","description":"Lower-case hex SHA-256 digest.","pattern":"^[0-9a-f]{64}$"};
-const schema317 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128};
-const schema322 = {"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 schema323 = {"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 schema335 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0};
-const schema321 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"};
-const schema338 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}};
-const pattern165 = new RegExp("^[a-z0-9][a-z0-9!#$&^_.+-]{0,126}/[a-z0-9][a-z0-9!#$&^_.+-]{0,126}$", "u");
-const pattern171 = new RegExp("^[0-9A-Za-z#$%*+,.:;=?@^_{|}~\\[\\]-]{6,100}$", "u");
-const pattern172 = new RegExp("^data:image/(?:webp|png|jpeg);base64,[A-Za-z0-9+/]+={0,2}$", "u");
-const pattern173 = new RegExp("^#[0-9a-f]{6}$", "u");
-const schema318 = {"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 schema319 = {"type":"string","description":"Absolute https URL without credentials, whitespace or backslashes.","pattern":"^https://[^\\s\\\\/?#@]+(?:[/?#][^\\s\\\\]*)?$","maxLength":2048};
-const pattern169 = new RegExp("^https://[^\\s\\\\/?#@]+(?:[/?#][^\\s\\\\]*)?$", "u");
+export const validateAsset = validate178;
+const schema328 = {"$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 schema329 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]};
+const schema330 = {"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 schema332 = {"type":"string","description":"Lower-case hex SHA-256 digest.","pattern":"^[0-9a-f]{64}$"};
+const schema334 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128};
+const schema339 = {"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 schema340 = {"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 schema352 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0};
+const schema338 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"};
+const schema355 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}};
+const pattern181 = new RegExp("^[a-z0-9][a-z0-9!#$&^_.+-]{0,126}/[a-z0-9][a-z0-9!#$&^_.+-]{0,126}$", "u");
+const pattern187 = new RegExp("^[0-9A-Za-z#$%*+,.:;=?@^_{|}~\\[\\]-]{6,100}$", "u");
+const pattern188 = new RegExp("^data:image/(?:webp|png|jpeg);base64,[A-Za-z0-9+/]+={0,2}$", "u");
+const pattern189 = new RegExp("^#[0-9a-f]{6}$", "u");
+const schema335 = {"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 schema336 = {"type":"string","description":"Absolute https URL without credentials, whitespace or backslashes.","pattern":"^https://[^\\s\\\\/?#@]+(?:[/?#][^\\s\\\\]*)?$","maxLength":2048};
+const pattern185 = new RegExp("^https://[^\\s\\\\/?#@]+(?:[/?#][^\\s\\\\]*)?$", "u");
-function validate163(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate179(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate163.evaluated;
+const evaluated0 = validate179.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -24181,7 +26548,7 @@ vErrors.push(err2);
errors++;
}
if(!(data0 === "blob")){
-const err3 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema318.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: schema335.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err3];
}
@@ -24265,7 +26632,7 @@ vErrors.push(err9);
errors++;
}
if(!(data1 === "external")){
-const err10 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema318.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: schema335.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err10];
}
@@ -24288,7 +26655,7 @@ vErrors.push(err11);
}
errors++;
}
-if(!pattern169.test(data2)){
+if(!pattern185.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];
@@ -24323,7 +26690,7 @@ vErrors.push(err14);
}
errors++;
}
-if(!pattern169.test(data3)){
+if(!pattern185.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];
@@ -24358,7 +26725,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: schema318.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: schema335.oneOf[1].properties.checksum.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err18];
}
@@ -24439,19 +26806,19 @@ vErrors = null;
}
}
}
-validate163.errors = vErrors;
+validate179.errors = vErrors;
evaluated0.props = props0;
return errors === 0;
}
-validate163.evaluated = {"dynamicProps":true,"dynamicItems":false};
+validate179.evaluated = {"dynamicProps":true,"dynamicItems":false};
-const schema326 = {"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 pattern177 = new RegExp("^[A-Za-z0-9][A-Za-z0-9.+-]*(?: (?:AND|OR|WITH) [A-Za-z0-9][A-Za-z0-9.+-]*)*$", "u");
+const schema343 = {"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 pattern193 = new RegExp("^[A-Za-z0-9][A-Za-z0-9.+-]*(?: (?:AND|OR|WITH) [A-Za-z0-9][A-Za-z0-9.+-]*)*$", "u");
-function validate165(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate181(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate165.evaluated;
+const evaluated0 = validate181.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -24508,7 +26875,7 @@ vErrors.push(err3);
errors++;
}
if(!(data0 === "spdx")){
-const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema326.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: schema343.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err4];
}
@@ -24531,7 +26898,7 @@ vErrors.push(err5);
}
errors++;
}
-if(!pattern177.test(data1)){
+if(!pattern193.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];
@@ -24617,7 +26984,7 @@ vErrors.push(err12);
errors++;
}
if(!(data2 === "url")){
-const err13 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema326.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: schema343.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err13];
}
@@ -24640,7 +27007,7 @@ vErrors.push(err14);
}
errors++;
}
-if(!pattern169.test(data3)){
+if(!pattern185.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];
@@ -24743,19 +27110,19 @@ vErrors = null;
}
}
}
-validate165.errors = vErrors;
+validate181.errors = vErrors;
evaluated0.props = props0;
return errors === 0;
}
-validate165.evaluated = {"dynamicProps":true,"dynamicItems":false};
+validate181.evaluated = {"dynamicProps":true,"dynamicItems":false};
-const schema328 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}};
-const schema329 = {"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 schema345 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}};
+const schema346 = {"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 validate167(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate183(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate167.evaluated;
+const evaluated0 = validate183.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -24808,7 +27175,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: schema329.enum},message:"must be equal to one of the allowed values"};
+const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/$defs/ActorKind/enum",keyword:"enum",params:{allowedValues: schema346.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err4];
}
@@ -24899,18 +27266,18 @@ vErrors.push(err11);
}
errors++;
}
-validate167.errors = vErrors;
+validate183.errors = vErrors;
return errors === 0;
}
-validate167.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate183.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-const schema331 = {"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 pattern180 = new RegExp("^[^/\\\\\\u0000-\\u001f\\u007f]+$", "u");
+const schema348 = {"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 pattern196 = new RegExp("^[^/\\\\\\u0000-\\u001f\\u007f]+$", "u");
-function validate169(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate185(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate169.evaluated;
+const evaluated0 = validate185.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -24957,7 +27324,7 @@ vErrors.push(err2);
errors++;
}
if(!(data0 === "upload")){
-const err3 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema331.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: schema348.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err3];
}
@@ -24990,7 +27357,7 @@ vErrors.push(err5);
}
errors++;
}
-if(!pattern180.test(data1)){
+if(!pattern196.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];
@@ -25086,7 +27453,7 @@ vErrors.push(err13);
errors++;
}
if(!(data2 === "fetch")){
-const err14 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema331.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: schema348.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err14];
}
@@ -25109,7 +27476,7 @@ vErrors.push(err15);
}
errors++;
}
-if(!pattern169.test(data3)){
+if(!pattern185.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];
@@ -25227,7 +27594,7 @@ vErrors.push(err24);
errors++;
}
if(!(data5 === "import")){
-const err25 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/2/properties/kind/enum",keyword:"enum",params:{allowedValues: schema331.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: schema348.oneOf[2].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err25];
}
@@ -25378,7 +27745,7 @@ vErrors.push(err36);
errors++;
}
if(!(data8 === "link")){
-const err37 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/3/properties/kind/enum",keyword:"enum",params:{allowedValues: schema331.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: schema348.oneOf[3].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err37];
}
@@ -25436,18 +27803,18 @@ vErrors = null;
}
}
}
-validate169.errors = vErrors;
+validate185.errors = vErrors;
evaluated0.props = props0;
return errors === 0;
}
-validate169.evaluated = {"dynamicProps":true,"dynamicItems":false};
+validate185.evaluated = {"dynamicProps":true,"dynamicItems":false};
-function validate162(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate178(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
/*# sourceURL="https://cloudhal.dev/schemas/v1/asset.schema.json" */;
let vErrors = null;
let errors = 0;
-const evaluated0 = validate162.evaluated;
+const evaluated0 = validate178.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -25606,7 +27973,7 @@ vErrors.push(err14);
errors++;
}
for(const key0 in data){
-if(!(func1.call(schema311.properties, key0))){
+if(!(func1.call(schema328.properties, key0))){
const err15 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};
if(vErrors === null){
vErrors = [err15];
@@ -25630,7 +27997,7 @@ vErrors.push(err16);
errors++;
}
if(!(data0 === 1)){
-const err17 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema312.enum},message:"must be equal to one of the allowed values"};
+const err17 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema329.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err17];
}
@@ -25743,7 +28110,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: schema311.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: schema328.properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err27];
}
@@ -25766,7 +28133,7 @@ vErrors.push(err28);
}
errors++;
}
-if(!pattern165.test(data4)){
+if(!pattern181.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];
@@ -25816,7 +28183,7 @@ errors++;
if(data.sha256 !== undefined){
let data6 = data.sha256;
if(typeof data6 === "string"){
-if(!pattern50.test(data6)){
+if(!pattern66.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];
@@ -25841,7 +28208,7 @@ errors++;
if(data.originalSha256 !== undefined){
let data7 = data.originalSha256;
if(typeof data7 === "string"){
-if(!pattern50.test(data7)){
+if(!pattern66.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];
@@ -25909,8 +28276,8 @@ errors++;
}
}
if(data.storage !== undefined){
-if(!(validate163(data.storage, {instancePath:instancePath+"/storage",parentData:data,parentDataProperty:"storage",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate163.errors : vErrors.concat(validate163.errors);
+if(!(validate179(data.storage, {instancePath:instancePath+"/storage",parentData:data,parentDataProperty:"storage",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate179.errors : vErrors.concat(validate179.errors);
errors = vErrors.length;
}
}
@@ -26034,7 +28401,7 @@ errors++;
if(data10.blurhash !== undefined){
let data14 = data10.blurhash;
if(typeof data14 === "string"){
-if(!pattern171.test(data14)){
+if(!pattern187.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];
@@ -26069,7 +28436,7 @@ vErrors.push(err53);
}
errors++;
}
-if(!pattern172.test(data15)){
+if(!pattern188.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];
@@ -26094,7 +28461,7 @@ errors++;
if(data10.color !== undefined){
let data16 = data10.color;
if(typeof data16 === "string"){
-if(!pattern173.test(data16)){
+if(!pattern189.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];
@@ -26129,7 +28496,7 @@ vErrors.push(err58);
errors++;
}
if(!(data17 === true)){
-const err59 = {instancePath:instancePath+"/image/alpha",schemaPath:"#/$defs/AssetImage/properties/alpha/enum",keyword:"enum",params:{allowedValues: schema322.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: schema339.properties.alpha.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err59];
}
@@ -26604,8 +28971,8 @@ errors++;
}
}
if(data.license !== undefined){
-if(!(validate165(data.license, {instancePath:instancePath+"/license",parentData:data,parentDataProperty:"license",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate165.errors : vErrors.concat(validate165.errors);
+if(!(validate181(data.license, {instancePath:instancePath+"/license",parentData:data,parentDataProperty:"license",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate181.errors : vErrors.concat(validate181.errors);
errors = vErrors.length;
}
}
@@ -26622,7 +28989,7 @@ vErrors.push(err99);
errors++;
}
if(!((data30 === "active") || (data30 === "retired"))){
-const err100 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema311.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: schema328.properties.status.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err100];
}
@@ -26633,14 +29000,14 @@ errors++;
}
}
if(data.uploadedBy !== undefined){
-if(!(validate167(data.uploadedBy, {instancePath:instancePath+"/uploadedBy",parentData:data,parentDataProperty:"uploadedBy",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate167.errors : vErrors.concat(validate167.errors);
+if(!(validate183(data.uploadedBy, {instancePath:instancePath+"/uploadedBy",parentData:data,parentDataProperty:"uploadedBy",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate183.errors : vErrors.concat(validate183.errors);
errors = vErrors.length;
}
}
if(data.source !== undefined){
-if(!(validate169(data.source, {instancePath:instancePath+"/source",parentData:data,parentDataProperty:"source",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate169.errors : vErrors.concat(validate169.errors);
+if(!(validate185(data.source, {instancePath:instancePath+"/source",parentData:data,parentDataProperty:"source",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate185.errors : vErrors.concat(validate185.errors);
errors = vErrors.length;
}
}
@@ -26757,30 +29124,30 @@ vErrors.push(err109);
}
errors++;
}
-validate162.errors = vErrors;
+validate178.errors = vErrors;
return errors === 0;
}
-validate162.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate178.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-export const validateMenu = validate171;
-const schema339 = {"$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 schema340 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]};
-const schema341 = {"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 schema343 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128};
-const schema363 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0};
-const schema364 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"};
-const schema366 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}};
-const schema344 = {"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 schema349 = {"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 pattern197 = 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 schema347 = {"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 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 pattern189 = new RegExp("^[^\\s\\x00-\\x1F\\x7F](?:[^\\x00-\\x1F\\x7F]*[^\\s\\x00-\\x1F\\x7F])?$", "u");
+export const validateMenu = validate187;
+const schema356 = {"$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 schema357 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]};
+const schema358 = {"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 schema360 = {"type":"string","description":"Lower-case URL segment.","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","minLength":1,"maxLength":128};
+const schema380 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0};
+const schema381 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"};
+const schema383 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}};
+const schema361 = {"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 schema366 = {"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 pattern213 = 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 schema364 = {"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 schema365 = {"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 pattern205 = new RegExp("^[^\\s\\x00-\\x1F\\x7F](?:[^\\x00-\\x1F\\x7F]*[^\\s\\x00-\\x1F\\x7F])?$", "u");
-function validate173(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate189(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate173.evaluated;
+const evaluated0 = validate189.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -26857,7 +29224,7 @@ vErrors.push(err5);
}
errors++;
}
-if(!pattern189.test(data0)){
+if(!pattern205.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];
@@ -26890,17 +29257,17 @@ vErrors.push(err8);
}
errors++;
}
-validate173.errors = vErrors;
+validate189.errors = vErrors;
return errors === 0;
}
-validate173.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate189.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-const wrapper0 = {validate: validate172};
+const wrapper0 = {validate: validate188};
-function validate172(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate188(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate172.evaluated;
+const evaluated0 = validate188.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -27012,7 +29379,7 @@ vErrors.push(err8);
errors++;
}
if(!(data1 === "page")){
-const err9 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema344.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: schema361.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err9];
}
@@ -27068,8 +29435,8 @@ errors++;
}
}
if(data.labels !== undefined){
-if(!(validate173(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate173.errors : vErrors.concat(validate173.errors);
+if(!(validate189(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate189.errors : vErrors.concat(validate189.errors);
errors = vErrors.length;
}
}
@@ -27086,7 +29453,7 @@ vErrors.push(err14);
errors++;
}
if(!(data4 === "mega")){
-const err15 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema349.enum},message:"must be equal to one of the allowed values"};
+const err15 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema366.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err15];
}
@@ -27257,7 +29624,7 @@ vErrors.push(err28);
errors++;
}
if(!(data8 === "contentEntry")){
-const err29 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema344.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: schema361.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err29];
}
@@ -27313,8 +29680,8 @@ errors++;
}
}
if(data.labels !== undefined){
-if(!(validate173(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate173.errors : vErrors.concat(validate173.errors);
+if(!(validate189(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate189.errors : vErrors.concat(validate189.errors);
errors = vErrors.length;
}
}
@@ -27331,7 +29698,7 @@ vErrors.push(err34);
errors++;
}
if(!(data11 === "mega")){
-const err35 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema349.enum},message:"must be equal to one of the allowed values"};
+const err35 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema366.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err35];
}
@@ -27509,7 +29876,7 @@ vErrors.push(err48);
errors++;
}
if(!(data15 === "term")){
-const err49 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/2/properties/kind/enum",keyword:"enum",params:{allowedValues: schema344.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: schema361.oneOf[2].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err49];
}
@@ -27565,8 +29932,8 @@ errors++;
}
}
if(data.labels !== undefined){
-if(!(validate173(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate173.errors : vErrors.concat(validate173.errors);
+if(!(validate189(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate189.errors : vErrors.concat(validate189.errors);
errors = vErrors.length;
}
}
@@ -27583,7 +29950,7 @@ vErrors.push(err54);
errors++;
}
if(!(data18 === "mega")){
-const err55 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema349.enum},message:"must be equal to one of the allowed values"};
+const err55 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema366.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err55];
}
@@ -27771,7 +30138,7 @@ vErrors.push(err69);
errors++;
}
if(!(data22 === "collection")){
-const err70 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/3/properties/kind/enum",keyword:"enum",params:{allowedValues: schema344.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: schema361.oneOf[3].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err70];
}
@@ -27827,8 +30194,8 @@ errors++;
}
}
if(data.labels !== undefined){
-if(!(validate173(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate173.errors : vErrors.concat(validate173.errors);
+if(!(validate189(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate189.errors : vErrors.concat(validate189.errors);
errors = vErrors.length;
}
}
@@ -27845,7 +30212,7 @@ vErrors.push(err75);
errors++;
}
if(!(data25 === "mega")){
-const err76 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema349.enum},message:"must be equal to one of the allowed values"};
+const err76 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema366.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err76];
}
@@ -28033,7 +30400,7 @@ vErrors.push(err90);
errors++;
}
if(!(data29 === "url")){
-const err91 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/4/properties/kind/enum",keyword:"enum",params:{allowedValues: schema344.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: schema361.oneOf[4].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err91];
}
@@ -28056,7 +30423,7 @@ vErrors.push(err92);
}
errors++;
}
-if(!pattern197.test(data30)){
+if(!pattern213.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];
@@ -28091,8 +30458,8 @@ errors++;
}
}
if(data.labels !== undefined){
-if(!(validate173(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate173.errors : vErrors.concat(validate173.errors);
+if(!(validate189(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate189.errors : vErrors.concat(validate189.errors);
errors = vErrors.length;
}
}
@@ -28109,7 +30476,7 @@ vErrors.push(err96);
errors++;
}
if(!(data33 === "mega")){
-const err97 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema349.enum},message:"must be equal to one of the allowed values"};
+const err97 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema366.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err97];
}
@@ -28297,7 +30664,7 @@ vErrors.push(err111);
errors++;
}
if(!(data37 === "group")){
-const err112 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/5/properties/kind/enum",keyword:"enum",params:{allowedValues: schema344.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: schema361.oneOf[5].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err112];
}
@@ -28308,8 +30675,8 @@ errors++;
}
}
if(data.labels !== undefined){
-if(!(validate173(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate173.errors : vErrors.concat(validate173.errors);
+if(!(validate189(data.labels, {instancePath:instancePath+"/labels",parentData:data,parentDataProperty:"labels",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate189.errors : vErrors.concat(validate189.errors);
errors = vErrors.length;
}
}
@@ -28326,7 +30693,7 @@ vErrors.push(err113);
errors++;
}
if(!(data39 === "mega")){
-const err114 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema349.enum},message:"must be equal to one of the allowed values"};
+const err114 = {instancePath:instancePath+"/display",schemaPath:"#/$defs/MenuDisplay/enum",keyword:"enum",params:{allowedValues: schema366.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err114];
}
@@ -28428,18 +30795,18 @@ vErrors = null;
}
}
}
-validate172.errors = vErrors;
+validate188.errors = vErrors;
evaluated0.props = props1;
return errors === 0;
}
-validate172.evaluated = {"dynamicProps":true,"dynamicItems":false};
+validate188.evaluated = {"dynamicProps":true,"dynamicItems":false};
-function validate171(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate187(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
/*# sourceURL="https://cloudhal.dev/schemas/v1/menu.schema.json" */;
let vErrors = null;
let errors = 0;
-const evaluated0 = validate171.evaluated;
+const evaluated0 = validate187.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -28538,7 +30905,7 @@ vErrors.push(err8);
errors++;
}
for(const key0 in data){
-if(!(func1.call(schema339.properties, key0))){
+if(!(func1.call(schema356.properties, key0))){
const err9 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};
if(vErrors === null){
vErrors = [err9];
@@ -28562,7 +30929,7 @@ vErrors.push(err10);
errors++;
}
if(!(data0 === 1)){
-const err11 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema340.enum},message:"must be equal to one of the allowed values"};
+const err11 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema357.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err11];
}
@@ -28757,8 +31124,8 @@ errors++;
}
const len0 = data5.length;
for(let i0=0; i0.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 schema465 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]};
-const schema466 = {"type":"string","description":"A release id: `rel_` and 6 to 32 letters or digits.","pattern":"^rel_[A-Za-z0-9]{6,32}$"};
-const schema467 = {"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 schema479 = {"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 schema480 = {"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 schema476 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"};
-const schema472 = {"type":"integer","description":"Monotonic per-node revision used for optimistic locking (compare-and-swap on write).","minimum":0};
-const schema508 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}};
-const pattern246 = new RegExp("^rel_[A-Za-z0-9]{6,32}$", "u");
-const pattern248 = new RegExp("^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$", "u");
-const pattern254 = new RegExp("^rl1:[0-9a-f]{64}$", "u");
-const schema468 = {"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 schema470 = {"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 schema471 = {"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}$"}}};
+export const validateRelease = validate211;
+const schema481 = {"$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 schema482 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]};
+const schema483 = {"type":"string","description":"A release id: `rel_` and 6 to 32 letters or digits.","pattern":"^rel_[A-Za-z0-9]{6,32}$"};
+const schema484 = {"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 schema496 = {"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 schema497 = {"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 schema493 = {"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 schema525 = {"type":"object","description":"Free-form string-to-string map for extensions. Keys should be namespaced (e.g. cloudhal.source).","additionalProperties":{"type":"string"}};
+const pattern262 = new RegExp("^rel_[A-Za-z0-9]{6,32}$", "u");
+const pattern264 = new RegExp("^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]+$", "u");
+const pattern270 = new RegExp("^rl1:[0-9a-f]{64}$", "u");
+const schema485 = {"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 schema487 = {"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 schema488 = {"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 validate197(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate213(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate197.evaluated;
+const evaluated0 = validate213.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -34446,7 +36813,7 @@ errors++;
if(data.hash !== undefined){
let data1 = data.hash;
if(typeof data1 === "string"){
-if(!pattern50.test(data1)){
+if(!pattern66.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];
@@ -34479,16 +36846,16 @@ vErrors.push(err7);
}
errors++;
}
-validate197.errors = vErrors;
+validate213.errors = vErrors;
return errors === 0;
}
-validate197.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate213.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-function validate196(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate212(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate196.evaluated;
+const evaluated0 = validate212.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -34565,7 +36932,7 @@ vErrors.push(err5);
errors++;
}
if(!(data0 === "set")){
-const err6 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema468.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: schema485.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err6];
}
@@ -34633,7 +37000,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: schema470.enum},message:"must be equal to one of the allowed values"};
+const err12 = {instancePath:instancePath+"/nodeKind",schemaPath:"#/$defs/ReleaseNodeKind/enum",keyword:"enum",params:{allowedValues: schema487.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err12];
}
@@ -34644,14 +37011,14 @@ errors++;
}
}
if(data.target !== undefined){
-if(!(validate197(data.target, {instancePath:instancePath+"/target",parentData:data,parentDataProperty:"target",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate197.errors : vErrors.concat(validate197.errors);
+if(!(validate213(data.target, {instancePath:instancePath+"/target",parentData:data,parentDataProperty:"target",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate213.errors : vErrors.concat(validate213.errors);
errors = vErrors.length;
}
}
if(data.base !== undefined){
-if(!(validate197(data.base, {instancePath:instancePath+"/base",parentData:data,parentDataProperty:"base",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate197.errors : vErrors.concat(validate197.errors);
+if(!(validate213(data.base, {instancePath:instancePath+"/base",parentData:data,parentDataProperty:"base",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate213.errors : vErrors.concat(validate213.errors);
errors = vErrors.length;
}
}
@@ -34739,7 +37106,7 @@ vErrors.push(err19);
errors++;
}
if(!(data5 === "remove")){
-const err20 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema468.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: schema485.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err20];
}
@@ -34807,7 +37174,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: schema470.enum},message:"must be equal to one of the allowed values"};
+const err26 = {instancePath:instancePath+"/nodeKind",schemaPath:"#/$defs/ReleaseNodeKind/enum",keyword:"enum",params:{allowedValues: schema487.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err26];
}
@@ -34818,8 +37185,8 @@ errors++;
}
}
if(data.base !== undefined){
-if(!(validate197(data.base, {instancePath:instancePath+"/base",parentData:data,parentDataProperty:"base",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate197.errors : vErrors.concat(validate197.errors);
+if(!(validate213(data.base, {instancePath:instancePath+"/base",parentData:data,parentDataProperty:"base",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate213.errors : vErrors.concat(validate213.errors);
errors = vErrors.length;
}
}
@@ -34869,21 +37236,21 @@ vErrors = null;
}
}
}
-validate196.errors = vErrors;
+validate212.errors = vErrors;
evaluated0.props = props0;
return errors === 0;
}
-validate196.evaluated = {"dynamicProps":true,"dynamicItems":false};
+validate212.evaluated = {"dynamicProps":true,"dynamicItems":false};
-const schema475 = {"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 schema477 = {"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 schema478 = {"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 pattern253 = 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 schema492 = {"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 schema494 = {"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 schema495 = {"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 pattern269 = 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 validate202(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate218(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate202.evaluated;
+const evaluated0 = validate218.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -34950,7 +37317,7 @@ vErrors.push(err4);
errors++;
}
if(!(data0 === "at")){
-const err5 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema475.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: schema492.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err5];
}
@@ -34998,7 +37365,7 @@ vErrors.push(err8);
}
errors++;
}
-if(!pattern66.test(data2)){
+if(!pattern82.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];
@@ -35023,7 +37390,7 @@ errors++;
if(data.localTime !== undefined){
let data3 = data.localTime;
if(typeof data3 === "string"){
-if(!pattern253.test(data3)){
+if(!pattern269.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];
@@ -35099,7 +37466,7 @@ vErrors.push(err16);
errors++;
}
if(!(data4 === "on-approval")){
-const err17 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema475.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: schema492.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err17];
}
@@ -35155,22 +37522,22 @@ vErrors = null;
}
}
}
-validate202.errors = vErrors;
+validate218.errors = vErrors;
evaluated0.props = props0;
return errors === 0;
}
-validate202.evaluated = {"dynamicProps":true,"dynamicItems":false};
+validate218.evaluated = {"dynamicProps":true,"dynamicItems":false};
-const schema481 = {"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 schema482 = {"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 pattern258 = new RegExp("^[A-Za-z0-9_-]{6,64}$", "u");
-const schema486 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}};
-const schema487 = {"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 schema498 = {"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 schema499 = {"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 pattern274 = new RegExp("^[A-Za-z0-9_-]{6,64}$", "u");
+const schema503 = {"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"$ref":"#/$defs/ActorKind"},"id":{"$ref":"#/$defs/Id"},"name":{"type":"string","maxLength":200}}};
+const schema504 = {"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 validate205(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate221(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate205.evaluated;
+const evaluated0 = validate221.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -35223,7 +37590,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: schema487.enum},message:"must be equal to one of the allowed values"};
+const err4 = {instancePath:instancePath+"/kind",schemaPath:"#/$defs/ActorKind/enum",keyword:"enum",params:{allowedValues: schema504.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err4];
}
@@ -35314,16 +37681,16 @@ vErrors.push(err11);
}
errors++;
}
-validate205.errors = vErrors;
+validate221.errors = vErrors;
return errors === 0;
}
-validate205.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate221.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-function validate204(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate220(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate204.evaluated;
+const evaluated0 = validate220.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -35390,7 +37757,7 @@ vErrors.push(err4);
errors++;
}
if(!(data0 === "person")){
-const err5 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema481.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: schema498.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err5];
}
@@ -35428,7 +37795,7 @@ errors++;
if(data1.id !== undefined){
let data2 = data1.id;
if(typeof data2 === "string"){
-if(!pattern232.test(data2)){
+if(!pattern248.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];
@@ -35676,7 +38043,7 @@ vErrors.push(err28);
errors++;
}
if(!(data7 === "auto")){
-const err29 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema481.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: schema498.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err29];
}
@@ -35687,15 +38054,15 @@ errors++;
}
}
if(data.by !== undefined){
-if(!(validate205(data.by, {instancePath:instancePath+"/by",parentData:data,parentDataProperty:"by",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate205.errors : vErrors.concat(validate205.errors);
+if(!(validate221(data.by, {instancePath:instancePath+"/by",parentData:data,parentDataProperty:"by",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate221.errors : vErrors.concat(validate221.errors);
errors = vErrors.length;
}
}
if(data.keyId !== undefined){
let data9 = data.keyId;
if(typeof data9 === "string"){
-if(!pattern258.test(data9)){
+if(!pattern274.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];
@@ -35813,20 +38180,20 @@ vErrors = null;
}
}
}
-validate204.errors = vErrors;
+validate220.errors = vErrors;
evaluated0.props = props0;
return errors === 0;
}
-validate204.evaluated = {"dynamicProps":true,"dynamicItems":false};
+validate220.evaluated = {"dynamicProps":true,"dynamicItems":false};
-const schema491 = {"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 pattern265 = new RegExp("^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$", "u");
-const schema498 = {"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 schema508 = {"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 pattern281 = new RegExp("^[^\\x00-\\x1F\\x7F\\u202A-\\u202E\\u2066-\\u2069]*$", "u");
+const schema515 = {"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 validate209(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate225(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate209.evaluated;
+const evaluated0 = validate225.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -35902,14 +38269,14 @@ errors++;
}
}
if(data.expected !== undefined){
-if(!(validate197(data.expected, {instancePath:instancePath+"/expected",parentData:data,parentDataProperty:"expected",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate197.errors : vErrors.concat(validate197.errors);
+if(!(validate213(data.expected, {instancePath:instancePath+"/expected",parentData:data,parentDataProperty:"expected",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate213.errors : vErrors.concat(validate213.errors);
errors = vErrors.length;
}
}
if(data.found !== undefined){
-if(!(validate197(data.found, {instancePath:instancePath+"/found",parentData:data,parentDataProperty:"found",rootData,dynamicAnchors}))){
-vErrors = vErrors === null ? validate197.errors : vErrors.concat(validate197.errors);
+if(!(validate213(data.found, {instancePath:instancePath+"/found",parentData:data,parentDataProperty:"found",rootData,dynamicAnchors}))){
+vErrors = vErrors === null ? validate213.errors : vErrors.concat(validate213.errors);
errors = vErrors.length;
}
}
@@ -35924,16 +38291,16 @@ vErrors.push(err6);
}
errors++;
}
-validate209.errors = vErrors;
+validate225.errors = vErrors;
return errors === 0;
}
-validate209.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
+validate225.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
-function validate208(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
+function validate224(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){
let vErrors = null;
let errors = 0;
-const evaluated0 = validate208.evaluated;
+const evaluated0 = validate224.evaluated;
if(evaluated0.dynamicProps){
evaluated0.props = undefined;
}
@@ -36040,7 +38407,7 @@ vErrors.push(err8);
errors++;
}
if(!(data0 === "published")){
-const err9 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema491.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: schema508.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err9];
}
@@ -36248,7 +38615,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: schema491.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: schema508.oneOf[0].properties.purge.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err27];
}
@@ -36332,7 +38699,7 @@ vErrors.push(err33);
errors++;
}
if(!(data7 === "conflict")){
-const err34 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema491.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: schema508.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"};
if(vErrors === null){
vErrors = [err34];
}
@@ -36437,8 +38804,8 @@ errors++;
}
const len0 = data10.length;
for(let i0=0; i0 (path ? `${path}/${key}` : key);
+ // Own keys and values only: `constructor`, `toString` or `__proto__` are not props of a block,
+ // and a prop named `isPrototypeOf` the value leaves out is absent, not Object.prototype's.
+ const own = (key: string) => (Object.hasOwn(v, key) ? v[key] : undefined);
for (const key of Object.keys(v))
- if (!(key in s.properties)) out.push({ path: at(key), message: "not a prop of this block" });
+ if (!Object.hasOwn(s.properties, key))
+ out.push({ path: at(key), message: "not a prop of this block" });
for (const key of s.required ?? [])
- if (v[key] === undefined) out.push({ path: at(key), message: "required" });
+ if (own(key) === undefined) out.push({ path: at(key), message: "required" });
for (const [key, child] of Object.entries(s.properties)) {
- const value = v[key];
+ const value = own(key);
if (value === undefined) continue;
if (child.type === "array") {
if (!Array.isArray(value)) {
diff --git a/packages/core-ts/src/errors.ts b/packages/core-ts/src/errors.ts
index a4cf4901..3d6b90fe 100644
--- a/packages/core-ts/src/errors.ts
+++ b/packages/core-ts/src/errors.ts
@@ -210,7 +210,14 @@ export type InvariantCode =
/** §2: a structured-data preset that does not fit the type's table or the collection's fields. */
| "structured_data_invalid"
/** §3: `expiry.field` is not a date or datetime field of the collection. */
- | "invalid_expiry";
+ | "invalid_expiry"
+ // ── RFC-0045 (collections stage 4) ──
+ /** §1: a reference or repeater definition; refused on every write path until rollout step 7. */
+ | "fields_unsupported"
+ /** §1: a reference's `collections` names a collection the site neither declares nor uses. */
+ | "reference_target_unknown"
+ /** §1: more than 100 reference values in one entry, across fields and repeater items. */
+ | "too_many_references";
export class InvariantError extends Error {
override readonly name = "InvariantError";
diff --git a/packages/core-ts/src/fields-operations/plan.ts b/packages/core-ts/src/fields-operations/plan.ts
index 0d6ce54b..1bb508ab 100644
--- a/packages/core-ts/src/fields-operations/plan.ts
+++ b/packages/core-ts/src/fields-operations/plan.ts
@@ -6,7 +6,7 @@
*/
import type { FieldDefinition, FieldMigration, Site } from "@cloudhal/core-contracts";
import { InvariantError } from "../errors.ts";
-import { RESERVED_FIELD_KEYS } from "../fields/types.ts";
+import { isReferenceFieldType, RESERVED_FIELD_KEYS } from "../fields/types.ts";
import type { Cond } from "../node-sql.ts";
import { renameSettingsReferences } from "../store/collection-settings.ts";
import { FIELD_OPERATIONS, type FieldOperation, finalKey, operationKey } from "./types.ts";
@@ -14,7 +14,10 @@ import { FIELD_OPERATIONS, type FieldOperation, finalKey, operationKey } from ".
const KEY = /^[a-z][A-Za-z0-9]{0,39}$/;
const COLLECTION = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
-function refuse(code: "unknown_field" | "field_key_duplicate" | "field_key_reserved", msg: string) {
+function refuse(
+ code: "unknown_field" | "field_key_duplicate" | "field_key_reserved" | "fields_unsupported",
+ msg: string,
+) {
return new InvariantError(code, msg, { issues: [] });
}
@@ -90,6 +93,9 @@ export function targetDefinition(def: FieldDefinition, op: FieldOperation): Fiel
/** Checks the request against the collection; throws when it can never run as written. */
export function checkOperation(site: Site, op: FieldOperation): void {
const { defs, def } = currentDefinition(site, op);
+ // RFC-0045: no operation turns a field into, or rewrites, a reference or repeater yet.
+ if (op.op === "change_field_type" && isReferenceFieldType(op.type))
+ throw refuse("fields_unsupported", `${op.type} fields are not enabled on this server yet`);
if (op.op === "rename_field") {
if (!KEY.test(op.to))
throw refuse("field_key_reserved", `${op.to} is not a field key (lower camelCase)`);
diff --git a/packages/core-ts/src/fields/definitions.ts b/packages/core-ts/src/fields/definitions.ts
index dfc12aa9..a8c1108a 100644
--- a/packages/core-ts/src/fields/definitions.ts
+++ b/packages/core-ts/src/fields/definitions.ts
@@ -5,6 +5,11 @@
* count; this module never reads entries.
*/
import type { CollectionSettings, FieldDefinition, Site } from "@cloudhal/core-contracts";
+import {
+ type ReferenceDefinitionOptions,
+ referenceDefinitionIssues,
+ subFieldCount,
+} from "../references/definitions.ts";
import {
FIELD_LIMITS,
type FieldIssue,
@@ -173,14 +178,22 @@ const OPERATION: Record = {
* guard for changes no plain edit may make (type, localization, `multiple`, the migration marker);
* removals and removed options are listed by `fieldDefinitionChanges`, since refusing them depends
* on stored values.
+ *
+ * RFC-0045: `reference` and `repeater` definitions are refused (`fields_unsupported`) unless
+ * `options.referenceTypes` says the writer's capability allows them; then their own rules run.
+ * Sub-fields count toward the per-site cap.
*/
-export function checkFieldDefinitions(site: SiteFields, before?: SiteFields): FieldIssue[] {
+export function checkFieldDefinitions(
+ site: SiteFields,
+ before?: SiteFields,
+ options: ReferenceDefinitionOptions = {},
+): FieldIssue[] {
const collections: Collections = site.collections ?? {};
- const out: FieldIssue[] = [];
+ const out: FieldIssue[] = referenceDefinitionIssues(site, options);
let total = 0;
for (const [key, settings] of Object.entries(collections)) {
if (!settings) continue;
- total += settings.fields?.length ?? 0;
+ total += (settings.fields?.length ?? 0) + subFieldCount(settings.fields);
out.push(...checkCollection(key, settings));
}
if (total > FIELD_LIMITS.fieldsPerSite)
diff --git a/packages/core-ts/src/fields/index.ts b/packages/core-ts/src/fields/index.ts
index ae0a27d4..a280d869 100644
--- a/packages/core-ts/src/fields/index.ts
+++ b/packages/core-ts/src/fields/index.ts
@@ -4,7 +4,43 @@
* express, values compiled to ADR-0030's props subset (`validateProps`), the JSON Schema agents
* receive, and the values readers see (`resolveFields`); RFC-0042's listings by field (`query.ts`)
* and derived expiry gates (`gate.ts`). Worker-safe and Temporal-free.
+ *
+ * RFC-0045 (`../references/`): the `reference` and `repeater` types' gate and definition rules,
+ * values, strict JSON Schema profile, `pickMember` and `referenceIssues`. Both types are refused on
+ * every write path (`fields_unsupported`) until rollout step 7.
*/
+
+export {
+ type FieldTypeSupport,
+ type ReferenceDefinitionOptions,
+ referenceDefinitionIssues,
+ referenceTypesSupported,
+ subFieldCount,
+ unsupportedFieldTypeIssues,
+} from "../references/definitions.ts";
+export {
+ hasReferenceFields,
+ type ReferenceIssue,
+ type ReferenceIssueReason,
+ type ReferenceSnapshot,
+ referenceIssues,
+} from "../references/issues.ts";
+export {
+ isMemberOf,
+ type MemberLike,
+ memberGroup,
+ membersByGroup,
+ type PickedMember,
+ type PickMemberOptions,
+ pickMember,
+} from "../references/members.ts";
+export {
+ type ReferenceUse,
+ referenceCountIssues,
+ referenceUses,
+ referenceValues,
+ requiredSubFieldIssues,
+} from "../references/values.ts";
export {
type Conversion,
type ConvertContext,
@@ -36,11 +72,14 @@ export {
export { type FormatFieldOptions, formatFieldValue } from "./format.ts";
export { dayStartIn, effectiveUntil, expiryInstant, fieldGateInstant, nextDay } from "./gate.ts";
export {
+ type FieldsJsonSchemaOptions,
fieldJsonSchema,
fieldsJsonSchema,
JSON_SCHEMA_KEYWORDS,
type JsonSchema,
+ type JsonType,
} from "./json-schema.ts";
+export { type StrictCost, strictFieldsSchema, strictSchemaCost } from "./json-schema-strict.ts";
export {
convertContext,
type FieldMigrationKind,
@@ -57,7 +96,9 @@ export {
DATE_PATTERN,
fieldPropSchema,
fieldsPropsSchema,
+ GROUP_ID_PATTERN,
INSTANT_PATTERN,
+ subFieldPropSchema,
} from "./props.ts";
export {
canOrderBy,
@@ -92,6 +133,8 @@ export {
translatableFieldValues,
} from "./translation.ts";
export {
+ ALL_FIELD_TYPES,
+ type EnabledFieldType,
FIELD_LIMITS,
FIELD_TYPES,
type FieldIssue,
@@ -100,9 +143,16 @@ export {
fieldDefinitions,
fieldHelp,
fieldLabel,
+ isKnownFieldType,
+ isReferenceFieldType,
optionLabel,
+ REFERENCE_FIELD_TYPES,
RESERVED_FIELD_KEYS,
+ type ReferenceFieldDefinition,
+ type ReferenceFieldType,
+ type RepeaterFieldDefinition,
type SiteFields,
+ type SubFieldType,
} from "./types.ts";
export {
isCalendarDay,
diff --git a/packages/core-ts/src/fields/json-schema-strict.ts b/packages/core-ts/src/fields/json-schema-strict.ts
new file mode 100644
index 00000000..859de793
--- /dev/null
+++ b/packages/core-ts/src/fields/json-schema-strict.ts
@@ -0,0 +1,76 @@
+/**
+ * RFC-0045 §6: the strict profile of the fields schema (`fieldsSchemaStrict`) and what it costs a
+ * provider (`strictCost`). OpenAI's strict mode needs every property required, optional ones as a
+ * `null` union, so every property that the open profile leaves optional becomes `[T, "null"]` (with
+ * `null` in its `enum`). At the top level `null` means "leave as it is"; inside a repeater item it
+ * means "unset", since a given list replaces the whole list. Properties the open profile already
+ * requires (a datetime's `at` and `timeZone`) stay as they are. Array items are never nullable.
+ */
+import type { JsonSchema } from "./json-schema.ts";
+
+/** What a strict schema spends of a provider's limits (OpenAI: 5,000 properties, 10 levels, 1,000 enum values; Anthropic: 16 unions per request). */
+export interface StrictCost {
+ /** Property entries in every object. */
+ properties: number;
+ /** Nesting levels, the root counting 1 (fields → list → item → value is 4). */
+ depth: number;
+ /** `enum` entries, `null` included. */
+ enumValues: number;
+ /** Schemas whose `type` is a list (`[T, "null"]`). */
+ unions: number;
+}
+
+const ROOT =
+ "Typed custom fields of this collection, for strict structured output (fieldsStrict): every key is required, null leaves the stored value as it is (on create: no value), and clearFields removes a value.";
+const ITEM_NULLS =
+ "Every key is required: null unsets that value in this item, since a given list replaces the whole stored list.";
+
+function nullable(schema: JsonSchema): JsonSchema {
+ const t = schema.type;
+ const type: JsonSchema["type"] = typeof t === "string" ? [t, "null"] : t;
+ const out: JsonSchema = { ...schema, type };
+ if (schema.enum) out.enum = [...schema.enum.filter((v) => v !== null), null];
+ return out;
+}
+
+function strictObject(schema: JsonSchema, isItem: boolean): JsonSchema {
+ const required = new Set(schema.required ?? []);
+ const properties: Record = {};
+ for (const [key, child] of Object.entries(schema.properties ?? {})) {
+ const strict = strictNode(child);
+ properties[key] = required.has(key) ? strict : nullable(strict);
+ }
+ const out: JsonSchema = { ...schema, properties, required: Object.keys(properties) };
+ if (isItem) out.description = out.description ? `${out.description} ${ITEM_NULLS}` : ITEM_NULLS;
+ return out;
+}
+
+function strictNode(schema: JsonSchema): JsonSchema {
+ if (schema.type === "object") return strictObject(schema, false);
+ if (schema.type === "array" && schema.items) {
+ const items = schema.items.type === "object" ? strictObject(schema.items, true) : schema.items;
+ return { ...schema, items };
+ }
+ return schema;
+}
+
+/** The strict profile of an open-profile fields schema (`fieldsJsonSchema`'s output). */
+export function strictFieldsSchema(open: JsonSchema): JsonSchema {
+ return { ...strictObject(open, false), description: ROOT };
+}
+
+/** Counts `schema`'s properties, depth, enum values and unions (`list_collections` `strictCost`). */
+export function strictSchemaCost(schema: JsonSchema): StrictCost {
+ const cost: StrictCost = { properties: 0, depth: 0, enumValues: 0, unions: 0 };
+ const walk = (s: JsonSchema, level: number): void => {
+ cost.depth = Math.max(cost.depth, level);
+ cost.enumValues += s.enum?.length ?? 0;
+ if (typeof s.type !== "string") cost.unions += 1;
+ const children = Object.values(s.properties ?? {});
+ cost.properties += children.length;
+ for (const c of children) walk(c, level + 1);
+ if (s.items) walk(s.items, level + 1);
+ };
+ walk(schema, 1);
+ return cost;
+}
diff --git a/packages/core-ts/src/fields/json-schema.ts b/packages/core-ts/src/fields/json-schema.ts
index 2682932d..4579285e 100644
--- a/packages/core-ts/src/fields/json-schema.ts
+++ b/packages/core-ts/src/fields/json-schema.ts
@@ -6,17 +6,27 @@
* `description`. Top-level properties are not required. Numeric and length limits, which
* Anthropic's strict mode drops, are written into each `description`; the checker enforces them.
* Generated from definitions only, never from text an agent wrote.
+ *
+ * RFC-0045 §6: references are group id strings (or lists of them) and repeaters arrays of closed
+ * flat objects; the root is always an object, with no `$ref` and no recursion. With
+ * `{ strict: true }` the same schema in its strict profile (`json-schema-strict.ts`).
*/
-import type { FieldDefinition } from "@cloudhal/core-contracts";
+import type { FieldDefinition, SubFieldDefinition } from "@cloudhal/core-contracts";
+import { type EmitWords, emitReference, emitRepeater } from "../references/json-schema.ts";
+import { strictFieldsSchema } from "./json-schema-strict.ts";
import { ASSET_ID_PATTERN } from "./props.ts";
-import { FIELD_LIMITS, fieldHelp, fieldLabel, optionLabel } from "./types.ts";
+import { FIELD_LIMITS, fieldHelp, fieldLabel, isKnownFieldType, optionLabel } from "./types.ts";
+
+export type JsonType = "object" | "string" | "number" | "integer" | "boolean" | "array";
/** The subset of JSON Schema this emitter writes. */
export interface JsonSchema {
- type: "object" | "string" | "number" | "integer" | "boolean" | "array";
+ /** A list only in the strict profile: `[T, "null"]`. */
+ type: JsonType | readonly [JsonType, "null"];
title?: string;
description?: string;
- enum?: string[];
+ /** `null` only in the strict profile. */
+ enum?: (string | null)[];
pattern?: string;
format?: "date" | "date-time";
items?: JsonSchema;
@@ -39,13 +49,17 @@ export const JSON_SCHEMA_KEYWORDS: readonly string[] = [
"additionalProperties",
];
-interface Words {
- locale: string;
- defaultLocale: string;
-}
+type Words = EmitWords;
type Emit = (def: FieldDefinition, words: Words) => { schema: JsonSchema; notes: string[] };
+export interface FieldsJsonSchemaOptions {
+ /** The strict profile (`fieldsSchemaStrict`): every property required, optional ones nullable. */
+ strict?: boolean | undefined;
+ /** A collection's name for reference targets ("Places"); absent: its key. */
+ collectionName?: ((key: string) => string | undefined) | undefined;
+}
+
const EMIT: Record = {
text: (def) => {
const max = (def.type === "text" && def.maxLength) || FIELD_LIMITS.textLength;
@@ -118,6 +132,33 @@ const EMIT: Record = {
}
: { schema: one, notes: [`One of: ${listed}.`] };
},
+ reference: (def, words) =>
+ def.type === "reference"
+ ? emitReference(def, words)
+ : { schema: { type: "string" }, notes: [] },
+ repeater: (def, words) =>
+ def.type === "repeater"
+ ? emitRepeater(def, words, subFieldJsonSchema)
+ : { schema: { type: "array" }, notes: [] },
+};
+
+/** A repeater sub-field's schema: its type's, with its label, help and limits (no localization note). */
+function subFieldJsonSchema(sub: SubFieldDefinition, words: Words): JsonSchema {
+ const def = sub as unknown as FieldDefinition;
+ const { schema, notes } = EMIT[sub.type](def, words);
+ const help = fieldHelp(def, words.locale, words.defaultLocale);
+ if (sub.type !== "boolean" && sub.requiredToPublish) notes.push("Needed to publish.");
+ return {
+ ...schema,
+ title: fieldLabel(def, words.locale, words.defaultLocale),
+ description: [help, ...notes].filter(Boolean).join(" "),
+ };
+}
+
+const SHARED: Partial> = {
+ reference:
+ "Set on the original only; every language version shows the targets in its own language.",
+ repeater: "Set on the original only; every language version shows this list.",
};
/** One field's schema, with its label as `title` and help plus limits as `description`. */
@@ -125,11 +166,13 @@ export function fieldJsonSchema(
def: FieldDefinition,
locale: string,
defaultLocale = locale,
+ options: Pick = {},
): JsonSchema {
- const words = { locale, defaultLocale };
+ const words: Words = { locale, defaultLocale, collectionName: options.collectionName };
const { schema, notes } = EMIT[def.type](def, words);
const help = fieldHelp(def, locale, defaultLocale);
- if (!def.localized) notes.push("Set on the original only; translations read it from there.");
+ if (!def.localized)
+ notes.push(SHARED[def.type] ?? "Set on the original only; translations read it from there.");
if (def.type !== "boolean" && def.requiredToPublish) notes.push("Needed to publish.");
return {
...schema,
@@ -140,20 +183,25 @@ export function fieldJsonSchema(
/**
* The schema of a collection's `fields` object, in `locale` (labels fall back to
- * `defaultLocale`, then any). Every property is optional; omit a key to leave it unset.
+ * `defaultLocale`, then any). Every property is optional; omit a key to leave it unset. With
+ * `strict`, its strict profile. A definition of a type this version does not know is left out.
*/
export function fieldsJsonSchema(
defs: readonly FieldDefinition[],
locale: string,
defaultLocale = locale,
+ options: FieldsJsonSchemaOptions = {},
): JsonSchema {
const properties: Record = {};
- for (const def of defs) properties[def.key] = fieldJsonSchema(def, locale, defaultLocale);
- return {
+ for (const def of defs)
+ if (isKnownFieldType(def.type))
+ properties[def.key] = fieldJsonSchema(def, locale, defaultLocale, options);
+ const open: JsonSchema = {
type: "object",
description:
"Typed custom fields of this collection. Every field is optional; leave a key out to leave it unset.",
properties,
additionalProperties: false,
};
+ return options.strict ? strictFieldsSchema(open) : open;
}
diff --git a/packages/core-ts/src/fields/props.ts b/packages/core-ts/src/fields/props.ts
index f1eaeb93..f0b71a37 100644
--- a/packages/core-ts/src/fields/props.ts
+++ b/packages/core-ts/src/fields/props.ts
@@ -4,8 +4,14 @@
* checks. What the subset cannot say (calendar days, zones, repeats, C0 controls, rich text over
* the subset's 10 000-character string ceiling) is in `values.ts`.
*/
-import type { FieldDefinition } from "@cloudhal/core-contracts";
-import type { Localized, PropSchema, PropsSchema } from "../blocks.ts";
+import type { FieldDefinition, SubFieldDefinition } from "@cloudhal/core-contracts";
+import type {
+ FlatObjectPropSchema,
+ Localized,
+ PropSchema,
+ PropsSchema,
+ ScalarPropSchema,
+} from "../blocks.ts";
import { FIELD_LIMITS } from "./types.ts";
/** `YYYY-MM-DD`; whether the day exists is checked in code. */
@@ -17,6 +23,8 @@ export const INSTANT_PATTERN =
export const TIME_ZONE_SHAPE = "^(?:UTC|[A-Z][A-Za-z_-]+(?:/[A-Za-z0-9_+-]+){1,2})$";
/** A bare asset id, `common.schema.json#/$defs/Id` with the `asset` prefix. */
export const ASSET_ID_PATTERN = "^asset_[A-Za-z0-9]{6,32}$";
+/** RFC-0045: a translation group id, an entry id (`common.schema.json#/$defs/Id`, `entry` prefix). */
+export const GROUP_ID_PATTERN = "^entry_[A-Za-z0-9]{6,32}$";
/** The subset's `Localized`: per-locale words when `en` is among them, else the first one. */
function localized(
@@ -28,7 +36,10 @@ function localized(
return entries[0]?.[1] ?? fallback;
}
-function annotations(def: FieldDefinition): { title: Localized; description?: Localized } {
+function annotations(def: Pick): {
+ title: Localized;
+ description?: Localized;
+} {
const labels: Record = {};
const help: Record = {};
for (const [locale, l] of Object.entries(def.labels)) {
@@ -81,8 +92,35 @@ const COMPILE: Record = {
? { type: "array", items: one, maxItems: values.length }
: one;
},
+ // RFC-0045: a group id, or a list of them (repeats are checked in value-checks.ts).
+ reference: (def) => {
+ const one = { type: "string" as const, pattern: GROUP_ID_PATTERN };
+ return def.type === "reference" && def.multiple
+ ? { type: "array", items: one, maxItems: def.maxItems ?? FIELD_LIMITS.listItems }
+ : one;
+ },
+ // RFC-0045: ADR-0030's array of flat objects; every sub-field compiles to a scalar.
+ repeater: (def) => {
+ const subs = def.type === "repeater" ? def.fields : [];
+ const items: FlatObjectPropSchema = {
+ type: "object",
+ properties: Object.fromEntries(subs.map((sub) => [sub.key, subFieldPropSchema(sub)])),
+ additionalProperties: false,
+ };
+ const maxItems = (def.type === "repeater" && def.maxItems) || FIELD_LIMITS.listItems;
+ return { type: "array", items, maxItems };
+ },
};
+/**
+ * A repeater sub-field's prop schema: its type's compiler (a sub-field is a field without
+ * `localized` and `migration`, and never `multiple`), with its labels as `title`/`description`.
+ */
+export function subFieldPropSchema(sub: SubFieldDefinition): ScalarPropSchema {
+ const def = sub as unknown as FieldDefinition;
+ return { ...COMPILE[sub.type](def), ...annotations(sub) } as ScalarPropSchema;
+}
+
const fieldCache = new WeakMap();
const listCache = new WeakMap();
diff --git a/packages/core-ts/src/fields/query.ts b/packages/core-ts/src/fields/query.ts
index 2ebdeb69..fbe45c41 100644
--- a/packages/core-ts/src/fields/query.ts
+++ b/packages/core-ts/src/fields/query.ts
@@ -60,6 +60,9 @@ export const FIELD_QUERY_OPS: Readonly([
"field_bounds",
"too_many_fields",
"field_change_needs_operation",
+ "fields_unsupported",
+ "reference_target_unknown",
]);
const asInvariant = (code: FieldIssueCode): InvariantCode =>
@@ -26,6 +29,7 @@ export function fieldRefusalCode(issues: readonly FieldIssue[]): InvariantCode {
const first = issues[0];
if (!first) return "invalid_fields";
if (issues.every((i) => i.code === first.code)) return asInvariant(first.code);
+ if (issues.some((i) => i.code === "fields_unsupported")) return "fields_unsupported";
if (issues.some((i) => i.code === "field_change_needs_operation"))
return "field_change_needs_operation";
const definition = issues.find((i) => DEFINITION_CODES.has(i.code));
diff --git a/packages/core-ts/src/fields/resolve.ts b/packages/core-ts/src/fields/resolve.ts
index 3fcb067c..c87c1726 100644
--- a/packages/core-ts/src/fields/resolve.ts
+++ b/packages/core-ts/src/fields/resolve.ts
@@ -8,12 +8,21 @@
* - during a field operation the `migration` marker is read through: a missing key falls back to
* `fromKey`; a value still in `fromType` that does not fit the new type is converted by RFC-0041
* §6's closed table (`conversions.ts`), and resolves to `undefined` with an issue only when the
- * table cannot convert it; a field being removed is already gone.
+ * table cannot convert it; a field being removed is already gone;
+ * - RFC-0045: a reference resolves to its group ids and a repeater to its items, each sub-value
+ * checked as a field is (loading targets is the renderer's job); a definition of a type this
+ * version does not know is skipped, so a newer writer's type never breaks an older reader.
*/
import type { ContentEntry, FieldDefinition } from "@cloudhal/core-contracts";
import { convertFieldValue } from "./conversions.ts";
import { convertContext, isBeingRemoved } from "./migration.ts";
-import { type FieldIssue, fieldDefinitions, isRecord, type SiteFields } from "./types.ts";
+import {
+ type FieldIssue,
+ fieldDefinitions,
+ isKnownFieldType,
+ isRecord,
+ type SiteFields,
+} from "./types.ts";
import { valueIssues } from "./value-checks.ts";
export interface ResolvedField {
@@ -72,7 +81,7 @@ export function resolveFields(
): ResolvedFields {
const out: ResolvedFields = {};
for (const def of fieldDefinitions(site, entry.collection)) {
- if (isBeingRemoved(def)) continue;
+ if (isBeingRemoved(def) || !isKnownFieldType(def.type)) continue;
const { value, fromOriginal } = pick(def, entry, original);
if (value === undefined) {
out[def.key] = { def, value: undefined, fromOriginal: false };
diff --git a/packages/core-ts/src/fields/types.ts b/packages/core-ts/src/fields/types.ts
index d58c5064..14a6fb03 100644
--- a/packages/core-ts/src/fields/types.ts
+++ b/packages/core-ts/src/fields/types.ts
@@ -2,10 +2,17 @@
* RFC-0041 / ADR-0043: shared names of typed custom fields — the issue shape, the limits, the
* reserved keys and small helpers. Worker-safe, no imports beyond contract types.
*/
-import type { FieldDefinition, Site } from "@cloudhal/core-contracts";
+import type { FieldDefinition, Site, SubFieldDefinition } from "@cloudhal/core-contracts";
export type FieldType = FieldDefinition["type"];
+export type SubFieldType = SubFieldDefinition["type"];
export type SelectFieldDefinition = Extract;
+export type ReferenceFieldDefinition = Extract;
+export type RepeaterFieldDefinition = Extract;
+/** RFC-0045: the two stage-4 types, refused on every write path until rollout step 7. */
+export type ReferenceFieldType = "reference" | "repeater";
+/** The types every writer accepts today (`FIELD_TYPES`). */
+export type EnabledFieldType = Exclude;
/** What field code reads of a site: its collection settings. */
export interface SiteFields {
collections?: Site["collections"] | undefined;
@@ -15,8 +22,11 @@ export interface SiteFields {
defaultLocale?: string | undefined;
}
-/** The nine closed types, in the contract's order. */
-export const FIELD_TYPES: readonly FieldType[] = [
+/**
+ * The nine types every writer accepts, in the contract's order: what Studio, MCP, the API and the
+ * CLI offer. The stage-4 types (`REFERENCE_FIELD_TYPES`) are not among them until rollout step 7.
+ */
+export const FIELD_TYPES: readonly EnabledFieldType[] = [
"text",
"richText",
"number",
@@ -28,6 +38,21 @@ export const FIELD_TYPES: readonly FieldType[] = [
"select",
];
+/** RFC-0045: `reference` and `repeater`, gated (`fields_unsupported`) until rollout step 7. */
+export const REFERENCE_FIELD_TYPES: readonly ReferenceFieldType[] = ["reference", "repeater"];
+
+/** Every type of the contract, in its order (`FIELD_TYPES` then `REFERENCE_FIELD_TYPES`). */
+export const ALL_FIELD_TYPES: readonly FieldType[] = [...FIELD_TYPES, ...REFERENCE_FIELD_TYPES];
+
+const KNOWN_TYPES: ReadonlySet = new Set(ALL_FIELD_TYPES);
+
+/** True for a type of this contract version; a newer writer's type is not (RFC-0045 §4). */
+export const isKnownFieldType = (type: string): type is FieldType => KNOWN_TYPES.has(type);
+
+/** True for `reference` and `repeater`. */
+export const isReferenceFieldType = (type: string): type is ReferenceFieldType =>
+ type === "reference" || type === "repeater";
+
/** RFC-0041 §2, §4 and Security: every limit in one place. */
export const FIELD_LIMITS = {
/** Per collection (also in the schema's `maxItems`). */
@@ -43,6 +68,12 @@ export const FIELD_LIMITS = {
richTextLength: 50_000,
urlLength: 2048,
selectOptions: 50,
+ /** RFC-0045: a multiple reference's or a repeater's `maxItems` when absent. */
+ listItems: 20,
+ /** RFC-0045: fields plus repeater sub-fields per collection. */
+ leavesPerCollection: 100,
+ /** RFC-0045: reference values per entry, across fields and repeater items. */
+ referencesPerEntry: 100,
} as const;
/**
@@ -83,6 +114,9 @@ export const RESERVED_FIELD_KEYS: ReadonlySet = new Set([
"toString",
"valueOf",
"hasOwnProperty",
+ "toLocaleString",
+ "isPrototypeOf",
+ "propertyIsEnumerable",
]);
export type FieldIssueCode =
@@ -109,7 +143,15 @@ export type FieldIssueCode =
/** Definitions: a change that could lose or reinterpret values (§6). */
| "field_change_needs_operation"
/** Reading: a value that no longer fits its definition resolves to `undefined`. */
- | "field_value_unreadable";
+ | "field_value_unreadable"
+ /** RFC-0045: a reference or repeater definition this server does not accept yet. */
+ | "fields_unsupported"
+ /** RFC-0045: a reference's `collections` names a collection the site does not have. */
+ | "reference_target_unknown"
+ /** RFC-0045: a reference's `where` does not fit its target collection's list fields. */
+ | "invalid_query"
+ /** RFC-0045: more than 100 reference values in one entry. */
+ | "too_many_references";
/** One problem, at a slash path (`fields/startsAt/at`, `collections/events/fields/2/key`). */
export interface FieldIssue {
@@ -126,6 +168,10 @@ export function fieldDefinitions(site: SiteFields, collection: string): readonly
export const isRecord = (v: unknown): v is Record =>
typeof v === "object" && v !== null && !Array.isArray(v);
+/** `record[key]` when it is the record's own property; never a prototype member (`isPrototypeOf`). */
+export const ownValue = (record: Readonly>, key: string): unknown =>
+ Object.hasOwn(record, key) ? record[key] : undefined;
+
/** Structural equality of JSON values (key order ignored). */
export function sameJson(a: unknown, b: unknown): boolean {
if (a === b) return true;
diff --git a/packages/core-ts/src/fields/value-checks.ts b/packages/core-ts/src/fields/value-checks.ts
index 9b69d016..ae9470cb 100644
--- a/packages/core-ts/src/fields/value-checks.ts
+++ b/packages/core-ts/src/fields/value-checks.ts
@@ -9,7 +9,7 @@ import { bodyDirectives } from "../body-blocks.ts";
import { checkDirective } from "../directives.ts";
import { checkBodySurfaces, createCatalog } from "../surfaces.ts";
import { fieldPropSchema } from "./props.ts";
-import { FIELD_LIMITS, type FieldIssue } from "./types.ts";
+import { FIELD_LIMITS, type FieldIssue, ownValue } from "./types.ts";
export interface ValueCheckOptions {
/** Directives and surface fences in rich text are checked against it (absent: shape only). */
@@ -125,8 +125,49 @@ const EXTRA: Partial> = {
const list = value as string[];
return new Set(list).size === list.length ? [] : [issue(path, "each option at most once")];
},
+ // RFC-0045 §1: a multiple reference names each group once.
+ reference(def, value, path) {
+ if (def.type !== "reference" || !def.multiple) return [];
+ const list = value as string[];
+ return new Set(list).size === list.length ? [] : [issue(path, "each entry at most once")];
+ },
+ // RFC-0045 §1: no empty item; each sub-value gets its type's own checks.
+ repeater(def, value, path, options) {
+ if (def.type !== "repeater" || !Array.isArray(value)) return [];
+ const out: FieldIssue[] = [];
+ (value as Record[]).forEach((item, i) => {
+ if (!def.fields.some((sub) => ownValue(item, sub.key) !== undefined))
+ out.push(issue(`${path}/${i}`, "an item needs a value"));
+ for (const sub of def.fields) {
+ const v = ownValue(item, sub.key);
+ const extra = EXTRA[sub.type];
+ if (v !== undefined && extra)
+ out.push(
+ ...extra(sub as unknown as FieldDefinition, v, `${path}/${i}/${sub.key}`, options),
+ );
+ }
+ });
+ return out;
+ },
};
+/**
+ * What the props subset checks: rich text, top-level or in a repeater item, as "" (the subset caps
+ * strings at 10 000 characters; rich text's own 50 000 limit is checked by `EXTRA`).
+ */
+function forSubset(def: FieldDefinition, value: unknown): unknown {
+ if (def.type === "richText") return typeof value === "string" ? "" : value;
+ if (def.type !== "repeater" || !Array.isArray(value)) return value;
+ const rich = def.fields.filter((s) => s.type === "richText").map((s) => s.key);
+ if (rich.length === 0) return value;
+ return value.map((item) => {
+ if (typeof item !== "object" || item === null || Array.isArray(item)) return item;
+ const copy: Record = { ...item };
+ for (const key of rich) if (typeof copy[key] === "string") copy[key] = "";
+ return copy;
+ });
+}
+
/** Problems with one value (`path` is where it sits, e.g. `fields/startsAt`). */
export function valueIssues(
def: FieldDefinition,
@@ -136,8 +177,7 @@ export function valueIssues(
): FieldIssue[] {
if (value === null)
return [issue(path, "null is never stored: leave the key out to remove the value")];
- // The subset caps strings at 10 000 characters; rich text's own limit is checked below.
- const typed = def.type === "richText" && typeof value === "string" ? "" : value;
+ const typed = forSubset(def, value);
const found = validateProps(
{ type: "object", properties: { v: fieldPropSchema(def) }, additionalProperties: false },
{ v: typed },
diff --git a/packages/core-ts/src/fields/values.ts b/packages/core-ts/src/fields/values.ts
index 17b18d8c..cbf6f221 100644
--- a/packages/core-ts/src/fields/values.ts
+++ b/packages/core-ts/src/fields/values.ts
@@ -7,6 +7,7 @@
*/
import type { FieldDefinition, PublishStatus } from "@cloudhal/core-contracts";
import { type BlockRegistry, createBlockRegistry } from "../blocks.ts";
+import { referenceCountIssues, requiredSubFieldIssues } from "../references/values.ts";
import { type FieldsEntry, type FieldsOriginal, hasFieldValue, resolveFields } from "./resolve.ts";
import { FIELD_LIMITS, type FieldIssue, jsonBytes, type SiteFields, sameJson } from "./types.ts";
import { valueIssues } from "./value-checks.ts";
@@ -82,6 +83,12 @@ export function checkFieldValues(
}
out.push(...valueIssues(def, value, path, { blocks }));
}
+ // RFC-0045: the per-entry reference cap, over every value (once one changed).
+ const touchesReferences = changedKeys(input).some((k) => {
+ const type = byKey.get(k)?.type;
+ return type === "reference" || type === "repeater";
+ });
+ if (out.length === 0 && touchesReferences) out.push(...referenceCountIssues(defs, input.values));
return out;
}
@@ -103,6 +110,8 @@ export function checkRequiredToPublish(
const out: FieldIssue[] = [];
for (const [key, field] of Object.entries(resolveFields(site, entry, original))) {
const def = field.def;
+ // RFC-0045: a repeater's required sub-fields need a value in every item.
+ out.push(...requiredSubFieldIssues(def, field.value, `fields/${key}`));
if (def.type === "boolean" || def.requiredToPublish !== true) continue;
if (!hasFieldValue(field.value))
out.push({
diff --git a/packages/core-ts/src/list-values/fingerprint.ts b/packages/core-ts/src/list-values/fingerprint.ts
index 99efcfb1..4b94d668 100644
--- a/packages/core-ts/src/list-values/fingerprint.ts
+++ b/packages/core-ts/src/list-values/fingerprint.ts
@@ -19,10 +19,19 @@ export const NO_LIST = "none";
/**
* Definition properties left out of the fingerprint: they never change a resolved value (labels,
- * help, publish rules, site search). Every other property counts, including future ones (a unit
- * test walks the schema and fails on a property that is neither here nor fingerprinted).
+ * help, publish rules, site search; RFC-0045's `where`, which narrows the picker and is never
+ * re-checked, and `itemLabel`, which only names items in Studio). Every other property counts,
+ * including future ones (a unit test walks the schema and fails on a property that is neither here
+ * nor fingerprinted). A reference's `collections`, `multiple` and `maxItems` and a repeater's
+ * `fields` count: they decide which stored values still read.
*/
-export const EXCLUDED: readonly string[] = ["labels", "requiredToPublish", "searchable"];
+export const EXCLUDED: readonly string[] = [
+ "labels",
+ "requiredToPublish",
+ "searchable",
+ "where",
+ "itemLabel",
+];
/** One listed definition as fingerprinted: options keep their values, not their labels. */
function shape(def: FieldDefinition): Record {
diff --git a/packages/core-ts/src/references/definitions.ts b/packages/core-ts/src/references/definitions.ts
new file mode 100644
index 00000000..aa7fed94
--- /dev/null
+++ b/packages/core-ts/src/references/definitions.ts
@@ -0,0 +1,244 @@
+/**
+ * RFC-0045 §1: the gate that refuses `reference` and `repeater` definitions (`fields_unsupported`)
+ * on every write path until rollout step 7, and the definition rules for both types that JSON
+ * Schema cannot express. Pure and Worker-safe; `checkFieldDefinitions` runs both.
+ */
+import type { FieldDefinition, Site, SubFieldDefinition } from "@cloudhal/core-contracts";
+import { BUILT_IN_COLLECTION } from "../collections.ts";
+import { checkFieldQuery } from "../fields/query.ts";
+import {
+ FIELD_LIMITS,
+ type FieldIssue,
+ isReferenceFieldType,
+ RESERVED_FIELD_KEYS,
+ type SiteFields,
+} from "../fields/types.ts";
+
+type Collections = NonNullable;
+
+/**
+ * The capability a writer asks before it accepts reference or repeater definitions, from a store
+ * option, `CopySiteOptions`, the site Worker's `fields: 2` or the in-process `FIELDS_SUPPORTED`
+ * (rollout steps 2 and 7). Unlike `GateSupport`, an absent capability refuses.
+ */
+export interface FieldTypeSupport {
+ /** True when the renderer serving `siteId` reads reference and repeater fields. */
+ supported(siteId: string): boolean | Promise;
+}
+
+/** True only when a capability is given and says yes for the site. */
+export async function referenceTypesSupported(
+ siteId: string,
+ support: FieldTypeSupport | undefined,
+): Promise {
+ return support !== undefined && (await support.supported(siteId));
+}
+
+export interface ReferenceDefinitionOptions {
+ /** The writer's `FieldTypeSupport` said yes for this site. Absent or false: both types are refused. */
+ referenceTypes?: boolean | undefined;
+ /** Collections inferred from entries (RFC-0040): valid reference targets though not declared. */
+ inUse?: Iterable | undefined;
+}
+
+const unsupported = (path: string, what: string): FieldIssue => ({
+ path,
+ code: "fields_unsupported",
+ message: `${what}: reference and repeater fields are not enabled on this server yet (RFC-0045)`,
+});
+
+/**
+ * The gate: one issue per definition of a stage-4 type, and per migration marker that names one
+ * (`fromType`, `sub`), so no write path stores a definition a renderer of this version cannot read.
+ */
+export function unsupportedFieldTypeIssues(site: SiteFields): FieldIssue[] {
+ const out: FieldIssue[] = [];
+ for (const [key, settings] of Object.entries(site.collections ?? {}))
+ (settings?.fields ?? []).forEach((def, i) => {
+ const at = `collections/${key}/fields/${i}`;
+ if (isReferenceFieldType(def.type))
+ out.push(unsupported(`${at}/type`, `${def.key} is a ${def.type} field`));
+ else if (
+ (def.migration?.fromType && isReferenceFieldType(def.migration.fromType)) ||
+ def.migration?.sub !== undefined
+ )
+ out.push(
+ unsupported(
+ `${at}/migration`,
+ `${def.key} converts from a ${def.migration.fromType ?? "repeater"} field`,
+ ),
+ );
+ });
+ return out;
+}
+
+/** Sub-fields of a collection's repeaters. */
+export function subFieldCount(fields: readonly FieldDefinition[] | undefined): number {
+ let n = 0;
+ for (const def of fields ?? []) if (def.type === "repeater") n += def.fields.length;
+ return n;
+}
+
+const LABELLING = new Set(["text", "select", "reference"]);
+
+function knownCollections(collections: Collections, inUse: Iterable | undefined) {
+ return new Set([...Object.keys(collections), BUILT_IN_COLLECTION, ...(inUse ?? [])]);
+}
+
+function targetIssues(
+ names: readonly string[],
+ known: ReadonlySet,
+ at: string,
+): FieldIssue[] {
+ return names.flatMap((name, i) =>
+ known.has(name)
+ ? []
+ : [
+ {
+ path: `${at}/collections/${i}`,
+ code: "reference_target_unknown",
+ message: `${name} is not a collection of this site`,
+ },
+ ],
+ );
+}
+
+function whereIssues(
+ def: Extract,
+ collections: Collections,
+ at: string,
+): FieldIssue[] {
+ const where = def.where;
+ if (!where) return [];
+ const issue = (path: string, message: string): FieldIssue => ({
+ path,
+ code: "invalid_query",
+ message,
+ });
+ const [target, ...more] = def.collections;
+ if (more.length > 0 || target === undefined)
+ return [issue(`${at}/where`, "where needs exactly one target collection")];
+ const settings = collections[target];
+ const types = new Map((settings?.fields ?? []).map((d) => [d.key, d.type]));
+ const out: FieldIssue[] = [];
+ where.forEach((c, i) => {
+ // `now` is the clock only on date and datetime fields; elsewhere it is a literal value.
+ const type = types.get(c.field);
+ const clock = type === "date" || type === "datetime";
+ if (clock && (c.value === "now" || c.values?.includes("now")))
+ out.push(
+ issue(`${at}/where/${i}`, "where cannot use now: it is checked when a target is chosen"),
+ );
+ });
+ for (const q of checkFieldQuery(settings?.fields ?? [], settings?.listFields, { where }))
+ out.push(issue(`${at}/${q.path}`, q.message));
+ return out;
+}
+
+function subFieldIssues(
+ def: Extract,
+ known: ReadonlySet,
+ at: string,
+): FieldIssue[] {
+ const out: FieldIssue[] = [];
+ const seen = new Set();
+ def.fields.forEach((sub, i) => {
+ const here = `${at}/fields/${i}`;
+ if (RESERVED_FIELD_KEYS.has(sub.key))
+ out.push({
+ path: `${here}/key`,
+ code: "field_key_reserved",
+ message: `${sub.key} is reserved`,
+ });
+ if (seen.has(sub.key))
+ out.push({
+ path: `${here}/key`,
+ code: "field_key_duplicate",
+ message: `${sub.key} is used by another sub-field of ${def.key}`,
+ });
+ seen.add(sub.key);
+ if (
+ sub.type === "number" &&
+ sub.minimum !== undefined &&
+ sub.maximum !== undefined &&
+ sub.minimum > sub.maximum
+ )
+ out.push({
+ path: `${here}/minimum`,
+ code: "field_bounds",
+ message: "minimum is above maximum",
+ });
+ if (sub.type === "select") {
+ const values = new Set();
+ sub.options.forEach((o, j) => {
+ if (values.has(o.value))
+ out.push({
+ path: `${here}/options/${j}/value`,
+ code: "field_option_duplicate",
+ message: `option ${o.value} is listed twice`,
+ });
+ values.add(o.value);
+ });
+ }
+ if (sub.type === "reference") out.push(...targetIssues(sub.collections, known, here));
+ });
+ const label = def.itemLabel;
+ if (label !== undefined && !def.fields.some((s) => s.key === label && LABELLING.has(s.type)))
+ out.push({
+ path: `${at}/itemLabel`,
+ code: "unknown_field",
+ message: `${label} is not a text, select or reference sub-field of ${def.key}`,
+ });
+ return out;
+}
+
+/** The rules of one reference or repeater definition (none for the other types). */
+function definitionIssues(
+ def: FieldDefinition,
+ collections: Collections,
+ known: ReadonlySet,
+ at: string,
+): FieldIssue[] {
+ if (def.type === "reference") {
+ const out = targetIssues(def.collections, known, at);
+ if (def.maxItems !== undefined && def.multiple !== true)
+ out.push({
+ path: `${at}/maxItems`,
+ code: "field_bounds",
+ message: "maxItems needs multiple",
+ });
+ return [...out, ...whereIssues(def, collections, at)];
+ }
+ if (def.type === "repeater") return subFieldIssues(def, known, at);
+ return [];
+}
+
+/**
+ * RFC-0045 §1's code rules: targets are collections of the site, `where` only with one target
+ * collection and on its list fields without `now`, `maxItems` only with `multiple`, sub-field keys
+ * unique and not reserved, `itemLabel` a text, select or reference sub-field, and at most 100
+ * leaves (fields plus sub-fields) per collection. Without `referenceTypes`, the gate's issues only.
+ */
+export function referenceDefinitionIssues(
+ site: SiteFields,
+ options: ReferenceDefinitionOptions = {},
+): FieldIssue[] {
+ if (!options.referenceTypes) return unsupportedFieldTypeIssues(site);
+ const collections: Collections = site.collections ?? {};
+ const known = knownCollections(collections, options.inUse);
+ const out: FieldIssue[] = [];
+ for (const [key, settings] of Object.entries(collections)) {
+ const fields = settings?.fields ?? [];
+ const leaves = fields.length + subFieldCount(fields);
+ if (leaves > FIELD_LIMITS.leavesPerCollection)
+ out.push({
+ path: `collections/${key}/fields`,
+ code: "too_many_fields",
+ message: `at most ${FIELD_LIMITS.leavesPerCollection} fields and sub-fields per collection (${leaves})`,
+ });
+ fields.forEach((def, i) => {
+ out.push(...definitionIssues(def, collections, known, `collections/${key}/fields/${i}`));
+ });
+ }
+ return out;
+}
diff --git a/packages/core-ts/src/references/issues.ts b/packages/core-ts/src/references/issues.ts
new file mode 100644
index 00000000..fbb75d21
--- /dev/null
+++ b/packages/core-ts/src/references/issues.ts
@@ -0,0 +1,98 @@
+/**
+ * RFC-0045 §2 "Publish warns, never fails": `referenceIssues` lists the references of published
+ * entries that will not resolve at an instant. Studio's publish dialog, the MCP `publish` tool's
+ * approval text, `cloudhal snapshot --publish` and `cloudhal import --dry-run` show it; nothing
+ * refuses on it. Pure: the caller passes a snapshot's site and entries, and `now`.
+ */
+import type { ContentEntry, Site } from "@cloudhal/core-contracts";
+import { derivedGates, entryVisibility, originalsOf } from "../fields/entry-gates.ts";
+import { resolveFields } from "../fields/resolve.ts";
+import { isReferenceFieldType } from "../fields/types.ts";
+import { membersByGroup, pickMember } from "./members.ts";
+import { type ReferenceUse, referenceUses } from "./values.ts";
+
+/**
+ * Why a reference does not resolve:
+ * - `missing`: the group has no member in this site in a collection the field allows;
+ * - `hidden`: it has, but none the published site shows at `now` (draft, archived, gated);
+ * - `locale`: shown members exist, but none in the referrer's locale, and the site does not fall
+ * back to its default locale (or has no member there either).
+ */
+export type ReferenceIssueReason = "missing" | "hidden" | "locale";
+
+export interface ReferenceIssue {
+ /** The published entry holding (or inheriting) the reference. */
+ entryId: string;
+ collection: string;
+ locale: string;
+ /** Where the value sits: `fields/venue`, `fields/speakers/1/person`. */
+ path: string;
+ groupId: string;
+ reason: ReferenceIssueReason;
+}
+
+/** What `referenceIssues` reads of a snapshot: its site and every entry in it. */
+export interface ReferenceSnapshot {
+ site: Pick;
+ entries: readonly ContentEntry[];
+}
+
+/** True when some collection of the site has a reference or repeater field. */
+export function hasReferenceFields(site: Pick): boolean {
+ return Object.values(site.collections ?? {}).some((c) =>
+ (c?.fields ?? []).some((d) => isReferenceFieldType(d.type)),
+ );
+}
+
+type Groups = ReadonlyMap;
+
+/** Why `use` does not resolve; undefined when it does. */
+function unresolved(
+ use: ReferenceUse,
+ locale: string,
+ view: { all: Groups; visible: Groups; site: ReferenceSnapshot["site"] },
+): ReferenceIssueReason | undefined {
+ const shown = view.visible.get(use.groupId) ?? [];
+ const pick = {
+ locale,
+ defaultLocale: view.site.defaultLocale,
+ fallback: view.site.localization?.fallback,
+ collections: use.collections,
+ };
+ if (pickMember(use.groupId, shown, pick)) return undefined;
+ const allowed = (m: ContentEntry) => use.collections.includes(m.collection);
+ if (!(view.all.get(use.groupId) ?? []).some(allowed)) return "missing";
+ return shown.some(allowed) ? "locale" : "hidden";
+}
+
+/**
+ * The references of entries shown at `now` (published, gates open, not hidden by a derived gate;
+ * `unlist`ed entries count) that resolve to nothing, by the locale rule (`pickMember`) over the
+ * entries shown at `now`. A translation's inherited values are checked in its own locale.
+ */
+export function referenceIssues(snapshot: ReferenceSnapshot, now: number): ReferenceIssue[] {
+ const { site, entries } = snapshot;
+ if (!hasReferenceFields(site)) return [];
+ const derived = derivedGates(site, entries);
+ const shown = entries.filter((e) => entryVisibility(e, derived.get(e.id), now) !== "hidden");
+ const view = { all: membersByGroup(entries), visible: membersByGroup(shown), site };
+ const originalOf = originalsOf(entries);
+ const out: ReferenceIssue[] = [];
+ for (const entry of shown)
+ for (const [key, field] of Object.entries(resolveFields(site, entry, originalOf(entry)))) {
+ if (!isReferenceFieldType(field.def.type)) continue;
+ for (const use of referenceUses(field.def, field.value, `fields/${key}`)) {
+ const reason = unresolved(use, entry.locale, view);
+ if (reason)
+ out.push({
+ entryId: entry.id,
+ collection: entry.collection,
+ locale: entry.locale,
+ path: use.path,
+ groupId: use.groupId,
+ reason,
+ });
+ }
+ }
+ return out;
+}
diff --git a/packages/core-ts/src/references/json-schema.ts b/packages/core-ts/src/references/json-schema.ts
new file mode 100644
index 00000000..e16c1fac
--- /dev/null
+++ b/packages/core-ts/src/references/json-schema.ts
@@ -0,0 +1,71 @@
+/**
+ * RFC-0045 §6: the open-profile JSON Schema of reference and repeater fields, for
+ * `fieldsJsonSchema`. A reference is a group id string (or a list of them); a repeater an array of
+ * closed flat objects. No `$ref`, no recursion, depth at most 4; limits go into `description`.
+ */
+import type { FieldDefinition, SubFieldDefinition } from "@cloudhal/core-contracts";
+import type { JsonSchema } from "../fields/json-schema.ts";
+import { GROUP_ID_PATTERN } from "../fields/props.ts";
+import { FIELD_LIMITS } from "../fields/types.ts";
+
+export interface EmitWords {
+ locale: string;
+ defaultLocale: string;
+ /** A collection's name for agents, e.g. "Places"; absent: the key alone. */
+ collectionName?: ((key: string) => string | undefined) | undefined;
+}
+
+export interface Emitted {
+ schema: JsonSchema;
+ notes: string[];
+}
+
+/** One sub-field's schema with its title and description (the caller emits the scalar types). */
+export type EmitSub = (sub: SubFieldDefinition, words: EmitWords) => JsonSchema;
+
+/** "Places (places) or People (people)". */
+function targets(collections: readonly string[], words: EmitWords): string {
+ const named = collections.map((key) => {
+ const name = words.collectionName?.(key);
+ return name && name !== key ? `${name} (${key})` : key;
+ });
+ return named.length > 1
+ ? `${named.slice(0, -1).join(", ")} or ${named[named.length - 1]}`
+ : (named[0] ?? "");
+}
+
+const ONE = (): JsonSchema => ({ type: "string", pattern: GROUP_ID_PATTERN });
+
+/** A reference: a group id, or with `multiple` a list of them. */
+export function emitReference(
+ def: Pick<
+ Extract,
+ "collections" | "multiple" | "maxItems"
+ >,
+ words: EmitWords,
+): Emitted {
+ const where = `An entry in ${targets(def.collections, words)} of this site, by the groupId list_entries returns (an entry's own id is replaced by its group's).`;
+ if (!def.multiple) return { schema: ONE(), notes: [where] };
+ const max = def.maxItems ?? FIELD_LIMITS.listItems;
+ return {
+ schema: { type: "array", items: ONE() },
+ notes: [
+ `An ordered list of at most ${max} entries in ${targets(def.collections, words)} of this site, by groupId, each at most once.`,
+ ],
+ };
+}
+
+/** A repeater: an ordered list of closed flat objects, one property per sub-field. */
+export function emitRepeater(
+ def: Extract,
+ words: EmitWords,
+ emitSub: EmitSub,
+): Emitted {
+ const properties: Record = {};
+ for (const sub of def.fields) properties[sub.key] = emitSub(sub, words);
+ const max = def.maxItems ?? FIELD_LIMITS.listItems;
+ return {
+ schema: { type: "array", items: { type: "object", properties, additionalProperties: false } },
+ notes: [`An ordered list of at most ${max} items; an item holds at least one value.`],
+ };
+}
diff --git a/packages/core-ts/src/references/members.ts b/packages/core-ts/src/references/members.ts
new file mode 100644
index 00000000..ff1563bf
--- /dev/null
+++ b/packages/core-ts/src/references/members.ts
@@ -0,0 +1,72 @@
+/**
+ * RFC-0045 §2 "Membership" and §3 "The locale rule": which node of this site a reference to a
+ * translation group shows. One function, `pickMember`, for the renderer, Studio's preview and
+ * `referenceIssues`. Pure; callers pass the members their view shows (gates applied).
+ */
+import type { ContentEntry, Localization } from "@cloudhal/core-contracts";
+
+/** What membership and the locale rule read of a node. */
+export type MemberLike = Pick & {
+ translation?: Pick, "group"> | undefined;
+};
+
+/** A node's translation group: `COALESCE(translation.group, id)`, the one rule every probe uses. */
+export const memberGroup = (node: Pick): string =>
+ node.translation?.group ?? node.id;
+
+/** True when `node` is a member of group `groupId`. */
+export const isMemberOf = (
+ node: Pick,
+ groupId: string,
+): boolean => memberGroup(node) === groupId;
+
+export interface PickMemberOptions {
+ /** The page's locale. */
+ locale: string;
+ /** The site's default locale. */
+ defaultLocale: string;
+ /** `Site.localization.fallback`; absent reads as `none`. */
+ fallback?: Localization["fallback"] | undefined;
+ /** The field's `collections`: only members in one of them count. */
+ collections: readonly string[];
+}
+
+export interface PickedMember {
+ member: T;
+ /** The member is the default locale's, shown under `default-with-notice`; themes set `lang`. */
+ fromFallbackLocale: boolean;
+}
+
+/**
+ * The member of `groupId` a reference shows: among `members` of the group in an allowed
+ * collection, the one in the page's locale; else, under `default-with-notice`, the default
+ * locale's (flagged); else none. The first such member wins (a group has one node per locale).
+ */
+export function pickMember(
+ groupId: string,
+ members: Iterable,
+ options: PickMemberOptions,
+): PickedMember | undefined {
+ const allowed = new Set(options.collections);
+ let fallback: T | undefined;
+ for (const m of members) {
+ if (!isMemberOf(m, groupId) || !allowed.has(m.collection)) continue;
+ if (m.locale === options.locale) return { member: m, fromFallbackLocale: false };
+ if (fallback === undefined && m.locale === options.defaultLocale) fallback = m;
+ }
+ return fallback !== undefined && options.fallback === "default-with-notice"
+ ? { member: fallback, fromFallbackLocale: true }
+ : undefined;
+}
+
+/** Members by group, for repeated `pickMember` calls over one view. */
+export function membersByGroup(members: Iterable): Map {
+ const out = new Map();
+ for (const m of members) {
+ const g = memberGroup(m);
+ const list = out.get(g);
+ if (list) list.push(m);
+ else out.set(g, [m]);
+ }
+ return out;
+}
diff --git a/packages/core-ts/src/references/values.ts b/packages/core-ts/src/references/values.ts
new file mode 100644
index 00000000..f87a1af1
--- /dev/null
+++ b/packages/core-ts/src/references/values.ts
@@ -0,0 +1,87 @@
+/**
+ * RFC-0045 §1: the entry-level value rules of references and repeaters: at most 100 reference
+ * values per entry (across fields and repeater items), and a repeater's `requiredToPublish`
+ * sub-fields in every item. Pure; the store's target guard (rollout step 2) checks the targets.
+ */
+import type { FieldDefinition } from "@cloudhal/core-contracts";
+import { FIELD_LIMITS, type FieldIssue, isRecord, ownValue } from "../fields/types.ts";
+
+/** "Has a value" for one sub-value: not absent, not an empty string. */
+const hasSubValue = (v: unknown): boolean =>
+ v !== undefined && v !== null && !(typeof v === "string" && v.trim() === "");
+
+/** One reference value where it sits: `fields/venue`, `fields/speakers/1/person`. */
+export interface ReferenceUse {
+ path: string;
+ groupId: string;
+ /** The collections the field (or sub-field) allows. */
+ collections: readonly string[];
+}
+
+/** The reference values a reference or repeater value holds, in order (other types: none). */
+export function referenceUses(def: FieldDefinition, value: unknown, path: string): ReferenceUse[] {
+ if (def.type === "reference") {
+ const { collections } = def;
+ if (typeof value === "string") return [{ path, groupId: value, collections }];
+ if (!Array.isArray(value)) return [];
+ return value.flatMap((v, i) =>
+ typeof v === "string" ? [{ path: `${path}/${i}`, groupId: v, collections }] : [],
+ );
+ }
+ if (def.type !== "repeater" || !Array.isArray(value)) return [];
+ const subs = def.fields.filter((s) => s.type === "reference");
+ const out: ReferenceUse[] = [];
+ value.forEach((item, i) => {
+ if (!isRecord(item)) return;
+ for (const sub of subs) {
+ const v = ownValue(item, sub.key);
+ if (typeof v === "string" && sub.type === "reference")
+ out.push({ path: `${path}/${i}/${sub.key}`, groupId: v, collections: sub.collections });
+ }
+ });
+ return out;
+}
+
+/** The group ids a reference or repeater value names, in order (other types: none). */
+export function referenceValues(def: FieldDefinition, value: unknown): string[] {
+ return referenceUses(def, value, "").map((u) => u.groupId);
+}
+
+/** The 100-references cap over an entry's stored values (unknown keys count nothing). */
+export function referenceCountIssues(
+ defs: readonly FieldDefinition[],
+ values: Readonly>,
+): FieldIssue[] {
+ let n = 0;
+ for (const def of defs)
+ if (Object.hasOwn(values, def.key)) n += referenceValues(def, values[def.key]).length;
+ if (n <= FIELD_LIMITS.referencesPerEntry) return [];
+ return [
+ {
+ path: "fields",
+ code: "too_many_references",
+ message: `${n} references; an entry holds at most ${FIELD_LIMITS.referencesPerEntry} across its fields and repeater items`,
+ },
+ ];
+}
+
+/** A resolved repeater's `requiredToPublish` sub-fields missing in an item, at their item paths. */
+export function requiredSubFieldIssues(
+ def: FieldDefinition,
+ value: unknown,
+ path: string,
+): FieldIssue[] {
+ if (def.type !== "repeater" || !Array.isArray(value)) return [];
+ const required = def.fields.filter((s) => s.type !== "boolean" && s.requiredToPublish === true);
+ const out: FieldIssue[] = [];
+ value.forEach((item, i) => {
+ for (const sub of required)
+ if (!isRecord(item) || !hasSubValue(ownValue(item, sub.key)))
+ out.push({
+ path: `${path}/${i}/${sub.key}`,
+ code: "required_field",
+ message: `${def.key} item ${i + 1} needs ${sub.key} before the entry goes to review or is published`,
+ });
+ });
+ return out;
+}
diff --git a/packages/core-ts/src/structured-data/values.ts b/packages/core-ts/src/structured-data/values.ts
index abb06e02..15b63210 100644
--- a/packages/core-ts/src/structured-data/values.ts
+++ b/packages/core-ts/src/structured-data/values.ts
@@ -197,6 +197,9 @@ const FIELD: Record = {
select(value, _def, { spec }) {
return fromVocabulary(spec, value);
},
+ // RFC-0045: no value until rollout step 8 (`ReferenceValueSource`); a repeater never maps.
+ reference: () => undefined,
+ repeater: () => undefined,
};
function imageUrl(image: { assetId?: string; src?: string }, ctx: StructuredDataContext) {
diff --git a/packages/core-ts/src/sync-checks.ts b/packages/core-ts/src/sync-checks.ts
index a2e588ed..a8836361 100644
--- a/packages/core-ts/src/sync-checks.ts
+++ b/packages/core-ts/src/sync-checks.ts
@@ -2,8 +2,8 @@
* The checks a site bundle passes before anything of it is written (`planImport`): the format, the
* site node, rows of this site only, every row a valid contract document whose columns agree with
* its body (precompiled validators only, AGENTS.md rule 6), rows the node batch's keys accept,
- * gates, the time zone, the media and takedowns the bundle lists, and every node body a snapshot
- * references. Worker-safe; not exported from the package (`planImport` is the public surface).
+ * gates, field types (RFC-0045), the time zone, the media and takedowns the bundle lists, and every
+ * node body a snapshot references. Worker-safe; not exported from the package (`planImport` is the public surface).
*/
import { type Asset, assertValid, type NodeRef } from "@cloudhal/core-contracts";
import {
@@ -14,9 +14,16 @@ import {
isTimeZone,
} from "./availability.ts";
import { InvariantError } from "./errors.ts";
+import { refuseFieldIssues } from "./fields/refusal.ts";
+import type { FieldIssue } from "./fields/types.ts";
import { nodeHash } from "./hash.ts";
import { validateMenuTree } from "./menu.ts";
import { isNodeKind, NODE_SPEC, type NodeKind, type NodeTypes } from "./nodes.ts";
+import {
+ type FieldTypeSupport,
+ referenceTypesSupported,
+ unsupportedFieldTypeIssues,
+} from "./references/definitions.ts";
import {
type BundleMedia,
type NodeRow,
@@ -247,14 +254,73 @@ export async function assertIdsFree(to: StoragePair, bundle: SiteBundle): Promis
}
}
+/** At most this many issues travel in a field-type refusal's `details.issues` (with `total`). */
+const MAX_TYPE_ISSUES = 50;
+
+type SiteBody = Parameters[0];
+
+/** The gate's refusal over `issues`, unless `support` allows the types for the site. */
+async function refuseUnsupportedTypes(
+ siteId: string,
+ issues: readonly FieldIssue[],
+ support: FieldTypeSupport | undefined,
+): Promise {
+ if (issues.length === 0 || (await referenceTypesSupported(siteId, support))) return;
+ refuseFieldIssues(issues.slice(0, MAX_TYPE_ISSUES), { siteId, total: issues.length });
+}
+
+/**
+ * RFC-0045 §1: a bundle whose working site defines a reference or repeater field (or a marker
+ * converting one) is refused (`fields_unsupported`) unless `support` says the site's renderer
+ * reads them. Without a capability the types are refused, unlike `assertGates`. One check for
+ * `importSite`, so for pull, push, `cloudhal import --replace` and `--revert`, and snapshot
+ * restores. The site bodies of the bundle's snapshots are checked by
+ * `assertSnapshotFieldTypes`, once `bodiesToWrite` has verified them.
+ */
+export async function assertFieldTypes(
+ siteId: string,
+ bundle: SiteBundle,
+ support: FieldTypeSupport | undefined,
+): Promise {
+ const site = bundle.nodes.find((n) => n.kind === "site" && n.id === siteId);
+ if (!site) return;
+ const issues = unsupportedFieldTypeIssues(JSON.parse(site.body) as SiteBody);
+ await refuseUnsupportedTypes(siteId, issues, support);
+}
+
+/**
+ * RFC-0045 §1 for snapshots: the site bodies an import writes (hash-checked and validated as the
+ * site of every snapshot ref to them, `bodiesToWrite`), since a publish or rollback can make any
+ * of them live. Bodies the target already holds are its own and are not written, so not checked.
+ * Entry bodies need no check: a value means nothing without a definition.
+ */
+export async function assertSnapshotFieldTypes(
+ siteId: string,
+ written: readonly { hash: string; body: unknown }[],
+ siteHashes: ReadonlySet,
+ support: FieldTypeSupport | undefined,
+): Promise {
+ const issues = written
+ .filter((b) => siteHashes.has(b.hash))
+ .flatMap((b) =>
+ unsupportedFieldTypeIssues(b.body as SiteBody).map((i) => ({
+ ...i,
+ path: `blobs/${b.hash}/${i.path}`,
+ })),
+ );
+ await refuseUnsupportedTypes(siteId, issues, support);
+}
+
/**
* The checks that need no target, in `importSite`'s order: the format, the site node, rows of this
- * site only, valid rows (`assertValidRows`), distinct rows (`assertDistinctRows`) and gates.
- * Returns the validated site row.
+ * site only, valid rows (`assertValidRows`), distinct rows (`assertDistinctRows`), gates and
+ * field types (`assertFieldTypes`; no `fieldTypes` refuses the RFC-0045 types). Returns the
+ * validated site row.
*/
export async function checkBundle(
bundle: SiteBundle,
gates: GateSupport | undefined,
+ fieldTypes?: FieldTypeSupport | undefined,
): Promise {
if (bundle.format !== SITE_BUNDLE_FORMAT)
throw new Error(`unsupported bundle format ${bundle.format}`);
@@ -269,6 +335,7 @@ export async function checkBundle(
await assertValidRows(siteId, bundle);
assertDistinctRows(bundle);
await assertGates(siteId, bundle, gates);
+ await assertFieldTypes(siteId, bundle, fieldTypes);
return siteRow;
}
diff --git a/packages/core-ts/src/sync-plan.ts b/packages/core-ts/src/sync-plan.ts
index e6b3f680..6196dd1a 100644
--- a/packages/core-ts/src/sync-plan.ts
+++ b/packages/core-ts/src/sync-plan.ts
@@ -27,6 +27,7 @@ import {
import {
assertIdsFree,
assertNodeBody,
+ assertSnapshotFieldTypes,
assertTimeZone,
checkBundle,
listedMedia,
@@ -349,6 +350,12 @@ export async function prepareImport(
await assertIdsFree(to, bundle);
const refs = bundle.snapshots.map((s) => ({ id: s.id, refs: snapshotRefs(s) }));
const blobs = await bodiesToWrite(to, bundle, refs);
+ // RFC-0045 §1: the site bodies of snapshots too, once verified (rollout step 2 passes the
+ // capability, CopySiteOptions.fieldTypes, here and to checkBundle).
+ const siteHashes = new Set(
+ refs.flatMap((s) => s.refs.filter((r) => r.kind === "site").map((r) => r.hash)),
+ );
+ await assertSnapshotFieldTypes(siteId, blobs, siteHashes, undefined);
await assertSnapshotsBacked(to, bundle, refs);
const snapshots = await snapshotsToAdd(to, bundle);
const project = await projectToAdd(to, bundle);
diff --git a/packages/core-ts/src/testing-reference-gate.ts b/packages/core-ts/src/testing-reference-gate.ts
new file mode 100644
index 00000000..76370d47
--- /dev/null
+++ b/packages/core-ts/src/testing-reference-gate.ts
@@ -0,0 +1,214 @@
+/**
+ * RFC-0045 rollout step 1: the gate. Until rollout step 7 no write path stores a `reference` or
+ * `repeater` definition (`fields_unsupported`): site creates and updates (Studio, MCP and the API
+ * all write through them), field operations, and every bundle `importSite` takes (import, pull,
+ * push, `cloudhal import --replace` and `--revert`, snapshot restores and edition copies all go
+ * through its `checkBundle`). Called by `describeSiteGraphStore`, so it runs on every backend.
+ */
+import type { Actor, FieldDefinition, NodeRef, Site } from "@cloudhal/core-contracts";
+import { describe, expect, it } from "vitest";
+import { InvariantError } from "./errors.ts";
+import { runFieldOperation } from "./fields-operations/index.ts";
+import { canonicalJson, nodeHash } from "./hash.ts";
+import type { SiteGraphStore } from "./store.ts";
+import { exportSite, importSite, type SiteBundle } from "./sync.ts";
+import type { SuiteContext } from "./testing-taxonomy.ts";
+
+const person: Actor = { kind: "human", id: "user_01HZX8K3Q9CD", name: "Halil" };
+const L = (label: string) => ({ en: { label } });
+
+const TEXT: FieldDefinition = { key: "room", type: "text", labels: L("Room"), localized: true };
+const VENUE: FieldDefinition = {
+ key: "venue",
+ type: "reference",
+ labels: L("Venue"),
+ localized: false,
+ collections: ["places"],
+};
+const SPEAKERS: FieldDefinition = {
+ key: "speakers",
+ type: "repeater",
+ labels: L("Speakers"),
+ localized: false,
+ fields: [{ key: "name", type: "text", labels: L("Name") }],
+};
+
+async function code(p: Promise): Promise {
+ try {
+ await p;
+ } catch (e) {
+ if (e instanceof InvariantError) return e.code;
+ throw e;
+ }
+ throw new Error("expected a rejection");
+}
+
+const events = (fields: FieldDefinition[]): NonNullable => ({
+ events: { fields: fields as [FieldDefinition, ...FieldDefinition[]] },
+ places: {},
+});
+
+/** `bundle` with its site node's `collections` replaced (body and hash kept consistent). */
+async function withCollections(bundle: SiteBundle, collections: Site["collections"]) {
+ const copy = structuredClone(bundle);
+ const row = copy.nodes.find((n) => n.kind === "site");
+ if (!row) throw new Error("no site row");
+ const body = { ...(JSON.parse(row.body) as Site), collections };
+ row.body = canonicalJson(body);
+ row.hash = await nodeHash(body);
+ return copy;
+}
+
+/**
+ * `bundle` with a clean working copy, whose snapshot `snapshotId` names a site body of the same id
+ * and rev with `collections` replaced: what a hand-made archive or a newer store could send.
+ */
+async function withSnapshotSite(
+ bundle: SiteBundle,
+ snapshotId: string,
+ collections: Site["collections"],
+) {
+ const copy = structuredClone(bundle);
+ const row = copy.snapshots.find((s) => s.id === snapshotId);
+ if (!row) throw new Error("no snapshot row");
+ const snapshot = JSON.parse(row.body) as { nodes: NodeRef[] };
+ const ref = snapshot.nodes.find((r) => r.kind === "site");
+ const text = ref ? copy.blobs[ref.hash] : undefined;
+ if (!ref || text === undefined) throw new Error("no site blob");
+ const body = { ...(JSON.parse(text) as Site), collections };
+ const hash = await nodeHash(body);
+ copy.blobs[hash] = canonicalJson(body);
+ ref.hash = hash;
+ row.body = canonicalJson(snapshot);
+ return copy;
+}
+
+export function referenceGateSuite(ctx: SuiteContext): void {
+ describe("RFC-0045 step 1: reference and repeater types are refused on every write path", () => {
+ let n = 0;
+ const store = (): SiteGraphStore => ctx.store();
+ const storage = () => ({ sql: ctx.sql(), blobs: ctx.blobs() });
+
+ async function setup(collections: Site["collections"] = events([TEXT])): Promise {
+ n++;
+ const project = await store().createProject({
+ name: "Gate",
+ slug: `ref-gate-${n}`,
+ ownerId: person.id,
+ siteIds: [],
+ });
+ return store().createSite({
+ projectId: project.id,
+ name: "Gate",
+ slug: `ref-gate-${n}`,
+ defaultLocale: "en",
+ locales: ["en", "tr"],
+ domains: [],
+ pageIds: [],
+ theme: { name: "blog" },
+ ...(collections ? { collections } : {}),
+ });
+ }
+
+ it("refuses them when a site is created or updated", async () => {
+ expect(await code(setup(events([VENUE])))).toBe("fields_unsupported");
+ const site = await setup();
+ for (const def of [VENUE, SPEAKERS])
+ expect(await code(store().updateSite({ ...site, collections: events([TEXT, def]) }))).toBe(
+ "fields_unsupported",
+ );
+ // a dry run refuses too, and nothing was written
+ expect(
+ await code(
+ store().updateSite({ ...site, collections: events([TEXT, VENUE]) }, { dryRun: true }),
+ ),
+ ).toBe("fields_unsupported");
+ expect((await store().getSite(site.id))?.collections).toEqual(site.collections);
+ });
+
+ it("refuses a field operation that turns a field into one", async () => {
+ const site = await setup();
+ for (const type of ["reference", "repeater"] as const)
+ expect(
+ await code(
+ runFieldOperation(
+ store(),
+ site.id,
+ { op: "change_field_type", collection: "events", key: "room", type },
+ { actor: person },
+ ),
+ ),
+ ).toBe("fields_unsupported");
+ expect((await store().getSite(site.id))?.collections).toEqual(site.collections);
+ });
+
+ it("refuses bundles that define them: bootstrap and replace imports alike", async () => {
+ const site = await setup();
+ const bundle = await exportSite(storage(), site.id);
+ for (const def of [VENUE, SPEAKERS]) {
+ const tainted = await withCollections(bundle, events([TEXT, def]));
+ expect(await code(importSite(storage(), tainted))).toBe("fields_unsupported");
+ expect(await code(importSite(storage(), tainted, { mode: "replace" }))).toBe(
+ "fields_unsupported",
+ );
+ }
+ // a marker converting from one is a stage-4 artefact too
+ const marker = await withCollections(
+ bundle,
+ events([{ ...TEXT, migration: { id: "snap_01HZX8K3Q9MG", fromType: "repeater" } }]),
+ );
+ expect(await code(importSite(storage(), marker, { mode: "replace" }))).toBe(
+ "fields_unsupported",
+ );
+ expect((await store().getSite(site.id))?.collections).toEqual(site.collections);
+ // the untouched bundle still imports
+ expect((await importSite(storage(), bundle)).nodes.unchanged).toBe(true);
+ });
+
+ it("refuses bundles whose snapshots' site bodies define them, the working copy clean", async () => {
+ const site = await setup();
+ const snap = await store().createSnapshot(site.id, { actor: person, reason: "manual" });
+ const bundle = await exportSite(storage(), site.id);
+ for (const collections of [
+ events([TEXT, VENUE]),
+ events([TEXT, SPEAKERS]),
+ events([{ ...TEXT, migration: { id: snap.id, fromType: "reference" } }]),
+ events([{ ...TEXT, migration: { id: snap.id, sub: "name" } }]),
+ ]) {
+ const tainted = await withSnapshotSite(bundle, snap.id, collections);
+ for (const mode of ["bootstrap", "replace"] as const)
+ expect(await code(importSite(storage(), tainted, { mode })), mode).toBe(
+ "fields_unsupported",
+ );
+ }
+ // an invalid site body is refused as before, by its schema, never by the gate's walk
+ for (const broken of [{ events: { fields: {} } }, { events: { fields: [null] } }]) {
+ const bad = await withSnapshotSite(bundle, snap.id, broken as never);
+ await expect(importSite(storage(), bad, { mode: "replace" })).rejects.toThrow(
+ /is not a valid site/,
+ );
+ }
+ // a body the target already holds is its own: a forged text under its hash is not read
+ const forged = structuredClone(bundle);
+ const ref = (
+ JSON.parse(forged.snapshots.find((r) => r.id === snap.id)?.body ?? "{}") as {
+ nodes: NodeRef[];
+ }
+ ).nodes.find((r) => r.kind === "site");
+ if (!ref) throw new Error("no site ref");
+ const text = forged.blobs[ref.hash] ?? "{}";
+ forged.blobs[ref.hash] = canonicalJson({
+ ...(JSON.parse(text) as Site),
+ collections: events([TEXT, VENUE]),
+ });
+ expect((await importSite(storage(), forged)).nodes.unchanged).toBe(true);
+ // the same rewrite with clean definitions passes the gate (and fails later, elsewhere)
+ const clean = await withSnapshotSite(bundle, snap.id, events([TEXT]));
+ const later = await importSite(storage(), clean, { mode: "replace" }).then(
+ () => "",
+ (e: unknown) => (e instanceof InvariantError ? e.code : "other"),
+ );
+ expect(later).not.toBe("fields_unsupported");
+ });
+ });
+}
diff --git a/packages/core-ts/src/testing.ts b/packages/core-ts/src/testing.ts
index 207b0bc5..4fdc30b8 100644
--- a/packages/core-ts/src/testing.ts
+++ b/packages/core-ts/src/testing.ts
@@ -17,6 +17,7 @@ import { fieldsSuite } from "./testing-fields.ts";
import { listingsSuite } from "./testing-listings.ts";
import { menuSuite } from "./testing-menu.ts";
import { permalinksSuite } from "./testing-permalinks.ts";
+import { referenceGateSuite } from "./testing-reference-gate.ts";
import { racingDriver, type SuiteContext, taxonomySuite } from "./testing-taxonomy.ts";
export interface StoreFactory {
@@ -275,6 +276,7 @@ export function describeSiteGraphStore(label: string, factory: StoreFactory): vo
collectionsSuite(suite);
fieldsSuite(suite);
fieldOperationsSuite(suite);
+ referenceGateSuite(suite);
listingsSuite(suite);
entryQuerySuite(suite);
});
diff --git a/packages/core-ts/test/fields-definitions.test.ts b/packages/core-ts/test/fields-definitions.test.ts
index 307f0f4e..6d016b9f 100644
--- a/packages/core-ts/test/fields-definitions.test.ts
+++ b/packages/core-ts/test/fields-definitions.test.ts
@@ -33,7 +33,9 @@ describe("checkFieldDefinitions", () => {
for (const name of await readdir(FIXTURES)) {
const data = JSON.parse(await readFile(path.join(FIXTURES, name), "utf8")) as Site;
expect(isValid("site", data), name).toBe(true);
- expect(checkFieldDefinitions(data), name).toEqual([]);
+ // RFC-0045: stage-4 definitions pass only once a writer's capability allows them.
+ expect(checkFieldDefinitions(data, undefined, { referenceTypes: true }), name).toEqual([]);
+ if (name !== "collection-stage4.json") expect(checkFieldDefinitions(data), name).toEqual([]);
}
});
@@ -57,7 +59,8 @@ describe("checkFieldDefinitions", () => {
}),
]);
}
- expect(RESERVED_FIELD_KEYS.size).toBe(32);
+ // RFC-0045 review: three more Object.prototype names (toLocaleString, isPrototypeOf, propertyIsEnumerable)
+ expect(RESERVED_FIELD_KEYS.size).toBe(35);
});
it("reserves every ContentEntry property of the contract", async () => {
diff --git a/packages/core-ts/test/fields-query.test.ts b/packages/core-ts/test/fields-query.test.ts
index d210c9a2..12960d39 100644
--- a/packages/core-ts/test/fields-query.test.ts
+++ b/packages/core-ts/test/fields-query.test.ts
@@ -15,7 +15,7 @@ import {
parseFieldQuery,
} from "../src/fields/query.ts";
import { type ResolvedFields, resolveFields } from "../src/fields/resolve.ts";
-import { FIELD_TYPES } from "../src/fields/types.ts";
+import { ALL_FIELD_TYPES, FIELD_TYPES } from "../src/fields/types.ts";
const L = { en: { label: "x" } };
const DEFS: FieldDefinition[] = [
@@ -74,7 +74,9 @@ const issues = (query: FieldQuery, listFields: string[] = ALL) =>
describe("the operator table (§4)", () => {
it("covers every field type, and only number, date and datetime sort", () => {
- expect(Object.keys(FIELD_QUERY_OPS).sort()).toEqual([...FIELD_TYPES].sort());
+ expect(Object.keys(FIELD_QUERY_OPS).sort()).toEqual([...ALL_FIELD_TYPES].sort());
+ // RFC-0045: neither stage-4 type is listable yet.
+ expect([FIELD_QUERY_OPS.reference, FIELD_QUERY_OPS.repeater]).toEqual([[], []]);
expect(FIELD_TYPES.filter(canOrderBy)).toEqual(["number", "date", "datetime"]);
expect(FIELD_QUERY_OPS.boolean).toEqual(["eq"]);
expect(FIELD_QUERY_OPS.richText).toEqual(["set", "unset"]);
diff --git a/packages/core-ts/test/list-values-rows.test.ts b/packages/core-ts/test/list-values-rows.test.ts
index 339678b7..c2d8d56e 100644
--- a/packages/core-ts/test/list-values-rows.test.ts
+++ b/packages/core-ts/test/list-values-rows.test.ts
@@ -207,7 +207,8 @@ describe("list fingerprint (RFC-0042 §4)", () => {
$defs: { FieldDefinition: { oneOf: { properties: Record }[] } };
};
const variants = schema.$defs.FieldDefinition.oneOf;
- expect(variants.length).toBe(9);
+ // the nine RFC-0041 types and RFC-0045's reference and repeater
+ expect(variants.length).toBe(11);
const sentinel = (name: string, type: string | undefined): unknown => {
if (name === "migration") return { id: "op_1", fromLocalized: true };
if (name === "options") return [{ value: "z", labels: { en: "Z" } }];
@@ -222,6 +223,10 @@ describe("list fingerprint (RFC-0042 §4)", () => {
key: "a",
type,
...(type === "select" ? { options: [{ value: "q", labels: { en: "Q" } }] } : {}),
+ ...(type === "reference" ? { collections: ["places"] } : {}),
+ ...(type === "repeater"
+ ? { fields: [{ key: "s", type: "text", labels: { en: { label: "S" } } }] }
+ : {}),
} as never);
const before = await listFingerprint(site([base], ["a"]), "c");
for (const [name, p] of Object.entries(props)) {
diff --git a/packages/core-ts/test/references-core.test.ts b/packages/core-ts/test/references-core.test.ts
new file mode 100644
index 00000000..6fafa5d9
--- /dev/null
+++ b/packages/core-ts/test/references-core.test.ts
@@ -0,0 +1,562 @@
+/**
+ * RFC-0045 rollout step 1: the pure core of reference and repeater fields — the gate, the
+ * definition rules, values, `resolveFields`, the agents' JSON Schema (open and strict),
+ * `pickMember` and `referenceIssues`. Store, renderer and tool behaviour come in later steps.
+ */
+import type { ContentEntry, FieldDefinition, Site } from "@cloudhal/core-contracts";
+import { describe, expect, it } from "vitest";
+import { validateProps } from "../src/blocks.ts";
+import {
+ checkFieldDefinitions,
+ checkFieldValues,
+ checkRequiredToPublish,
+ FIELD_LIMITS,
+ fieldPropSchema,
+ fieldRefusalCode,
+ fieldsJsonSchema,
+ JSON_SCHEMA_KEYWORDS,
+ type JsonSchema,
+ pickMember,
+ referenceIssues,
+ resolveFields,
+ strictSchemaCost,
+ unsupportedFieldTypeIssues,
+ valueIssues,
+} from "../src/fields/index.ts";
+
+const L = (label: string) => ({ en: { label } });
+const G1 = "entry_01HZX8K3Q9P1";
+const G2 = "entry_01HZX8K3Q9P2";
+
+const VENUE = {
+ key: "venue",
+ type: "reference",
+ labels: L("Venue"),
+ localized: false,
+ collections: ["places"],
+} as const satisfies FieldDefinition;
+const PARTNERS = {
+ key: "partners",
+ type: "reference",
+ labels: L("Partners"),
+ localized: true,
+ collections: ["places", "people"],
+ multiple: true,
+ maxItems: 3,
+} as const satisfies FieldDefinition;
+const SPEAKERS = {
+ key: "speakers",
+ type: "repeater",
+ labels: L("Speakers"),
+ localized: false,
+ maxItems: 4,
+ fields: [
+ {
+ key: "person",
+ type: "reference",
+ labels: L("Person"),
+ requiredToPublish: true,
+ collections: ["people"],
+ },
+ {
+ key: "role",
+ type: "select",
+ labels: L("Role"),
+ options: [
+ { value: "host", labels: { en: "Host" } },
+ { value: "speaker", labels: { en: "Speaker" } },
+ { value: "panelist", labels: { en: "Panelist" } },
+ ],
+ },
+ { key: "talk", type: "text", labels: L("Talk"), maxLength: 200 },
+ { key: "abstract", type: "richText", labels: L("Abstract") },
+ { key: "day", type: "date", labels: L("Day") },
+ { key: "slides", type: "url", labels: L("Slides") },
+ ],
+} as const satisfies FieldDefinition;
+const STARTS = {
+ key: "startsAt",
+ type: "datetime",
+ labels: L("Starts"),
+ localized: false,
+} as const satisfies FieldDefinition;
+
+type Collections = NonNullable;
+const siteWith = (events: FieldDefinition[], more: Collections = {}) => ({
+ defaultLocale: "en",
+ collections: {
+ events: { fields: events },
+ places: { labels: { en: { singular: "Place", plural: "Places" } } },
+ people: {
+ fields: [
+ {
+ key: "team",
+ type: "select",
+ labels: L("Team"),
+ localized: false,
+ options: [
+ { value: "core", labels: { en: "Core" } },
+ { value: "now", labels: { en: "Now" } },
+ ],
+ },
+ { key: "joined", type: "date", labels: L("Joined"), localized: false },
+ ],
+ listFields: ["team", "joined"],
+ },
+ ...more,
+ } as unknown as Collections,
+});
+const codes = (issues: readonly { code: string }[]) => issues.map((i) => i.code);
+const ENABLED = { referenceTypes: true };
+
+describe("the gate (fields_unsupported)", () => {
+ it("refuses both types, and markers naming them, without a capability", () => {
+ const site = siteWith([VENUE, SPEAKERS]);
+ const issues = checkFieldDefinitions(site);
+ expect(issues.map((i) => [i.path, i.code])).toEqual([
+ ["collections/events/fields/0/type", "fields_unsupported"],
+ ["collections/events/fields/1/type", "fields_unsupported"],
+ ]);
+ expect(fieldRefusalCode(issues)).toBe("fields_unsupported");
+ const marker = (migration: object) =>
+ ({
+ ...STARTS,
+ type: "text",
+ migration: { id: "snap_01HZX8K3Q9MG", ...migration },
+ }) as FieldDefinition;
+ expect(
+ codes(unsupportedFieldTypeIssues(siteWith([marker({ fromType: "reference" })]))),
+ ).toEqual(["fields_unsupported"]);
+ expect(codes(unsupportedFieldTypeIssues(siteWith([marker({ sub: "talk" })])))).toEqual([
+ "fields_unsupported",
+ ]);
+ expect(unsupportedFieldTypeIssues(siteWith([marker({ fromType: "date" })]))).toEqual([]);
+ });
+
+ it("wins the refusal code over other definition problems", () => {
+ const issues = checkFieldDefinitions(siteWith([VENUE, { ...STARTS, key: "title" }]));
+ expect(codes(issues)).toEqual(["fields_unsupported", "field_key_reserved"]);
+ expect(fieldRefusalCode(issues)).toBe("fields_unsupported");
+ });
+
+ it("changes nothing for sites without the new types", () => {
+ expect(checkFieldDefinitions(siteWith([STARTS]))).toEqual([]);
+ expect(checkFieldDefinitions(siteWith([STARTS]), undefined, ENABLED)).toEqual([]);
+ });
+});
+
+describe("definition rules (with the capability)", () => {
+ const check = (events: FieldDefinition[], more?: Collections, inUse?: string[]) =>
+ checkFieldDefinitions(siteWith(events, more), undefined, { ...ENABLED, inUse });
+
+ it("accepts declared, inferred and built-in target collections only", () => {
+ expect(check([VENUE, PARTNERS, SPEAKERS])).toEqual([]);
+ expect(check([{ ...VENUE, collections: ["posts"] }])).toEqual([]);
+ const venue = { ...VENUE, collections: ["halls"] } as FieldDefinition;
+ expect(check([venue]).map((i) => [i.path, i.code])).toEqual([
+ ["collections/events/fields/0/collections/0", "reference_target_unknown"],
+ ]);
+ expect(check([venue], {}, ["halls"])).toEqual([]);
+ const sub = { ...SPEAKERS, fields: [{ ...SPEAKERS.fields[0], collections: ["crew"] }] };
+ expect(check([sub as unknown as FieldDefinition]).map((i) => i.path)).toEqual([
+ "collections/events/fields/0/fields/0/collections/0",
+ ]);
+ });
+
+ it("checks where: one target, its list fields, no now", () => {
+ const where = (w: object, collections = ["people"]) =>
+ check([{ ...VENUE, collections, where: [w] } as unknown as FieldDefinition]);
+ expect(where({ field: "team", op: "eq", value: "core" })).toEqual([]);
+ expect(codes(where({ field: "team", op: "eq", value: "core" }, ["people", "places"]))).toEqual([
+ "invalid_query",
+ ]);
+ expect(where({ field: "nope", op: "eq", value: "x" })[0]?.path).toBe(
+ "collections/events/fields/0/where/0/field",
+ );
+ // `now` is refused as the clock on a date field, and is a plain option value on a select
+ expect(where({ field: "joined", op: "lt", value: "now" }).map((i) => [i.path, i.code])).toEqual(
+ [["collections/events/fields/0/where/0", "invalid_query"]],
+ );
+ expect(where({ field: "team", op: "eq", value: "now" })).toEqual([]);
+ expect(where({ field: "team", op: "in", values: ["core", "now"] })).toEqual([]);
+ expect(where({ field: "joined", op: "lt", value: "2026-01-01" })).toEqual([]);
+ });
+
+ it("refuses maxItems without multiple, bad sub-fields and a bad itemLabel", () => {
+ expect(codes(check([{ ...VENUE, maxItems: 3 } as FieldDefinition]))).toEqual(["field_bounds"]);
+ const subs = (fields: object[], extra: object = {}) =>
+ check([{ ...SPEAKERS, fields, ...extra } as unknown as FieldDefinition]);
+ const talk = SPEAKERS.fields[2];
+ expect(codes(subs([talk, talk]))).toEqual(["field_key_duplicate"]);
+ expect(codes(subs([{ ...talk, key: "title" }]))).toEqual(["field_key_reserved"]);
+ expect(
+ codes(subs([{ key: "n", type: "number", labels: L("N"), minimum: 2, maximum: 1 }])),
+ ).toEqual(["field_bounds"]);
+ const role = SPEAKERS.fields[1];
+ expect(codes(subs([{ ...role, options: [role.options[0], role.options[0]] }]))).toEqual([
+ "field_option_duplicate",
+ ]);
+ expect(subs([talk], { itemLabel: "talk" })).toEqual([]);
+ expect(codes(subs([talk, SPEAKERS.fields[4]], { itemLabel: "day" }))).toEqual([
+ "unknown_field",
+ ]);
+ });
+
+ it("counts sub-fields toward the leaf and site caps", () => {
+ const ten = Array.from({ length: 10 }, (_, i) => ({
+ key: `s${i}`,
+ type: "text",
+ labels: L("S"),
+ }));
+ const repeaters = Array.from(
+ { length: 10 },
+ (_, i) => ({ ...SPEAKERS, key: `r${i}`, fields: ten }) as unknown as FieldDefinition,
+ );
+ // 10 fields + 100 sub-fields = 110 leaves
+ expect(codes(check(repeaters))).toEqual(["too_many_fields"]);
+ const nine = repeaters.slice(0, 9);
+ expect(check(nine)).toEqual([]); // 99 leaves
+ const sites: Collections = {};
+ for (const c of ["a", "b"])
+ sites[c] = { fields: nine as [FieldDefinition, ...FieldDefinition[]] };
+ // 3 collections × 99 > 200
+ expect(codes(check(nine, sites)).at(-1)).toBe("too_many_fields");
+ });
+});
+
+describe("values", () => {
+ it("compiles references and repeaters into the props subset", () => {
+ expect(fieldPropSchema(VENUE)).toMatchObject({
+ type: "string",
+ pattern: "^entry_[A-Za-z0-9]{6,32}$",
+ });
+ expect(fieldPropSchema(PARTNERS)).toMatchObject({ type: "array", maxItems: 3 });
+ const repeater = fieldPropSchema(SPEAKERS);
+ expect(repeater).toMatchObject({ type: "array", maxItems: 4, items: { type: "object" } });
+ expect(Object.keys((repeater as { items: { properties: object } }).items.properties)).toEqual(
+ SPEAKERS.fields.map((s) => s.key),
+ );
+ expect(
+ validateProps(
+ { type: "object", properties: { v: repeater }, additionalProperties: false },
+ {
+ v: [{ person: G1, role: "host" }],
+ },
+ ),
+ ).toEqual([]);
+ });
+
+ it("checks reference values", () => {
+ expect(valueIssues(VENUE, G1, "fields/venue")).toEqual([]);
+ expect(valueIssues(VENUE, "asset_01HZX8K3Q9P1", "fields/venue")).toHaveLength(1);
+ expect(valueIssues(VENUE, [G1], "fields/venue")).toHaveLength(1);
+ expect(valueIssues(PARTNERS, [G1, G2], "fields/partners")).toEqual([]);
+ expect(valueIssues(PARTNERS, [G1, G1], "fields/partners")[0]?.message).toBe(
+ "each entry at most once",
+ );
+ expect(valueIssues(PARTNERS, [G1, G2, G1, G2], "fields/partners")[0]?.message).toContain(
+ "at most 3",
+ );
+ });
+
+ it("checks every repeater item against its sub-fields", () => {
+ const at = (v: unknown) => valueIssues(SPEAKERS, v, "fields/speakers").map((i) => i.path);
+ expect(at([{ person: G1, talk: "Hi", day: "2026-11-11", slides: "/s" }])).toEqual([]);
+ expect(at([{}])).toEqual(["fields/speakers/0"]);
+ expect(at([{ nope: 1 }])).toEqual(["fields/speakers/0/nope"]);
+ // prototype names are not sub-fields, and do not make an item non-empty
+ expect(at([{ constructor: { deep: [1, { x: "y" }] } }])).toEqual([
+ "fields/speakers/0/constructor",
+ ]);
+ expect(at([{ toString: 1, talk: "x" }])).toEqual(["fields/speakers/0/toString"]);
+ expect(at([JSON.parse('{"__proto__": {"a": 1}}')])).toEqual(["fields/speakers/0/__proto__"]);
+ expect(at([{ talk: "a\u0007b" }])).toEqual(["fields/speakers/0/talk"]);
+ expect(at([{ day: "2026-02-30" }])).toEqual(["fields/speakers/0/day"]);
+ expect(at([{ slides: "asset:asset_01HZX8K3Q9P1" }])).toEqual(["fields/speakers/0/slides"]);
+ expect(at([{ role: "chair" }])).toEqual(["fields/speakers/0/role"]);
+ expect(at([{ person: null }])).toEqual(["fields/speakers/0/person"]);
+ expect(at(Array.from({ length: 5 }, () => ({ talk: "x" })))).toEqual(["fields/speakers"]);
+ // Rich text in an item has rich text's 50 000 limit, not the subset's 10 000.
+ expect(at([{ abstract: "a".repeat(20_000) }])).toEqual([]);
+ expect(at([{ abstract: "a".repeat(50_001) }])).toEqual(["fields/speakers/0/abstract"]);
+ });
+
+ it("reads only an item's own values, so prototype-named sub-fields are simply absent", () => {
+ const odd = {
+ ...SPEAKERS,
+ fields: ["isPrototypeOf", "toLocaleString", "propertyIsEnumerable"].map((key) => ({
+ key,
+ type: "text",
+ labels: L(key),
+ })),
+ } as unknown as FieldDefinition;
+ expect(valueIssues(odd, [{ isPrototypeOf: "x" }], "fields/odd")).toEqual([]);
+ expect(
+ valueIssues(odd, [{ toLocaleString: "y" }, { propertyIsEnumerable: "z" }], "fields/odd"),
+ ).toEqual([]);
+ // a required prop the value leaves out is missing, not Object.prototype's function
+ expect(
+ validateProps(
+ {
+ type: "object",
+ properties: { isPrototypeOf: { type: "string" as const } },
+ required: ["isPrototypeOf"],
+ additionalProperties: false,
+ },
+ {},
+ ),
+ ).toEqual([{ path: "isPrototypeOf", message: "required" }]);
+ // and the names are reserved as keys, top-level and in a repeater
+ const reserved = checkFieldDefinitions(
+ siteWith([odd, { ...STARTS, key: "isPrototypeOf" }]),
+ undefined,
+ ENABLED,
+ );
+ expect(reserved.map((i) => i.path)).toEqual([
+ "collections/events/fields/0/fields/0/key",
+ "collections/events/fields/0/fields/1/key",
+ "collections/events/fields/0/fields/2/key",
+ "collections/events/fields/1/key",
+ ]);
+ });
+
+ it("caps references per entry across fields and items", () => {
+ const many = {
+ key: "many",
+ type: "reference",
+ labels: L("M"),
+ localized: true,
+ collections: ["places"],
+ multiple: true,
+ maxItems: 50,
+ } as const satisfies FieldDefinition;
+ const more = { ...many, key: "more" };
+ const big = { ...SPEAKERS, maxItems: 50, fields: [SPEAKERS.fields[0]] } as FieldDefinition;
+ const ids = (n: number, p: string) =>
+ Array.from({ length: n }, (_, i) => `entry_${p}${String(i).padStart(6, "0")}`);
+ const values = { many: ids(50, "a"), more: ids(50, "b"), speakers: [{ person: G1 }] };
+ expect(codes(checkFieldValues([many, more, big], { values }))).toEqual(["too_many_references"]);
+ expect(
+ checkFieldValues([many, more, big], { values: { many: values.many, more: values.more } }),
+ ).toEqual([]);
+ expect(FIELD_LIMITS.referencesPerEntry).toBe(100);
+ });
+
+ it("needs required sub-fields in every item to publish", () => {
+ const site = siteWith([SPEAKERS]);
+ const entry = (status: "draft" | "published") =>
+ ({
+ id: "entry_01HZX8K3Q9E1",
+ collection: "events",
+ status,
+ fields: { speakers: [{ person: G1 }, { talk: "x" }] },
+ }) as const;
+ expect(checkRequiredToPublish(site, entry("draft"))).toEqual([]);
+ expect(checkRequiredToPublish(site, entry("published")).map((i) => i.path)).toEqual([
+ "fields/speakers/1/person",
+ ]);
+ });
+});
+
+describe("resolveFields", () => {
+ it("resolves group ids and items, and skips types it does not know", () => {
+ const future = {
+ key: "geo",
+ type: "geopoint",
+ labels: L("Geo"),
+ localized: true,
+ } as unknown as FieldDefinition;
+ const site = siteWith([VENUE, SPEAKERS, future]);
+ const items = [{ person: G2, role: "host" }];
+ const r = resolveFields(site, {
+ id: "entry_01HZX8K3Q9E1",
+ collection: "events",
+ fields: { venue: G1, speakers: items, geo: { lat: 1 } },
+ });
+ expect(Object.keys(r)).toEqual(["venue", "speakers"]);
+ expect([r.venue?.value, r.speakers?.value]).toEqual([G1, items]);
+ const bad = resolveFields(site, {
+ id: "entry_01HZX8K3Q9E1",
+ collection: "events",
+ fields: { speakers: [{}] },
+ });
+ expect(bad.speakers).toMatchObject({
+ value: undefined,
+ issue: { code: "field_value_unreadable" },
+ });
+ });
+});
+
+/** Every `[path, keyword]` of a schema. */
+function keywords(s: JsonSchema, at = ""): [string, string][] {
+ const out: [string, string][] = Object.keys(s).map((k) => [at, k]);
+ for (const [k, c] of Object.entries(s.properties ?? {})) out.push(...keywords(c, `${at}/${k}`));
+ if (s.items) out.push(...keywords(s.items, `${at}/items`));
+ return out;
+}
+
+describe("fieldsJsonSchema", () => {
+ const ROLE = SPEAKERS.fields[1];
+ const EXAMPLE: FieldDefinition[] = [
+ STARTS,
+ VENUE,
+ { ...SPEAKERS, maxItems: 20, fields: [SPEAKERS.fields[0], ROLE, SPEAKERS.fields[2]] },
+ ];
+ const name = (key: string) => ({ places: "Places", people: "People" })[key];
+ const open = fieldsJsonSchema(EXAMPLE, "en", "en", { collectionName: name });
+ const strict = fieldsJsonSchema(EXAMPLE, "en", "en", { collectionName: name, strict: true });
+
+ it("emits references as group id strings and repeaters as closed item lists", () => {
+ const p = open.properties ?? {};
+ expect(p.venue).toMatchObject({ type: "string", pattern: "^entry_[A-Za-z0-9]{6,32}$" });
+ expect(p.venue?.description).toContain(
+ "An entry in Places (places) of this site, by the groupId",
+ );
+ expect(p.venue?.description).toContain("every language version shows the targets");
+ expect(p.speakers).toMatchObject({
+ type: "array",
+ items: { type: "object", additionalProperties: false },
+ });
+ expect(p.speakers?.description).toContain("at most 20 items");
+ expect(p.speakers?.items?.properties?.person?.description).toContain("People (people)");
+ expect(p.speakers?.items?.properties?.person?.description).toContain("Needed to publish.");
+ expect(p.speakers?.items?.required).toBeUndefined();
+ const partners = fieldsJsonSchema([PARTNERS], "en").properties?.partners;
+ expect(partners).toMatchObject({ type: "array", items: { type: "string" } });
+ expect(partners?.description).toContain("at most 3 entries in places or people");
+ });
+
+ it("keeps to the strict-mode keywords, a root object, no $ref and depth 4", () => {
+ for (const s of [open, strict]) {
+ for (const [, k] of keywords(s)) expect(JSON_SCHEMA_KEYWORDS).toContain(k);
+ expect(s.type).toBe("object");
+ expect(JSON.stringify(s)).not.toMatch(/\$ref|\$defs|oneOf|anyOf/);
+ expect(strictSchemaCost(s).depth).toBe(4);
+ }
+ });
+
+ it("makes every property required and nullable in the strict profile", () => {
+ const p = strict.properties ?? {};
+ expect(strict.required).toEqual(["startsAt", "venue", "speakers"]);
+ expect([p.startsAt?.type, p.venue?.type, p.speakers?.type]).toEqual([
+ ["object", "null"],
+ ["string", "null"],
+ ["array", "null"],
+ ]);
+ // a datetime's own parts stay required and not nullable
+ expect(p.startsAt?.required).toEqual(["at", "timeZone"]);
+ expect(p.startsAt?.properties?.at?.type).toBe("string");
+ const item = p.speakers?.items;
+ expect(item?.required).toEqual(["person", "role", "talk"]);
+ expect(item?.properties?.role?.enum).toEqual(["host", "speaker", "panelist", null]);
+ expect(item?.description).toContain("null unsets that value in this item");
+ expect(strict.description).toContain("null leaves the stored value as it is");
+ });
+
+ it("counts the RFC's example: 8 properties, depth 4, 4 enum values, 6 unions", () => {
+ expect(strictSchemaCost(strict)).toEqual({ properties: 8, depth: 4, enumValues: 4, unions: 6 });
+ expect(strictSchemaCost(open)).toEqual({ properties: 8, depth: 4, enumValues: 3, unions: 0 });
+ });
+});
+
+type Node = Pick & {
+ translation?: { group: string };
+};
+
+describe("pickMember", () => {
+ const venueEn: Node = { id: G1, collection: "places", locale: "en" };
+ const venueTr: Node = {
+ id: "entry_01HZX8K3Q9T1",
+ collection: "places",
+ locale: "tr",
+ translation: { group: G1 },
+ };
+ const relinked: Node = {
+ id: G2,
+ collection: "places",
+ locale: "de",
+ translation: { group: "entry_01HZX8K3Q9ZZ" },
+ };
+ const opts = { defaultLocale: "en", collections: ["places"] };
+
+ it("takes the page's locale, then the default locale under default-with-notice", () => {
+ expect(pickMember(G1, [venueEn, venueTr], { ...opts, locale: "tr" })?.member).toBe(venueTr);
+ expect(pickMember(G1, [venueEn], { ...opts, locale: "tr" })).toBeUndefined();
+ expect(
+ pickMember(G1, [venueEn], { ...opts, locale: "tr", fallback: "default-with-notice" }),
+ ).toEqual({
+ member: venueEn,
+ fromFallbackLocale: true,
+ });
+ });
+
+ it("counts members by COALESCE(translation.group, id) in allowed collections only", () => {
+ expect(pickMember(G1, [venueTr], { ...opts, locale: "tr" })?.member).toBe(venueTr);
+ // a relinked node is no longer a member of its own id's group
+ expect(pickMember(G2, [relinked], { ...opts, locale: "de" })).toBeUndefined();
+ expect(
+ pickMember(G1, [venueEn], { ...opts, collections: ["halls"], locale: "en" }),
+ ).toBeUndefined();
+ });
+});
+
+describe("referenceIssues", () => {
+ const site = { ...siteWith([VENUE, SPEAKERS]), localization: { fallback: "none" } } as const;
+ const entry = (id: string, extra: Partial): ContentEntry =>
+ ({
+ schemaVersion: 1,
+ id,
+ siteId: "site_01HZX8K3Q9T1",
+ collection: "events",
+ slug: id.slice(-4).toLowerCase(),
+ locale: "en",
+ status: "published",
+ title: id,
+ body: { format: "markdown", markdown: "" },
+ rev: 1,
+ createdAt: "2026-11-11T09:00:00Z",
+ updatedAt: "2026-11-11T09:00:00Z",
+ ...extra,
+ }) as ContentEntry;
+ const place = entry(G1, { collection: "places" });
+ const NOW = Date.parse("2026-11-11T10:00:00Z");
+
+ it("is empty for a site without reference definitions", () => {
+ expect(referenceIssues({ site: siteWith([STARTS]), entries: [place] }, NOW)).toEqual([]);
+ });
+
+ it("names missing, hidden and locale-less targets of shown entries", () => {
+ const person = entry(G2, { collection: "people", status: "draft" });
+ const event = entry("entry_01HZX8K3Q9E1", {
+ fields: { venue: G1, speakers: [{ person: G2 }, { person: "entry_01HZX8K3Q9NO" }] },
+ });
+ const turkish = entry("entry_01HZX8K3Q9E2", {
+ locale: "tr",
+ fields: {},
+ translation: {
+ group: event.id,
+ method: "human",
+ source: { id: event.id, locale: "en", rev: 1, hash: "h".repeat(64) },
+ },
+ });
+ const draft = entry("entry_01HZX8K3Q9E3", {
+ status: "draft",
+ fields: { venue: "entry_01HZX8K3Q9NO" },
+ });
+ const issues = referenceIssues({ site, entries: [place, person, event, turkish, draft] }, NOW);
+ expect(issues.map((i) => [i.entryId.slice(-2), i.path, i.reason])).toEqual([
+ ["E1", "fields/speakers/0/person", "hidden"],
+ ["E1", "fields/speakers/1/person", "missing"],
+ // the Turkish version inherits the shared values and resolves them in Turkish
+ ["E2", "fields/venue", "locale"],
+ ["E2", "fields/speakers/0/person", "hidden"],
+ ["E2", "fields/speakers/1/person", "missing"],
+ ]);
+ const fallback = { ...site, localization: { fallback: "default-with-notice" as const } };
+ expect(
+ referenceIssues({ site: fallback, entries: [place, event, turkish] }, NOW).some(
+ (i) => i.path === "fields/venue",
+ ),
+ ).toBe(false);
+ });
+});
diff --git a/packages/mcp-server/src/collection-fields.ts b/packages/mcp-server/src/collection-fields.ts
index 0dee89c8..da0f6f69 100644
--- a/packages/mcp-server/src/collection-fields.ts
+++ b/packages/mcp-server/src/collection-fields.ts
@@ -6,19 +6,36 @@
*/
import type { CollectionSettings, FieldDefinition, Site } from "@cloudhal/core-contracts";
import { SiteSchema } from "@cloudhal/core-contracts";
-import { fieldLabel, fieldsJsonSchema } from "@cloudhal/core-ts/fields";
+import { fieldLabel, fieldsJsonSchema, isReferenceFieldType } from "@cloudhal/core-ts/fields";
import { type JsonSchema, quote } from "./common.ts";
+import { defsClosure } from "./schema-transforms.ts";
const DEFS = SiteSchema.$defs as Record;
const VARIANTS = (DEFS.FieldDefinition as JsonSchema).oneOf as JsonSchema[];
+/** A variant's `type` value (`{ enum: ["text"] }`). */
+const typeOf = (variant: JsonSchema): string =>
+ ((variant.properties as Record).type?.enum ?? [])[0] ?? "";
+
/** A definition variant without `migration`: only field operations write the marker. */
function withoutMarker(variant: JsonSchema): JsonSchema {
const { migration: _m, ...properties } = variant.properties as Record;
return { ...variant, properties };
}
-/** `update_collection`'s `fields`: the contract's definitions, minus the operation marker. */
+/**
+ * The definitions `update_collection` takes: the contract's variants of the enabled types (RFC-0045's
+ * `reference` and `repeater` join at rollout step 7), each without the operation marker.
+ */
+const FIELD_DEFINITION: JsonSchema = {
+ ...DEFS.FieldDefinition,
+ oneOf: VARIANTS.filter((v) => !isReferenceFieldType(typeOf(v))).map(withoutMarker),
+};
+
+/**
+ * `update_collection`'s `fields`: the enabled definitions, minus the operation marker, with every
+ * `$defs` entry they reach (`defsClosure`), so a contract change cannot leave a `$ref` dangling.
+ */
export const FIELD_DEFINITIONS_INPUT: JsonSchema = {
type: "array",
maxItems: 30,
@@ -26,16 +43,8 @@ export const FIELD_DEFINITIONS_INPUT: JsonSchema = {
"The collection's typed custom fields, the whole list in form order ([] removes them all). Free edits only: add fields, reorder, change labels and help, add select options or change their labels, toggle multiline, searchable or requiredToPublish, change maxLength/minimum/maximum. Renaming a key, changing a type, localized or multiple, and dropping a key or option still in use need rename_field, change_field_type, set_field_localized or remove_field.",
items: { $ref: "#/$defs/FieldDefinition" },
$defs: {
- FieldDefinition: {
- ...DEFS.FieldDefinition,
- oneOf: VARIANTS.map(withoutMarker),
- },
- FieldKey: DEFS.FieldKey,
- FieldLabel: DEFS.FieldLabel,
- FieldLabels: DEFS.FieldLabels,
- FieldOption: DEFS.FieldOption,
- Slug: DEFS.Slug,
- Locale: DEFS.Locale,
+ ...defsClosure(SiteSchema as JsonSchema, FIELD_DEFINITION),
+ FieldDefinition: FIELD_DEFINITION,
},
};
diff --git a/packages/mcp-server/src/field-hints.ts b/packages/mcp-server/src/field-hints.ts
index c19d6470..fab8f971 100644
--- a/packages/mcp-server/src/field-hints.ts
+++ b/packages/mcp-server/src/field-hints.ts
@@ -4,6 +4,7 @@
* `details.issues`, each at its slash path (`fields/startsAt/at`), so the hint only names the tool.
*/
import type { InvariantCode } from "@cloudhal/core-ts";
+import { FIELD_TYPES } from "@cloudhal/core-ts/fields";
const OPERATIONS = "rename_field, change_field_type, set_field_localized or remove_field";
@@ -37,6 +38,12 @@ const HINTS: Partial> = {
"fix the mappings at the paths in details.issues: list_collections shows a preset's paths, which are required and the sources each takes (structuredData.paths); field sources need a field of a fitting type, and required values come from the entry, never a constant",
invalid_expiry:
"expiry.field must be a date or datetime field of the collection (list_collections → fields)",
+ // RFC-0045 (references and repeaters; refused until they are enabled)
+ fields_unsupported: `reference and repeater fields are not available on this server yet: give the field one of the types update_collection's fields input takes (${FIELD_TYPES.join(", ")}; text for now), and keep the rest of the change as it is`,
+ reference_target_unknown:
+ "a reference field's collections must be collections of this site (list_collections)",
+ too_many_references:
+ "an entry holds at most 100 references across its fields and repeater items; remove some",
list_field_in_use:
"contentList sections on the pages in details.pages still filter or sort by that field: change their where/orderBy first (update_page), then try again",
};
diff --git a/packages/mcp-server/src/schema-transforms.ts b/packages/mcp-server/src/schema-transforms.ts
index 49821f3c..ada965e1 100644
--- a/packages/mcp-server/src/schema-transforms.ts
+++ b/packages/mcp-server/src/schema-transforms.ts
@@ -30,6 +30,44 @@ export function hoistDefs(schema: JsonSchema): JsonSchema {
: root;
}
+const LOCAL_REF = "#/$defs/";
+
+/** The `$defs` names `node` refers to directly (`#/$defs/` only). */
+function refsOf(node: unknown, out: Set): Set {
+ if (Array.isArray(node)) for (const n of node) refsOf(n, out);
+ else if (node && typeof node === "object")
+ for (const [k, v] of Object.entries(node as Record)) {
+ if (k === "$ref" && typeof v === "string" && v.startsWith(LOCAL_REF))
+ out.add(v.slice(LOCAL_REF.length));
+ else refsOf(v, out);
+ }
+ return out;
+}
+
+/**
+ * RFC-0045 §6: the `$defs` of `schema` that `from` reaches, transitively through `#/$defs/*`
+ * references: a `$defs` name (included itself) or a schema node of its own (not included). A tool
+ * that embeds part of a contract carries exactly these, so no `$ref` dangles when a contract
+ * change adds one (`hoistDefs` only hoists nested `$defs` and finds no missing ones). Throws on a
+ * reference `schema` does not define.
+ */
+export function defsClosure(
+ schema: JsonSchema,
+ from: string | JsonSchema,
+): Record {
+ const defs = (schema.$defs ?? {}) as Record;
+ const out: Record = {};
+ const queue = typeof from === "string" ? [from] : [...refsOf(from, new Set())];
+ for (let name = queue.shift(); name !== undefined; name = queue.shift()) {
+ if (Object.hasOwn(out, name)) continue;
+ const def = defs[name];
+ if (!def) throw new Error(`$defs has no ${name}`);
+ out[name] = def;
+ for (const next of refsOf(def, new Set())) if (!Object.hasOwn(out, next)) queue.push(next);
+ }
+ return out;
+}
+
/**
* Inputs may name an image by asset alone (`{ assetId, alt? }`, RFC-0004 §2.2): the store writes
* `src`, `width` and `height`. So an input ImageRef needs `src` or `assetId`, and `alt` may be left
diff --git a/packages/mcp-server/test/tool-schemas.test.ts b/packages/mcp-server/test/tool-schemas.test.ts
new file mode 100644
index 00000000..8aea655b
--- /dev/null
+++ b/packages/mcp-server/test/tool-schemas.test.ts
@@ -0,0 +1,154 @@
+/**
+ * RFC-0045 §6 and rollout step 1: every `$ref` in every tool's input and output schema resolves
+ * (`defsClosure` replaced `update_collection`'s hand-written `$defs`), `update_collection` lists
+ * only the enabled field types, and a reference or repeater definition is refused through it.
+ */
+import { AuditLog, PolicyEngine } from "@cloudhal/agent-runtime";
+import { SiteSchema } from "@cloudhal/core-contracts";
+import { SiteGraphStore, seedStarterSite } from "@cloudhal/core-ts";
+import { MemoryBlobStore, migrate, migrations } from "@cloudhal/storage";
+import { BetterSqlite3Driver } from "@cloudhal/storage/sqlite";
+import { describe, expect, it } from "vitest";
+import { FIELD_DEFINITIONS_INPUT } from "../src/collection-fields.ts";
+import type { JsonSchema } from "../src/common.ts";
+import { fieldHint } from "../src/field-hints.ts";
+import { defsClosure } from "../src/schema-transforms.ts";
+import { defineTools, runTool, toolPolicies } from "../src/tools.ts";
+
+const tools = defineTools();
+
+/** Every `$ref` under `node`, with where it sits. */
+function refs(node: unknown, at = "", out: [string, string][] = []): [string, string][] {
+ if (Array.isArray(node))
+ node.forEach((n, i) => {
+ refs(n, `${at}/${i}`, out);
+ });
+ else if (node && typeof node === "object")
+ for (const [k, v] of Object.entries(node as Record))
+ if (k === "$ref" && typeof v === "string") out.push([at, v]);
+ else refs(v, `${at}/${k}`, out);
+ return out;
+}
+
+/** The node a local JSON pointer (`#/$defs/X`, `#/properties/a`) names in `root`, if any. */
+function resolve(root: unknown, ref: string): unknown {
+ if (!ref.startsWith("#")) return undefined;
+ let node: unknown = root;
+ for (const raw of ref.slice(2).split("/").filter(Boolean)) {
+ const key = raw.replaceAll("~1", "/").replaceAll("~0", "~");
+ if (!node || typeof node !== "object" || !Object.hasOwn(node, key)) return undefined;
+ node = (node as Record)[key];
+ }
+ return node;
+}
+
+describe("tool schemas", () => {
+ it("resolve every $ref in every tool's input and output schema", () => {
+ const dangling: string[] = [];
+ for (const tool of tools)
+ for (const [which, schema] of [
+ ["input", tool.inputSchema],
+ ["output", tool.outputSchema],
+ ] as const)
+ for (const [at, ref] of refs(schema))
+ if (resolve(schema, ref) === undefined)
+ dangling.push(`${tool.name} ${which}${at}: ${ref}`);
+ expect(dangling).toEqual([]);
+ });
+
+ it("offers update_collection the enabled field types only", () => {
+ const defs = FIELD_DEFINITIONS_INPUT.$defs as Record;
+ const variants = (defs.FieldDefinition?.oneOf ?? []) as JsonSchema[];
+ const types = variants.map(
+ (v) => (v.properties as Record).type?.enum[0],
+ );
+ expect(types).toEqual([
+ "text",
+ "richText",
+ "number",
+ "boolean",
+ "date",
+ "datetime",
+ "url",
+ "image",
+ "select",
+ ]);
+ // no marker, and nothing the stage-4 variants alone need
+ expect(variants.every((v) => !("migration" in (v.properties as object)))).toBe(true);
+ expect(Object.keys(defs).sort()).toEqual([
+ "FieldDefinition",
+ "FieldKey",
+ "FieldLabel",
+ "FieldLabels",
+ "FieldOption",
+ "Locale",
+ "Slug",
+ ]);
+ });
+});
+
+describe("defsClosure", () => {
+ const site = SiteSchema as unknown as JsonSchema;
+
+ it("follows $refs transitively from a $defs name or a node", () => {
+ const closure = defsClosure(site, "FieldDefinition");
+ for (const name of [
+ "FieldDefinition",
+ "SubFieldDefinition",
+ "FieldCondition",
+ "FieldMigration",
+ "Id",
+ ])
+ expect(Object.keys(closure)).toContain(name);
+ expect(
+ Object.keys(defsClosure(site, { items: { $ref: "#/$defs/FieldLabels" } })).sort(),
+ ).toEqual(["FieldLabel", "FieldLabels", "Locale"]);
+ });
+
+ it("throws on a reference the schema does not define", () => {
+ expect(() => defsClosure(site, { $ref: "#/$defs/Nope" })).toThrow(/Nope/);
+ });
+});
+
+describe("update_collection and the RFC-0045 gate", () => {
+ it("hints at the types update_collection takes, from the enabled list", () => {
+ const hint = fieldHint("fields_unsupported", {}) ?? "";
+ expect(hint).toContain("update_collection's fields input");
+ expect(hint).toContain("text, richText, number, boolean, date, datetime, url, image, select");
+ expect(hint).not.toContain("list_collections");
+ });
+
+ it("refuses a reference or repeater definition with fields_unsupported", async () => {
+ const sql = await BetterSqlite3Driver.open(":memory:");
+ await migrate(sql, migrations);
+ const store = new SiteGraphStore({ sql, blobs: new MemoryBlobStore() });
+ const site = await seedStarterSite(store, { name: "Gate", slug: "gate" });
+ await store.updateSite({ ...site, collections: { places: {}, events: {} } });
+ const tool = tools.find((t) => t.name === "update_collection") as (typeof tools)[number];
+ const agent = { kind: "agent" as const, id: "agent_gate0001", name: "Organiser" };
+ const label = { en: { label: "Venue" } };
+ for (const def of [
+ { key: "venue", type: "reference", labels: label, localized: false, collections: ["places"] },
+ {
+ key: "speakers",
+ type: "repeater",
+ labels: label,
+ localized: false,
+ fields: [{ key: "name", type: "text", labels: label }],
+ },
+ ]) {
+ const call = runTool(
+ tool,
+ { siteId: site.id, collection: "events", fields: [def] },
+ { store, principal: { actor: agent, scopes: ["*"], mode: "edits" } },
+ {
+ policy: new PolicyEngine(toolPolicies(tools)),
+ audit: new AuditLog(sql),
+ confirmed: true,
+ },
+ );
+ await expect(call).rejects.toMatchObject({ code: "fields_unsupported" });
+ }
+ expect((await store.getSite(site.id))?.collections?.events?.fields).toBeUndefined();
+ });
+});
diff --git a/packages/ui/src/blocks/EntryFieldsForm.tsx b/packages/ui/src/blocks/EntryFieldsForm.tsx
index 91c9d1e3..9481c3e6 100644
--- a/packages/ui/src/blocks/EntryFieldsForm.tsx
+++ b/packages/ui/src/blocks/EntryFieldsForm.tsx
@@ -326,6 +326,24 @@ function ImageControl(c: Ctl) {
);
}
+/**
+ * RFC-0045: reference and repeater fields until their blocks ship (rollout step 6). Both types are
+ * refused on every write path until then, so this only shows a stored value read-only, as JSON,
+ * and never writes.
+ */
+function StoredValueControl({ label, description, error, value }: Ctl) {
+ return (
+
+ );
+}
+
const CONTROLS: Record ReactNode> = {
text: TextControl,
url: TextControl,
@@ -336,6 +354,8 @@ const CONTROLS: Record ReactNode> = {
datetime: DateControl,
image: ImageControl,
select: SelectControl,
+ reference: StoredValueControl,
+ repeater: StoredValueControl,
};
/** Placeholders only fit controls that show text; the rest say it under the control. */
diff --git a/packages/ui/test/entry-fields-form.test.tsx b/packages/ui/test/entry-fields-form.test.tsx
index 4c9d579e..6022c2b6 100644
--- a/packages/ui/test/entry-fields-form.test.tsx
+++ b/packages/ui/test/entry-fields-form.test.tsx
@@ -208,6 +208,43 @@ describe("EntryFieldsForm", () => {
"https://x.test",
);
});
+
+ it("shows RFC-0045 reference and repeater values read-only until their blocks ship", () => {
+ const onChange = vi.fn();
+ const defs: FieldDefinition[] = [
+ {
+ key: "host",
+ type: "reference",
+ labels: { en: { label: "Host" } },
+ localized: false,
+ collections: ["people"],
+ },
+ {
+ key: "agenda",
+ type: "repeater",
+ labels: { en: { label: "Agenda" } },
+ localized: false,
+ fields: [{ key: "talk", type: "text", labels: { en: { label: "Talk" } } }],
+ },
+ ];
+ render(
+ z}
+ labels={labels}
+ />,
+ );
+ const host = screen.getByRole("textbox", { name: "Host" });
+ expect(host).toBeDisabled();
+ expect(host).toHaveValue('"entry_01HZX8K3Q9P1"');
+ expect(screen.getByRole("textbox", { name: "Agenda" })).toHaveValue('[{"talk":"Hi"}]');
+ expect(onChange).not.toHaveBeenCalled();
+ });
});
describe("entry fields model", () => {