@aotter/mantle 0.2.0-alpha.7 → 0.2.0-alpha.8

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 (108) hide show
  1. package/README.md +10 -5
  2. package/dist/auth/caller.js +2 -1
  3. package/dist/auth/identity.d.ts +2 -2
  4. package/dist/auth/oauth.d.ts +33 -1
  5. package/dist/auth/oauth.js +121 -28
  6. package/dist/auth/routes.js +3 -2
  7. package/dist/build/compiler.d.ts +44 -122
  8. package/dist/build/compiler.js +202 -277
  9. package/dist/build/spec/domain/model/ManifestGrammar.d.ts +6 -5
  10. package/dist/build/spec/domain/model/SqlIr.d.ts +2 -0
  11. package/dist/build/spec/domain/service/PlanFingerprint.d.ts +2 -0
  12. package/dist/build/spec/kernel/diagnostic.d.ts +1 -1
  13. package/dist/domain/describe/{chunk-ZIQVLA7X.js → chunk-ZVXBGP7Y.js} +69 -8
  14. package/dist/domain/describe/index.js +1 -1
  15. package/dist/domain/describe/target-hash.js +1 -1
  16. package/dist/domain/describe/targets.d.ts +1 -1
  17. package/dist/domain/generated-tools.js +2 -0
  18. package/dist/domain/paging.js +41 -15
  19. package/dist/infrastructure/bundle.js +105 -15
  20. package/dist/infrastructure/cli.js +11 -15
  21. package/dist/infrastructure/generate.d.ts +1 -3
  22. package/dist/infrastructure/generate.js +2 -2
  23. package/dist/infrastructure/generateAuth.js +6 -0
  24. package/dist/infrastructure/inputs.js +8 -3
  25. package/dist/infrastructure/wrangler.js +35 -12
  26. package/dist/kernel/drain.d.ts +5 -0
  27. package/dist/kernel/drain.js +35 -0
  28. package/dist/kernel/driver/postgres.js +3 -0
  29. package/dist/kernel/host-caller.d.ts +1 -0
  30. package/dist/kernel/host-caller.js +1 -0
  31. package/dist/kernel/mcp-apps.d.ts +29 -9
  32. package/dist/kernel/mcp-apps.js +2 -2
  33. package/dist/kernel/mcp-handler.js +100 -17
  34. package/dist/kernel/mcp.d.ts +7 -2
  35. package/dist/kernel/mcp.js +29 -5
  36. package/dist/kernel/rest-router.js +3 -2
  37. package/dist/kernel/site.d.ts +1 -3
  38. package/dist/kernel/site.js +2 -2
  39. package/dist/kernel/web.d.ts +3 -1
  40. package/dist/kernel/web.js +3 -0
  41. package/dist/usecase/adminBindings.js +2 -3
  42. package/dist/usecase/adminClient.js +87 -0
  43. package/dist/usecase/adminDescriptors.js +19 -65
  44. package/dist/usecase/generate.js +5 -4
  45. package/dist/usecase/mcp-media.js +2 -2
  46. package/dist/usecase/mcp.js +16 -12
  47. package/dist/usecase/paging.js +2 -3
  48. package/dist/usecase/publishing.js +2 -2
  49. package/dist/usecase/tool-version.js +9 -0
  50. package/dist/usecase/translations.js +1 -1
  51. package/dist/usecase/validation.js +120 -19
  52. package/dist/usecase/web.js +3 -2
  53. package/docs/examples/README.md +1 -1
  54. package/docs/examples/app-shell/README.md +39 -1
  55. package/docs/examples/app-shell/package.json +14 -6
  56. package/docs/examples/app-shell/smoke-kit.mjs +54 -0
  57. package/docs/examples/app-shell/src/auth.ts +4 -2
  58. package/docs/examples/app-shell/src/service.ts +2 -2
  59. package/docs/examples/app-shell/tsconfig.json +4 -1
  60. package/docs/examples/app-shell/worker-configuration.d.ts +2 -2
  61. package/docs/examples/app-shell/wrangler.jsonc +5 -4
  62. package/docs/examples/generated-web/README.md +6 -2
  63. package/docs/examples/generated-web/wrangler.jsonc +1 -0
  64. package/docs/examples/purchasing/README.md +1 -1
  65. package/docs/examples/purchasing/manifests/purchasing.yaml +1 -1
  66. package/docs/examples/purchasing/smoke.mjs +10 -4
  67. package/docs/examples/purchasing/src/admin.tsx +13 -0
  68. package/docs/examples/purchasing/wrangler.jsonc +5 -4
  69. package/docs/examples/recipes/wrangler.jsonc +5 -4
  70. package/docs/handbook/cloudflare/authentication.md +22 -2
  71. package/docs/handbook/cloudflare/deploy-and-operate.md +21 -0
  72. package/docs/handbook/cloudflare/service-entry.md +55 -28
  73. package/docs/handbook/concepts/authorization.md +1 -1
  74. package/docs/handbook/concepts/mcp-and-agents.md +19 -0
  75. package/docs/handbook/examples/hub.md +1 -1
  76. package/docs/handbook/guides/admin-ui.md +107 -1
  77. package/docs/handbook/guides/agent-setup.md +27 -9
  78. package/docs/handbook/guides/mcp-apps.md +445 -6
  79. package/docs/handbook/guides/native-admin.md +296 -51
  80. package/docs/handbook/guides/oauth.md +61 -5
  81. package/docs/handbook/guides/public-pages.md +2 -2
  82. package/docs/handbook/guides/size-and-startup.md +1 -1
  83. package/docs/handbook/navigation.json +0 -4
  84. package/docs/handbook/reference/admin-descriptors.md +0 -35
  85. package/docs/handbook/reference/procedure.md +5 -1
  86. package/docs/handbook/reference/schema.md +13 -3
  87. package/docs/handbook/reference/surface.md +123 -13
  88. package/docs/handbook/reference/trigger.md +34 -1
  89. package/docs/handbook/reference/view.md +1 -1
  90. package/docs/handbook/releases/index.md +74 -2
  91. package/docs/handbook/start/overview.md +1 -1
  92. package/docs/handbook/start/project-and-cli.md +8 -8
  93. package/docs/handbook/start/quickstart-worker.md +1 -1
  94. package/docs/skills/develop/SKILL.md +4 -3
  95. package/docs/skills/integrate/SKILL.md +1 -1
  96. package/docs/skills/media-gc/SKILL.md +7 -5
  97. package/docs/skills/provision/SKILL.md +4 -1
  98. package/docs/skills/update/SKILL.md +1 -1
  99. package/docs/upgrade-0.1-to-0.2.md +6 -6
  100. package/package.json +2 -1
  101. package/skills/README.md +11 -16
  102. package/skills/mantle/SKILL.md +4 -4
  103. package/skills/mantle/references/local-testing.md +4 -0
  104. package/skills/mantle/references/web.md +1 -1
  105. package/skills/mantle/references/website.md +24 -22
  106. package/docs/examples/app-shell/src/admin.ts +0 -10
  107. package/docs/examples/purchasing/src/admin.ts +0 -12
  108. package/docs/handbook/guides/admin-extensions.md +0 -20
@@ -115,6 +115,7 @@ function canonical(v) {
115
115
  return `{${Object.keys(v).sort().filter((k) => v[k] !== void 0).map((k) => `${JSON.stringify(k)}:${canonical(v[k])}`).join(",")}}`;
116
116
  return JSON.stringify(v) ?? "null";
117
117
  }
118
+ var fnv = (s) => [...s].reduce((h, c) => Math.imul(h ^ c.codePointAt(0), 16777619) >>> 0, 2166136261).toString(16).padStart(8, "0");
118
119
  async function planFingerprint(plan) {
119
120
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonical(plan)));
120
121
  return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
