@aotter/mantle 0.1.0-alpha.9 → 0.1.2-alpha.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 +38 -38
- package/dist/cli/generate.d.ts +10 -1
- package/dist/cli/generate.d.ts.map +1 -1
- package/dist/cli/generate.js +49 -4
- package/dist/cli/generate.js.map +1 -1
- package/dist/cli/main.js +0 -8
- package/dist/cli/main.js.map +1 -1
- package/dist/codegen/emitMantleModule.js +8 -0
- package/dist/codegen/emitMantleModule.js.map +1 -1
- package/docs/adapter-guide.md +23 -26
- package/docs/adr/0008-structured-diagnostic-shape.md +7 -2
- package/docs/adr/0010-locale-and-translates.md +14 -16
- package/docs/adr/0011-adapter-port-spec.md +2 -1
- package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +100 -0
- package/docs/adr/0018-core-starters-repository-boundary.md +3 -0
- package/docs/adr/0019-sealed-manifest-runtime-pipeline.md +38 -2
- package/docs/adr/0020-builtin-handler-contracts-and-matched-upsert.md +199 -0
- package/docs/adr/0021-retire-starter-scaffolding.md +30 -0
- package/docs/adr/0022-caller-observed-version-occ.md +212 -0
- package/docs/adr/0023-port-failure-contract.md +69 -0
- package/docs/adr/0024-manifest-native-schema-tables.md +260 -0
- package/docs/adr/README.md +19 -3
- package/docs/adr/adr-lite-803-request-diagnostics.md +38 -0
- package/docs/adr/adr-lite-808-route-readiness.md +47 -0
- package/docs/adr/adr-lite-809-bounded-public-content.md +71 -0
- package/docs/adr/adr-lite-812-native-parity.md +150 -0
- package/docs/adr/adr-lite-823-home-statistics.md +64 -0
- package/docs/adr/adr-lite-842-mcp-authoring.md +30 -0
- package/docs/adr/adr-lite-861-admin-webmcp.md +42 -0
- package/docs/adr/adr-lite-909-admin-ui-kit.md +37 -0
- package/docs/api-mcp-authorization.md +3 -664
- package/docs/auth-hosting-model.md +4 -4
- package/docs/cloudflare-low-level-composition.md +3 -104
- package/docs/deferred-lifecycle-queues.md +2 -243
- package/docs/design-atoms.md +16 -796
- package/docs/direct-authoring.md +5 -0
- package/docs/examples/minimal-worker/README.md +37 -0
- package/docs/examples/minimal-worker/manifests/site.yaml +25 -0
- package/docs/examples/minimal-worker/package.json +26 -0
- package/docs/examples/minimal-worker/smoke.mjs +23 -0
- package/docs/examples/minimal-worker/src/index.ts +4 -0
- package/docs/examples/minimal-worker/tsconfig.json +17 -0
- package/docs/examples/minimal-worker/wrangler.jsonc +22 -0
- package/docs/handbook/cloudflare/authentication.md +167 -0
- package/docs/handbook/cloudflare/bindings.md +236 -0
- package/docs/handbook/cloudflare/conventional-worker.md +162 -0
- package/docs/handbook/cloudflare/deferred-hooks-queues.md +171 -0
- package/docs/handbook/cloudflare/deploy-and-operate.md +105 -0
- package/docs/handbook/cloudflare/low-level-composition.md +169 -0
- package/docs/handbook/cloudflare/media-r2.md +216 -0
- package/docs/handbook/cloudflare/public-web.md +144 -0
- package/docs/handbook/concepts/authorization.md +116 -0
- package/docs/handbook/concepts/four-atoms.md +173 -0
- package/docs/handbook/concepts/lifecycle-and-locales.md +142 -0
- package/docs/handbook/concepts/mcp-and-agents.md +109 -0
- package/docs/handbook/concepts/procedures-and-triggers.md +221 -0
- package/docs/handbook/concepts/runtime-and-adapters.md +177 -0
- package/docs/handbook/concepts/views.md +191 -0
- package/docs/handbook/examples/commerce-transaction.md +813 -0
- package/docs/handbook/examples/guarded-api.md +427 -0
- package/docs/handbook/examples/intake-form.md +319 -0
- package/docs/handbook/examples/legal-documents.md +218 -0
- package/docs/handbook/examples/procurement-approvals.md +240 -0
- package/docs/handbook/examples/publication.md +240 -0
- package/docs/handbook/examples/reservation.md +220 -0
- package/docs/handbook/navigation.json +63 -0
- package/docs/handbook/reference/authorization.md +295 -0
- package/docs/handbook/reference/diagnostics.md +183 -0
- package/docs/handbook/reference/manifest.md +132 -0
- package/docs/handbook/reference/procedure.md +263 -0
- package/docs/handbook/reference/schema.md +250 -0
- package/docs/handbook/reference/site-config.md +233 -0
- package/docs/handbook/reference/surface.md +207 -0
- package/docs/handbook/reference/trigger.md +193 -0
- package/docs/handbook/reference/view.md +256 -0
- package/docs/handbook/start/project-and-cli.md +122 -0
- package/docs/handbook/start/quickstart-worker.md +193 -0
- package/docs/labels.md +3 -1
- package/docs/media-uploads.md +3 -184
- package/docs/migration-0.1.2.md +60 -3
- package/docs/performance-harness.md +127 -10
- package/docs/release-process.md +85 -231
- package/docs/schema-indexes.md +3 -180
- package/docs/sealed-pipeline-ownership.md +6 -5
- package/docs/spec-only-host-adoption.md +158 -0
- package/docs/transaction-patterns.md +5 -0
- package/package.json +21 -25
- package/skills/README.md +4 -4
- package/skills/develop/SKILL.md +23 -28
- package/skills/install/SKILL.md +55 -147
- package/skills/provision/SKILL.md +4 -3
- package/skills/theme/SKILL.md +6 -6
- package/skills/update/SKILL.md +32 -68
- package/dist/cli/create.d.ts +0 -2
- package/dist/cli/create.d.ts.map +0 -1
- package/dist/cli/create.js +0 -243
- package/dist/cli/create.js.map +0 -1
- package/dist/cli/update.d.ts +0 -2
- package/dist/cli/update.d.ts.map +0 -1
- package/dist/cli/update.js +0 -413
- package/dist/cli/update.js.map +0 -1
- package/dist/provision/renderProvisionBundle.d.ts +0 -70
- package/dist/provision/renderProvisionBundle.d.ts.map +0 -1
- package/dist/provision/renderProvisionBundle.js +0 -367
- package/dist/provision/renderProvisionBundle.js.map +0 -1
- package/dist/provision.d.ts +0 -2
- package/dist/provision.d.ts.map +0 -1
- package/dist/provision.js +0 -2
- package/dist/provision.js.map +0 -1
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Schema field reference — JSON Schema subset, extension keywords, uiSchema, indexes, searchable fields, translates and lifecycle rules.
|
|
3
|
+
---
|
|
4
|
+
# Schema
|
|
5
|
+
|
|
6
|
+
A Schema declares one collection: the JSON Schema for each entry's `data`, its indexes, its Admin presentation and its lifecycle mode. Schemas are never exposed directly; Views read them and Procedures write them. This page is the field-level contract; the concepts are in [The four atoms](../concepts/four-atoms.md) and [Lifecycle and locales](../concepts/lifecycle-and-locales.md). Envelope rules are in [Manifest envelope and conventions](./manifest.md).
|
|
7
|
+
|
|
8
|
+
## Fields
|
|
9
|
+
|
|
10
|
+
| Field | Type | Required | Default | Rules |
|
|
11
|
+
|---|---|---|---|---|
|
|
12
|
+
| `title` | LocalizedText | yes | — | Admin label. Non-empty string or locale map. |
|
|
13
|
+
| `description` | LocalizedText | no | — | Same shape as `title`. |
|
|
14
|
+
| `schema` | JSON Schema 2020-12 | yes | — | Must be an object. Walked by the [subset validator](#json-schema-subset). |
|
|
15
|
+
| `uiSchema` | object | no | — | Accepts `fields`, `list`, and `nav`. Violations are `SCHEMA_UI_INVALID`. |
|
|
16
|
+
| `uniqueIndexes` | `string[][]` | no | `[]` | Ordered tuples of top-level scalar fields. See [Indexes](#indexes). |
|
|
17
|
+
| `indexes` | `string[][]` | no | `[]` | Ordered non-unique tuples. Must not repeat a `uniqueIndexes` tuple. |
|
|
18
|
+
| `searchableFields` | `string[]` | no | `[]` | Top-level string fields for Admin and Staff MCP substring search. |
|
|
19
|
+
| `localized` | boolean | no | `false` | When `false`, a `locale` property is rejected. Must be a boolean. |
|
|
20
|
+
| `translates` | `{ parent, on }` | no | — | Marks a translation child. Requires `localized: true`. |
|
|
21
|
+
| `lifecycle` | `publishing` \| `operational` | no | `publishing` | Selects the [state machine](#lifecycle). |
|
|
22
|
+
|
|
23
|
+
### Reserved entry columns
|
|
24
|
+
|
|
25
|
+
Every entry carries `id`, `status`, `version`, `createdAt`, `updatedAt` and `authorId` as native columns outside `data`. They cannot be indexed (`SCHEMA_INDEX_INVALID`) but are valid in View `fields`, `filter`, `orderBy` and `uiSchema.list`. `locale` is a reserved data field: only a localized Schema may declare it, and the runtime requires it on writes to a localized Schema. Do not name data properties after the native columns; SQL Views project the native column. Do not declare `expectedVersion` under `spec.schema.properties` — that name is the reserved Procedure OCC token; validate fails closed with `INVALID_MANIFEST_ENVELOPE` (ADR-0022). New reserved Procedure input names need an ADR.
|
|
26
|
+
|
|
27
|
+
## Example
|
|
28
|
+
|
|
29
|
+
```yaml
|
|
30
|
+
apiVersion: cms.mantle.aotter.net/v1
|
|
31
|
+
kind: Schema
|
|
32
|
+
metadata:
|
|
33
|
+
name: support-requests
|
|
34
|
+
spec:
|
|
35
|
+
title: { en: Support requests, "zh-TW": 客服請求 }
|
|
36
|
+
description: Requests submitted from the public contact form.
|
|
37
|
+
lifecycle: operational
|
|
38
|
+
schema:
|
|
39
|
+
type: object
|
|
40
|
+
additionalProperties: false
|
|
41
|
+
required: [ticketNumber, subject, body, requestStatus]
|
|
42
|
+
properties:
|
|
43
|
+
ticketNumber: { type: string, pattern: "^SR-[0-9]{6}$" }
|
|
44
|
+
subject: { type: string, minLength: 1, maxLength: 200 }
|
|
45
|
+
body: { type: string, x-mcp-hint: markdown }
|
|
46
|
+
requestStatus: { type: string, enum: [open, waiting, closed], default: open }
|
|
47
|
+
customerId: { type: string, x-mantle-ref: customers }
|
|
48
|
+
attachmentId: { type: string, x-mantle-ref: media_assets, x-mcp-hint: media-file }
|
|
49
|
+
submittedBy: { type: string, x-mantle-bind: ctx.user }
|
|
50
|
+
submittedAt: { type: integer, x-mcp-hint: timestamp-ms, x-mantle-bind: now }
|
|
51
|
+
uiSchema:
|
|
52
|
+
fields:
|
|
53
|
+
body: { widget: textarea }
|
|
54
|
+
list:
|
|
55
|
+
filterField: requestStatus
|
|
56
|
+
primaryField: ticketNumber
|
|
57
|
+
columns: [subject, requestStatus, submittedAt]
|
|
58
|
+
uniqueIndexes: [[ticketNumber]]
|
|
59
|
+
indexes: [[requestStatus, submittedAt], [customerId]]
|
|
60
|
+
searchableFields: [ticketNumber, subject]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## `translates`
|
|
64
|
+
|
|
65
|
+
A translation child holds the locale-specific fields of a non-localized parent, joined on a shared field.
|
|
66
|
+
|
|
67
|
+
```yaml
|
|
68
|
+
apiVersion: cms.mantle.aotter.net/v1
|
|
69
|
+
kind: Schema
|
|
70
|
+
metadata:
|
|
71
|
+
name: product-translations
|
|
72
|
+
spec:
|
|
73
|
+
title: Product translations
|
|
74
|
+
localized: true
|
|
75
|
+
translates: { parent: products, on: sku }
|
|
76
|
+
schema:
|
|
77
|
+
type: object
|
|
78
|
+
required: [sku, name]
|
|
79
|
+
properties:
|
|
80
|
+
sku: { type: string }
|
|
81
|
+
locale: { type: string }
|
|
82
|
+
name: { type: string, minLength: 1 }
|
|
83
|
+
description: { type: string, x-mcp-hint: markdown }
|
|
84
|
+
uniqueIndexes: [[sku, locale]]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`locale` is declared but not listed in `required`; the write-time gate enforces presence.
|
|
88
|
+
|
|
89
|
+
| Phase | Rule | Diagnostic |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| parse | `parent` and `on` are non-empty strings; no other keys. | `INVALID_MANIFEST_ENVELOPE` |
|
|
92
|
+
| parse | `localized: true` is set. | `TRANSLATES_REQUIRES_LOCALIZED` |
|
|
93
|
+
| parse | At least one property besides `locale` and the join field. | `TRANSLATES_REQUIRES_CONTENT_FIELD` |
|
|
94
|
+
| parse | `on` is declared in this Schema's `properties`. | `TRANSLATES_FIELD_NOT_IN_CHILD` |
|
|
95
|
+
| validate and boot | `parent` names a declared Schema. | `TRANSLATES_PARENT_UNKNOWN` |
|
|
96
|
+
| validate and boot | The parent is not `localized: true`. | `TRANSLATES_PARENT_IS_LOCALIZED` |
|
|
97
|
+
| validate and boot | `on` is declared in the parent's `properties`. | `TRANSLATES_FIELD_NOT_IN_PARENT` |
|
|
98
|
+
| boot | A localized Schema needs at least one site locale. | `SCHEMA_LOCALIZED_REQUIRES_SITE_LOCALES` |
|
|
99
|
+
|
|
100
|
+
## JSON Schema subset
|
|
101
|
+
|
|
102
|
+
Authors write JSON Schema; the runtime validates with zod via `z.fromJSONSchema`. The parser accepts a bounded subset so every manifest converts.
|
|
103
|
+
|
|
104
|
+
| Recognized keywords |
|
|
105
|
+
|---|
|
|
106
|
+
| `$defs`, `$ref`, `oneOf`, `const`, `type`, `properties`, `required`, `items`, `enum`, `format`, `pattern`, `minLength`, `maxLength`, `minimum`, `maximum`, `minItems`, `maxItems`, `nullable`, `readOnly`, `default`, `additionalProperties`, `title`, `description`, and any `x-` keyword |
|
|
107
|
+
|
|
108
|
+
| Rejected keywords (`JSON_SCHEMA_UNSUPPORTED`) |
|
|
109
|
+
|---|
|
|
110
|
+
| `anyOf`, `allOf`, `not`, `if`, `then`, `else`, `$anchor`, `$dynamicAnchor`, `$dynamicRef`, `definitions`, `patternProperties`, `prefixItems`, `contains`, `dependentSchemas`, `propertyNames`, `unevaluatedProperties` |
|
|
111
|
+
|
|
112
|
+
Other standard keywords are not rejected by the parser, but only the recognized set is part of the documented contract.
|
|
113
|
+
|
|
114
|
+
| Rule | Diagnostic |
|
|
115
|
+
|---|---|
|
|
116
|
+
| Nesting deeper than 100 levels, or more than 10,000 schema nodes. | `JSON_SCHEMA_LIMIT_EXCEEDED` |
|
|
117
|
+
| `$ref` must begin `#/$defs/` and resolve to an object in the same document. | `JSON_SCHEMA_REF_INVALID` |
|
|
118
|
+
| `pattern` must compile as a JavaScript regular expression. | `INVALID_PATTERN` |
|
|
119
|
+
| Every `required` entry of `spec.schema` must be declared under `properties`. | `REQUIRED_FIELD_UNKNOWN` |
|
|
120
|
+
| `properties` and `$defs` must be objects; `oneOf` a non-empty array; `additionalProperties` a boolean or a schema. | `INVALID_MANIFEST_ENVELOPE` |
|
|
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.
|
|
123
|
+
|
|
124
|
+
### `x-mantle-bind`
|
|
125
|
+
|
|
126
|
+
Marks a property as server-stamped. The value is a closed enum; anything else is `BIND_VALUE_NOT_IN_ENUM`.
|
|
127
|
+
|
|
128
|
+
| Value | Stamped as | Typical field |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| `ctx.user` | `ctx.user?.id ?? null` | `authorId`, `submittedBy` |
|
|
131
|
+
| `ctx.staff` | `ctx.staff?.id ?? null` | `approvedBy`, `grantedBy` |
|
|
132
|
+
| `now` | write-time Unix epoch milliseconds | `createdAt`, `submittedAt` |
|
|
133
|
+
|
|
134
|
+
Stamping semantics: on create the computed value replaces whatever the caller sent. On update the existing stamp is preserved and only computed when the stored row lacks it, so a `now` bind does not become an updated-at field. A `null` stamp is dropped before JSON Schema validation, so `type: string` passes for anonymous callers. Bound properties are removed from Staff MCP `create_*` and `update_*` tool schemas. Stamped values appear in View output as ordinary fields.
|
|
135
|
+
|
|
136
|
+
### `x-mantle-ref`
|
|
137
|
+
|
|
138
|
+
An informational foreign-key marker on a string property that holds another collection's entry id (`x-mantle-ref: customers`). Nothing is enforced: no constraint, cascade or orphan check. Admin uses it for pickers and related rows; `x-mantle-ref: media_assets` marks a media asset id. Declare a single-field index on the ref field when reverse lookups must stay bounded. On a Procedure input property the marker exposes a row action; see [Procedure](./procedure.md#uischema).
|
|
139
|
+
|
|
140
|
+
### `x-mcp-hint`
|
|
141
|
+
|
|
142
|
+
A free-form string that tells agents and Admin widgets how to render or produce a value. The grammar accepts any string; these values are conventional.
|
|
143
|
+
|
|
144
|
+
| Value | Meaning |
|
|
145
|
+
|---|---|
|
|
146
|
+
| `markdown`, `richtext`, `code` | Text editor and authoring format. |
|
|
147
|
+
| `media`, `media-image`, `media-video`, `media-file` | Media-shaped URL or asset reference (`isMediaMcpHint`). |
|
|
148
|
+
| `money-minor` | Integer amount in minor currency units. |
|
|
149
|
+
| `timestamp-ms` | Unix epoch milliseconds. |
|
|
150
|
+
| `idempotency-key` | On a Procedure input: Admin generates and hides one UUID per form; other callers generate one and reuse it on retry. |
|
|
151
|
+
|
|
152
|
+
Do not use a hint for optimistic concurrency. The reserved Procedure input name `expectedVersion` is the OCC token (observed `entry.version` at read time). First-party Admin binds and hides it by that name. Schema `spec.schema.properties` must not declare it (`INVALID_MANIFEST_ENVELOPE`).
|
|
153
|
+
|
|
154
|
+
### Root `readOnly: true`
|
|
155
|
+
|
|
156
|
+
`schema.readOnly: true` at the root marks a Procedure-managed collection. Staff MCP emits no `create_*` or `update_*` tool for it, and Admin's generic create, update, status change and delete return `CONFLICT` with the message that the Schema is read-only on generic authoring surfaces. Declared Views and Procedures (builtin or `ref`) keep working.
|
|
157
|
+
|
|
158
|
+
## `uiSchema`
|
|
159
|
+
|
|
160
|
+
Closed Admin-only roots: `fields`, `list`, `nav`. Nested keys are closed too. Unknown roots or nested keys are `SCHEMA_UI_INVALID`.
|
|
161
|
+
|
|
162
|
+
| Key | Rule |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `fields.<field>.widget` | Only `textarea`. The field must be a top-level property with a string type (`string` or `[string, null]`). |
|
|
165
|
+
| `list.filterField` | Operational Schemas only. A declared property with a non-empty string `enum` that is the first field of some `indexes` or `uniqueIndexes` tuple. Admin renders the enum as sidebar links and list tabs. |
|
|
166
|
+
| `list.primaryField` | Operational Schemas only. A non-empty top-level scalar property; rendered as the linked leading column. |
|
|
167
|
+
| `list.columns` | Operational Schemas only. Top-level properties, no repeats and not repeating `primaryField`; structured values render as compact JSON. |
|
|
168
|
+
| `nav.standalone` | Boolean. `true` also emits a main Admin Nav list entry with a **parent autocomplete filter**. It does not unfold: required `x-mantle-ref` children still compose under the parent. Omit or `false` means fold-only (discover via the parent-entry workbench). Rejected on top-level Schemas, `translates` children, and Schemas with no eligible required-ref parent. |
|
|
169
|
+
| `nav.parentField` | Allowed only with `standalone: true`. Names a required `x-mantle-ref` field used as the parent filter. One eligible required ref is inferred; more than one requires an explicit `parentField`. Do not rely on property-order heuristics when multiple refs exist. |
|
|
170
|
+
|
|
171
|
+
```yaml
|
|
172
|
+
uiSchema:
|
|
173
|
+
list:
|
|
174
|
+
primaryField: name
|
|
175
|
+
columns: [status]
|
|
176
|
+
nav:
|
|
177
|
+
standalone: true
|
|
178
|
+
parentField: organizationId
|
|
179
|
+
```
|
|
180
|
+
|
|
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
|
+
|
|
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).
|
|
184
|
+
|
|
185
|
+
## Indexes
|
|
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.
|
|
188
|
+
|
|
189
|
+
| Rule | Diagnostic |
|
|
190
|
+
|---|---|
|
|
191
|
+
| When any index is declared, `metadata.name` matches `/^[A-Za-z][A-Za-z0-9_.-]*$/`. | `SCHEMA_INDEX_INVALID` at `/metadata/name` |
|
|
192
|
+
| Each tuple is a non-empty array of strings. | `INVALID_MANIFEST_ENVELOPE` (shape) or `SCHEMA_INDEX_INVALID` (empty) |
|
|
193
|
+
| No field repeats within a tuple; field names match the same safe pattern. | `SCHEMA_INDEX_INVALID` |
|
|
194
|
+
| Fields are not reserved entry columns. | `SCHEMA_INDEX_INVALID` |
|
|
195
|
+
| Fields are exact top-level keys of `properties`. | `UNIQUE_INDEX_FIELD_UNKNOWN` or `SCHEMA_INDEX_FIELD_UNKNOWN` |
|
|
196
|
+
| Fields are indexable scalars: exactly one non-null type, optionally nullable. `string` maps to TEXT, `integer` and `boolean` to INTEGER, `number` to REAL. | `SCHEMA_INDEX_INVALID` |
|
|
197
|
+
| No tuple repeats within a list; `indexes` does not repeat a `uniqueIndexes` tuple. | `SCHEMA_INDEX_INVALID` |
|
|
198
|
+
|
|
199
|
+
On SQLite storage each Schema is a native table and every tuple becomes an
|
|
200
|
+
index over its native field columns; queries benefit from a leftmost prefix.
|
|
201
|
+
Unique indexes are also checked before every write; a conflicting row is
|
|
202
|
+
`CONFLICT`.
|
|
203
|
+
|
|
204
|
+
## `searchableFields`
|
|
205
|
+
|
|
206
|
+
An array of unique top-level string properties (`string` or `[string, null]`). Non-array or non-string entries are `INVALID_MANIFEST_ENVELOPE`; duplicates and non-string properties are `SCHEMA_SEARCH_INVALID`; unknown fields are `SCHEMA_SEARCH_FIELD_UNKNOWN`. The entry `id` is always searched. Substring search does not use `indexes`.
|
|
207
|
+
|
|
208
|
+
## Lifecycle
|
|
209
|
+
|
|
210
|
+
`publishing` is the default state machine.
|
|
211
|
+
|
|
212
|
+
| From | To |
|
|
213
|
+
|---|---|
|
|
214
|
+
| `draft` | `published`, `archived` |
|
|
215
|
+
| `published` | `archived`, `draft` |
|
|
216
|
+
| `archived` | `draft` |
|
|
217
|
+
|
|
218
|
+
`operational` has no transitions. Entries are created with `status: published`, edited in place, and any publish, unpublish or archive request returns `CONFLICT`. Admin hides lifecycle controls; Staff MCP emits `create_record_<segment>` and `update_record_<segment>` instead of the draft tools. A builtin `archive` Procedure cannot target an operational Schema.
|
|
219
|
+
|
|
220
|
+
Deleting a `published` entry of a publishing Schema is `CONFLICT` (unpublish first). Operational entries can be deleted in any status.
|
|
221
|
+
|
|
222
|
+
### Write-time locale gate
|
|
223
|
+
|
|
224
|
+
Every authoring path (Admin, Staff MCP, builtin Procedures) runs the same guard after stamping, per request, reading the current site locales.
|
|
225
|
+
|
|
226
|
+
| Condition | Result |
|
|
227
|
+
|---|---|
|
|
228
|
+
| Non-localized Schema and `data.locale` is present. | `INPUT_VALIDATION_FAILED` |
|
|
229
|
+
| Localized Schema and `data.locale` missing or empty. | `INPUT_VALIDATION_FAILED` (skipped for partial draft saves; publish re-checks). |
|
|
230
|
+
| Localized Schema and `data.locale` not in the site locales. | `INPUT_VALIDATION_FAILED` with the enabled locales as candidates. |
|
|
231
|
+
| Site locales list is empty. | Locale membership is not checked. |
|
|
232
|
+
|
|
233
|
+
Site locales are configured in [Site config](./site-config.md).
|
|
234
|
+
|
|
235
|
+
## Source
|
|
236
|
+
|
|
237
|
+
- [`packages/mantle-spec/src/domain/model/ManifestGrammar.ts`](../../../packages/mantle-spec/src/domain/model/ManifestGrammar.ts)
|
|
238
|
+
- [`packages/mantle-spec/src/domain/service/ManifestParser.ts`](../../../packages/mantle-spec/src/domain/service/ManifestParser.ts)
|
|
239
|
+
- [`packages/mantle-spec/src/domain/service/ManifestGraphValidator.ts`](../../../packages/mantle-spec/src/domain/service/ManifestGraphValidator.ts)
|
|
240
|
+
- [`packages/mantle-spec/src/domain/service/CrossSchemaChecker.ts`](../../../packages/mantle-spec/src/domain/service/CrossSchemaChecker.ts)
|
|
241
|
+
- [`packages/mantle-spec/src/domain/service/SchemaIndexChecker.ts`](../../../packages/mantle-spec/src/domain/service/SchemaIndexChecker.ts)
|
|
242
|
+
- [`packages/mantle-spec/src/domain/service/SchemaSearchChecker.ts`](../../../packages/mantle-spec/src/domain/service/SchemaSearchChecker.ts)
|
|
243
|
+
- [`packages/mantle-spec/src/domain/service/SchemaAdminUiChecker.ts`](../../../packages/mantle-spec/src/domain/service/SchemaAdminUiChecker.ts)
|
|
244
|
+
- [`packages/mantle-spec/src/domain/service/LifecycleStateMachine.ts`](../../../packages/mantle-spec/src/domain/service/LifecycleStateMachine.ts)
|
|
245
|
+
- [`packages/mantle-spec/src/domain/service/JsonSchemaToZod.ts`](../../../packages/mantle-spec/src/domain/service/JsonSchemaToZod.ts)
|
|
246
|
+
- [`packages/mantle-runtime/src/domain/service/BuiltinProjector.ts`](../../../packages/mantle-runtime/src/domain/service/BuiltinProjector.ts)
|
|
247
|
+
- [`packages/mantle-runtime/src/domain/service/io/EntryWriteGuard.ts`](../../../packages/mantle-runtime/src/domain/service/io/EntryWriteGuard.ts)
|
|
248
|
+
- [`packages/mantle-runtime/src/domain/service/io/EntryDeleteGuard.ts`](../../../packages/mantle-runtime/src/domain/service/io/EntryDeleteGuard.ts)
|
|
249
|
+
- [`packages/mantle-runtime/src/infrastructure/mcp/McpToolCatalog.ts`](../../../packages/mantle-runtime/src/infrastructure/mcp/McpToolCatalog.ts)
|
|
250
|
+
- [`packages/mantle-admin/src/mountMantleAdmin.ts`](../../../packages/mantle-admin/src/mountMantleAdmin.ts)
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: siteDefaults reference — locales, brand, icons, media purposes, the runtime SiteConfig read shape, site_config row ownership and what boot validates.
|
|
3
|
+
---
|
|
4
|
+
# Site config
|
|
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.
|
|
7
|
+
|
|
8
|
+
## `siteDefaults`
|
|
9
|
+
|
|
10
|
+
| Key | Type | Required | Rules |
|
|
11
|
+
|---|---|---|---|
|
|
12
|
+
| `locales` | `string[]` | no | Ordered. `locales[0]` is the canonical locale. Empty or omitted turns the locale subsystem off site-wide. Every entry must canonicalize; see [Boot validation](#validated-at-boot). |
|
|
13
|
+
| `brand` | string | no | Operator-facing label for Admin chrome and MCP `serverInfo.title`. Distinct from `title`. |
|
|
14
|
+
| `title` | string | no | Site title — `<title>` suffix and `og:site_name`. |
|
|
15
|
+
| `description` | string | no | Default `<meta name="description">` and `og:description` for entries with none. |
|
|
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
|
+
| `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
|
+
| `media.purposes` | `MediaPurposePolicy[]` | no | The upload taxonomy. Omitting the key, or declaring an empty array, keeps first-party media uploads disabled. |
|
|
21
|
+
|
|
22
|
+
Nothing in this object is validated for length or content beyond the rules above: `brand`, `title`, `description` and `origin` are free strings.
|
|
23
|
+
|
|
24
|
+
## `SiteIcon`
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
interface SiteIcon {
|
|
28
|
+
readonly src: string;
|
|
29
|
+
readonly mimeType?: "image/png" | "image/jpeg" | "image/svg+xml" | "image/webp";
|
|
30
|
+
readonly sizes?: readonly string[];
|
|
31
|
+
readonly theme?: "light" | "dark";
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
| Field | Rules |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `src` | Either root-relative (starts with a single `/`, no whitespace, no backslash) or an absolute `https:` URL. Anything else is rejected. |
|
|
38
|
+
| `mimeType` | Optional. One of the four listed types. |
|
|
39
|
+
| `sizes` | Optional. When present, a non-empty array whose entries are `any` or `<width>x<height>` with non-zero decimal dimensions, for example `64x64`. |
|
|
40
|
+
| `theme` | Optional. `light` or `dark`. |
|
|
41
|
+
|
|
42
|
+
When the deployment declares no icons, the runtime stores `DEFAULT_SITE_ICONS`:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
const DEFAULT_SITE_ICONS = [{
|
|
46
|
+
src: "/_mantle/admin/favicon.svg",
|
|
47
|
+
mimeType: "image/svg+xml",
|
|
48
|
+
sizes: ["any"],
|
|
49
|
+
}];
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
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).
|
|
53
|
+
|
|
54
|
+
## `MediaPurposePolicy`
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
interface MediaPurposePolicy {
|
|
58
|
+
readonly name: string;
|
|
59
|
+
readonly required: readonly string[];
|
|
60
|
+
readonly maxBytes: Readonly<Record<string, number>>;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
| Field | Rules |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `name` | The slug callers pass as `purpose`. Must match `^[a-z0-9]+(-[a-z0-9]+)*$`: lowercase alphanumerics, dash-separated, no leading, trailing or repeated dashes. |
|
|
67
|
+
| `required` | Ordered list of acceptable mime **slots**, at least one. Each entry uses the HTML `<input accept>` grammar. |
|
|
68
|
+
| `maxBytes` | Per-mime byte cap keyed by fully expanded mime type. Must name every mime that appears in any slot after expansion, and every value must be a positive number. |
|
|
69
|
+
|
|
70
|
+
A slot entry is one of a full mime (`image/jpeg`), a comma-list of full mimes (`image/jpg,image/png`, meaning either is acceptable for that slot), or a shorthand subtype (`webp` expands to `image/webp`, `jpg` and `image/jpg` both expand to `image/jpeg`). Whitespace around commas is tolerated.
|
|
71
|
+
|
|
72
|
+
Slot position does not determine variant role. Per asset the uploading agent picks one mime per slot and independently declares exactly one supplied variant as `primary` — the format `<img>` falls back to — with the rest `alternate`, preferred through `<picture><source>`. Because a variant maps to a slot by its mime alone, mime sets across slots must not overlap; an overlap is rejected at boot rather than per upload.
|
|
73
|
+
|
|
74
|
+
## Runtime `SiteConfig`
|
|
75
|
+
|
|
76
|
+
`siteDefaults` is the author-time declaration. `SiteConfig` is the read shape that templates, the MCP catalog and Admin see after the seed has run and an operator has had a chance to edit.
|
|
77
|
+
|
|
78
|
+
| Field | Type | Value when the row is absent |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `title` | string | `"CMS"` |
|
|
81
|
+
| `description` | string | `""` |
|
|
82
|
+
| `origin` | string | `""` |
|
|
83
|
+
| `brand` | string | `"AotterMantle"` |
|
|
84
|
+
| `locales` | `readonly string[]` | `[]` |
|
|
85
|
+
| `canonicalLocale` | `string \| null` | `locales[0]` or `null` when the list is empty |
|
|
86
|
+
| `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
|
+
| `media.purposes` | `readonly MediaPurposePolicy[]` | `[]` |
|
|
90
|
+
|
|
91
|
+
`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.
|
|
92
|
+
|
|
93
|
+
## `site_config` rows
|
|
94
|
+
|
|
95
|
+
The table is a flat key/value store. Keys fall into two ownership classes, and the seed treats them differently.
|
|
96
|
+
|
|
97
|
+
| Key | Ownership | Written by seed | Source of truth |
|
|
98
|
+
|---|---|---|---|
|
|
99
|
+
| `brand` | UI-editable, seed-once | `INSERT … ON CONFLICT DO NOTHING` | The database, once the row exists |
|
|
100
|
+
| `title` | UI-editable, seed-once | `INSERT … ON CONFLICT DO NOTHING` | The database, once the row exists |
|
|
101
|
+
| `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
|
+
| `origin` | Code-canonical, boot-synced | Upsert when the stored value differs | The declaration |
|
|
105
|
+
| `faviconUrl` | Code-canonical, boot-synced | Upsert when the stored value differs; holds the `icons` array as JSON | The declaration |
|
|
106
|
+
| `locales` | Code-canonical, boot-synced | Upsert when the stored value differs; holds the declared list as a comma-separated string | The declaration |
|
|
107
|
+
| `mediaPurposes` | Code-canonical, boot-synced | Upsert when the stored value differs; holds the purposes array as JSON | The declaration |
|
|
108
|
+
|
|
109
|
+
Blank values are skipped in both classes: an absent, empty or empty-array field never writes and never clears an existing row, so a partial declaration cannot clobber stored values. The boot-synced keys are read-compared before writing, so an unchanged deployment issues no write.
|
|
110
|
+
|
|
111
|
+
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
|
+
|
|
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
|
+
## Validated at boot
|
|
117
|
+
|
|
118
|
+
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.
|
|
119
|
+
|
|
120
|
+
| Error | Thrown when |
|
|
121
|
+
|---|---|
|
|
122
|
+
| `InvalidSiteDefaultsError` | Any declared locale fails canonicalization. Carries `invalidLocales`; the message adds script-subtag guidance when one is present. |
|
|
123
|
+
| `InvalidSiteIconsError` | `icons` is declared as an empty array, or any icon fails the [`SiteIcon`](#siteicon) rules. Carries the offending icons. |
|
|
124
|
+
| `InvalidMediaPurposesError` | Any declared purpose fails a policy rule. Carries one issue per purpose. |
|
|
125
|
+
|
|
126
|
+
`InvalidMediaPurposesError` reports exactly one reason per purpose, checked in this order and stopping at the first hit:
|
|
127
|
+
|
|
128
|
+
| Reason | Meaning |
|
|
129
|
+
|---|---|
|
|
130
|
+
| `invalid-slug` | `name` does not match the slug pattern. |
|
|
131
|
+
| `empty-required` | `required` is an empty array. |
|
|
132
|
+
| `empty-required-slot` | A `required` entry parses to zero mimes. |
|
|
133
|
+
| `overlapping-slot-mimes` | Two slots accept the same mime, so a variant cannot be mapped to one slot. |
|
|
134
|
+
| `maxBytes-missing-mime` | `maxBytes` has no entry for a mime that some slot accepts. |
|
|
135
|
+
| `maxBytes-non-positive` | A `maxBytes` entry is not a positive number. |
|
|
136
|
+
|
|
137
|
+
Locale canonicalization accepts a 2- or 3-letter ISO 639 language plus an optional 2-letter ISO 3166 region, separated by `-` or `_`, or run together in the 2+2 form. Case is irrelevant: `zh-tw`, `ZH_TW` and `zhTW` all canonicalize to `zh-TW`. The canonicalized list is deduplicated, so `["zh-tw", "zh-TW"]` collapses to one entry. Script subtags are valid BCP 47 but deliberately unsupported in v0.1 — `zh-Hant`, `zh-Hans`, `sr-Latn` and `sr-Cyrl` are all rejected; use region tags such as `zh-TW` and `zh-CN`. Variants such as `de-1996` are likewise rejected. The `locales` row stores the declared list verbatim once it validates.
|
|
138
|
+
|
|
139
|
+
Deployment readiness runs alongside the seed and collects boot-phase diagnostics, throwing `BootValidationError` when any is an error.
|
|
140
|
+
|
|
141
|
+
| Check | Diagnostic |
|
|
142
|
+
|---|---|
|
|
143
|
+
| `checkSiteLocales`: the site locale list canonicalizes. | `INVALID_LOCALE` at `site_config/locales`, listing the invalid entries |
|
|
144
|
+
| `checkSiteLocales`: no Schema declares `localized: true` while the site has zero valid locales. | `SCHEMA_LOCALIZED_REQUIRES_SITE_LOCALES` |
|
|
145
|
+
| `translates` references resolve: the parent exists, is not itself localized, and declares the join field. | `TRANSLATES_PARENT_UNKNOWN`, `TRANSLATES_PARENT_IS_LOCALIZED`, `TRANSLATES_FIELD_NOT_IN_PARENT` |
|
|
146
|
+
| Every `handler.kind: ref` key is registered in the `handlers` map. The registered keys are attached as `candidates`. | `HANDLER_NOT_REGISTERED` |
|
|
147
|
+
| No HTTP Trigger path falls under an adapter-reserved prefix. | `TRIGGER_PATH_INVALID` |
|
|
148
|
+
| Every `sql` View's dialect is supported by the bound storage adapter. | `VIEW_DIALECT_UNSUPPORTED` |
|
|
149
|
+
|
|
150
|
+
## Checked per request
|
|
151
|
+
|
|
152
|
+
| Read | When |
|
|
153
|
+
|---|---|
|
|
154
|
+
| `siteConfig.load()` | Every public render, every `/llms.txt`, `.md` mirror and sitemap response, every MCP catalog build, the Admin site payload. Reads all rows and applies the fallbacks above. |
|
|
155
|
+
| `siteConfig.readLocales()` | Locale resolution on public routes and the write-time locale gate. Reads only the `locales` row; the value is memoized per repository instance once a prepared revision proves the code-owned locale policy is current. |
|
|
156
|
+
| `siteConfig.readMediaPurposes()` | Upload authorization. Always reads the canonical row, never a cached catalog snapshot. |
|
|
157
|
+
|
|
158
|
+
The `data.locale` write gate runs on every authoring path — Admin, Staff MCP and builtin Procedures — after stamping, against the locales read for that request. Its Schema-side rules are in [Schema](./schema.md#write-time-locale-gate).
|
|
159
|
+
|
|
160
|
+
| Condition | Result |
|
|
161
|
+
|---|---|
|
|
162
|
+
| Non-localized Schema and `data.locale` is present. | `INPUT_VALIDATION_FAILED` |
|
|
163
|
+
| Localized Schema and `data.locale` is missing or empty. | `INPUT_VALIDATION_FAILED`, skipped for partial draft saves; publish re-checks. |
|
|
164
|
+
| Localized Schema and `data.locale` is not in the site locales. | `INPUT_VALIDATION_FAILED` with the enabled locales as `candidates` |
|
|
165
|
+
| The site locale list is empty. | Membership is not checked. |
|
|
166
|
+
|
|
167
|
+
Media checks run per upload. `create_media_upload` and `commit_media_upload` are registered only when the runtime has a `mediaStorage` port bound **and** at least one purpose is declared; without both, the tools do not appear in `tools/list` at all.
|
|
168
|
+
|
|
169
|
+
| Condition | Diagnostic |
|
|
170
|
+
|---|---|
|
|
171
|
+
| `purpose` is not one of the declared slugs. The declared set is returned in `expected`. | `MEDIA_PURPOSE_REJECTED` |
|
|
172
|
+
| The variants manifest does not cover every required mime for the purpose. | `MEDIA_VARIANTS_INCOMPLETE` |
|
|
173
|
+
| A variant's declared `byteSize` exceeds its mime's `maxBytes`. | `MEDIA_VARIANT_SIZE_EXCEEDED` |
|
|
174
|
+
| A single upload exceeds its cap. | `MEDIA_SIZE_EXCEEDED` |
|
|
175
|
+
| A modern format is larger than its fallback, which means the uploader skipped optimization. | `MEDIA_VARIANTS_SUSPICIOUS_SIZE` |
|
|
176
|
+
|
|
177
|
+
The Cloudflare recipe for binding R2 is in [Media on R2](../cloudflare/media-r2.md).
|
|
178
|
+
|
|
179
|
+
## Example
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import { createMantleWorker } from "@aotter/mantle/cloudflare";
|
|
183
|
+
import { plan } from "../.mantle/generated/mantle.js";
|
|
184
|
+
import { handlers } from "./handlers.js";
|
|
185
|
+
|
|
186
|
+
export default createMantleWorker({
|
|
187
|
+
plan,
|
|
188
|
+
handlers,
|
|
189
|
+
siteDefaults: {
|
|
190
|
+
locales: ["en", "zh-TW"],
|
|
191
|
+
brand: "Northwind",
|
|
192
|
+
title: "Northwind Supply",
|
|
193
|
+
description: "Industrial parts, shipped the same day.",
|
|
194
|
+
origin: "https://northwind.example.com",
|
|
195
|
+
icons: [
|
|
196
|
+
{ src: "/site-icon.svg", mimeType: "image/svg+xml", sizes: ["any"] },
|
|
197
|
+
{ src: "/site-icon.png", mimeType: "image/png", sizes: ["64x64"] },
|
|
198
|
+
],
|
|
199
|
+
ga4MeasurementId: "G-XXXXXXXXXX",
|
|
200
|
+
media: {
|
|
201
|
+
purposes: [
|
|
202
|
+
{
|
|
203
|
+
name: "product-cover",
|
|
204
|
+
required: ["image/jpg,image/png", "webp", "avif"],
|
|
205
|
+
maxBytes: {
|
|
206
|
+
"image/jpeg": 5_000_000,
|
|
207
|
+
"image/png": 5_000_000,
|
|
208
|
+
"image/webp": 3_000_000,
|
|
209
|
+
"image/avif": 2_000_000,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`en` is canonical, so `/` redirects to `/en` and `zh-TW` is served at `/zh-tw`. Both icon files live in the project's `public/` directory. The one purpose declares three slots: the first accepts a JPEG or a PNG primary, the other two carry the modern alternates. See [Conventional Worker](../cloudflare/conventional-worker.md) for the surrounding bindings and [Public web](../cloudflare/public-web.md) for what the locale list turns on.
|
|
219
|
+
|
|
220
|
+
## Source
|
|
221
|
+
|
|
222
|
+
- [`packages/mantle-spec/src/domain/model/SiteConfig.ts`](../../../packages/mantle-spec/src/domain/model/SiteConfig.ts)
|
|
223
|
+
- [`packages/mantle-spec/src/domain/model/MediaMimeAccept.ts`](../../../packages/mantle-spec/src/domain/model/MediaMimeAccept.ts)
|
|
224
|
+
- [`packages/mantle-spec/src/domain/service/SiteDefaultsValidator.ts`](../../../packages/mantle-spec/src/domain/service/SiteDefaultsValidator.ts)
|
|
225
|
+
- [`packages/mantle-spec/src/domain/service/LocaleCanonicalizer.ts`](../../../packages/mantle-spec/src/domain/service/LocaleCanonicalizer.ts)
|
|
226
|
+
- [`packages/mantle-spec/src/domain/service/CrossSchemaChecker.ts`](../../../packages/mantle-spec/src/domain/service/CrossSchemaChecker.ts)
|
|
227
|
+
- [`packages/mantle-runtime/src/infrastructure/persistence/DatabaseSiteConfigRepository.ts`](../../../packages/mantle-runtime/src/infrastructure/persistence/DatabaseSiteConfigRepository.ts)
|
|
228
|
+
- [`packages/mantle-runtime/src/usecase/boot/ValidateBootUseCase.ts`](../../../packages/mantle-runtime/src/usecase/boot/ValidateBootUseCase.ts)
|
|
229
|
+
- [`packages/mantle-runtime/src/usecase/media/diagnostics.ts`](../../../packages/mantle-runtime/src/usecase/media/diagnostics.ts)
|
|
230
|
+
- [`packages/mantle-runtime/src/domain/service/io/EntryWriteGuard.ts`](../../../packages/mantle-runtime/src/domain/service/io/EntryWriteGuard.ts)
|
|
231
|
+
- [`packages/mantle-runtime/src/infrastructure/mcp/McpToolCatalog.ts`](../../../packages/mantle-runtime/src/infrastructure/mcp/McpToolCatalog.ts)
|
|
232
|
+
- [`packages/adapters/cloudflare/src/worker/createMantleWorker.ts`](../../../packages/adapters/cloudflare/src/worker/createMantleWorker.ts)
|
|
233
|
+
- [`docs/media-uploads.md`](../../../docs/media-uploads.md)
|