@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.
Files changed (110) hide show
  1. package/README.md +15 -10
  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 +6 -1
  11. package/docs/adr/adr-lite-845-frontend-client.md +38 -0
  12. package/docs/agent-prompts.md +91 -0
  13. package/docs/api-mcp-authorization.md +1 -1
  14. package/docs/auth-hosting-model.md +1 -1
  15. package/docs/examples/README.md +22 -0
  16. package/docs/examples/builtin-commerce.md +269 -0
  17. package/docs/examples/builtin-intake.md +143 -0
  18. package/docs/examples/builtin-legal-documents.md +189 -0
  19. package/docs/examples/builtin-procurement.md +241 -0
  20. package/docs/examples/builtin-publication.md +241 -0
  21. package/docs/examples/builtin-reservation.md +149 -0
  22. package/docs/examples/cf-primitives-commerce-inventory.md +809 -0
  23. package/docs/examples/cf-primitives-guarded-api.md +429 -0
  24. package/docs/examples/cf-primitives-intake-hooks.md +319 -0
  25. package/docs/examples/host-chatgpt-sites/.openai/hosting.json +1 -0
  26. package/docs/examples/host-chatgpt-sites/README.md +53 -0
  27. package/docs/examples/host-chatgpt-sites/drizzle/0000_sites_users.sql +8 -0
  28. package/docs/examples/host-chatgpt-sites/drizzle/0001_mantle.sql +294 -0
  29. package/docs/examples/host-chatgpt-sites/drizzle/0002_article_cover.sql +5 -0
  30. package/docs/examples/host-chatgpt-sites/drizzle/meta/_journal.json +1 -0
  31. package/docs/examples/host-chatgpt-sites/manifests/site.yaml +27 -0
  32. package/docs/examples/host-chatgpt-sites/package.json +1 -0
  33. package/docs/examples/host-chatgpt-sites/public/site.css +1 -0
  34. package/docs/examples/host-chatgpt-sites/scripts/build.mjs +12 -0
  35. package/docs/examples/host-chatgpt-sites/scripts/check.mjs +99 -0
  36. package/docs/examples/host-chatgpt-sites/scripts/migration.mjs +9 -0
  37. package/docs/examples/host-chatgpt-sites/src/chatgpt-auth.ts +63 -0
  38. package/docs/examples/host-chatgpt-sites/src/index.ts +46 -0
  39. package/docs/examples/host-chatgpt-sites/src/mcp.ts +49 -0
  40. package/docs/examples/host-chatgpt-sites/src/media.ts +109 -0
  41. package/docs/examples/host-chatgpt-sites/src/r2-lab.ts +38 -0
  42. package/docs/examples/host-chatgpt-sites/src/storage-fingerprint.json +1 -0
  43. package/docs/examples/host-chatgpt-sites/src/web.ts +35 -0
  44. package/docs/examples/host-chatgpt-sites/tsconfig.json +1 -0
  45. package/docs/examples/host-chatgpt-sites/wrangler.jsonc +10 -0
  46. package/docs/examples/host-local-admin-otp/.dev.vars.example +3 -0
  47. package/docs/examples/host-local-admin-otp/README.md +70 -0
  48. package/docs/examples/host-local-admin-otp/ensure-dev-vars.mjs +5 -0
  49. package/docs/examples/host-local-admin-otp/package.json +29 -0
  50. package/docs/examples/host-local-admin-otp/public/.gitkeep +1 -0
  51. package/docs/examples/host-local-admin-otp/smoke.mjs +141 -0
  52. package/docs/examples/host-local-admin-otp/src/index.ts +54 -0
  53. package/docs/examples/host-local-admin-otp/wrangler.jsonc +23 -0
  54. package/docs/examples/{minimal-worker → host-minimal-worker}/README.md +9 -5
  55. package/docs/examples/host-minimal-worker/manifests/site.yaml +25 -0
  56. package/docs/examples/{minimal-worker → host-minimal-worker}/package.json +3 -3
  57. package/docs/examples/host-minimal-worker/tsconfig.json +17 -0
  58. package/docs/handbook/cloudflare/authentication.md +17 -2
  59. package/docs/handbook/cloudflare/bindings.md +9 -7
  60. package/docs/handbook/cloudflare/chatgpt-sites.md +29 -0
  61. package/docs/handbook/cloudflare/conventional-worker.md +3 -3
  62. package/docs/handbook/cloudflare/deferred-hooks-queues.md +0 -1
  63. package/docs/handbook/cloudflare/deploy-and-operate.md +14 -20
  64. package/docs/handbook/cloudflare/media-r2.md +2 -2
  65. package/docs/handbook/cloudflare/public-web.md +1 -1
  66. package/docs/handbook/cloudflare/site-chrome.md +75 -0
  67. package/docs/handbook/concepts/authorization.md +2 -2
  68. package/docs/handbook/concepts/four-atoms.md +2 -2
  69. package/docs/handbook/concepts/lifecycle-and-locales.md +1 -1
  70. package/docs/handbook/concepts/mcp-and-agents.md +1 -1
  71. package/docs/handbook/concepts/procedures-and-triggers.md +1 -1
  72. package/docs/handbook/concepts/runtime-and-adapters.md +2 -2
  73. package/docs/handbook/concepts/views.md +2 -2
  74. package/docs/handbook/examples/commerce-transaction.md +4 -806
  75. package/docs/handbook/examples/commerce.md +11 -0
  76. package/docs/handbook/examples/guarded-api.md +3 -420
  77. package/docs/handbook/examples/hub.md +10 -0
  78. package/docs/handbook/examples/intake-form.md +6 -313
  79. package/docs/handbook/examples/intake-hooks.md +11 -0
  80. package/docs/handbook/examples/legal-documents.md +3 -211
  81. package/docs/handbook/examples/procurement-approvals.md +3 -233
  82. package/docs/handbook/examples/publication.md +3 -233
  83. package/docs/handbook/examples/reservation.md +3 -213
  84. package/docs/handbook/navigation.json +17 -2
  85. package/docs/handbook/reference/authorization.md +1 -1
  86. package/docs/handbook/reference/procedure.md +2 -2
  87. package/docs/handbook/reference/schema.md +3 -3
  88. package/docs/handbook/reference/site-config.md +5 -16
  89. package/docs/handbook/reference/surface.md +3 -7
  90. package/docs/handbook/sites/equipment-checkout.md +231 -0
  91. package/docs/handbook/sites/host-reference.md +113 -0
  92. package/docs/handbook/sites/index.md +111 -0
  93. package/docs/handbook/start/project-and-cli.md +22 -14
  94. package/docs/handbook/start/quickstart-admin.md +239 -0
  95. package/docs/handbook/start/quickstart-worker.md +22 -23
  96. package/docs/migration-0.1.2.md +26 -0
  97. package/docs/release-process.md +92 -7
  98. package/docs/sealed-pipeline-ownership.md +2 -2
  99. package/docs/spec-only-host-adoption.md +3 -4
  100. package/docs/transaction-patterns.md +2 -2
  101. package/package.json +15 -15
  102. package/skills/README.md +18 -2
  103. package/skills/develop/SKILL.md +32 -23
  104. package/skills/install/SKILL.md +50 -11
  105. package/skills/provision/SKILL.md +21 -5
  106. /package/docs/examples/{minimal-worker → host-local-admin-otp}/manifests/site.yaml +0 -0
  107. /package/docs/examples/{minimal-worker → host-local-admin-otp}/tsconfig.json +0 -0
  108. /package/docs/examples/{minimal-worker → host-minimal-worker}/smoke.mjs +0 -0
  109. /package/docs/examples/{minimal-worker → host-minimal-worker}/src/index.ts +0 -0
  110. /package/docs/examples/{minimal-worker → host-minimal-worker}/wrangler.jsonc +0 -0
