@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
@@ -0,0 +1,70 @@
1
+ # Local Admin OTP
2
+
3
+ This is the opt-in Dev UI path: a directly authored Cloudflare application
4
+ that opens Mantle Admin locally with email OTP. Admin is not required to
5
+ ship a complete service. It is not a Starter or CLI generator. Business
6
+ Manifests live in the [Examples hub](../README.md). The notes
7
+ Schema is example business data; `mantle generate` never invents it.
8
+
9
+ Spec + adapter without Admin:
10
+ [`docs/examples/host-minimal-worker`](../host-minimal-worker/README.md).
11
+
12
+ This reference depends on every `@aotter/mantle*` package via the `latest`
13
+ dist-tag, so a fresh install always resolves the current stable release.
14
+ Core's test runner substitutes its exact candidate in a disposable copy.
15
+
16
+ ## One-shot
17
+
18
+ Outside the SDK workspace, with Node 22+ and pnpm 9+:
19
+
20
+ ```sh
21
+ pnpm install && pnpm generate && pnpm dev
22
+ ```
23
+
24
+ `predev` copies `.dev.vars.example` to `.dev.vars` when that file is
25
+ missing. `pnpm dev` binds `127.0.0.1:8787` so wrangler's Ready-on origin
26
+ matches `PUBLIC_ORIGIN` (`http://127.0.0.1:8787`). Open that Ready-on
27
+ URL. Browsing `http://localhost:8787` still serves HTML, but the OTP
28
+ Origin header mismatches `PUBLIC_ORIGIN` and Better Auth returns
29
+ `INVALID_ORIGIN`. Then:
30
+
31
+ 1. Open `http://127.0.0.1:8787/admin/sign-in`.
32
+ 2. Sign in with the bootstrap owner email from `.dev.vars` (`owner@example.com`
33
+ in the example).
34
+ 3. Read the one-time code from the wrangler log line
35
+ `[ConsoleEmailSender] auth.email-otp.sign-in → …`.
36
+ 4. Land in the Admin / Dev UI as `owner`.
37
+
38
+ `mantle generate` syncs the prebuilt Admin SPA from `@aotter/mantle-admin-ui`
39
+ into `public/_mantle/admin/`. There is no Vite or frontend build step unless
40
+ you are developing `admin-ui` itself.
41
+
42
+ ## Hard requirements for Admin
43
+
44
+ Admin is not a Worker route that paints itself. These must all be present:
45
+
46
+ | Piece | Why |
47
+ |---|---|
48
+ | `@aotter/mantle-admin` and `@aotter/mantle-admin-ui` | Admin API plus the prebuilt SPA |
49
+ | `assets.directory = ./public` and `binding = ASSETS` | Serves `/_mantle/admin/assets/*` |
50
+ | `createAuth` `email-otp` + `ConsoleEmailSender` | Local human sign-in; code is in wrangler logs |
51
+ | `bootstrapOwner.match: email` | First matching sign-in becomes `owner` |
52
+ | `PUBLIC_ORIGIN` and `BETTER_AUTH_SECRET` | Auth construction; copied from `.dev.vars.example` |
53
+
54
+ If `/admin` returns HTML `200` while `/_mantle/admin/assets/*` is `404`, the
55
+ SPA white-screens. That is a missing or misconfigured `ASSETS` binding, not a
56
+ failed frontend build. Do not put `/_mantle` in `run_worker_first`; asset
57
+ paths must fall through to Static Assets.
58
+
59
+ `ConsoleEmailSender` is the local path only. Do not wire it in production.
60
+
61
+ ## What this project does not do
62
+
63
+ `/` is `404`: no visitor frontend is installed. Public GET
64
+ `/api/views/published-notes` still works against fresh local D1. Conventional
65
+ GitHub Auth (`MANTLE_AUTH_MODE`) is not used; the Worker replaces construction
66
+ with `createAuth` while Core still owns `/admin` and `/api/auth/*`.
67
+
68
+ Commit the resolved lockfile in a real application and use frozen installs
69
+ afterwards. Configure a real D1 `database_id`, a production email sender, and
70
+ secrets before any remote deploy. Never commit `.dev.vars`.
@@ -0,0 +1,5 @@
1
+ import { copyFileSync, existsSync } from "node:fs";
2
+
3
+ if (!existsSync(".dev.vars")) {
4
+ copyFileSync(".dev.vars.example", ".dev.vars");
5
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "mantle-local-admin-otp",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "generate": "mantle generate",
7
+ "validate": "mantle validate",
8
+ "typecheck": "tsc --noEmit",
9
+ "predev": "node ensure-dev-vars.mjs",
10
+ "dev": "wrangler dev --local --ip 127.0.0.1 --port 8787",
11
+ "check": "node ensure-dev-vars.mjs && mantle generate && mantle generate --check && mantle validate && mantle skills && mantle skills --check && tsc --noEmit && node smoke.mjs"
12
+ },
13
+ "dependencies": {
14
+ "@aotter/mantle": "latest",
15
+ "@aotter/mantle-admin": "latest",
16
+ "@aotter/mantle-admin-ui": "latest",
17
+ "@aotter/mantle-cloudflare": "latest",
18
+ "better-auth": "1.7.2",
19
+ "hono": "^4.13.3",
20
+ "zod": "^4.5.4",
21
+ "aws4fetch": "^1.0.20"
22
+ },
23
+ "devDependencies": {
24
+ "@cloudflare/workers-types": "^5.20260907.1",
25
+ "typescript": "^6.0.3",
26
+ "wrangler": "^4.125.0"
27
+ },
28
+ "packageManager": "pnpm@9.15.0"
29
+ }
@@ -0,0 +1 @@
1
+ # Static Assets root. `mantle generate` writes public/_mantle/admin/ here.
@@ -0,0 +1,141 @@
1
+ import assert from "node:assert/strict";
2
+ import { spawn } from "node:child_process";
3
+ import { readFileSync, writeFileSync } from "node:fs";
4
+
5
+ await import("./ensure-dev-vars.mjs");
6
+
7
+ const ownerEmail = "owner@example.com";
8
+ const port = 18787;
9
+ const origin = `http://127.0.0.1:${port}`;
10
+ writeFileSync(".dev.vars", [
11
+ `PUBLIC_ORIGIN=${origin}`,
12
+ "BETTER_AUTH_SECRET=local-admin-otp-dev-secret-do-not-use-in-prod",
13
+ `ADMIN_EMAIL=${ownerEmail}`,
14
+ "",
15
+ ].join("\n"));
16
+ const logs = [];
17
+
18
+ const wrangler = spawn("pnpm", [
19
+ "exec",
20
+ "wrangler",
21
+ "dev",
22
+ "--local",
23
+ "--ip",
24
+ "127.0.0.1",
25
+ "--port",
26
+ String(port),
27
+ "--inspector-port",
28
+ "0",
29
+ "--persist-to",
30
+ ".wrangler/smoke",
31
+ ], {
32
+ env: { ...process.env, WRANGLER_SEND_METRICS: "false", CI: "1" },
33
+ stdio: ["ignore", "pipe", "pipe"],
34
+ });
35
+
36
+ const onLog = (chunk) => {
37
+ logs.push(String(chunk));
38
+ };
39
+ wrangler.stdout.on("data", onLog);
40
+ wrangler.stderr.on("data", onLog);
41
+
42
+ try {
43
+ await waitForReady(origin);
44
+ const signIn = await fetch(`${origin}/admin/sign-in`);
45
+ assert.equal(signIn.status, 200, `/admin/sign-in ${signIn.status}`);
46
+ const html = await signIn.text();
47
+ assert.match(html, /<!doctype html|<html/i);
48
+ const assets = [...html.matchAll(/(?:href|src)="(\/_mantle\/admin\/[^"]+)"/g)]
49
+ .map((match) => match[1])
50
+ .filter((path, index, all) => all.indexOf(path) === index);
51
+ assert.ok(assets.length > 0, "Admin HTML must reference /_mantle/admin assets");
52
+ for (const path of assets) {
53
+ const asset = await fetch(`${origin}${path}`);
54
+ assert.equal(asset.status, 200, `${path} ${asset.status}`);
55
+ }
56
+
57
+ const methods = await fetch(`${origin}/api/auth/methods`);
58
+ assert.equal(methods.status, 200);
59
+ assert.deepEqual(await methods.json(), { methods: [{ kind: "email-otp" }] });
60
+
61
+ const sent = await fetch(`${origin}/api/auth/email-otp/send-verification-otp`, {
62
+ method: "POST",
63
+ headers: { origin, "content-type": "application/json" },
64
+ body: JSON.stringify({ email: ownerEmail, type: "sign-in" }),
65
+ });
66
+ assert.equal(sent.status, 200, `send OTP ${sent.status} ${await sent.text()}`);
67
+ const otp = await waitForOtp();
68
+ const signedIn = await fetch(`${origin}/api/auth/sign-in/email-otp`, {
69
+ method: "POST",
70
+ headers: { origin, "content-type": "application/json" },
71
+ body: JSON.stringify({ email: ownerEmail, otp }),
72
+ });
73
+ assert.equal(signedIn.status, 200, `sign-in ${signedIn.status} ${await signedIn.text()}`);
74
+ const cookie = cookieHeader(signedIn);
75
+ assert.ok(cookie, "sign-in must set a session cookie");
76
+
77
+ const shell = await fetch(`${origin}/admin`, { headers: { cookie } });
78
+ assert.equal(shell.status, 200, `/admin ${shell.status}`);
79
+ const settings = await fetch(`${origin}/admin/api/site-settings`, { headers: { cookie } });
80
+ assert.equal(settings.status, 200, `/admin/api/site-settings ${settings.status}`);
81
+ const pkg = JSON.parse(readFileSync("node_modules/@aotter/mantle/package.json", "utf8"));
82
+ console.log(`Mantle ${pkg.version}: /admin assets 200, OTP ${otp} in wrangler logs, owner Admin shell 200`);
83
+ } finally {
84
+ wrangler.kill("SIGTERM");
85
+ await waitForExit(wrangler);
86
+ }
87
+
88
+ function cookieHeader(response) {
89
+ const cookies = typeof response.headers.getSetCookie === "function"
90
+ ? response.headers.getSetCookie()
91
+ : [response.headers.get("set-cookie")].filter(Boolean);
92
+ return cookies.map((value) => value.split(";", 1)[0]).join("; ");
93
+ }
94
+
95
+ async function waitForReady(url) {
96
+ const deadline = Date.now() + 60_000;
97
+ while (Date.now() < deadline) {
98
+ if (wrangler.exitCode !== null) {
99
+ throw new Error(`wrangler exited ${wrangler.exitCode}\n${logs.join("")}`);
100
+ }
101
+ try {
102
+ const response = await fetch(`${url}/admin/sign-in`);
103
+ if (response.status === 200) return;
104
+ } catch {
105
+ // Worker still booting.
106
+ }
107
+ await delay(250);
108
+ }
109
+ throw new Error(`wrangler did not become ready\n${logs.join("")}`);
110
+ }
111
+
112
+ async function waitForOtp() {
113
+ const deadline = Date.now() + 15_000;
114
+ while (Date.now() < deadline) {
115
+ const match = logs.join("").match(/Your Mantle sign-in code: (\d{6})/);
116
+ if (match) return match[1];
117
+ await delay(100);
118
+ }
119
+ throw new Error(`OTP log line missing\n${logs.join("")}`);
120
+ }
121
+
122
+ function waitForExit(child) {
123
+ return new Promise((resolve) => {
124
+ if (child.exitCode !== null) {
125
+ resolve();
126
+ return;
127
+ }
128
+ const timer = setTimeout(() => {
129
+ child.kill("SIGKILL");
130
+ resolve();
131
+ }, 5_000);
132
+ child.once("exit", () => {
133
+ clearTimeout(timer);
134
+ resolve();
135
+ });
136
+ });
137
+ }
138
+
139
+ function delay(ms) {
140
+ return new Promise((resolve) => setTimeout(resolve, ms));
141
+ }
@@ -0,0 +1,54 @@
1
+ import {
2
+ ConsoleEmailSender,
3
+ createAuth,
4
+ createMantleWorker,
5
+ type MantleCloudflareEnv,
6
+ } from "@aotter/mantle/cloudflare";
7
+ import { plan } from "../.mantle/generated/mantle.js";
8
+
9
+ export interface Env extends MantleCloudflareEnv {
10
+ readonly ASSETS: Fetcher;
11
+ readonly BETTER_AUTH_SECRET: string;
12
+ readonly ADMIN_EMAIL: string;
13
+ }
14
+
15
+ const sender = new ConsoleEmailSender();
16
+
17
+ export default createMantleWorker<Env>({
18
+ plan,
19
+ cacheScope: "local-admin-otp",
20
+ siteDefaults: (env) => ({
21
+ brand: "Local Admin",
22
+ title: "Local Admin",
23
+ origin: originOf(env),
24
+ }),
25
+ auth: (env) => {
26
+ const origin = originOf(env);
27
+ const secret = required(env.BETTER_AUTH_SECRET, "BETTER_AUTH_SECRET");
28
+ const ownerEmail = required(env.ADMIN_EMAIL, "ADMIN_EMAIL");
29
+ return createAuth({
30
+ database: env.DB,
31
+ baseURL: origin,
32
+ secret,
33
+ methods: [{ kind: "email-otp", sender }],
34
+ bootstrapOwner: { match: "email", value: ownerEmail },
35
+ oauthProvider: {
36
+ loginPage: "/admin/sign-in",
37
+ consentPage: "/oauth/consent",
38
+ scopes: ["mcp"],
39
+ mcpResource: `${origin}/mcp`,
40
+ },
41
+ });
42
+ },
43
+ });
44
+
45
+ function originOf(env: Env): string {
46
+ return env.PUBLIC_ORIGIN?.replace(/\/+$/, "") ?? "http://127.0.0.1:8787";
47
+ }
48
+
49
+ function required(value: string | undefined, name: string): string {
50
+ if (!value) {
51
+ throw new Error(`${name} is required. Copy .dev.vars.example to .dev.vars.`);
52
+ }
53
+ return value;
54
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "node_modules/wrangler/config-schema.json",
3
+ "name": "mantle-local-admin-otp",
4
+ "main": "src/index.ts",
5
+ "compatibility_date": "2026-09-08",
6
+ "compatibility_flags": [
7
+ "nodejs_compat",
8
+ "global_fetch_strictly_public"
9
+ ],
10
+ "observability": {
11
+ "enabled": true
12
+ },
13
+ "assets": {
14
+ "directory": "./public",
15
+ "binding": "ASSETS"
16
+ },
17
+ "d1_databases": [
18
+ {
19
+ "binding": "DB",
20
+ "database_name": "mantle-local-admin-otp"
21
+ }
22
+ ]
23
+ }
@@ -1,14 +1,18 @@
1
1
  # Minimal Worker reference
