@aotter/mantle 0.1.2-alpha.6 → 0.1.2-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -6
- package/dist/cli/generate.d.ts +9 -0
- package/dist/cli/generate.d.ts.map +1 -1
- package/dist/cli/generate.js +40 -1
- package/dist/cli/generate.js.map +1 -1
- package/dist/cli/main.d.ts +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +37 -9
- package/dist/cli/main.js.map +1 -1
- package/docs/adapter-guide.md +1 -1
- package/docs/adr/adr-lite-845-frontend-client.md +38 -0
- package/docs/agent-prompts.md +92 -0
- package/docs/api-mcp-authorization.md +1 -1
- package/docs/auth-hosting-model.md +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 +39 -0
- package/docs/examples/host-chatgpt-sites/drizzle/0000_sites_users.sql +8 -0
- package/docs/examples/host-chatgpt-sites/drizzle/0001_mantle.sql +294 -0
- package/docs/examples/host-chatgpt-sites/drizzle/0002_article_cover.sql +5 -0
- package/docs/examples/host-chatgpt-sites/drizzle/meta/_journal.json +1 -0
- package/docs/examples/host-chatgpt-sites/manifests/site.yaml +27 -0
- package/docs/examples/host-chatgpt-sites/package-lock.json +7088 -0
- package/docs/examples/host-chatgpt-sites/package.json +1 -0
- package/docs/examples/host-chatgpt-sites/public/site.css +1 -0
- package/docs/examples/host-chatgpt-sites/scripts/build.mjs +12 -0
- package/docs/examples/host-chatgpt-sites/scripts/check.mjs +99 -0
- package/docs/examples/host-chatgpt-sites/scripts/migration.mjs +9 -0
- package/docs/examples/host-chatgpt-sites/src/chatgpt-auth.ts +63 -0
- package/docs/examples/host-chatgpt-sites/src/index.ts +46 -0
- package/docs/examples/host-chatgpt-sites/src/mcp.ts +49 -0
- package/docs/examples/host-chatgpt-sites/src/media.ts +109 -0
- package/docs/examples/host-chatgpt-sites/src/r2-lab.ts +38 -0
- package/docs/examples/host-chatgpt-sites/src/storage-fingerprint.json +1 -0
- package/docs/examples/host-chatgpt-sites/src/web.ts +35 -0
- package/docs/examples/host-chatgpt-sites/tsconfig.json +1 -0
- package/docs/examples/host-chatgpt-sites/wrangler.jsonc +10 -0
- package/docs/examples/host-local-admin-otp/.dev.vars.example +3 -0
- package/docs/examples/host-local-admin-otp/README.md +71 -0
- package/docs/examples/host-local-admin-otp/ensure-dev-vars.mjs +5 -0
- package/docs/examples/host-local-admin-otp/package.json +29 -0
- package/docs/examples/host-local-admin-otp/public/.gitkeep +1 -0
- package/docs/examples/host-local-admin-otp/smoke.mjs +141 -0
- package/docs/examples/host-local-admin-otp/src/index.ts +54 -0
- package/docs/examples/host-local-admin-otp/wrangler.jsonc +23 -0
- package/docs/examples/{minimal-worker → host-minimal-worker}/README.md +8 -5
- package/docs/examples/host-minimal-worker/manifests/site.yaml +25 -0
- package/docs/examples/{minimal-worker → host-minimal-worker}/package.json +3 -3
- package/docs/examples/host-minimal-worker/tsconfig.json +17 -0
- package/docs/handbook/cloudflare/authentication.md +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/views.md +2 -2
- package/docs/handbook/examples/commerce-transaction.md +4 -806
- package/docs/handbook/examples/commerce.md +11 -0
- package/docs/handbook/examples/guarded-api.md +3 -420
- package/docs/handbook/examples/hub.md +10 -0
- package/docs/handbook/examples/intake-form.md +6 -313
- package/docs/handbook/examples/intake-hooks.md +11 -0
- package/docs/handbook/examples/legal-documents.md +3 -211
- package/docs/handbook/examples/procurement-approvals.md +3 -233
- package/docs/handbook/examples/publication.md +3 -233
- package/docs/handbook/examples/reservation.md +3 -213
- package/docs/handbook/navigation.json +17 -2
- package/docs/handbook/reference/authorization.md +1 -1
- package/docs/handbook/reference/procedure.md +2 -2
- package/docs/handbook/reference/schema.md +3 -3
- package/docs/handbook/reference/site-config.md +5 -16
- package/docs/handbook/reference/surface.md +3 -7
- package/docs/handbook/sites/equipment-checkout.md +231 -0
- package/docs/handbook/sites/host-reference.md +116 -0
- package/docs/handbook/sites/index.md +117 -0
- package/docs/handbook/start/project-and-cli.md +22 -14
- package/docs/handbook/start/quickstart-admin.md +239 -0
- package/docs/handbook/start/quickstart-worker.md +21 -22
- package/docs/migration-0.1.2.md +26 -0
- package/docs/release-process.md +90 -6
- package/docs/sealed-pipeline-ownership.md +1 -1
- package/docs/transaction-patterns.md +2 -2
- package/package.json +15 -15
- package/skills/develop/SKILL.md +32 -23
- package/skills/install/SKILL.md +34 -11
- package/skills/provision/SKILL.md +19 -5
- /package/docs/examples/{minimal-worker → host-local-admin-otp}/manifests/site.yaml +0 -0
- /package/docs/examples/{minimal-worker → host-local-admin-otp}/tsconfig.json +0 -0
- /package/docs/examples/{minimal-worker → host-minimal-worker}/smoke.mjs +0 -0
- /package/docs/examples/{minimal-worker → host-minimal-worker}/src/index.ts +0 -0
- /package/docs/examples/{minimal-worker → host-minimal-worker}/wrangler.jsonc +0 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Host-owned site chrome — Cloudflare-first analytics, verification, robots, and what still belongs in mantle-web.
|
|
3
|
+
---
|
|
4
|
+
# Site chrome
|
|
5
|
+
|
|
6
|
+
Core `siteConfig` is deployment identity: locales, brand, title, description, origin, icons and media. Visitor analytics, pixels, search-engine verification tokens, `ads.txt` and similar chrome are **host responsibility**. Admin Settings does not accept tracking IDs, and `@aotter/mantle-web` does not inject vendor snippets from `siteConfig`.
|
|
7
|
+
|
|
8
|
+
Cloudflare-native platform capabilities are the first-class place for chrome that the zone already covers. Do not invent a parallel “CDN settings center” inside Mantle.
|
|
9
|
+
|
|
10
|
+
## Why Core does not inject
|
|
11
|
+
|
|
12
|
+
A headless runtime that rewrites the first `</head>` (or prepends to `<body>`) on a composed HTML string assumes one frontend shape: a complete SSR document that Core is allowed to mutate after the fact. Hosts are not one shape.
|
|
13
|
+
|
|
14
|
+
Frontends are plural. A Mantle consumer may serve SSR full HTML documents, a Vite/React (or other) SPA, a Worker plus static assets, Cloudflare Pages, or a custom wrapper that already owns `<html>`. Only the first of those even has a complete document string a runtime could splice into — and even then the attach point is the host template, not Core.
|
|
15
|
+
|
|
16
|
+
SPA, client-routed and hydration hosts attach analytics in **their own entry**: GTM or Zaraz, the app bootstrap, or Cloudflare Web Analytics. They do not expect Runtime or `mantle-web` to parse an HTML string and splice tags into it.
|
|
17
|
+
|
|
18
|
+
Silent injection from `siteConfig` would be a surprising side effect for any host that did not opt into “I emit a full document and you may rewrite it.” Core must not pretend to know every frontend’s attach point.
|
|
19
|
+
|
|
20
|
+
Therefore tracking and verification stay **host chrome**. Core `siteConfig` is not a tag-manager. The tables below say where to put chrome.
|
|
21
|
+
|
|
22
|
+
## Two layers
|
|
23
|
+
|
|
24
|
+
| Concern | Prefer | Usually still app / mantle-web |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| `robots.txt`, simple redirects, some `/.well-known` files, Cloudflare Web Analytics | **Cloudflare-native** | — |
|
|
27
|
+
| Dynamic sitemap (grows with content), `llms.txt`, SEO `.md` mirrors | — | Application mount or `@aotter/mantle-web` composition, then serve publicly |
|
|
28
|
+
|
|
29
|
+
Turning on Cloudflare Web Analytics is **not** the same as having an indexable sitemap or `llms.txt`. Analytics answers “who visited”; discovery files answer “what can a crawler or agent list”. Keep those jobs on the layer that owns the data.
|
|
30
|
+
|
|
31
|
+
Cloud tenant UX for ads.txt / robots / sitemap tools lives on the host product. Core stays injection-free.
|
|
32
|
+
|
|
33
|
+
## Cloudflare-first install
|
|
34
|
+
|
|
35
|
+
Use the dashboard, Wrangler, or an agent that can edit Worker / Pages config. None of these paths write Core Admin fields.
|
|
36
|
+
|
|
37
|
+
### Analytics
|
|
38
|
+
|
|
39
|
+
Prefer **Cloudflare Web Analytics** (and Zaraz when you need a tag manager) for first-party traffic measurement. Enable it on the zone; no Core `siteConfig` key is required.
|
|
40
|
+
|
|
41
|
+
To add GA4 or Meta Pixel:
|
|
42
|
+
|
|
43
|
+
1. Add the provider through **Cloudflare Zaraz** (recommended on a CF zone), or
|
|
44
|
+
2. Emit the vendor snippet from **your** entry/list templates in `@aotter/mantle-web`, or from a host HTML wrapper.
|
|
45
|
+
|
|
46
|
+
Do not put Measurement IDs or Pixel IDs in `siteDefaults`. Core does not persist or inject them.
|
|
47
|
+
|
|
48
|
+
### Search Console and Bing verification
|
|
49
|
+
|
|
50
|
+
Verify ownership with a host-owned method. On Cloudflare, prefer in this order:
|
|
51
|
+
|
|
52
|
+
1. **DNS TXT** at the zone apex (Search Console and Bing Webmaster both accept this).
|
|
53
|
+
2. **Cloudflare-managed HTML** via Zaraz or a small Worker / Pages header rewrite that adds `<meta name="google-site-verification">` / Bing equivalent.
|
|
54
|
+
3. A **static verification file** at `/` or `/.well-known/` served from `public/` / the `ASSETS` binding.
|
|
55
|
+
|
|
56
|
+
HTML-tag verification belongs in the host template `<head>`, not in Core Settings.
|
|
57
|
+
|
|
58
|
+
### robots.txt, redirects, well-known
|
|
59
|
+
|
|
60
|
+
Serve a static `robots.txt` from assets, or use Cloudflare Redirect Rules / Transform Rules / a tiny Worker route. `mountPublicRoutes` also emits `GET /robots.txt` (`Allow: /` plus the sitemap URL) when you opt into public pages — that is an application mount, not a Core Admin setting. See [Public web](./public-web.md).
|
|
61
|
+
|
|
62
|
+
Simple path redirects and most `/.well-known` files (including verification and `security.txt`) belong on the same Cloudflare-native layer.
|
|
63
|
+
|
|
64
|
+
## What still belongs in the app
|
|
65
|
+
|
|
66
|
+
Dynamic **sitemap** and **`llms.txt`** grow with published content. Compose them with `@aotter/mantle-web` (`composeSitemap`, `composeLlmsTxt`) and mount the public URLs, or implement the same contract in the application. Cloudflare Web Analytics does not generate those files.
|
|
67
|
+
|
|
68
|
+
SEO `.md` mirrors are likewise application / `mantle-web` composition. Details: [Public web](./public-web.md) and [Site config](../reference/site-config.md).
|
|
69
|
+
|
|
70
|
+
## Source
|
|
71
|
+
|
|
72
|
+
- [`packages/mantle-spec/src/domain/model/SiteConfig.ts`](../../../packages/mantle-spec/src/domain/model/SiteConfig.ts)
|
|
73
|
+
- [`packages/mantle-web/src/service/HtmlRenderer.ts`](../../../packages/mantle-web/src/service/HtmlRenderer.ts)
|
|
74
|
+
- [`docs/handbook/reference/site-config.md`](../reference/site-config.md)
|
|
75
|
+
- [`docs/handbook/cloudflare/public-web.md`](./public-web.md)
|
|
@@ -3,7 +3,7 @@ description: One authorization pipeline for REST, MCP and Admin — identity kin
|
|
|
3
3
|
---
|
|
4
4
|
# Authorization
|
|
5
5
|
|
|
6
|
-
Mantle has one authorization pipeline. A manifest HTTP Trigger, a View over REST, an MCP `tools/call` and an Admin operation all reach the same evaluator, so a rule written once holds on every surface. This page explains the model; the exact `requires` grammar is in the [authorization reference](../reference/authorization.md), and a worked ladder of four access levels is in [Guarded API access](
|
|
6
|
+
Mantle has one authorization pipeline. A manifest HTTP Trigger, a View over REST, an MCP `tools/call` and an Admin operation all reach the same evaluator, so a rule written once holds on every surface. This page explains the model; the exact `requires` grammar is in the [authorization reference](../reference/authorization.md), and a worked ladder of four access levels is in [Guarded API access](../../examples/cf-primitives-guarded-api.md).
|
|
7
7
|
|
|
8
8
|
## The fixed order
|
|
9
9
|
|
|
@@ -104,7 +104,7 @@ An MCP `tools/call` runs the identical evaluator, in the identical order, with t
|
|
|
104
104
|
## Related
|
|
105
105
|
|
|
106
106
|
- [Authorization requirements](../reference/authorization.md) — the `requires` shape, predicate forms, guard rules, OpenAPI projection.
|
|
107
|
-
- [Guarded API access](
|
|
107
|
+
- [Guarded API access](../../examples/cf-primitives-guarded-api.md) — a full resolver, guards and the REST/MCP outcome tables.
|
|
108
108
|
- [Reads: Views, REST and MCP](./views.md) — the `$ctx.user` identity-View sentinel.
|
|
109
109
|
- [Authentication](../cloudflare/authentication.md) — sessions, first owner, role management routes.
|
|
110
110
|
|
|
@@ -26,9 +26,9 @@ The consequence for authors: a Procedure that has no Trigger is unreachable from
|
|
|
26
26
|
|
|
27
27
|
## The composition rule
|
|
28
28
|
|
|
29
|
-
Anything more domain-shaped than these four is not an atom. A Form, Membership, Email, Webhook, Workflow or ScheduledJob is a composition of Schemas, Views, Procedures and Triggers plus your own TypeScript.
|
|
29
|
+
Anything more domain-shaped than these four is not an atom. A Form, Membership, Email, Webhook, Workflow or ScheduledJob is a composition of Schemas, Views, Procedures and Triggers plus your own TypeScript. A fifth kind would recreate the same overlap: authors would have to decide whether a contact form was a Form, a Workflow or an Email.
|
|
30
30
|
|
|
31
|
-
If you find yourself wanting
|
|
31
|
+
If you find yourself wanting another kind, sketch the same thing as a composition of the four first. Almost always it works.
|
|
32
32
|
|
|
33
33
|
## The Manifest envelope
|
|
34
34
|
|
|
@@ -129,7 +129,7 @@ The parent holds what every language shares and stays non-localized. The child s
|
|
|
129
129
|
|
|
130
130
|
When you keep parallel locale rows in step, translate display strings only. Field names, option values, step identifiers and result keys must stay identical across locales, or the same View and the same MCP tool stop describing the same thing.
|
|
131
131
|
|
|
132
|
-
The full worked version of this pattern, with a locale-parameterized public View and its REST response, is [Publication](
|
|
132
|
+
The full worked version of this pattern, with a locale-parameterized public View and its REST response, is [Publication](../../examples/builtin-publication.md).
|
|
133
133
|
|
|
134
134
|
## Source
|
|
135
135
|
|
|
@@ -33,7 +33,7 @@ Procedures are never exposed on their own. A Procedure becomes a tool only throu
|
|
|
33
33
|
|
|
34
34
|
## The OAuth model
|
|
35
35
|
|
|
36
|
-
The Cloudflare adapter runs one Better Auth 1.7 instance for staff identity, authorization, consent, client registration and MCP resource verification. Client identity is CIMD-first — the MCP 2026-07-28 Client ID Metadata Document profile, which is why the Worker needs the `global_fetch_strictly_public` flag to fetch client metadata across the public Internet boundary
|
|
36
|
+
The Cloudflare adapter runs one Better Auth 1.7 instance for staff identity, authorization, consent, client registration and MCP resource verification. Client identity is CIMD-first — the MCP 2026-07-28 Client ID Metadata Document profile, which is why the Worker needs the `global_fetch_strictly_public` flag to fetch client metadata across the public Internet boundary. Unauthenticated Dynamic Client Registration remains available as a bounded path with a 90-day default lifetime for clients that do not present CIMD. One non-colon scope, `mcp`, is advertised in `scopes_supported`, because clients such as claude.ai reject colon-shaped scopes; per-surface enforcement then happens server-side, not through scope strings. Authorization is session-bound: the JWT's originating Better Auth session must still exist and be unexpired, so signing out of Admin also ends that session's MCP access, and a refresh token is not an independent authorization. Unauthenticated requests to either mount answer `401` with a `WWW-Authenticate` challenge pointing at the RFC 9728 protected-resource metadata document served under the auth mount. Authorization endpoints live under `/api/auth/oauth2/*` and are discovered from the advertised metadata, never hard-coded.
|
|
37
37
|
|
|
38
38
|
## Connecting a local client
|
|
39
39
|
|
|
@@ -209,7 +209,7 @@ What each piece is doing:
|
|
|
209
209
|
- `submittedAt` is stamped by the runtime, so it is absent from the Procedure input and from Staff MCP authoring tools.
|
|
210
210
|
- Adding `{ kind: mcp, surface: public }` as a fourth Trigger would publish the same Procedure as an agent tool without touching a handler.
|
|
211
211
|
|
|
212
|
-
Register the two `ref` keys in the project's handlers map, then run the check loop from [Project layout and the CLI loop](../start/project-and-cli.md). The complete version with real Turnstile and email handlers is [Intake
|
|
212
|
+
Register the two `ref` keys in the project's handlers map, then run the check loop from [Project layout and the CLI loop](../start/project-and-cli.md). The complete version with real Turnstile and email handlers is [Intake Turnstile and email hooks](../../examples/cf-primitives-intake-hooks.md). The Builder-safe Manifest without those hooks is [Intake form](../../examples/builtin-intake.md).
|
|
213
213
|
|
|
214
214
|
## Source
|
|
215
215
|
|
|
@@ -47,7 +47,7 @@ A View can filter to the calling user's own rows with the closed sentinel `{ "$c
|
|
|
47
47
|
2. The View must require a signed-in user: `ctx.user` in `requires.auth.all` — otherwise `VIEW_FILTER_CTX_USER_REF_REQUIRES_AUTH`.
|
|
48
48
|
3. The compared field must be the leftmost field of a declared index on the source Schema — otherwise `VIEW_FILTER_CTX_USER_REF_REQUIRES_INDEX`.
|
|
49
49
|
|
|
50
|
-
This is a filter, not a row-level policy engine. `requires` authorizes the whole query; it does not inject per-row visibility predicates. Membership, payment and entitlement checks belong in a guard Procedure. See [Procurement approvals](
|
|
50
|
+
This is a filter, not a row-level policy engine. `requires` authorizes the whole query; it does not inject per-row visibility predicates. Membership, payment and entitlement checks belong in a guard Procedure. See [Procurement approvals](../../examples/builtin-procurement.md).
|
|
51
51
|
|
|
52
52
|
## Shared response cache
|
|
53
53
|
|
|
@@ -176,7 +176,7 @@ spec:
|
|
|
176
176
|
limit: 200
|
|
177
177
|
```
|
|
178
178
|
|
|
179
|
-
`tags` is an array property, so `json_each` unnests it and one request appears once per tag. The runtime wraps the whole statement as a subquery before applying pagination, which is why Admin's search and filters attach to the SQL output aliases — `tag` is a real filterable column even though no Schema property is named `tag`. See [Commerce
|
|
179
|
+
`tags` is an array property, so `json_each` unnests it and one request appears once per tag. The runtime wraps the whole statement as a subquery before applying pagination, which is why Admin's search and filters attach to the SQL output aliases — `tag` is a real filterable column even though no Schema property is named `tag`. See [Commerce inventory](../../examples/cf-primitives-commerce-inventory.md) for the same technique over order lines.
|
|
180
180
|
|
|
181
181
|
## Source
|
|
182
182
|
|