@aotter/mantle 0.2.0-alpha.1 → 0.2.0-alpha.3

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 (67) hide show
  1. package/dist/admin/createAdminSurface.d.ts.map +1 -1
  2. package/dist/admin/createAdminSurface.js +3 -0
  3. package/dist/admin/createAdminSurface.js.map +1 -1
  4. package/dist/admin/design.d.ts +7 -0
  5. package/dist/admin/design.d.ts.map +1 -0
  6. package/dist/admin/design.js +18 -0
  7. package/dist/admin/design.js.map +1 -0
  8. package/dist/admin/developerConsole.d.ts +3 -0
  9. package/dist/admin/developerConsole.d.ts.map +1 -1
  10. package/dist/admin/developerConsole.js +3 -3
  11. package/dist/admin/developerConsole.js.map +1 -1
  12. package/dist/admin/index.d.ts +2 -0
  13. package/dist/admin/index.d.ts.map +1 -1
  14. package/dist/admin/index.js +2 -0
  15. package/dist/admin/index.js.map +1 -1
  16. package/dist/admin/procedureFlow.d.ts +14 -1
  17. package/dist/admin/procedureFlow.d.ts.map +1 -1
  18. package/dist/admin/procedureFlow.js +35 -1
  19. package/dist/admin/procedureFlow.js.map +1 -1
  20. package/dist/auth/buildAuth.d.ts +6 -5
  21. package/dist/auth/buildAuth.d.ts.map +1 -1
  22. package/dist/auth/buildAuth.js +5 -1
  23. package/dist/auth/buildAuth.js.map +1 -1
  24. package/dist/auth/createAuthRoutes.d.ts +1 -1
  25. package/dist/auth/createAuthRoutes.js +7 -6
  26. package/dist/auth/createAuthRoutes.js.map +1 -1
  27. package/dist/auth/createMantleAuth.d.ts.map +1 -1
  28. package/dist/auth/createMantleAuth.js +32 -12
  29. package/dist/auth/createMantleAuth.js.map +1 -1
  30. package/dist/cli/generate.d.ts.map +1 -1
  31. package/dist/cli/generate.js +38 -2
  32. package/dist/cli/generate.js.map +1 -1
  33. package/dist/cli/preset.d.ts.map +1 -1
  34. package/dist/cli/preset.js +2 -1
  35. package/dist/cli/preset.js.map +1 -1
  36. package/dist/core/withCaller.js +1 -1
  37. package/dist/core/withCaller.js.map +1 -1
  38. package/dist/spec/infrastructure/sql/PgQueryParser.js +1 -1
  39. package/dist/spec/infrastructure/sql/PgQueryParser.js.map +1 -1
  40. package/dist/spec/infrastructure/sql/compileSql.js +2 -2
  41. package/dist/spec/infrastructure/sql/compileSql.js.map +1 -1
  42. package/dist/web/createRestSurface.d.ts.map +1 -1
  43. package/dist/web/createRestSurface.js +5 -3
  44. package/dist/web/createRestSurface.js.map +1 -1
  45. package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +20 -0
  46. package/docs/adr/0031-host-cloud-core-contract.md +17 -1
  47. package/docs/adr/0032-store-centric-core-and-service-contract.md +1 -1
  48. package/docs/adr/README.md +1 -1
  49. package/docs/examples/procurement.md +12 -1
  50. package/docs/examples/reference-service/package.json +6 -6
  51. package/docs/examples/reference-service/smoke.mjs +6 -2
  52. package/docs/examples/reference-service/src/service.ts +1 -1
  53. package/docs/handbook/cloudflare/authentication.md +112 -1
  54. package/docs/handbook/cloudflare/deploy-and-operate.md +15 -4
  55. package/docs/handbook/concepts/authorization.md +7 -1
  56. package/docs/handbook/concepts/mcp-and-agents.md +2 -2
  57. package/docs/handbook/guides/admin-ui.md +29 -0
  58. package/docs/handbook/reference/surface.md +1 -1
  59. package/docs/handbook/releases/index.md +7 -2
  60. package/docs/handbook/start/overview.md +1 -1
  61. package/docs/handbook/start/project-and-cli.md +7 -0
  62. package/docs/handbook/start/quickstart-worker.md +5 -4
  63. package/docs/skills/develop/SKILL.md +10 -0
  64. package/docs/skills/develop/mcp-app.md +1 -1
  65. package/docs/skills/provision/SKILL.md +4 -3
  66. package/docs/skills/update/SKILL.md +6 -0
  67. package/package.json +14 -12