2
2
 
3
3
  This is a directly authored, API-only application and executable consumer test.
4
- It is not a Starter, template catalog or CLI generator. The notes Schema/View
5
- is example business data; `mantle generate` never invents it.
4
+ It is not a Starter, template catalog or CLI generator. Admin is opt-in; this
5
+ reference has none. When humans need a console, see
6
+ [`docs/examples/host-local-admin-otp`](../host-local-admin-otp/README.md). Business
7
+ Manifests live in the [Examples hub](../README.md). The notes
8
+ Schema/View is example business data; `mantle generate` never invents it.
6
9
 
7
10
  For your own project, author package.json, manifests, Worker/provider config
8
11
  and TypeScript settings for your requirements. Pin all selected `@aotter/mantle*`
9
- dependencies to the same intended release. This reference records alpha.17 as
10
- its last published baseline; Core's test runner substitutes its exact candidate
11
- in a disposable copy, including during a future release.
12
+ dependencies to the same intended release. This reference depends on them via
13
+ the `latest` dist-tag instead, so a fresh install always resolves the current
14
+ stable release; Core's test runner substitutes its exact candidate in a
15
+ disposable copy.
12
16
 
13
17
  Outside the SDK workspace, with Node 22+ and pnpm 9+:
14
18
 
@@ -0,0 +1,25 @@
1
+ apiVersion: cms.mantle.aotter.net/v1
2
+ kind: Schema
3
+ metadata:
4
+ name: notes
5
+ spec:
6
+ title: Notes
7
+ schema:
8
+ type: object
9
+ required: [title]
10
+ properties:
11
+ title: { type: string }
12
+ lifecycle: publishing
13
+ ---
14
+ apiVersion: cms.mantle.aotter.net/v1
15
+ kind: View
16
+ metadata:
17
+ name: published-notes
18
+ spec:
19
+ surface: public
20
+ from: notes
21
+ cache: { sharedMaxAge: 3600 }
22
+ fields: [id, title]
23
+ filter:
24
+ eq: { field: status, value: published }
25
+ limit: 20
@@ -6,12 +6,12 @@
6
6
  "generate": "mantle generate",
