@caelo-cms/provisioning 0.10.72 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/agent-db.js +363 -80
  2. package/package.json +2 -2
package/dist/agent-db.js CHANGED
@@ -6335,6 +6335,175 @@ var ZodIssueCode = {
6335
6335
  };
6336
6336
  var ZodFirstPartyTypeKind;
6337
6337
  (function(ZodFirstPartyTypeKind) {})(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {}));
6338
+ // ../shared/src/lists/fields.ts
6339
+ var LIST_RESERVED_NAMES = new Set([
6340
+ "id",
6341
+ "node",
6342
+ "index",
6343
+ "depth",
6344
+ "first",
6345
+ "last",
6346
+ "items",
6347
+ "page",
6348
+ "recurse"
6349
+ ]);
6350
+ var listNameSchema = string2().regex(/^[a-z][a-z0-9_]{0,63}$/, "must be snake_case (lowercase letters, digits, underscores)");
6351
+ var fieldLabel = string2().min(1).max(128);
6352
+ var fieldExtSchema = record(string2().regex(/^[a-z0-9-]+$/), unknown());
6353
+ var base = {
6354
+ name: listNameSchema,
6355
+ label: fieldLabel,
6356
+ required: boolean2().optional(),
6357
+ default: unknown().optional(),
6358
+ ext: fieldExtSchema.optional()
6359
+ };
6360
+ var textField = object({
6361
+ ...base,
6362
+ type: literal("text"),
6363
+ maxLength: number2().int().min(1).max(2000).optional()
6364
+ }).strict();
6365
+ var richtextField = object({
6366
+ ...base,
6367
+ type: literal("richtext"),
6368
+ maxLength: number2().int().min(1).max(20000).optional()
6369
+ }).strict();
6370
+ var linkField = object({ ...base, type: literal("link") }).strict();
6371
+ var mediaField = object({ ...base, type: literal("media") }).strict();
6372
+ var selectField = object({
6373
+ ...base,
6374
+ type: literal("select"),
6375
+ options: array(listNameSchema).min(1).max(64)
6376
+ }).strict();
6377
+ var booleanField = object({ ...base, type: literal("boolean") }).strict();
6378
+ var numberField = object({
6379
+ ...base,
6380
+ type: literal("number"),
6381
+ min: number2().optional(),
6382
+ max: number2().optional()
6383
+ }).strict();
6384
+ var dateField = object({ ...base, type: literal("date") }).strict();
6385
+ var iconField = object({ ...base, type: literal("icon") }).strict();
6386
+ var moduleField = object({
6387
+ ...base,
6388
+ type: literal("module"),
6389
+ accepts: array(string2().regex(/^[a-z0-9-]+$/)).max(32).optional()
6390
+ }).strict();
6391
+ var termField = object({
6392
+ ...base,
6393
+ type: literal("term"),
6394
+ list: string2().regex(/^[a-z0-9-]+$/),
6395
+ multiple: boolean2().optional()
6396
+ }).strict();
6397
+ var fieldDefSchema = discriminatedUnion("type", [
6398
+ textField,
6399
+ richtextField,
6400
+ linkField,
6401
+ mediaField,
6402
+ selectField,
6403
+ booleanField,
6404
+ numberField,
6405
+ dateField,
6406
+ iconField,
6407
+ moduleField,
6408
+ termField
6409
+ ]);
6410
+ var linkValueSchema = object({
6411
+ href: string2().min(1).max(2000),
6412
+ target: _enum(["_self", "_blank"]).optional()
6413
+ }).strict();
6414
+ var embeddedModuleValueSchema = object({ moduleId: string2().uuid(), contentInstanceId: string2().uuid() }).strict();
6415
+
6416
+ // ../shared/src/lists/list-type.ts
6417
+ var LIST_MAX_DEPTH = 8;
6418
+ var LIST_MAX_ITEMS = 500;
6419
+ var LIST_QUERY_MAX_LIMIT = 50;
6420
+ var QUERY_KNOBS = [
6421
+ "page_type",
6422
+ "terms",
6423
+ "terms_mode",
6424
+ "parent",
6425
+ "exclude_current",
6426
+ "order",
6427
+ "limit"
6428
+ ];
6429
+ var SYSTEM_SOURCE_NAMES = [
6430
+ "breadcrumbs",
6431
+ "children",
6432
+ "siblings",
6433
+ "page_terms",
6434
+ "term_pages"
6435
+ ];
6436
+ var queryOrderSchema = union([
6437
+ _enum(["published_desc", "published_asc", "title_asc"]),
6438
+ string2().regex(/^prop_(?:desc|asc):[a-z][a-z0-9_]{0,63}$/)
6439
+ ]);
6440
+ var pageQuerySchema = object({
6441
+ page_type: string2().regex(/^[a-z0-9-]+$/).optional(),
6442
+ terms: array(string2().regex(/^caelo:term\/[0-9a-f-]{36}\/[0-9a-f-]{36}$/)).max(20).optional(),
6443
+ terms_mode: _enum(["any", "all"]).optional(),
6444
+ parent: string2().regex(/^caelo:page\/[0-9a-f-]{36}$/).optional(),
6445
+ exclude_current: boolean2().optional(),
6446
+ order: queryOrderSchema,
6447
+ limit: number2().int().min(1).max(LIST_QUERY_MAX_LIMIT)
6448
+ }).strict();
6449
+ var pageQueryPartialSchema = pageQuerySchema.partial();
6450
+ var sourceDefSchema = discriminatedUnion("kind", [
6451
+ object({
6452
+ kind: literal("query"),
6453
+ editable: array(_enum(QUERY_KNOBS)).max(QUERY_KNOBS.length),
6454
+ defaults: pageQueryPartialSchema
6455
+ }).strict(),
6456
+ object({
6457
+ kind: literal("system"),
6458
+ name: _enum(SYSTEM_SOURCE_NAMES, {
6459
+ error: () => `system sources are ${SYSTEM_SOURCE_NAMES.join(", ")} \u2014 a plugin's list source (consent_categories, language_links, \u2026) is {kind:"plugin", name:"<source>"}, and a module that only iterates it needs no field at all: {{#<source>}}\u2026{{/<source>}}.`
6460
+ })
6461
+ }).strict(),
6462
+ object({
6463
+ kind: literal("plugin"),
6464
+ name: string2().regex(/^[a-z][a-z0-9_]{0,63}$/)
6465
+ }).strict()
6466
+ ]);
6467
+ var childrenSchema = object({
6468
+ name: listNameSchema,
6469
+ allow: array(listNameSchema).min(1).max(32),
6470
+ max: number2().int().min(1).max(LIST_MAX_ITEMS).optional()
6471
+ }).strict();
6472
+ var nodeDefSchema = object({
6473
+ label: string2().min(1).max(128),
6474
+ fields: array(fieldDefSchema).max(32),
6475
+ children: childrenSchema.optional(),
6476
+ current_from: listNameSchema.optional(),
6477
+ source: sourceDefSchema.optional(),
6478
+ ext: fieldExtSchema.optional()
6479
+ }).strict();
6480
+ var LIST_TYPE_ROLES = ["navigation", "vocabulary", "content"];
6481
+ var listTypeDefSchema = object({
6482
+ slug: string2().regex(/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/),
6483
+ name: string2().min(1).max(200),
6484
+ purpose: string2().min(1).max(1000),
6485
+ role: _enum(LIST_TYPE_ROLES),
6486
+ root: object({
6487
+ allow: array(listNameSchema).min(1).max(32),
6488
+ max: number2().int().min(1).max(LIST_MAX_ITEMS).optional()
6489
+ }).strict(),
6490
+ nodes: record(listNameSchema, nodeDefSchema),
6491
+ max_depth: number2().int().min(1).max(LIST_MAX_DEPTH).optional()
6492
+ }).strict();
6493
+
6494
+ // ../shared/src/lists/items.ts
6495
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
6496
+ var listItemInputSchema = lazy(() => object({
6497
+ id: string2().regex(UUID_RE, "id must be a lowercase uuid").optional(),
6498
+ node: string2().min(1).max(64),
6499
+ values: record(string2(), unknown()).default({}),
6500
+ children: array(listItemInputSchema).optional(),
6501
+ query: record(string2(), unknown()).optional()
6502
+ }).strict());
6503
+
6504
+ // ../shared/src/lists/refs.ts
6505
+ var LIST_REF_RE = /^caelo:list\/([0-9a-f-]{36})$/;
6506
+
6338
6507
  // ../shared/src/content.ts