@@ -24,10 +24,14 @@ createMantleAuth({
24
24
  methods: [{ kind: "email-otp", sender: new ConsoleEmailSender() }],
25
25
  bootstrapOwner: { match: "email", value: env.ADMIN_EMAIL },
26
26
  ipAddressHeaders: ["cf-connecting-ip"],
27
- oauthProvider: { loginPage: "/admin/sign-in", consentPage: "/oauth/consent", scopes: ["mcp"], mcpResource: `${origin}/mcp` },
27
+ oauthProvider: { loginPage: "/admin/sign-in", consentPage: "/admin/oauth/consent", scopes: ["mcp"], mcpResource: `${origin}/mcp` },
28
28
  });
29
29
  ```
30
30
 
31
+ `consentPage` is Admin's consent page when the service mounts Admin (the
32
+ preset does); without Admin it is `/oauth/consent`, the plain page
33
+ `createAuthRoutes` serves. Both post the decision to `/oauth/consent`.
34
+
31
35
  then `createCallerResolver(auth, { jwtBearer: { audience: `${origin}/mcp`, scopes: ["mcp"] } })`,
32
36
  `createAuthRoutes(auth, { resolver })` and an `AdminIdentity` over the auth's
33
37
  own methods. With dialect `postgres` the preset passes `database:
