@aotter/mantle 0.1.2-alpha.6 → 0.1.2
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 +15 -10
- 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 +6 -1
- package/docs/adr/adr-lite-845-frontend-client.md +38 -0
- package/docs/agent-prompts.md +91 -0
- package/docs/api-mcp-authorization.md +1 -1
- package/docs/auth-hosting-model.md +1 -1
- 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 +53 -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.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 +70 -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 +9 -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 +17 -2
- 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/runtime-and-adapters.md +2 -2
- 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 +113 -0
- package/docs/handbook/sites/index.md +111 -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 +22 -23
- package/docs/migration-0.1.2.md +26 -0
- package/docs/release-process.md +92 -7
- package/docs/sealed-pipeline-ownership.md +2 -2
- package/docs/spec-only-host-adoption.md +3 -4
- package/docs/transaction-patterns.md +2 -2
- package/package.json +15 -15
- package/skills/README.md +18 -2
- package/skills/develop/SKILL.md +32 -23
- package/skills/install/SKILL.md +50 -11
- package/skills/provision/SKILL.md +21 -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
|
@@ -1,220 +1,10 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: Redirects to the builtin reservation example in the Examples hub.
|
|
3
3
|
---
|
|
4
4
|
# Reservation requests
|
|
5
5
|
|
|
6
|
-
This example
|
|
7
|
-
|
|
8
|
-
## Problem
|
|
9
|
-
|
|
10
|
-
A visitor asks for a reservation by giving a name, an email address, the requested date or slot, an optional party size and a note. Staff see the newest requests first in Admin, over the staff View REST route, or through Staff MCP, and follow up outside the system. Requests are live operational records; nothing is drafted or published. The system does not decide whether a slot is free.
|
|
11
|
-
|
|
12
|
-
## Manifest
|
|
13
|
-
|
|
14
|
-
```yaml
|
|
15
|
-
apiVersion: cms.mantle.aotter.net/v1
|
|
16
|
-
kind: Schema
|
|
17
|
-
metadata:
|
|
18
|
-
name: reservations
|
|
19
|
-
spec:
|
|
20
|
-
title: Reservations
|
|
21
|
-
lifecycle: operational
|
|
22
|
-
schema:
|
|
23
|
-
type: object
|
|
24
|
-
additionalProperties: false
|
|
25
|
-
required: [name, email, requestedFor]
|
|
26
|
-
properties:
|
|
27
|
-
name: { type: string, minLength: 1, maxLength: 120 }
|
|
28
|
-
email: { type: string, format: email }
|
|
29
|
-
requestedFor: { type: string, description: Requested date, time, or slot. }
|
|
30
|
-
partySize: { type: integer, minimum: 1 }
|
|
31
|
-
note: { type: string, maxLength: 1000 }
|
|
32
|
-
createdAt: { type: number, x-mcp-hint: timestamp-ms, x-mantle-bind: now }
|
|
33
|
-
---
|
|
34
|
-
apiVersion: cms.mantle.aotter.net/v1
|
|
35
|
-
kind: View
|
|
36
|
-
metadata:
|
|
37
|
-
name: reservation-queue
|
|
38
|
-
spec:
|
|
39
|
-
title: Reservation queue
|
|
40
|
-
surface: staff
|
|
41
|
-
from: reservations
|
|
42
|
-
fields: [id, name, email, requestedFor, partySize, note, createdAt]
|
|
43
|
-
orderBy:
|
|
44
|
-
- { field: createdAt, direction: desc }
|
|
45
|
-
limit: 50
|
|
46
|
-
---
|
|
47
|
-
apiVersion: cms.mantle.aotter.net/v1
|
|
48
|
-
kind: Procedure
|
|
49
|
-
metadata:
|
|
50
|
-
name: submit-reservation
|
|
51
|
-
spec:
|
|
52
|
-
title: Submit reservation
|
|
53
|
-
input:
|
|
54
|
-
type: object
|
|
55
|
-
additionalProperties: false
|
|
56
|
-
required: [name, email, requestedFor]
|
|
57
|
-
properties:
|
|
58
|
-
name: { type: string, minLength: 1, maxLength: 120 }
|
|
59
|
-
email: { type: string, format: email }
|
|
60
|
-
requestedFor: { type: string }
|
|
61
|
-
partySize: { type: integer, minimum: 1 }
|
|
62
|
-
note: { type: string, maxLength: 1000 }
|
|
63
|
-
output: { type: object }
|
|
64
|
-
handler: { kind: builtin, op: create, schema: reservations }
|
|
65
|
-
---
|
|
66
|
-
apiVersion: cms.mantle.aotter.net/v1
|
|
67
|
-
kind: Trigger
|
|
68
|
-
metadata:
|
|
69
|
-
name: submit-reservation-http
|
|
70
|
-
spec:
|
|
71
|
-
source: { kind: http, method: POST, path: /api/reservations }
|
|
72
|
-
target: { procedure: submit-reservation }
|
|
73
|
-
---
|
|
74
|
-
apiVersion: cms.mantle.aotter.net/v1
|
|
75
|
-
kind: Trigger
|
|
76
|
-
metadata:
|
|
77
|
-
name: submit-reservation-mcp
|
|
78
|
-
spec:
|
|
79
|
-
source: { kind: mcp, surface: public }
|
|
80
|
-
target: { procedure: submit-reservation }
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
`createdAt` is stamped by the server (`x-mantle-bind: now`); a caller-supplied value is ignored. `requestedFor` is a free string on purpose: the preset does not impose a calendar model. The staff View orders by `createdAt`, so the newest request is first regardless of the requested slot.
|
|
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
|
-
### Optional: reject requests for the past
|
|
97
|
-
|
|
98
|
-
If `requestedFor` is expected to be an ISO date-time, a `before_create` lifecycle Trigger can reject values that already passed. Add two documents to the Manifest:
|
|
99
|
-
|
|
100
|
-
```yaml
|
|
101
|
-
apiVersion: cms.mantle.aotter.net/v1
|
|
102
|
-
kind: Procedure
|
|
103
|
-
metadata:
|
|
104
|
-
name: reject-past-reservation
|
|
105
|
-
spec:
|
|
106
|
-
input:
|
|
107
|
-
type: object
|
|
108
|
-
properties:
|
|
109
|
-
requestedFor: { type: string }
|
|
110
|
-
output: { type: object }
|
|
111
|
-
handler: { kind: ref, ref: reject-past-reservation }
|
|
112
|
-
---
|
|
113
|
-
apiVersion: cms.mantle.aotter.net/v1
|
|
114
|
-
kind: Trigger
|
|
115
|
-
metadata:
|
|
116
|
-
name: reservations-before-create-guard
|
|
117
|
-
spec:
|
|
118
|
-
source:
|
|
119
|
-
kind: lifecycle
|
|
120
|
-
schema: reservations
|
|
121
|
-
on: [before_create]
|
|
122
|
-
errorPolicy: abort
|
|
123
|
-
target: { procedure: reject-past-reservation }
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
And one handler. `before_*` hooks receive the caller's original input; a thrown `InvokeFailure` cancels the write and its diagnostic is returned to the caller unchanged:
|
|
127
|
-
|
|
128
|
-
```ts
|
|
129
|
-
// src/handlers.ts
|
|
130
|
-
import { InvokeFailure } from "@aotter/mantle/runtime";
|
|
131
|
-
import { runtimeDiagnostic } from "@aotter/mantle/spec";
|
|
132
|
-
|
|
133
|
-
export async function rejectPastReservation(
|
|
134
|
-
input: { readonly requestedFor?: string },
|
|
135
|
-
): Promise<{ ok: true }> {
|
|
136
|
-
const at = input.requestedFor ? Date.parse(input.requestedFor) : Number.NaN;
|
|
137
|
-
if (Number.isFinite(at) && at < Date.now()) {
|
|
138
|
-
throw new InvokeFailure(
|
|
139
|
-
runtimeDiagnostic({
|
|
140
|
-
code: "LIFECYCLE_HOOK_REJECTED",
|
|
141
|
-
severity: "error",
|
|
142
|
-
path: "/requestedFor",
|
|
143
|
-
value: input.requestedFor,
|
|
144
|
-
expected: "a date and time that has not passed",
|
|
145
|
-
message: "Reservations cannot be requested for a past time.",
|
|
146
|
-
}),
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
return { ok: true };
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
```ts
|
|
154
|
-
// src/index.ts
|
|
155
|
-
import { createMantleWorker } from "@aotter/mantle/cloudflare";
|
|
156
|
-
import { plan, type MantleHandlers } from "../.mantle/generated/mantle.js";
|
|
157
|
-
import { rejectPastReservation } from "./handlers.js";
|
|
158
|
-
|
|
159
|
-
const handlers = {
|
|
160
|
-
"reject-past-reservation": rejectPastReservation,
|
|
161
|
-
} satisfies MantleHandlers;
|
|
162
|
-
|
|
163
|
-
export default createMantleWorker({ plan, extend: () => ({ handlers }) });
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
Unparseable strings pass through so free-form slots such as `Friday evening` still work. Tighten the input schema with `format: date-time` if only timestamps are acceptable. `LIFECYCLE_HOOK_REJECTED` maps to HTTP 409; the hook may throw any catalogued code, for example `INPUT_VALIDATION_FAILED` for 400. See [Writes: Procedures, Triggers and hooks](../concepts/procedures-and-triggers.md).
|
|
167
|
-
|
|
168
|
-
## Try it
|
|
169
|
-
|
|
170
|
-
```sh
|
|
171
|
-
curl -sS -X POST http://localhost:8787/api/reservations \
|
|
172
|
-
-H 'content-type: application/json' \
|
|
173
|
-
-d '{"name":"Ada","email":"ada@example.test","requestedFor":"2026-10-03T19:00:00+08:00","partySize":4}'
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
```json
|
|
177
|
-
{
|
|
178
|
-
"ok": true,
|
|
179
|
-
"data": {
|
|
180
|
-
"id": "res_01j...",
|
|
181
|
-
"collection": "reservations",
|
|
182
|
-
"status": "published",
|
|
183
|
-
"version": 1,
|
|
184
|
-
"data": { "name": "Ada", "email": "ada@example.test", "requestedFor": "2026-10-03T19:00:00+08:00", "partySize": 4, "createdAt": 1788879363492 },
|
|
185
|
-
"authorId": null,
|
|
186
|
-
"createdAt": 1788879363492,
|
|
187
|
-
"updatedAt": 1788879363492
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
A `partySize` of `0` is HTTP 400 `INPUT_VALIDATION_FAILED`. With the optional guard installed, a `requestedFor` in the past is HTTP 409 with `diagnostic.code: "LIFECYCLE_HOOK_REJECTED"` and no row is written.
|
|
193
|
-
|
|
194
|
-
Staff list the queue at `GET /admin/api/views/reservation-queue?page=1&show=50` (staff session required); `GET /admin/api/views/reservation-queue/export` returns the matching rows as CSV.
|
|
195
|
-
|
|
196
|
-
MCP tools:
|
|
197
|
-
|
|
198
|
-
| Surface | Tool | Origin |
|
|
199
|
-
|---|---|---|
|
|
200
|
-
| `/mcp` | `submit_reservation` | `submit-reservation-mcp` Trigger |
|
|
201
|
-
| `/mcp/staff` | `query_view_reservation_queue` | `reservation-queue` View |
|
|
202
|
-
| `/mcp/staff` | `create_record_reservations`, `update_record_reservations` | operational Schema `reservations` |
|
|
203
|
-
|
|
204
|
-
Mantle Builder ships this Manifest as its Reservation preset.
|
|
205
|
-
|
|
206
|
-
## What this deliberately leaves out
|
|
207
|
-
|
|
208
|
-
Requests are **not confirmed automatically**. A successful `POST` means the request was recorded, nothing more. The pattern omits:
|
|
209
|
-
|
|
210
|
-
- **Slot inventory.** There is no `slots` Schema and no capacity count.
|
|
211
|
-
- **Double-booking prevention.** Two requests for the same time both succeed. Preventing that needs an authority that serializes reservations, as the [commerce example](./commerce-transaction.md) does for stock with a Durable Object.
|
|
212
|
-
- **Calendar sync, payments, deposits.**
|
|
213
|
-
- **Confirmation messages.** Add an `after_create` handler as in the [intake form](./intake-form.md) when staff want a notification.
|
|
6
|
+
This example lives in the Examples hub: [`docs/examples/builtin-reservation.md`](../../examples/builtin-reservation.md).
|
|
214
7
|
|
|
215
8
|
## Source
|
|
216
9
|
|
|
217
|
-
- [`
|
|
218
|
-
- [Procedure reference](../reference/procedure.md) — builtin `create`, lifecycle hooks
|
|
219
|
-
- [`packages/mantle-runtime/src/usecase/procedure/InvokeProcedureUseCase.ts`](../../../packages/mantle-runtime/src/usecase/procedure/InvokeProcedureUseCase.ts) — `InvokeFailure`
|
|
220
|
-
- [`packages/mantle-runtime/src/index.ts`](../../../packages/mantle-runtime/src/index.ts) — `InvokeFailure` export re-exported by `@aotter/mantle/runtime`
|
|
10
|
+
- [`docs/examples/builtin-reservation.md`](../../examples/builtin-reservation.md)
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
"text": "Start here",
|
|
6
6
|
"items": [
|
|
7
7
|
{ "text": "Quickstart: a minimal Worker", "link": "/start/quickstart-worker" },
|
|
8
|
+
{ "text": "Build with ChatGPT Sites", "link": "/sites/index" },
|
|
9
|
+
{ "text": "Quickstart: local Admin (opt-in)", "link": "/start/quickstart-admin" },
|
|
8
10
|
{ "text": "Project layout and the CLI loop", "link": "/start/project-and-cli" }
|
|
9
11
|
]
|
|
10
12
|
},
|
|
@@ -20,6 +22,15 @@
|
|
|
20
22
|
{ "text": "Runtime pipeline and adapters", "link": "/concepts/runtime-and-adapters" }
|
|
21
23
|
]
|
|
22
24
|
},
|
|
25
|
+
{
|
|
26
|
+
"text": "ChatGPT Sites",
|
|
27
|
+
"items": [
|
|
28
|
+
{ "text": "Get started", "link": "/sites/index" },
|
|
29
|
+
{ "text": "Host reference", "link": "/sites/host-reference" },
|
|
30
|
+
{ "text": "Equipment checkout and external APIs", "link": "/sites/equipment-checkout" },
|
|
31
|
+
{ "text": "Earlier guide URL", "link": "/cloudflare/chatgpt-sites" }
|
|
32
|
+
]
|
|
33
|
+
},
|
|
23
34
|
{
|
|
24
35
|
"text": "Cloudflare",
|
|
25
36
|
"items": [
|
|
@@ -27,6 +38,7 @@
|
|
|
27
38
|
{ "text": "Bindings and primitives", "link": "/cloudflare/bindings" },
|
|
28
39
|
{ "text": "Authentication", "link": "/cloudflare/authentication" },
|
|
29
40
|
{ "text": "Public web, SEO and cache", "link": "/cloudflare/public-web" },
|
|
41
|
+
{ "text": "Site chrome", "link": "/cloudflare/site-chrome" },
|
|
30
42
|
{ "text": "Deferred hooks with Queues", "link": "/cloudflare/deferred-hooks-queues" },
|
|
31
43
|
{ "text": "Media uploads with R2", "link": "/cloudflare/media-r2" },
|
|
32
44
|
{ "text": "Deploy and operate", "link": "/cloudflare/deploy-and-operate" },
|
|
@@ -36,12 +48,15 @@
|
|
|
36
48
|
{
|
|
37
49
|
"text": "Examples",
|
|
38
50
|
"items": [
|
|
39
|
-
{ "text": "
|
|
51
|
+
{ "text": "Examples hub", "link": "/examples/hub" },
|
|
52
|
+
{ "text": "Intake form (builtin)", "link": "/examples/intake-form" },
|
|
53
|
+
{ "text": "Intake Turnstile and email hooks", "link": "/examples/intake-hooks" },
|
|
40
54
|
{ "text": "Publication", "link": "/examples/publication" },
|
|
41
55
|
{ "text": "Legal documents and consent", "link": "/examples/legal-documents" },
|
|
42
56
|
{ "text": "Reservation requests", "link": "/examples/reservation" },
|
|
43
57
|
{ "text": "Procurement approvals", "link": "/examples/procurement-approvals" },
|
|
44
|
-
{ "text": "Commerce
|
|
58
|
+
{ "text": "Commerce catalog (builtin)", "link": "/examples/commerce" },
|
|
59
|
+
{ "text": "Commerce inventory (DO/Queue)", "link": "/examples/commerce-transaction" },
|
|
45
60
|
{ "text": "Guarded API access", "link": "/examples/guarded-api" }
|
|
46
61
|
]
|
|
47
62
|
},
|
|
@@ -3,7 +3,7 @@ description: The requires grammar — closed auth predicates, guard Procedures,
|
|
|
3
3
|
---
|
|
4
4
|
# Authorization
|
|
5
5
|
|
|
6
|
-
`requires` is the whole authorization grammar. It appears on [Procedure](./procedure.md) and [View](./view.md) and nowhere else; Schemas and Triggers carry no `requires`. This page is the field-level contract and the runtime behaviour it produces. The narrative version is [Authorization](../concepts/authorization.md), and a worked deployment is [Guarded API](
|
|
6
|
+
`requires` is the whole authorization grammar. It appears on [Procedure](./procedure.md) and [View](./view.md) and nowhere else; Schemas and Triggers carry no `requires`. This page is the field-level contract and the runtime behaviour it produces. The narrative version is [Authorization](../concepts/authorization.md), and a worked deployment is [Guarded API](../../examples/cf-primitives-guarded-api.md). Diagnostic codes named here are catalogued in [Diagnostics](./diagnostics.md).
|
|
7
7
|
|
|
8
8
|
## `requires`
|
|
9
9
|
|
|
@@ -128,7 +128,7 @@ handler:
|
|
|
128
128
|
| `create` | Projects `input ∩ Schema.properties` into `data`, stamps every `x-mantle-bind` property, generates an id and writes. `status` is `draft`, or `published` on a `lifecycle: operational` Schema. `authorId` is `ctx.user?.id ?? null`. Returns the created row. | `input` is an object schema. No other required properties. |
|
|
129
129
|
| `update` | Loads the row (`NOT_FOUND` if absent), merges the patch over the stored `data` so omitted fields and existing stamps survive, writes under optimistic concurrency against the caller's `expectedVersion` (observed native `entry.version` at read time, not `version+1`), bumps `version`. | `id` (strict `type: string`) and `expectedVersion` (strict `type: number`) declared under `properties` **and** listed in `required`. |
|
|
130
130
|
| `upsert` with `match` | Reads the matched fields off the validated input and looks the row up by those data values. Found: the update path, using the **caller's** `expectedVersion` (never the preloaded row's version). Not found: the create path only when `expectedVersion` is omitted; a versioned write for a missing row is `NOT_FOUND` and does not recreate. | `match` equals one declared `uniqueIndexes` tuple exactly, in order. Every matched field is a Schema property, is declared in `input.properties`, and appears in `input.required`. `input` must **not** declare `id`. `expectedVersion` **must** be declared as strict `number`; it is not globally required so create can omit it. |
|
|
131
|
-
| `upsert` without `match` |
|
|
131
|
+
| `upsert` without `match` | Id-based upsert. Create when the caller omits `expectedVersion` (and either omits `id` or the id is unknown). Update when a resolved `id` is present — the caller token is required and is the OCC check. A versioned write for a missing id is `NOT_FOUND`. | `expectedVersion` must be declared as strict `number`. If `id` is declared it must be strict `string`. Neither is in `required`. |
|
|
132
132
|
| `delete` | Loads the row (`NOT_FOUND` if absent), runs the delete guard, then hard-deletes pinned to the row's status and version. Returns `{ removed }`. | `id` (strict `type: string`) declared and in `required`. |
|
|
133
133
|
| `archive` | Loads the row, checks the lifecycle state machine (`CONFLICT` on an illegal transition), then transitions to `archived` pinned to the version just read. | `id` (strict `type: string`) declared and in `required`. The target Schema must be `lifecycle: publishing`; an operational target is rejected. |
|
|
134
134
|
|
|
@@ -241,7 +241,7 @@ Each listed operation carries `name`, `title`, `description`, `input`, `uiSchema
|
|
|
241
241
|
|
|
242
242
|
When `input` declares `expectedVersion`, Admin treats that reserved name as magic: it captures the OCC target's current `version` at read time, submits it, and does not render an editable version field. Changing the selected target rebinds version (an organization row must not supply a membership mutation's version). A resolvable OCC target or a row-bound dialog must have that captured version before Run is enabled, including matched upsert where the field is declared but not globally required. If `expectedVersion` is in `input.required` and no target can be resolved, submit stays disabled. Collection create / no-row dialogs may omit it when it is not required. Builtin operations also expose `targetCollection` (the handler schema) so Admin can pick the mutated collection over a contextual parent.
|
|
243
243
|
|
|
244
|
-
Worked end-to-end examples live in [Commerce
|
|
244
|
+
Worked end-to-end examples live in [Commerce inventory](../../examples/cf-primitives-commerce-inventory.md), [Commerce catalog](../../examples/builtin-commerce.md), and [Procurement approvals](../../examples/builtin-procurement.md).
|
|
245
245
|
|
|
246
246
|
## Source
|
|
247
247
|
|
|
@@ -119,7 +119,7 @@ Other standard keywords are not rejected by the parser, but only the recognized
|
|
|
119
119
|
| Every `required` entry of `spec.schema` must be declared under `properties`. | `REQUIRED_FIELD_UNKNOWN` |
|
|
120
120
|
| `properties` and `$defs` must be objects; `oneOf` a non-empty array; `additionalProperties` a boolean or a schema. | `INVALID_MANIFEST_ENVELOPE` |
|
|
121
121
|
|
|
122
|
-
Two
|
|
122
|
+
Two accepted spellings are normalized at the boundary: `nullable: true` becomes a `type` array that includes `"null"`, and `format: url` becomes `format: uri`. `additionalProperties` keeps standard semantics: omitted or `true` preserves extra keys, `false` rejects them, a schema validates them.
|
|
123
123
|
|
|
124
124
|
### `x-mantle-bind`
|
|
125
125
|
|
|
@@ -180,11 +180,11 @@ uiSchema:
|
|
|
180
180
|
|
|
181
181
|
Every violation is `SCHEMA_UI_INVALID`. Without `primaryField` and `columns`, Admin lists an operational collection with platform metadata only. `nav` is operational Admin navigation only — it does not change runtime, MCP, or publishing validation. Parent autocomplete coexists with `list.filterField` enum tabs as a separate control.
|
|
182
182
|
|
|
183
|
-
Keep implementation-detail children fold-only. Use `nav.standalone: true` when staff also need a cross-parent list; see the [inventory example](
|
|
183
|
+
Keep implementation-detail children fold-only. Use `nav.standalone: true` when staff also need a cross-parent list; see the [inventory example](../../examples/cf-primitives-commerce-inventory.md).
|
|
184
184
|
|
|
185
185
|
## Indexes
|
|
186
186
|
|
|
187
|
-
`uniqueIndexes` and `indexes` are arrays of ordered field tuples. Shape errors are `INVALID_MANIFEST_ENVELOPE`; semantic errors are `SCHEMA_INDEX_INVALID` unless noted.
|
|
187
|
+
`uniqueIndexes` and `indexes` are arrays of ordered field tuples. Shape errors are `INVALID_MANIFEST_ENVELOPE`; semantic errors are `SCHEMA_INDEX_INVALID` unless noted. Each index is an array of field names, even when it has one field; a bare string is rejected.
|
|
188
188
|
|
|
189
189
|
| Rule | Diagnostic |
|
|
190
190
|
|---|---|
|
|
@@ -3,7 +3,7 @@ description: siteDefaults reference — locales, brand, icons, media purposes, t
|
|
|
3
3
|
---
|
|
4
4
|
# Site config
|
|
5
5
|
|
|
6
|
-
Site config is a sibling of the Manifest grammar, not part of it. The four atoms describe content; `siteDefaults` describes the deployment: the locales the site publishes in, its brand and title, its canonical origin, its icons
|
|
6
|
+
Site config is a sibling of the Manifest grammar, not part of it. The four atoms describe content; `siteDefaults` describes the deployment: the locales the site publishes in, its brand and title, its canonical origin, its icons and its media taxonomy. Tracking pixels, search-engine verification tokens and similar chrome are host-owned — consumers ship SSR documents, SPAs, Workers plus assets, Pages and custom wrappers, so Core does not rewrite `</head>` after the fact. See [Why Core does not inject](../cloudflare/site-chrome.md#why-core-does-not-inject). The deployment declares `siteDefaults` as a TypeScript object and passes it to the adapter; the runtime seeds it into the `site_config` table and every render, MCP catalog build and Admin page reads it back from there.
|
|
7
7
|
|
|
8
8
|
## `siteDefaults`
|
|
9
9
|
|
|
@@ -15,8 +15,6 @@ Site config is a sibling of the Manifest grammar, not part of it. The four atoms
|
|
|
15
15
|
| `description` | string | no | Default `<meta name="description">` and `og:description` for entries with none. |
|
|
16
16
|
| `origin` | string | no | Canonical absolute origin, no trailing slash, for example `https://example.com`. Used to build absolute URLs in `/llms.txt`, the `.md` mirrors and `/sitemap.xml`. An empty origin yields relative URLs. |
|
|
17
17
|
| `icons` | `SiteIcon[]` | no | One site identity reused by browser favicons, Admin chrome and MCP `serverInfo.icons`. Declaring an empty array is an error. |
|
|
18
|
-
| `ga4MeasurementId` | string | no | GA4 Measurement ID such as `G-XXXXXXXXXX`. When present the runtime injects the standard gtag snippet into rendered public HTML. |
|
|
19
|
-
| `facebookPixelId` | string | no | Meta Pixel ID. When present the runtime injects the standard Pixel base snippet. |
|
|
20
18
|
| `media.purposes` | `MediaPurposePolicy[]` | no | The upload taxonomy. Omitting the key, or declaring an empty array, keeps first-party media uploads disabled. |
|
|
21
19
|
|
|
22
20
|
Nothing in this object is validated for length or content beyond the rules above: `brand`, `title`, `description` and `origin` are free strings.
|
|
@@ -42,11 +40,10 @@ interface SiteIcon {
|
|
|
42
40
|
When the deployment declares no icons, the runtime stores `DEFAULT_SITE_ICONS`:
|
|
43
41
|
|
|
44
42
|
```ts
|
|
45
|
-
const DEFAULT_SITE_ICONS = [
|
|
46
|
-
src: "/_mantle/admin/favicon.
|
|
47
|
-
mimeType: "image/svg+xml",
|
|
48
|
-
|
|
49
|
-
}];
|
|
43
|
+
const DEFAULT_SITE_ICONS = [
|
|
44
|
+
{ src: "/_mantle/admin/favicon.png", mimeType: "image/png", sizes: ["64x64"] },
|
|
45
|
+
{ src: "/_mantle/admin/favicon.svg", mimeType: "image/svg+xml", sizes: ["any"] },
|
|
46
|
+
];
|
|
50
47
|
```
|
|
51
48
|
|
|
52
49
|
Multiple renditions are allowed. Keep SVG as the source and add a PNG rendition when a target MCP client needs a baseline raster format. `GET /favicon.ico` resolves against this list; see [Surface](./surface.md).
|
|
@@ -84,8 +81,6 @@ Slot position does not determine variant role. Per asset the uploading agent pic
|
|
|
84
81
|
| `locales` | `readonly string[]` | `[]` |
|
|
85
82
|
| `canonicalLocale` | `string \| null` | `locales[0]` or `null` when the list is empty |
|
|
86
83
|
| `icons` | `readonly SiteIcon[]` | `DEFAULT_SITE_ICONS` |
|
|
87
|
-
| `ga4MeasurementId` | `string \| undefined` | `undefined` (an empty stored value also reads as `undefined`) |
|
|
88
|
-
| `facebookPixelId` | `string \| undefined` | `undefined` |
|
|
89
84
|
| `media.purposes` | `readonly MediaPurposePolicy[]` | `[]` |
|
|
90
85
|
|
|
91
86
|
`canonicalLocale` is computed, never stored. Templates emit `<html lang>` only when it is non-null; silent omission is the correct behaviour for a zero-locale site, not a fabricated default.
|
|
@@ -99,8 +94,6 @@ The table is a flat key/value store. Keys fall into two ownership classes, and t
|
|
|
99
94
|
| `brand` | UI-editable, seed-once | `INSERT … ON CONFLICT DO NOTHING` | The database, once the row exists |
|
|
100
95
|
| `title` | UI-editable, seed-once | `INSERT … ON CONFLICT DO NOTHING` | The database, once the row exists |
|
|
101
96
|
| `description` | UI-editable, seed-once | `INSERT … ON CONFLICT DO NOTHING` | The database, once the row exists |
|
|
102
|
-
| `ga4MeasurementId` | UI-editable, seed-once | `INSERT … ON CONFLICT DO NOTHING` | The database, once the row exists |
|
|
103
|
-
| `facebookPixelId` | UI-editable, seed-once | `INSERT … ON CONFLICT DO NOTHING` | The database, once the row exists |
|
|
104
97
|
| `origin` | Code-canonical, boot-synced | Upsert when the stored value differs | The declaration |
|
|
105
98
|
| `faviconUrl` | Code-canonical, boot-synced | Upsert when the stored value differs; holds the `icons` array as JSON | The declaration |
|
|
106
99
|
| `locales` | Code-canonical, boot-synced | Upsert when the stored value differs; holds the declared list as a comma-separated string | The declaration |
|
|
@@ -110,9 +103,6 @@ Blank values are skipped in both classes: an absent, empty or empty-array field
|
|
|
110
103
|
|
|
111
104
|
The seed-once keys have an Admin edit path at `PATCH /admin/api/site-settings` (owner only); the boot-synced keys do not, which is why the declaration wins on every boot. A custom-domain change therefore becomes canonical by editing the code and redeploying, with no manual database edit.
|
|
112
105
|
|
|
113
|
-
> **Warning**
|
|
114
|
-
> `mediaPurposes` is JSON. Rows written by pre-`#272` deployments used a CSV form and do not round-trip. Re-run the seed, or delete the row, after upgrading.
|
|
115
|
-
|
|
116
106
|
## Validated at boot
|
|
117
107
|
|
|
118
108
|
Storage preparation calls `assertSiteDefaultsCanonical(siteDefaults)` synchronously, before the runtime accepts traffic. It throws — it does not return diagnostics — so a typo rejects the deployment rather than corrupting the seed.
|
|
@@ -196,7 +186,6 @@ export default createMantleWorker({
|
|
|
196
186
|
{ src: "/site-icon.svg", mimeType: "image/svg+xml", sizes: ["any"] },
|
|
197
187
|
{ src: "/site-icon.png", mimeType: "image/png", sizes: ["64x64"] },
|
|
198
188
|
],
|
|
199
|
-
ga4MeasurementId: "G-XXXXXXXXXX",
|
|
200
189
|
media: {
|
|
201
190
|
purposes: [
|
|
202
191
|
{
|
|
@@ -176,13 +176,11 @@ The umbrella installs Spec and Runtime only. Web, Admin, Admin UI, Bun, Vercel a
|
|
|
176
176
|
|
|
177
177
|
## Versions
|
|
178
178
|
|
|
179
|
-
This handbook
|
|
179
|
+
This handbook describes the snapshot in this source tree. Pin every `@aotter/mantle*` package to one exact version and keep them together. For a registry installation, use the documentation at the matching release tag. For a source-built package, record the source commit as well as the package version.
|
|
180
180
|
|
|
181
|
-
The documentation site pins its handbook commit in `docs/handbook.json` and records its vendored SDK source in `vendor/mantle/SOURCE.txt`. Those commits may differ when the intervening changes are behavior-neutral; the site verifies that condition during the build.
|
|
181
|
+
The documentation site pins its handbook commit in `docs/handbook.json` and records its vendored SDK source in `vendor/mantle/SOURCE.txt`. Those commits may differ when the intervening changes are behavior-neutral; the site verifies that condition during the build.
|
|
182
182
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
Prerelease packages take their exact version from their own `package.json`, which is the authority; APIs may change between prereleases until `0.1.2`.
|
|
183
|
+
Projects are authored directly: write the manifests, run `generate`, `skills`, `validate` and `emit-openapi`, and wire the Worker. There is no project generator. Prerelease packages take their exact version from their own `package.json`; APIs may change between prereleases until a stable line ships.
|
|
186
184
|
|
|
187
185
|
## Source
|
|
188
186
|
|
|
@@ -203,5 +201,3 @@ Prerelease packages take their exact version from their own `package.json`, whic
|
|
|
203
201
|
- [`packages/adapters/cloudflare/src/mount/mountPublicRoutes.ts`](../../../packages/adapters/cloudflare/src/mount/mountPublicRoutes.ts)
|
|
204
202
|
- [`packages/adapters/cloudflare/src/mount/mountMcp.ts`](../../../packages/adapters/cloudflare/src/mount/mountMcp.ts)
|
|
205
203
|
- [`packages/adapters/cloudflare/src/oauth/cachePolicy.ts`](../../../packages/adapters/cloudflare/src/oauth/cachePolicy.ts)
|
|
206
|
-
- [`docs/adr/0021-retire-starter-scaffolding.md`](../../../docs/adr/0021-retire-starter-scaffolding.md)
|
|
207
|
-
- [`docs/migration-0.1.2.md`](../../../docs/migration-0.1.2.md)
|