@aotter/mantle 0.1.2-alpha.5 → 0.1.2-rc.1

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 (111) hide show
  1. package/README.md +11 -6
  2. package/dist/cli/generate.d.ts +9 -0
  3. package/dist/cli/generate.d.ts.map +1 -1
  4. package/dist/cli/generate.js +40 -1
  5. package/dist/cli/generate.js.map +1 -1
  6. package/dist/cli/main.d.ts +1 -1
  7. package/dist/cli/main.d.ts.map +1 -1
  8. package/dist/cli/main.js +37 -9
  9. package/dist/cli/main.js.map +1 -1
  10. package/docs/adapter-guide.md +1 -1
  11. package/docs/adr/0011-adapter-port-spec.md +4 -2
  12. package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +21 -0
  13. package/docs/adr/README.md +1 -1
  14. package/docs/adr/adr-lite-845-frontend-client.md +38 -0
  15. package/docs/agent-prompts.md +92 -0
  16. package/docs/api-mcp-authorization.md +1 -1
  17. package/docs/auth-hosting-model.md +12 -13
  18. package/docs/examples/README.md +22 -0
  19. package/docs/examples/builtin-commerce.md +269 -0
  20. package/docs/examples/builtin-intake.md +143 -0
  21. package/docs/examples/builtin-legal-documents.md +189 -0
  22. package/docs/examples/builtin-procurement.md +241 -0
  23. package/docs/examples/builtin-publication.md +241 -0
  24. package/docs/examples/builtin-reservation.md +149 -0
  25. package/docs/examples/cf-primitives-commerce-inventory.md +809 -0
  26. package/docs/examples/cf-primitives-guarded-api.md +429 -0
  27. package/docs/examples/cf-primitives-intake-hooks.md +319 -0
  28. package/docs/examples/host-chatgpt-sites/.openai/hosting.json +1 -0
  29. package/docs/examples/host-chatgpt-sites/README.md +39 -0
  30. package/docs/examples/host-chatgpt-sites/drizzle/0000_sites_users.sql +8 -0
  31. package/docs/examples/host-chatgpt-sites/drizzle/0001_mantle.sql +294 -0
  32. package/docs/examples/host-chatgpt-sites/drizzle/0002_article_cover.sql +5 -0
  33. package/docs/examples/host-chatgpt-sites/drizzle/meta/_journal.json +1 -0
  34. package/docs/examples/host-chatgpt-sites/manifests/site.yaml +27 -0
  35. package/docs/examples/host-chatgpt-sites/package-lock.json +7088 -0
  36. package/docs/examples/host-chatgpt-sites/package.json +1 -0
  37. package/docs/examples/host-chatgpt-sites/public/site.css +1 -0
  38. package/docs/examples/host-chatgpt-sites/scripts/build.mjs +12 -0
  39. package/docs/examples/host-chatgpt-sites/scripts/check.mjs +99 -0
  40. package/docs/examples/host-chatgpt-sites/scripts/migration.mjs +9 -0
  41. package/docs/examples/host-chatgpt-sites/src/chatgpt-auth.ts +63 -0
  42. package/docs/examples/host-chatgpt-sites/src/index.ts +46 -0
  43. package/docs/examples/host-chatgpt-sites/src/mcp.ts +49 -0
  44. package/docs/examples/host-chatgpt-sites/src/media.ts +109 -0
  45. package/docs/examples/host-chatgpt-sites/src/r2-lab.ts +38 -0
  46. package/docs/examples/host-chatgpt-sites/src/storage-fingerprint.json +1 -0
  47. package/docs/examples/host-chatgpt-sites/src/web.ts +35 -0
  48. package/docs/examples/host-chatgpt-sites/tsconfig.json +1 -0
  49. package/docs/examples/host-chatgpt-sites/wrangler.jsonc +10 -0
  50. package/docs/examples/host-local-admin-otp/.dev.vars.example +3 -0
  51. package/docs/examples/host-local-admin-otp/README.md +71 -0
  52. package/docs/examples/host-local-admin-otp/ensure-dev-vars.mjs +5 -0
  53. package/docs/examples/host-local-admin-otp/package.json +29 -0
  54. package/docs/examples/host-local-admin-otp/public/.gitkeep +1 -0
  55. package/docs/examples/host-local-admin-otp/smoke.mjs +141 -0
  56. package/docs/examples/host-local-admin-otp/src/index.ts +54 -0
  57. package/docs/examples/host-local-admin-otp/wrangler.jsonc +23 -0
  58. package/docs/examples/{minimal-worker → host-minimal-worker}/README.md +8 -5
  59. package/docs/examples/host-minimal-worker/manifests/site.yaml +25 -0
  60. package/docs/examples/{minimal-worker → host-minimal-worker}/package.json +3 -3
  61. package/docs/examples/host-minimal-worker/tsconfig.json +17 -0
  62. package/docs/handbook/cloudflare/authentication.md +79 -10
  63. package/docs/handbook/cloudflare/bindings.md +9 -7
  64. package/docs/handbook/cloudflare/chatgpt-sites.md +29 -0
  65. package/docs/handbook/cloudflare/conventional-worker.md +3 -3
  66. package/docs/handbook/cloudflare/deferred-hooks-queues.md +0 -1
  67. package/docs/handbook/cloudflare/deploy-and-operate.md +14 -20
  68. package/docs/handbook/cloudflare/media-r2.md +2 -2
  69. package/docs/handbook/cloudflare/public-web.md +1 -1
  70. package/docs/handbook/cloudflare/site-chrome.md +75 -0
  71. package/docs/handbook/concepts/authorization.md +2 -2
  72. package/docs/handbook/concepts/four-atoms.md +2 -2
  73. package/docs/handbook/concepts/lifecycle-and-locales.md +1 -1
  74. package/docs/handbook/concepts/mcp-and-agents.md +1 -1
  75. package/docs/handbook/concepts/procedures-and-triggers.md +1 -1
  76. package/docs/handbook/concepts/views.md +2 -2
  77. package/docs/handbook/examples/commerce-transaction.md +4 -806
  78. package/docs/handbook/examples/commerce.md +11 -0
  79. package/docs/handbook/examples/guarded-api.md +3 -420
  80. package/docs/handbook/examples/hub.md +10 -0
  81. package/docs/handbook/examples/intake-form.md +6 -313
  82. package/docs/handbook/examples/intake-hooks.md +11 -0
  83. package/docs/handbook/examples/legal-documents.md +3 -211
  84. package/docs/handbook/examples/procurement-approvals.md +3 -233
  85. package/docs/handbook/examples/publication.md +3 -233
  86. package/docs/handbook/examples/reservation.md +3 -213
  87. package/docs/handbook/navigation.json +17 -2
  88. package/docs/handbook/reference/authorization.md +1 -1
  89. package/docs/handbook/reference/procedure.md +2 -2
  90. package/docs/handbook/reference/schema.md +3 -3
  91. package/docs/handbook/reference/site-config.md +5 -16
  92. package/docs/handbook/reference/surface.md +3 -7
  93. package/docs/handbook/sites/equipment-checkout.md +231 -0
  94. package/docs/handbook/sites/host-reference.md +116 -0
  95. package/docs/handbook/sites/index.md +117 -0
  96. package/docs/handbook/start/project-and-cli.md +22 -14
  97. package/docs/handbook/start/quickstart-admin.md +239 -0
  98. package/docs/handbook/start/quickstart-worker.md +21 -22
  99. package/docs/migration-0.1.2.md +26 -0
  100. package/docs/release-process.md +90 -6
  101. package/docs/sealed-pipeline-ownership.md +1 -1
  102. package/docs/transaction-patterns.md +2 -2
  103. package/package.json +15 -15
  104. package/skills/develop/SKILL.md +32 -23
  105. package/skills/install/SKILL.md +34 -11
  106. package/skills/provision/SKILL.md +19 -5
  107. /package/docs/examples/{minimal-worker → host-local-admin-otp}/manifests/site.yaml +0 -0
  108. /package/docs/examples/{minimal-worker → host-local-admin-otp}/tsconfig.json +0 -0
  109. /package/docs/examples/{minimal-worker → host-minimal-worker}/smoke.mjs +0 -0
  110. /package/docs/examples/{minimal-worker → host-minimal-worker}/src/index.ts +0 -0
  111. /package/docs/examples/{minimal-worker → host-minimal-worker}/wrangler.jsonc +0 -0