@@ -38,6 +42,54 @@ passes `bunAuthDatabase(sql)` and `bunDatabaseDriver(sql)` (or the bun:sqlite
38
42
 
39
43
  ### Local sign-in
40
44
 
45
+ Install auth and MCP dependencies at the versions declared in the installed
46
+ Core package's `peerDependencies`; the generator prints a versioned install
47
+ command and refuses incompatible installed peers before writing generated files.
48
+ The preset owns convergence; a normal fresh local database needs no manual auth
49
+ migration. `auth.ready` initializes Better Auth's context; auth tables are prepared
50
+ lazily before authenticated operations. Mantle runs Better Auth's explicit schema
51
+ validation after that preparation, including when the schema ledger is current;
52
+ an incompatible existing schema still rejects authentication.
53
+
54
+ #### Upgrading an existing 1.7.0–1.7.2 auth database
55
+
56
+ Those Better Auth versions created a required `account.issuer` column that
57
+ 1.7.7 no longer writes. Automatic additive convergence preserves that column;
58
+ it cannot make a required unused column safe for future inserts. Before starting
59
+ the upgraded service, the database owner must migrate this legacy constraint.
60
+ Fresh databases need no such step.
61
+
62
+ For PostgreSQL, first inspect the service's own database/schema and back it up:
63
+
64
+ ```sql
65
+ SELECT table_schema, is_nullable, column_default
66
+ FROM information_schema.columns
67
+ WHERE table_name = 'account' AND column_name = 'issuer';
68
+ ```
69
+
70
+ If the legacy column is required and has no default, apply this in the service's
71
+ auth schema (the example assumes it is selected by `search_path`):
72
+
73
+ ```sql
74
+ BEGIN;
75
+ ALTER TABLE "account" ALTER COLUMN "issuer" DROP NOT NULL;
76
+ COMMIT;
77
+ ```
78
+
79
+ This retains every row and existing issuer value. It changes only the obsolete
80
+ constraint; new accounts may leave that column null. Restart the service after
81
+ the migration so Better Auth checks the repaired schema in a fresh context.
82
+ Verify existing users, roles, sessions and content rows, then sign in again.
83
+ Do not drop auth tables or disable schema validation. This applies to native Bun
84
+ PostgreSQL and Cloudflare/Hyperdrive PostgreSQL alike.
85
+
86
+ For D1/SQLite, `ALTER COLUMN` is unavailable. Use a database-owner-reviewed table
87
+ rebuild that retains every column/value, index and foreign-key relationship while
88
+ making the legacy issuer nullable; do not run the PostgreSQL statement there.
89
+ See Better Auth's [1.7 upgrade guide](https://www.better-auth.com/docs/guides/1-7-upgrade-guide).
90
+ Mantle's portable auth facade does not silently perform engine-specific table
91
+ rebuilds or remove historical fields.
92
+
41
93
  ```sh
42
94
  cp .dev.vars.example .dev.vars # ADMIN_EMAIL, a random BETTER_AUTH_SECRET, PUBLIC_ORIGIN=http://127.0.0.1:8787
43
95
  pnpm exec wrangler dev --local
@@ -53,6 +105,65 @@ origin and both secrets exist. Otherwise it uses
53
105
  `createSetupIncompleteAuth`, which refuses sign-in with a message, so a
54
106
  deployed service never prints codes to its log.
55
107
 
108
+ On Bun, copy `.env.example` to `.env`, set `PUBLIC_ORIGIN=http://127.0.0.1:3000`
109
+ and run `bun src/index.ts`; send the same HTTP requests to port 3000. Codes print
110
+ to the Bun console. Normalize test email addresses to lowercase when matching
111
+ that output. Treat codes and session cookies as secrets in test logs.
112
+
113
+ Local clients share the loopback socket IP and therefore share sign-in limits.
114
+ Multi-user smoke tests must respect the retry window (HTTP 429) rather than
115
+ spoofing forwarding headers or disabling production limits. `rateLimit: {
116
+ window: 60, max: 100 }` changes the general quota, but plugin-specific OTP limits
117
+ still apply; changing it is not a way to bypass those limits.
118
+
119
+ ### MCP clients for members
120
+
121
+ `/admin/sign-in` suits staff. When members connect an MCP client to the public
122
+ `/mcp`, set `loginPage` to the service's own sign-in page. That page must
123
+ continue the authorization: Better Auth sends the browser there with the
124
+ request signed, and the sign-in carries it back as `oauth_query`, then follows
125
+ the `url` it answers (consent) instead of the page's own destination:
126
+
127
+ ```ts
128
+ import { signedOAuthQuery } from "@aotter/mantle-ui/kit";
129
+
130
+ const oauthQuery = signedOAuthQuery(window.location.search);
131
+ const res = await fetch("/api/auth/sign-in/email-otp", { method: "POST", credentials: "include",
132
+ headers: { "content-type": "application/json" }, body: JSON.stringify({ email, otp, ...(oauthQuery ? { oauth_query: oauthQuery } : {}) }) });
133
+ const { url } = await res.json();
134
+ location.assign(url ?? "/account");
135
+ ```
136
+
137
+ The generated preset does not enable dynamic client registration (DCR).
138
+ An `access_denied` response from `/api/auth/oauth2/register` does not mean
139
+ member OAuth is broken. An owner can register a known local client through
140
+ the public `MantleAuth` API, without opening DCR:
141
+
142
+ ```ts
143
+ const client = await auth.registerOAuthClient({
144
+ requestHeaders: ownerRequest.headers,
145
+ clientName: "Local MCP client",
146
+ redirectUris: ["http://127.0.0.1:3456/callback"],
147
+ applicationType: "native",
148
+ tokenEndpointAuthMethod: "none",
149
+ grantTypes: ["authorization_code", "refresh_token"],
150
+ responseTypes: ["code"],
151
+ scope: ["mcp"],
152
+ requirePKCE: true,
153
+ });
154
+ ```
155
+
156
+ Use a real owner session, keep persisted user consent, and configure the MCP
157
+ client with the returned `clientId`. This belongs in an owner-controlled
158
+ management path, not an anonymous registration handler. Choose DCR separately
159
+ according to the host's registration policy.
160
+
161
+ When DCR is enabled, registration takes a client that names no `application_type`
162
+ and registers only `http` loopback redirects (`localhost`, `127.0.0.1`,
163
+ `[::1]`) as `native` (RFC 8252), which is how local MCP clients register; any
164
+ other registration is checked as Better Auth checks it (`web` needs `https` on a
165
+ non-loopback host).
166
+
56
167
  ### Production
57
168
 
58
169
  Replace the local choices in `src/service.ts`:
@@ -99,10 +99,21 @@ you built (`PLAN_FINGERPRINT_MISMATCH`). The fingerprint is in
99
99
 
100
100
  ## Mantle Cloud
101
101
 
102
- Deploying a 0.2.0 service to Mantle Cloud needs Cloud's host protocol 3, which
103
- uploads the compiled plan and the service entry. Mantle Cloud does not accept
104
- 0.2.0 services yet. The plugin's `mantle` skill and its helper script check
105
- the plan and the installed Core version so a project is ready when it does.
102
+ Mantle Cloud accepts its pinned 0.2 service contract through host protocol 4. A verified source receipt is required before save.
103
+ Connect Cloud MCP and follow the plugin's `mantle` skill and its
104
+ `references/cloud.md` workflow. Call `cloud_host_contract` for the selected
105
+ project before installing or packing: Cloud returns the exact Core version
106
+ and revision. Do not substitute a moving npm tag or upload a self-hosted
107
+ Worker's platform wiring. Cloud supplies identity and D1 storage; PostgreSQL
108
+ plans are not accepted.
109
+
110
+ The skill's `scripts/mantle-cloud.mjs` helper orchestrates save, paired
111
+ preview and publish through the connected MCP tools. Follow its literal
112
+ `nextAction`, keeping grants on stdin and preserving the project and operation
113
+ IDs on retry. A saved or paired version is not a serving deployment; publish
114
+ only under the user's authorization and verify Cloud's returned serving state.
115
+ Its offline `check` reports `cloud: "not_checked"`, which proves local
116
+ readiness only. Use Cloud's workflow, not `wrangler deploy`, for that target.
106
117
 
107
118
  ## Moving data from 0.1.x
108
119
 
@@ -117,7 +117,13 @@ The writer comes from the caller, never from an input. See
117
117
 
118
118
  Admin's gate admits any staff role, and each Admin route names the least role
119
119
  it needs. Admin reads and writes through `runtime.store.as(caller)`, so scope
120
- and `requires` apply to staff exactly as to anyone. See
120
+ applies to generic entry routes. A View or Procedure's `requires` applies when
121
+ that atom runs, including Admin reports and operations; it does not restrict
122
+ generic entry reads or writes. Root Schema `readOnly: true` blocks generic
123
+ writes, while reads still follow Admin's role gate and Schema scope.
124
+ For an unscoped table shared by members and reviewers, an owner-filtered View
125
+ does not hide its rows from contributors using generic Admin routes. Compose
126
+ a stricter Admin surface in the application when the product requires it. See
121
127
  [Customize Admin](../guides/admin-ui.md).
122
128
 
123
129
  ## Further reading
@@ -50,8 +50,8 @@ again.
50
50
 
51
51
  `createMantleAuth` runs Better Auth's OAuth provider with `mcpResource:
52
52
  {PUBLIC_ORIGIN}/mcp`. An MCP client discovers it from the 401 challenge's
53
- `resource_metadata` (`/.well-known/oauth-protected-resource/mcp`), registers,
54
- sends the user through sign-in and consent (`/oauth/consent`), and calls `/mcp`
53
+ `resource_metadata` (`/.well-known/oauth-protected-resource/mcp`), uses a registered client,
54
+ sends the user through sign-in and consent (Admin's `/admin/oauth/consent`, or the plain `/oauth/consent`), and calls `/mcp`
55
55
  with the token. The resolver (`createCallerResolver(auth, { jwtBearer: { audience, scopes: ["mcp"] } })`)
56
56
  verifies it on every request and reads the user's current role; nothing is
57
57
  cached. Every MCP surface also keeps the scope floor (ADR-0014): a credential
@@ -121,6 +121,35 @@ and manages media, `owner` manages staff, site settings and the developer
121
121
  console. Operations and staff Views are further limited by their own
122
122
  `requires`.
123
123
 
124
+ Those atom predicates do not restrict generic Schema entry routes. A root
125
+ Schema `readOnly: true` blocks generic writes, not reads. In a service where
126
+ only owner/editor reviewers may read an unscoped table, restrict the Admin
127
+ surface in the application-owned `src/service.ts`:
128
+
129
+ ```ts
130
+ // Replace the preset's Admin composition, keeping its existing options.
131
+ const adminSurface = createAdminSurface(runtime, { /* existing Admin options */ });
132
+ const admin = withCaller(resolver, async (request, caller) => {
133
+ if (new URL(request.url).pathname.startsWith("/admin/api/") &&
134
+ caller.kind === "user" && caller.role === "contributor") {
135
+ return Response.json({ error: {
136
+ code: "AUTH_DENIED", message: "Admin access requires an owner or editor role.",
137
+ } }, { status: 403 });
138
+ }
139
+ return adminSurface(request, caller);
140
+ });
141
+ ```
142
+
143
+ The preset already imports `withCaller` from `@aotter/mantle`. Reuse its
144
+ resolver so credential validation and the session origin guard run once;
145
+ Admin retains its own gates for members and anonymous callers. Contributors
146
+ can use an application-owned page over an owner-filtered public View. Keep
147
+ sign-in, OAuth consent and UI assets reachable; restricting the whole `/admin`
148
+ prefix would also block member MCP consent at `/admin/oauth/consent`. This is
149
+ host surface composition, not a Procedure `ref` handler or a new Manifest
150
+ permission key. Exercise generic entry reads, reports and operations with
151
+ each role, including after revoking a role in an existing session.
152
+
124
153
  ## Verify a change
125
154
 
126
155
  1. Run `mantle generate`, then `mantle generate --check`.
@@ -34,7 +34,7 @@ origins.
34
34
  | `GET /api/auth/methods` | the sign-in methods this service offers |
35
35
  | `/api/auth/*` | Better Auth: email OTP (`/email-otp/send-verification-otp`, `/sign-in/email-otp`), magic link, social and generic OAuth sign-in, sessions, sign-out, the OAuth provider |
36
36
  | `/.well-known/oauth-authorization-server/*`, `/.well-known/oauth-protected-resource[/mcp]` | OAuth metadata, served by Better Auth |
37
- | `/oauth/consent`, `/oauth/consents` | consent and connected apps, session only |
37
+ | `/oauth/consent`, `/oauth/consents` | consent and connected apps, session only; with Admin, consent renders at `/admin/oauth/consent` and posts here |
38
38
 
39
39
  ### MCP
40
40
 
@@ -56,8 +56,13 @@ by hand.
56
56
  `emit-openapi`, `skills` and `mantle-harness` are removed.
57
57
  - **The Admin console** moves to `@aotter/mantle-ui/admin`, served at `/admin`
58
58
  by the preset through the service's `ASSETS` binding.
59
- - **Not yet in 0.2.0:** Mantle-rendered public pages, the MCP interaction App
60
- tools, and deploying to Mantle Cloud.
59
+ - **Mantle Cloud** uses its pinned Core contract and host protocol 4 through
60
+ the plugin's `mantle` Cloud workflow, requiring a verified source receipt
61
+ before save, with separate preview and publish
62
+ states. Its host contract decides the accepted version; offline readiness
63
+ is not a deployed service.
64
+ - **Not yet in 0.2.0:** Mantle-rendered public pages and the MCP interaction
65
+ App tools.
61
66
 
62
67
  ## 0.1.4 — 2026-09-24
63
68
 
@@ -43,6 +43,7 @@ After that they are your files.
43
43
  | Look up a field | [Feature table](../reference/features.md), then the [Schema](../reference/schema.md), [View](../reference/view.md), [Procedure](../reference/procedure.md) and [Trigger](../reference/trigger.md) references |
44
44
  | Wire sign-in, media or deploy | [Authentication](../cloudflare/authentication.md), [Media uploads with R2](../cloudflare/media-r2.md), [Deploy and operate](../cloudflare/deploy-and-operate.md) |
45
45
  | Run on ChatGPT Sites | [Mantle on ChatGPT Sites](../cloudflare/chatgpt-sites.md) |
46
+ | Save, preview and publish on Mantle Cloud | [Mantle Cloud](../cloudflare/deploy-and-operate.md#mantle-cloud), then the plugin's `mantle` Cloud workflow |
46
47
  | See whole services | [Examples](../examples/hub.md) and the runnable reference service |
47
48
  | Move a 0.1.x project | `docs/upgrade-0.1-to-0.2.md` in the installed package |
48
49
 
@@ -60,4 +61,3 @@ check the code.
60
61
  `createMantle`. The Bun preset has no scheduler.
61
62
  - **Site settings and media on PostgreSQL.** Only the `sqlite` dialect's
62
63
  storage takes `SiteDefaults`; see [Site defaults](../reference/site-config.md).
63
- - **Deploying to Mantle Cloud.** See [Deploy and operate](../cloudflare/deploy-and-operate.md).
@@ -59,6 +59,13 @@ and stops before writing anything, naming the install command for your
59
59
  package manager. It never installs anything. The host and dialect rows, and the
60
60
  staff MCP App, are checked only while the preset is still to be written.
61
61
 
62
+ Use the versions in the installed Core's `package.json` `peerDependencies`,
63
+ not registry latest, for optional auth and MCP packages. The diagnostic's
64
+ install command includes those versions and incompatible installed peers are
65
+ refused before generation. Keep `@aotter/mantle-ui` at Core's
66
+ exact version. A package manager accepting an incompatible peer does not make
67
+ it a supported combination; inspect peer warnings before starting the service.
68
+
62
69
  | Selection | Packages |
63
70
  |---|---|
64
71
  | always | `@aotter/mantle` |
@@ -112,12 +112,13 @@ pnpm exec mantle generate
112
112
  ```
113
113
 
114
114
  With no flags the selection is identity `mantle` and every feature. The first
115
- run names the packages it needs, with the install command, and writes nothing
116
- until they are installed:
115
+ run names the packages it needs, with versioned install commands from the
116
+ installed Core's `peerDependencies`, and writes nothing until they are installed
117
+ at compatible versions. Copy those commands rather than installing registry
118
+ latest; `@aotter/mantle-ui` must match Core's exact version:
117
119
 
118
120
  ```sh
119
- pnpm add better-auth @better-auth/oauth-provider @better-auth/mcp @better-auth/cimd \
120
- @modelcontextprotocol/server @modelcontextprotocol/ext-apps @aotter/mantle-ui
121
+ # Run each versioned pnpm add command printed above first.
121
122
  pnpm exec mantle generate
122
123
  cp .dev.vars.example .dev.vars # set ADMIN_EMAIL and a random BETTER_AUTH_SECRET
123
124
  pnpm exec wrangler dev --local
@@ -66,6 +66,11 @@ Choose in this order, stopping at the first that works:
66
66
  published-only into public Views. Never repeat them.
67
67
  - `scope` hides rows from staff too. When staff must see every row, store
68
68
  `auth.uid()` in a field and filter on it instead.
69
+ - That filter protects the View, not generic Admin Schema reads. Root
70
+ `schema.readOnly: true` blocks generic writes only. If a contributor must
71
+ not read other users' rows, restrict the application-owned Admin surface
72
+ inside its existing `withCaller` boundary; see
73
+ `docs/handbook/guides/admin-ui.md` (Roles). Keep business transitions in SQL.
69
74
 
70
75
  ## Handlers
71
76
 
@@ -112,6 +117,11 @@ Then exercise what changed over HTTP: the REST View or Trigger, `/mcp`
112
117
  sign-in and the Admin route (`docs/handbook/cloudflare/authentication.md`).
113
118
  Check the stored rows, not only a 200. Before deploying a Schema change on
114
119
  SQLite, run `mantle generate --check --database <file>` to see the storage SQL.
120
+ For a workflow, verify valid and invalid state transitions, ownership on every
121
+ mounted surface (including generic Admin reads and writes), concurrent writes
122
+ with one expected version, and persisted data and sessions after a restart.
123
+ Exercise the actual UI when the product includes one; serving HTML alone does
124
+ not verify its forms or operations.
115
125
 
116
126
  ## Rules
117
127
 
@@ -19,7 +19,7 @@ decision. Build it case by case from these pieces.
19
19
  | `SchemaFields` and the primitives | `@aotter/mantle-ui/kit`, styled by `@aotter/mantle-ui/kit.css` | Admin's form controls |
20
20
 
21
21
  The App's own build installs what it imports: `@modelcontextprotocol/ext-apps`
22
- at the exact version `@aotter/mantle` depends on, React and the kit's peers
22
+ at the exact version in the installed Core's `peerDependencies`, React and the kit's peers
23
23
  (`@aotter/mantle-ui`'s README lists them). `docs/handbook/concepts/mcp-and-agents.md`
24
24
  (MCP Apps) describes the catalog and the staff App these share.
25
25
 
@@ -23,7 +23,7 @@ Ask which target, unless the project already shows one.
23
23
  |---|---|---|
24
24
  | Cloudflare Workers | `wrangler.jsonc` | the rest of this skill |
25
25
  | ChatGPT Sites | the Site's hosting config | installed `docs/handbook/cloudflare/chatgpt-sites.md`; publish through Sites, never `wrangler deploy` |
26
- | Mantle Cloud | — | not available for 0.2 services yet; the plugin's `mantle` skill says when it is |
26
+ | Mantle Cloud | Cloud project link | the plugin's `mantle` skill and its Cloud workflow; connect Cloud MCP and use its exact host contract, not `wrangler deploy` |
27
27
  | Bun (host `bun`) | `.env` | self-hosted; installed `docs/handbook/start/project-and-cli.md` (Bun), not this skill |
28
28
 
29
29
  Self-hosting is always an option; never present a hosted target as required.
@@ -58,8 +58,9 @@ Confirm the active Cloudflare account with the user before creating anything.
58
58
  and `bootstrapOwner: { match: "github-login", value }`. The user creates a
59
59
  GitHub OAuth App with callback `<PUBLIC_ORIGIN>/api/auth/callback/github`.
60
60
  Remove the preset's loopback-only check once the method is real.
61
- 4. **Secrets.** Never in chat, files or logs. Use a connector, or hidden
62
- input:
61
+ 4. **Secrets.** Never commit them or put them in chat, logs or `wrangler.jsonc`
62
+ `vars`. Store production values in the deployment's secret store. Use a
63
+ connector, or hidden input:
63
64
 
64
65
  ```bash
65
66
  openssl rand -hex 32 | pnpm exec wrangler secret put BETTER_AUTH_SECRET
@@ -27,6 +27,12 @@ Upgrade deliberately; never overwrite application-owned code.
27
27
  `@aotter/mantle-ui/admin`), and update the lockfile with the
28
28
  package manager. Review the dependency diff and any peer changes
29
29
  `mantle generate` names.
30
+ With identity `mantle`, inspect auth database upgrade requirements in
31
+ `docs/handbook/cloudflare/authentication.md` before starting the new service.
32
+ Better Auth 1.7.0–1.7.2's required legacy `account.issuer` needs a
33
+ data-preserving host-specific constraint migration when moving to 1.7.7;
34
+ fresh-database success alone does not verify an upgrade. Back up the service's
35
+ own database and retain existing users, sessions, roles and content.
30
36
  5. Regenerate and check:
31
37
 
32
38
  ```sh
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aotter/mantle",
3
- "version": "0.2.0-alpha.1",
3
+ "version": "0.2.0-alpha.3",
4
4
  "description": "Mantle: a manifest-driven service core. Store authored as SQL, surfaces as Fetch functions, and the mantle CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://mantle.tools/",
@@ -85,33 +85,35 @@
85
85
  "libpg-query": "18.1.5",
86
86
  "pgsql-deparser": "18.3.8",
87
87
  "yaml": "^2.9.0",
88
- "zod": "^4.5.4"
88
+ "zod": "^4.5.4",
89
+ "semver": "^7.8.5"
89
90
  },
90
91
  "devDependencies": {
91
- "@better-auth/cimd": "1.7.2",
92
- "@better-auth/core": "1.7.2",
93
- "@better-auth/mcp": "1.7.2",
94
- "@better-auth/oauth-provider": "1.7.2",
92
+ "@better-auth/cimd": "1.7.7",
93
+ "@better-auth/core": "1.7.7",
94
+ "@better-auth/mcp": "1.7.7",
95
+ "@better-auth/oauth-provider": "1.7.7",
95
96
  "@better-auth/utils": "0.4.2",
96
97
  "@modelcontextprotocol/client": "2.1.0",
97
98
  "@modelcontextprotocol/ext-apps": "2.0.0",
98
99
  "@modelcontextprotocol/server": "2.1.0",
99
100
  "@types/node": "^26",
100
101
  "@types/pg": "8.23.1",
101
- "better-auth": "1.7.2",
102
+ "better-auth": "1.7.7",
102
103
  "bun-types": "1.3.14",
103
104
  "pg": "8.23.1",
104
105
  "typescript": "^6.0.3",
105
106
  "vitest": "^4.1.11",
106
- "wrangler": "4.126.0"
107
+ "wrangler": "4.126.0",
108
+ "@types/semver": "^7.7.1"
107
109
  },
108
110
  "peerDependencies": {
109
- "@better-auth/cimd": "1.7.2",
110
- "@better-auth/mcp": "1.7.2",
111
- "@better-auth/oauth-provider": "1.7.2",
111
+ "@better-auth/cimd": "1.7.7",
112
+ "@better-auth/mcp": "1.7.7",
113
+ "@better-auth/oauth-provider": "1.7.7",
112
114
  "@modelcontextprotocol/ext-apps": "2.0.0",
113
115
  "@modelcontextprotocol/server": "2.1.0",
114
- "better-auth": "1.7.2"
116
+ "better-auth": "1.7.7"
115
117
  },
116
118
  "peerDependenciesMeta": {
117
119
  "@better-auth/cimd": {