7
7
  "validate": "mantle validate",
8
8
  "typecheck": "tsc --noEmit",
9
- "dev": "wrangler dev --local",
9
+ "dev": "wrangler dev --local --ip 127.0.0.1 --port 8787",
10
10
  "check": "mantle generate && mantle generate --check && mantle validate && mantle skills && mantle skills --check && tsc --noEmit && node smoke.mjs"
11
11
  },
12
12
  "dependencies": {
13
- "@aotter/mantle": "0.1.0-alpha.17",
14
- "@aotter/mantle-cloudflare": "0.1.0-alpha.17",
13
+ "@aotter/mantle": "latest",
14
+ "@aotter/mantle-cloudflare": "latest",
15
15
  "better-auth": "1.7.2",
16
16
  "hono": "^4.13.3",
17
17
  "zod": "^4.5.4",
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "noEmit": true,
8
+ "skipLibCheck": true,
9
+ "types": [
10
+ "@cloudflare/workers-types"
11
+ ]
12
+ },
13
+ "include": [
14
+ "src/**/*.ts",
15
+ ".mantle/generated/**/*.ts"
16
+ ]
17
+ }
@@ -5,6 +5,19 @@ description: Configure MANTLE_AUTH_MODE, secrets, the first owner and staff role
5
5
 