@@ -122,8 +123,6 @@ async function planFingerprint(plan) {
122
123
 
123
124
  // src/build/spec/domain/model/ManifestGrammar.ts
124
125
  var API_VERSION = "cms.mantle.aotter.net/v2";
125
- var UI_EXTENSION_REF = /^([a-z][a-z0-9-]{0,62})\/([a-z][a-z0-9-]{0,62})$/;
126
- var isUiExtensionRef = (value2) => typeof value2 === "string" && UI_EXTENSION_REF.test(value2);
127
126
  function isMediaMcpHint(value2) {
128
127
  return value2 === "media" || value2 === "media-image" || value2 === "media-video" || value2 === "media-file";
129
128
  }
@@ -180,6 +179,7 @@ var RESERVED_ENTRY_COLUMNS = [
180
179
  "authorId"
181
180
  ];
182
181
  var EXPECTED_VERSION_PROPERTY = "expectedVersion";
182
+ var INPUT_RULE_KEYWORDS = ["dependentRequired", "anyOf", "if", "then", "else"];
183
183
  var RESERVED_PROCEDURE_INPUT_NAMES = [EXPECTED_VERSION_PROPERTY];
184
184
 
185
185
  // src/build/spec/domain/service/SqlRefusal.ts
@@ -280,6 +280,7 @@ function storageColumns(s) {
280
280
  return cols;
281
281
  }
282
282
  var NATIVE_NOT_NULL = /* @__PURE__ */ new Set(["id", "version", "created_at", "updated_at", "status"]);
283
+ var NATIVE_COLUMNS = { id: "id", status: "status", version: "version", createdAt: "created_at", updatedAt: "updated_at", authorId: "author_id" };
283
284
  function notNullColumns(s) {
284
285
  const out = /* @__PURE__ */ new Set();
285
286
  for (const u of s.userScopes ?? []) out.add(u.column);
@@ -490,8 +491,8 @@ function collectSourceSpans(root, lineCounter) {
490
491
  }
491
492
  function sourceLocationForNode(sourceId, documentIndex, path, doc, lineCounter) {
492
493
  const parts = path === "/" ? [] : path.slice(1).split("/").map((part) => {
493
- const decoded = part.replace(/~1/g, "/").replace(/~0/g, "~");
494
- return /^\d+$/.test(decoded) ? Number(decoded) : decoded;
494
+ const decoded2 = part.replace(/~1/g, "/").replace(/~0/g, "~");
495
+ return /^\d+$/.test(decoded2) ? Number(decoded2) : decoded2;
495
496
  });
496
497
  let range;
497
498
  for (let length = parts.length; length >= 0 && !range; length--) {
@@ -520,34 +521,23 @@ function sourceSpan(lineCounter, range) {
520
521
  }
521
522
 
522
523
  // src/build/spec/domain/model/AdminNav.ts
523
- var BUILT_IN_NAV_GROUPS = { content: 10, operations: 20, reports: 30, more: 90 };
524
- var NAV_PLACEMENT_KEYS = ["group", "order", "hidden"];
524
+ var BUILT_IN_NAV_GROUPS = /* @__PURE__ */ new Set(["content", "operations", "reports", "more"]);
525
525
  var ID = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
526
- function navPlacementProblem(key, value2) {
527
- if (key === "group") return typeof value2 === "string" && ID.test(value2) ? null : "a kebab-case group id";
528
- if (key === "order") return Number.isInteger(value2) ? null : "an integer";
529
- if (key === "hidden") return typeof value2 === "boolean" ? null : "a boolean";
526
+ var PLACEMENT = {
527
+ group: [(v) => typeof v === "string" && ID.test(v), "a kebab-case group id"],
528
+ order: [Number.isInteger, "an integer"],
529
+ hidden: [(v) => typeof v === "boolean", "a boolean"]
530
+ };
531
+ function navPlacementProblems(nav, own = []) {
532
+ const keys = [...own, ...Object.keys(PLACEMENT)];
533
+ for (const [key, value2] of Object.entries(nav)) {
534
+ if (own.includes(key)) continue;
535
+ const rule = Object.hasOwn(PLACEMENT, key) ? PLACEMENT[key] : null;
536
+ if (!rule) return [key, `${keys.slice(0, -1).join(", ")} or ${keys.at(-1)}`];
537
+ if (!rule[0](value2)) return [key, rule[1]];
538
+ }
530
539
  return null;
531
540
  }
532
- var isText = (v) => typeof v === "string" && v.length > 0 || !!v && typeof v === "object" && !Array.isArray(v) && Object.keys(v).length > 0 && Object.values(v).every((x) => typeof x === "string" && x.length > 0);
533
- function navGroupProblems(groups) {
534
- if (groups === void 0) return [];
535
- if (!Array.isArray(groups)) return [["admin/navGroups", "navGroups must be an array."]];
536
- const seen = /* @__PURE__ */ new Set(), problems = [];
537
- groups.forEach((g, i) => {
538
- const path = `admin/navGroups/${i}`;
539
- if (!g || typeof g !== "object" || Array.isArray(g)) return problems.push([path, "A nav group is an object { id, title, order? }."]);
540
- const { id, title, order, ...extra } = g;
541
- if (Object.keys(extra).length) problems.push([`${path}/${Object.keys(extra)[0]}`, "A nav group has only id, title and order."]);
542
- if (typeof id !== "string" || !ID.test(id)) problems.push([`${path}/id`, "A nav group id is kebab-case."]);
543
- else if (Object.hasOwn(BUILT_IN_NAV_GROUPS, id) || id === "settings") problems.push([`${path}/id`, `'${id}' is a built-in group; place items in it without declaring it.`]);
544
- else if (seen.has(id)) problems.push([`${path}/id`, `Duplicate nav group id '${id}'.`]);
545
- else seen.add(id);
546
- if (!isText(title)) problems.push([`${path}/title`, "A nav group title is text or a { locale: text } map."]);
547
- if (order !== void 0 && !Number.isInteger(order)) problems.push([`${path}/order`, "A nav group order is an integer."]);
548
- });
549
- return problems;
550
- }
551
541
 
552
542
  // src/build/spec/domain/service/SchemaIndexChecker.ts
553
543
  var SAFE_INDEX_NAME = /^[A-Za-z][A-Za-z0-9_.-]*$/;
@@ -820,10 +810,8 @@ function checkViewAdminUi(view) {
820
810
  const nav = uiSchema["nav"];
821
811
  if (nav !== void 0) {
822
812
  if (!nav || typeof nav !== "object" || Array.isArray(nav)) return { list: EMPTY_VIEW_LIST, problems: [problem("/spec/uiSchema/nav", nav, "an object", "View.spec.uiSchema.nav must be an object.")] };
823
- for (const [key, value2] of Object.entries(nav)) {
824
- const expected = NAV_PLACEMENT_KEYS.includes(key) ? navPlacementProblem(key, value2) : "group, order or hidden";
825
- if (expected) return { list: EMPTY_VIEW_LIST, problems: [problem(`/spec/uiSchema/nav/${key}`, value2, expected, `View.spec.uiSchema.nav.${key} must be ${expected}.`)] };
826
- }
813
+ const bad = navPlacementProblems(nav);
814
+ if (bad) return { list: EMPTY_VIEW_LIST, problems: [problem(`/spec/uiSchema/nav/${bad[0]}`, nav[bad[0]], bad[1], `View.spec.uiSchema.nav.${bad[0]} must be ${bad[1]}.`)] };
827
815
  }
828
816
  const list = uiSchema["list"];
829
817
  if (list === void 0) return { list: EMPTY_VIEW_LIST, problems: [] };
@@ -836,13 +824,13 @@ function checkViewAdminUi(view) {
836
824
  )] };
837
825
  }
838
826
  const config = list;
839
- const allowed = /* @__PURE__ */ new Set(["columns", "searchFields", "filterFields", "cells"]);
827
+ const allowed = /* @__PURE__ */ new Set(["columns", "searchFields", "filterFields"]);
840
828
  const unknown = Object.keys(config).find((key) => !allowed.has(key));
841
829
  if (unknown) {
842
830
  return { list: EMPTY_VIEW_LIST, problems: [problem(
843
831
  `/spec/uiSchema/list/${unknown}`,
844
832
  config[unknown],
845
- "columns, searchFields, filterFields, or cells",
833
+ "columns, searchFields, or filterFields",
846
834
  `View.spec.uiSchema.list.${unknown} is not supported.`
847
835
  )] };
848
836
  }
@@ -874,16 +862,13 @@ function checkViewAdminUi(view) {
874
862
  }
875
863
  normalized[key] = raw;
876
864
  }
877
- const cellProblem = checkCells(config["cells"], (field) => field.length > 0, "View", "a View output name");
878
- if (cellProblem) return { list: EMPTY_VIEW_LIST, problems: [cellProblem] };
879
865
  return { list: normalized, problems: [] };
880
866
  }
881
867
 
882
868
  // src/build/spec/domain/service/SchemaAdminUiChecker.ts
883
869
  var EMPTY_LIST = { primaryField: null, columns: [] };
884
- var SCHEMA_UI_ROOTS = /* @__PURE__ */ new Set(["fields", "list", "nav", "panels"]);
885
- var SCHEMA_LIST_KEYS = /* @__PURE__ */ new Set(["filterField", "primaryField", "columns", "cells"]);
886
- var SCHEMA_NAV_KEYS = /* @__PURE__ */ new Set(["standalone", "parentField", ...NAV_PLACEMENT_KEYS]);
870
+ var SCHEMA_UI_ROOTS = /* @__PURE__ */ new Set(["fields", "list", "nav"]);
871
+ var SCHEMA_LIST_KEYS = /* @__PURE__ */ new Set(["filterField", "primaryField", "columns"]);
887
872
  var SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "number", "integer", "boolean"]);
