@aotter/mantle 0.2.0-alpha.8 → 0.2.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 (176) hide show
  1. package/README.md +26 -3
  2. package/dist/auth/adapter.js +10 -34
  3. package/dist/auth/admin.js +2 -5
  4. package/dist/auth/caller.js +0 -27
  5. package/dist/auth/consent.js +100 -0
  6. package/dist/auth/hardening.js +0 -7
  7. package/dist/auth/identity.js +0 -11
  8. package/dist/auth/oauth.js +0 -32
  9. package/dist/auth/resource-seeds.js +0 -2
  10. package/dist/auth/route-http.js +33 -0
  11. package/dist/auth/routes.js +8 -153
  12. package/dist/build/compiler.d.ts +19 -609
  13. package/dist/build/compiler.js +3 -6896
  14. package/dist/build/spec/domain/model/SqlIr.d.ts +2 -0
  15. package/dist/build/spec/domain/model/UserFields.d.ts +1 -0
  16. package/dist/bun/assets.js +0 -2
  17. package/dist/domain/adminOrder.js +0 -2
  18. package/dist/domain/columns.js +7 -20
  19. package/dist/domain/describe/chunk-SGMRVDGE.js +1 -0
  20. package/dist/domain/describe/index.js +1 -115
  21. package/dist/domain/describe/target-hash.js +1 -12
  22. package/dist/domain/entry.js +0 -14
  23. package/dist/domain/generated-tools.js +0 -1
  24. package/dist/domain/input.js +0 -2
  25. package/dist/domain/paging.js +0 -17
  26. package/dist/domain/recipes.js +10 -7
  27. package/dist/domain/userReferences.js +0 -1
  28. package/dist/domain/wireScalars.js +0 -6
  29. package/dist/infrastructure/adminEntries.js +10 -7
  30. package/dist/infrastructure/adminPaging.js +55 -57
  31. package/dist/infrastructure/artifacts.js +0 -4
  32. package/dist/infrastructure/authSchema.js +0 -9
  33. package/dist/infrastructure/bundle.js +17 -32
  34. package/dist/infrastructure/cli.js +25 -11
  35. package/dist/infrastructure/generate.js +18 -17
  36. package/dist/infrastructure/generateAuth.js +48 -5
  37. package/dist/infrastructure/help.js +55 -0
  38. package/dist/infrastructure/init.js +50 -0
  39. package/dist/infrastructure/inputs.js +0 -9
  40. package/dist/infrastructure/mounts.js +22 -0
  41. package/dist/infrastructure/wrangler.js +18 -9
  42. package/dist/kernel/authorization.js +0 -1
  43. package/dist/kernel/binds.d.ts +16 -7
  44. package/dist/kernel/binds.js +4 -10
  45. package/dist/kernel/caller-identity.d.ts +0 -3
  46. package/dist/kernel/caller-identity.js +0 -15
  47. package/dist/kernel/caller-mode.d.ts +4 -0
  48. package/dist/kernel/caller-mode.js +14 -0
  49. package/dist/kernel/contracts.d.ts +7 -11
  50. package/dist/kernel/contracts.js +8 -44
  51. package/dist/kernel/cron.js +0 -4
  52. package/dist/kernel/dialect/common.d.ts +0 -1
  53. package/dist/kernel/dialect/common.js +0 -3
  54. package/dist/kernel/dialect/postgres.js +0 -2
  55. package/dist/kernel/dialect/sqlite.js +1 -6
  56. package/dist/kernel/drain.js +2 -8
  57. package/dist/kernel/driver/bun-sqlite.js +0 -4
  58. package/dist/kernel/driver/d1.js +0 -1
  59. package/dist/kernel/driver/postgres.js +3 -17
  60. package/dist/kernel/driver/transaction-context.js +0 -1
  61. package/dist/kernel/email.js +0 -4
  62. package/dist/kernel/host-access.js +2 -3
  63. package/dist/kernel/host-caller.d.ts +1 -1
  64. package/dist/kernel/host-caller.js +3 -6
  65. package/dist/kernel/json-body.js +0 -2
  66. package/dist/kernel/mcp-apps.js +1 -6
  67. package/dist/kernel/mcp-call.js +0 -1
  68. package/dist/kernel/mcp-handler.js +3 -27
  69. package/dist/kernel/mcp.d.ts +5 -1
  70. package/dist/kernel/mcp.js +2 -6
  71. package/dist/kernel/media.js +0 -18
  72. package/dist/kernel/paging.js +1 -9
  73. package/dist/kernel/publishing.js +3 -4
  74. package/dist/kernel/r2-media.js +0 -7
  75. package/dist/kernel/rest-dispatch.js +0 -1
  76. package/dist/kernel/rest-router.js +2 -3
  77. package/dist/kernel/rest.js +0 -3
  78. package/dist/kernel/scalars.js +4 -11
  79. package/dist/kernel/search.js +0 -1
  80. package/dist/kernel/site.js +0 -2
  81. package/dist/kernel/store.d.ts +11 -1
  82. package/dist/kernel/store.js +21 -34
  83. package/dist/kernel/surface-failure.d.ts +10 -0
  84. package/dist/kernel/surface-failure.js +29 -0
  85. package/dist/kernel/surface.js +0 -3
  86. package/dist/kernel/sweep.js +0 -1
  87. package/dist/kernel/translations.js +0 -1
  88. package/dist/kernel/user-caller.js +2 -8
  89. package/dist/kernel/user-fields.d.ts +3 -2
  90. package/dist/kernel/user-fields.js +9 -9
  91. package/dist/kernel/validation.js +0 -8
  92. package/dist/kernel/web-gateway.js +0 -2
  93. package/dist/kernel/web-head.js +0 -1
  94. package/dist/kernel/web-index.js +39 -0
  95. package/dist/kernel/web-response.js +0 -1
  96. package/dist/kernel/web-serve.js +97 -0
  97. package/dist/kernel/web.d.ts +1 -1
  98. package/dist/kernel/web.js +10 -125
  99. package/dist/kernel/where.js +0 -6
  100. package/dist/usecase/adminBindings.js +30 -21
  101. package/dist/usecase/adminClient.js +0 -21
  102. package/dist/usecase/adminDescriptors.js +2 -9
  103. package/dist/usecase/generate.js +7 -12
  104. package/dist/usecase/handlers.js +0 -3
  105. package/dist/usecase/mcp-media.js +0 -1
  106. package/dist/usecase/mcp.js +0 -4
  107. package/dist/usecase/paging.js +1 -3
  108. package/dist/usecase/publishing.js +1 -5
  109. package/dist/usecase/rest.js +0 -1
  110. package/dist/usecase/site.js +0 -2
  111. package/dist/usecase/stores.js +4 -14
  112. package/dist/usecase/tool-version.js +0 -3
  113. package/dist/usecase/translations.js +0 -2
  114. package/dist/usecase/validation.js +8 -20
  115. package/docs/examples/README.md +13 -5
  116. package/docs/examples/app-shell/README.md +43 -8
  117. package/docs/examples/app-shell/accept.mjs +91 -0
  118. package/docs/examples/app-shell/gitignore +7 -0
  119. package/docs/examples/app-shell/package.json +7 -6
  120. package/docs/examples/app-shell/smoke-kit.mjs +29 -15
  121. package/docs/examples/app-shell/src/auth.ts +3 -2
  122. package/docs/examples/app-shell/src/routes.ts +6 -3
  123. package/docs/examples/app-shell/src/service.ts +4 -3
  124. package/docs/examples/club-events/README.md +7 -2
  125. package/docs/examples/club-events/manifests/club-events.yaml +22 -0
  126. package/docs/examples/club-events/smoke.mjs +7 -1
  127. package/docs/examples/club-events/src/handlers.ts +1 -1
  128. package/docs/examples/purchasing/README.md +12 -3
  129. package/docs/examples/purchasing/manifests/purchasing.yaml +36 -3
  130. package/docs/examples/purchasing/smoke.mjs +14 -0
  131. package/docs/examples/purchasing/src/handlers.ts +1 -1
  132. package/docs/examples/recipes/README.md +5 -3
  133. package/docs/handbook/cloudflare/authentication.md +21 -4
  134. package/docs/handbook/cloudflare/chatgpt-sites.md +4 -2
  135. package/docs/handbook/cloudflare/deploy-and-operate.md +24 -1
  136. package/docs/handbook/cloudflare/media-r2.md +2 -3
  137. package/docs/handbook/cloudflare/service-entry.md +17 -4
  138. package/docs/handbook/concepts/runtime-and-adapters.md +1 -1
  139. package/docs/handbook/examples/hub.md +1 -1
  140. package/docs/handbook/guides/agent-setup.md +15 -10
  141. package/docs/handbook/guides/agent-workflow-acceptance.md +232 -0
  142. package/docs/handbook/guides/local-testing.md +40 -0
  143. package/docs/handbook/guides/mcp-apps.md +78 -1
  144. package/docs/handbook/guides/native-admin.md +29 -6
  145. package/docs/handbook/guides/native-execution.md +5 -4
  146. package/docs/handbook/guides/oauth.md +58 -0
  147. package/docs/handbook/guides/public-pages.md +44 -4
  148. package/docs/handbook/guides/sql-patterns.md +266 -0
  149. package/docs/handbook/guides/typed-queries.md +173 -0
  150. package/docs/handbook/navigation.json +16 -0
  151. package/docs/handbook/reference/admin-descriptors.md +4 -1
  152. package/docs/handbook/reference/authorization.md +1 -1
  153. package/docs/handbook/reference/diagnostics.md +6 -1
  154. package/docs/handbook/reference/procedure.md +5 -5
  155. package/docs/handbook/reference/schema.md +10 -4
  156. package/docs/handbook/reference/sql.md +64 -0
  157. package/docs/handbook/reference/surface.md +11 -4
  158. package/docs/handbook/reference/trigger.md +9 -0
  159. package/docs/handbook/reference/view.md +11 -3
  160. package/docs/handbook/releases/index.md +27 -6
  161. package/docs/handbook/start/agents.md +45 -0
  162. package/docs/handbook/start/project-and-cli.md +15 -1
  163. package/docs/handbook/start/quickstart-worker.md +1 -1
  164. package/docs/skills/develop/SKILL.md +44 -16
  165. package/docs/skills/integrate/SKILL.md +4 -9
  166. package/docs/skills/provision/SKILL.md +15 -12
  167. package/docs/skills/update/SKILL.md +21 -18
  168. package/package.json +24 -4
  169. package/skills/README.md +1 -1
  170. package/skills/mantle/SKILL.md +48 -107
  171. package/dist/domain/describe/chunk-ZVXBGP7Y.js +0 -424
  172. package/docs/skills/develop/mcp-app.md +0 -39
  173. package/skills/.keep +0 -0
  174. package/skills/mantle/references/local-testing.md +0 -15
  175. package/skills/mantle/references/web.md +0 -41
  176. package/skills/mantle/references/website.md +0 -49