6
6
  Conventional Auth is chosen by one variable, `MANTLE_AUTH_MODE`, and fails closed when its configuration is incomplete. This page covers the two modes, the secrets each needs, first-owner bootstrap, roles, the routes that require a session, and the Better Auth integration surface.
7
7
 
8
+ ## Local Admin: email OTP
9
+
10
+ The local human path does not use `MANTLE_AUTH_MODE` or GitHub. Pass `auth` to `createMantleWorker` with `email-otp`, `ConsoleEmailSender`, and `bootstrapOwner.match: "email"`. The one-time code is printed on the wrangler log. See [Quickstart: local Admin](../start/quickstart-admin.md). `ConsoleEmailSender` is for `wrangler dev` only; production needs a real sender. The official example binds `127.0.0.1:8787` and sets `PUBLIC_ORIGIN` to that origin; Better Auth rejects OTP with `INVALID_ORIGIN` when they diverge from the origin wrangler prints.
11
+
12
+ With `auth` set, the mode matrix below is not read. Core still owns `/admin` and `/api/auth/*`.
13
+
14
+ For production email OTP, keep the custom `createAuth()` factory, replace
15
+ `ConsoleEmailSender` with the application's production `EmailSender`, and keep
16
+ `bootstrapOwner: { match: "email", value: <owner email> }`. Store the sender
17
+ credentials and `BETTER_AUTH_SECRET` as Worker secrets. If the application has
18
+ no transactional-email provider, use self-managed GitHub OAuth instead; never
19
+ deploy console delivery.
20
+
8
21
  ## Mode matrix
9
22
 
10
23
  | Mode | Non-secret vars | Worker secrets | Must be absent |
@@ -14,7 +27,7 @@ Conventional Auth is chosen by one variable, `MANTLE_AUTH_MODE`, and fails close
14
27
 
15
28
  Validation rules:
16
29
 