888
873
  function checkSchemaAdminUi(schema) {
889
874
  const formProblem = checkFormUiSchema(schema.spec.schema, schema.spec.uiSchema, "Schema")[0];
@@ -896,7 +881,7 @@ function checkSchemaAdminUi(schema) {
896
881
  return invalid(problem2(
897
882
  `/spec/uiSchema/${unknownRoot}`,
898
883
  roots[unknownRoot],
899
- "fields, list, nav, or panels",
884
+ "fields, list, or nav",
900
885
  `Schema.spec.uiSchema.${unknownRoot} is not supported.`
901
886
  ));
902
887
  }
@@ -916,7 +901,7 @@ function checkSchemaAdminUi(schema) {
916
901
  return invalid(problem2(
917
902
  `/spec/uiSchema/list/${unknownList}`,
918
903
  config[unknownList],
919
- "filterField, primaryField, columns, or cells",
904
+ "filterField, primaryField, or columns",
920
905
  `Schema.spec.uiSchema.list.${unknownList} is not supported.`
921
906
  ));
922
907
  }
@@ -981,10 +966,6 @@ function checkSchemaAdminUi(schema) {
981
966
  ));
982
967
  }
983
968
  }
984
- const cellProblem = checkCells(config["cells"], (field) => NATIVE_LIST_FIELDS.has(field) || !!schema.spec.schema.properties?.[field], "Schema", "a top-level key in spec.schema.properties or a native entry column");
985
- if (cellProblem) return invalid(cellProblem);
986
- const panelProblem = checkPanels(roots["panels"]);
987
- if (panelProblem) return invalid(panelProblem);
988
969
  const navResult = checkNav(schema, roots["nav"]);
989
970
  if (navResult.problem) return invalid(navResult.problem);
990
971
  return {
@@ -1062,41 +1043,21 @@ function checkFormUiSchema(schema, uiSchema, owner) {
1062
1043
  )];
1063
1044
  }
1064
1045
  const config2 = rawConfig;
1065
- const unknownKey = Object.keys(config2).find((key) => key !== "widget" && key !== "options");
1046
+ const unknownKey = Object.keys(config2).find((key) => key !== "widget");
1066
1047
  if (unknownKey) {
1067
1048
  return [problem2(
1068
1049
  `/spec/uiSchema/fields/${field}/${unknownKey}`,
1069
1050
  config2[unknownKey],
1070
- "widget and, for an extension widget, options",
1051
+ "widget",
1071
1052
  `${owner}.spec.uiSchema.fields.${field}.${unknownKey} is not supported.`
1072
1053
  )];
1073
1054
  }
1074
- if (isUiExtensionRef(config2["widget"])) {
1075
- const options = config2["options"];
1076
- if (options !== void 0 && (!options || typeof options !== "object" || Array.isArray(options))) {
1077
- return [problem2(
1078
- `/spec/uiSchema/fields/${field}/options`,
1079
- options,
1080
- "an object",
1081
- `${owner}.spec.uiSchema.fields.${field}.options must be an object.`
1082
- )];
1083
- }
1084
- continue;
1085
- }
1086
1055
  if (config2["widget"] !== "textarea") {
1087
1056
  return [problem2(
1088
1057
  `/spec/uiSchema/fields/${field}/widget`,
1089
1058
  config2["widget"],
1090
- '"textarea" or an Admin extension contribution <extension>/<contribution>',
1091
- `${owner}.spec.uiSchema.fields.${field}.widget must be 'textarea' or name an Admin extension contribution.`
1092
- )];
1093
- }
1094
- if (config2["options"] !== void 0) {
1095
- return [problem2(
1096
- `/spec/uiSchema/fields/${field}/options`,
1097
- config2["options"],
1098
- "options only with an extension widget",
1099
- `${owner}.spec.uiSchema.fields.${field}.options needs an extension widget.`
1059
+ '"textarea"',
1060
+ `${owner}.spec.uiSchema.fields.${field}.widget must be 'textarea'.`
1100
1061
  )];
1101
1062
  }
1102
1063
  if (!isString(property)) {
@@ -1172,22 +1133,8 @@ function checkNav(schema, nav) {
1172
1133
  };
1173
1134
  }
1174
1135
  const config = nav;
1175
- const unknown = Object.keys(config).find((key) => !SCHEMA_NAV_KEYS.has(key));
1176
- if (unknown) {
1177
- return {
1178
- nav: null,
1179
- problem: problem2(
1180
- `/spec/uiSchema/nav/${unknown}`,
1181
- config[unknown],
1182
- "standalone, parentField, group, order or hidden",
1183
- `Schema.spec.uiSchema.nav.${unknown} is not supported.`
1184
- )
1185
- };
1186
- }
1187
- for (const key of NAV_PLACEMENT_KEYS) {
1188
- const expected = config[key] === void 0 ? null : navPlacementProblem(key, config[key]);
1189
- if (expected) return { nav: null, problem: problem2(`/spec/uiSchema/nav/${key}`, config[key], expected, `Schema.spec.uiSchema.nav.${key} must be ${expected}.`) };
1190
- }
1136
+ const bad = navPlacementProblems(config, ["standalone", "parentField"]);
1137
+ if (bad) return { nav: null, problem: problem2(`/spec/uiSchema/nav/${bad[0]}`, config[bad[0]], bad[1], `Schema.spec.uiSchema.nav.${bad[0]} must be ${bad[1]}.`) };
1191
1138
  const standalone = config["standalone"];
