@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.
- package/README.md +11 -6
- package/dist/cli/generate.d.ts +9 -0
- package/dist/cli/generate.d.ts.map +1 -1
- package/dist/cli/generate.js +40 -1
- package/dist/cli/generate.js.map +1 -1
- package/dist/cli/main.d.ts +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +37 -9
- package/dist/cli/main.js.map +1 -1
- package/docs/adapter-guide.md +1 -1
- package/docs/adr/0011-adapter-port-spec.md +4 -2
- package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +21 -0
- package/docs/adr/README.md +1 -1
- package/docs/adr/adr-lite-845-frontend-client.md +38 -0
- package/docs/agent-prompts.md +92 -0
- package/docs/api-mcp-authorization.md +1 -1
- package/docs/auth-hosting-model.md +12 -13
- package/docs/examples/README.md +22 -0
- package/docs/examples/builtin-commerce.md +269 -0
- package/docs/examples/builtin-intake.md +143 -0
- package/docs/examples/builtin-legal-documents.md +189 -0
- package/docs/examples/builtin-procurement.md +241 -0
- package/docs/examples/builtin-publication.md +241 -0
- package/docs/examples/builtin-reservation.md +149 -0
- package/docs/examples/cf-primitives-commerce-inventory.md +809 -0
- package/docs/examples/cf-primitives-guarded-api.md +429 -0
- package/docs/examples/cf-primitives-intake-hooks.md +319 -0
- package/docs/examples/host-chatgpt-sites/.openai/hosting.json +1 -0
- package/docs/examples/host-chatgpt-sites/README.md +39 -0
- package/docs/examples/host-chatgpt-sites/drizzle/0000_sites_users.sql +8 -0
- package/docs/examples/host-chatgpt-sites/drizzle/0001_mantle.sql +294 -0
- package/docs/examples/host-chatgpt-sites/drizzle/0002_article_cover.sql +5 -0
- package/docs/examples/host-chatgpt-sites/drizzle/meta/_journal.json +1 -0
- package/docs/examples/host-chatgpt-sites/manifests/site.yaml +27 -0
- package/docs/examples/host-chatgpt-sites/package-lock.json +7088 -0
- package/docs/examples/host-chatgpt-sites/package.json +1 -0
- package/docs/examples/host-chatgpt-sites/public/site.css +1 -0
- package/docs/examples/host-chatgpt-sites/scripts/build.mjs +12 -0
- package/docs/examples/host-chatgpt-sites/scripts/check.mjs +99 -0
- package/docs/examples/host-chatgpt-sites/scripts/migration.mjs +9 -0
- package/docs/examples/host-chatgpt-sites/src/chatgpt-auth.ts +63 -0
- package/docs/examples/host-chatgpt-sites/src/index.ts +46 -0
- package/docs/examples/host-chatgpt-sites/src/mcp.ts +49 -0
- package/docs/examples/host-chatgpt-sites/src/media.ts +109 -0
- package/docs/examples/host-chatgpt-sites/src/r2-lab.ts +38 -0
- package/docs/examples/host-chatgpt-sites/src/storage-fingerprint.json +1 -0
- package/docs/examples/host-chatgpt-sites/src/web.ts +35 -0
- package/docs/examples/host-chatgpt-sites/tsconfig.json +1 -0
- package/docs/examples/host-chatgpt-sites/wrangler.jsonc +10 -0
- package/docs/examples/host-local-admin-otp/.dev.vars.example +3 -0
- package/docs/examples/host-local-admin-otp/README.md +71 -0
- package/docs/examples/host-local-admin-otp/ensure-dev-vars.mjs +5 -0
- package/docs/examples/host-local-admin-otp/package.json +29 -0
- package/docs/examples/host-local-admin-otp/public/.gitkeep +1 -0
- package/docs/examples/host-local-admin-otp/smoke.mjs +141 -0
- package/docs/examples/host-local-admin-otp/src/index.ts +54 -0
- package/docs/examples/host-local-admin-otp/wrangler.jsonc +23 -0
- package/docs/examples/{minimal-worker → host-minimal-worker}/README.md +8 -5
- package/docs/examples/host-minimal-worker/manifests/site.yaml +25 -0
- package/docs/examples/{minimal-worker → host-minimal-worker}/package.json +3 -3
- package/docs/examples/host-minimal-worker/tsconfig.json +17 -0
- package/docs/handbook/cloudflare/authentication.md +79 -10
- package/docs/handbook/cloudflare/bindings.md +9 -7
- package/docs/handbook/cloudflare/chatgpt-sites.md +29 -0
- package/docs/handbook/cloudflare/conventional-worker.md +3 -3
- package/docs/handbook/cloudflare/deferred-hooks-queues.md +0 -1
- package/docs/handbook/cloudflare/deploy-and-operate.md +14 -20
- package/docs/handbook/cloudflare/media-r2.md +2 -2
- package/docs/handbook/cloudflare/public-web.md +1 -1
- package/docs/handbook/cloudflare/site-chrome.md +75 -0
- package/docs/handbook/concepts/authorization.md +2 -2
- package/docs/handbook/concepts/four-atoms.md +2 -2
- package/docs/handbook/concepts/lifecycle-and-locales.md +1 -1
- package/docs/handbook/concepts/mcp-and-agents.md +1 -1
- package/docs/handbook/concepts/procedures-and-triggers.md +1 -1
- package/docs/handbook/concepts/views.md +2 -2
- package/docs/handbook/examples/commerce-transaction.md +4 -806
- package/docs/handbook/examples/commerce.md +11 -0
- package/docs/handbook/examples/guarded-api.md +3 -420
- package/docs/handbook/examples/hub.md +10 -0
- package/docs/handbook/examples/intake-form.md +6 -313
- package/docs/handbook/examples/intake-hooks.md +11 -0
- package/docs/handbook/examples/legal-documents.md +3 -211
- package/docs/handbook/examples/procurement-approvals.md +3 -233
- package/docs/handbook/examples/publication.md +3 -233
- package/docs/handbook/examples/reservation.md +3 -213
- package/docs/handbook/navigation.json +17 -2
- package/docs/handbook/reference/authorization.md +1 -1
- package/docs/handbook/reference/procedure.md +2 -2
- package/docs/handbook/reference/schema.md +3 -3
- package/docs/handbook/reference/site-config.md +5 -16
- package/docs/handbook/reference/surface.md +3 -7
- package/docs/handbook/sites/equipment-checkout.md +231 -0
- package/docs/handbook/sites/host-reference.md +116 -0
- package/docs/handbook/sites/index.md +117 -0
- package/docs/handbook/start/project-and-cli.md +22 -14
- package/docs/handbook/start/quickstart-admin.md +239 -0
- package/docs/handbook/start/quickstart-worker.md +21 -22
- package/docs/migration-0.1.2.md +26 -0
- package/docs/release-process.md +90 -6
- package/docs/sealed-pipeline-ownership.md +1 -1
- package/docs/transaction-patterns.md +2 -2
- package/package.json +15 -15
- package/skills/develop/SKILL.md +32 -23
- package/skills/install/SKILL.md +34 -11
- package/skills/provision/SKILL.md +19 -5
- /package/docs/examples/{minimal-worker → host-local-admin-otp}/manifests/site.yaml +0 -0
- /package/docs/examples/{minimal-worker → host-local-admin-otp}/tsconfig.json +0 -0
- /package/docs/examples/{minimal-worker → host-minimal-worker}/smoke.mjs +0 -0
- /package/docs/examples/{minimal-worker → host-minimal-worker}/src/index.ts +0 -0
- /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
|