17
- - `PUBLIC_ORIGIN` is the site's HTTPS origin without a trailing slash. When unset, the adapter falls back to `http://localhost:8787`.
30
+ - `PUBLIC_ORIGIN` is the site's HTTPS origin without a trailing slash. When unset, the adapter falls back to `http://localhost:8787`. That fallback string is not the preferred local Admin pin. The origin wrangler prints is authoritative; the Admin OTP reference binds `127.0.0.1:8787` and sets `PUBLIC_ORIGIN` to the same origin.
18
31
  - Self-managed uses the site's own GitHub OAuth app. Register its callback URL as `<PUBLIC_ORIGIN>/api/auth/callback/github`.
19
32
  - Hosted is a public PKCE client with no client secret. `MANTLE_HOSTED_AUTH_ISSUER` must be an HTTPS root origin (no path, query or fragment; `http` only for loopback). `MANTLE_HOSTED_AUTH_CLIENT_ID` must be a URL on that same origin shaped `/clients/<id>`.
20
33
  - `ADMIN_GITHUB_LOGIN` must be a valid GitHub login.
@@ -218,4 +231,6 @@ rejected; there is no introspection fallback.
218
231
  - [`packages/mantle-admin/src/mountMantleAdmin.ts`](../../../packages/mantle-admin/src/mountMantleAdmin.ts)
219
232
  - [`docs/auth-hosting-model.md`](../../../docs/auth-hosting-model.md)
220
233
  - [`docs/adapter-guide.md`](../../../docs/adapter-guide.md)
221
- - [`docs/examples/minimal-worker/.gitignore`](../../../docs/examples/minimal-worker/.gitignore)
234
+ - [`docs/examples/host-local-admin-otp/src/index.ts`](../../../docs/examples/host-local-admin-otp/src/index.ts)
235
+ - [`docs/examples/host-local-admin-otp/.dev.vars.example`](../../../docs/examples/host-local-admin-otp/.dev.vars.example)
236
+ - [`docs/examples/host-minimal-worker/.gitignore`](../../../docs/examples/host-minimal-worker/.gitignore)
@@ -17,7 +17,7 @@ Every Mantle Worker keeps both flags:
17
17
 
18
18
  ## The composition-root rule
19
19
 
20
- Bindings appear in two places only: the Worker entry (`createMantleWorker` options and the `bindings` hook) and `wrangler.jsonc`. Procedure handlers receive them through `ctx.env`. An application may own additional tables behind its own repository, but it never queries Mantle-owned tables (`entries`, site settings, media, Auth) outside the runtime. Use `runtime.entries`, generated `bindMantle(runtime)` and Views instead.
20
+ Bindings appear in two places only: the Worker entry (`createMantleWorker` options and the `bindings` hook) and `wrangler.jsonc`. Procedure handlers receive them through `ctx.env`. An application may own additional tables behind its own repository, but it never queries Mantle-owned native Schema tables, site settings, media or Auth tables outside the runtime. Use `runtime.entries`, generated `bindMantle(runtime)` and Views instead.
21
21
 
22
22
  ## The Env interface
23
23
 
@@ -69,9 +69,11 @@ Omit `database_id` only for local development. Set the production id before any
69
69
  "assets": { "directory": "./public", "binding": "ASSETS" }
70
70
  ```
71
71
 
72
- `mantle generate` syncs the Admin SPA into `public/_mantle/admin/` when `@aotter/mantle-admin-ui` is installed. The facade wraps `ASSETS` as the Admin asset server and falls back to `/_mantle/admin/index.html` for client-side Admin routes. The same binding serves your own CSS, JS and icons.
72
+ `ASSETS` is a hard requirement for Admin. `mantle generate` syncs the **prebuilt** Admin SPA into `public/_mantle/admin/` when `@aotter/mantle-admin-ui` is installed — there is no Vite step unless you are developing `admin-ui` itself. The facade wraps `ASSETS` as the Admin asset server and falls back to `/_mantle/admin/index.html` for client-side Admin routes. The same binding serves your own CSS, JS and icons.
73
73
 
74
- When Worker routes and static paths overlap, list Worker-owned paths in `run_worker_first` and keep `not_found_handling` at `"none"` so unmatched requests fall through to the Worker. A production configuration lists `/admin`, `/admin/*`, `/mcp`, `/mcp/*`, `/oauth`, `/oauth/*`, `/.well-known/*`, `/api/*`, `/llms.txt`, `/robots.txt`, `/sitemap.xml`, `/*/llms.txt` and every public content prefix there, so a static file can never shadow a Mantle route.
74
+ Without this binding, `/admin` can still return SPA HTML (`200`) while `/_mantle/admin/assets/*` is `404`. That white-screens the Dev UI.
75
+
76
+ When Worker routes and static paths overlap, list Worker-owned paths in `run_worker_first` and keep `not_found_handling` at `"none"` so unmatched requests fall through to the Worker. **Leave `/_mantle` out of `run_worker_first`** so hashed Admin JS/CSS stay on the assets layer. A production configuration lists `/admin`, `/admin/*`, `/mcp`, `/mcp/*`, `/oauth`, `/oauth/*`, `/.well-known/*`, `/api/*`, `/llms.txt`, `/robots.txt`, `/sitemap.xml`, `/*/llms.txt` and every public content prefix there, so a static file can never shadow a Mantle route.
75
77
 
76
78
  ## Workers Cache
77
79
 
@@ -157,7 +159,7 @@ Durable Objects are not a Core primitive. Use one as an application-owned coordi
157
159
  export { InventoryCoordinator } from "./commerce/InventoryCoordinator.js";
158
160
  ```
