@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
@@ -12,8 +12,12 @@ A ref handler is application code called as `(ctx, input)`. Generation prints re
12
12
 
13
13
  `target: { schema, id, version? }` binds an Admin operation to a row. SQL targets can be inferred for a single locked row operation; declare a target for a ref operation when needed. Input fields must have the required shapes.
14
14
 
15
+ ## MCP tool hints
16
+
17
+ Every generated AI-facing tool (MCP, WebMCP, publishing and media tools) carries a `title`, `readOnlyHint` and `destructiveHint`; clients such as Claude use them to decide between auto-approval and confirmation. The title is the declared `title`, else the name made readable (`add-item` reads `Add item`). A View is `readOnlyHint: true, destructiveHint: false`. A Procedure is `readOnlyHint: false`, and `destructiveHint` is `false` only when its SQL inserts without overwriting: any DELETE or UPDATE, an `ON CONFLICT DO UPDATE` or a ref handler makes it `true`. Of the generated publishing transitions only `archive` is destructive. The media tools always carry `openWorldHint: true`; the upload policy, list and get tools are read-only and only `delete_media_asset` is destructive. Admin's WebMCP navigation tools (`admin_get_context`, `admin_navigate`) are read-only. `spec.mcp` accepts `readOnlyHint`, `destructiveHint` and `openWorldHint`, and a declaration always wins; a declared `readOnlyHint: true` defaults `destructiveHint` to `false`. Outside the media tools `openWorldHint` appears only when declared. `idempotentHint` is never generated, and `spec.mcp` refuses it. Hints never change authorization.
18
+
15
19
  Generated requires checks gate calls. Boundary input validators are generated separately; direct queries accept driver-ready, app-validated input. Dynamic guards and lifecycle hooks are rejected. See [native execution](../guides/native-execution.md) and [surface contracts](surface.md).
16
20
 
17
21
  SQL INSERT/upsert into a user-field scoped Schema is system-only. For a verified user, insert through `ctx.store` so membership and single-partition autofill run before the native write. `ctx.store` speaks storage names, not the manifest's: the handler's `input` uses authored names (`input.parentId`), while Store writes, filters and rows use the folded column (`insert({ parentid: input.parentId })`, `where: { parentid }`, `row.parentid`); generated Store types show the exact names ([typed queries](../guides/typed-queries.md)). SET cannot change a field-scope column, including ON CONFLICT DO UPDATE. Scoped unique conflict targets gain the owner/field prefix automatically.
18
22
 
19
- Triggers are the only way request data reaches a Procedure: REST and MCP validate the input against the Procedure's schema, then call it. App code may also call a generated Procedure directly (`import run from "../generated/procedures/<name>.js"`, `await run(db, caller, input)`); `requires`, row policy and checks still apply, but input validation does not, so pass only data the app produced or has verified (a cron job, a callback after its signature check). A page that takes user input submits it to the Procedure's REST Trigger; see `docs/examples/purchasing/src/requests-page.ts`.
23
+ Triggers are the only way request data reaches a Procedure: REST and MCP validate the input against the Procedure's schema, then call it. App code may also call a generated Procedure directly (`import run from "../generated/procedures/<name>.js"`, `await run(db, caller, input)`); `requires`, row policy and checks still apply, but input validation does not, so pass only data the app produced or has verified (a cron job, a callback after its signature check). A page that takes user input submits it to the Procedure's REST Trigger; see `node_modules/@aotter/mantle/docs/examples/purchasing/src/requests-page.ts`.
@@ -54,7 +54,17 @@ Owner and field columns prefix every unique index in that order. User reads/writ
54
54
 
55
55
  ## Indexes, checks and search
56
56
 