1192
1139
  if (standalone !== void 0 && typeof standalone !== "boolean") {
1193
1140
  return {
@@ -1213,13 +1160,15 @@ function checkNav(schema, nav) {
1213
1160
  };
1214
1161
  }
1215
1162
  if (parentField !== void 0 && standalone !== true) {
1216
- return {
1163
+ const required = new Set(schema.spec.schema.required ?? []);
1164
+ const ok = !schema.spec.translates && required.has(parentField) && resolveMantleRef(schema.spec.schema.properties?.[parentField]) !== null;
1165
+ return ok ? { nav: null } : {
1217
1166
  nav: null,
1218
1167
  problem: problem2(
1219
1168
  "/spec/uiSchema/nav/parentField",
1220
1169
  parentField,
1221
- "parentField only with standalone: true",
1222
- "Schema.spec.uiSchema.nav.parentField requires nav.standalone: true."
1170
+ "a required x-mantle-ref field of a Schema that is not a translates child",
1171
+ schema.spec.translates ? `Schema '${schema.metadata.name}' cannot declare nav.parentField because it is a translates child.` : `Schema '${schema.metadata.name}' nav.parentField must name a required x-mantle-ref field.`
1223
1172
  )
1224
1173
  };
1225
1174
  }
@@ -1235,7 +1184,7 @@ function checkNav(schema, nav) {
1235
1184
  )
1236
1185
  };
1237
1186
  }