6339
6508
  var slugSchema = string2().regex(/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/, "slug must be lowercase letters, digits, or hyphens (1\u201364 chars, no leading/trailing hyphen)");
6340
6509
  var displayNameSchema = string2().min(1).max(128);
@@ -6343,12 +6512,39 @@ var MODULE_CSS_MAX = 128 * 1024;
6343
6512
  var MODULE_JS_MAX = 256 * 1024;
6344
6513
  var TEMPLATE_HTML_MAX = 512 * 1024;
6345
6514
  var TEMPLATE_CSS_MAX = 128 * 1024;
6346
- var moduleHtml = string2().max(MODULE_HTML_MAX, `html exceeds ${MODULE_HTML_MAX} bytes`);
6515
+ function jsonEncodedCodeMessage(field, value) {
6516
+ const t = value.trim();
6517
+ if (t.length < 2 || !t.startsWith('"') || !t.endsWith('"'))
6518
+ return null;
6519
+ try {
6520
+ if (typeof JSON.parse(t) !== "string")
6521
+ return null;
6522
+ } catch {
6523
+ return null;
6524
+ }
6525
+ return `${field} is JSON-encoded (wrapped in quotes with \\" escapes) \u2014 send the raw ${field} itself, not a JSON string of it.`;
6526
+ }
6527
+ function htmlEscapedMarkupMessage(field, value) {
6528
+ if (value.includes("<") || !/&lt;\/?[a-zA-Z]/.test(value))
6529
+ return null;
6530
+ return `${field} is HTML-escaped (&lt;\u2026&gt; instead of <\u2026>) \u2014 send the markup itself, unescaped.`;
6531
+ }
6532
+ var noCorruptMarkup = (field) => (value, ctx) => {
6533
+ const message = jsonEncodedCodeMessage(field, value) ?? htmlEscapedMarkupMessage(field, value);
6534
+ if (message)
6535
+ ctx.addIssue({ code: "custom", message });
6536
+ };
6537
+ var noJsonEncodedCode = (field) => (value, ctx) => {
6538
+ const message = jsonEncodedCodeMessage(field, value);
6539
+ if (message)
6540
+ ctx.addIssue({ code: "custom", message });
6541
+ };
6542
+ var moduleHtml = string2().max(MODULE_HTML_MAX, `html exceeds ${MODULE_HTML_MAX} bytes`).superRefine(noCorruptMarkup("html"));
6347
6543
  var CDATA_OPEN = "<![CDATA[";
6348
6544
  var noCdata = (value) => !value.includes(CDATA_OPEN);
6349
6545
  var cdataMessage = (field) => `${field} contains an XML CDATA marker (${CDATA_OPEN}) \u2014 module ${field} is stored and served ` + `verbatim, so CDATA wrappers reach the browser unparsed and break rendering. ` + `Remove the wrapper and resend plain ${field === "css" ? "CSS" : "JavaScript"}.`;