159
161
 
160
- The coordinator keeps its own state; Mantle entries mirror the result. See [Commerce transaction](../examples/commerce-transaction.md).
162
+ The coordinator keeps its own state; Mantle entries mirror the result. See [Commerce inventory](../../examples/cf-primitives-commerce-inventory.md).
161
163
 
162
164
  ## Email Service: `[[send_email]]`
163
165
 
@@ -219,7 +221,7 @@ extend: ({ env }) => ({
219
221
  }),
220
222
  ```
221
223
 
222
- Authenticated callers (`ctx.user` set) bypass the check. The literal secret `"dev-stub"` skips the network and rejects only the token `"fail"`. Rejection throws `AUTH_DENIED`, which maps to HTTP `403`. See [Intake form](../examples/intake-form.md).
224
+ Authenticated callers (`ctx.user` set) bypass the check. The literal secret `"dev-stub"` skips the network and rejects only the token `"fail"`. Rejection throws `AUTH_DENIED`, which maps to HTTP `403`. See [Intake Turnstile and email hooks](../../examples/cf-primitives-intake-hooks.md).
223
225
 
224
226
  ## Source
225
227
  - [`packages/adapters/cloudflare/README.md`](../../../packages/adapters/cloudflare/README.md)
@@ -232,5 +234,5 @@ Authenticated callers (`ctx.user` set) bypass the check. The literal secret `"de
232
234
  - [`docs/media-uploads.md`](../../../docs/media-uploads.md)
233
235
  - [`docs/performance-harness.md`](../../../docs/performance-harness.md)
234
236
  - [`docs/cloudflare-low-level-composition.md`](../../../docs/cloudflare-low-level-composition.md)
