@aotter/mantle 0.2.0-alpha.10 → 0.2.0-alpha.11

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 (121) hide show
  1. package/dist/auth/adapter.js +1 -28
  2. package/dist/auth/admin.js +2 -5
  3. package/dist/auth/caller.js +0 -27
  4. package/dist/auth/consent.js +100 -0
  5. package/dist/auth/hardening.js +0 -7
  6. package/dist/auth/identity.js +0 -11
  7. package/dist/auth/oauth.js +0 -32
  8. package/dist/auth/resource-seeds.js +0 -2
  9. package/dist/auth/route-http.js +33 -0
  10. package/dist/auth/routes.js +5 -151
  11. package/dist/build/compiler.d.ts +12 -611
  12. package/dist/build/compiler.js +3 -7029
  13. package/dist/bun/assets.js +0 -2
  14. package/dist/domain/adminOrder.js +0 -2
  15. package/dist/domain/columns.js +0 -15
  16. package/dist/domain/describe/chunk-SGMRVDGE.js +1 -0
  17. package/dist/domain/describe/index.js +1 -115
  18. package/dist/domain/describe/target-hash.js +1 -12
  19. package/dist/domain/entry.js +0 -14
  20. package/dist/domain/generated-tools.js +0 -1
  21. package/dist/domain/input.js +0 -2
  22. package/dist/domain/paging.js +0 -17
  23. package/dist/domain/recipes.js +0 -2
  24. package/dist/domain/userReferences.js +0 -1
  25. package/dist/domain/wireScalars.js +0 -6
  26. package/dist/infrastructure/adminEntries.js +9 -6
  27. package/dist/infrastructure/adminPaging.js +0 -2
  28. package/dist/infrastructure/artifacts.js +0 -4
  29. package/dist/infrastructure/authSchema.js +0 -9
  30. package/dist/infrastructure/bundle.js +0 -29
  31. package/dist/infrastructure/cli.js +0 -7
  32. package/dist/infrastructure/generate.js +0 -14
  33. package/dist/infrastructure/generateAuth.js +1 -9
  34. package/dist/infrastructure/help.js +0 -5
  35. package/dist/infrastructure/init.js +0 -4
  36. package/dist/infrastructure/inputs.js +0 -9
  37. package/dist/infrastructure/mounts.js +0 -3
  38. package/dist/infrastructure/wrangler.js +0 -6
  39. package/dist/kernel/authorization.js +0 -1
  40. package/dist/kernel/binds.js +1 -11
  41. package/dist/kernel/caller-identity.d.ts +0 -3
  42. package/dist/kernel/caller-identity.js +0 -15
  43. package/dist/kernel/caller-mode.d.ts +4 -0
  44. package/dist/kernel/caller-mode.js +14 -0
  45. package/dist/kernel/contracts.d.ts +1 -9
  46. package/dist/kernel/contracts.js +1 -42
  47. package/dist/kernel/cron.js +0 -4
  48. package/dist/kernel/dialect/common.js +0 -3
  49. package/dist/kernel/dialect/postgres.js +0 -2
  50. package/dist/kernel/dialect/sqlite.js +1 -6
  51. package/dist/kernel/drain.js +2 -8
  52. package/dist/kernel/driver/bun-sqlite.js +0 -4
  53. package/dist/kernel/driver/d1.js +0 -1
  54. package/dist/kernel/driver/postgres.js +0 -15
  55. package/dist/kernel/driver/transaction-context.js +0 -1
  56. package/dist/kernel/email.js +0 -4
  57. package/dist/kernel/host-access.js +2 -3
  58. package/dist/kernel/host-caller.d.ts +1 -1
  59. package/dist/kernel/host-caller.js +3 -6
  60. package/dist/kernel/json-body.js +0 -2
  61. package/dist/kernel/mcp-apps.js +0 -5
  62. package/dist/kernel/mcp-call.js +0 -1
  63. package/dist/kernel/mcp-handler.js +2 -26
  64. package/dist/kernel/mcp.js +0 -5
  65. package/dist/kernel/media.js +0 -18
  66. package/dist/kernel/paging.js +1 -9
  67. package/dist/kernel/publishing.js +1 -2
  68. package/dist/kernel/r2-media.js +0 -7
  69. package/dist/kernel/rest-dispatch.js +0 -1
  70. package/dist/kernel/rest-router.js +2 -3
  71. package/dist/kernel/rest.js +0 -3
  72. package/dist/kernel/scalars.js +1 -9
  73. package/dist/kernel/search.js +0 -1
  74. package/dist/kernel/site.js +0 -2
  75. package/dist/kernel/store.js +1 -26
  76. package/dist/kernel/surface-failure.d.ts +10 -0
  77. package/dist/kernel/surface-failure.js +29 -0
  78. package/dist/kernel/surface.js +0 -3
  79. package/dist/kernel/sweep.js +0 -1
  80. package/dist/kernel/translations.js +0 -1
  81. package/dist/kernel/user-caller.js +0 -6
  82. package/dist/kernel/user-fields.d.ts +1 -1
  83. package/dist/kernel/user-fields.js +1 -11
  84. package/dist/kernel/validation.js +0 -8
  85. package/dist/kernel/web-gateway.js +0 -2
  86. package/dist/kernel/web-head.js +0 -1
  87. package/dist/kernel/web-index.js +0 -1
  88. package/dist/kernel/web-response.js +0 -1
  89. package/dist/kernel/web-serve.js +97 -0
  90. package/dist/kernel/web.d.ts +1 -1
  91. package/dist/kernel/web.js +10 -91
  92. package/dist/kernel/where.js +0 -6
  93. package/dist/usecase/adminBindings.js +28 -21
  94. package/dist/usecase/adminClient.js +0 -21
  95. package/dist/usecase/adminDescriptors.js +2 -9
  96. package/dist/usecase/generate.js +0 -6
  97. package/dist/usecase/handlers.js +0 -3
  98. package/dist/usecase/mcp-media.js +0 -1
  99. package/dist/usecase/mcp.js +0 -4
  100. package/dist/usecase/paging.js +0 -2
  101. package/dist/usecase/publishing.js +0 -4
  102. package/dist/usecase/rest.js +0 -1
  103. package/dist/usecase/site.js +0 -1
  104. package/dist/usecase/stores.js +1 -13
  105. package/dist/usecase/tool-version.js +0 -3
  106. package/dist/usecase/translations.js +0 -2
  107. package/dist/usecase/validation.js +0 -20
  108. package/docs/examples/README.md +1 -1
  109. package/docs/examples/app-shell/package.json +3 -3
  110. package/docs/examples/app-shell/src/routes.ts +3 -1
  111. package/docs/examples/app-shell/src/service.ts +2 -1
  112. package/docs/handbook/cloudflare/service-entry.md +3 -0
  113. package/docs/handbook/examples/hub.md +1 -1
  114. package/docs/handbook/guides/agent-setup.md +4 -4
  115. package/docs/handbook/guides/public-pages.md +6 -0
  116. package/docs/handbook/reference/admin-descriptors.md +4 -1
  117. package/docs/handbook/reference/schema.md +1 -1
  118. package/docs/handbook/releases/index.md +3 -3
  119. package/package.json +2 -2
  120. package/skills/README.md +1 -1
  121. package/dist/domain/describe/chunk-ZVXBGP7Y.js +0 -424