@@ -0,0 +1,269 @@
1
+ ---
2
+ description: A product catalog and guest orders using only builtin create and update. No Durable Object, Queue, payment provider, or ref handlers.
3
+ ---
4
+ # Commerce catalog and orders
5
+
6
+ **Handler class:** builtin · **Builder:** yes · [Examples hub](./README.md). Stock authority, Queue expiry, and a verified payment callback are [Commerce inventory](./cf-primitives-commerce-inventory.md).
7
+
8
+ This example publishes products, lists them publicly, accepts guest orders with builtin `create`, and lets staff mark orders fulfilled or cancelled with builtin `update`. Every Procedure is `handler.kind: builtin`. It does not re-price lines, reserve stock, or talk to a payment provider.
9
+
10
+ ## Problem
11
+
12
+ Staff publish products with a price. A guest places an order by sending customer fields, an order number, currency, a total, and line items. Staff see submitted orders and record fulfillment or cancellation. Mantle stores the catalog and the order rows. There is no inventory authority in this Manifest.
13
+
14
+ ## Manifest
15
+
16
+ ```yaml
17
+ apiVersion: cms.mantle.aotter.net/v1
18
+ kind: Schema
19
+ metadata:
20
+ name: products
21
+ spec:
22
+ title: Products
23
+ lifecycle: publishing
24
+ uniqueIndexes:
25
+ - [slug]
26
+ schema:
27
+ type: object
28
+ additionalProperties: false
29
+ required: [slug, title, priceMinor, currency]
30
+ properties:
31
+ slug: { type: string, pattern: "^[a-z0-9-]+$" }
32
+ title: { type: string, minLength: 1, maxLength: 160 }
33
+ summary: { type: string, maxLength: 500 }
34
+ priceMinor: { type: integer, minimum: 0, x-mcp-hint: money-minor }
35
+ currency: { type: string, pattern: "^[A-Z]{3}$" }
36
+ ---
37
+ apiVersion: cms.mantle.aotter.net/v1
38
+ kind: Schema
39
+ metadata:
40
+ name: orders
41
+ spec:
42
+ title: Orders
43
+ description: Guest orders created and updated only by declared builtin Procedures.
44
+ lifecycle: operational
45
+ uniqueIndexes:
46
+ - [orderNumber]
47
+ indexes:
48
+ - [orderStatus]
49
+ searchableFields: [orderNumber, customerName, customerEmail]
50
+ schema:
51
+ type: object
52
+ additionalProperties: false
53
+ required: [orderNumber, orderStatus, currency, totalMinor, customerName, customerEmail, shippingAddress, items, placedAt]
54
+ properties:
55
+ orderNumber: { type: string, minLength: 1, maxLength: 40 }
56
+ orderStatus: { type: string, enum: [submitted, fulfilled, cancelled] }
57
+ currency: { type: string, pattern: "^[A-Z]{3}$" }
58
+ totalMinor: { type: integer, minimum: 0, x-mcp-hint: money-minor }
59
+ customerName: { type: string, minLength: 1, maxLength: 120 }
60
+ customerEmail: { type: string, format: email }
61
+ shippingAddress: { type: string, minLength: 1, maxLength: 500 }
62
+ items:
63
+ type: array
64
+ minItems: 1
65
+ maxItems: 20
66
+ items:
67
+ type: object
68
+ additionalProperties: false
69
+ required: [productSlug, title, quantity, unitPriceMinor, lineTotalMinor]
70
+ properties:
71
+ productSlug: { type: string, pattern: "^[a-z0-9-]+$" }
72
+ title: { type: string }
73
+ quantity: { type: integer, minimum: 1, maximum: 99 }
74
+ unitPriceMinor: { type: integer, minimum: 0 }
75
+ lineTotalMinor: { type: integer, minimum: 0 }
76
+ trackingNumber: { type: string, maxLength: 120 }
77
+ cancelReason: { type: string, maxLength: 500 }
78
+ placedAt: { type: number, x-mcp-hint: timestamp-ms, x-mantle-bind: now }
79
+ ---
80
+ apiVersion: cms.mantle.aotter.net/v1
81
+ kind: View
82
+ metadata:
83
+ name: public-products
84
+ spec:
85
+ title: Public products
86
+ surface: public
87
+ from: products
88
+ fields: [id, slug, title, summary, priceMinor, currency, updatedAt]
89
+ filter:
90
+ eq: { field: status, value: published }
91
+ orderBy:
92
+ - { field: slug, direction: asc }
93
+ limit: 100
94
+ ---
95
+ apiVersion: cms.mantle.aotter.net/v1
96
+ kind: View
97
+ metadata:
98
+ name: submitted-orders
99
+ spec:
100
+ title: Submitted orders
101
+ surface: staff
102
+ from: orders
103
+ fields: [id, version, orderNumber, orderStatus, customerName, customerEmail, shippingAddress, totalMinor, currency, placedAt]
104
+ filter:
105
+ eq: { field: orderStatus, value: submitted }
106
+ orderBy:
107
+ - { field: placedAt, direction: asc }
108
+ limit: 100
109
+ ---
110
+ apiVersion: cms.mantle.aotter.net/v1
111
+ kind: Procedure
112
+ metadata:
113
+ name: place-order
114
+ spec:
115
+ title: Place order
116
+ input:
117
+ type: object
118
+ additionalProperties: false
119
+ required: [orderNumber, orderStatus, currency, totalMinor, customerName, customerEmail, shippingAddress, items]
120
+ properties:
121
+ orderNumber: { type: string, minLength: 1, maxLength: 40 }
122
+ orderStatus: { type: string, enum: [submitted] }
123
+ currency: { type: string, pattern: "^[A-Z]{3}$" }
124
+ totalMinor: { type: integer, minimum: 0, x-mcp-hint: money-minor }
125
+ customerName: { type: string, minLength: 1, maxLength: 120 }
126
+ customerEmail: { type: string, format: email }
127
+ shippingAddress: { type: string, minLength: 1, maxLength: 500 }
128
+ items:
129
+ type: array
130
+ minItems: 1
131
+ maxItems: 20
132
+ items:
133
+ type: object
134
+ additionalProperties: false
135
+ required: [productSlug, title, quantity, unitPriceMinor, lineTotalMinor]
136
+ properties:
137
+ productSlug: { type: string, pattern: "^[a-z0-9-]+$" }
138
+ title: { type: string }
139
+ quantity: { type: integer, minimum: 1, maximum: 99 }
140
+ unitPriceMinor: { type: integer, minimum: 0 }
141
+ lineTotalMinor: { type: integer, minimum: 0 }
142
+ output: { type: object }
143
+ handler: { kind: builtin, op: create, schema: orders }
144
+ ---
145
+ apiVersion: cms.mantle.aotter.net/v1
146
+ kind: Procedure
147
+ metadata:
148
+ name: review-order
149
+ spec:
150
+ title: Review order
151
+ requires:
152
+ auth:
153
+ all:
154
+ - { "ctx.staff": [owner, editor] }
155
+ input:
156
+ type: object
157
+ additionalProperties: false
158
+ required: [id, expectedVersion, orderStatus]
159
+ properties:
160
+ id: { type: string }
161
+ expectedVersion: { type: number, minimum: 1 }
162
+ orderStatus: { type: string, enum: [fulfilled, cancelled] }
163
+ trackingNumber: { type: string, maxLength: 120 }
164
+ cancelReason: { type: string, maxLength: 500 }
165
+ output: { type: object }
166
+ handler: { kind: builtin, op: update, schema: orders }
167
+ ---
168
+ apiVersion: cms.mantle.aotter.net/v1
169
+ kind: Trigger
170
+ metadata:
171
+ name: place-order-http
172
+ spec:
173
+ source: { kind: http, method: POST, path: /api/commerce/orders }
174
+ target: { procedure: place-order }
175
+ ---
176
+ apiVersion: cms.mantle.aotter.net/v1
177
+ kind: Trigger
178
+ metadata:
179
+ name: place-order-mcp
180
+ spec:
181
+ source: { kind: mcp, surface: public }
182
+ target: { procedure: place-order }
183
+ ---
184
+ apiVersion: cms.mantle.aotter.net/v1
185
+ kind: Trigger
186
+ metadata:
187
+ name: review-order-mcp
188
+ spec:
189
+ source: { kind: mcp, surface: staff }
190
+ target: { procedure: review-order }
191
+ ```
192
+
193
+ Points worth noticing:
194
+
195
+ - `products` is a publishing Schema. Staff draft and publish through Admin or Staff MCP generic lifecycle tools. The public View filters `status: published`.
196
+ - `orders` is operational. `place-order` is builtin `create`; `review-order` is builtin `update` with `id` and `expectedVersion` required. `placedAt` is server-stamped.
197
+ - `orderStatus` on create is narrowed to `submitted`, so this Procedure cannot insert a fulfilled row. Staff `review-order` can only move a row to `fulfilled` or `cancelled`.
198
+ - Line titles, unit prices and `totalMinor` are caller-supplied. This Manifest does not re-price from `products` and does not reserve stock.
199
+ - `review-order` does not set `x-mantle-ref` on `id`. Drive reviews from `submitted-orders` (which exposes `id` and `version`) through Staff MCP or Admin operations. A lone `[orderNumber]` unique index would otherwise make Admin prefill `id` with the order number; see [Procurement approvals](./builtin-procurement.md).
200
+
201
+ ## Worker and handlers
202
+
203
+ None. Both Procedures are builtin:
204
+
205
+ ```ts
206
+ import { createMantleWorker } from "@aotter/mantle/cloudflare";
207
+ import { plan } from "../.mantle/generated/mantle.js";
208
+
209
+ export default createMantleWorker({ plan });
210
+ ```
211
+
212
+ Sign-in for staff review is the conventional Worker's Auth; see [Authentication](../handbook/cloudflare/authentication.md). The [local Admin OTP host](./host-local-admin-otp/README.md) is the opt-in Dev UI path.
213
+
214
+ ## Try it
215
+
216
+ Public catalog:
217
+
218
+ ```sh
219
+ curl -sS 'http://localhost:8787/api/views/public-products?show=20'
220
+ ```
221
+
222
+ Place an order (the body is stored as sent; the server does not re-price):
223
+
224
+ ```sh
225
+ curl -sS -X POST http://localhost:8787/api/commerce/orders \
226
+ -H 'content-type: application/json' \
227
+ -d '{"orderNumber":"MNT-20260919-1","orderStatus":"submitted","currency":"TWD","totalMinor":2400,"customerName":"Ada","customerEmail":"ada@example.test","shippingAddress":"1 Shell Lane","items":[{"productSlug":"notebook","title":"Notebook","quantity":2,"unitPriceMinor":1200,"lineTotalMinor":2400}]}'
228
+ ```
229
+
230
+ The response is `{ ok: true, data: <EntryRow> }` with `collection: "orders"` and `status: "published"`. A duplicate `orderNumber` is HTTP 409 `CONFLICT`. `orderStatus` other than `submitted` is HTTP 400 `INPUT_VALIDATION_FAILED`.
231
+
232
+ Staff, on `/mcp/staff`, read `query_view_submitted_orders` then:
233
+
234
+ ```json
235
+ {
236
+ "jsonrpc": "2.0", "id": 2, "method": "tools/call",
237
+ "params": {
238
+ "name": "review_order",
239
+ "arguments": { "id": "ord_01j...", "expectedVersion": 1, "orderStatus": "fulfilled", "trackingNumber": "TEST-1" }
240
+ }
241
+ }
242
+ ```
243
+
244
+ A second reviewer replaying `expectedVersion: 1` receives `CONFLICT`. A contributor-role session is denied with `AUTH_DENIED`.
245
+
246
+ | Surface | Tool | Origin |
247
+ |---|---|---|
248
+ | `/mcp` | `query_view_public_products` | public View |
249
+ | `/mcp` | `place_order` | `place-order-mcp` Trigger |
250
+ | `/mcp/staff` | `query_view_submitted_orders` | staff View |
251
+ | `/mcp/staff` | `review_order` | staff MCP Trigger |
252
+ | `/mcp/staff` | `create_draft_products`, `update_draft_products`, `request_publish`, ... | publishing Schema `products` |
253
+ | `/mcp/staff` | `create_record_orders`, `update_record_orders` | operational Schema `orders` |
254
+
255
+ ## What this deliberately leaves out
256
+
257
+ - **Stock authority.** There is no `inventory` Schema and no Durable Object. Two orders for the last unit both succeed. That story is [Commerce inventory](./cf-primitives-commerce-inventory.md).
258
+ - **Re-pricing.** `totalMinor` and line totals are whatever the caller sent.
259
+ - **Payments.** `submitted` is not `pending_payment`. There is no provider callback and no `pay-order` Procedure.
260
+ - **Queue expiry and cron.** Unpaid reservation release does not apply; there is no reservation.
261
+ - **Picking-list SQL View.** `submitted-orders` is a declared View over `orders`. The `json_each` picking list lives on the cf-primitives page.
262
+
263
+ Related: [Publication](./builtin-publication.md) for localized product copy; [Procurement approvals](./builtin-procurement.md) for `ctx.user`-owned rows and OCC updates.
264
+
265
+ ## Source
266
+
267
+ - [Commerce inventory](./cf-primitives-commerce-inventory.md) — DO stock, Queue expiry, payment callback
268
+ - [Schema](../handbook/reference/schema.md), [Procedure](../handbook/reference/procedure.md), and [View](../handbook/reference/view.md) references — publishing vs operational, builtin `create`/`update`
269
+ - [`host-local-admin-otp`](./host-local-admin-otp/README.md) — opt-in Admin host for publishing products
@@ -0,0 +1,143 @@
1
+ ---
2
+ description: A public intake form that creates operational request rows with a builtin handler. No Turnstile or email ref hooks.
3
+ ---
4
+ # Intake form
5
+
6
+ **Handler class:** builtin · **Builder:** yes · [Examples hub](./README.md). Turnstile verification and staff email are [Intake Turnstile and email hooks](./cf-primitives-intake-hooks.md).
7
+
8
+ This example collects a public request and lists recent rows for staff. Every Procedure is `handler.kind: builtin`. Read it if you need any form that anonymous visitors submit and you are not wiring bot checks or mailers into the Manifest.
9
+
10
+ ## Problem
11
+
12
+ Visitors submit a name, an email address and a message. Staff read recent submissions in Admin, over the staff View REST route, or through Staff MCP. Submissions are live records, not authored content, so the Schema is `operational`.
13
+
14
+ ## Manifest
15
+
16
+ ```yaml
17
+ apiVersion: cms.mantle.aotter.net/v1
18
+ kind: Schema
19
+ metadata:
20
+ name: requests
21
+ spec:
22
+ title: Requests
23
+ description: Requests submitted through the public intake flow.
24
+ lifecycle: operational
25
+ schema:
26
+ type: object
27
+ additionalProperties: false
28
+ required: [name, email, message]
29
+ properties:
30
+ name: { type: string, minLength: 1, maxLength: 120 }
31
+ email: { type: string, format: email }
32
+ message: { type: string, minLength: 1, maxLength: 2000 }
33
+ createdAt: { type: number, x-mcp-hint: timestamp-ms, x-mantle-bind: now }
34
+ ---
35
+ apiVersion: cms.mantle.aotter.net/v1
36
+ kind: View
37
+ metadata:
38
+ name: recent-requests
39
+ spec:
40
+ title: Recent requests
41
+ surface: staff
42
+ from: requests
43
+ fields: [id, name, email, message, createdAt]
44
+ orderBy:
45
+ - { field: createdAt, direction: desc }
46
+ limit: 50
47
+ ---
48
+ apiVersion: cms.mantle.aotter.net/v1
49
+ kind: Procedure
50
+ metadata:
51
+ name: submit-request
52
+ spec:
53
+ title: Submit request
54
+ description: Create a new public request.
55
+ input:
56
+ type: object
57
+ additionalProperties: false
58
+ required: [name, email, message]
59
+ properties:
60
+ name: { type: string, minLength: 1, maxLength: 120 }
61
+ email: { type: string, format: email }
62
+ message: { type: string, minLength: 1, maxLength: 2000 }
63
+ output: { type: object }
64
+ handler: { kind: builtin, op: create, schema: requests }
65
+ ---
66
+ apiVersion: cms.mantle.aotter.net/v1
67
+ kind: Trigger
68
+ metadata:
69
+ name: submit-request-http
70
+ spec:
71
+ source: { kind: http, method: POST, path: /api/requests }
72
+ target: { procedure: submit-request }
73
+ ---
74
+ apiVersion: cms.mantle.aotter.net/v1
75
+ kind: Trigger
76
+ metadata:
77
+ name: submit-request-mcp
78
+ spec:
79
+ source: { kind: mcp, surface: public }
80
+ target: { procedure: submit-request }
81
+ ```
82
+
83
+ `createdAt` is stamped by the server (`x-mantle-bind: now`); a caller-supplied value is ignored. The builtin `create` projects `input ∩ Schema.properties`.
84
+
85
+ ## Worker and handlers
86
+
87
+ None are required. The Manifest above runs on the minimal Worker:
88
+
89
+ ```ts
90
+ import { createMantleWorker } from "@aotter/mantle/cloudflare";
91
+ import { plan } from "../.mantle/generated/mantle.js";
92
+
93
+ export default createMantleWorker({ plan });
94
+ ```
95
+
96
+ ## Try it
97
+
98
+ ```sh
99
+ curl -sS -X POST http://localhost:8787/api/requests \
100
+ -H 'content-type: application/json' \
101
+ -d '{"name":"Ada","email":"ada@example.test","message":"Please call me back."}'
102
+ ```
103
+
104
+ ```json
105
+ {
106
+ "ok": true,
107
+ "data": {
108
+ "id": "req_01j...",
109
+ "collection": "requests",
110
+ "status": "published",
111
+ "version": 1,
112
+ "data": { "name": "Ada", "email": "ada@example.test", "message": "Please call me back.", "createdAt": 1788879363492 },
113
+ "authorId": null,
114
+ "createdAt": 1788879363492,
115
+ "updatedAt": 1788879363492
116
+ }
117
+ }
118
+ ```
119
+
120
+ The builtin `create` returns the `EntryRow`; `status` is `published` immediately because the Schema is operational. A missing `name` is HTTP 400 `INPUT_VALIDATION_FAILED`.
121
+
122
+ Staff read the queue at `GET /admin/api/views/recent-requests?page=1&show=50` with a staff session; the envelope is `{ ok, data: { rows, page, show, hasMore } }`.
123
+
124
+ MCP tools:
125
+
126
+ | Surface | Tool | Origin |
127
+ |---|---|---|
128
+ | `/mcp` | `submit_request` | `submit-request-mcp` Trigger |
129
+ | `/mcp/staff` | `query_view_recent_requests` | `recent-requests` View |
130
+ | `/mcp/staff` | `create_record_requests`, `update_record_requests` | operational Schema `requests` |
131
+
132
+ ## What this deliberately leaves out
133
+
134
+ - **Bot check and notification.** Those are `ref` lifecycle hooks in [Intake Turnstile and email hooks](./cf-primitives-intake-hooks.md). They are not in this Manifest; Builder must not ingest that page.
135
+ - **Deduplication.** Two identical submissions create two rows.
136
+ - **Rate limiting.** The adapter applies its own request limits to Auth and Admin routes, not a per-form quota.
137
+
138
+ Related: [Reservation requests](./builtin-reservation.md) uses the same builtin-create shape; [Procurement approvals](./builtin-procurement.md) adds member and staff roles.
139
+
140
+ ## Source
141
+
142
+ - [Procedure reference](../handbook/reference/procedure.md) — builtin `create`
143
+ - [Intake Turnstile and email hooks](./cf-primitives-intake-hooks.md) — Turnstile and email `ref` hooks on the same Schema
@@ -0,0 +1,189 @@
1
+ ---
2
+ description: Localized Terms and Privacy documents with immutable revisions, public lookup and signed-in acceptance records.
3
+ ---
4
+ # Legal documents and consent
5
+
6
+ **Handler class:** builtin · **Builder:** yes · [Examples hub](./README.md).
7
+
8
+ This example gives an application a portable shape for Terms of Use, Privacy Policy and consent receipts. The published Manifest uses only `handler.kind: builtin`. Core does not install it or own the public pages. A live “must be a published revision” check is a `ref` guard; see [Guarded API access](./cf-primitives-guarded-api.md).
9
+
10
+ Each `(kind, revision, locale)` row is one legal artifact. Immutability here is not a legal-specific Runtime: it is the generic-surface `schema.readOnly: true` pattern (Admin and Staff MCP suppress generic create, update, status and delete), the unique `(kind, revision, locale)` index, and a Procedure-only write path. Host code that calls `updateDraft` (or other mutation use cases) directly can still rewrite a row — do not expose those host mutation APIs for this collection. The English Procedure description is also the MCP and WebMCP authoring instruction.
11
+
12
+ ```yaml
13
+ apiVersion: cms.mantle.aotter.net/v1
14
+ kind: Schema
15
+ metadata:
16
+ name: legal-documents
17
+ spec:
18
+ title:
19
+ en: Legal documents
20
+ zh-TW: 法律文件
21
+ description:
22
+ en: Supply the site's complete, reviewed legal text. Never create empty, placeholder, or agent-invented terms. Create a new revision instead of rewriting a published document accepted by users.
23
+ zh-TW: 請填入網站已審閱的完整法律正文。不得建立空白、佔位或由 agent 虛構的條款;已有使用者同意的已發佈文件應建立新修訂,不得覆寫。
24
+ localized: true
25
+ lifecycle: publishing
26
+ uniqueIndexes:
27
+ - [kind, revision, locale]
28
+ indexes:
29
+ - [kind, locale, effectiveAt]
30
+ searchableFields: [title, revision]
31
+ schema:
32
+ type: object
33
+ readOnly: true
34
+ additionalProperties: false
35
+ required: [kind, revision, locale, title, body, effectiveAt]
36
+ properties:
37
+ kind: { type: string, enum: [terms, privacy] }
38
+ revision: { type: string, minLength: 1, maxLength: 100 }
39
+ locale: { type: string }
40
+ title: { type: string, minLength: 1, maxLength: 200 }
41
+ body:
42
+ type: string
43
+ minLength: 1
44
+ x-mcp-hint: markdown
45
+ description:
46
+ en: Complete reviewed legal text in Markdown; placeholders are not acceptable.
47
+ zh-TW: 已審閱的完整 Markdown 法律正文,不得使用佔位文字。
48
+ effectiveAt: { type: integer, minimum: 0, x-mcp-hint: timestamp-ms }
49
+ ---
50
+ apiVersion: cms.mantle.aotter.net/v1
51
+ kind: View
52
+ metadata:
53
+ name: current-legal-document
54
+ spec:
55
+ title:
56
+ en: Current legal document
57
+ zh-TW: 現行法律文件
58
+ surface: public
59
+ from: legal-documents
60
+ params:
61
+ type: object
62
+ additionalProperties: false
63
+ required: [kind, locale]
64
+ properties:
65
+ kind: { type: string, enum: [terms, privacy] }
66
+ locale: { type: string }
67
+ fields: [id, kind, revision, locale, title, body, effectiveAt, updatedAt]
68
+ filter:
69
+ and:
70
+ - eq: { field: status, value: published }
71
+ - eq: { field: kind, value: { $param: kind } }
72
+ - eq: { field: locale, value: { $param: locale } }
73
+ orderBy:
74
+ - { field: effectiveAt, direction: desc }
75
+ limit: 1
76
+ ---
77
+ apiVersion: cms.mantle.aotter.net/v1
78
+ kind: Procedure
79
+ metadata:
80
+ name: create-legal-document
81
+ spec:
82
+ title:
83
+ en: Create legal document revision
84
+ zh-TW: 建立法律文件修訂
85
+ description:
86
+ en: Supply the site's complete, reviewed legal text. Never create empty, placeholder, or agent-invented terms. Create a new revision instead of rewriting a published document accepted by users.
87
+ zh-TW: 請填入網站已審閱的完整法律正文。不得建立空白、佔位或由 agent 虛構的條款;已有使用者同意的已發佈文件應建立新修訂,不得覆寫。
88
+ input:
89
+ type: object
90
+ additionalProperties: false
91
+ required: [kind, revision, locale, title, body, effectiveAt]
92
+ properties:
93
+ kind: { type: string, enum: [terms, privacy] }
94
+ revision: { type: string, minLength: 1, maxLength: 100 }
95
+ locale: { type: string }
96
+ title: { type: string, minLength: 1, maxLength: 200 }
97
+ body:
98
+ type: string
99
+ minLength: 1
100
+ x-mcp-hint: markdown
101
+ description:
102
+ en: Complete reviewed legal text in Markdown; placeholders are not acceptable.
103
+ zh-TW: 已審閱的完整 Markdown 法律正文,不得使用佔位文字。
104
+ effectiveAt: { type: integer, minimum: 0, x-mcp-hint: timestamp-ms }
105
+ output: { type: object }
106
+ handler: { kind: builtin, op: create, schema: legal-documents }
107
+ requires:
108
+ auth:
109
+ all:
110
+ - ctx.user
111
+ - { ctx.staff: [owner, editor] }
112
+ ---
113
+ apiVersion: cms.mantle.aotter.net/v1
114
+ kind: Trigger
115
+ metadata:
116
+ name: create-legal-document-staff
117
+ spec:
118
+ source: { kind: mcp, surface: staff }
119
+ target: { procedure: create-legal-document }
120
+ ---
121
+ apiVersion: cms.mantle.aotter.net/v1
122
+ kind: Schema
123
+ metadata:
124
+ name: legal-acceptances
125
+ spec:
126
+ title:
127
+ en: Legal acceptances
128
+ zh-TW: 法律文件同意紀錄
129
+ description:
130
+ en: Append-only receipts bound by the server to the signed-in user and acceptance time.
131
+ zh-TW: 由伺服器綁定登入使用者與同意時間的唯增紀錄。
132
+ lifecycle: operational
133
+ uniqueIndexes:
134
+ - [documentId, userId]
135
+ indexes:
136
+ - [userId, acceptedAt]
137
+ schema:
138
+ type: object
139
+ readOnly: true
140
+ additionalProperties: false
141
+ required: [documentId, userId, acceptedAt]
142
+ properties:
143
+ documentId: { type: string, format: uuid, x-mantle-ref: legal-documents }
144
+ userId: { type: string, x-mantle-bind: ctx.user }
145
+ acceptedAt: { type: integer, x-mantle-bind: now, x-mcp-hint: timestamp-ms }
146
+ ---
147
+ apiVersion: cms.mantle.aotter.net/v1
148
+ kind: Procedure
149
+ metadata:
150
+ name: accept-legal-document
151
+ spec:
152
+ title:
153
+ en: Accept legal document
154
+ zh-TW: 同意法律文件
155
+ description:
156
+ en: Record the signed-in user's acceptance of one legal document revision.
157
+ zh-TW: 記錄登入使用者對一份法律文件修訂的同意。
158
+ input:
159
+ type: object
160
+ additionalProperties: false
161
+ required: [documentId]
162
+ properties:
163
+ documentId: { type: string, format: uuid, x-mantle-ref: legal-documents }
164
+ output: { type: object }
165
+ handler: { kind: builtin, op: create, schema: legal-acceptances }
166
+ requires:
167
+ auth: { all: [ctx.user] }
168
+ ---
169
+ apiVersion: cms.mantle.aotter.net/v1
170
+ kind: Trigger
171
+ metadata:
172
+ name: accept-legal-document-http
173
+ spec:
174
+ source: { kind: http, method: POST, path: /api/legal/acceptances }
175
+ target: { procedure: accept-legal-document }
176
+ ```
177
+
178
+ The HTTP endpoint records only `documentId`; `userId` and `acceptedAt` are server-bound and cannot be supplied by the caller. This Manifest does not include a live “row is published” guard. Add that as a `ref` Procedure on `requires.guard` when the application owns the lookup; the contract is [Guarded API access](./cf-primitives-guarded-api.md).
179
+
180
+ Serve `/terms` and `/privacy` in application code by querying `current-legal-document` with the requested locale, falling back to the site's default locale, and rendering Markdown as escaped/sanitized HTML. Return a clear unavailable page when no reviewed document is published. The application also owns the checkbox or other consent UI, authentication, retention and export policy.
181
+
182
+ Staff MCP and Admin WebMCP expose `create_legal_document` from the explicit staff Trigger. Both use the same Procedure description, so agents are told to collect real reviewed text rather than inventing it. Root `readOnly` deliberately emits no generic update tool on Admin or Staff MCP; that is a generic-surface gate, not a storage lock. Do not add an MCP Trigger for `accept-legal-document`: accepting legal terms is an explicit user-interface action.
183
+
184
+ ## Source
185
+
186
+ - [Publication](./builtin-publication.md) — localized publishing pattern
187
+ - [Authorization](../handbook/reference/authorization.md) — server-bound identity
188
+ - [Guarded API access](./cf-primitives-guarded-api.md) — optional live published-document `ref` guard
189
+ - [MCP and agents](../handbook/concepts/mcp-and-agents.md) — manifest-derived tool contracts