@@ -1,424 +0,0 @@
1
- // src/build/spec/domain/model/ManifestGrammar.ts
2
- function resolveLocalizedText(value, preferred, canonical2) {
3
- if (value == null) return null;
4
- if (typeof value === "string") return value;
5
- if (Object.prototype.hasOwnProperty.call(value, preferred)) {
6
- return value[preferred];
7
- }
8
- if (canonical2 && Object.prototype.hasOwnProperty.call(value, canonical2)) {
9
- return value[canonical2];
10
- }
11
- const firstKey = Object.keys(value)[0];
12
- return firstKey !== void 0 ? value[firstKey] : null;
13
- }
14
- var INPUT_RULE_KEYWORDS = ["dependentRequired", "anyOf", "if", "then", "else"];
15
-
16
- // src/build/spec/domain/service/RootRefChain.ts
17
- function lookupLocalRef(root, ref) {
18
- if (typeof ref !== "string" || !ref.startsWith("#/")) return void 0;
19
- let value = root;
20
- try {
21
- for (const key of decodeURIComponent(ref.slice(2)).split("/").map((key2) => key2.replace(/~1/g, "/").replace(/~0/g, "~"))) value = value && typeof value === "object" && Object.hasOwn(value, key) ? value[key] : void 0;
22
- } catch {
23
- return void 0;
24
- }
25
- return value && typeof value === "object" ? value : void 0;
26
- }
27
- function rootRefChain(input) {
28
- const chain = [];
29
- for (let schema = input; schema !== void 0; schema = lookupLocalRef(input, schema.$ref)) {
30
- if (chain.includes(schema)) return void 0;
31
- chain.push(schema);
32
- if (schema.$ref === void 0) return chain;
33
- }
34
- return void 0;
35
- }
36
-
37
- // src/build/spec/domain/service/McpToolNaming.ts
38
- function mcpToolNameSegment(name) {
39
- return name.toLowerCase().replace(/-/g, "_");
40
- }
41
- function toolTitle(name) {
42
- const words = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_.\s]+/g, " ").trim().toLowerCase();
43
- return words.charAt(0).toUpperCase() + words.slice(1);
44
- }
45
- var viewAnnotations = { readOnlyHint: true, destructiveHint: false };
46
- function procedureAnnotations(p) {
47
- const readOnlyHint = p.mcp?.readOnlyHint ?? false;
48
- return { readOnlyHint, destructiveHint: !readOnlyHint && ("ref" in p.handler || overwrites(p.handler.sql.stmts)), ...p.mcp };
49
- }
50
- var overwrites = (node) => !!node && typeof node === "object" && ("DeleteStmt" in node || "UpdateStmt" in node || node.onConflictClause?.action === "ONCONFLICT_UPDATE" || Object.values(node).some(overwrites));
51
- function mcpTools(plan, surface, locale = "en") {
52
- const text = (t) => resolveLocalizedText(t, locale, "en") ?? void 0;
53
- const tools = /* @__PURE__ */ new Map();
54
- const add = (tool) => {
55
- const prior = tools.get(tool.name);
56
- if (prior && (prior.kind !== tool.kind || prior.source !== tool.source)) throw new TypeError(`MCP tool '${tool.name}' comes from both '${prior.source}' and '${tool.source}'.`);
57
- tools.set(tool.name, tool);
58
- };
59
- for (const t of Object.values(plan.triggers)) {
60
- if (t.source.kind !== "mcp" || t.source.surface !== surface) continue;
61
- const p = plan.procedures[t.procedure];
62
- const title = text(p.title);
63
- add({
64
- name: mcpToolNameSegment(t.procedure),
65
- kind: "procedure",
66
- source: t.procedure,
67
- title: title ?? toolTitle(t.procedure),
68
- description: text(p.description) ?? title ?? t.procedure,
69
- inputSchema: p.input,
70
- // structuredContent must be an object
71
- ...p.output.type === "object" ? { outputSchema: p.output } : {},
72
- annotations: procedureAnnotations(p),
73
- requires: p.requires
74
- });
75
- }
76
- for (const [name, v] of Object.entries(plan.views)) {
77
- if (v.surface !== surface) continue;
78
- const chain = (v.input ? rootRefChain(v.input) ?? [v.input] : []).reverse();
79
- const props = Object.assign({}, ...chain.map((s) => s.properties ?? {}));
80
- const required = [...new Set(chain.flatMap((s) => s.required ?? []))];
81
- if ("limit" in props || "cursor" in props) throw new TypeError(`View '${name}' declares an input named limit or cursor; the MCP tool reserves both for paging.`);
82
- 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);
83
- const defs = v.input ? referencedDefs(v.input, props) : {};
84
- const title = text(v.title);
85
- add({
86
- name: mcpToolNameSegment(name),
87
- kind: "view",
88
- source: name,
89
- title: title ?? toolTitle(name),
90
- description: text(v.description) ?? title ?? name,
91
- inputSchema: {
92
- type: "object",
93
- properties: { ...props, limit: { type: "integer", minimum: 1, maximum: 500 }, cursor: { type: "string" } },
94
- ...required.length ? { required } : {},
95
- ...rules.length > 1 ? { allOf: rules } : rules[0],
96
- ...Object.keys(defs).length ? { $defs: defs } : {}
97
- },
98
- annotations: viewAnnotations,
99
- requires: v.requires
100
- });
101
- }
102
- return [...tools.values()];
103
- }
104
- function referencedDefs(root, node, found = {}) {
105
- if (!node || typeof node !== "object") return found;
106
- const ref = node.$ref, [head, segment] = typeof ref === "string" && ref.startsWith("#/") ? decoded(ref.slice(2)).split("/") : [];
107
- const key = head === "$defs" ? segment?.replace(/~1/g, "/").replace(/~0/g, "~") : void 0;
108
- if (key !== void 0 && !Object.hasOwn(found, key) && root.$defs && Object.hasOwn(root.$defs, key)) {
109
- found[key] = root.$defs[key];
110
- referencedDefs(root, found[key], found);
111
- }
112
- for (const child of Object.values(node)) referencedDefs(root, child, found);
113
- return found;
114
- }
115
- var decoded = (pointer) => {
116
- try {
117
- return decodeURIComponent(pointer);
118
- } catch {
119
- return "";
120
- }
121
- };
122
-
123
- // src/build/spec/domain/service/PlanFingerprint.ts
124
- function canonical(v) {
125
- if (Array.isArray(v)) return `[${v.map(canonical).join(",")}]`;
126
- if (v && typeof v === "object")
127
- return `{${Object.keys(v).sort().filter((k) => v[k] !== void 0).map((k) => `${JSON.stringify(k)}:${canonical(v[k])}`).join(",")}}`;
128
- return JSON.stringify(v) ?? "null";
129
- }
130
-
131
- // src/domain/generated-tools.ts
132
- var publishingTransitions = [["publish", ["draft"], "published"], ["unpublish", ["published", "archived"], "draft"], ["archive", ["draft", "published"], "archived"]];
133
- var publishingInput = { type: "object", properties: { id: { type: "string", minLength: 1 }, expectedVersion: { type: "integer", minimum: 1 } }, required: ["id", "expectedVersion"], additionalProperties: false };
134
- function mediaDefinitions() {
135
- const define = (name, description, inputSchema, action, readOnlyHint = false) => ({ name, description, inputSchema, action, annotations: { readOnlyHint, destructiveHint: action === "delete", openWorldHint: true } });
136
- const string = { type: "string" };
137
- const texts = { alt: { type: "string", maxLength: 1e3 }, caption: { type: "string", maxLength: 1e3 } };
138
- const input = (properties, required = []) => ({ type: "object", properties, required, additionalProperties: false });
139
- return [
140
- define("get_media_upload_policy", "Read declared image purpose MIME slots and byte caps before preparing variants or requesting an upload. Optimization runs locally, never in the host.", input({}), "policy", true),
141
- define("create_media_upload", "Create short-lived PUT capabilities for image variants. Read site media purpose policy first; optimize images locally. Obtain attachment bytes yourself, then PUT each variant with its requiredHeaders and call commit_media_upload. Never store upload URLs as published images or ask the user to run terminal commands.", input({ filename: { type: "string", maxLength: 255 }, purpose: string, variants: { type: "array", minItems: 1, items: input({ mimeType: string, byteSize: { type: "integer", minimum: 1 }, role: { type: "string", enum: ["primary", "alternate", "fallback"] } }, ["mimeType", "byteSize", "role"]) }, ...texts }, ["filename", "purpose", "variants"]), "create"),
142
- define("commit_media_upload", "Verify all uploaded variants and commit one permanent media asset. Store the returned asset id in fields referencing media_assets.id, never a temporary upload URL. Unknown or expired upload groups require a new create_media_upload.", input({ uploadGroupId: string, ...texts }, ["uploadGroupId"]), "commit"),
143
- define("list_media_assets", "List committed image assets, newest first, with their permanent variants and alt/caption. Search matches id, alt or caption; reuse only a returned cursor.", input({ limit: { type: "integer", minimum: 1, maximum: 500 }, cursor: string, search: string }), "list", true),
144
- define("get_media_asset", "Read one committed media asset and its permanent variant URLs.", input({ id: string }, ["id"]), "get", true),
145
- define("update_media_asset", "Update committed asset alt or caption; omitted fields remain unchanged and empty strings clear text.", input({ id: string, ...texts }, ["id"]), "update"),
146
- define("delete_media_asset", "Delete an asset's objects and library record. Check application references first. A partial failure can be retried using the same id.", input({ id: string }, ["id"]), "delete")
147
- ];
148
- }
149
-
150
- // src/domain/describe/targets.ts
151
- function inspectSql(value) {
152
- const schemas = /* @__PURE__ */ new Set(), readSchemas = /* @__PURE__ */ new Set();
153
- const writes = [];
154
- let readOnly = true;
155
- const walk = (v) => {
156
- if (!v || typeof v !== "object") return;
157
- const n = v;
158
- if (n.RangeVar?.mantle === "table") {
159
- schemas.add(n.RangeVar.relname.toLowerCase());
160
- readSchemas.add(n.RangeVar.relname.toLowerCase());
161
- }
162
- for (const kind of ["InsertStmt", "UpdateStmt", "DeleteStmt"]) if (n[kind]) {
163
- readOnly = false;
164
- if (n[kind].relation?.relname) writes.push({ schema: n[kind].relation.relname.toLowerCase(), operation: kind === "InsertStmt" ? "create" : kind === "UpdateStmt" ? "update" : "delete" });
165
- if (n[kind].relation?.relname) {
166
- schemas.add(n[kind].relation.relname.toLowerCase());
167
- if (kind !== "InsertStmt") readSchemas.add(n[kind].relation.relname.toLowerCase());
168
- }
169
- }
170
- Object.values(n).forEach(walk);
171
- };
172
- walk(value);
173
- return { schemas: [...schemas].sort(), readSchemas: [...readSchemas].sort(), readOnly, writes };
174
- }
175
- function mergeRestrict(policies, conjunctive) {
176
- return Object.fromEntries([...new Set(policies.flatMap((p) => Object.keys(p)))].sort().map((schema) => {
177
- const rows = policies.map((p) => p[schema] ?? { terms: [] });
178
- const terms = [...new Map(rows.flatMap((p) => p.terms).map((t) => [canonical(t), t])).values()].filter((t) => conjunctive || rows.every((p) => p.terms.some((v) => canonical(v) === canonical(t))));
179
- const owner = rows.find((p) => p.owner)?.owner;
180
- return [schema, { ...owner && (conjunctive || rows.every((p) => p.owner === owner)) ? { owner } : {}, terms }];
181
- }));
182
- }
183
- function inspectBehavior(plan, ir, opaque, requires, restrict, writes) {
184
- const inspected = inspectSql(ir);
185
- if (writes) {
186
- inspected.writes = [...writes];
187
- inspected.schemas.push(...writes.map((w) => w.schema));
188
- inspected.readSchemas.push(...writes.filter((w) => w.operation !== "create").map((w) => w.schema));
189
- }
190
- const dependencies = [], policies = [restrict ?? {}];
191
- const procedures = /* @__PURE__ */ new Set(), hooks = /* @__PURE__ */ new Set();
192
- const visit = (name) => {
193
- if (procedures.has(name)) return;
194
- procedures.add(name);
195
- const p = plan.procedures[name];
196
- if (!p) throw new TypeError(`Unknown dependency '${name}'.`);
197
- dependencies.push({ procedure: name, declaration: p });
198
- policies.push(p.restrict ?? {});
199
- opaque ||= "ref" in p.handler;
200
- const g = inspectSql("sql" in p.handler ? p.handler.sql.stmts : void 0);
201
- inspected.schemas.push(...g.schemas);
202
- inspected.readSchemas.push(...g.readSchemas);
203
- inspected.writes.push(...g.writes);
204
- inspected.readOnly &&= g.readOnly;
205
- if (p.requires?.guard) visit(p.requires.guard.procedure);
206
- };
207
- if (requires?.guard) visit(requires.guard.procedure);
208
- for (; ; ) {
209
- const before = hooks.size;
210
- for (const [id, t] of Object.entries(plan.triggers).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
211
- if (t.source.kind !== "lifecycle" || hooks.has(id)) continue;
212
- const source = t.source;
213
- if (!opaque && !inspected.writes.some((w) => w.schema === source.schema.toLowerCase() && source.on.some((on) => on.endsWith(`_${w.operation}`)))) continue;
214
- hooks.add(id);
215
- dependencies.push({ trigger: id, source });
216
- visit(t.procedure);
217
- }
218
- if (hooks.size === before) break;
219
- }
220
- const related = (names) => {
221
- const all = new Set(opaque ? Object.keys(plan.schemas) : names);
222
- for (const name of all) {
223
- const parent = plan.schemas[name]?.translates?.parent.toLowerCase();
224
- if (parent) all.add(parent);
225
- }
226
- return [...all].sort();
227
- };
228
- return {
229
- ...inspected,
230
- schemas: related(inspected.schemas),
231
- readSchemas: related(inspected.readSchemas),
232
- opaque,
233
- restrict: mergeRestrict(policies, true),
234
- dependencies
235
- };
236
- }
237
- var objectInput = { type: "object", properties: {} };
238
- var pathTemplate = (path) => path.replace(/\{([^}]+)\}/g, ":$1");
239
- var adminRoutes = [
240
- "GET /me",
241
- "GET /site",
242
- "GET /collections",
243
- "GET /views-manifest",
244
- "GET /operations",
245
- "GET /webmcp",
246
- "POST /webmcp/:tool",
247
- "GET /bootstrap",
248
- "GET /views/:name/export",
249
- "GET /views/:name",
250
- "GET /view-batch",
251
- "GET /developer/views/:name",
252
- "POST /operations/:name",
253
- "GET /developer-console",
254
- "GET /collections/:name/statistics",
255
- "GET /entries",
256
- "GET /entries/export",
257
- "GET /entries/:id",
258
- "POST /entries",
259
- "PATCH /entries/:id",
260
- "POST /entries/:id/publish",
261
- "POST /entries/:id/unpublish",
262
- "POST /entries/:id/archive",
263
- "DELETE /entries/:id",
264
- "GET /site-settings",
265
- "PATCH /site-settings",
266
- "POST /media/uploads",
267
- "POST /media/uploads/:groupId/commit",
268
- "GET /media",
269
- "GET /media/:id",
270
- "PATCH /media/:id",
271
- "DELETE /media/:id",
272
- "GET /admin",
273
- "GET /members",
274
- "PATCH /admin/:id/role",
275
- "POST /admin/invitations",
276
- "DELETE /admin/invitations/:id"
277
- ];
278
- function targetDefinitions(plan) {
279
- const out = [];
280
- const tools = [...mcpTools(plan, "public"), ...mcpTools(plan, "admin")];
281
- for (const [kind, entries] of [["view", plan.views], ["procedure", plan.procedures]]) {
282
- for (const [name, p] of Object.entries(entries).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
283
- const view = kind === "view" ? plan.views[name] : void 0;
284
- const proc = kind === "procedure" ? plan.procedures[name] : void 0;
285
- const ir = view?.stmts ?? (proc && "sql" in proc.handler ? proc.handler.sql.stmts : void 0);
286
- const inspected = inspectBehavior(plan, ir, !!proc && "ref" in proc.handler, p.requires, p.restrict);
287
- const tool = tools.find((t) => t.kind === kind && t.source === name);
288
- const title = resolveLocalizedText(p.title, "en", "en") ?? name;
289
- const triggers = Object.entries(plan.triggers).filter(([, t]) => kind === "procedure" && t.procedure === name).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([id, t]) => ({ id, source: t.source }));
290
- const surfaces = [.../* @__PURE__ */ new Set([
291
- ...view ? [view.surface, "admin", ...view.surface === "public" ? ["rest"] : []] : [],
292
- ...triggers.map((t) => t.source.kind === "mcp" ? t.source.surface : t.source.kind === "http" ? "rest" : t.source.kind)
293
- ])].sort();
294
- if (!surfaces.length) surfaces.push("internal");
295
- out.push({
296
- id: `${kind}:${name}`,
297
- name: tool?.name ?? mcpToolNameSegment(name),
298
- kind,
299
- surface: view?.surface ?? (surfaces.includes("public") || surfaces.includes("rest") ? "public" : surfaces.includes("admin") ? "admin" : "internal"),
300
- surfaces,
301
- triggers,
302
- title,
303
- description: resolveLocalizedText(p.description, "en", "en") ?? title,
304
- inputSchema: tool?.inputSchema ?? view?.input ?? proc?.input ?? objectInput,
305
- annotations: tool?.annotations ?? {},
306
- requires: p.requires,
307
- restrict: inspected.restrict,
308
- schemas: inspected.schemas,
309
- readSchemas: inspected.readSchemas,
310
- readOnly: !inspected.opaque && inspected.readOnly,
311
- opaque: inspected.opaque,
312
- trustedOnly: !!proc?.trustedOnly,
313
- variants: [],
314
- behavior: { declaration: p, dependencies: inspected.dependencies }
315
- });
316
- }
317
- }
318
- const addRoute = (id, dependencies, inputSchema = objectInput) => {
319
- const admin = id.startsWith("admin:");
320
- out.push({
321
- id,
322
- name: id.replace(/[^a-zA-Z0-9_-]/g, "_"),
323
- title: id,
324
- description: id,
325
- kind: admin ? "admin" : "rest",
326
- surface: admin ? "admin" : "public",
327
- surfaces: [admin ? "admin" : "rest"],
328
- triggers: dependencies.flatMap((d) => d.triggers),
329
- inputSchema,
330
- annotations: {},
331
- requires: admin ? { auth: { all: ["ctx.admin"] } } : dependencies[0]?.requires,
332
- restrict: mergeRestrict(dependencies.map((d) => d.restrict), false),
333
- schemas: [...new Set(dependencies.flatMap((d) => d.schemas))].sort(),
334
- readSchemas: [...new Set(dependencies.flatMap((d) => d.readSchemas))].sort(),
335
- readOnly: dependencies.length > 0 && dependencies.every((d) => d.readOnly) && (!admin || id.startsWith("admin:GET ") || id === "admin:POST /operations/:name" || id === "admin:POST /webmcp/:tool"),
336
- opaque: dependencies.some((d) => d.opaque),
337
- trustedOnly: dependencies.some((d) => d.trustedOnly),
338
- variants: dependencies,
339
- behavior: dependencies.map((d) => ({ id: d.id, behavior: d.behavior, surfaces: d.surfaces, triggers: d.triggers, input: d.inputSchema, requires: d.requires, restrict: d.restrict, trustedOnly: d.trustedOnly }))
340
- });
341
- };
342
- const declarations = [...out];
343
- const adminOperations = new Set(mcpTools(plan, "admin").filter((t) => t.kind === "procedure").map((t) => `procedure:${t.source}`));
344
- for (const d of declarations) if (d.kind === "view" && d.surfaces.includes("public")) addRoute(`rest:GET /api/views/${d.id.slice(5)}`, [d], d.inputSchema);
345
- for (const [, t] of Object.entries(plan.triggers).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) if (t.source.kind === "http") {
346
- const d = declarations.find((d2) => d2.id === `procedure:${t.procedure}`);
347
- addRoute(`rest:${t.source.method} ${pathTemplate(t.source.path)}`, [d], d.inputSchema);
348
- }
349
- const generated = [];
350
- for (const [schema, s] of Object.entries(plan.schemas).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) if (s.publishing) for (const [verb, from, to] of publishingTransitions) {
351
- addRoute(`procedure:${s.name}.${verb}`, [], publishingInput);
352
- const d = out.at(-1);
353
- const inspected = inspectBehavior(plan, void 0, false, void 0, void 0, [{ schema, operation: verb === "publish" ? "publish" : "update" }]);
354
- Object.assign(d, {
355
- kind: "procedure",
356
- name: mcpToolNameSegment(`${verb}_${s.name}`),
357
- surface: "admin",
358
- surfaces: ["admin"],
359
- requires: { auth: { all: ["ctx.admin"] } },
360
- schemas: [.../* @__PURE__ */ new Set([schema, ...inspected.schemas])].sort(),
361
- readSchemas: [.../* @__PURE__ */ new Set([schema, ...inspected.readSchemas])].sort(),
362
- opaque: inspected.opaque,
363
- behavior: { schema, verb, from, to, dependencies: inspected.dependencies }
364
- });
365
- generated.push(d);
366
- }
367
- for (const tool of mediaDefinitions()) {
368
- addRoute(`admin:tool:${tool.name}`, [], tool.inputSchema);
369
- const d = out.at(-1);
370
- Object.assign(d, { name: tool.name, readOnly: tool.annotations.readOnlyHint, opaque: !tool.annotations.readOnlyHint, annotations: tool.annotations, behavior: { tool, schemas: plan.schemas } });
371
- generated.push(d);
372
- }
373
- for (const route of adminRoutes) {
374
- const deps = route.includes("/views/:") || route === "GET /view-batch" ? declarations.filter((d2) => d2.kind === "view" && (route.includes("/developer/") || plan.views[d2.id.slice(5)]?.surface === "admin")) : route.includes("/operations/:") ? declarations.filter((d2) => adminOperations.has(d2.id)) : route === "POST /webmcp/:tool" ? declarations.filter((d2) => mcpTools(plan, "admin").some((t) => `${t.kind}:${t.source}` === d2.id)).concat(generated) : [];
375
- const generic = route.includes("/entries") || route.includes("/statistics") || route === "GET /bootstrap";
376
- addRoute(`admin:${route}`, deps, deps.length && route !== "GET /view-batch" ? { ...objectInput, anyOf: deps.map((d2) => ({ type: "object", properties: { [route.includes("/webmcp/") ? "tool" : "name"]: { type: "string", const: route.includes("/webmcp/") ? d2.name : d2.id.slice(d2.kind === "view" ? 5 : 10) } } })) } : objectInput);
377
- const d = out.at(-1);
378
- if (generic) {
379
- const operation = route === "POST /entries" ? "create" : route === "DELETE /entries/:id" ? "delete" : route.startsWith("GET ") ? void 0 : route.endsWith("/publish") ? "publish" : "update";
380
- const inspected = inspectBehavior(plan, void 0, false, void 0, void 0, operation ? Object.keys(plan.schemas).map((schema) => ({ schema, operation })) : []);
381
- d.schemas = Object.keys(plan.schemas).sort();
382
- d.readSchemas = d.schemas;
383
- d.opaque = inspected.opaque;
384
- d.behavior = { schemas: plan.schemas, dependencies: inspected.dependencies };
385
- }
386
- }
387
- return [...new Map(out.map((d) => [d.id, d])).values()].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
388
- }
389
-
390
- // src/domain/describe/target-hash.ts
391
- async function hash(value) {
392
- const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonical(value)));
393
- return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
394
- }
395
- function hashDefinition(plan, d) {
396
- return hash({
397
- id: d.id,
398
- surfaces: d.surfaces,
399
- triggers: d.triggers,
400
- behavior: d.behavior,
401
- input: d.inputSchema,
402
- requires: d.requires,
403
- restrict: d.restrict,
404
- dialect: plan.dialect,
405
- schemas: Object.fromEntries(d.schemas.map((name) => [name, plan.schemas[name]])),
406
- userFields: plan.access?.userFields,
407
- readOnly: d.readOnly,
408
- opaque: d.opaque,
409
- trustedOnly: d.trustedOnly
410
- });
411
- }
412
- async function targetHash(plan, targetId) {
413
- const target = targetDefinitions(plan).find((d) => d.id === targetId);
414
- if (!target) throw new TypeError(`Unknown target '${targetId}'.`);
415
- return hashDefinition(plan, target);
416
- }
417
-
418
- export {
419
- canonical,
420
- targetDefinitions,
421
- hash,
422
- hashDefinition,
423
- targetHash
424
- };
@@ -1,39 +0,0 @@
1
- # Recipe: an MCP App for members
2
-
3
- ## Native applications
4
-
5
- Read the installed MCP App contract. The SDK's generic hook attaches only the resources the app selects;
6
- only selected modules enter the service.
7
-
8
- ```ts
9
- import type { McpApps } from '@aotter/mantle/mcp';
10
- import { mcp } from '../generated/mcp.js';
11
- const memberApp: McpApps = { resources: [{
12
- uri: 'ui://shop/my-orders',
13
- name: 'my-orders-app',
14
- renders: ['my_orders'], // the tool for the declared public View my-orders
15
- html: async () => (await import('./member-app.js')).memberHtml,
16
- }] };
17
- const tools = mcp({ db, caller, handlers, apps: { public: memberApp } });
18
- ```
19
-
20
- Each endpoint accepts one contributor or an array. The separate
21
- `@aotter/mantle-admin` package contributes its admin App through this same
22
- `apps.admin` hook; it owns `ui://mantle-admin/…`. App resources use their own
23
- `ui://<app>/…` namespace. Duplicate resource URI/name, conflicting `renders`,
24
- or `renders`/`appOnly` naming an unavailable endpoint tool (or a Procedure for
25
- `appOnly`) fail when `mcp(...)` is built; with a request-bound tool table, the
26
- tool names are checked on the first matching request. Only clients that
27
- declare no MCP Apps support see no UI resources; a request that advertises no
28
- capabilities at all is treated as supported.
29
-
30
- Use `renders` for a declared tool; `appOnly` optionally hides it from non-UI
31
- clients. Import HTML lazily; it carries no secrets or caller data. Tool results
32
- use the caller's SQL policy, and an App cannot authorize a hidden admin tool.
33
- Generic interaction components and controllers remain in `@aotter/mantle-ui`;
34
- Admin-specific controllers/catalogues belong to `@aotter/mantle-admin`.
35
-
36
- Verify tools/list metadata, resources/read, actual tool calls and a host-rendered
37
- member App. Test anonymous/member isolation and resource conflicts. Never retry
38
- unknown writes blindly; retain OCC and re-read state after an uncertain outcome.
39
- Commit generated output and run generation with `--check` before deployment.
package/skills/.keep DELETED
File without changes
@@ -1,15 +0,0 @@
1
- # Local HTTP acceptance
2
-
3
-
4
- Generation with `--check` and typechecking prove compilation, not handler execution. Before reporting local readiness or deploying, run the application's existing dev command with isolated local data and a console/test email sender. Use a small application-owned Node `assert` + `fetch` script; no browser, deployment or helper server runner is required.
5
-
6
- Better Auth limits each email-OTP path to 3 requests/minute per IP; `authHardening` stores rate limits in the database. Local tests signing in several users should send a distinct `cf-connecting-ip` header per user (with that header configured in `ipAddressHeaders`), or wait out the window. With the console sender, the OTP appears in the Wrangler log.
7
-
8
- - Exercise actual HTTP Triggers for every custom Procedure handler: valid input/output, invalid input, anonymous refusal on protected operations, authorized synthetic caller and expected stored result. A status below 500 alone is not success; assert the expected status and JSON envelope/body. Use only the generated contract's routes, inputs and permissions.
9
- - For an app-owned `fetch`, exercise its real public routes, not just dist files. Assert HTML content type, visible content and metadata/canonical/JSON-LD with no JavaScript execution, sitemap, robots and an unknown URL returning 404. Create a local synthetic draft and assert it is hidden anonymously; publish, edit and delete it through authorized operations and check the page and sitemap after each change. Reuse the application's existing smoke-test pattern and clean up test rows/server in `finally`.
10
- - If the app serves Admin, run `npm run build` and then `node --test generated/admin-mcp-app.check.mjs` (the examples name it `npm test`): the generated `generated/admin-mcp-app.check.mjs` loads the Admin MCP App in an MCP host and fails on a network request, console error or exception. It needs `playwright-core` (a devDependency) and Chrome (`npx playwright-core install chromium` or `MANTLE_TEST_CHROMIUM`); with no browser it fails, so report it as blocked rather than skipped. See `docs/handbook/guides/mcp-apps.md`.
11
- - Keep caller authority explicit: use supplied `resolveCaller` and caller-scoped Store/Procedure APIs from the installed service-entry docs. Use the actual local host capabilities; local tests do not prove remote identity/provider wiring.
12
- - Account for each declared interface separately: verified with the command/identity/result, intentionally not exposed, or deferred/unverified with a reason. Generation proves declaration and artifacts, not route mounting. An early Admin preview or a WebMCP-only application may defer remote MCP; preserve that pending item in the application's existing README, handoff or test report, without a new status/configuration format.
13
- - For selected remote MCP, use the installed `docs/examples/app-shell/smoke-kit.mjs` `assertMcpDiscovery(endpoint, { tools, headers })` in the application's smoke scenario. Derive `tools` from the generated `mcp.ts` exports, or the pure static `developerConsole.interfaces.callable` in generated `admin-descriptors.ts`; filter to the selected surface and explicitly select the expected permitted subset when application authorization narrows it. Include app-composed tools when present. Do not count `describe.targets` or reconstruct authorization rules in the harness. Assert initialized discovery and all `tools/list` pages; a 404, HTML 200, protocol error or missing expected tools must fail. Keep anonymous/member denial assertions separate. Discovery never needs a business-write `tools/call`.
14
- - Supply test credentials from the application's normal authorized sign-in/test setup (the smoke kit's console OTP sign-in is valid). The discovery assertion only receives credentials: it must not acquire credentials itself, scrape other sessions or manufacture authority. Report local cookie-session discovery, OAuth Bearer/client integration and browser WebMCP as separate evidence. Cookie or WebMCP success does not prove remote OAuth/ChatGPT wiring; missing credentials leave that case unverified, not passed. Do not enable remote routes or OAuth merely to make acceptance pass.
15
- - Record commands and passed/failed/blocked cases for the exact commit. Fix failures before source admission; rerun relevant checks after changing source. Packing does not run these checks and an agent's checklist is not server build attestation.
@@ -1,41 +0,0 @@
1
- # Generated public websites
2
-
3
- Use this flow only when the installed package README documents concrete native
4
- queries and generated `web.ts`.
5
- Earlier Core versions use their own installed handbook. Read the installed
6
- `docs/handbook/reference/surface.md` and `docs/examples/generated-web/README.md` under `node_modules/@aotter/mantle/` before writing
7
- routes; the installed version is authoritative.
8
-
9
- Mount `web()` when the application serves public HTML and needs `llms.txt`, a
10
- sitemap or Markdown alternates. Import the generated `web.ts`; pass the native
11
- `db`, a verified Caller (or the app's resolver), the configured public `origin`,
12
- `title`, `description` and `pages`. Mount generated `rest.ts` for public View and
13
- HTTP Trigger APIs. Neither module renders the application's HTML.
14
-
15
- Each page template's `{parameter}` names exactly match its public View inputs;
16
- both its View and list View return these parameters. A list View accepts empty
17
- input. `markdown` names a textual column in the detail View. Supply the same
18
- SQL-visible data to the HTML renderer through generated Views or the typed Store.
19
- Never add a frontend status filter: generated SQL owns publishing, scope and TTL.
20
-
21
- In the Fetch entry, return a handled REST/web response, then render HTML for the
22
- remaining page routes. Insert `await site.head(request, env)` inside `<head>`:
23
- it supplies canonical and Markdown alternate tags from the current View row,
24
- using the configured origin rather than the request host. Escape all other
25
- application text. An unavailable page needs an application-owned 404; an empty
26
- `head()` result alone does not render that response.
27
-
28
- Verify locally before deploying:
29
-
30
- 1. Generate, typecheck, implement every listed ref handler and typecheck again.
31
- 2. Walk the public list's complete cursor chain. Its rows equal the `llms.txt`
32
- entries and sitemap detail URLs below the documented index caps.
33
- 3. Request each rendered page and assert its canonical uses the configured
34
- origin and its `.md` alternate returns current Markdown. Request hidden
35
- drafts, expired/scoped-out rows and malformed paths; they must not leak data.
36
- 4. Check GET/HEAD, 404 `no-store` and escaped HTML. Importing no web entry should
37
- keep web rendering code out of the application's bundle.
38
-
39
- The current caps are 5,000 scanned rows/10 queries/1 MiB for `llms.txt`, and
40
- 50,000 rows/100 queries/50 MiB for sitemap. Translation-aware indexes and
41
- hreflang are a documented follow-up; do not claim them from `locales[0]`.
@@ -1,49 +0,0 @@
1
- # Complete website defaults
2
-
3
- These are default generation and acceptance requirements for a complete website,
4
- unless the user explicitly changes the scope. They do not add Manifest keys or
5
- change Core's optional composition. Read the installed version's docs and the
6
- selected host's frontend kit before choosing APIs. Native applications use
7
- their installed `docs/handbook/guides/native-admin.md` and the
8
- [web contract](web.md).
9
-
10
- - For self-hosted applications, mount the selected Admin API and serve the
11
- separate Admin package's assets with its shell/CSP contract. The application
12
- owns auth, OTP delivery and verified Caller wiring; use a console/test sender
13
- locally and configure production email for deployment. If a host already
14
- supplies identity/Admin, reuse its explicit contract and reserved routes.
15
- Add visitor sign-in where needed. Backend permissions enforce roles and data
16
- access; host membership alone is not application admin authority.
17
- - Integrate image selection/upload into content editing through the existing
18
- Admin media library and the application's selected storage. Follow the pinned Core upload/PUT/commit
19
- contract and `x-mantle-ref: media_assets` plus `x-mcp-hint: media-image`; store committed asset IDs, resolve permanent variants in the frontend, never use an ID as an image URL. The application's `/mcp/admin` supplies `get_media_upload_policy` → `create_media_upload` → direct HTTP PUT → `commit_media_upload` when its shared media adapter is wired. Follow the installed media authentication contract; host project credentials are not application credentials. Upload capability URLs are temporary,
20
- not committed public images; anonymous visitors must not gain admin media access.
21
- - Generate an accessible, responsive public frontend by reading the installed
22
- generated `web.ts` and REST contracts; browser code on static hosts uses
23
- same-origin fetch, not a server surface imported into the browser. Do not install the historical standalone
24
- `@aotter/mantle-web` or claim Core supplies an HTML renderer. The application-owned Fetch entry renders HTML; follow the selected host's static/template restrictions.
25
- - Public content follows the installed `docs/handbook/guides/public-pages.md`
26
- (the one list of head tags, JSON-LD, languages, Markdown, sitemap, robots and
27
- `llms.txt`). For public content on static hosts disable SPA fallback and check unknown URLs
28
- return 404. Follow the selected host's permanent origin contract. Missing
29
- final origin blocks origin-dependent checks.
30
- Structured data must match visible content; do not fabricate facts or promise
31
- ranking or AI citation.
32
- - Verify that content publication, edits and deletion update the crawlable page
33
- and sitemap. If static generation requires a rebuild, explain the publishing
34
- dependency; a dynamic blog is blocked until that path exists. Do not silently
35
- substitute build-time sample content for live tenant content.
36
-
37
- Follow the [local HTTP acceptance](local-testing.md) before reporting local
38
- readiness: inspect HTML without JavaScript, check metadata/sitemap and anonymous
39
- draft/permission refusal, and exercise OTP and editing with isolated synthetic
40
- accounts. Local readiness does not require publication, a hosted tenant or a
41
- candidate preview. Report production email, public origin and real object-store
42
- upload/commit checks separately when local capabilities cannot exercise them.
43
-
44
- For an explicitly requested deployment, use the selected host's native workflow
45
- and any preview it provides. Verify disabled preview capabilities on an already
46
- authorized live target; synthetic writes and cleanup stay within that scope.
47
- Pending production checks block a production-readiness claim, not an otherwise
48
- passing local-development handoff. Preserve preview isolation and permissions;
49
- report passed, failed and blocked checks for each environment separately.