1238
- const eligible = requiredMantleRefFields(schema);
1187
+ const eligible = requiredMantleRefFields(schema.spec.schema);
1239
1188
  if (eligible.length === 0) {
1240
1189
  return {
1241
1190
  nav: null,
@@ -1264,17 +1213,7 @@ function checkNav(schema, nav) {
1264
1213
  nav: { standalone: true, parentField: only.field, parentCollection: only.collection }
1265
1214
  };
1266
1215
  }
1267
- if (typeof parentField !== "string") {
1268
- return {
1269
- nav: null,
1270
- problem: problem2(
1271
- "/spec/uiSchema/nav/parentField",
1272
- parentField,
1273
- `one of ${eligible.map((item) => item.field).join(", ")}`,
1274
- `Schema '${schema.metadata.name}' declares more than one required x-mantle-ref; nav.parentField is required.`
1275
- )
1276
- };
1277
- }
1216
+ if (typeof parentField !== "string") return { nav: null };
1278
1217
  const matched = eligible.find((item) => item.field === parentField);
1279
1218
  if (!matched) {
1280
1219
  return {
@@ -1291,9 +1230,9 @@ function checkNav(schema, nav) {
1291
1230
  nav: { standalone: true, parentField: matched.field, parentCollection: matched.collection }
1292
1231
  };
1293
1232
  }
1294
- function requiredMantleRefFields(schema) {
1295
- const required = new Set(schema.spec.schema.required ?? []);
1296
- const properties = schema.spec.schema.properties ?? {};
1233
+ function requiredMantleRefFields(body) {
1234
+ const required = new Set(body.required ?? []);
1235
+ const properties = body.properties ?? {};
1297
1236
  const fields = [];
1298
1237
  for (const [field, property] of Object.entries(properties)) {
1299
1238
  if (!required.has(field)) continue;
@@ -1304,18 +1243,62 @@ function requiredMantleRefFields(schema) {
1304
1243
  }
1305
1244
  function checkSchemaNavTargets(schema, schemasByName) {
1306
1245
  const { nav, problems } = checkSchemaAdminUi(schema);
1307
- if (problems.length > 0 || !nav) return null;
1308
- const parent = schemasByName.get(nav.parentCollection);
1309
- if (!parent || parent.spec.translates) {
1310
- const declaredParentField = schema.spec.uiSchema && typeof schema.spec.uiSchema === "object" && !Array.isArray(schema.spec.uiSchema) && schema.spec.uiSchema["nav"] && typeof schema.spec.uiSchema["nav"] === "object" && !Array.isArray(schema.spec.uiSchema["nav"]) && "parentField" in schema.spec.uiSchema["nav"];
1246
+ if (problems.length > 0 || schema.spec.translates) return null;
1247
+ const declared = declaredParentField(schema);
1248
+ const target2 = nav?.parentCollection ?? (declared === void 0 ? void 0 : resolveMantleRef(schema.spec.schema.properties?.[declared])?.schema);
1249
+ if (target2 !== void 0 && !isFoldParent(schemasByName.get(target2))) {
1311
1250
  return problem2(
1312
- declaredParentField ? "/spec/uiSchema/nav/parentField" : "/spec/uiSchema/nav/standalone",
1313
- nav.parentCollection,
1251
+ declared === void 0 ? "/spec/uiSchema/nav/standalone" : "/spec/uiSchema/nav/parentField",
1252
+ target2,
1314
1253
  "the metadata.name of an existing non-translates Schema",
1315
- `Schema '${schema.metadata.name}' nav parent collection '${nav.parentCollection}' is not an eligible fold parent.`
1254
+ `Schema '${schema.metadata.name}' nav parent collection '${target2}' is not an eligible fold parent.`
1316
1255
  );
1317
1256
  }
1318
- return null;
1257
+ if (declared === void 0) {
1258
+ const eligible = eligibleIdRefs(schema, schemasByName);
1259
+ if (eligible.length < 2) return null;
1260
+ const names = eligible.join(", ");
1261
+ const keep = eligible.find((field) => !closesParentCycle(schema, field, schemasByName));
1262
+ return problem2(
1263
+ "/spec/uiSchema/nav/parentField",
1264
+ void 0,
1265
+ `one of ${names}`,
1266
+ `Schema '${schema.metadata.name}' has more than one required id x-mantle-ref (${names}); set uiSchema.nav.parentField to choose its parent.${keep === void 0 ? "" : ` To keep the current choice, add nav: { parentField: ${keep} }.`}`
1267
+ );
1268
+ }
1269
+ return closesParentCycle(schema, declared, schemasByName) ? problem2(
1270
+ "/spec/uiSchema/nav/parentField",
1271
+ declared,
1272
+ "a reference whose parent chain does not lead back to this Schema",
1273
+ `Schema '${schema.metadata.name}' nav.parentField '${declared}' creates a parent cycle.`
1274
+ ) : null;
1275
+ }
1276
+ function closesParentCycle(schema, field, schemasByName) {
1277
+ const seen = /* @__PURE__ */ new Set();
1278
+ for (let at = resolveMantleRef(schema.spec.schema.properties?.[field])?.schema; at !== void 0 && !seen.has(at); at = foldParentOf(schemasByName.get(at), schemasByName)) {
1279
+ if (at === schema.metadata.name) return true;
1280
+ seen.add(at);
1281
+ }
1282
+ return false;
1283
+ }
1284
+ function declaredParentField(schema) {
1285
+ const nav = schema.spec.uiSchema?.["nav"];
1286
+ return typeof nav?.["parentField"] === "string" ? nav["parentField"] : void 0;
1287
+ }
1288
+ var isFoldParent = (s) => !!s && !s.spec.translates;
1289
+ function eligibleIdRefs(schema, schemasByName) {
1290
+ return requiredMantleRefFields(schema.spec.schema).filter((r) => isFoldParent(schemasByName.get(r.collection))).map((r) => r.field);
1291
+ }
1292
+ function foldParentOf(schema, schemasByName) {
1293
+ if (!schema || schema.spec.translates) return void 0;
1294
+ const field = foldParentField(schema.spec.schema, declaredParentField(schema), (name4) => isFoldParent(schemasByName.get(name4)));
1295
+ return field === void 0 ? void 0 : resolveMantleRef(schema.spec.schema.properties?.[field])?.schema;
1296
+ }
1297
+ function foldParentField(body, declared, isParent) {
1298
+ const props = body.properties ?? {};
1299
+ const required = new Set(body.required ?? []);
1300
+ const refs = Object.keys(props).filter((f) => required.has(f) && isParent(resolveMantleRef(props[f])?.schema ?? ""));
1301
+ return declared ?? requiredMantleRefFields(body).find((r) => isParent(r.collection))?.field ?? refs[0];
1319
1302
  }
1320
1303
  var NATIVE_LIST_FIELDS = new Set(RESERVED_ENTRY_COLUMNS);
1321
1304
  function isScalar3(schema) {
@@ -1326,24 +1309,6 @@ function isString(schema) {
1326
1309
  const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
1327
1310
  return types.includes("string") && types.every((type) => type === "string" || type === "null");
1328
1311
  }
1329
- function checkCells(cells, known, owner, expected) {
1330
- if (cells === void 0) return null;
1331
- if (!cells || typeof cells !== "object" || Array.isArray(cells)) {
1332
- return problem2("/spec/uiSchema/list/cells", cells, "an object of field -> <extension>/<contribution>", `${owner}.spec.uiSchema.list.cells must be an object.`);
1333
- }
1334
- for (const [field, ref2] of Object.entries(cells)) {
1335
- if (!known(field)) return problem2(`/spec/uiSchema/list/cells/${field}`, field, expected, `${owner}.spec.uiSchema.list.cells references unknown field '${field}'.`);
1336
- if (!isUiExtensionRef(ref2)) return problem2(`/spec/uiSchema/list/cells/${field}`, ref2, "<extension>/<contribution>", `${owner}.spec.uiSchema.list.cells.${field} must name an Admin extension contribution.`);
1337
- }
1338
- return null;
1339
- }
1340
- function checkPanels(panels) {
1341
- if (panels === void 0) return null;
1342
- if (!Array.isArray(panels) || panels.length > 16 || !panels.every(isUiExtensionRef) || new Set(panels).size !== panels.length) {
1343
- return problem2("/spec/uiSchema/panels", panels, "up to 16 distinct <extension>/<contribution> names", "Schema.spec.uiSchema.panels must list Admin extension contributions.");
1344
- }
1345
- return null;
1346
- }
1347
1312
  function problem2(pointer2, value2, expected, message2) {
1348
1313
  return { pointer: pointer2, value: value2, expected, message: message2 };
1349
1314
  }
@@ -1684,12 +1649,8 @@ function validateSchemaSpec(m, idx) {
1684
1649
  return m;
1685
1650
  }
1686
1651
  var UNSUPPORTED_JSON_SCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
1687
- "anyOf",
1688
1652
  "allOf",
1689
1653
  "not",
1690
- "if",
1691
- "then",
1692
- "else",
1693
1654
  "$anchor",
1694
1655
  "$dynamicAnchor",
1695
1656
  "$dynamicRef",
@@ -1731,6 +1692,16 @@ function validateJsonSchema(root, idx, kind, name4, basePointer) {
1731
1692
  );
1732
1693
  }
1733
1694
  }
1695
+ if (basePointer !== "/spec/input") for (const keyword of INPUT_RULE_KEYWORDS) {
1696
+ if (keyword in value2) {
1697
+ throw new ManifestParseError(
1698
+ `${kind} '${name4}' uses JSON Schema keyword '${keyword}', which is supported only in View and Procedure input`,
1699
+ idx,
1700
+ `${pointer2}/${escapeJsonPointerSegment(keyword)}`,
1701
+ "JSON_SCHEMA_UNSUPPORTED"
1702
+ );
1703
+ }
1704
+ }
1734
1705
  if ("$ref" in value2) refs.push([`${pointer2}/$ref`, value2["$ref"], validateLocalSchemaRef(value2["$ref"], rootObject, idx, kind, name4, `${pointer2}/$ref`)]);
1735
1706
  if (value2["enum"] !== void 0 && (!Array.isArray(value2["enum"]) || value2["enum"].length > MAX_JSON_SCHEMA_ENUM)) {
1736
1707
  throw new ManifestParseError(
@@ -2026,23 +1997,23 @@ function validateViewCache(raw, view, idx) {
2026
1997
  }
2027
1998
  }
2028
1999
  function validateViewInput(raw, idx) {
2029
- const invalid3 = (message2, pointer2, code = "VIEW_INPUT_INVALID_SHAPE") => {
2000
+ const invalid2 = (message2, pointer2, code = "VIEW_INPUT_INVALID_SHAPE") => {
2030
2001
  throw new ManifestParseError(message2, idx, pointer2, code);
2031
2002
  };
2032
2003
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
2033
- invalid3("View.spec.input must be a JSON Schema object", "/spec/input");
2004
+ invalid2("View.spec.input must be a JSON Schema object", "/spec/input");
2034
2005
  }
2035
2006
  const p = raw;
2036
2007
  if (p["type"] !== "object") {
2037
- invalid3(`View.spec.input.type must be "object"; got ${JSON.stringify(p["type"])}`, "/spec/input/type");
2008
+ invalid2(`View.spec.input.type must be "object"; got ${JSON.stringify(p["type"])}`, "/spec/input/type");
2038
2009
  }
2039
2010
  const props = p["properties"];
2040
2011
  if (typeof props !== "object" || props === null || Array.isArray(props)) {
2041
- invalid3("View.spec.input.properties is required (declare each accepted parameter)", "/spec/input/properties");
2012
+ invalid2("View.spec.input.properties is required (declare each accepted parameter)", "/spec/input/properties");
2042
2013
  }
2043
2014
  for (const reserved2 of VIEW_INPUT_RESERVED) {
2044
2015
  if (Object.hasOwn(props, reserved2)) {
2045
- invalid3(
2016
+ invalid2(
2046
2017
  `View.spec.input.properties.${reserved2} is reserved (the runtime owns ${VIEW_INPUT_RESERVED.join(", ")} for pagination); rename it.`,
2047
2018
  `/spec/input/properties/${reserved2}`,
2048
2019
  "VIEW_INPUT_RESERVED_NAME"
@@ -2829,10 +2800,41 @@ function jsonValueWithinLimits() {
2829
2800
  });
2830
2801
  }
2831
2802
 
2803
+ // src/build/spec/domain/service/RootRefChain.ts
2804
+ function lookupLocalRef(root, ref2) {
2805
+ if (typeof ref2 !== "string" || !ref2.startsWith("#/")) return void 0;
2806
+ let value2 = root;
2807
+ try {
2808
+ for (const key of decodeURIComponent(ref2.slice(2)).split("/").map((key2) => key2.replace(/~1/g, "/").replace(/~0/g, "~"))) value2 = value2 && typeof value2 === "object" && Object.hasOwn(value2, key) ? value2[key] : void 0;
2809
+ } catch {
2810
+ return void 0;
2811
+ }
2812
+ return value2 && typeof value2 === "object" ? value2 : void 0;
2813
+ }
2814
+ function rootRefChain(input) {
2815
+ const chain = [];
2816
+ for (let schema = input; schema !== void 0; schema = lookupLocalRef(input, schema.$ref)) {
2817
+ if (chain.includes(schema)) return void 0;
2818
+ chain.push(schema);
2819
+ if (schema.$ref === void 0) return chain;
2820
+ }
2821
+ return void 0;
2822
+ }
2823
+
2832
2824
  // src/build/spec/domain/service/McpToolNaming.ts
2833
2825
  function mcpToolNameSegment(name4) {
2834
2826
  return name4.toLowerCase().replace(/-/g, "_");
2835
2827
  }
2828
+ function toolTitle(name4) {
2829
+ const words = name4.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_.\s]+/g, " ").trim().toLowerCase();
2830
+ return words.charAt(0).toUpperCase() + words.slice(1);
2831
+ }
2832
+ var viewAnnotations = { readOnlyHint: true, destructiveHint: false };
2833
+ function procedureAnnotations(p) {
2834
+ const readOnlyHint = p.mcp?.readOnlyHint ?? false;
2835
+ return { readOnlyHint, destructiveHint: !readOnlyHint && ("ref" in p.handler || overwrites(p.handler.sql.stmts)), ...p.mcp };
2836
+ }
2837
+ var overwrites = (node) => !!node && typeof node === "object" && ("DeleteStmt" in node || "UpdateStmt" in node || node.onConflictClause?.action === "ONCONFLICT_UPDATE" || Object.values(node).some(overwrites));
2836
2838
  function mcpTools(plan, surface, locale = "en") {
2837
2839
  const text = (t) => resolveLocalizedText(t, locale, "en") ?? void 0;
2838
2840
  const tools = /* @__PURE__ */ new Map();
@@ -2849,33 +2851,61 @@ function mcpTools(plan, surface, locale = "en") {
2849
2851
  name: mcpToolNameSegment(t.procedure),
2850
2852
  kind: "procedure",
2851
2853
  source: t.procedure,
2852
- ...title ? { title } : {},
2854
+ title: title ?? toolTitle(t.procedure),
2853
2855
  description: text(p.description) ?? title ?? t.procedure,
2854
2856
  inputSchema: p.input,
2855
2857
  // structuredContent must be an object
2856
2858
  ...p.output.type === "object" ? { outputSchema: p.output } : {},
2857
- ...p.mcp ? { annotations: p.mcp } : {},
2859
+ annotations: procedureAnnotations(p),
2858
2860
  requires: p.requires
2859
2861
  });
2860
2862
  }
2861
2863
  for (const [name4, v] of Object.entries(plan.views)) {
2862
2864
  if (v.surface !== surface) continue;
2863
- const props = v.input?.properties ?? {};
2865
+ const chain = (v.input ? rootRefChain(v.input) ?? [v.input] : []).reverse();
2866
+ const props = Object.assign({}, ...chain.map((s) => s.properties ?? {}));
2867
+ const required = [...new Set(chain.flatMap((s) => s.required ?? []))];
2864
2868
  if ("limit" in props || "cursor" in props) throw new TypeError(`View '${name4}' declares an input named limit or cursor; the MCP tool reserves both for paging.`);
2869
+ const rules = chain.map((s) => Object.fromEntries(INPUT_RULE_KEYWORDS.flatMap((k) => s[k] === void 0 ? [] : [[k, s[k]]]))).filter((r) => Object.keys(r).length);
2870
+ const defs = v.input ? referencedDefs(v.input, props) : {};
2865
2871
  const title = text(v.title);
2866
2872
  add({
2867
2873
  name: mcpToolNameSegment(name4),
2868
2874
  kind: "view",
2869
2875
  source: name4,
2870
- ...title ? { title } : {},
2876
+ title: title ?? toolTitle(name4),
2871
2877
  description: text(v.description) ?? title ?? name4,
2872
- inputSchema: { type: "object", properties: { ...props, limit: { type: "integer", minimum: 1, maximum: 500 }, cursor: { type: "string" } }, ...v.input?.required ? { required: v.input.required } : {} },
2873
- annotations: { readOnlyHint: true },
2878
+ inputSchema: {
2879
+ type: "object",
2880
+ properties: { ...props, limit: { type: "integer", minimum: 1, maximum: 500 }, cursor: { type: "string" } },
2881
+ ...required.length ? { required } : {},
2882
+ ...rules.length > 1 ? { allOf: rules } : rules[0],
2883
+ ...Object.keys(defs).length ? { $defs: defs } : {}
2884
+ },
2885
+ annotations: viewAnnotations,
2874
2886
  requires: v.requires
2875
2887
  });
2876
2888
  }
2877
2889
  return [...tools.values()];
2878
2890
  }
2891
+ function referencedDefs(root, node, found = {}) {
2892
+ if (!node || typeof node !== "object") return found;
2893
+ const ref2 = node.$ref, [head, segment] = typeof ref2 === "string" && ref2.startsWith("#/") ? decoded(ref2.slice(2)).split("/") : [];
2894
+ const key = head === "$defs" ? segment?.replace(/~1/g, "/").replace(/~0/g, "~") : void 0;
2895
+ if (key !== void 0 && !Object.hasOwn(found, key) && root.$defs && Object.hasOwn(root.$defs, key)) {
2896
+ found[key] = root.$defs[key];
2897
+ referencedDefs(root, found[key], found);
2898
+ }
2899
+ for (const child2 of Object.values(node)) referencedDefs(root, child2, found);
2900
+ return found;
2901
+ }
2902
+ var decoded = (pointer2) => {
2903
+ try {
2904
+ return decodeURIComponent(pointer2);
2905
+ } catch {
2906
+ return "";
2907
+ }
2908
+ };
2879
2909
 
2880
2910
  // src/build/spec/domain/service/TriggerGraphChecks.ts
2881
2911
  function checkProcedureTarget(procedure, schemasByName, filePaths) {
@@ -4459,7 +4489,6 @@ function writePolicy(s, kind, o = {}) {
4459
4489
  }
4460
4490
 
4461
4491
  // src/build/spec/infrastructure/sql/compilePlan.ts
4462
- var NATIVE_COLUMNS = { id: "id", status: "status", version: "version", createdAt: "created_at", updatedAt: "updated_at", authorId: "author_id" };
4463
4492
  var indexColumn = (field) => Object.hasOwn(NATIVE_COLUMNS, field) ? NATIVE_COLUMNS[field] : field.toLowerCase();
4464
4493
  function toDiagnostic2(d, source, pointer2) {
4465
4494
  const at = d.line === void 0 ? void 0 : { line: d.line, column: d.column, ...d.token ? { token: d.token } : {} };
@@ -4651,10 +4680,6 @@ async function compileLinkedPlan(linked, dialect = compile_exports) {
4651
4680
  diagnostics.push(validateDiagnostic({ code: "VIEW_UI_INVALID", severity: "error", path: `${source.sourceId}#/${source.documentIndex}/spec/uiSchema/list/${key}/${i}`, source: { ...source, path: `/spec/uiSchema/list/${key}/${i}` }, value: f, expected: `one of the View's outputs: ${outputs.keys.join(", ")}`, message: `View '${v.metadata.name}' uiSchema.list.${key} names '${f}', which the View's SELECT does not output.` }));
4652
4681
  }
4653
4682
  const wire = (outputs?.keys ?? []).map((k) => columns2[k]?.field === k ? schemas[columns2[k].schema].names?.[k] ?? k : k);
4654
- for (const f of Object.keys(v.spec.uiSchema?.["list"]?.cells ?? {})) {
4655
- if (!outputs?.keys || wire.includes(f)) continue;
4656
- diagnostics.push(validateDiagnostic({ code: "VIEW_UI_INVALID", severity: "error", path: `${source.sourceId}#/${source.documentIndex}/spec/uiSchema/list/cells/${f}`, source: { ...source, path: `/spec/uiSchema/list/cells/${f}` }, value: f, expected: `one of the View's outputs: ${wire.join(", ")}`, message: `View '${v.metadata.name}' uiSchema.list.cells names '${f}', which the View's SELECT does not output.` }));
4657
- }
4658
4683
  for (const [i, f] of (list["columns"] ?? []).entries()) {
4659
4684
  if (!outputs?.keys || wire.includes(f)) continue;
4660
4685
  const folded = wire.find((k) => k.toLowerCase() === f.toLowerCase());
@@ -4887,7 +4912,7 @@ function toCanonicalLocale(input) {
4887
4912
  }
4888
4913
  function canonicalizeLocaleList(inputs) {
4889
4914
  const valid = [];
4890
- const invalid3 = [];
4915
+ const invalid2 = [];
4891
4916
  const seen = /* @__PURE__ */ new Set();
4892
4917
  for (const raw of inputs) {
4893
4918
  try {
@@ -4897,10 +4922,10 @@ function canonicalizeLocaleList(inputs) {
4897
4922
  valid.push(canonical2);
4898
4923
  }
4899
4924
  } catch {
4900
- invalid3.push(raw);
4925
+ invalid2.push(raw);
4901
4926
  }
4902
4927
  }
4903
- return { valid, invalid: invalid3 };
4928
+ return { valid, invalid: invalid2 };
4904
4929
  }
4905
4930
  function containsScriptSubtagLocale(inputs) {
4906
4931
  return inputs.some((raw) => SCRIPT_SUBTAG_SHAPE.test(raw));
@@ -4956,13 +4981,13 @@ var InvalidSiteIconsError = class extends Error {
4956
4981
  };
4957
4982
  function assertSiteDefaultsCanonical(defaults) {
4958
4983
  if (defaults?.locales && defaults.locales.length > 0) {
4959
- const { invalid: invalid3 } = canonicalizeLocaleList(defaults.locales);
4960
- if (invalid3.length > 0) throw new InvalidSiteDefaultsError(invalid3);
4984
+ const { invalid: invalid2 } = canonicalizeLocaleList(defaults.locales);
4985
+ if (invalid2.length > 0) throw new InvalidSiteDefaultsError(invalid2);
4961
4986
  }
4962
4987
  if (defaults?.icons) {
4963
- const invalid3 = defaults.icons.filter((icon) => !validSiteIcon(icon));
4964
- if (defaults.icons.length === 0 || invalid3.length > 0) {
4965
- throw new InvalidSiteIconsError(invalid3.length > 0 ? invalid3 : defaults.icons);
4988
+ const invalid2 = defaults.icons.filter((icon) => !validSiteIcon(icon));
4989
+ if (defaults.icons.length === 0 || invalid2.length > 0) {
4990
+ throw new InvalidSiteIconsError(invalid2.length > 0 ? invalid2 : defaults.icons);
4966
4991
  }
4967
4992
  }
4968
4993
  const origin = defaults?.origin;
@@ -5069,7 +5094,7 @@ function lifecycleHookSets(plan) {
5069
5094
  }
5070
5095
 
5071
5096
  // src/build/core/version.ts
5072
- var MANTLE_VERSION = "0.2.0-alpha.7";
5097
+ var MANTLE_VERSION = "0.2.0-alpha.8";
5073
5098
 
5074
5099
  // src/build/core/sql/ast.ts
5075
5100
  var S = (s) => ({ String: { sval: s } });
@@ -6368,7 +6393,6 @@ function initialStorageSql(plan) {
6368
6393
  // src/build/postgres/storage.ts
6369
6394
  var q4 = (id) => `"${id.replace(/"/g, '""')}"`;
6370
6395
  var ident = (n) => new TextEncoder().encode(n).length <= 63 ? n : `${n.slice(0, 50)}_${fnv(n)}`;
6371
- var fnv = (s) => [...s].reduce((h, c) => Math.imul(h ^ c.codePointAt(0), 16777619) >>> 0, 2166136261).toString(16).padStart(8, "0");
6372
6396
  function columns(s) {
6373
6397
  return [
6374
6398
  ...s.scope ? [{ name: s.scope, type: "text", native: true }] : [],
@@ -6418,15 +6442,13 @@ function adminIndexColumns(s) {
6418
6442
  function collectionOf(s, schemas) {
6419
6443
  const names = new Set(schemas.filter((x) => !x.translates).map((x) => x.name));
6420
6444
  const props = propsOf(s.schema);
6421
- const required = new Set(s.schema.required ?? []);
6422
6445
  const ui = s.uiSchema ?? {};
6423
6446
  const list = ui["list"] ?? {};
6424
6447
  const nav = ui["nav"];
6425
6448
  const filterField = list["filterField"];
6426
- const refs = Object.keys(props).filter((f) => required.has(f) && names.has(resolveMantleRef(props[f])?.schema ?? ""));
6427
- const byId = refs.find((f) => resolveMantleRef(props[f]).field === "id");
6428
- const parentField = byId ?? refs[0];
6429
- const navField = nav?.["standalone"] === true ? nav["parentField"] ?? byId : void 0;
6449
+ const declared = nav?.["parentField"];
6450
+ const parentField = foldParentField(s.schema, declared, (name4) => names.has(name4));
6451
+ const navField = nav?.["standalone"] === true ? declared ?? (s.translates ? void 0 : requiredMantleRefFields(s.schema).find((r) => names.has(r.collection))?.field) : void 0;
6430
6452
  const navParent = navField ? resolveMantleRef(props[navField]) : null;
6431
6453
  const children = schemas.filter((c) => c.translates?.parent === s.name);
6432
6454
  return {
@@ -6448,6 +6470,10 @@ function collectionOf(s, schemas) {
6448
6470
  nav: navField && navParent ? { standalone: true, parentField: navField, parentCollection: navParent.schema } : null
6449
6471
  };
6450
6472
  }
6473
+ function isPrimaryCollection(s, schemas) {
6474
+ const c = collectionOf(s, schemas);
6475
+ return !s.translates && (!c.parent || c.nav !== null);
6476
+ }
6451
6477
 
6452
6478
  // src/build/admin/procedureFlow.ts
6453
6479
  function expression(node) {
@@ -6780,114 +6806,6 @@ function developerConsole(plan) {
6780
6806
  };
6781
6807
  }
6782
6808
 
6783
- // src/build/admin/extensions.ts
6784
- var invalid2 = (code, path, message2, value2) => new DiagnosticError(makeDiagnostic({ code, phase: "boot", severity: "error", path, message: message2, ...value2 === void 0 ? {} : { value: value2 } }));
6785
- var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
6786
- var isText2 = (v) => typeof v === "string" && v.length > 0 || isObject(v) && Object.keys(v).length > 0 && Object.values(v).every((x) => typeof x === "string" && x.length > 0);
6787
- var keysWithin = (o, allowed) => Object.keys(o).find((k) => !allowed.includes(k));
6788
- var FORM_TYPES = ["string", "number", "integer", "boolean"];
6789
- var FORM_PROPERTY_KEYS = ["type", "title", "description", "default", "enum", "oneOf", "minimum", "maximum", "minLength", "maxLength"];
6790
- function formSchemaProblem(schema) {
6791
- if (!isObject(schema) || schema["type"] !== "object" || !isObject(schema["properties"])) return "an object schema with properties";
6792
- const extra = keysWithin(schema, ["type", "properties", "required", "title", "description", "additionalProperties"]);
6793
- if (extra) return `no '${extra}' at the top level`;
6794
- if (schema["additionalProperties"] !== void 0 && schema["additionalProperties"] !== false) return "additionalProperties false or absent";
6795
- const props = schema["properties"];
6796
- const names = Object.keys(props);
6797
- if (names.length === 0 || names.length > 64) return "1 to 64 properties";
6798
- if (names.includes("__proto__")) return "no property named __proto__";
6799
- const required = schema["required"];
6800
- if (required !== void 0 && (!Array.isArray(required) || required.some((r) => typeof r !== "string" || !names.includes(r)))) return "required naming declared properties";
6801
- for (const [name4, p] of Object.entries(props)) {
6802
- if (!isObject(p) || typeof p["type"] !== "string" || !FORM_TYPES.includes(p["type"])) return `property '${name4}' typed string, number, integer or boolean`;
6803
- const bad = keysWithin(p, FORM_PROPERTY_KEYS);
6804
- if (bad) return `property '${name4}' without '${bad}'`;
6805
- if (p["title"] !== void 0 && !isText2(p["title"])) return `property '${name4}' with a text title`;
6806
- if (p["description"] !== void 0 && !isText2(p["description"])) return `property '${name4}' with a text description`;
6807
- if (p["enum"] !== void 0 && (!Array.isArray(p["enum"]) || p["enum"].length === 0 || p["enum"].some((x) => !matchesType(p["type"], x)))) return `property '${name4}' with an enum of its type`;
6808
- if (p["oneOf"] !== void 0 && (!Array.isArray(p["oneOf"]) || p["oneOf"].length === 0 || p["oneOf"].some((o) => !isObject(o) || keysWithin(o, ["const", "title"]) || !matchesType(p["type"], o["const"]) || o["title"] !== void 0 && !isText2(o["title"])))) return `property '${name4}' with a oneOf of { const, title }`;
6809
- for (const k of ["minimum", "maximum", "minLength", "maxLength"]) if (p[k] !== void 0 && (typeof p[k] !== "number" || !Number.isFinite(p[k]))) return `property '${name4}' with a numeric ${k}`;
6810
- if (p["default"] !== void 0 && checkValue(p, p["default"]) !== null) return `property '${name4}' with a default it accepts`;
6811
- }
6812
- return null;
6813
- }
6814
- function matchesType(type, v) {
6815
- if (type === "string") return typeof v === "string";
6816
- if (type === "boolean") return typeof v === "boolean";
6817
- if (type === "integer") return Number.isInteger(v);
6818
- return typeof v === "number" && Number.isFinite(v);
6819
- }
6820
- function checkValue(p, v) {
6821
- const type = p["type"];
6822
- if (!matchesType(type, v)) return type === "integer" ? "an integer" : `a ${type}`;
6823
- if (Array.isArray(p["enum"]) && !p["enum"].includes(v)) return "one of the listed values";
6824
- if (Array.isArray(p["oneOf"]) && !p["oneOf"].some((o) => o["const"] === v)) return "one of the listed values";
6825
- if (typeof v === "number") {
6826
- if (typeof p["minimum"] === "number" && v < p["minimum"]) return `at least ${p["minimum"]}`;
6827
- if (typeof p["maximum"] === "number" && v > p["maximum"]) return `at most ${p["maximum"]}`;
6828
- }
6829
- if (typeof v === "string") {
6830
- const length = [...v].length;
6831
- if (typeof p["minLength"] === "number" && length < p["minLength"]) return `at least ${p["minLength"]} characters`;
6832
- if (typeof p["maxLength"] === "number" && length > p["maxLength"]) return `at most ${p["maxLength"]} characters`;
6833
- }
6834
- return null;
6835
- }
6836
- function checkFormValue(schema, value2) {
6837
- if (!isObject(value2)) return { "": "an object" };
6838
- const props = schema.properties ?? {};
6839
- const errors = /* @__PURE__ */ Object.create(null);
6840
- const has = (name4) => Object.hasOwn(value2, name4) && value2[name4] !== void 0;
6841
- for (const k of Object.keys(value2)) if (!Object.hasOwn(props, k)) errors[k] = "no such field";
6842
- for (const name4 of schema.required ?? []) if (!has(name4)) errors[name4] = "required";
6843
- for (const [name4, p] of Object.entries(props)) {
6844
- if (!has(name4) || Object.hasOwn(errors, name4)) continue;
6845
- const problem3 = checkValue(p, value2[name4]);
6846
- if (problem3) errors[name4] = `expected ${problem3}`;
6847
- }
6848
- return errors;
6849
- }
6850
- function planUiExtensionRefs(plan) {
6851
- const refs = [];
6852
- const fields = (owner, ui) => {
6853
- for (const [name4, c] of Object.entries(isObject(ui?.["fields"]) ? ui["fields"] : {})) {
6854
- if (isObject(c) && typeof c["widget"] === "string" && UI_EXTENSION_REF.test(c["widget"])) refs.push({ path: `${owner}/uiSchema/fields/${name4}/widget`, ref: c["widget"], target: "field.input/v1", ...c["options"] === void 0 ? {} : { options: c["options"] } });
6855
- }
6856
- };
6857
- const cells = (owner, ui) => {
6858
- const list = ui?.["list"];
6859
- for (const [name4, ref2] of Object.entries(isObject(list) && isObject(list["cells"]) ? list["cells"] : {})) if (typeof ref2 === "string") refs.push({ path: `${owner}/uiSchema/list/cells/${name4}`, ref: ref2, target: "field.cell/v1" });
6860
- };
6861
- for (const [name4, s] of Object.entries(plan.schemas)) {
6862
- const owner = `schema:${s.name ?? name4}`;
6863
- const ui = s.uiSchema;
6864
- fields(owner, ui);
6865
- cells(owner, ui);
6866
- const panels = ui?.["panels"];
6867
- if (Array.isArray(panels)) {
6868
- for (const ref2 of panels) if (typeof ref2 === "string") refs.push({ path: `${owner}/uiSchema/panels`, ref: ref2, target: "record.sidebar/v1" });
6869
- }
6870
- }
6871
- for (const [name4, p] of Object.entries(plan.procedures)) fields(`procedure:${name4}`, p.uiSchema);
6872
- for (const [name4, v] of Object.entries(plan.views)) cells(`view:${name4}`, v.uiSchema);
6873
- return refs;
6874
- }
6875
- function checkPlanUiExtensions(plan, extensions) {
6876
- for (const { path, ref: ref2, target: target2, options } of planUiExtensionRefs(plan)) {
6877
- const [, extId, cId] = UI_EXTENSION_REF.exec(ref2);
6878
- const ext = extensions.find((e) => e.id === extId);
6879
- const c = ext ? [...ext.contributes.fields ?? [], ...ext.contributes.panels ?? []].find((x) => x.id === cId) : void 0;
6880
- if (!c) throw invalid2("UI_EXTENSION_UNKNOWN", path, `no Admin extension contribution '${ref2}'`, ref2);
6881
- if (c.target !== target2) throw invalid2("UI_EXTENSION_TARGET", path, `'${ref2}' is a ${c.target} contribution; this key needs ${target2}`, ref2);
6882
- if (options === void 0) continue;
6883
- const schema = c.optionsSchema;
6884
- if (!schema) throw invalid2("UI_EXTENSION_OPTIONS", path, `'${ref2}' takes no options`, options);
6885
- const errors = checkFormValue(schema, options);
6886
- const first = Object.entries(errors)[0];
6887
- if (first) throw invalid2("UI_EXTENSION_OPTIONS", `${path}/${first[0]}`, `options for '${ref2}': ${first[0] || "options"} ${first[1]}`, options);
6888
- }
6889
- }
6890
-
6891
6809
  // src/build/mcp/catalog.ts
6892
6810
  var nativeSchema = (field) => Object.hasOwn(NATIVE_OUTPUT_TYPES, field) ? NATIVE_OUTPUT_TYPES[field] === "integer" ? { type: "integer" } : { type: "string", format: "date-time" } : void 0;
6893
6811
  function entrySchema(plan, v) {
@@ -6932,12 +6850,14 @@ function appCatalog(plan, surface) {
6932
6850
  export {
6933
6851
  BUILT_IN_NAV_GROUPS,
6934
6852
  DiagnosticError,
6853
+ INPUT_RULE_KEYWORDS,
6854
+ NATIVE_COLUMNS,
6935
6855
  NATIVE_NOT_NULL,
6936
6856
  adminIndexColumns,
6937
6857
  appCatalog,
6938
6858
  assertSiteDefaultsCanonical,
6859
+ canonical,
6939
6860
  canonicalizeLocaleList,
6940
- checkPlanUiExtensions,
6941
6861
  collectionOf,
6942
6862
  compilePlan,
6943
6863
  compileProgram,
@@ -6946,26 +6866,31 @@ export {
6946
6866
  entrySchema,
6947
6867
  enumOptions,
6948
6868
  expandPolicyRequired,
6949
- formSchemaProblem,
6869
+ fnv,
6870
+ isPrimaryCollection,
6871
+ lookupLocalRef,
6950
6872
  lowerPlan,
6951
6873
  mcpToolNameSegment,
6952
6874
  mcpTools,
6953
- navGroupProblems,
6875
+ navPlacementProblems,
6954
6876
  notNullColumns,
6955
6877
  pagedOf,
6956
6878
  parseNumeric,
6957
6879
  parsePgSql,
6958
- planUiExtensionRefs,
6959
6880
  compile_exports2 as postgresCompiler,
6960
6881
  postgresDialect,
6961
6882
  initialStorageSql2 as postgresSchema,
6883
+ procedureAnnotations,
6962
6884
  protectedColumns,
6963
6885
  resolveLocalizedText,
6886
+ rootRefChain,
6964
6887
  rowPolicy,
6965
6888
  rowPreconditions,
6966
6889
  compile_exports as sqliteCompiler,
6967
6890
  initialStorageSql as sqliteSchema,
6891
+ toolTitle,
6968
6892
  userScopeTerm,
6893
+ viewAnnotations,
6969
6894
  viewOutputs,
6970
6895
  writePolicy
6971
6896
  };