@@ -14,7 +14,6 @@ export function emitRest(plan, lowered, target, description) {
14
14
  const views = [], procedures = [];
15
15
  let hasWire = false;
16
16
  const convert = (code) => code ? `, convert: ${code}` : "";
17
- // Each segment matches in its encoded form; `{name}` segments capture.
18
17
  const route = (method, path) => {
19
18
  const keys = [];
20
19
  const source = path.split("/").map(segment => segment.startsWith("{") && segment.endsWith("}") ? (keys.push(segment.slice(1, -1)), "([^/]+)") : encodeURIComponent(segment).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/");
@@ -1,4 +1,3 @@
1
- /** Generate the optional site/media module and deployment DDL, never runtime preparation. */
2
1
  import { assertSiteDefaultsCanonical, canonicalizeLocaleList, expandPolicyRequired } from "../build/compiler.js";
3
2
  import { driverFor, isSqlite, q } from "../domain/entry.js";
4
3
  export function emitSite(target, defaults, refuse, schemaNames) {
@@ -1,24 +1,14 @@
1
- /**
2
- * One typed collection per Schema, over the generic `createStore`. Core's row rules (`rowPolicy`, `writePolicy`) are evaluated
3
- * here, at build time, ONCE for a placeholder scope and TTL into the shared `Rules`; a Schema adds only a `Fact` (scope, TTL,
4
- * publishing, its date-time and date columns). Generation is refused if a Schema's expanded rules differ from Core's own for it
5
- * (caller and trusted rows, insert fills, update sets and lock, delete lock).
6
- * Column names, value types, writable columns and `orderBy` are TypeScript types only: nothing at runtime knows them.
7
- */
8
1
  import { NATIVE_NOT_NULL, notNullColumns, rowPolicy, writePolicy } from "../build/compiler.js";
9
2
  import { declaredType, inputType, nativeType } from "../domain/columns.js";
10
3
  import { q } from "../domain/entry.js";
11
4
  import { expand, SCOPE, TTL } from "../kernel/store.js";
12
5
  import { refChecks, userScopeTerm } from '../build/compiler.js';
13
- // Types the typed Store compares and writes as plain bind values. Fixed numerics are scaled integers on SQLite; geo is two columns.
14
6
  const SCALAR = new Set(["text", "real", "float8", "integer", "int4", "int8", "bool", "timestamptz", "date"]);
15
- /** Core's own rules for a Schema, in the shape of `Rules` without the id expression (a delete's lock is compared separately). */
16
7
  const core = (s, schemas) => {
17
8
  const update = writePolicy(s, "update", { lock: true });
18
9
  return { caller: rowPolicy(s, "caller", schemas), trusted: rowPolicy(s, "trusted", schemas), fills: writePolicy(s, "insert").fills, sets: update.sets, lock: update.lock };
19
10
  };
20
11
  const probe = { scope: SCOPE, ttl: TTL, ttlSeconds: 0, publishing: true };
21
- /** @internal */
22
12
  export function emitStores(schemas, isSqlite, newId, refuse, required = false) {
23
13
  const all = Object.values(schemas).sort((a, b) => a.name < b.name ? -1 : 1);
24
14
  if (!all.length && !required)
@@ -28,7 +18,7 @@ export function emitStores(schemas, isSqlite, newId, refuse, required = false) {
28
18
  for (const s of all) {
29
19
  const type = (column) => declaredType(s.fields, column) ?? (column === "version" ? "integer" : column === "published_at" ? "timestamptz" : "text");
30
20
  const declared = Object.keys(s.fields).filter(f => f !== s.scope && s.fields[f] !== "geo");
31
- const geo = Object.keys(s.fields).filter(f => s.fields[f] === "geo").flatMap(f => [`${f}_lat`, `${f}_lng`]); // `*` returns a geo field's two columns
21
+ const geo = Object.keys(s.fields).filter(f => s.fields[f] === "geo").flatMap(f => [`${f}_lat`, `${f}_lng`]);
32
22
  const names = [...["id", "version", "created_at", "updated_at", "author_id", ...(s.publishing ? ["status", "published_at"] : [])], ...declared];
33
23
  const required = new Set(s.schema.required ?? []), notNull = notNullColumns(s);
34
24
  const nonNull = (column) => NATIVE_NOT_NULL.has(column) || notNull.has(column);
@@ -45,11 +35,9 @@ export function emitStores(schemas, isSqlite, newId, refuse, required = false) {
45
35
  ...(ref.length ? { ref } : {}),
46
36
  ...(parent ? { parent: { on: s.translates.on.toLowerCase(), ...(parent.scope ? { scope: [parent.scope, s.scope] } : {}), table: s.translates.parent.toLowerCase(), visible: rowPolicy(s, "caller", schemas).find(t => t[1] === "parent"), caller: rowPolicy(parent, "caller", schemas), trusted: rowPolicy(parent, "trusted", schemas), fields: inherited, ts: inherited.filter(c => parent.fields[c] === "timestamptz"), d: inherited.filter(c => parent.fields[c] === "date") } } : {}),
47
37
  };
48
- // the shared rules, with this Schema's scope and TTL put in, are Core's rules for it: or the runtime would enforce something else
49
38
  const expected = q([core(s, schemas), writePolicy(s, "delete", { lock: true }).lock]), got = q([expand(rules, fact), rules.lock]);
50
39
  if (expected !== got)
51
40
  refuse(`schemas/${s.name}`, `Core's row rules for this Schema differ from the shared rules (${expected} vs ${got}); the generic Store cannot serve it.`);
52
- // ORDER BY is allowed where the caller's scoped read can walk an index (its first column, or the one after the scope column) and the column is NOT NULL.
53
41
  const indexed = new Set([...(s.publishing ? [[...(s.scope ? [s.scope] : []), "published_at", "id"]] : []), ...(s.indexes ?? []), ...(s.unique ?? [])].map(i => i[0] === s.scope ? i[1] : i[0]).filter((c) => !!c));
54
42
  const key = (c) => /^[A-Za-z_]\w*$/.test(c) ? c : q(c);
55
43
  const of = (columns, value) => `{ ${columns.map(c => `${key(c)}: ${value(c)};`).join(" ")} }`;
@@ -1,8 +1,5 @@
1
- /** Generate-time tool versions: a stale host copy shows the model an older version than results carry. */
2
1
  import { canonical, fnv } from "../build/compiler.js";
3
- /** A hash of what a catalogue lists, so an unchanged definition keeps its version across deploys. */
4
2
  export const versionOf = (value) => fnv(canonical(value));
5
- /** A listed tool: its version, and its description (each locale's) ending with that version. */
6
3
  export function versioned(tool) {
7
4
  const v = versionOf(tool), mark = (text) => `${text} (tool version ${v})`;
8
5
  return { ...tool, description: typeof tool.description === "string" ? mark(tool.description) : Object.fromEntries(Object.entries(tool.description).map(([locale, text]) => [locale, mark(text)])), version: v };
@@ -1,12 +1,10 @@
1
1
  import { userReferences } from "../domain/userReferences.js";
2
- /** Translation helpers use generated SQL policy, never a request-time manifest. */
3
2
  import { rowPolicy } from "../build/compiler.js";
4
3
  import { renderTerm } from "../kernel/store.js";
5
4
  import { dialect as SQLITE } from "../kernel/dialect/sqlite.js";
6
5
  import { dialect as POSTGRES } from "../kernel/dialect/postgres.js";
7
6
  import { declaredType } from "../domain/columns.js";
8
7
  import { driverFor, isSqlite, q } from "../domain/entry.js";
9
- /** The parent is checked by the same atomic UPDATE that publishes the child. */
10
8
  export function publishedParent(plan, child, d, recipes, trusted) {
11
9
  const tr = child.translates, parent = plan.schemas[tr.parent.toLowerCase()], on = tr.on.toLowerCase();
12
10
  const conditions = [`p.${d.ident(on)} = ${d.ident(child.name)}.${d.ident(on)}`];
@@ -1,4 +1,3 @@
1
- /** Compile boundary checks into each operation; no schema enters the request bundle. */
2
1
  import { INPUT_RULE_KEYWORDS, lookupLocalRef as lookup, rootRefChain } from "../build/compiler.js";
3
2
  import { literal, q } from "../domain/entry.js";
4
3
  import { publishingInput } from "./publishing.js";
@@ -6,14 +5,9 @@ import { equal } from "../kernel/validation.js";
6
5
  const pointer = (key) => key.replace(/~/g, "~0").replace(/\//g, "~1");
7
6
  const supported = new Set([..."$defs $ref $schema $id type nullable properties required additionalProperties items enum const format pattern minLength maxLength minimum maximum exclusiveMinimum exclusiveMaximum multipleOf minItems maxItems title description default readOnly writeOnly examples deprecated x-mantle-ref x-mcp-hint".split(" "), ...INPUT_RULE_KEYWORDS]);
8
7
  const shapes = "see docs/handbook/reference/surface.md";
9
- // YAML .inf and .nan parse to numbers that JSON writes as null.
10
8
  const finite = (value) => typeof value === "number" ? Number.isFinite(value) : !!value && typeof value === "object" ? Object.values(value).every(finite) : true;
11
9
  const has = (key) => `Object.hasOwn(object,${q(key)})`;
12
10
  const exactly = (value, keys) => !!value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).sort().join(" ") === keys;
13
- /**
14
- * An object schema's rules, each failing `when` (JavaScript on `object`) with its sentence at the missing field `key`, or the
15
- * reason a rule has an unsupported shape.
16
- */
17
11
  function rules(schema) {
18
12
  const declared = (names) => Array.isArray(names) && names.length > 0 && names.every(name => typeof name === "string" && Object.hasOwn(schema.properties ?? {}, name));
19
13
  const list = (names) => names.map(name => `'${name}'`).join(" and ");
@@ -34,7 +28,6 @@ function rules(schema) {
34
28
  if (condition !== undefined || then !== undefined || otherwise !== undefined) {
35
29
  const [key] = exactly(condition, "properties required") ? Object.keys(condition.properties) : [];
36
30
  const value = key === undefined ? undefined : condition.properties[key];
37
- // Without required: [key], an absent key satisfies `if`.
38
31
  if (key === undefined || !exactly(condition.properties, key) || !exactly(value, "const") || !(["string", "boolean"].includes(typeof value.const) || Number.isFinite(value.const) || value.const === null) || q(condition.required) !== q([key]) || !declared([key]))
39
32
  return `if is supported only as exactly { properties: { <field>: { const: <string, finite number, boolean or null> } }, required: [<field>] }, so an absent field does not satisfy it; ${shapes}.`;
40
33
  if (then === undefined && otherwise === undefined || [then, otherwise].some(branch => branch !== undefined && !(exactly(branch, "required") && declared(branch.required))))
@@ -47,19 +40,12 @@ function rules(schema) {
47
40
  }
48
41
  return found;
49
42
  }
50
- /** Whether an input schema accepts `{}`: a list View is read with no input, so web indexes need this. */
51
43
  export function acceptsEmpty(input) {
52
44
  if (input === undefined)
53
45
  return true;
54
- // Validation applies a root `$ref`'s target too, so each schema along the chain must accept `{}`; an unresolved or cyclic chain does not.
55
- // dependentRequired needs a present key; an if needs its field, so `{}` meets only an else. An enum or const must admit `{}`.
56
46
  return rootRefChain(input)?.every(schema => !schema.required?.length && schema.anyOf === undefined && !schema.else?.required?.length
57
47
  && (schema.enum === undefined || schema.enum.some(option => equal({}, option))) && (schema.const === undefined || equal({}, schema.const)) && (schema.type === undefined || [schema.type].flat().includes("object"))) ?? false;
58
48
  }
59
- /**
60
- * An input schema as an AI tool catalogue advertises it: some model APIs refuse rules (a root `anyOf`, `if`), so each rule leaves
61
- * the schema and becomes a sentence of the description.
62
- */
63
49
  export function advertised(input, description) {
64
50
  if (!input)
65
51
  return { inputSchema: input, description };
@@ -68,7 +54,6 @@ export function advertised(input, description) {
68
54
  if (!node || typeof node !== "object")
69
55
  return;
70
56
  const schema = node;
71
- // An MCP View tool keeps each rule set of its root `$ref` chain as an `allOf` branch over the merged properties.
72
57
  const branches = Array.isArray(schema.allOf) ? schema.allOf : [];
73
58
  for (const branch of [schema, ...branches.map(branch => ({ ...branch, properties: schema.properties }))]) {
74
59
  const found = rules(branch);
@@ -111,9 +96,7 @@ export function emitValidation(plan, refuse = (path, message) => { throw Error(`
111
96
  files[file] = `import { transition } from "@aotter/mantle/validation";\nexport const validate = (raw: unknown) => transition(${q(base)}, raw);\n`;
112
97
  continue;
113
98
  }
114
- // Only a local `$ref`, or more than 100 nested objects and arrays, can reach the nesting limit: only then is `depth` passed.
115
99
  const recursive = json.includes('"$ref"') || json.split(/[[{]/).length > 101, depth = recursive ? ",depth+1" : "";
116
- // Identical bodies share one function.
117
100
  const nodes = new Map(), bodies = new Map(), functions = [], formats = new Set(), patterns = new Map();
118
101
  let equality = false;
119
102
  const resolve = (ref) => {
@@ -142,7 +125,6 @@ export function emitValidation(plan, refuse = (path, message) => { throw Error(`
142
125
  refuse(base, `Unsupported input type '${type}'.`);
143
126
  if (types.length)
144
127
  lines.push(typeCheck(types));
145
- // After a single type check `fail` (never) has narrowed `value`: that type's guard is implied.
146
128
  const only = types.length === 1 ? types[0] : undefined;
147
129
  const guard = (type, test = `(${condition(type)})`) => only === type || type === "number" && only === "integer" ? "" : test + " && ";
148
130
  if (schema.enum) {
@@ -197,7 +179,6 @@ export function emitValidation(plan, refuse = (path, message) => { throw Error(`
197
179
  }
198
180
  if (schema.items)
199
181
  lines.push(`${only === "array" ? "" : "if (Array.isArray(value)) "}for (let i=0;i<value.length;i++) ${emit(schema.items)}(value[i],path+"/"+i${depth});`);
200
- // Several keys that need no JSON-pointer escaping are checked by one loop, in the same order.
201
182
  const required = schema.required ?? [], checks = Object.entries(schema.properties ?? {}).map(([key, child]) => [key, emit(child)]);
202
183
  const loop = (keys) => keys.length > 1 && keys.every(key => pointer(key) === key);
203
184
  const object = loop(required) ? [`for (const key of ${q(required)}) if (!Object.hasOwn(object,key)) fail(path+"/"+key,"Required field is missing.");`] : required.map(key => `if (!Object.hasOwn(object,${q(key)})) fail(path+${q("/" + pointer(key))},"Required field is missing.");`);
@@ -217,7 +198,6 @@ export function emitValidation(plan, refuse = (path, message) => { throw Error(`
217
198
  if (object.length)
218
199
  lines.push(`${only === "object" ? "" : `if (${condition("object")}) `}{ const object = value as Record<string,unknown>;\n${object.join("\n")}\n}`);
219
200
  const body = lines.join("\n");
220
- // Without recursion nothing calls a check before its body is complete, so an identical body's function serves it.
221
201
  const fn = recursive ? named : bodies.get(body) ?? named;
222
202
  if (fn === named) {
223
203
  bodies.set(body, fn);
@@ -39,5 +39,5 @@ shell's allow-all `src/signup.ts`, unless sign-up should be invite-only. Purchas
39
39
 
40
40
  Every example uses two roles: `admin` (the admin layer) and `user`.
41
41
 
42
- [Reference service](https://github.com/aotter/mantle/tree/v0.2.0-alpha.10/docs/examples/reference-service) is the release smoke test for packed packages, not a starting point. Its source lives in the tagged repository; it is not included in the npm docs.
42
+ [Reference service](https://github.com/aotter/mantle/tree/v0.2.0-alpha.11/docs/examples/reference-service) is the release smoke test for packed packages, not a starting point. Its source lives in the tagged repository; it is not included in the npm docs.
43
43
  Projects on 0.1.x move with [the upgrade guide](../upgrade-0.1-to-0.2.md).
@@ -15,8 +15,8 @@
15
15
  "accept": "node accept.mjs"
16
16
  },
17
17
  "dependencies": {
18
- "@aotter/mantle": "0.2.0-alpha.10",
19
- "@aotter/mantle-ui": "0.2.0-alpha.10",
18
+ "@aotter/mantle": "0.2.0-alpha.11",
19
+ "@aotter/mantle-ui": "0.2.0-alpha.11",
20
20
  "@better-auth/cimd": "1.7.7",
21
21
  "@better-auth/core": "1.7.7",
22
22
  "@better-auth/mcp": "1.7.7",
@@ -25,7 +25,7 @@
25
25
  "@modelcontextprotocol/ext-apps": "2.0.0",
26
26
  "@modelcontextprotocol/server": "2.2.0",
27
27
  "better-auth": "1.7.7",
28
- "@aotter/mantle-admin": "0.2.0-alpha.10",
28
+ "@aotter/mantle-admin": "0.2.0-alpha.11",
29
29
  "react": "19.3.0",
30
30
  "react-dom": "19.3.0"
31
31
  },
@@ -2,7 +2,9 @@ import type { Caller } from "@aotter/mantle/contracts";
2
2
  import { signInPage } from "./sign-in.js";
3
3
 
4
4
  /**
5
- * App-owned routes, tried before Admin, MCP and REST. Return undefined to fall through.
5
+ * App-owned routes, tried before REST. Return undefined to fall through. Paths under /admin and /mcp never reach here, and
6
+ * neither do Better Auth's (/api/auth/*, /oauth/consent*, /.well-known/oauth-*). A root catch-all page (/:slug) must
7
+ * still return undefined for the paths of the app's own HTTP Triggers (REST), or it shadows them.
6
8
  * GET /sign-in: member sign-in (src/sign-in.ts); a member page sends anonymous visitors there with signInRedirect.
7
9
  * AGENT: add visitor pages and callbacks/webhooks here. Visitor pages carry everything in `mantle help public-pages`
8
10
  * (canonical, Open Graph, JSON-LD, answer block, .md alternates, sitemap.xml, robots.txt, llms.txt; recipes example).
@@ -43,7 +43,8 @@ export async function fetchService(request: Request, env: Env, context: Executio
43
43
  const ownAuth = await withAuthTasks(context.waitUntil.bind(context), () => authRoutes(auth!, { methods: [{ kind: "email-otp" }], resolver: caller })(request));
44
44
  if (ownAuth) return respond(ownAuth);
45
45
  const url = new URL(request.url);
46
- const own = await appRoutes(request, env, { caller, waitUntil: context.waitUntil.bind(context) });
46
+ // /admin and /mcp belong to Admin and MCP: a root catch-all page in src/routes.ts (/:slug) never shadows them.
47
+ const own = /^\/(?:admin|mcp)(?:\/|$)/.test(url.pathname) ? undefined : await appRoutes(request, env, { caller, waitUntil: context.waitUntil.bind(context) });
47
48
  if (own) return respond(own);
48
49
  let response: Response | undefined;
49
50
  if (url.pathname === "/admin/api" || url.pathname.startsWith("/admin/api/")) {
@@ -81,6 +81,9 @@ export default {
81
81
  if (shell) return shell;
82
82
  // Your public frontend/web.ts routes can use an anonymous Caller without
83
83
  // loading auth; their generated SQL still controls publication visibility.
84
+ // Mounted here, after every Mantle surface, a root catch-all page (/:slug)
85
+ // cannot shadow /admin, /mcp or REST. Mount them earlier only behind a
86
+ // guard that skips those paths, as the app shell does.
84
87
  return new Response('Not found', { status: 404, headers: { 'cache-control': 'no-store' } });
85
88
  } catch (error) { return surfaceFailure(error); }
86
89
  });
@@ -2,4 +2,4 @@
2
2
 
3
3
  Use the [Worker quickstart](../start/quickstart-worker.md) for a complete native manifest and entry, [typed queries](../guides/typed-queries.md) for embedding, and [service composition](../cloudflare/service-entry.md) for optional surfaces.
4
4
 
5
- The runnable [reference service](https://github.com/aotter/mantle/tree/v0.2.0-alpha.10/docs/examples/reference-service) exercises the native package integration. See the [generated public website](../../examples/generated-web/README.md) for a native Worker example.
5
+ The runnable [reference service](https://github.com/aotter/mantle/tree/v0.2.0-alpha.11/docs/examples/reference-service) exercises the native package integration. See the [generated public website](../../examples/generated-web/README.md) for a native Worker example.
@@ -13,21 +13,21 @@ carries.
13
13
  The Mantle plugin carries one OSS authoring skill, `mantle`, and local
14
14
  acceptance references. It does not configure a hosted-service connection.
15
15
  Install from the Git tag matching the exact SDK version you selected (currently
16
- `v0.2.0-alpha.10`). Alpha releases integrate on `develop`; an unqualified
16
+ `v0.2.0-alpha.11`). Alpha releases integrate on `develop`; an unqualified
17
17
  `aotter/mantle` source follows `main` and can install older, incompatible
18
18
  instructions. A skill install does not upgrade the application's SDK.
19
19
 
20
20
  For Codex, pin the marketplace checkout:
21
21
 
22
22
  ```sh
23
- codex plugin marketplace add aotter/mantle --ref v0.2.0-alpha.10
23
+ codex plugin marketplace add aotter/mantle --ref v0.2.0-alpha.11
24
24
  codex plugin add mantle@mantle
25
25
  ```
26
26
 
27
27
  For Claude Code, first clone the release into a directory you will keep:
28
28
 
29
29
  ```sh
30
- git clone --depth 1 --branch v0.2.0-alpha.10 https://github.com/aotter/mantle.git mantle-plugin
30
+ git clone --depth 1 --branch v0.2.0-alpha.11 https://github.com/aotter/mantle.git mantle-plugin
31
31
  ```
32
32
 
33
33
  Then, in Claude Code, use that checkout's absolute path:
@@ -41,7 +41,7 @@ Cursor and GitHub Copilot use their plugin manifests from the same tagged
41
41
  checkout. Without a plugin host, install only the consumer skill by name:
42
42
 
43
43
  ```sh
44
- npx skills add https://github.com/aotter/mantle/tree/v0.2.0-alpha.10 --skill mantle
44
+ npx skills add https://github.com/aotter/mantle/tree/v0.2.0-alpha.11 --skill mantle
45
45
  ```
46
46
 
47
47
  When upgrading the SDK, update the marketplace ref or local tagged checkout and
@@ -11,6 +11,12 @@ here instead of repeating it.
11
11
  - **App-owned pages** in `src/routes.ts`, when the page needs its own layout, JSON-LD or languages. The reference
12
12
  implementation is `node_modules/@aotter/mantle/docs/examples/recipes` (`src/pages.ts`, `src/routes.ts`; `smoke.mjs` checks every item below).
13
13
 
14
+ ## URLs
15
+
16
+ Mantle owns `/admin`, `/mcp`, `/api/auth/`, `/oauth/consent` and `/.well-known/oauth-*`, and the app's HTTP Triggers own
17
+ their declared paths. Give visitor pages their own prefix (`/{locale}/…`, `/teas/{slug}`), or make a root page (`/{slug}`)
18
+ return nothing for those paths; the app shell already keeps `/admin` and `/mcp` away from `src/routes.ts`.
19
+
14
20
  ## Every public page
15
21
 
16
22
  - Server-rendered HTML per URL with the content in the body. A client-only SPA shell or metadata set by script is not
@@ -37,7 +37,10 @@ execution plan or SQL AST, storage access or execution observer is included.
37
37
  admin tool names. A conflicting declared admin tool is refused.
38
38
  - `webmcp` carries static tool declarations and Admin page routes. `bootstrap`
39
39
  combines `collections`, `operations`, `views` and `webmcp`; the handler adds
40
- request-owned `me`, `site` and any selected entries page.
40
+ request-owned `me`, `site` and any selected entries page. Its
41
+ `developerConsole` is a loader for the same snapshot, generated again in
42
+ `admin-developer-console.ts`, so Admin imports it on a developer-console
43
+ request instead of at Worker startup.
41
44
 
42
45
  Generation reuses the 0.2 projection and SQL-logic helpers. View columns come
43
46
  from the compiled plan (Core's `viewOutputs` provenance, including aliases,
@@ -94,7 +94,7 @@ For inserts, one allowed partition autofills a missing value; multiple values re
94
94
 
95
95
  #### References to a scoped Schema
96
96
 
97
- A non-null `x-mantle-ref` value that points to a scoped Schema (field or owner scope) must name a row of that Schema the caller can read, matched on `id` or on the object form's `field`, and that row must have the same value as the child for every scope field the two share (`ADR-0034`: a reference between scoped Schemas stays inside one owner or partition). A Store insert or update, and a Procedure `INSERT … VALUES`, `UPDATE … SET` or `DO UPDATE SET`, check it in the same statement; a missing, unreadable or other-partition parent behaves like a missing row: a single-row statement returns HTTP 409 (`CONFLICT`), and an `ON CONFLICT` insert or a set update writes nothing for that row. A caller holding several values or `{ all: true }` can still not point a child in one partition at a parent in another. `INSERT … SELECT` that writes such a reference runs only for system callers. A caller's `UPDATE … SET` or `DO UPDATE SET` of such a reference takes no subquery, which could read the parent's columns as its own. Trusted writes (system callers, `runtime.store`) are not checked, like the other row rules, and native Admin's generic create and patch do not check references yet. NULL is always allowed; deleting a parent does not touch its children.
97
+ A non-null `x-mantle-ref` value that points to a scoped Schema (field or owner scope) must name a row of that Schema the caller can read, matched on `id` or on the object form's `field`, and that row must have the same value as the child for every scope field the two share (`ADR-0034`: a reference between scoped Schemas stays inside one owner or partition). A Store insert or update, a Procedure `INSERT … VALUES`, `UPDATE … SET` or `DO UPDATE SET`, and native Admin's generic create and patch check it in the same statement; a missing, unreadable or other-partition parent behaves like a missing row: a single-row statement returns HTTP 409 (`CONFLICT`), and an `ON CONFLICT` insert or a set update writes nothing for that row. A caller holding several values or `{ all: true }` can still not point a child in one partition at a parent in another. `INSERT … SELECT` that writes such a reference runs only for system callers. A caller's `UPDATE … SET` or `DO UPDATE SET` of such a reference takes no subquery, which could read the parent's columns as its own. Trusted writes (system callers, `runtime.store`) are not checked, like the other row rules. NULL is always allowed; deleting a parent does not touch its children.
98
98
 
99
99
  ## Reserved entry columns
100
100
 
@@ -17,10 +17,10 @@ Pin every package you use to the same exact version and upgrade them together.
17
17
 
18
18
  ## 0.2.0 — in preparation
19
19
 
20
- ### Upgrading to 0.2.0-alpha.10
20
+ ### Upgrading to 0.2.0-alpha.11
21
21
 
22
22
  Pin every selected `@aotter/mantle`, `@aotter/mantle-ui` and
23
- `@aotter/mantle-admin` dependency to `0.2.0-alpha.10`, refresh the lockfile,
23
+ `@aotter/mantle-admin` dependency to `0.2.0-alpha.11`, refresh the lockfile,
24
24
  then regenerate and check the application's owned artifacts. Keep existing
25
25
  production migrations application-owned; generated initial DDL is for an empty
26
26
  database. This alpha includes breaking Admin authoring and paging changes.
@@ -112,7 +112,7 @@ Existing alpha.6 applications require a source migration:
112
112
  application code. Review [authorization and Caller wiring](../concepts/runtime-and-adapters.md)
113
113
  and regenerate all owned artifacts before running `generate --check`.
114
114
 
115
- The [reference Worker](https://github.com/aotter/mantle/tree/v0.2.0-alpha.10/docs/examples/reference-service) and
115
+ The [reference Worker](https://github.com/aotter/mantle/tree/v0.2.0-alpha.11/docs/examples/reference-service) and
116
116
  [app shell](../../examples/app-shell/) demonstrate the current composition.
117
117
  The following earlier-alpha notes are historical; the current handbook and
118
118
  the native migration above supersede their runtime and CLI instructions.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aotter/mantle",
3
- "version": "0.2.0-alpha.10",
3
+ "version": "0.2.0-alpha.11",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "engines": {
@@ -140,7 +140,7 @@
140
140
  "registry": "https://registry.npmjs.org"
141
141
  },
142
142
  "scripts": {
143
- "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.lib.json && node scripts/bundle-compiler.mjs && node scripts/prune-declarations.mjs",
143
+ "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.lib.json && node ../../scripts/strip-comments.mjs tsconfig.lib.json && node scripts/bundle-compiler.mjs && node scripts/prune-declarations.mjs",
144
144
  "typecheck": "tsc --noEmit -p tsconfig.json",
145
145
  "test": "bun test tests/*.test.ts && bun run test:compiler",
146
146
  "generate": "bun src/infrastructure/cli.ts",
package/skills/README.md CHANGED
@@ -27,7 +27,7 @@ release tag as the application's SDK; alpha releases on `develop` can be newer
27
27
  than the default `main` branch. For a skill-only installation:
28
28
 
29
29
  ```sh
30
- npx skills add https://github.com/aotter/mantle/tree/v0.2.0-alpha.10 --skill mantle
30
+ npx skills add https://github.com/aotter/mantle/tree/v0.2.0-alpha.11 --skill mantle
31
31
  ```
32
32
 
33
33
  The explicit skill name excludes repository maintainer skills. The manifests