6350
- var moduleCss = string2().max(MODULE_CSS_MAX, `css exceeds ${MODULE_CSS_MAX} bytes`).refine(noCdata, cdataMessage("css"));
6351
- var moduleJs = string2().max(MODULE_JS_MAX, `js exceeds ${MODULE_JS_MAX} bytes`).refine(noCdata, cdataMessage("js"));
6546
+ var moduleCss = string2().max(MODULE_CSS_MAX, `css exceeds ${MODULE_CSS_MAX} bytes`).refine(noCdata, cdataMessage("css")).superRefine(noJsonEncodedCode("css"));
6547
+ var moduleJs = string2().max(MODULE_JS_MAX, `js exceeds ${MODULE_JS_MAX} bytes`).refine(noCdata, cdataMessage("js")).superRefine(noJsonEncodedCode("js"));
6352
6548
  var MODULE_FIELD_PRIMITIVE_KINDS = [
6353
6549
  "text",
6354
6550
  "richtext",
@@ -6358,7 +6554,7 @@ var MODULE_FIELD_PRIMITIVE_KINDS = [
6358
6554
  "boolean",
6359
6555
  "link"
6360
6556
  ];
6361
- var MODULE_FIELD_LIST_KINDS = ["text-list", "link-list"];
6557
+ var MODULE_FIELD_LIST_KINDS = ["list"];
6362
6558
  var MODULE_FIELD_NESTED_KINDS = ["module", "module-list"];
6363
6559
  var MODULE_FIELD_KINDS = [
6364
6560
  ...MODULE_FIELD_PRIMITIVE_KINDS,
@@ -6388,26 +6584,55 @@ var moduleFieldModuleListSchema = object({
6388
6584
  min: number2().int().nonnegative().optional(),
6389
6585
  max: number2().int().positive().max(256).optional()
6390
6586
  }).strict();
6391
- var moduleFieldTextListSchema = object({
6392
- name: moduleFieldName,
6393
- kind: literal("text-list"),
6394
- label: moduleFieldLabel,
6395
- min: number2().int().nonnegative().optional(),
6396
- max: number2().int().positive().max(256).optional(),
6397
- default: array(string2()).optional()
6398
- }).strict();
6399
- var moduleFieldLinkListSchema = object({
6587
+ var moduleFieldListSchema = object({
6400
6588
  name: moduleFieldName,
6401
- kind: literal("link-list"),
6589
+ kind: literal("list"),
6402
6590
  label: moduleFieldLabel,
6591
+ listType: slugSchema,
6592
+ bind: _enum(["local", "shared", "source"]).optional(),
6593
+ list: string2().regex(LIST_REF_RE, "list must be caelo:list/<id>").optional(),
6594
+ source: sourceDefSchema.optional(),
6403
6595
  min: number2().int().nonnegative().optional(),
6404
- max: number2().int().positive().max(256).optional(),
6405
- default: array(object({ label: string2(), href: string2() }).strict()).optional()
6596
+ max: number2().int().positive().max(LIST_MAX_ITEMS).optional(),
6597
+ default: union([
6598
+ array(listItemInputSchema),
6599
+ string2().regex(LIST_REF_RE, "a shared default must be caelo:list/<id>")
6600
+ ]).optional()
6406
6601
  }).strict();
6602
+ function listFieldIssues(f) {
6603
+ const bind = f.bind ?? "local";
6604
+ const issues = [];
6605
+ if (bind === "source" && f.source === undefined) {
6606
+ issues.push(`list field "${f.name}": bind "source" needs a source (query, system or plugin).`);
6607
+ }
6608
+ if (bind !== "source" && f.source !== undefined) {
6609
+ issues.push(`list field "${f.name}": source is only allowed with bind "source".`);
6610
+ }
6611
+ if (bind === "local" && f.list !== undefined) {
6612
+ issues.push(`list field "${f.name}": list is only allowed with bind "shared" \u2014 a local list stores its entries in the placement.`);
6613
+ }
6614
+ if (bind === "source" && f.list !== undefined && f.source?.kind !== "system") {
6615
+ issues.push(`list field "${f.name}": list on a source field names the vocabulary of a page_terms source only.`);
6616
+ }
6617
+ if (f.default !== undefined) {
6618
+ if (bind === "local" && typeof f.default === "string") {
6619
+ issues.push(`list field "${f.name}": a local default is an array of entries, not a list reference.`);
6620
+ }
6621
+ if (bind === "shared" && Array.isArray(f.default)) {
6622
+ issues.push(`list field "${f.name}": a shared default is caelo:list/<id> \u2014 create the list with upsert_lists_many first.`);
6623
+ }
6624
+ if (bind === "source") {
6625
+ issues.push(`list field "${f.name}": a source field has no default; its entries are generated.`);
6626
+ }
6627
+ }
6628
+ if (f.min !== undefined && f.max !== undefined && f.min > f.max) {
6629
+ issues.push(`list field "${f.name}": min is greater than max.`);
6630
+ }
6631
+ return issues;
6632
+ }
6407
6633
  var moduleFieldSchema = discriminatedUnion("kind", [
6408
6634
  moduleFieldPrimitiveSchema,
6409
- moduleFieldTextListSchema,
6410
- moduleFieldLinkListSchema,
6635
+ moduleFieldListSchema,
6411
6636
  moduleFieldModuleSchema,
6412
6637
  moduleFieldModuleListSchema
6413
6638
  ]);
@@ -6448,11 +6673,16 @@ var moduleFieldsArray = array(moduleFieldSchema).max(64, "modules may declare at
6448
6673
  return;
6449
6674
  }
6450
6675
  seen.add(f.name);
6676
+ if (f.kind === "list") {
6677
+ for (const message of listFieldIssues(f)) {
6678
+ ctx.addIssue({ code: ZodIssueCode.custom, message });
6679
+ }
6680
+ }
6451
6681
  }
6452
6682
  for (const run of findNumberedScalarFanoutRuns(arr)) {
6453
6683
  ctx.addIssue({
6454
6684
  code: ZodIssueCode.custom,
6455
- message: `numbered-scalar fanout: ${run.map((n) => `"${n}"`).join(", ")} repeat one role as ` + "numbered scalar fields \u2014 repeating content is ONE list field, never numbered scalars " + '(CLAUDE.md \xA71A). Replace the run with a single field: kind "text-list" (list of ' + 'strings, inner template iterates {{.}}), "link-list" (list of {label, href}), or ' + '"module-list" (rich per-item structure: mint a card sub-module and reference it per ' + "item \u2014 in build_page, author the card detached with `ref` and pass " + '[{"$ref": "<ref>"}, \u2026] as the field value). A two-field split like col1/col2 is fine; ' + `${NUMBERED_SCALAR_FANOUT_THRESHOLD}+ numbered siblings is a list.`
6685
+ message: `numbered-scalar fanout: ${run.map((n) => `"${n}"`).join(", ")} repeat one role as ` + "numbered scalar fields \u2014 repeating content is ONE list field, never numbered scalars " + '(CLAUDE.md \xA71A). Replace the run with a single field: kind "list" with a list type ' + '(listType "texts" for plain strings \u2014 the template iterates {{#f}}{{text}}{{/f}}; ' + '"links" for label + link \u2014 {{#f}}<a href="{{link}}">{{label}}</a>{{/f}}), or ' + '"module-list" (rich per-item structure: mint a card sub-module and reference it per ' + "item \u2014 in build_page, author the card detached with `ref` and pass " + '[{"$ref": "<ref>"}, \u2026] as the field value). A two-field split like col1/col2 is fine; ' + `${NUMBERED_SCALAR_FANOUT_THRESHOLD}+ numbered siblings is a list.`
6456
6686
  });
6457
6687
  }
6458
6688
  });
@@ -6481,21 +6711,12 @@ var moduleUpdateSchema = object({
6481
6711
  js: moduleJs.optional(),
6482
6712
  fields: moduleFieldsArray.optional()
6483
6713
  }).strict();
6484
- var TEMPLATE_KINDS = [
6485
- "home",
6486
- "landing",
6487
- "product",
6488
- "blog",
6489
- "doc",
6490
- "content",
6491
- "utility"
6492
- ];
6493
- var templateKindSchema = _enum(TEMPLATE_KINDS);
6714
+ var pageTypeSlugSchema = string2().regex(/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/, "page type slug: lowercase letters, digits, hyphens");
6494
6715
  var templateCreateSchema = object({
6495
6716
  slug: slugSchema,
6496
6717
  displayName: displayNameSchema,
6497
- kind: templateKindSchema.default("content"),
6498
- html: string2().max(TEMPLATE_HTML_MAX, `html exceeds ${TEMPLATE_HTML_MAX} bytes`),
6718
+ pageType: pageTypeSlugSchema.optional(),
6719
+ html: string2().max(TEMPLATE_HTML_MAX, `html exceeds ${TEMPLATE_HTML_MAX} bytes`).superRefine(noCorruptMarkup("html")),
6499
6720
  css: string2().max(TEMPLATE_CSS_MAX, `css exceeds ${TEMPLATE_CSS_MAX} bytes`).default(""),
6500
6721
  layoutId: string2().uuid().optional(),
6501
6722
  blocks: array(object({
@@ -6512,8 +6733,8 @@ var templateBlockSchema = object({
6512
6733
  var templateUpdateSchema = object({
6513
6734
  templateId: string2().uuid(),
6514
6735
  displayName: displayNameSchema.optional(),
6515
- kind: templateKindSchema.optional(),
6516
- html: string2().max(TEMPLATE_HTML_MAX).optional(),
6736
+ pageType: pageTypeSlugSchema.optional(),
6737
+ html: string2().max(TEMPLATE_HTML_MAX).superRefine(noCorruptMarkup("html")).optional(),
6517
6738
  css: string2().max(TEMPLATE_CSS_MAX).optional(),
6518
6739
  layoutId: string2().uuid().optional(),
6519
6740
  blocks: array(templateBlockSchema).optional()
@@ -6529,7 +6750,10 @@ var pageCreateSchema = object({
6529
6750
  title: string2().min(1).max(256),
6530
6751
  templateId: string2().uuid().optional(),
6531
6752
  status: pageStatusSchema.optional(),
6532
- parentPageId: string2().uuid().nullable().optional()
6753
+ parentPageId: string2().uuid().nullable().optional(),
6754
+ pageType: pageTypeSlugSchema.optional(),
6755
+ properties: record(string2(), unknown()).optional(),
6756
+ terms: array(string2().max(120)).max(100).optional()
6533
6757
  }).strict();
6534
6758
  var pageMoveSchema = object({
6535
6759
  pageId: string2().uuid(),
@@ -6548,7 +6772,10 @@ var pageUpdateSchema = object({
6548
6772
  slug: slugSchema.optional(),
6549
6773
  templateId: string2().uuid().optional(),
6550
6774
  status: pageStatusSchema.optional(),
6551
- redirectFromOld: _enum(["auto", "skip"]).optional()
6775
+ redirectFromOld: _enum(["auto", "skip"]).optional(),
6776
+ pageType: pageTypeSlugSchema.optional(),
6777
+ properties: record(string2(), unknown()).optional(),
6778
+ terms: array(string2().max(120)).max(100).optional()
6552
6779
  }).strict();
6553
6780
  var pageSetModulesSchema = object({
6554
6781
  pageId: string2().uuid(),
@@ -6592,6 +6819,11 @@ var forkPlacementContentSchema = object({
6592
6819
  position: number2().int().nonnegative()
6593
6820
  }).strict();
6594
6821
 
6822
+ // ../shared/src/lists/item-attr.ts
6823
+ var UUID = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
6824
+ var NAME = "[a-z][a-z0-9_]{0,63}";
6825
+ var LIST_ITEM_ATTR_RE = new RegExp(`^(?:${UUID}/${UUID}|instance/${UUID}/${NAME}/${UUID}|local/${NAME}/${UUID})$`);
6826
+
6595
6827
  // ../shared/src/plugin-preview.ts
6596
6828
  var identifier = string2().regex(/^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,127}$/);
6597
6829
  var pluginPreviewTargetSchema = object({
@@ -6683,6 +6915,42 @@ function unwrapCdataEnvelope(value) {
6683
6915
  return m[1];
6684
6916
  }
6685
6917
 
6918
+ // ../shared/src/source-text-transport.ts
6919
+ function decodeJsonEncodedSource(value) {
6920
+ if (typeof value !== "string")
6921
+ return value;
6922
+ const t = value.trim();
6923
+ if (t.length < 2 || !t.startsWith('"') || !t.endsWith('"'))
6924
+ return value;
6925
+ if (!/\\["ntr\\/u]/.test(t))
6926
+ return value;
6927
+ try {
6928
+ const decoded = JSON.parse(t);
6929
+ return typeof decoded === "string" ? decoded : value;
6930
+ } catch {
6931
+ return value;
6932
+ }
6933
+ }
6934
+ var ENTITIES = {
6935
+ "&lt;": "<",
6936
+ "&gt;": ">",
6937
+ "&quot;": '"',
6938
+ "&#34;": '"',
6939
+ "&#39;": "'",
6940
+ "&#x27;": "'",
6941
+ "&apos;": "'"
6942
+ };
6943
+ function decodeHtmlEscapedSource(value) {
6944
+ if (typeof value !== "string")
6945
+ return value;
6946
+ if (value.includes("<") || !/&lt;\/?[a-zA-Z]/.test(value))
6947
+ return value;
6948
+ return value.replace(/&lt;|&gt;|&quot;|&#34;|&#39;|&#x27;|&apos;/g, (e) => ENTITIES[e] ?? e).replace(/&amp;/g, "&");
6949
+ }
6950
+ function decodeSourceTextTransport(value) {
6951
+ return decodeHtmlEscapedSource(decodeJsonEncodedSource(unwrapCdataEnvelope(value)));
6952
+ }
6953
+
6686
6954
  // ../shared/src/ai-tools.ts
6687
6955
  function stripSurroundingQuotes(v) {
6688
6956
  if (typeof v !== "string")
@@ -6691,7 +6959,7 @@ function stripSurroundingQuotes(v) {
6691
6959
  return m ? m[2] : v;
6692
6960
  }
6693
6961
  function sourceTextArg(schema) {
6694
- return preprocess(unwrapCdataEnvelope, schema);
6962
+ return preprocess(decodeSourceTextTransport, schema);
6695
6963
  }
6696
6964
  var positionInputSchema = preprocess(stripSurroundingQuotes, union([
6697
6965
  _enum(["top", "bottom"]),
@@ -6708,7 +6976,9 @@ var editModuleToolInput = object({
6708
6976
  js: sourceTextArg(string2().max(MODULE_JS_MAX)).optional(),
6709
6977
  fields: array(moduleFieldSchema).max(64).optional(),
6710
6978
  bindThemeLiterals: boolean2().optional()
6711
- }).strict();
6979
+ }).strict().refine((v) => Object.keys(v).some((k) => k !== "moduleId" && k !== "bindThemeLiterals"), {
6980
+ message: "edit_module changes nothing: pass what to change (html, css, js, fields, displayName, description, kind or type). list_modules shows the modules; add_module creates a new one."
6981
+ });
6712
6982
  var setPageModuleContentToolInput = object({
6713
6983
  pageId: string2().uuid(),
6714
6984
  blockName: string2().min(1).max(64),
@@ -6771,10 +7041,11 @@ var addModuleToolInput = object({
6771
7041
  message: "Pass either `moduleId` (place an existing module from list_modules) " + "or `displayName` + `html` (mint a new module)."
6772
7042
  });
6773
7043
  }
6774
- if (input.fields !== undefined && input.fields.length > 0 && input.values === undefined && !input.fields.some((f) => ("default" in f) && f.default !== undefined)) {
7044
+ const carriesContent = (f) => ("default" in f) && f.default !== undefined || f.kind === "list" && (f.bind === "source" || f.bind === "shared" && f.list !== undefined);
7045
+ if (input.fields !== undefined && input.fields.length > 0 && input.values === undefined && !input.fields.some(carriesContent)) {
6775
7046
  ctx.addIssue({
6776
7047
  code: ZodIssueCode.custom,
6777
- message: "A minted module needs its initial content IN THIS CALL: pass `values` " + "({fieldName: value, \u2026}) for this placement, or give the fields `default`s. " + "Without either, the module renders empty placeholders until a second call."
7048
+ message: "A minted module needs its initial content IN THIS CALL: pass `values` " + "({fieldName: value, \u2026}) for this placement, or give the fields `default`s. " + 'A list field bound to a site list carries its content through `list` ({kind:"list", bind:"shared", list:"caelo:list/<id>"} \u2014 create the list first with upsert_lists_many). ' + "Without either, the module renders empty placeholders until a second call."
6778
7049
  });
6779
7050
  }
6780
7051
  });
@@ -6821,7 +7092,9 @@ var chatChipSchema = object({
6821
7092
  blockName: string2().min(1).max(80).optional(),
6822
7093
  position: number2().int().nonnegative().optional(),
6823
7094
  field: string2().min(1).max(200).optional(),
6824
- index: number2().int().nonnegative().optional()
7095
+ item: string2().uuid().optional(),
7096
+ key: string2().regex(/^[a-z][a-z0-9_]{0,63}$/).optional(),
7097
+ listItem: string2().max(200).regex(LIST_ITEM_ATTR_RE).optional()
6825
7098
  }).strict();
6826
7099
  var CHAT_MAX_CHIPS = 20;
6827
7100
  var chatSendMessageInput = object({
@@ -6853,7 +7126,9 @@ var chatPublishInput = object({
6853
7126
  "pageLayout",
6854
7127
  "pageModuleContent",
6855
7128
  "layout",
6856
- "structuredSet",
7129
+ "listType",
7130
+ "list",
7131
+ "pageType",
6857
7132
  "theme",
6858
7133
  "pluginRow"
6859
7134
  ]),
@@ -6885,6 +7160,7 @@ var createTemplateToolInput = object({
6885
7160
  html: sourceTextArg(string2().min(1).max(2000000)),
6886
7161
  css: sourceTextArg(string2().max(2000000)).default(""),
6887
7162
  layoutId: string2().uuid().optional(),
7163
+ pageType: pageTypeSlugSchema.optional(),
6888
7164
  blocks: array(object({
6889
7165
  name: string2().min(1).max(80),
6890
7166
  displayName: string2().min(1).max(200),
@@ -6896,12 +7172,6 @@ var removeModuleFromToolInput = object({
6896
7172
  targetRef: string2().min(1).max(500),
6897
7173
  moduleId: string2().uuid()
6898
7174
  }).strict();
6899
- var setStructuredSetToolInput = object({
6900
- kind: _enum(["nav-menu", "taxonomy", "tags", "link-list"]),
6901
- slug: slugInputSchema,
6902
- displayName: string2().min(1).max(200),
6903
- items: array(unknown())
6904
- }).strict();
6905
7175
  var setTemplateLayoutToolInput = object({
6906
7176
  templateId: string2().uuid(),
6907
7177
  layoutSlug: slugInputSchema
@@ -7263,7 +7533,7 @@ var buildPageContentSchema = discriminatedUnion("source", [
7263
7533
  var buildPageModuleSchema = object({
7264
7534
  blockName: slugSchema.optional(),
7265
7535
  position: positionInputSchema.optional(),
7266
- ref: string2().regex(/^[a-z][a-z0-9_-]{0,31}$/, "ref must be a short lowercase handle").optional(),
7536
+ ref: string2().regex(/^[A-Za-z][A-Za-z0-9_-]{0,31}$/, "ref must be a short handle: a letter, then letters, digits, _ or -").optional(),
7267
7537
  moduleId: union([
7268
7538
  string2().uuid('moduleId must be a UUID from list_modules \u2014 to re-place a module minted earlier in THIS call, pass {"$ref": "<its ref>"} instead'),
7269
7539
  object({ $ref: string2() }).strict()
@@ -7307,11 +7577,24 @@ var buildPageTargetSchema = object({
7307
7577
  name: string2().min(1).max(256).optional(),
7308
7578
  templateId: string2().uuid().optional(),
7309
7579
  status: pageStatusSchema.optional(),
7310
- parentPageId: string2().uuid().optional(),
7580
+ parentPageId: string2().uuid().nullable().optional(),
7581
+ pageType: pageCreateSchema.shape.pageType,
7582
+ properties: pageCreateSchema.shape.properties,
7583
+ terms: pageCreateSchema.shape.terms,
7311
7584
  importPageId: string2().uuid().optional(),
7312
7585
  metaDescription: string2().trim().min(1).max(SEO_DESCRIPTION_HARD_MAX).optional()
7313
7586
  }).strict().superRefine((page, ctx) => {
7314
- const createKeys = ["slug", "title", "name", "templateId", "status", "parentPageId"].filter((k) => page[k] !== undefined);
7587
+ const createKeys = [
7588
+ "slug",
7589
+ "title",
7590
+ "name",
7591
+ "templateId",
7592
+ "status",
7593
+ "parentPageId",
7594
+ "pageType",
7595
+ "properties",
7596
+ "terms"
7597
+ ].filter((k) => page[k] !== undefined);
7315
7598
  if (page.pageId !== undefined) {
7316
7599
  if (createKeys.length > 0) {
7317
7600
  ctx.addIssue({
@@ -7386,7 +7669,7 @@ var INTERACTIONS = {
7386
7669
  carousel: { css: CAROUSEL_CSS, js: CAROUSEL_JS }
7387
7670
  };
7388
7671
  var INTERACTION_IDS = Object.keys(INTERACTIONS);
7389
- var INTERACTION_MARKUP_CONTRACT = '**Vetted interactions - use these instead of writing JS for them.** Add `data-caelo-ui="<id>"` to the markup and Caelo ships accessible, dependency-free behaviour once per page; style it freely in the module CSS. ' + '`accordion` (FAQ, collapsible sections): `<div data-caelo-ui="accordion" data-single>` containing `<details id="..."><summary>Question</summary><div>Answer</div></details>` items - `data-single` keeps one open; a link to `#<details id>` opens it. ' + '`tabs`: `<div data-caelo-ui="tabs"><div role="tablist" aria-label="..."><button type="button" role="tab" id="t1" aria-controls="p1" aria-selected="true">...</button><button type="button" role="tab" id="t2" aria-controls="p2" aria-selected="false" tabindex="-1">...</button></div><div role="tabpanel" id="p1" aria-labelledby="t1">...</div><div role="tabpanel" id="p2" aria-labelledby="t2" hidden>...</div></div>` - ids must be unique on the page. ' + '`carousel` (slider, testimonials): `<section data-caelo-ui="carousel" aria-roledescription="carousel" aria-label="..."><div data-caelo-carousel-track tabindex="0"><div role="group" aria-roledescription="slide" aria-label="1 of 3">...</div>...</div><button type="button" data-caelo-carousel-prev aria-label="Previous slide"><span aria-hidden="true">&lsaquo;</span></button><button type="button" data-caelo-carousel-next aria-label="Next slide"><span aria-hidden="true">&rsaquo;</span></button></section>` (an icon button keeps its visible content aria-hidden; a text button needs no aria-label) - one slide per view by default; set the slides\' `flex-basis` in module CSS to show more. ' + "`mobile-nav` is applied by the built-in nav-menu renderer; never add it by hand. " + "Write module JS only for behaviour the kit does not cover.";
7672
+ var INTERACTION_MARKUP_CONTRACT = '**Vetted interactions - use these instead of writing JS for them.** Add `data-caelo-ui="<id>"` to the markup and Caelo ships accessible, dependency-free behaviour once per page; style it freely in the module CSS. ' + '`accordion` (FAQ, collapsible sections): `<div data-caelo-ui="accordion" data-single>` containing `<details id="..."><summary>Question</summary><div>Answer</div></details>` items - `data-single` keeps one open; a link to `#<details id>` opens it. ' + '`tabs`: `<div data-caelo-ui="tabs"><div role="tablist" aria-label="..."><button type="button" role="tab" id="t1" aria-controls="p1" aria-selected="true">...</button><button type="button" role="tab" id="t2" aria-controls="p2" aria-selected="false" tabindex="-1">...</button></div><div role="tabpanel" id="p1" aria-labelledby="t1">...</div><div role="tabpanel" id="p2" aria-labelledby="t2" hidden>...</div></div>` - ids must be unique on the page. ' + '`carousel` (slider, testimonials): `<section data-caelo-ui="carousel" aria-roledescription="carousel" aria-label="..."><div data-caelo-carousel-track tabindex="0"><div role="group" aria-roledescription="slide" aria-label="1 of 3">...</div>...</div><button type="button" data-caelo-carousel-prev aria-label="Previous slide"><span aria-hidden="true">&lsaquo;</span></button><button type="button" data-caelo-carousel-next aria-label="Next slide"><span aria-hidden="true">&rsaquo;</span></button></section>` (an icon button keeps its visible content aria-hidden; a text button needs no aria-label) - one slide per view by default; set the slides\' `flex-basis` in module CSS to show more. ' + '`mobile-nav` (menu collapsing behind a toggle on small screens): `<nav class="caelo-nav-menu" data-caelo-ui="mobile-nav" data-nav-open="false"><button type="button" class="caelo-nav-toggle" aria-expanded="false" aria-label="Menu"><span class="caelo-nav-bar"></span><span class="caelo-nav-bar"></span><span class="caelo-nav-bar"></span></button><ul>{{#menu}}<li>\u2026</li>{{/menu}}</ul></nav>` around the module\'s menu `list` field. ' + "Write module JS only for behaviour the kit does not cover.";
7390
7673
 
7391
7674
  // ../shared/src/media.ts
7392
7675
  var MEDIA_ALLOWED_MIMES = [
@@ -8297,7 +8580,7 @@ var languageTagSchema = string2().max(35).regex(/^[A-Za-z]{2,8}(-[A-Za-z0-9]{1,8
8297
8580
  var HTML_WS = new Set(["\t", `
8298
8581
  `, "\f", "\r", " "]);
8299
8582
  // ../shared/src/edit-markers.ts
8300
- var EDITABLE_KINDS = new Set(["text", "text-list", "link-list", "url", "image"]);
8583
+ var EDITABLE_KINDS = new Set(["text", "url", "image", "list"]);
8301
8584
  var MARKABLE_ATTRIBUTES = new Set(["href", "src", "alt"]);
8302
8585
  var RAW_TEXT_ELEMENTS2 = new Set(["script", "style", "title", "textarea"]);
8303
8586
  // ../shared/src/font-usage.ts
@@ -8456,6 +8739,31 @@ var VARIANT_RE = LINK_REF_PART_PATTERNS.variant;
8456
8739
  var SITE_SERVED_FILES = new Set(["/robots.txt", "/sitemap.xml"]);
8457
8740
  var URL_KINDS = new Set(["url", "link", "image"]);
8458
8741
  var NESTED_KINDS = new Set(["module", "module-list"]);
8742
+ // ../shared/src/lists/address.ts
8743
+ var SEG = "[a-z0-9_-]+";
8744
+ var ADDRESS_RE = new RegExp(`^(?:list/(${SEG})/item/(${SEG})/(${SEG})` + `|instance/(${SEG})/(${SEG})/item/(${SEG})/(${SEG})` + `|page/(${SEG})/props/(${SEG})` + `|list_type/(${SEG})/node/(${SEG})/(${SEG}))$`);
8745
+ // ../shared/src/lists/page-type.ts
8746
+ var pageTypeDefSchema = object({
8747
+ slug: string2().regex(/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/),
8748
+ name: string2().min(1).max(200),
8749
+ purpose: string2().min(1).max(1000),
8750
+ templates: array(string2().uuid()).min(1).max(64),
8751
+ defaultTemplate: string2().uuid(),
8752
+ properties: array(fieldDefSchema).max(32).default([]),
8753
+ vocabularies: array(object({
8754
+ list: string2().uuid(),
8755
+ cardinality: _enum(["one", "many"]),
8756
+ required: boolean2().optional()
8757
+ }).strict()).max(16).default([])
8758
+ }).strict();
8759
+ // ../shared/src/lists/resolve.ts
8760
+ var SEG2 = "[a-z0-9_-]+";
8761
+ var LIST_VALUE_RE = new RegExp(`^list/(${SEG2})/item/(${SEG2})/(${SEG2})$`);
8762
+ var INSTANCE_VALUE_RE = new RegExp(`^instance/(${SEG2})/(${SEG2})/item/(${SEG2})/(${SEG2})$`);
8763
+ var PAGE_PROP_RE = new RegExp(`^page/(${SEG2})/props/(${SEG2})$`);
8764
+ var LIST_HIDDEN_RE = new RegExp(`^list/(${SEG2})/item/(${SEG2})$`);
8765
+ var INSTANCE_HIDDEN_RE = new RegExp(`^instance/(${SEG2})/(${SEG2})/item/(${SEG2})$`);
8766
+ var STRUCTURAL_TYPES = new Set(["module", "term"]);
8459
8767
  // ../shared/src/markup-structure.ts
8460
8768
  var LEAF_TAGS = new Set(["svg", "math", "script", "style", "noscript", "iframe"]);
8461
8769
  // ../shared/src/page-log.ts
@@ -8588,31 +8896,6 @@ var PINNED_FONT_SLICE_FILE_PATTERN = "[0-9a-f]{64}\\.[0-9a-f]{12}\\.[a-z]+(?:-[a
8588
8896
  var PINNED_FONT_SLICE_FILE_RE = new RegExp(`^${PINNED_FONT_SLICE_FILE_PATTERN}$`);
8589
8897
  var CONTENT_HASHED_PATH_PATTERN = `^/(?:_app/immutable/.+|_assets/fonts/[^/]+/[0-9a-f]{16}\\.woff2|_assets/fonts/pinned/${PINNED_FONT_SLICE_FILE_PATTERN}|_assets/[a-z0-9][a-z0-9-]*(?:/[a-z][a-z0-9-]*)?\\.[0-9a-f]{16}\\.[a-z0-9]{2,5}|_caelo/plugin/[^/]+/[^/]+\\.[0-9a-f]{12}\\.(?:js|css))$`;
8590
8898
  var CONTENT_HASHED_PATH_RE = new RegExp(CONTENT_HASHED_PATH_PATTERN);
8591
- // ../shared/src/structured-sets.ts
8592
- var navMenuItem = lazy(() => object({
8593
- label: string2().min(1).max(120),
8594
- href: string2().min(1).max(500),
8595
- target: _enum(["_self", "_blank"]).optional(),
8596
- children: array(navMenuItem).optional(),
8597
- adSlotId: string2().min(1).max(100).optional()
8598
- }).strict());
8599
- var taxonomyItem = object({
8600
- slug: string2().min(1).max(120),
8601
- displayName: string2().min(1).max(200),
8602
- parentSlug: string2().min(1).max(120).optional(),
8603
- description: string2().max(2000).optional()
8604
- }).strict();
8605
- var tagItem = object({
8606
- slug: string2().min(1).max(120).regex(/^[a-z0-9-]+$/, "lowercase letters/digits/hyphens"),
8607
- displayName: string2().min(1).max(200),
8608
- color: string2().regex(/^#[0-9a-fA-F]{3,8}$/, "hex color").optional()
8609
- }).strict();
8610
- var linkListItem = object({
8611
- label: string2().min(1).max(200),
8612
- href: string2().min(1).max(500),
8613
- description: string2().max(500).optional()
8614
- }).strict();
8615
- var structuredSetKind = _enum(["nav-menu", "taxonomy", "tags", "link-list"]);
8616
8899
  // ../shared/src/subagents.ts
8617
8900
  var expectedReturnShape = _enum(["verdict", "tree", "freeform", "rebuild"]);
8618
8901
  var EXPECTED_RETURN_SHAPES = expectedReturnShape.options;
@@ -18008,7 +18291,7 @@ async function recordAudit(tx, opts) {
18008
18291
  var SYSTEM_ACTOR_ID = "00000000-0000-0000-0000-00000000ffff";
18009
18292
 
18010
18293
  // ../query-api/src/branch-access.ts
18011
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
18294
+ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
18012
18295
  function namedBranches(ctx, input) {
18013
18296
  const out = new Set;
18014
18297
  if (ctx.chatBranchId)
@@ -18029,11 +18312,11 @@ function branchNotFound(operation, chatBranchId) {
18029
18312
  };
18030
18313
  }
18031
18314
  function firstMalformedBranch(branches) {
18032
- return branches.find((b) => !UUID_RE.test(b)) ?? null;
18315
+ return branches.find((b) => !UUID_RE2.test(b)) ?? null;
18033
18316
  }
18034
18317
  async function firstInvisibleBranch(tx, branches) {
18035
18318
  for (const branch of branches) {
18036
- if (!UUID_RE.test(branch))
18319
+ if (!UUID_RE2.test(branch))
18037
18320
  return branch;
18038
18321
  const rows = await tx.execute(sql`SELECT caelo_branch_visible(${branch}::uuid) AS visible`);
18039
18322
  if (rows[0]?.visible !== true)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caelo-cms/provisioning",
3
- "version": "0.10.72",
3
+ "version": "0.11.0",
4
4
  "private": false,
5
5
  "license": "MPL-2.0",
6
6
  "description": "Pulumi-driven provisioning for Caelo CMS — `bunx @caelo-cms/provisioning --provider <self-hosted|gcp|aws|azure>` brings up a TLS-served install with Postgres + storage + secrets manager + the admin + the API gateway.",
@@ -40,7 +40,7 @@
40
40
  "prepublishOnly": "tsc -b --force && bun build ../platform-updater/src/agent-db.ts --target=bun --outfile=dist/agent-db.js && tsc --project stacks/gcp/tsconfig.json && tsc --project stacks/gcp-firebase/tsconfig.json"
41
41
  },
42
42
  "dependencies": {
43
- "@caelo-cms/shared": "^0.10.72",
43
+ "@caelo-cms/shared": "^0.11.0",
44
44
  "@clack/prompts": "1.8.1",
45
45
  "@pulumi/command": "^1",
46
46
  "@pulumi/gcp": "^10",