@@ -1,220 +1,10 @@
1
1
  ---
2
- description: Accept public reservation requests and expose the queue to staff, with an optional guard against past dates.
2
+ description: Redirects to the builtin reservation example in the Examples hub.
3
3
  ---
4
4
  # Reservation requests
5
5
 
6
- This example accepts reservation requests from the public and lists them for staff. It is the Builder `reservation` preset, fully declarative, plus one optional lifecycle guard. Read it if you take appointments, bookings or table requests and confirm them by hand.
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
- - [`README.md`](../../../README.md) — reservations excerpt
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": "Intake form", "link": "/examples/intake-form" },
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 and settlement", "link": "/examples/commerce-transaction" },
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](../examples/guarded-api.md). Diagnostic codes named here are catalogued in [Diagnostics](./diagnostics.md).
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` | Legacy form. 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`. |
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 transaction](../examples/commerce-transaction.md) and [Procurement approvals](../examples/procurement-approvals.md).
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 compatibility normalizations run 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.
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](../examples/commerce-transaction.md).
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. Bare strings and the retired `indexedFields` key are rejected; each index is an array of field names, even when it has one field.
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, its analytics ids and its media taxonomy. The deployment declares it 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.
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.svg",
47
- mimeType: "image/svg+xml",
48
- sizes: ["any"],
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 was added on the development branch after `v0.1.0-alpha.17`. It describes that development snapshot and will ship with a future release; it is not included in the published `0.1.0-alpha.17` package. 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: a development checkout can still carry the previous release version.
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. The site's source-built tarballs are not the npm `alpha.17` artifacts.
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
- `0.1.0-alpha.17` is immutable and no stable `0.1.0` is planned; the first stable target is `0.1.2`. That line removes the scaffolding path — the `mantle create` command, the bundle-oriented `mantle update` command, and the `@aotter/mantle/provision` subpath — with no aliases and no replacement scaffold command. `generate`, `skills`, `validate` and `emit-openapi` remain, and generation and runtime Web rendering keep their existing responsibilities. New projects are authored directly: write the manifests, run `generate`, wire the Worker. The reasoning is [ADR-0021](../../../docs/adr/0021-retire-starter-scaffolding.md).
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)