57
- Indexes are ordered composite scalar/native columns. A paged View's `ORDER BY` must match an index on the base Schema after any fixed equality prefix, ending with `id` as the tiebreak: `ORDER BY createdAt DESC, id DESC` needs `indexes: [[createdAt, id]]` (generation reports `Missing index for ORDER BY ...` otherwise). Required operational fields are NOT NULL; publishing drafts may be incomplete. Store sortable types use indexed/non-null columns, with explicit nullable published_at support.
57
+ Indexes are ordered composite scalar/native columns. A paged View's `ORDER BY` must match an index on the base Schema after any fixed equality prefix ([index prefixes](#index-prefixes-for-paged-views)), ending with `id` as the tiebreak: `ORDER BY created_at DESC, id DESC` needs `indexes: [[createdAt, id]]` (generation reports `Missing index for ORDER BY ...` otherwise). Required operational fields are NOT NULL; publishing drafts may be incomplete. Store sortable types use indexed/non-null columns, with explicit nullable published_at support.
58
+
59
+ ### Index prefixes for paged Views
60
+
61
+ Only equality fixes an index prefix: `col = input.x`, `col = 'literal'` and an `= auth.uid()` scope or restrict. A user-field term (`region IN auth.user('region')`, as a `scope` or `restrict`) binds a set of values, so it never fixes a prefix, even when the caller holds one value or `{ all: true }`: the statement is prepared for a set, and an `ORDER BY` after that column would read and sort all of the caller's rows on every page. With `scope: "region IN auth.user('region')"`, `indexes: [[region, orderNo, id]]` and `ORDER BY orderNo`, generation refuses the View, names the bounded order and links here. The bounded ways:
62
+
63
+ - `ORDER BY region, orderNo`, served by the same index: rows come back grouped by region. `region DESC, orderNo DESC` reads it backwards; on PostgreSQL confirm with `EXPLAIN` that no Sort remains. Every column added to the `ORDER BY` must be non-null and nameable in SQL; a `publishing` Schema field other than a user-field scope column can be null, and the `scope` owner column cannot be named, so the refusal then says this order is unavailable.
64
+ - A View input bound with equality, `WHERE region = input.region ORDER BY orderNo`: the input fixes the prefix, and the scope still applies. Every set-bound column before the ordered ones needs its own equality.
65
+ - For `{ all: true }` callers, an index on the equality-fixed columns, then the ordered columns, then `id`: `[orderNo, id]` here, `[tier, orderNo, id]` with `WHERE tier = input.tier`. It is not bounded for scoped callers, who scan the index and filter, and it does not help a View with any `restrict`, which [`{ all: true }` never lifts](view.md#restrict).
66
+
67
+ Declaring both indexes does not make one View bounded for every caller. System (trusted) reads drop scope terms, so a scope-first index such as `[owner, at]` is not a proved range scan for them.
58
68
 
59
69
  `uniqueIndexes` accept declared fields only, never native entry columns such as `authorId`. For per-member uniqueness, declare a required string `member` field, write `member: caller.subject` in a handler, and use `uniqueIndexes: [[member, orderNumber]]`; do not take `member` from client input.
60
70
 
@@ -68,9 +78,9 @@ The runtime never creates, alters or checks tables. Initial DDL is for an empty
68
78
 
69
79
  A property marked `readOnly: true` in the JSON Schema is server-maintained (a counter, a total): Admin's generic create stores its `default` whatever the input says, Admin edits ignore it, and only Procedures (or server code through the Store) change it afterwards. JSON Schema defaults are applied by Admin and the Store, not by the database: a SQL Procedure that inserts the row sets the field itself. A root `readOnly: true` makes the whole Schema read-only in Admin.
70
80
 
71
- `fields.<name>.widget/options` select supported controls or extension contributions; `list.primaryField`, columns/cells and enum `filterField` select list presentation; `nav.standalone/parentField` control child navigation; `nav.group/order/hidden` place it in the sidebar ([sidebar](../guides/native-admin.md#sidebar)); panels select record sidebars. Generation validates these against declared fields/indexes and selected extension declarations. JSON Schema root `schema.readOnly: true` disables generic Admin writes, not declared Procedures; `uiSchema.readOnly` is rejected with `SCHEMA_UI_INVALID`. `x-mantle-ref` relates a field to another Schema id or single-field unique field.
81
+ `fields.<name>.widget` selects a built-in control (`textarea`); `list.primaryField`, `list.columns` and enum `filterField` select list presentation; `nav.parentField` chooses the parent a child folds under (required when two or more required `id` references exist) and `nav.standalone` lists the child in the sidebar; `nav.group/order/hidden` place it in the sidebar ([sidebar](../guides/native-admin.md#sidebar)). `list.columns` must not repeat `list.primaryField` or itself (`SCHEMA_UI_INVALID`). Generation validates these against declared fields/indexes. Components that render a field, cell or record aside are not manifest references: they live in `defineAdmin` ([customising Admin](../guides/native-admin.md#customising-admin)), and a manifest that still names one (`widget: <extension>/<contribution>`, `widget` `options`, `list.cells`, `panels`) is refused. JSON Schema root `schema.readOnly: true` disables generic Admin writes, not declared Procedures; `uiSchema.readOnly` is rejected with `SCHEMA_UI_INVALID`. `x-mantle-ref` relates a field to another Schema id or single-field unique field.
72
82
 
73
- See [Admin](../guides/admin-ui.md), [extensions](../guides/admin-extensions.md) and [diagnostics](diagnostics.md).
83
+ See [Admin](../guides/admin-ui.md) and [diagnostics](diagnostics.md).
74
84
 
75
85
  ### Field scope
76
86
 
@@ -30,16 +30,36 @@ The generated `validation/views/<name>.ts` and
30
30
  also export `query(URLSearchParams)` returning `{ input, limit?, cursor? }`.
31
31
  REST and MCP use the same generated checks, with no runtime schema interpreter.
32
32
  Supported checks are types/unions and nullable, required/properties/additionalProperties,
33
- arrays/items and length bounds, enum, local `$defs`/`$ref`, string length/pattern,
34
- numeric bounds/`multipleOf`, and date/date-time/email. The deliberately permissive
33
+ arrays/items and length bounds, enum, `const`, local `$defs`/`$ref`, string length/pattern,
34
+ numeric bounds/`multipleOf`, and the formats `date`, `date-time`, `email` and `uri`. The deliberately permissive
35
35
  email check requires exactly one `@`, non-empty local and domain parts, and no
36
36
  whitespace; it does not require a dot, verify deliverability, or implement full
37
- RFC mailbox syntax. Integers must be safe; fractional
38
- `multipleOf` uses a 1e-9 quotient tolerance. `const`, `oneOf`, `uniqueItems` and
39
- object property-count bounds are refused, alongside other unsupported keywords
40
- and formats. Defaults remain
37
+ RFC mailbox syntax. `uri` means an absolute `http:` or `https:` URL, stricter than
38
+ JSON Schema's any-scheme URI, so `javascript:` and `data:` links fail. Integers must be safe; fractional
39
+ `multipleOf` uses a 1e-9 quotient tolerance. Defaults remain
41
40
  annotations and are not inserted into request input.
42
41
 
42
+ View and Procedure input objects (not Schemas or outputs) also take three rules
43
+ between their declared properties, each failing with one `INPUT_VALIDATION_FAILED`
44
+ error whose message names the rule:
45
+
46
+ - `dependentRequired: { weight: [unit] }`: "'unit' is required when 'weight' is
47
+ present.", at the missing field.
48
+ - `anyOf` whose every branch is exactly `{ required: [...] }`: "At least one of
49
+ 'weight', 'heightCm' is required.", at the object.
50
+ - `if: { properties: { undo: { const: true } }, required: [undo] }` with `then`
51
+ and/or `else` each exactly `{ required: [...] }`: "'item' is required unless
52
+ 'undo' is true.", at the missing field. `if` must list its field in `required`:
53
+ otherwise an absent field would satisfy it.
54
+
55
+ Any other `anyOf`/`if`/`then`/`else`, `oneOf`, `uniqueItems`, object property-count
56
+ bounds and other keywords and formats are refused at generation. The rules only
57
+ validate: types, SQL binds and Admin forms keep the declared fields, and Admin
58
+ shows the server's error at its field. `describe` keeps them as written; MCP and
59
+ WebMCP tools advertise the schema without them and append each rule's sentence to
60
+ the tool description, since some model APIs refuse these keywords in a tool's
61
+ input schema. [Trigger](trigger.md#input-rules) has an example.
62
+
43
63
  Native `paged/views/<name>.ts` has a default export, callable as
44
64
  `page(db, caller, input, { limit?, cursor? })` after importing it as `page`,
45
65
  answering `{ rows, nextCursor? }`.
@@ -56,10 +76,18 @@ their contracts. Native integer bigint cursor keys are serialized separately.
56
76
  REST/MCP use separate `paged/wire/views/<name>.ts` SQL when declared integers
57
77
  require exact transport projections. Web pages and internal paged Views keep
58
78
  driver-native rows. Internal paging creates no public route/tool.
59
- Authored SQL `LIMIT` caps each page, up to 500, rather than the whole cursor walk.
79
+ Authored SQL `LIMIT` bounds the complete ordered row set, including across cursor pages.
80
+ Use `limit` in the paging options to choose each page size (up to 500); it does not expand the View's declared result set.
81
+ An MCP View page keeps the `limit` of 1 to 500 but holds only as many of those rows
82
+ as fit 60,000 characters of JSON, so a result stays under host truncation limits. It
83
+ is cut before the first row that overflows and its next cursor resumes at that row.
84
+ A single row over the budget is returned alone with the cursor, and a View without an
85
+ order stays one whole page; a page still over the budget carries a notice that hosts
86
+ may truncate it.
60
87
  A pageable Schema View has a complete NOT NULL scalar tuple ending in the
61
88
  compiler's `id` tiebreak. Its ordered columns must match an index after fixed
62
- equality prefixes (including caller scope); multiple columns and mixed directions
89
+ equality prefixes; a user-field `IN` scope or restrict is not one (see
90
+ [index prefixes](schema.md#index-prefixes-for-paged-views)); multiple columns and mixed directions
63
91
  retain the authored order. CTE projections preserve the proof; joins must have a
64
92
  proved singleton side, or equality on the joined Schema's `id` or single-field
65
93
  unique key, so the base id cannot fan out. LEFT JOIN accepts nullable or missing
@@ -132,7 +160,10 @@ const tags = await site.head(request, env); // HTML string, empty for unknown/mi
132
160
  ```
133
161
 
134
162
  Page parameter names must exactly match the View's declared input properties.
135
- Page and list Views must return the page parameters; list Views must accept empty input. Markdown
163
+ Page and list Views must return the page parameters; list Views must accept empty input: no
164
+ `required`, no root `anyOf`, `enum` or `const`, no `else` rule, and a root `type`, when given, that includes `object`.
165
+ These conditions also apply to each schema a root `$ref` chain reaches, and an unresolved or cyclic chain fails them.
166
+ `web()` throws a `TypeError` for any other list View. Markdown
136
167
  must name a textual output column. HTML remains application-owned.
137
168
 
138
169
  `/llms.txt` and `/sitemap.xml` traverse each list's `nextCursor`; `.md` and head
@@ -169,6 +200,19 @@ transport loads on the first matching request. Pass `handlers` when ref
169
200
  Procedures are declared, as for REST. Admin tools are listed only on the admin
170
201
  endpoint, and publishing visibility belongs to the generated SQL.
171
202
 
203
+ `apps.public` and `apps.admin` accept a `McpAppsSource` or an array mixing static
204
+ contributors with async `({ env, request }) => McpApps` factories. Static resources
205
+ are checked once at construction; factories resolve only after the endpoint's
206
+ origin and caller gates, and their resource identities and ownership are checked
207
+ on each request even when the tool names are unchanged.
208
+
209
+ Discovery is caller-independent by default (ADR-0014); listing a tool grants no
210
+ execution authority. Configure `authorize` to filter `tools/list` and the tool
211
+ names supplied to resource HTML loaders: that mode also excludes tools refused
212
+ by declared identity, scope and `check` requirements. Without `authorize`, the
213
+ complete endpoint catalogue is not a permission-filtered catalogue. Operation
214
+ validation, guards and credential checks still apply to calls.
215
+
172
216
  The app resolves bearer tokens with `resolveCaller(request, auth, { audience:
173
217
  new URL('/mcp', request.url).href })` (both surfaces use `<origin>/mcp`). The app
174
218
  must also mount Better Auth's `/.well-known/oauth-protected-resource/mcp`
@@ -176,7 +220,8 @@ metadata route. Auth challenges point to that route. Presented bearer credential
176
220
  need the `mcp` scope; cookie sessions use the same-origin mutation checks.
177
221
 
178
222
  Generation emits `mcp.ts` only for a non-internal View or MCP Trigger. These
179
- surfaces typecheck without the `@modelcontextprotocol/server` 2.1.0 peer.
223
+ surfaces typecheck without the optional `@modelcontextprotocol/server` peer;
224
+ install the exact version declared in the installed SDK's `peerDependencies`.
180
225
  The peer loads by dynamic import on the first matching `/mcp` request; if
181
226
  missing, that request fails at runtime. Publishing alone does not select MCP;
182
227
  on a selected
@@ -207,15 +252,42 @@ as an explicit option.
207
252
  ### MCP catalogue and callbacks
208
253
 
209
254
  `mcp({ db, caller, handlers?, locale?, instructions?, serverInfo?, requiredScopes?,
210
- resourceMetadata?, onObservation?, onRefusal? })` keeps the SDK behind its first
255
+ resourceMetadata?, allowedOrigins?, onObservation?, onRefusal? })` keeps the SDK behind its first
211
256
  request. Locale defaults to `en`; localized titles/descriptions fall back to
212
- English, then their first entry. Views advertise `readOnlyHint`; Procedures use
213
- their declared MCP annotations. `instructions` is returned by initialize.
257
+ English, then their first entry. Tools carry the [tool hints](procedure.md#mcp-tool-hints). `instructions` is returned by initialize.
214
258
  The scope floor defaults to `["mcp"]` for every credential except cookie sessions;
215
259
  `requiredScopes: []` disables that floor. An anonymous closed surface challenges
216
260
  from initialize, and every call in a batch is checked before any call runs.
217
261
  `resourceMetadata` overrides the challenge URL for reverse proxies.
218
262
 
263
+ Both endpoints answer a request without `Origin`, which is what server-side MCP
264
+ clients send; a bearer credential needs none. A cookie session is ambient authority, so
265
+ `checkOrigin` also guards it: on a request that is not GET, HEAD or OPTIONS, at least one of
266
+ `Origin` and `Sec-Fetch-Site` must be present and every present one must agree (`Origin` equal
267
+ to the request's own origin; `Sec-Fetch-Site` `same-origin` or `none`), else `AUTH_DENIED` at
268
+ `request:origin`. On MCP it runs only for a tool call that executes (not a read-only call from an
269
+ App host), not for initialize or `tools/list`; `allowedOrigins` does not lift it. A present `Origin` marks a browser: it must name a loopback host
270
+ (`localhost`, `127.0.0.1`, `[::1]`, any port) or an `allowedOrigins` entry, else
271
+ 403 before authentication on every credential; `Origin: null` is refused. The
272
+ site's own origin is not implied, because a DNS-rebinding page sends an `Origin`
273
+ equal to the `Host` it rebound, so a browser client served from your deployed
274
+ site lists that origin: `allowedOrigins: ["https://app.example"]`. Entries are
275
+ compared by hostname as the official SDK does (not scheme or port); each is a
276
+ full origin (`https://console.example`) or the SDK's bare host
277
+ (`console.example`, `localhost:8787`), and anything else, such as a path, throws
278
+ when `mcp(...)` is configured; so does a wildcard such as `*` or
279
+ `https://*.example.com`, since entries are matched by exact hostname. `Host` is
280
+ not checked. Cookie writes also need the full same-origin check.
281
+
282
+ `/mcp` sends no CORS headers. `allowedOrigins` therefore serves pages on the
283
+ same origin as the endpoint, or clients behind a proxy that forwards their
284
+ `Origin`; a browser client on another origin also needs the app to answer
285
+ preflight and add CORS headers itself. Under `wrangler dev` with `routes` or
286
+ `dev.host`, Miniflare rewrites an `Origin` equal to the request's own origin to
287
+ the route or `dev.host` origin, and a DNS-rebinding request always has that
288
+ shape. Listing that host would admit rebinding against the local server, so keep
289
+ the route and `dev.host` hosts out of `allowedOrigins` in local development.
290
+
219
291
  `onObservation` receives request refusals and invocation attempt/completion
220
292
  metadata, including duration and outcome. Its Caller and scopes are frozen copies.
221
293
  `onRefusal` receives credential/origin refusals. Neither callback receives request
@@ -232,6 +304,44 @@ with an output schema they omit `structuredContent` to avoid validating an error
232
304
  against a success schema. Without one they also expose diagnostic structured
233
305
  content. MCP adds no prompts or non-UI resources.
234
306
 
307
+ ### Tool versions
308
+
309
+ Each generated tool (MCP, media, publishing and WebMCP) carries `version`, a
310
+ short hash computed by `mantle generate` of the definition as listed: name,
311
+ title, description without the marker, input schema (a View tool's with its
312
+ `limit` and `cursor` paging properties), output schema and annotations. A
313
+ localized title or description is hashed with all its locales. The description
314
+ ends with `(tool version <version>)`, so an unchanged tool keeps a
315
+ byte-identical description across deploys.
316
+
317
+ MCP results of a call that reaches the tool add a second text block,
318
+ `tool version <version>`; failures other than authorization refusals append
319
+ `; input schema <JSON>`, the input schema the tool is listed with.
320
+ `structuredContent` is unchanged. A `tools/call` naming a tool the endpoint
321
+ serves but the caller may not use is answered by the authentication or
322
+ authorization challenge (HTTP 401 or 403) before the SDK's transport and
323
+ request checks, even though that caller's `tools/list` omits the tool. A
324
+ `tools/call` naming a tool not in the endpoint's catalogue, in a batch or not
325
+ and from a host caller too, is answered with JSON-RPC error `-32602` and the
326
+ message
327
+ `No tool named "<name>" exists in the current catalogue (catalogue version <serverInfo.version>).`
328
+ For such a name the SDK's transport and request checks (headers, protocol
329
+ version, JSON-RPC shape) answer first, so a malformed request gets the same
330
+ refusal as one naming a served tool the caller may use.
331
+ Unless `serverInfo.version` is given, the kernel's `mcp()` sets it to
332
+ a hash of the `surface:name:version` of every tool the endpoint serves, before
333
+ per-caller filtering, so `/mcp` and `/mcp/admin` each report their own catalogue
334
+ version, and it changes when a generated tool's definition changes or a tool is
335
+ added or removed, however the tools are wired.
336
+
337
+ `POST /admin/api/webmcp/{tool}` returns `{ output, version }`. A refusal's
338
+ diagnostic adds `toolVersion`, and `inputSchema` unless it is an authorization
339
+ refusal; internal errors and refusals before the call carry neither. Admin's
340
+ WebMCP results repeat these as the same text block MCP adds. The navigation
341
+ tools `admin_get_context` and `admin_navigate` have no version. Tools an
342
+ application adds itself carry a version only if it sets one. Why: [MCP and
343
+ agents](../concepts/mcp-and-agents.md#when-a-host-shows-old-tools).
344
+
235
345
  ### Declared View scalars on the wire
236
346
 
237
347
  REST and MCP View pages expose declared JSON columns as JSON values and declared
@@ -28,8 +28,41 @@ spec:
28
28
 
29
29
  The target names a declared Procedure. `http` sources use POST/PUT/PATCH/DELETE and an `/api/` path. Path parameters bind to corresponding declared input fields and override JSON-body values. The generated REST module retains the Procedure's authorization and validates boundary input.
30
30
 
31
- An MCP source is `{ kind: mcp, surface: public }` or admin. The selected MCP module exposes the Procedure as a snake-case tool with its declared description/annotations and requires checks.
31
+ An MCP source is `{ kind: mcp, surface: public }` or admin. The selected MCP module exposes the Procedure as a snake-case tool with its description, [tool hints](procedure.md#mcp-tool-hints) and requires checks.
32
32
 
33
33
  Lifecycle and schedule sources are rejected at generation with `GENERATE_UNSUPPORTED`, including disabled schedules. Use an app-owned handler or scheduler. For signed webhooks, verify the original raw Request in the app and call a generated Procedure with validated input; do not route it through a JSON boundary before signature verification.
34
34
 
35
+ ## Input rules
36
+
37
+ The admin MCP tool below lists `reason` and `closed` without the rules, and its
38
+ description ends "'reason' is required when 'closed' is false."; a call with
39
+ `closed: false` and no `reason` fails with `INPUT_VALIDATION_FAILED` at
40
+ `procedures/set-ticket/input/reason` (the error path is
41
+ `procedures/<name>/input/<field>`). See [surface contracts](surface.md) for the
42
+ supported rules.
43
+
44
+ ```yaml
45
+ apiVersion: cms.mantle.aotter.net/v2
46
+ kind: Procedure
47
+ metadata: { name: set-ticket }
48
+ spec:
49
+ description: Open or close a ticket.
50
+ input:
51
+ type: object
52
+ required: [id, closed]
53
+ properties: { id: { type: string }, closed: { type: boolean }, reason: { type: string, maxLength: 200 } }
54
+ additionalProperties: false
55
+ if: { properties: { closed: { const: false } }, required: [closed] }
56
+ then: { required: [reason] }
57
+ output: { type: object }
58
+ handler: { sql: 'UPDATE tickets SET closed=input.closed WHERE id=input.id RETURNING id,closed' }
59
+ ---
60
+ apiVersion: cms.mantle.aotter.net/v2
61
+ kind: Trigger
62
+ metadata: { name: set-ticket-mcp }
63
+ spec:
64
+ source: { kind: mcp, surface: admin }
65
+ target: { procedure: set-ticket }
66
+ ```
67
+
35
68
  See [surfaces](surface.md) for mounted paths, error mapping and origin checks.
@@ -29,7 +29,7 @@ Store keeps `find`/`first` search and `count`. `count` scans every matching row;
29
29
 
30
30
  Each SELECT output needs a unique static name. Unquoted SQL identifiers fold to lower case; use an explicit quoted alias to preserve case (`r.reqStatus AS "reqStatus"`). Generation warns when an unaliased direct declared field would use its lower-case storage name. Declared-column lineage supplies generated types; computed outputs remain native/unknown. SQL formats declared date-time/date outputs, while computed timestamps retain driver values. See [native execution](../guides/native-execution.md).
31
31
 
32
- Bare `views/<encoded-name>.ts` returns row arrays. Proved pageable exposed/internal Views also receive native `paged/views/*`, returning `{ rows, nextCursor? }`. Selected REST/MCP boundaries use separate `paged/wire/views/*` SQL when declared integers require exact transport projections; bare modules also export `WireRow`. Indexed ordering after fixed equality prefixes, non-null typed keys, complete groups and singleton joins/lookups are checked at generation. Unsupported internal paging retains its bare View; exposure requires a supported page contract. Aggregates still scan/group their sources: inspect EXPLAIN.
32
+ Bare `views/<encoded-name>.ts` returns row arrays. Proved pageable exposed/internal Views also receive native `paged/views/*`, returning `{ rows, nextCursor? }`. Selected REST/MCP boundaries use separate `paged/wire/views/*` SQL when declared integers require exact transport projections; bare modules also export `WireRow`. Indexed ordering after fixed equality prefixes ([index prefixes](schema.md#index-prefixes-for-paged-views)), non-null typed keys, complete groups and singleton joins/lookups are checked at generation. Unsupported internal paging retains its bare View; exposure requires a supported page contract. Aggregates still scan/group their sources: inspect EXPLAIN.
33
33
 
34
34
  Publishing Schema fields may be NULL in drafts even when required, so public View paging cannot order by them. `published_at` is accepted by Store ordering but rejected as a View paging key. Use `ORDER BY id`, or keep the business date in an operational Schema as an indexed, required non-null field.
35
35
 
@@ -17,6 +17,78 @@ 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.8
21
+
22
+ Pin every selected `@aotter/mantle`, `@aotter/mantle-ui` and
23
+ `@aotter/mantle-admin` dependency to `0.2.0-alpha.8`, refresh the lockfile,
24
+ then regenerate and check the application's owned artifacts. Keep existing
25
+ production migrations application-owned; generated initial DDL is for an empty
26
+ database. This alpha includes breaking Admin authoring and paging changes.
27
+
28
+ 1. **Build output and host configuration.** `mantle bundle` writes server
29
+ modules to `dist/worker` and static files to `dist/client`, including
30
+ `dist/client/admin`. Rerun `mantle generate` with the application's `--entry`
31
+ and check the resulting Wrangler `main`, `base_dir`, asset directory and
32
+ build command. See [CLI and generated ownership](../start/project-and-cli.md).
33
+ 2. **Application-owned React.** Install React and React DOM in the application
34
+ at the same exact version (the examples use `19.3.0`). Admin takes them as
35
+ peers; a mismatched pair fails its build.
36
+ 3. **Replace extensions with application composition.** Move to
37
+ `defineAdmin(meta, { nav, pages, home, components, plugins, mcp })` in
38
+ `src/admin.tsx`. Remove `--admin-extensions`, `adminExtensions`,
39
+ `defineAdminExtension`, extension handlers and removed manifest widget,
40
+ `list.cells` and panel references. `navGroups` becomes `nav.groups`.
41
+ Use the [Admin migration table](../guides/native-admin.md#migrating-from-admin-extensions)
42
+ rather than carrying the former registration layer forward.
43
+ 4. **Build the MCP App with the application.** The prebuilt
44
+ `@aotter/mantle-admin/mcp-app` / `mantleAppHtml` export is gone. Use
45
+ `adminMcpApps({ catalogue: mcpAppCatalogues.admin })` with the generated
46
+ catalogue; it reads the built asset. Declare CSP once in `defineAdmin`.
47
+ The default resource URI hashes the built HTML, catalogue and metadata;
48
+ custom HTML functions need an explicit immutable URI. Load interactive
49
+ host HTML through authenticated `resources/read`, which embeds its
50
+ catalogue. See [MCP Apps](../guides/mcp-apps.md).
51
+ 5. **Run the generated compatibility check.** Run
52
+ `node --test generated/admin-mcp-app.check.mjs` after building. Install
53
+ the optional `playwright-core` peer and a browser, or set
54
+ `MANTLE_TEST_CHROMIUM`; a missing browser fails rather than skips. This
55
+ checks rendering with a synthetic catalogue, not application authorization
56
+ or completion of a Procedure action.
57
+ 6. **Keep the host graph safe.** The MCP build rejects `/admin/api` access,
58
+ cookies and undeclared remote loads. Its entry must be readable in Node.
59
+ An optional `src/admin.mcp.tsx` (or `buildAdmin({ mcpEntry })`) isolates
60
+ browser-only integrations; both roots can reuse one host-safe config,
61
+ components and CSS. Without it the whole shared entry graph is checked.
62
+ 7. **Distinguish total bounds from page sizes.** Authored SQL `LIMIT` now
63
+ bounds the complete ordered result set across cursor pages, including web
64
+ indexes. Use paging `options.limit` for page size; remove an authored limit
65
+ only if it was incorrectly being used as a page-size setting. MCP accepts
66
+ sizes of 1–500, but a response budget can return fewer rows: append pages
67
+ until `nextCursor` is absent, not until a short page. See
68
+ [View paging](../reference/surface.md).
69
+ 8. **Install build dependencies where the build runs.** Wrangler dev/deploy
70
+ runs its configured build command, so that environment needs
71
+ `node_modules`. The resulting built server/assets do not read
72
+ `node_modules` when served. See the
73
+ [application shell](../../examples/app-shell/README.md).
74
+
75
+ Record/collection cells, inputs, record actions, asides and `RecordView` are
76
+ now truthfully browser-only (`surface: "browser"`); remove unreachable MCP
77
+ branches. View cells remain `both`, and pages opt in through `pages[].mcp`.
78
+ A Procedure action's input is distinct from a collection record input: use a
79
+ Procedure target and MCP Trigger with the View's unchanged `id` and observed
80
+ `version` for native row actions. [Shared custom-page headers](../guides/mcp-apps.md#customising-admin-for-mcp-hosts)
81
+ can compose localized title, description and actions on both surfaces; omit the
82
+ option to keep prior behavior, or use `header: false` for an element-owned heading.
83
+
84
+ OAuth issuer verification now follows Better Auth's normalized URL while
85
+ preserving explicit issuer/JWKS settings. Cloudflare OAuth configurations need
86
+ `global_fetch_strictly_public` for public JWKS fetches; generated settings and
87
+ the official examples retain it. Native MCP Apps use the official ext-apps
88
+ client. Browser Admin now displays a localized explicit reload action when a
89
+ lazy page fails, rather than going blank; it does not reload automatically or
90
+ replay writes.
91
+
20
92
  ### Native SDK migration (alpha.7)
21
93
 
22
94
  Alpha.7 replaces the previous Core runtime and generated service contract.
@@ -32,7 +104,7 @@ Existing alpha.6 applications require a source migration:
32
104
  runs DDL at boot. Apply generated `schema.sql` only to an empty database;
33
105
  migrate existing data explicitly. PostgreSQL uses Bun.SQL on Bun and
34
106
  postgres.js on Workers. See [native execution](../guides/native-execution.md).
35
- - Install the separate `@aotter/mantle-admin` package when selecting Admin.
107
+ - Install the separate `@aotter/mantle-admin` package when selecting Admin, with `react` and `react-dom` in the application pinned to one identical exact version (Admin no longer installs them).
36
108
  Compose its [native bindings](../guides/native-admin.md) in the application.
37
109
  Bare queries retain driver-native values; selected REST/MCP/Admin boundaries
38
110
  own validation and declared-column wire conversion.
@@ -40,7 +112,7 @@ Existing alpha.6 applications require a source migration:
40
112
  application code. Review [authorization and Caller wiring](../concepts/runtime-and-adapters.md)
41
113
  and regenerate all owned artifacts before running `generate --check`.
42
114
 
43
- The [reference Worker](https://github.com/aotter/mantle/tree/v0.2.0-alpha.7/docs/examples/reference-service) and
115
+ The [reference Worker](https://github.com/aotter/mantle/tree/v0.2.0-alpha.8/docs/examples/reference-service) and
44
116
  [app shell](../../examples/app-shell/) demonstrate the current composition.
45
117
  The following earlier-alpha notes are historical; the current handbook and
46
118
  the native migration above supersede their runtime and CLI instructions.
@@ -9,7 +9,7 @@ Mantle compiles a closed v2 manifest into native SQL and typed TypeScript. The a
9
9
  | --- | --- |
10
10
  | `@aotter/mantle` | SDK, private compiler, generator and `mantle` CLI |
11
11
  | `@aotter/mantle-ui` | Pure UI kit and generic interaction controller; no SDK dependency |
12
- | `@aotter/mantle-admin` | Admin server, SPA, extensions, preview and MCP App; SDK peer |
12
+ | `@aotter/mantle-admin` | Admin server, app-built SPA library, preview and MCP App; SDK peer |
13
13
 
14
14
  Start with [a Worker](quickstart-worker.md) or [CLI and targets](project-and-cli.md). Existing TypeScript/Node apps can import selected generated operations using their own handle and verified Caller; no router or identity replacement is required. See the packaged [integrate skill](../../skills/integrate/SKILL.md).
15
15
 
@@ -10,13 +10,13 @@ description: Generation targets, CLI options and ownership of native application
10
10
  | `generated/` | Generator: TypeScript, native SQL and selected bindings |
11
11
  | `migrations/` | App's agent and native migration tool |
12
12
  | `wrangler.jsonc` | App configuration; generator maintains selected split-build keys |
13
- | `dist/` | Disposable bundle output; do not commit |
13
+ | `dist/` | Disposable bundle output (`dist/worker`, `dist/client`); do not commit |
14
14
 
15
15
  ## Commands
16
16
 
17
17
  ```sh
18
18
  mantle generate <target> <output-directory> <manifest>... [options]
19
- mantle bundle <entry> <outdir>
19
+ mantle bundle <entry> <outdir> [--assets <dir>]
20
20
  ```
21
21
 
22
22
  | Target | Dialect | Handle |
@@ -34,24 +34,24 @@ Other handles may adapt to the same [structural driver contracts](../guides/adap
34
34
  | `--admin` | Emit native Admin bindings and `admin/*` modules; install the separate Admin package. |
35
35
  | `--auth <module>` | Read plain `authOptions(env)` (or an exported auth instance's options) and append selected Better Auth DDL. Repeat it once per Better Auth instance. Prefer plain options without initialization side effects. |
36
36
  | `--site <module>` | Read `siteDefaults`; emit site/media bindings and deployment SQL. |
37
- | `--admin-extensions <module>` | Read the named/default `adminExtensions` array and the optional `navGroups` (sidebar groups; see native-admin.md "Sidebar"). The app shell passes `src/admin.ts`. |
38
37
  | `--wrangler <path>` | Existing JSONC/JSON/TOML config; default finds one or creates `wrangler.jsonc`. TOML receives manual-edit instructions. |
39
38
  | `--entry <path>` | Worker source entry; default `src/worker.ts`. |
40
- | `--outdir <path>` | Split bundle output; default `dist`. Keep separate from source/generated files. |
39
+ | `--outdir <path>` | Bundle output; default `dist`. The Worker goes to `<outdir>/worker`. Keep separate from source/generated files. |
40
+ | `--assets <path>` | With `--admin`: the static assets directory `mantle bundle` builds Admin into (`<assets>/admin`); default `<outdir>/client`. Written to Wrangler's `assets.directory`, and to the build command when not the default. |
41
41
 
42
42
  ## Outputs
43
43
 
44
- `generated/.mantle-inputs.json` records the Mantle version and a hash of every input the run read (manifests and the
45
- `--auth`, `--site`, `--admin-extensions` modules). `mantle bundle`, which Wrangler runs for `wrangler dev` and every
44
+ `generated/.mantle-inputs.json` records the Mantle version, the target, and a hash of every input the run read (manifests and the
45
+ `--auth` and `--site` modules). `mantle bundle`, which Wrangler runs for `wrangler dev` and every
46
46
  deploy, refuses to build when an input changed since, so stale generated code cannot run; regenerate and rebuild.
47
47
  Commit it with the other generated files.
48
48
  The bundle also prints notes, never errors, for SQL in the app's own code against Better Auth's tables (read users
49
49
  through `userOf(caller)` or `readIdentity(auth)` instead) and for Better Auth roles beyond `admin` and `user`.
50
50
 
51
- `generated.ts` exports typed queries and Store. Direct `views/<encoded-name>.ts` and `procedures/<encoded-name>.ts` entries export an operation and its types. Proved pageable Views also have `paged/views/*`; boundary checks live in `validation/*`. `admin-descriptors.ts`, `rest.ts` and `web.ts` are always emitted. Manifest exposure selects `mcp.ts`; `admin-bindings.ts` and `admin/*` require `--admin`. Auth and site are explicit options.
51
+ `generated.ts` exports typed queries and Store. Direct `views/<encoded-name>.ts` and `procedures/<encoded-name>.ts` entries export an operation and its types. Proved pageable Views also have `paged/views/*`; boundary checks live in `validation/*`. `admin-descriptors.ts`, `rest.ts` and `web.ts` are always emitted. Manifest exposure selects `mcp.ts`; `admin-bindings.ts`, `admin-client.ts` (browser-side types and hooks for `admin.tsx`) and `admin/*` require `--admin`. Auth and site are explicit options.
52
52
 
53
53
  Apply `schema.sql` only to an empty database. Later changes require [explicit migrations](../concepts/runtime-and-adapters.md#schema-installation-and-migrations); generation and runtime do not inspect the database. Ref declarations print the `Handlers` keys to implement.
54
54
 
55
55
  Never edit generated output by hand. Merge manifests/options, regenerate with the same command, then review TypeScript and SQL diffs. Use that exact command with `--check` in CI. Unmarked app files are preserved. Imported option modules must be pure; generation does not make their side effects safe.
56
56
 
57
- On Workers, generation configures `mantle bundle` as Wrangler's build command. It emits minified split ESM with a startup guard for eager Better Auth imports. On Bun, the app owns `Bun.serve`, its Database/SQL pool and scheduler. See [service composition](../cloudflare/service-entry.md).
57
+ On Workers, generation configures `mantle bundle` as Wrangler's build command. It emits minified split ESM into `<outdir>/worker` with a startup guard for eager Better Auth imports and for Admin's browser code (`@aotter/mantle-admin/app` and `/hooks`, which `generated/admin-client.ts` imports; they belong to `admin.tsx`). When the Worker imports `@aotter/mantle-admin/server`, it also builds Admin with Vite into `<assets>/admin` (`--assets`, default `<outdir>/client`), from `admin.tsx` beside the Worker entry or Admin's built-in entry; `src/admin.ts` is not an entry. The assets directory must not overlap the Worker directory, and neither the outdir nor the assets directory may be the working directory or contain the Worker entry. The built-in entry is not rebuilt while the installed `@aotter/mantle-admin` keeps its path and version and its direct dependencies and its `react`/`react-dom` peers keep theirs; an `admin.tsx` entry builds every time. The build needs no install scripts (`npm ci --ignore-scripts` works). A `package-lock.json` made on one OS can lack the native build binaries (rolldown, `@tailwindcss/oxide`, lightningcss) that npm installs per platform from `optionalDependencies`, so a lockfile made on macOS can fail the build on a Linux host; create the lockfile on the build platform or use pnpm. `wrangler dev` and `wrangler deploy` run that build command, so they need `node_modules`. The built output (`<outdir>/worker`, `<assets>`) never reads it, so a pipeline that deletes `node_modules` must run `wrangler deploy` first. On Bun, the app owns `Bun.serve`, its Database/SQL pool and scheduler; Bun runs the server as TypeScript, so for a `bun-*` target recorded in `.mantle-inputs.json`, `mantle bundle src/server.ts dist` skips the Worker bundle and builds only Admin, into `dist/client/admin` (read with `bunAdminAssets("dist/client/admin")`, adapted for `adminShell` as in service composition). See [service composition](../cloudflare/service-entry.md).
@@ -3,7 +3,7 @@ description: Generate a small native D1 service and mount an application-owned W
3
3
  ---
4
4
  # Quickstart: a service on Workers
5
5
 
6
- Create an application outside the SDK checkout. Install an exact released `@aotter/mantle` version, TypeScript, Wrangler and Cloudflare Worker types. Read that installed version's handbook; this page describes the native 0.2.0 contract.
6
+ Create an application outside the SDK checkout. Install an exact released `@aotter/mantle` version, TypeScript, Wrangler and Cloudflare Worker types; `node_modules/@aotter/mantle/docs/examples/app-shell/package.json` lists the versions this release is tested with. Read that installed version's handbook; this page describes the native 0.2.0 contract.
7
7
 
8
8
  Save `manifests/tickets.yaml`:
9
9
 
@@ -18,7 +18,7 @@ Follow the installed [native generation and service workflow](../../handbook/clo
18
18
  with its [native contract](../../handbook/guides/native-execution.md) and [supported declarations](../../handbook/reference/features.md). Commit
19
19
  all generated output, run the same generation command with `--check`, and keep
20
20
  bundle `dist/` untracked. The app owns its verified Caller, handlers and native
21
- resources; Better Auth’s admin plugin `adminRoles` defines the admin layer (default `["admin"]`); `ctx.admin` accepts open role names.
21
+ resources; Better Auth’s admin plugin `adminRoles` defines the admin layer (default `["admin"]`); `ctx.admin` takes no role list ([authorization](../../handbook/cloudflare/authentication.md)).
22
22
 
23
23
  Use the native Store/query/surface entries and app-owned fetch from the service composition guide.
24
24
  Keep optional modules out of unused entry imports and auth dynamically loaded.
@@ -41,8 +41,9 @@ version? }` selects row actions and binds the row id / reviewed OCC version to
41
41
  the named inputs. Other admin operations appear under Operations in the sidebar.
42
42
  Keep state conditions in the SQL WHERE: simple AND equality/IN literals become
43
43
  row-menu hints, while the Procedure remains the authority. Required-ref child
44
- collections fold under their parent's sidebar group; use Schema
45
- `uiSchema.nav.standalone: true` for a top-level entry.
44
+ collections fold under their parent's sidebar group; with two or more required
45
+ `id` refs, set Schema `uiSchema.nav.parentField` to the parent's ref (generate
46
+ refuses otherwise); use `uiSchema.nav.standalone: true` for a top-level entry.
46
47
 
47
48
  ## Database changes
48
49
 
@@ -18,7 +18,7 @@ Follow the installed [native generation and service workflow](../../handbook/clo
18
18
  with its [native contract](../../handbook/guides/native-execution.md) and [supported declarations](../../handbook/reference/features.md). Commit
19
19
  all generated output, run the same generation command with `--check`, and keep
20
20
  bundle `dist/` untracked. The app owns its verified Caller, handlers and native
21
- resources; Better Auth’s admin plugin `adminRoles` defines the admin layer (default `["admin"]`); `ctx.admin` accepts open role names.
21
+ resources; Better Auth’s admin plugin `adminRoles` defines the admin layer (default `["admin"]`); `ctx.admin` takes no role list ([authorization](../../handbook/cloudflare/authentication.md)).
22
22
 
23
23
  Keep the application router, database/pool, identity and frontend. Replace
24
24
  Core consumers explicitly and compose only selected generated entries. Resolve
@@ -41,11 +41,13 @@ objects approved by the user.
41
41
 
42
42
  `POST /admin/api/media/uploads` issues presigned PUT URLs that expire after 15
43
43
  minutes, for keys under `uploads/<purpose>/<group>/<role>.<ext>`. Commit
44
- copies each checked object to `<purpose>/<group>/<role>.<ext>` (with
45
- `committedAt` metadata) and deletes the upload key; a rejected group's upload
46
- keys are deleted. So an object still under `uploads/` long after its URL
47
- expired belongs to an upload that was never committed, and nothing can commit
48
- it any more. Objects outside `uploads/` are committed media: never candidates.
44
+ copies each checked object to `committed/<group>/<attempt>/<role>.<ext>` (with
45
+ `committedAt` metadata); the attempt UUID isolates concurrent commits. Upload
46
+ keys are removed after the database commit succeeds, or when a group is
47
+ rejected. An old upload key may also be leftover cleanup from a successful or
48
+ uncertain commit: it is not evidence that the media row is absent. Once expired,
49
+ it cannot create a new asset. Objects outside `uploads/` are never candidates;
50
+ reconcile attempt copies separately with live database references.
49
51
 
50
52
  ## Audit
51
53
 
@@ -19,7 +19,7 @@ Follow the installed [native generation and service workflow](../../handbook/clo
19
19
  with its [native contract](../../handbook/guides/native-execution.md) and [supported declarations](../../handbook/reference/features.md). Commit
20
20
  all generated output, run the same generation command with `--check`, and keep
21
21
  bundle `dist/` untracked. The app owns its verified Caller, handlers and native
22
- resources; Better Auth’s admin plugin `adminRoles` defines the admin layer (default `["admin"]`); `ctx.admin` accepts open role names.
22
+ resources; Better Auth’s admin plugin `adminRoles` defines the admin layer (default `["admin"]`); `ctx.admin` takes no role list ([authorization](../../handbook/cloudflare/authentication.md)).
23
23
 
24
24
  Deploy/create resources only within the user's authorization; reuse configured
25
25
  host resources. Apply reviewed and rehearsed migrations before routing traffic;
@@ -27,6 +27,9 @@ imports/first requests perform no DDL or schema checks. Keep auth dynamic, produ
27
27
  secrets in the host store, trusted HTTPS origin and verified bootstrap rule,
28
28
  production email and selected media wiring. Configure Admin ASSETS worker-first
29
29
  as in [Workers assets and secrets](../../handbook/cloudflare/service-entry.md#workers-assets-and-secrets). Hyperdrive caching stays off; PG settings are deployment-owned.
30
+ Keep the Workers [public-fetch flags](../../handbook/cloudflare/deploy-and-operate.md#public-fetch-routing),
31
+ including environment overrides; verify deployed MCP with a real OAuth bearer
32
+ (`initialize` and `tools/list`), not only a browser session cookie.
30
33
  Bun uses native Database/SQL, an app server and scheduler. Verify admin sign-in
31
34
  (role admin), denied access, public reads, a persisted write, MCP and selected UI.
32
35
  Report artifact/resources/checks and keep rollback artifacts.
@@ -18,7 +18,7 @@ Follow the installed [native generation and service workflow](../../handbook/clo
18
18
  with its [native contract](../../handbook/guides/native-execution.md) and [supported declarations](../../handbook/reference/features.md). Commit
19
19
  all generated output, run the same generation command with `--check`, and keep
20
20
  bundle `dist/` untracked. The app owns its verified Caller, handlers and native
21
- resources; Better Auth’s admin plugin `adminRoles` defines the admin layer (default `["admin"]`); `ctx.admin` accepts open role names.
21
+ resources; Better Auth’s admin plugin `adminRoles` defines the admin layer (default `["admin"]`); `ctx.admin` takes no role list ([authorization](../../handbook/cloudflare/authentication.md)).
22
22
 
23
23
  Pin exact target SDK/peer versions and read its release notes and Admin asset
24
24
  installation instructions. Core-to-native replaces app imports/wiring; it is not
@@ -13,12 +13,12 @@ The remainder describes the earlier alpha contract for historical reference.
13
13
 
14
14
  No codemod, and no in-place upgrade of a 0.1.x database. A coding agent moves
15
15
  one project by hand with this guide. When it and
16
- [ADR-0032](https://github.com/aotter/mantle/blob/develop/docs/adr/0032-store-centric-core-and-service-contract.md),
17
- [ADR-0033](https://github.com/aotter/mantle/blob/develop/docs/adr/0033-storage-converges-to-the-plan.md),
18
- [ADR-0034](https://github.com/aotter/mantle/blob/develop/docs/adr/0034-store-is-authored-as-sql.md),
19
- [ADR-0035](https://github.com/aotter/mantle/blob/develop/docs/adr/0035-sql-dialects.md),
20
- [ADR-0036](https://github.com/aotter/mantle/blob/develop/docs/adr/0036-host-and-dialect.md) or
21
- [ADR-0038](https://github.com/aotter/mantle/blob/develop/docs/adr/0038-bun-native-host.md) disagree, the ADR wins.
16
+ [ADR-0032](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0032-store-centric-core-and-service-contract.md),
17
+ [ADR-0033](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0033-storage-converges-to-the-plan.md),
18
+ [ADR-0034](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0034-store-is-authored-as-sql.md),
19
+ [ADR-0035](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0035-sql-dialects.md),
20
+ [ADR-0036](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0036-host-and-dialect.md) or
21
+ [ADR-0038](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0038-bun-native-host.md) disagree, the ADR wins.
22
22
 
23
23
  ## Rules
24
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aotter/mantle",
3
- "version": "0.2.0-alpha.7",
3
+ "version": "0.2.0-alpha.8",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "engines": {
@@ -92,6 +92,7 @@
92
92
  }
93
93
  },
94
94
  "devDependencies": {
95
+ "@modelcontextprotocol/client": "2.1.0",
95
96
  "@modelcontextprotocol/server": "2.2.0",
96
97
  "@better-auth/core": "1.7.7",
97
98
  "@better-auth/oauth-provider": "1.7.7",