235
- - [`docs/examples/minimal-worker/wrangler.jsonc`](../../../docs/examples/minimal-worker/wrangler.jsonc)
236
- - Retired-starter patterns: [`blank/wrangler.toml`](https://github.com/aotter/mantle-starters/blob/a66ec0ea3aaefc09a0229b7d8ca35af630f2b55d/blank/wrangler.toml), [`overlays/transaction/wrangler.append.toml`](https://github.com/aotter/mantle-starters/blob/a66ec0ea3aaefc09a0229b7d8ca35af630f2b55d/overlays/transaction/wrangler.append.toml), [`overlays/transaction/src/index.ts`](https://github.com/aotter/mantle-starters/blob/a66ec0ea3aaefc09a0229b7d8ca35af630f2b55d/overlays/transaction/src/index.ts), [`overlays/transaction/src/mantle/config.ts`](https://github.com/aotter/mantle-starters/blob/a66ec0ea3aaefc09a0229b7d8ca35af630f2b55d/overlays/transaction/src/mantle/config.ts), [`overlays/intake/src/worker/features/intake/notifyIntake.ts`](https://github.com/aotter/mantle-starters/blob/a66ec0ea3aaefc09a0229b7d8ca35af630f2b55d/overlays/intake/src/worker/features/intake/notifyIntake.ts), [`overlays/intake/manifests/site.yaml`](https://github.com/aotter/mantle-starters/blob/a66ec0ea3aaefc09a0229b7d8ca35af630f2b55d/overlays/intake/manifests/site.yaml)
237
+ - [`docs/examples/host-local-admin-otp/wrangler.jsonc`](../../../docs/examples/host-local-admin-otp/wrangler.jsonc)
238
+ - [`docs/examples/host-minimal-worker/wrangler.jsonc`](../../../docs/examples/host-minimal-worker/wrangler.jsonc)
@@ -0,0 +1,29 @@
1
+ ---
2
+ description: "Mantle on ChatGPT Sites now has its own integration guide."
3
+ ---
4
+ # Mantle on ChatGPT Sites
5
+
6
+ ChatGPT Sites has a dedicated section in the Mantle handbook. Start with
7
+ [Mantle on ChatGPT Sites](../sites/index.md) for installation, supported
8
+ capabilities, and your first published article. This page preserves earlier
9
+ links to the host checklist.
10
+
11
+ ## First deployment
12
+
13
+ See [the host deployment checklist](../sites/host-reference.md#first-deployment).
14
+
15
+ ## Media with only an R2 binding
16
+
17
+ See [R2 media integration](../sites/host-reference.md#media-with-only-an-r2-binding).
18
+
19
+ ## Remote MCP is a separate gate
20
+
21
+ See [MCP support and authentication](../sites/host-reference.md#remote-mcp-is-a-separate-gate).
22
+
23
+ ## Capability boundaries
24
+
25
+ See [host capability boundaries](../sites/host-reference.md#capability-boundaries).
26
+
27
+ ## Source
28
+
29
+ See [integration sources](../sites/host-reference.md#source).
@@ -14,7 +14,7 @@ import { plan } from "../.mantle/generated/mantle.js";
14
14
  export default createMantleWorker({ plan, cacheScope: "my-site-production" });
15
15
  ```
16
16
 
17
- `plan` is the sealed plan that `mantle generate` writes to `.mantle/generated/mantle.ts`. With only `plan`, the Worker serves public View REST, HTTP Triggers, Admin, Auth, OAuth and MCP. It renders no public pages and `/` is a 404; see [Public web](./public-web.md).
17
+ `plan` is the sealed plan that `mantle generate` writes to `.mantle/generated/mantle.ts`. With only `plan`, the Worker serves public View REST, HTTP Triggers, Admin, Auth, OAuth and MCP. It renders no public pages and `/` is a 404; see [Public web](./public-web.md). Local Admin with email OTP replaces Auth construction; see [Quickstart: local Admin](../start/quickstart-admin.md). Admin still requires the `ASSETS` binding.
18
18
 
19
19
  ## Options
20
20
 
@@ -158,5 +158,5 @@ The conventional set is `{ db, adminAssets, mcpCatalogKv? }`. Never drop `db`.
158
158
  - [`packages/adapters/cloudflare/src/worker/createMantleWorker.ts`](../../../packages/adapters/cloudflare/src/worker/createMantleWorker.ts)
159
159
  - [`packages/adapters/cloudflare/src/bindings/conventionalBindings.ts`](../../../packages/adapters/cloudflare/src/bindings/conventionalBindings.ts)
160
160
  - [`packages/adapters/cloudflare/src/mount/cmsConfig.ts`](../../../packages/adapters/cloudflare/src/mount/cmsConfig.ts)
161
- - [`docs/examples/minimal-worker/src/index.ts`](../../../docs/examples/minimal-worker/src/index.ts)
162
- - [`docs/migration-0.1.2.md`](../../../docs/migration-0.1.2.md)
161
+ - [`docs/examples/host-local-admin-otp/src/index.ts`](../../../docs/examples/host-local-admin-otp/src/index.ts)
162
+ - [`docs/examples/host-minimal-worker/src/index.ts`](../../../docs/examples/host-minimal-worker/src/index.ts)
@@ -168,4 +168,3 @@ For a site, run `wrangler dev`, submit a mutation that has an `after_*` Trigger,
168
168
  - [`packages/adapters/cloudflare/src/bindings/WorkersQueueHookDispatcher.ts`](../../../packages/adapters/cloudflare/src/bindings/WorkersQueueHookDispatcher.ts)
169
169
  - [`packages/adapters/cloudflare/src/mount/cmsConfig.ts`](../../../packages/adapters/cloudflare/src/mount/cmsConfig.ts)
170
170
  - [`packages/adapters/cloudflare/README.md`](../../../packages/adapters/cloudflare/README.md)
171
- - Retired-starter pattern: [`overlays/transaction/src/index.ts`](https://github.com/aotter/mantle-starters/blob/a66ec0ea3aaefc09a0229b7d8ca35af630f2b55d/overlays/transaction/src/index.ts)
@@ -1,16 +1,16 @@
1
1
  ---
2
- description: Production checklist, the check loop, deploy and post-deploy probes, day-to-day content operations, and upgrades.
2
+ description: Production checklist, the check loop, deploy and post-deploy probes, day-to-day content operations, and version pins.
3
3
  ---
4
4
  # Deploy and operate
5
5
 
6
- This page is the production checklist for a Mantle Worker on Cloudflare: what to pin and configure, which checks to run before `wrangler deploy`, how to verify a deployment, how content is operated afterwards, and how to upgrade.
6
+ This page is the production checklist for a Mantle Worker on Cloudflare: what to pin and configure, which checks to run before `wrangler deploy`, how to verify a deployment, how content is operated afterwards, and how to move versions.
7
7
 
8
8
  ## Before the first deploy
9
9
 
10
10
  - Pin every `@aotter/mantle*` package to one exact version and commit the lockfile. Install with `pnpm install --frozen-lockfile` (or `npm ci`) from then on. See [Project and CLI](../start/project-and-cli.md).
11
11
  - Set `PUBLIC_ORIGIN` to the real HTTPS origin, without a trailing slash. It drives canonical URLs, `.md` mirrors, `llms.txt`, the MCP resource and the OAuth callback. If a static documentation build also emits absolute URLs, give it the same value.
12
- - Set the production D1 `database_id` (and `account_id` if your deployment needs it) in `wrangler.jsonc`. A local `database_name` is not a production identifier, and local data is not migrated.
13
- - Choose `MANTLE_AUTH_MODE` and store the secrets with `wrangler secret put`. See [Authentication](./authentication.md).
12
+ - Set the production D1 `database_id` (and `account_id` if your deployment needs it) in `wrangler.jsonc`. A local `database_name` is not a production identifier, and local D1 is not production data.
13
+ - Choose production Auth: replace local console email delivery with a real `EmailSender`, or configure `MANTLE_AUTH_MODE` for GitHub OAuth or hosted Auth. Store secrets with `wrangler secret put`. See [Authentication](./authentication.md).
14
14
  - Keep `compatibility_flags: ["nodejs_compat", "global_fetch_strictly_public"]`.
15
15
  - Enable observability:
16
16
 
@@ -72,34 +72,28 @@ Sign in to Admin with a staff account. Publishing collections (`lifecycle: publi
72
72
 
73
73
  Operational collections (`lifecycle: operational`) have no publish step; records are edited in place and do not purge the public cache. The same operations are available to agents through Staff MCP; see [MCP and agents](../concepts/mcp-and-agents.md).
74
74
 
75
- Site settings split by owner. Brand, title and description seed once from `siteDefaults` and are then edited in Admin (`owner`); each edit purges the public cache. Origin, icons, locales and media purposes are code-owned and re-sync from `siteDefaults` on every boot, so change them in the Worker and redeploy. See [Site config](../reference/site-config.md).
75
+ Site settings split by owner. Brand, title and description seed once from `siteDefaults` and are then edited in Admin (`owner`); each edit purges the public cache. Origin, icons, locales and media purposes are code-owned and re-sync from `siteDefaults` on every boot, so change them in the Worker and redeploy. Analytics, pixels and search-engine verification are host chrome, not Core settings; see [Site chrome](./site-chrome.md) and [Site config](../reference/site-config.md).
76
76
 
77
- ## Upgrading
77
+ ## Changing versions
78
78
 
79
- Read the migration notes shipped with the target release before changing versions; docs on the development branch do not describe your installed version. The 0.1.2 line removes `mantle create`, the bundle `mantle update` and `@aotter/mantle/provision`; `generate`, `validate`, `emit-openapi` and `skills` remain. To upgrade:
79
+ Pin every selected `@aotter/mantle*` package to one exact version and keep them together. This handbook describes the snapshot in this source tree; use the docs that ship with the version you install.
80
80
 
81
- 1. Pin the new exact release for every selected package and update the lockfile through the package manager; review peer upgrades.
82
- 2. Remove scripts that call retired commands. Keep application source, Worker/D1/KV identity, origins, auth mode and secrets.
81
+ 1. Pin the new exact release for every selected package and update the lockfile through the package manager; review peer ranges.
82
+ 2. Keep application source, Worker/D1/KV identity, origins, auth mode and secrets.
83
83
  3. Run `mantle generate`, `generate --check`, `skills`, `skills --check`, `validate`, typecheck and tests.
84
84
  4. Test local routes and authorization, then deploy.
85
85
 
86
- The native-table storage contract is a pre-beta breaking change. Existing
87
- generic-`entries` databases are unsupported: rebuild the instance and move any
88
- required data manually outside Mantle. New native-table instances deploy safe
89
- additive changes online; destructive changes are rejected and require another
90
- manual rebuild. Any `uniqueIndexes` tuple change is destructive. Mantle does not
91
- ship an in-product migration workflow for this unreleased storage format.
86
+ Each Manifest Schema is a native table. Safe additive storage changes deploy online. Destructive changes — including any `uniqueIndexes` tuple add, remove, reorder or rewrite — are rejected; rebuild the instance and move required data outside Mantle. There is no in-product data-move workflow.
92
87
 
93
88
  ## Source
94
89
  - [`packages/mantle/README.md`](../../../packages/mantle/README.md)
95
90
  - [`packages/adapters/cloudflare/README.md`](../../../packages/adapters/cloudflare/README.md)
96
91
  - [`docs/direct-authoring.md`](../../../docs/direct-authoring.md)
97
- - [`docs/migration-0.1.2.md`](../../../docs/migration-0.1.2.md)
98
92
  - [`docs/performance-harness.md`](../../../docs/performance-harness.md)
99
- - [`docs/examples/minimal-worker/README.md`](../../../docs/examples/minimal-worker/README.md)
100
- - [`docs/examples/minimal-worker/package.json`](../../../docs/examples/minimal-worker/package.json)
101
- - [`docs/examples/minimal-worker/wrangler.jsonc`](../../../docs/examples/minimal-worker/wrangler.jsonc)
102
- - [`docs/examples/minimal-worker/smoke.mjs`](../../../docs/examples/minimal-worker/smoke.mjs)
93
+ - [`docs/examples/host-minimal-worker/README.md`](../../../docs/examples/host-minimal-worker/README.md)
94
+ - [`docs/examples/host-minimal-worker/package.json`](../../../docs/examples/host-minimal-worker/package.json)
95
+ - [`docs/examples/host-minimal-worker/wrangler.jsonc`](../../../docs/examples/host-minimal-worker/wrangler.jsonc)
96
+ - [`docs/examples/host-minimal-worker/smoke.mjs`](../../../docs/examples/host-minimal-worker/smoke.mjs)
103
97
  - [`packages/mantle-spec/src/domain/model/SiteConfig.ts`](../../../packages/mantle-spec/src/domain/model/SiteConfig.ts)
104
98
  - [`packages/mantle-admin/src/mountMantleAdmin.ts`](../../../packages/mantle-admin/src/mountMantleAdmin.ts)
105
99
  - [`packages/adapters/cloudflare/src/oauth/cachePolicy.ts`](../../../packages/adapters/cloudflare/src/oauth/cachePolicy.ts)