@aotter/mantle 0.1.5-alpha.1 → 0.2.0-alpha.10

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 (495) hide show
  1. package/README.md +166 -298
  2. package/dist/auth/adapter.d.ts +32 -0
  3. package/dist/auth/adapter.js +288 -0
  4. package/dist/auth/admin.d.ts +3 -0
  5. package/dist/auth/admin.js +21 -0
  6. package/dist/auth/caller.d.ts +86 -0
  7. package/dist/auth/caller.js +155 -0
  8. package/dist/auth/hardening.d.ts +24 -0
  9. package/dist/auth/hardening.js +40 -0
  10. package/dist/auth/identity.d.ts +121 -0
  11. package/dist/auth/identity.js +203 -0
  12. package/dist/auth/oauth.d.ts +63 -0
  13. package/dist/auth/oauth.js +217 -0
  14. package/dist/auth/resource-seeds.js +10 -0
  15. package/dist/auth/routes.d.ts +32 -0
  16. package/dist/auth/routes.js +226 -0
  17. package/dist/build/compiler.d.ts +721 -0
  18. package/dist/build/compiler.js +7029 -0
  19. package/dist/build/spec/domain/model/ManifestGrammar.d.ts +439 -0
  20. package/dist/build/spec/domain/model/RuntimePlan.d.ts +168 -0
  21. package/dist/build/spec/domain/model/SqlIr.d.ts +129 -0
  22. package/dist/build/spec/domain/model/UserFields.d.ts +21 -0
  23. package/dist/build/spec/domain/service/PlanFingerprint.d.ts +7 -0
  24. package/dist/build/spec/kernel/diagnostic.d.ts +108 -0
  25. package/dist/bun/assets.d.ts +2 -0
  26. package/dist/bun/assets.js +24 -0
  27. package/dist/domain/adminOrder.js +24 -0
  28. package/dist/domain/columns.js +57 -0
  29. package/dist/domain/describe/chunk-ZVXBGP7Y.js +424 -0
  30. package/dist/domain/describe/index.d.ts +58 -0
  31. package/dist/domain/describe/index.js +115 -0
  32. package/dist/domain/describe/target-hash.d.ts +8 -0
  33. package/dist/domain/describe/target-hash.js +12 -0
  34. package/dist/domain/describe/targets.d.ts +40 -0
  35. package/dist/domain/entry.js +33 -0
  36. package/dist/domain/generated-tools.js +19 -0
  37. package/dist/domain/generation.d.ts +16 -0
  38. package/dist/domain/generation.js +1 -0
  39. package/dist/domain/input.js +17 -0
  40. package/dist/domain/model.js +1 -0
  41. package/dist/domain/paging.js +230 -0
  42. package/dist/domain/recipes.js +24 -0
  43. package/dist/domain/userReferences.js +5 -0
  44. package/dist/domain/wireScalars.js +47 -0
  45. package/dist/infrastructure/adminEntries.js +153 -0
  46. package/dist/infrastructure/adminPaging.js +72 -0
  47. package/dist/infrastructure/artifacts.js +71 -0
  48. package/dist/infrastructure/authSchema.js +83 -0
  49. package/dist/infrastructure/bundle.js +188 -0
  50. package/dist/infrastructure/cli.js +118 -0
  51. package/dist/infrastructure/generate.d.ts +12 -0
  52. package/dist/infrastructure/generate.js +189 -0
  53. package/dist/infrastructure/generateAuth.d.ts +8 -0
  54. package/dist/infrastructure/generateAuth.js +74 -0
  55. package/dist/infrastructure/help.js +60 -0
  56. package/dist/infrastructure/init.js +54 -0
  57. package/dist/infrastructure/inputs.js +41 -0
  58. package/dist/infrastructure/mounts.js +25 -0
  59. package/dist/infrastructure/wrangler.js +109 -0
  60. package/dist/kernel/authorization.d.ts +4 -0
  61. package/dist/kernel/authorization.js +16 -0
  62. package/dist/kernel/binds.d.ts +60 -0
  63. package/dist/kernel/binds.js +49 -0
  64. package/dist/kernel/caller-identity.d.ts +24 -0
  65. package/dist/kernel/caller-identity.js +22 -0
  66. package/dist/kernel/contract-error.d.ts +12 -0
  67. package/dist/kernel/contract-error.js +14 -0
  68. package/dist/kernel/contracts.d.ts +143 -0
  69. package/dist/kernel/contracts.js +97 -0
  70. package/dist/kernel/cron.d.ts +1 -0
  71. package/dist/kernel/cron.js +58 -0
  72. package/dist/kernel/dialect/common.d.ts +38 -0
  73. package/dist/kernel/dialect/common.js +6 -0
  74. package/dist/kernel/dialect/postgres.d.ts +16 -0
  75. package/dist/kernel/dialect/postgres.js +18 -0
  76. package/dist/kernel/dialect/sqlite.d.ts +20 -0
  77. package/dist/kernel/dialect/sqlite.js +34 -0
  78. package/dist/kernel/drain.d.ts +5 -0
  79. package/dist/kernel/drain.js +35 -0
  80. package/dist/kernel/driver/bun-sqlite.d.ts +19 -0
  81. package/dist/kernel/driver/bun-sqlite.js +77 -0
  82. package/dist/kernel/driver/d1.d.ts +24 -0
  83. package/dist/kernel/driver/d1.js +22 -0
  84. package/dist/kernel/driver/postgres.d.ts +25 -0
  85. package/dist/kernel/driver/postgres.js +99 -0
  86. package/dist/kernel/driver/transaction-context.d.ts +10 -0
  87. package/dist/kernel/driver/transaction-context.js +29 -0
  88. package/dist/kernel/email.d.ts +28 -0
  89. package/dist/kernel/email.js +38 -0
  90. package/dist/kernel/host-access.d.ts +10 -0
  91. package/dist/kernel/host-access.js +65 -0
  92. package/dist/kernel/host-caller.d.ts +39 -0
  93. package/dist/kernel/host-caller.js +56 -0
  94. package/dist/kernel/json-body.d.ts +4 -0
  95. package/dist/kernel/json-body.js +44 -0
  96. package/dist/kernel/mcp-apps.d.ts +69 -0
  97. package/dist/kernel/mcp-apps.js +40 -0
  98. package/dist/kernel/mcp-call.d.ts +5 -0
  99. package/dist/kernel/mcp-call.js +25 -0
  100. package/dist/kernel/mcp-handler.js +371 -0
  101. package/dist/kernel/mcp.d.ts +110 -0
  102. package/dist/kernel/mcp.js +50 -0
  103. package/dist/kernel/media.d.ts +27 -0
  104. package/dist/kernel/media.js +216 -0
  105. package/dist/kernel/paging.d.ts +55 -0
  106. package/dist/kernel/paging.js +95 -0
  107. package/dist/kernel/publishing.d.ts +4 -0
  108. package/dist/kernel/publishing.js +16 -0
  109. package/dist/kernel/r2-media.d.ts +42 -0
  110. package/dist/kernel/r2-media.js +78 -0
  111. package/dist/kernel/rest-dispatch.js +38 -0
  112. package/dist/kernel/rest-router.js +64 -0
  113. package/dist/kernel/rest.d.ts +58 -0
  114. package/dist/kernel/rest.js +41 -0
  115. package/dist/kernel/scalars.d.ts +6 -0
  116. package/dist/kernel/scalars.js +44 -0
  117. package/dist/kernel/search.js +5 -0
  118. package/dist/kernel/site-types.d.ts +146 -0
  119. package/dist/kernel/site-types.js +1 -0
  120. package/dist/kernel/site.d.ts +39 -0
  121. package/dist/kernel/site.js +32 -0
  122. package/dist/kernel/store.d.ts +175 -0
  123. package/dist/kernel/store.js +269 -0
  124. package/dist/kernel/surface.d.ts +10 -0
  125. package/dist/kernel/surface.js +19 -0
  126. package/dist/kernel/sweep.d.ts +17 -0
  127. package/dist/kernel/sweep.js +27 -0
  128. package/dist/kernel/translations.d.ts +1 -0
  129. package/dist/kernel/translations.js +13 -0
  130. package/dist/kernel/user-caller.d.ts +31 -0
  131. package/dist/kernel/user-caller.js +67 -0
  132. package/dist/kernel/user-field-name.js +3 -0
  133. package/dist/kernel/user-fields.d.ts +35 -0
  134. package/dist/kernel/user-fields.js +81 -0
  135. package/dist/kernel/validation.d.ts +15 -0
  136. package/dist/kernel/validation.js +54 -0
  137. package/dist/kernel/web-gateway.js +68 -0
  138. package/dist/kernel/web-head.js +15 -0
  139. package/dist/kernel/web-index.js +40 -0
  140. package/dist/kernel/web-response.js +8 -0
  141. package/dist/kernel/web.d.ts +77 -0
  142. package/dist/kernel/web.js +124 -0
  143. package/dist/kernel/where.js +27 -0
  144. package/dist/testing/index.d.ts +1 -0
  145. package/dist/testing/index.js +1 -0
  146. package/dist/usecase/adminBindings.js +143 -0
  147. package/dist/usecase/adminClient.js +87 -0
  148. package/dist/usecase/adminDescriptors.js +107 -0
  149. package/dist/usecase/generate.js +166 -0
  150. package/dist/usecase/handlers.js +21 -0
  151. package/dist/usecase/mcp-media.js +17 -0
  152. package/dist/usecase/mcp.js +100 -0
  153. package/dist/usecase/paging.js +71 -0
  154. package/dist/usecase/publishing.js +63 -0
  155. package/dist/usecase/refusals.js +27 -0
  156. package/dist/usecase/rest.js +54 -0
  157. package/dist/usecase/search.js +7 -0
  158. package/dist/usecase/site.js +39 -0
  159. package/dist/usecase/stores.js +64 -0
  160. package/dist/usecase/tool-version.js +9 -0
  161. package/dist/usecase/translations.js +78 -0
  162. package/dist/usecase/ttl.js +20 -0
  163. package/dist/usecase/validation.js +241 -0
  164. package/dist/usecase/web.js +23 -0
  165. package/docs/examples/README.md +39 -18
  166. package/docs/examples/app-shell/.dev.vars.example +4 -0
  167. package/docs/examples/app-shell/README.md +121 -0
  168. package/docs/examples/app-shell/accept.mjs +91 -0
  169. package/docs/examples/app-shell/gitignore +7 -0
  170. package/docs/examples/app-shell/package.json +43 -0
  171. package/docs/examples/app-shell/pnpm-workspace.yaml +8 -0
  172. package/docs/examples/app-shell/smoke-kit.mjs +168 -0
  173. package/docs/examples/app-shell/src/auth.ts +58 -0
  174. package/docs/examples/app-shell/src/handlers.ts +5 -0
  175. package/docs/examples/app-shell/src/index.ts +4 -0
  176. package/docs/examples/app-shell/src/routes.ts +14 -0
  177. package/docs/examples/app-shell/src/schedule.ts +8 -0
  178. package/docs/examples/app-shell/src/service.ts +70 -0
  179. package/docs/examples/app-shell/src/sign-in.ts +41 -0
  180. package/docs/examples/app-shell/src/signup.ts +8 -0
  181. package/docs/examples/app-shell/tsconfig.json +26 -0
  182. package/docs/examples/app-shell/worker-configuration.d.ts +22 -0
  183. package/docs/examples/app-shell/wrangler.jsonc +62 -0
  184. package/docs/examples/club-events/README.md +65 -0
  185. package/docs/examples/club-events/manifests/club-events.yaml +247 -0
  186. package/docs/examples/club-events/smoke.mjs +127 -0
  187. package/docs/examples/club-events/src/handlers.ts +25 -0
  188. package/docs/examples/club-events/src/signup.ts +9 -0
  189. package/docs/examples/generated-web/README.md +78 -0
  190. package/docs/examples/generated-web/posts.yaml +38 -0
  191. package/docs/examples/generated-web/seed.sql +6 -0
  192. package/docs/examples/generated-web/src/worker.ts +43 -0
  193. package/docs/examples/generated-web/tsconfig.json +8 -0
  194. package/docs/examples/generated-web/wrangler.jsonc +8 -0
  195. package/docs/examples/purchasing/.dev.vars.example +6 -0
  196. package/docs/examples/purchasing/README.md +99 -0
  197. package/docs/examples/purchasing/manifests/purchasing.yaml +374 -0
  198. package/docs/examples/purchasing/seed.sql +4 -0
  199. package/docs/examples/purchasing/smoke.mjs +191 -0
  200. package/docs/examples/purchasing/src/admin.tsx +13 -0
  201. package/docs/examples/purchasing/src/handlers.ts +33 -0
  202. package/docs/examples/purchasing/src/notify.ts +29 -0
  203. package/docs/examples/purchasing/src/requests-page.ts +57 -0
  204. package/docs/examples/purchasing/src/routes.ts +53 -0
  205. package/docs/examples/purchasing/src/schedule.ts +31 -0
  206. package/docs/examples/purchasing/src/signup.ts +5 -0
  207. package/docs/examples/purchasing/src/supplier.ts +32 -0
  208. package/docs/examples/purchasing/wrangler.jsonc +69 -0
  209. package/docs/examples/recipes/README.md +60 -0
  210. package/docs/examples/recipes/manifests/recipes.yaml +75 -0
  211. package/docs/examples/recipes/smoke.mjs +102 -0
  212. package/docs/examples/recipes/src/media.ts +13 -0
  213. package/docs/examples/recipes/src/pages.ts +99 -0
  214. package/docs/examples/recipes/src/routes.ts +71 -0
  215. package/docs/examples/recipes/src/site-config.ts +12 -0
  216. package/docs/examples/recipes/wrangler.jsonc +74 -0
  217. package/docs/handbook/cloudflare/authentication.md +180 -322
  218. package/docs/handbook/cloudflare/chatgpt-sites.md +5 -23
  219. package/docs/handbook/cloudflare/deploy-and-operate.md +55 -77
  220. package/docs/handbook/cloudflare/media-r2.md +61 -205
  221. package/docs/handbook/cloudflare/service-entry.md +239 -0
  222. package/docs/handbook/concepts/authorization.md +10 -107
  223. package/docs/handbook/concepts/four-atoms.md +9 -168
  224. package/docs/handbook/concepts/lifecycle-and-locales.md +9 -132
  225. package/docs/handbook/concepts/mcp-and-agents.md +24 -121
  226. package/docs/handbook/concepts/procedures-and-triggers.md +5 -217
  227. package/docs/handbook/concepts/runtime-and-adapters.md +15 -169
  228. package/docs/handbook/concepts/views.md +10 -185
  229. package/docs/handbook/examples/hub.md +3 -8
  230. package/docs/handbook/guides/adapter-guide.md +11 -0
  231. package/docs/handbook/guides/admin-ui.md +115 -133
  232. package/docs/handbook/guides/agent-setup.md +64 -74
  233. package/docs/handbook/guides/agent-workflow-acceptance.md +232 -0
  234. package/docs/handbook/guides/email.md +86 -0
  235. package/docs/handbook/guides/local-testing.md +40 -0
  236. package/docs/handbook/guides/mcp-apps.md +560 -0
  237. package/docs/handbook/guides/native-admin.md +566 -0
  238. package/docs/handbook/guides/native-execution.md +133 -0
  239. package/docs/handbook/guides/oauth.md +219 -0
  240. package/docs/handbook/guides/postgres-adapters.md +56 -0
  241. package/docs/handbook/guides/public-pages.md +87 -0
  242. package/docs/handbook/guides/size-and-startup.md +9 -0
  243. package/docs/handbook/guides/sql-patterns.md +266 -0
  244. package/docs/handbook/guides/typed-queries.md +211 -107
  245. package/docs/handbook/navigation.json +176 -55
  246. package/docs/handbook/reference/admin-descriptors.md +53 -0
  247. package/docs/handbook/reference/authorization.md +39 -266
  248. package/docs/handbook/reference/describe.md +56 -0
  249. package/docs/handbook/reference/diagnostics.md +19 -186
  250. package/docs/handbook/reference/features.md +45 -55
  251. package/docs/handbook/reference/manifest.md +6 -129
  252. package/docs/handbook/reference/procedure.md +11 -303
  253. package/docs/handbook/reference/schema.md +64 -240
  254. package/docs/handbook/reference/site-config.md +5 -218
  255. package/docs/handbook/reference/sql.md +64 -0
  256. package/docs/handbook/reference/surface.md +392 -201
  257. package/docs/handbook/reference/trigger.md +56 -192
  258. package/docs/handbook/reference/view.md +24 -243
  259. package/docs/handbook/releases/index.md +244 -77
  260. package/docs/handbook/start/agents.md +45 -0
  261. package/docs/handbook/start/overview.md +9 -48
  262. package/docs/handbook/start/project-and-cli.md +51 -104
  263. package/docs/handbook/start/quickstart-worker.md +43 -178
  264. package/docs/skills/develop/SKILL.md +79 -249
  265. package/docs/skills/integrate/SKILL.md +39 -50
  266. package/docs/skills/media-gc/SKILL.md +34 -10
  267. package/docs/skills/provision/SKILL.md +36 -186
  268. package/docs/skills/update/SKILL.md +23 -38
  269. package/docs/source/scripts/postgres-adapter.ts +101 -0
  270. package/docs/upgrade-0.1-to-0.2.md +227 -0
  271. package/package.json +120 -131
  272. package/skills/README.md +48 -108
  273. package/skills/mantle/SKILL.md +76 -0
  274. package/dist/admin-ui.d.ts +0 -2
  275. package/dist/admin-ui.d.ts.map +0 -1
  276. package/dist/admin-ui.js +0 -2
  277. package/dist/admin-ui.js.map +0 -1
  278. package/dist/admin.d.ts +0 -2
  279. package/dist/admin.d.ts.map +0 -1
  280. package/dist/admin.js +0 -2
  281. package/dist/admin.js.map +0 -1
  282. package/dist/auth.d.ts +0 -2
  283. package/dist/auth.d.ts.map +0 -1
  284. package/dist/auth.js +0 -2
  285. package/dist/auth.js.map +0 -1
  286. package/dist/bun.d.ts +0 -2
  287. package/dist/bun.d.ts.map +0 -1
  288. package/dist/bun.js +0 -2
  289. package/dist/bun.js.map +0 -1
  290. package/dist/cli/generate-cloudflare.d.ts +0 -7
  291. package/dist/cli/generate-cloudflare.d.ts.map +0 -1
  292. package/dist/cli/generate-cloudflare.js +0 -123
  293. package/dist/cli/generate-cloudflare.js.map +0 -1
  294. package/dist/cli/generate-project.d.ts +0 -25
  295. package/dist/cli/generate-project.d.ts.map +0 -1
  296. package/dist/cli/generate-project.js +0 -282
  297. package/dist/cli/generate-project.js.map +0 -1
  298. package/dist/cli/generate-sites.d.ts +0 -8
  299. package/dist/cli/generate-sites.d.ts.map +0 -1
  300. package/dist/cli/generate-sites.js +0 -273
  301. package/dist/cli/generate-sites.js.map +0 -1
  302. package/dist/cli/generate.d.ts +0 -20
  303. package/dist/cli/generate.d.ts.map +0 -1
  304. package/dist/cli/generate.js +0 -299
  305. package/dist/cli/generate.js.map +0 -1
  306. package/dist/cli/harness.d.ts +0 -3
  307. package/dist/cli/harness.d.ts.map +0 -1
  308. package/dist/cli/harness.js +0 -214
  309. package/dist/cli/harness.js.map +0 -1
  310. package/dist/cli/main.d.ts +0 -3
  311. package/dist/cli/main.d.ts.map +0 -1
  312. package/dist/cli/main.js +0 -77
  313. package/dist/cli/main.js.map +0 -1
  314. package/dist/cli/skills.d.ts +0 -3
  315. package/dist/cli/skills.d.ts.map +0 -1
  316. package/dist/cli/skills.js +0 -128
  317. package/dist/cli/skills.js.map +0 -1
  318. package/dist/cloudflare.d.ts +0 -2
  319. package/dist/cloudflare.d.ts.map +0 -1
  320. package/dist/cloudflare.js +0 -2
  321. package/dist/cloudflare.js.map +0 -1
  322. package/dist/codegen/emitMantleModule.d.ts +0 -22
  323. package/dist/codegen/emitMantleModule.d.ts.map +0 -1
  324. package/dist/codegen/emitMantleModule.js +0 -291
  325. package/dist/codegen/emitMantleModule.js.map +0 -1
  326. package/dist/codegen.d.ts +0 -2
  327. package/dist/codegen.d.ts.map +0 -1
  328. package/dist/codegen.js +0 -2
  329. package/dist/codegen.js.map +0 -1
  330. package/dist/runtime-testing.d.ts +0 -2
  331. package/dist/runtime-testing.d.ts.map +0 -1
  332. package/dist/runtime-testing.js +0 -2
  333. package/dist/runtime-testing.js.map +0 -1
  334. package/dist/runtime.d.ts +0 -2
  335. package/dist/runtime.d.ts.map +0 -1
  336. package/dist/runtime.js +0 -2
  337. package/dist/runtime.js.map +0 -1
  338. package/dist/spec.d.ts +0 -2
  339. package/dist/spec.d.ts.map +0 -1
  340. package/dist/spec.js +0 -2
  341. package/dist/spec.js.map +0 -1
  342. package/dist/vercel-libsql.d.ts +0 -2
  343. package/dist/vercel-libsql.d.ts.map +0 -1
  344. package/dist/vercel-libsql.js +0 -2
  345. package/dist/vercel-libsql.js.map +0 -1
  346. package/dist/vercel.d.ts +0 -2
  347. package/dist/vercel.d.ts.map +0 -1
  348. package/dist/vercel.js +0 -2
  349. package/dist/vercel.js.map +0 -1
  350. package/dist/web.d.ts +0 -2
  351. package/dist/web.d.ts.map +0 -1
  352. package/dist/web.js +0 -2
  353. package/dist/web.js.map +0 -1
  354. package/docs/adapter-guide.md +0 -222
  355. package/docs/adr/0001-four-atom-manifest-model.md +0 -440
  356. package/docs/adr/0002-closed-enums-for-bindings.md +0 -253
  357. package/docs/adr/0007-ai-as-primary-author.md +0 -473
  358. package/docs/adr/0008-structured-diagnostic-shape.md +0 -338
  359. package/docs/adr/0009-consumer-supplied-manifests.md +0 -149
  360. package/docs/adr/0010-locale-and-translates.md +0 -453
  361. package/docs/adr/0011-adapter-port-spec.md +0 -281
  362. package/docs/adr/0012-views-as-public-rest.md +0 -161
  363. package/docs/adr/0013-agent-provisioned-consumer-projects.md +0 -154
  364. package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +0 -900
  365. package/docs/adr/0016-site-semantic-layer.md +0 -70
  366. package/docs/adr/0017-media-multi-variant-agent-side-optimization.md +0 -181
  367. package/docs/adr/0018-core-starters-repository-boundary.md +0 -179
  368. package/docs/adr/0019-sealed-manifest-runtime-pipeline.md +0 -240
  369. package/docs/adr/0020-builtin-handler-contracts-and-matched-upsert.md +0 -199
  370. package/docs/adr/0021-retire-starter-scaffolding.md +0 -30
  371. package/docs/adr/0022-caller-observed-version-occ.md +0 -212
  372. package/docs/adr/0023-port-failure-contract.md +0 -69
  373. package/docs/adr/0024-manifest-native-schema-tables.md +0 -264
  374. package/docs/adr/0025-reserved-native-columns-and-published-only-public-views.md +0 -105
  375. package/docs/adr/0026-progressive-generate.md +0 -36
  376. package/docs/adr/0027-scheduled-procedure-triggers.md +0 -31
  377. package/docs/adr/0028-schema-ttl-expiry.md +0 -33
  378. package/docs/adr/README.md +0 -81
  379. package/docs/adr/adr-lite-803-request-diagnostics.md +0 -38
  380. package/docs/adr/adr-lite-808-route-readiness.md +0 -47
  381. package/docs/adr/adr-lite-809-bounded-public-content.md +0 -71
  382. package/docs/adr/adr-lite-812-native-parity.md +0 -150
  383. package/docs/adr/adr-lite-823-home-statistics.md +0 -64
  384. package/docs/adr/adr-lite-842-mcp-authoring.md +0 -30
  385. package/docs/adr/adr-lite-845-frontend-client.md +0 -38
  386. package/docs/adr/adr-lite-861-admin-webmcp.md +0 -42
  387. package/docs/adr/adr-lite-909-admin-ui-kit.md +0 -37
  388. package/docs/agent-prompts.md +0 -64
  389. package/docs/api-mcp-authorization.md +0 -5
  390. package/docs/assets/mantle-admin-developer.png +0 -0
  391. package/docs/assets/mantle-admin-operations.png +0 -0
  392. package/docs/assets/mantle-hero.jpg +0 -0
  393. package/docs/auth-hosting-model.md +0 -166
  394. package/docs/cloudflare-low-level-composition.md +0 -5
  395. package/docs/consumer-onboarding-audit.md +0 -65
  396. package/docs/deferred-lifecycle-queues.md +0 -5
  397. package/docs/design-atoms.md +0 -16
  398. package/docs/design-references/editorial-blog-2026-05-05.md +0 -218
  399. package/docs/direct-authoring.md +0 -5
  400. package/docs/examples/builtin-commerce.md +0 -269
  401. package/docs/examples/builtin-intake.md +0 -143
  402. package/docs/examples/builtin-legal-documents.md +0 -189
  403. package/docs/examples/builtin-procurement.md +0 -241
  404. package/docs/examples/builtin-publication.md +0 -241
  405. package/docs/examples/builtin-reservation.md +0 -149
  406. package/docs/examples/cf-primitives-commerce-inventory.md +0 -809
  407. package/docs/examples/cf-primitives-guarded-api.md +0 -429
  408. package/docs/examples/cf-primitives-intake-hooks.md +0 -319
  409. package/docs/examples/host-chatgpt-sites/.openai/hosting.json +0 -1
  410. package/docs/examples/host-chatgpt-sites/README.md +0 -70
  411. package/docs/examples/host-chatgpt-sites/drizzle/0000_sites_users.sql +0 -8
  412. package/docs/examples/host-chatgpt-sites/drizzle/0001_mantle.sql +0 -294
  413. package/docs/examples/host-chatgpt-sites/drizzle/0002_article_cover.sql +0 -5
  414. package/docs/examples/host-chatgpt-sites/drizzle/0003_store_instance_id.sql +0 -1
  415. package/docs/examples/host-chatgpt-sites/drizzle/0004_mantle.sql +0 -10
  416. package/docs/examples/host-chatgpt-sites/drizzle/0005_mantle.sql +0 -24
  417. package/docs/examples/host-chatgpt-sites/drizzle/meta/_journal.json +0 -1
  418. package/docs/examples/host-chatgpt-sites/drizzle/meta/mantle-state.json +0 -60
  419. package/docs/examples/host-chatgpt-sites/manifests/site.yaml +0 -27
  420. package/docs/examples/host-chatgpt-sites/package.json +0 -1
  421. package/docs/examples/host-chatgpt-sites/public/site.css +0 -1
  422. package/docs/examples/host-chatgpt-sites/scripts/build.mjs +0 -13
  423. package/docs/examples/host-chatgpt-sites/scripts/check.mjs +0 -101
  424. package/docs/examples/host-chatgpt-sites/scripts/migration.mjs +0 -43
  425. package/docs/examples/host-chatgpt-sites/src/chatgpt-auth.ts +0 -66
  426. package/docs/examples/host-chatgpt-sites/src/index.ts +0 -46
  427. package/docs/examples/host-chatgpt-sites/src/mcp.ts +0 -49
  428. package/docs/examples/host-chatgpt-sites/src/media.ts +0 -109
  429. package/docs/examples/host-chatgpt-sites/src/r2-lab.ts +0 -38
  430. package/docs/examples/host-chatgpt-sites/src/storage-fingerprint.json +0 -1
  431. package/docs/examples/host-chatgpt-sites/src/web.ts +0 -35
  432. package/docs/examples/host-chatgpt-sites/tsconfig.json +0 -1
  433. package/docs/examples/host-chatgpt-sites/wrangler.jsonc +0 -10
  434. package/docs/examples/host-local-admin-otp/.dev.vars.example +0 -3
  435. package/docs/examples/host-local-admin-otp/README.md +0 -80
  436. package/docs/examples/host-local-admin-otp/ensure-dev-vars.mjs +0 -5
  437. package/docs/examples/host-local-admin-otp/manifests/site.yaml +0 -25
  438. package/docs/examples/host-local-admin-otp/package.json +0 -29
  439. package/docs/examples/host-local-admin-otp/public/.gitkeep +0 -1
  440. package/docs/examples/host-local-admin-otp/smoke.mjs +0 -141
  441. package/docs/examples/host-local-admin-otp/src/index.ts +0 -54
  442. package/docs/examples/host-local-admin-otp/tsconfig.json +0 -17
  443. package/docs/examples/host-local-admin-otp/wrangler.jsonc +0 -23
  444. package/docs/examples/host-minimal-worker/README.md +0 -41
  445. package/docs/examples/host-minimal-worker/manifests/site.yaml +0 -25
  446. package/docs/examples/host-minimal-worker/package.json +0 -26
  447. package/docs/examples/host-minimal-worker/smoke.mjs +0 -23
  448. package/docs/examples/host-minimal-worker/src/index.ts +0 -4
  449. package/docs/examples/host-minimal-worker/tsconfig.json +0 -17
  450. package/docs/examples/host-minimal-worker/wrangler.jsonc +0 -22
  451. package/docs/handbook/chatgpt-sites/equipment-checkout.md +0 -231
  452. package/docs/handbook/chatgpt-sites/generated-app.md +0 -84
  453. package/docs/handbook/chatgpt-sites/host-reference.md +0 -113
  454. package/docs/handbook/chatgpt-sites/index.md +0 -133
  455. package/docs/handbook/cloudflare/bindings.md +0 -291
  456. package/docs/handbook/cloudflare/conventional-worker.md +0 -166
  457. package/docs/handbook/cloudflare/deferred-hooks-queues.md +0 -170
  458. package/docs/handbook/cloudflare/low-level-composition.md +0 -169
  459. package/docs/handbook/cloudflare/public-web.md +0 -144
  460. package/docs/handbook/cloudflare/site-chrome.md +0 -75
  461. package/docs/handbook/examples/commerce-transaction.md +0 -11
  462. package/docs/handbook/examples/commerce.md +0 -11
  463. package/docs/handbook/examples/guarded-api.md +0 -10
  464. package/docs/handbook/examples/intake-form.md +0 -12
  465. package/docs/handbook/examples/intake-hooks.md +0 -11
  466. package/docs/handbook/examples/legal-documents.md +0 -10
  467. package/docs/handbook/examples/procurement-approvals.md +0 -10
  468. package/docs/handbook/examples/publication.md +0 -10
  469. package/docs/handbook/examples/reservation.md +0 -10
  470. package/docs/handbook/start/quickstart-admin.md +0 -52
  471. package/docs/labels.md +0 -71
  472. package/docs/media-uploads.md +0 -5
  473. package/docs/migration-0.1.2.md +0 -13
  474. package/docs/performance-harness.md +0 -228
  475. package/docs/release-process.md +0 -244
  476. package/docs/schema-indexes.md +0 -5
  477. package/docs/sealed-pipeline-ownership.md +0 -101
  478. package/docs/skills/plugin/SKILL.md +0 -132
  479. package/docs/skills/theme/SKILL.md +0 -77
  480. package/docs/spec-only-host-adoption.md +0 -157
  481. package/docs/transaction-patterns.md +0 -5
  482. package/skills/develop/SKILL.md +0 -258
  483. package/skills/install/SKILL.md +0 -112
  484. package/skills/integrate/SKILL.md +0 -60
  485. package/skills/media-gc/SKILL.md +0 -85
  486. package/skills/plugin/SKILL.md +0 -132
  487. package/skills/provision/SKILL.md +0 -194
  488. package/skills/theme/SKILL.md +0 -77
  489. package/skills/update/SKILL.md +0 -51
  490. package/templates/chatgpt-sites/auth.ts +0 -66
  491. package/templates/chatgpt-sites/build.mjs +0 -14
  492. package/templates/chatgpt-sites/env.ts +0 -1
  493. package/templates/chatgpt-sites/mcp.ts +0 -48
  494. package/templates/chatgpt-sites/public-mcp.ts +0 -29
  495. package/templates/chatgpt-sites/smoke-local.mjs +0 -20
@@ -1,355 +1,213 @@
1
- ---
2
- description: Configure MANTLE_AUTH_MODE, secrets, the first owner and staff roles; understand which routes need a session.
3
- ---
4
- # Authentication
1
+ # Auth in Mantle
2
+
3
+ Your application configures `betterAuth()` and its official plugins in `auth.ts`.
4
+ Mantle supplies the database adapter, schema generation and caller resolution:
5
+
6
+ | Mantle | What it does |
7
+ | --- | --- |
8
+ | `@aotter/mantle/auth/adapter` | `d1Adapter(env.DB)` / `postgresAdapter(sql)`: Better Auth's `database` on the same native handle as your data, no Kysely. `postgresAdapter` takes the PostgreSQL client the data layer uses, postgres.js (`cloudflare-postgres`, usually through Hyperdrive) or Bun.SQL; on Workers create that client per request, or per isolate with `max: 1`. Both pass Better Auth's official adapter suite (`@better-auth/test-utils/adapter`). |
9
+ | `mantle generate … --auth ./auth.ts` | Adds the tables Better Auth declares for your options (`getSchema`) to `schema.sql`. |
10
+ | `@aotter/mantle/generate/auth` | Programmatic `generateFromSources({ target, sources, auth: authOptions, ... })`; the selected entry types `auth` as Better Auth options. |
11
+ | `@aotter/mantle/auth` | `authLoader` and `resolveCaller(request, loader, { audience })`: the `Caller` that generated queries take. |
12
+
13
+ ## `auth.ts`
5
14
 
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.
15
+ > **Verification status:** the GitHub social-provider and magic-link recipes have not yet been exercised end to end against real deployed provider flows.
16
+
17
+ Copy `src/auth.ts` from the [app shell](../../examples/app-shell/README.md), which CI runs from the packed packages;
18
+ installed, it is `node_modules/@aotter/mantle/docs/examples/app-shell/src/auth.ts`. Its essentials:
7
19
 
8
- ## Local Admin: email OTP
20
+ - Imports: `betterAuth` from `better-auth/minimal`; `admin`, `emailOTP` (and `jwt` for MCP) from `better-auth/plugins`;
21
+ `createAccessControl` from `better-auth/plugins/access`; `defaultStatements` from `better-auth/plugins/admin/access`;
22
+ `d1Adapter` from `@aotter/mantle/auth/adapter`; the hardening and bootstrap helpers below. For MCP it also imports `jwt`,
23
+ `mcp` from `@better-auth/mcp`, `cimd` from `@better-auth/cimd` and `oauthGrantExtension`/`oauthGrantRevocation` from
24
+ `@aotter/mantle/auth/oauth`; the file marks that block as deletable.
25
+ - `authOptions(env)` returns plain options and is the module's export for `--auth src/auth.ts`. Generation calls it with an
26
+ empty `env`, so it must not throw: fall back to placeholders for `secret` and origin there.
27
+ - The bootstrap admin: `d1BootstrapAdmin(env.DB, { match: "email", value: env.ADMIN_EMAIL }, () => ({ options: { plugins } }))`
28
+ is assigned to `databaseHooks.user.create.after` and `.update.after` ([Identity helpers](#identity-helpers)).
29
+ - `createAuth(env)` is the same module's second export: it checks its three variables are set and returns
30
+ `betterAuth(authOptions(env))`. The Worker's `authLoader(() => import("./auth.js").then(m => m.createAuth(env)))`
31
+ ([service composition](service-entry.md)) is what calls it.
9
32
 
10
- The generated CF app writes `.dev.vars.example`. Copy it to `.dev.vars` for
11
- local use, then set `MANTLE_AUTH_MODE=local-otp`, the real `ADMIN_EMAIL`, a
12
- random `BETTER_AUTH_SECRET`, and the exact loopback `PUBLIC_ORIGIN`. It configures
13
- its email-OTP auth factory only for a
14
- loopback origin. If you author the Worker yourself, pass `auth` to
15
- `createMantleWorker` with `email-otp`, `ConsoleEmailSender`, and
16
- `bootstrapOwner.match: "email"`; that factory does not read the conventional
17
- mode matrix. The one-time code is printed in wrangler logs. See
18
- [Quickstart: local Admin](../start/quickstart-admin.md). `ConsoleEmailSender`
19
- is for `wrangler dev` only; production needs a real sender. Match
20
- `PUBLIC_ORIGIN` to wrangler's actual `127.0.0.1:8787` origin or Better Auth
21
- will reject OTP with `INVALID_ORIGIN`.
33
+ The application owns Better Auth and its optional plugins. Schema generation reads plain options without constructing
34
+ Better Auth; apply `schema.sql` during deployment, never at boot. OAuth resource
35
+ rows are created on first OAuth use from runtime configuration; they require no
36
+ resource deployment step.
22
37
 
23
- For production, the generated Worker's local OTP branch is disabled outside
24
- loopback. Configure the conventional `self-managed` or `hosted` Auth mode,
25
- including its required provider credentials and `ADMIN_GITHUB_LOGIN`, instead
26
- of editing the CLI-owned Worker. An application that needs production email
27
- delivery should author its own Worker Auth factory and real `EmailSender`.
38
+ OSS uses `admin({adminRoles: ["admin"], ac, roles: {admin: ac.newRole(defaultStatements), user: ac.newRole({user: [], session: []})}})`.
39
+ Mantle reads the official admin plugin’s `adminRoles` when auth loads (default `["admin"]`); it has no role setting of its own. `Caller.role` preserves comma-separated roles and `Caller.admin` carries the layer. A caller holding any configured admin role is an admin; a member’s role names never grant privilege. Publishing, Admin and `/mcp/admin` read this layer. Better Auth still checks permissions for its identity API calls.
28
40
 
29
- With `auth` set, the mode matrix below is not read. Core still owns `/admin` and `/api/auth/*`.
41
+ `@aotter/mantle/auth/hardening` supplies optional helpers:
30
42
 
31
- For production email OTP in a manually authored Worker, keep the custom `createAuth()` factory, replace
32
- `ConsoleEmailSender` with the application's production `EmailSender`, and keep
33
- `bootstrapOwner: { match: "email", value: <owner email> }`. Store the sender
34
- credentials and `BETTER_AUTH_SECRET` as Worker secrets. If the application has
35
- no transactional-email provider, use self-managed GitHub OAuth instead; never
36
- deploy console delivery.
43
+ - `hashEmailOtp(secret, otp)` is keyed HMAC-SHA-256. Pass it as emailOTP's `storeOTP.hash`; an unkeyed hash permits enumeration of six-digit codes after a database leak.
44
+ - `authHardening({ipAddressHeaders, baseURL, errorURL})` refuses missing host-trusted ingress headers and cross-origin error redirects, and enables Better Auth's rate limiter with `storage: "database"`, which the Better Auth docs recommend for serverless: counts are shared across isolates, and Better Auth's defaults and plugin rules (for example 3 per minute per email-OTP path) set the limits. The generated schema includes the `rateLimit` table. Never assume client X-Forwarded-For is trusted.
45
+ - `guardGithubLogin(user, context, trustedProviders)` goes in user create/update before-hooks. Only explicitly trusted provider callbacks can populate this field. GitHub maps `githubLogin: profile.login`; generic OAuth ids need trusted profile mappers.
46
+ - `withAuthTasks(ctx.waitUntil.bind(ctx), () => auth.handler(request))` retains background sends/cleanup for the current request, with concurrent requests isolated. `authHardening` installs `retainAuthTask` as Better Auth's background handler. Failure logs are generic and contain no OTPs/credentials.
47
+ - `authSetupIncomplete()` returns 503/no-store while secrets/providers are unconfigured; do not authenticate by fallback.
37
48
 
38
- ## Mode matrix
49
+ Better Auth config is app-owned, so restrict who may sign up (an allowlist, an invitation) with a
50
+ `databaseHooks.user.create.before` hook that throws `APIError("FORBIDDEN")` from `better-auth/api` (the app shell calls
51
+ `src/signup.ts` there; returning `false` instead breaks email-OTP sign-in in Better Auth 1.7.7, which does not expect a refused create).
39
52
 
40
- | Mode | Non-secret vars | Worker secrets | Must be absent |
41
- |---|---|---|---|
42
- | `self-managed` | `MANTLE_AUTH_MODE=self-managed`, `PUBLIC_ORIGIN`, `GITHUB_CLIENT_ID`, `ADMIN_GITHUB_LOGIN` | `GITHUB_CLIENT_SECRET`, `BETTER_AUTH_SECRET` | `MANTLE_HOSTED_AUTH_ISSUER`, `MANTLE_HOSTED_AUTH_CLIENT_ID` |
43
- | `hosted` | `MANTLE_AUTH_MODE=hosted`, `PUBLIC_ORIGIN`, `MANTLE_HOSTED_AUTH_ISSUER`, `MANTLE_HOSTED_AUTH_CLIENT_ID`, `ADMIN_GITHUB_LOGIN` | `BETTER_AUTH_SECRET` | `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET` |
53
+ Magic links use hashed tokens, 15-minute expiry and the same task retention. Email callbacks pass the first
54
+ Accept-Language tag (fallback `en`) to the app's sender. `authRoutes` (as the app shell mounts it) also guards raw Better Auth `/admin/*` routes with the fresh database-backed Caller and the shared session origin check, so a stale KV role cannot bypass the directory gate. Mount that handler when selecting session KV. Configure ordinary Better Auth social providers;
55
+ Apple needs `https://appleid.apple.com` trusted origin and secure SameSite=None callback cookies. Generic OAuth login
56
+ uses `genericOAuth`, distinct provider ids and discovery or explicit authorization/token URLs. Cookie prefixes,
57
+ account linking and trusted origins remain app-owned. Host-only cookies require HTTPS, secure/path=/, a `__Host-`
58
+ prefix and no Domain/cross-subdomain sharing.
44
59
 
45
- Validation rules:
60
+ For OAuth/MCP add `jwt({disableSettingJwtHeader:true})`, official `mcp` plus `cimd` for client metadata discovery,
61
+ or `oauthProvider` for ordinary OAuth. These are optional app dependencies behind the dynamic auth import.
62
+ Use the live grant/session hooks and route wiring in [OAuth](../guides/oauth.md); merge verification hooks with the app shell's
63
+ user hooks. `genericOAuth` is a login plugin, distinct from the OAuth provider.
46
64
 
47
- - `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.
48
- - Self-managed uses the site's own GitHub OAuth app. Register its callback URL as `<PUBLIC_ORIGIN>/api/auth/callback/github`.
49
- - 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>`.
50
- - `ADMIN_GITHUB_LOGIN` must be a valid GitHub login.
65
+ ## Identity helpers
51
66
 
52
- Any missing, invalid, partial or mixed-mode configuration produces a setup-incomplete Auth instance instead of a working one.
67
+ `@aotter/mantle/auth/identity` exports `d1Identity(db, auth, caller, request.headers)` and
68
+ `postgresIdentity(sql, auth, caller, request.headers)` (Bun.SQL/postgres.js). Helpers use the template's default
69
+ Better Auth table/field names. Every operation requires a current admin cookie session.
53
70
 
54
- ## `503 setup_incomplete`
71
+ Methods: `listUsers()` lists all admin; `listMembers({search,limit,cursor,cursorDirection})` searches id/name/email
72
+ (literal LIKE), pages 1–100 and supports forward/backward opaque cursors bound to normalized search;
73
+ `setUserRole(id, "admin" | null)`, `inviteUser(email)`, `revokeInvite(id)`, `deleteUser(id)`.
74
+ Only identity fields reach outputs, never tokens/internal columns. Self-demotion/deletion is refused. Inviting an existing
75
+ member grants admin only after email verification; an unverified existing account is refused. Revocation deletes only an unverified admin invitation with no linked account. Deletion clears OTP
76
+ records first and delegates user/session/cache cleanup to Better Auth. Invitation email is an optional email-module step
77
+ after success; invitations still work when email is absent.
55
78
 
56
- With incomplete Auth, public routes keep working: public Views, public HTTP Triggers, public pages, `.md` mirrors, `llms.txt` and the sitemap. Auth-owned private routes return:
79
+ `d1BootstrapAdmin(db, {match: "email" | "github-login", value}, () => auth)` / `postgresBootstrapAdmin(sql, rule, () => auth)` return
80
+ `databaseHooks.user.create.after` (and user.update.after for a later email verification). Only the first matching signup wins (case-insensitive); email matching requires emailVerified=true (required for bootstrap).
81
+ D1 uses one conditional UPDATE; PostgreSQL uses a transaction advisory lock before UPDATE, avoiding concurrent winners.
82
+ A blocked promotion logs a warning. A GitHub rule requires a configured trusted GitHub provider/profile mapper.
83
+ The promotion runs after Better Auth creates the user, so the first sign-in response (and a cached session payload) can still show the previous role, while authorization re-reads the role on every request and `/admin/api/me` and later sessions show the admin role.
84
+ Helpers run no boot queries or DDL.
57
85
 
58
- ```json
59
- { "error": "setup_incomplete", "message": "Self-managed Auth configuration errors: BETTER_AUTH_SECRET is not set; ..." }
60
- ```
61
-
62
- with status `503` and `private, no-store`. The affected paths are `/admin` and `/admin/*`, `/api/auth` and `/api/auth/*`, `/oauth` and `/oauth/*`, `/.well-known/oauth*`, `/mcp` and `/mcp/*`. The message lists every failing check.
63
-
64
- ## Secrets and local values
65
-
66
- ```sh
67
- wrangler secret put BETTER_AUTH_SECRET
68
- wrangler secret put GITHUB_CLIENT_SECRET # self-managed only
69
- ```
70
-
71
- Non-secret vars go in `wrangler.jsonc` under `vars`. For local development put the same names in `.dev.vars`, which the minimal reference ignores in git (`.dev.vars*`). Never commit a secret.
72
-
73
- ## First owner
86
+ ## Worker entry
74
87
 
75
- Every new user receives the default role `user`, which has no staff access. The first sign-in whose GitHub login matches `ADMIN_GITHUB_LOGIN` is promoted to `owner`. Promotion is blocked once any staff user exists, so the variable only bootstraps an empty site.
88
+ Keep auth behind authLoader with a dynamic import of the app-owned auth module. Pass resolveCaller the request, loader, expected MCP audience and outgoing responseHeaders. Preserve returned Set-Cookie headers so sessions can slide. Share a lazy request-local resolver with auth/consent/Admin/MCP/REST; public anonymous routes can avoid auth. See [service composition](service-entry.md) for wiring.
76
89
 
77
- ## Roles
90
+ Invalid presented bearer credentials fail rather than becoming anonymous. Fresh session authority comes from Better Auth's primary result; role, ban and revocation checks remain live. Custom cookiePrefix must match the loader; renamed session_token cookie names are refused.
78
91
 
79
- Staff roles are `owner`, `editor` and `contributor`, in descending order. Owners manage them in Admin; the underlying routes are:
92
+ ## Build: keep Better Auth out of startup
80
93
 
81
- | Route | Minimum role |
82
- |---|---|
83
- | `GET /admin/api/staff`, `PATCH /admin/api/staff/:id/role` (a role or `null` to revoke) | `owner` |
84
- | `POST /admin/api/staff/invitations`, `DELETE /admin/api/staff/invitations/:id` | `owner` |
85
- | `GET /admin/api/site-settings`, `PATCH /admin/api/site-settings` | `owner` |
86
- | `GET /admin/api/members` | `editor` |
87
- | `POST /admin/api/entries/:id/publish`, `POST /admin/api/entries/:id/unpublish`, `DELETE /admin/api/entries/:id` | `editor` |
94
+ Use the [Workers build instructions](../../../README.md#workers-build).
95
+ The metafile startup guard rejects static Better Auth imports; keep auth behind
96
+ `authLoader(() => import(...))`.
88
97
 
89
- The staff role is re-read from D1 on every protected REST and MCP call; a revoked role takes effect on the next request. Manifest-level rules such as `requires.auth` are covered in [Authorization](../concepts/authorization.md) and the [authorization reference](../reference/authorization.md).
98
+ ## Schema
90
99
 
91
- ## What needs a session
100
+ `mantle generate <target> <out> <manifest>… --auth ./auth.ts` calls the module's `authOptions` with an empty env
101
+ (the tables depend on plugins and options, not env values; nothing is constructed) and appends Better Auth's tables to `schema.sql`. Nothing runs DDL at runtime; migrations
102
+ are yours (#1463).
103
+
104
+ For programmatic auth generation, import `generateFromSources` from
105
+ `@aotter/mantle/generate/auth`, not the bare `/generate` entry. Both use the
106
+ same generator; the selected entry preserves Better Auth's option types while
107
+ bare generation requires no optional auth peer in its public declarations.
108
+ Pass plain options rather than constructing Better Auth to obtain the schema.
109
+ The CLI's `--auth` selects this path automatically.
92
110
 
93
- | Surface | Requirement |
94
- |---|---|
95
- | `/admin`, `/admin/api/*` | Staff session; role gates per route |
96
- | `/api/auth/*`, `/oauth/*`, `/.well-known/oauth*` | Auth-owned; public endpoints of the OAuth flow |
97
- | `/mcp` | Same caller resolution as HTTP routes (bearer, same-origin cookie session, or anonymous); each tool's `requires` gates the call, and a call that needs identity answers `401` with a `WWW-Authenticate` challenge |
98
- | `/mcp/staff` | Authenticated caller with a staff role |
99
- | `/<locale>/<segment>/<slug>?preview=1` | Staff session (`401` without a session, `403` without a staff role) |
100
- | Public Views, public HTTP Triggers, public pages, `.md`, `llms.txt`, sitemap | None, unless the manifest declares `requires` |
111
+ Native adapters enable Better Auth joins by default. Without secondaryStorage, fresh cookie authorization uses Better Auth's primary session/user result with disableCookieCache. Passing responseHeaders permits the same call to slide a due cookie. An explicit advanced.database.joins:false retains separate-read behavior. Authority reflects the primary result at that read; a later concurrent revocation applies on the next request.
101
112
 
102
- MCP tokens are session-bound: signing out of Admin ends MCP access. See [MCP and agents](../concepts/mcp-and-agents.md).
113
+ ### Explicit read-only cookie snapshots
103
114
 
104
- ## Session cache and database replacement
105
-
106
- When optional session caching is enabled, the cache is derived from the
107
- canonical store, not a second identity authority. Auth prefixes keys with
108
- `better-auth:<store-instance-id>:`. Preparing a new store gives it a distinct
109
- identity, so reusing the same KV namespace after replacing D1 cannot resurrect
110
- the previous store's cached sessions. Ordinary preparation of the same store
111
- preserves its identity.
112
-
113
- Custom low-level Auth composition must prepare the Mantle store before cached
114
- Auth operations; do not construct cache keys or seed the identity yourself.
115
- OTP verification remains in the primary database and rate limiting remains
116
- isolate-local. Revocation/user-update cache invalidation still follows KV
117
- propagation; the namespace change is isolation across stores, not a promise of
118
- instant global invalidation. See the [Auth decision](../../adr/0014-auth-better-auth-and-multi-tenant-mcp.md).
119
-
120
- ## Better Auth configuration
121
-
122
- `createAuth()` owns the Worker lifecycle, Admin metadata, sender integration,
123
- bootstrap rules, roles and MCP invariants. Method-specific configuration stays
124
- native to Better Auth under `options`, so provider updates and type inference do
125
- not need a matching Mantle DSL update. There is deliberately no
126
- `Partial<BetterAuthOptions>` deep merge.
115
+ Authority remains fresh by default. An application may pass `readOnly: true`
116
+ only for a positively classified read. Enable Better Auth's official cache in
117
+ app-owned auth options:
127
118
 
128
119
  ```ts
129
- methods: [
130
- {
131
- kind: "social",
132
- provider: "google",
133
- options: { clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET, accessType: "offline" },
134
- },
135
- {
136
- kind: "social",
137
- provider: "apple",
138
- options: async () => ({ clientId: env.APPLE_CLIENT_ID, clientSecret: await loadAppleSecret(env) }),
139
- },
140
- {
141
- kind: "email-otp",
142
- sender,
143
- options: { otpLength: 8 },
144
- },
145
- ]
120
+ session: {
121
+ cookieCache: { enabled: true, maxAge: 60, refreshCache: false, version: "read-60-v1" },
122
+ }
146
123
  ```
147
124
 
148
- Email OTP storage defaults to a keyed HMAC-SHA-256 of the code using
149
- `BETTER_AUTH_SECRET`. Magic-link tokens remain `hashed` (high-entropy).
150
- Explicit official overrides remain available, including `plain` and custom
151
- hashing/encryption:
152
-
153
- ```ts
154
- { kind: "email-otp", sender, options: {
155
- storeOTP: { encrypt: encryptOtp, decrypt: decryptOtp },
156
- } }
157
- { kind: "magic-link", sender, options: {
158
- storeToken: { type: "custom-hasher", hash: hashMagicToken },
159
- } }
160
- ```
161
-
162
- For complete callback ownership, register an official plugin instance directly.
163
- Raw plugins do not add a button to `Auth.methods`; the application owns that UI.
164
- Duplicate plugin ids fail at construction rather than silently replacing one:
165
-
166
- ```ts
167
- createAuth({
168
- database: env.DB,
169
- baseURL: env.PUBLIC_ORIGIN,
170
- secret: env.BETTER_AUTH_SECRET,
171
- methods: [],
172
- plugins: [emailOTP({ sendVerificationOTP, storeOTP: "encrypted" })],
173
- });
174
- ```
175
-
176
- When several first-party apps share one parent domain that the same party controls, configure shared cookies explicitly. `cookiePrefix` is required whenever more than one Better Auth app writes cookies under that domain; `trustedOrigins` is the auth-flow trust list, not a CORS policy.
177
-
178
- ```ts
179
- const auth = createAuth({
180
- database: env.DB,
181
- baseURL: "https://platform.example.com",
182
- secret: env.BETTER_AUTH_SECRET,
183
- methods,
184
- trustedOrigins: ["https://example.com", "https://www.example.com"],
185
- cookiePrefix: "example-platform",
186
- crossSubDomainCookies: { enabled: true, domain: "example.com" },
187
- });
188
- ```
189
-
190
- Shared cookies do not cross registrable domains. A browser never sends an `example.com` cookie to `customer.com`. For a customer-owned domain, use an OAuth/OIDC broker flow: the customer site redirects to the identity provider's authorize endpoint, receives the callback, verifies the response and creates its own local session. The broker returns identity; the customer site remains the authority for its members and grants.
191
-
192
- ### Account linking across providers
193
-
194
- One person signing in with Google, then with GitHub, may land on one user row
195
- or be refused — Better Auth decides this, and `createAuth()` does not override
196
- it. Left unconfigured, Better Auth's own defaults apply: implicit linking is
197
- on, so a social sign-in whose provider reports a verified email attaches to the
198
- existing row carrying that email. It never creates a second row for the same
199
- address; when linking is not permitted the sign-in fails with
200
- `account not linked`.
201
-
202
- Two defaults are worth knowing before you change anything. `requireLocalEmailVerified`
203
- is on, so linking is refused while the *local* row is still unverified — this is
204
- what stops someone pre-registering an unverified row at your user's address and
205
- having that user's Google identity attach to it. It is also why a staff invitation
206
- (`inviteUser` writes `emailVerified: 0`) cannot be claimed by a social sign-in
207
- until the invitee verifies by email once. Separately, `trustedProviders` is
208
- empty, so every provider must supply `email_verified` to link at all.
209
-
210
- Pass `accountLinking` to scope this. It is forwarded verbatim:
211
-
212
- ```ts
213
- const auth = createAuth({
214
- database: env.DB,
215
- baseURL: env.PUBLIC_ORIGIN,
216
- secret: env.BETTER_AUTH_SECRET,
217
- methods,
218
- accountLinking: {
219
- // Accept these providers' word without an `email_verified` claim.
220
- trustedProviders: ["google", "github"],
221
- },
222
- });
223
- ```
224
-
225
- Listing a provider in `trustedProviders` asserts that it verifies the addresses
226
- it returns; a provider that does not turns the list into an account-takeover
227
- path. To go the other way and keep every identity separate, set
228
- `disableImplicitLinking: true` (users may still link deliberately via
229
- `linkSocial()` while signed in) or `enabled: false` to refuse linking outright.
230
-
231
- ## Self-hosted and hosted
232
-
233
- A free self-hosted site runs every method `createAuth()` exposes: Better Auth social providers, email OTP, magic link, and parent-domain SSO. The owner supplies provider credentials, email sending and cookie policy. Hosted auth is an operations convenience: the platform holds provider and email configuration and registers the site as a PKCE client, while the site still owns grants, members, content and `ctx.user`/`ctx.staff` mapping. Neither mode changes the runtime's authorization vocabulary.
234
-
235
- ## Replacing Auth construction
236
-
237
- ```ts
238
- createMantleWorker({
239
- plan,
240
- auth: (env) => createAuth({ /* curated, site-specific methods */ }),
241
- });
242
- ```
243
-
244
- With `auth` set, `MANTLE_AUTH_MODE` and the mode variables are not read. Core still owns the Auth routes: the factory's `basePath` joins the reserved paths, the MCP resource defaults to `auth.mcpResource ?? <PUBLIC_ORIGIN>/mcp`, and a rejected `auth.ready` evicts the isolate's assembly. Keep the D1 `DB` binding; Better Auth tables live there.
245
-
246
- ## OAuth resource primitives
247
-
248
- When one Mantle site is an OAuth client of another, request a stable RFC 8707
249
- resource and use standard `offline_access` when refresh is needed:
250
-
251
- ```ts
252
- const clientAuth = createAuth({
253
- // database, baseURL, secret, other methods...
254
- methods: [{
255
- kind: "oauth",
256
- options: {
257
- providerId: "mantle-platform",
258
- clientId: env.PLATFORM_CLIENT_ID,
259
- discoveryUrl: "https://platform.example.com/api/auth/.well-known/openid-configuration",
260
- scopes: ["openid", "offline_access", "accounts:read"],
261
- authorizationUrlParams: { resource: "https://api.example.com" },
262
- tokenUrlParams: { resource: "https://api.example.com" },
263
- refreshTokenParams: { resource: "https://api.example.com" },
264
- },
265
- }],
266
- });
267
-
268
- const { accessToken, accessTokenExpiresAt, scopes } =
269
- await clientAuth.getProviderAccessToken(request, "mantle-platform");
270
- ```
271
-
272
- The server-side getter is bound to the current local session request and never
273
- returns a refresh token or account row. On the provider:
274
-
275
- ```ts
276
- const providerAuth = createAuth({
277
- // database, baseURL, secret, methods...
278
- oauthProvider: {
279
- loginPage: "/sign-in",
280
- consentPage: "/consent",
281
- scopes: ["openid", "offline_access", "accounts:read"],
282
- resources: ["https://api.example.com"],
283
- },
284
- });
285
-
286
- const verification = await providerAuth.verifyOAuthAccessToken(request, {
287
- audience: "https://api.example.com",
288
- scopes: ["accounts:read"],
289
- });
290
- ```
291
-
292
- The verifier accepts JWT access tokens only and checks the configured issuer,
293
- JWKS/signature, audience, time claims, required scopes, and—when passed the
294
- request—DPoP proof binding with database-backed replay protection. It returns
295
- only `userId`, `clientId`, `credentialId`, and scopes. Opaque tokens are
296
- rejected; there is no introspection fallback.
297
-
298
- ## Enterprise-Managed Authorization
125
+ A valid signed cache hit avoids Caller DB reads. Missing, tampered, expired or version-mismatched snapshots fall back to primary authority. The SDK accepts this opt-in only without secondaryStorage, with explicit maxAge from 1 to 60 seconds and refreshCache:false. KV gains no cache-authority guarantee; bearer grant checks remain live.
299
126
 
300
- MCP's [Enterprise-Managed Authorization](https://modelcontextprotocol.io/extensions/auth/enterprise-managed-authorization)
301
- extension lets an enterprise IdP decide which employees may reach an MCP
302
- server. The MCP client exchanges the user's IdP login for an ID-JAG (identity
303
- assertion authorization grant) and presents it to the server's token endpoint
304
- as `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer`; no consent page
305
- is shown. Everything after that is an ordinary access token.
127
+ Revocation, bans and role changes reach cached reads within `maxAge` (at most
128
+ 60 seconds). Sign-out clears both the session-token and cache cookies. Bearer
129
+ tokens still use their live grant query on every request.
306
130
 
307
- Core does not know any issuer or JWKS. The token grant is a
308
- `@better-auth/oauth-provider` extension supplied by the adopter through
309
- `oauthProvider.extensions`, appended after Core's own claims extension:
131
+ Generated REST, MCP and web boundaries pass `{ readOnly }` as the third argument
132
+ to the application's caller callback. Forward it to `resolveCaller`:
310
133
 
311
134
  ```ts
312
- import { identityAssertionAuthorizationGrant } from "@aotterclam/id-jag";
313
-
314
- const auth = createAuth({
315
- // database, baseURL, secret, methods...
316
- oauthProvider: {
317
- loginPage: "/admin/sign-in",
318
- consentPage: "/oauth/consent",
319
- scopes: ["mcp", "offline_access"],
320
- mcpResource: env.PUBLIC_ORIGIN + "/mcp",
321
- extensions: [
322
- identityAssertionAuthorizationGrant({
323
- issuer: env.ENTERPRISE_IDP_ISSUER,
324
- jwksUrl: env.ENTERPRISE_IDP_JWKS_URL,
325
- authorizationServer: env.PUBLIC_ORIGIN,
326
- resource: env.PUBLIC_ORIGIN + "/mcp",
327
- scopes: ["mcp"],
328
- fetchJwks: (input, init) => fetch(input, { ...init, redirect: "manual" }),
329
- }),
330
- ],
331
- },
332
- });
135
+ const caller = (request: Request, _env: Env, authority = { readOnly: false }) =>
136
+ resolveCaller(request, loader, { ...authority, responseHeaders });
333
137
  ```
334
138
 
335
- The extension validates the assertion's signature, issuer, audience and
336
- lifetime, maps its subject to a user, and issues tokens through the provider's
337
- shared token path, so `verifyOAuthAccessToken`, DPoP and the MCP challenge
338
- behave exactly as for interactive grants. `@aotterclam/id-jag` is a reference
339
- implementation, not a Core dependency; any `OAuthProviderExtension` works.
340
- Extensions may also add client-authentication strategies, discovery metadata
341
- and additional claims. The same passthrough applies without `mcpResource`.
342
-
343
- ## Source
344
- - [`packages/adapters/cloudflare/README.md`](../../../packages/adapters/cloudflare/README.md)
345
- - [`packages/adapters/cloudflare/src/auth/conventionalAuth.ts`](../../../packages/adapters/cloudflare/src/auth/conventionalAuth.ts)
346
- - [`packages/adapters/cloudflare/src/auth/createAuth.ts`](../../../packages/adapters/cloudflare/src/auth/createAuth.ts)
347
- - [`packages/adapters/cloudflare/src/worker/createMantleWorker.ts`](../../../packages/adapters/cloudflare/src/worker/createMantleWorker.ts)
348
- - [`packages/adapters/cloudflare/src/mount/mountPublicRoutes.ts`](../../../packages/adapters/cloudflare/src/mount/mountPublicRoutes.ts)
349
- - [`packages/adapters/cloudflare/src/mount/mountMcp.ts`](../../../packages/adapters/cloudflare/src/mount/mountMcp.ts)
350
- - [`packages/mantle-admin/src/mountMantleAdmin.ts`](../../../packages/mantle-admin/src/mountMantleAdmin.ts)
351
- - [`docs/auth-hosting-model.md`](../../../docs/auth-hosting-model.md)
352
- - [`docs/adapter-guide.md`](../../../docs/adapter-guide.md)
353
- - [`docs/examples/host-local-admin-otp/src/index.ts`](../../../docs/examples/host-local-admin-otp/src/index.ts)
354
- - [`docs/examples/host-local-admin-otp/.dev.vars.example`](../../../docs/examples/host-local-admin-otp/.dev.vars.example)
355
- - [`docs/examples/host-minimal-worker/.gitignore`](../../../docs/examples/host-minimal-worker/.gitignore)
139
+ REST permits cached authority only on GET/HEAD Views; HEAD returns no body.
140
+ Web permits it only on GET/HEAD page and index reads. For Cookie requests without
141
+ an Authorization header, MCP classifies parsed
142
+ `initialize`, `tools/list`, `resources/list`, `resources/read`, generated View
143
+ tools and generated native Admin media reads before resolving Caller. Generated
144
+ metadata identifies Views versus Procedures; names and `readOnlyHint` annotations
145
+ never make a Procedure eligible. A batch uses cached authority only if every
146
+ message is a known read. Unknown/malformed methods or tools, mutations,
147
+ publishing transitions and Admin writes use fresh authority. Dynamic admin tool
148
+ implementations still load only after the admin gate; their read catalogue is
149
+ generated separately. App-composed tool tables default to fresh authority, including tools with a
150
+ `page` callback or read annotations, unless the
151
+ app supplies equivalent trusted read metadata to `mcp`'s fourth argument.
152
+
153
+ Auth/admin endpoints (including GET) and consent keep the default fresh resolver.
154
+ Do not infer authority from HTTP method alone or pass `readOnly: true` for a whole
155
+ surface. A caller callback that ignores the third argument retains the default
156
+ primary checks. In request-local memoization, keep cached reads separate from
157
+ fresh authority so a prior read cannot supply authority for a write.
158
+
159
+ **Bump `cookieCache.version` when enabling this policy or changing its TTL or
160
+ semantics.** A cache minted under an earlier 300-second setting retains its old
161
+ signed expiry; changing `maxAge` alone does not shorten it. Better Auth's version
162
+ check invalidates that snapshot. The SDK cannot infer prior deployment config.
163
+ Sign-out clears the session-token and cache cookies; pass SDK Set-Cookie headers
164
+ through to the browser. A copied pre-sign-out cookie can remain usable for
165
+ read-only snapshots until TTL, while fresh authority rejects revoked sessions.
166
+
167
+ With secondaryStorage, disableCookieCache does not bypass KV. Caller keeps a primary authority check before any second SDK call refreshes/forwards cookies. Stale snapshots cannot revive deleted/expired primary sessions or retain obsolete role/ban state.
168
+
169
+ `authRoutes` may be imported statically: its consent/provider integration loads
170
+ only for a matching consent request, after Caller/origin/body guards. Optional
171
+ Better Auth adapter code stays outside the Worker startup graph.
172
+
173
+ `@aotter/mantle/auth/admin` exports `guardAuthAdminRequest(request, resolveFreshCaller, basePath?)` for selected auth transports. It resolves authority only for raw/encoded admin paths, requires an admin cookie session and checks mutation origin. Both the app-owned template and OAuth routes use this same guard.
174
+
175
+ Bare `ctx.admin` admits any verified admin layer classified by Better Auth's
176
+ `adminRoles` / `adminUserIds`. Role lists in `requires` are refused; decide
177
+ per-target permissions in `authorize(target, caller)`, using `hasAdminRole`
178
+ when appropriate. Verified role names match `^[a-z][a-zA-Z0-9_-]{0,62}$`.
179
+ Member role names alone grant no privilege. See
180
+ [authorization requirements](../reference/authorization.md).
181
+
182
+ The generated auth DDL adds a native `(createdAt, id)` member-paging index (respecting schema field/table names). Unfiltered forward/backward paging orders native timestamps through that index; PostgreSQL cursors retain all six fractional digits and bind as text before timestamp casting. Member search (`%term%` over id/name/email, casting UUID ids to text only in the search expression) is a full scan; the index does not accelerate it.
183
+
184
+ ## Several Better Auth instances in one app
185
+
186
+ The default is one instance with `admin` and `user` roles. An app that keeps two populations apart (for example
187
+ staff and customers) can run one Better Auth instance per population in the same Worker; every Mantle primitive
188
+ takes the instance it works on:
189
+
190
+ - Give each instance its own `basePath`, `advanced.cookiePrefix`, `secret` and core table names (`user.modelName`,
191
+ `session.modelName`, `account.modelName`, `verification.modelName`). Keep each instance's options in its own
192
+ module and pass one `--auth` per module to `mantle generate`; the tables are merged into one migration, and two
193
+ instances that define the same table differently are refused. The native adapter follows each instance's mapping.
194
+ - Better Auth plugin factories share their schema object, so differing plugin field/model mappings across instances
195
+ in one Worker override each other. Use one of two supported ways:
196
+ 1. Separate the instances by core table names only and leave every plugin mapping at its default.
197
+ 2. When plugin tables must differ (`jwt`'s `jwks`, `@better-auth/oauth-provider`'s `oauth*` tables), map them
198
+ explicitly in every instance, the default one included, and give each instance its own copy of the plugin's
199
+ schema right after constructing it:
200
+ `const keys = jwt({ schema: { jwks: { modelName: "staff_jwks" } } }); const plugin = { ...keys, schema: structuredClone(keys.schema) };`
201
+
202
+ `mantle generate` refuses anything else when two or more `--auth` instances use the same plugin (`jwt`, `admin`,
203
+ `oauth-provider`, `two-factor`, `phone-number`, `anonymous`, `siwe`, `device-authorization`) and a mapping exists
204
+ while an instance keeps the shared schema object, or two instances resolve a mapping to the same name (generate
205
+ cannot tell an identical mapping from an inherited one, so mapped names must differ between instances). Build
206
+ every instance's plugins in the same order in the Worker as in `--auth`; an instance built earlier than a mapped
207
+ one can still inherit its mapping, which generate cannot see.
208
+ - One `authLoader(load, cookiePrefix)` per instance; call `resolveCaller(request, loader)` with the instance the
209
+ route belongs to, and mount `authRoutes(loader, ...)` per instance (it guards that instance's `/admin/*`). The
210
+ loader takes `cookiePrefix` separately on purpose: it checks the cookie before loading Better Auth, so anonymous
211
+ requests never pay for it. Read both from one constant.
212
+ - Admin signs in with the instance passed to `adminShell(request, assets, { authBasePath })`; its identity
213
+ directory is the instance you pass to `d1Identity` / `postgresIdentity`.
@@ -1,29 +1,11 @@
1
- ---
2
- description: "Mantle on ChatGPT Sites now has its own integration guide."
3
- ---
4
1
  # Mantle on ChatGPT Sites
5
2
 
6
- ChatGPT Sites has a dedicated section in the Mantle handbook. Start with
7
- [Mantle on ChatGPT Sites](../chatgpt-sites/index.md) for installation, supported
8
- capabilities, and your first published article. This page preserves earlier
9
- links to the host checklist.
3
+ > **Verification status:** this guide is experimental and has not yet been exercised end to end on a real ChatGPT Site.
10
4
 
11
- ## First deployment
5
+ Treat the Site as an existing TypeScript host. Import selected generated Views/Procedures, pass the host's native handle and verified Caller, and compose only the surfaces it supports. Use [partial integration](../../skills/integrate/SKILL.md); Mantle supplies no Sites-specific identity or deployment adapter.
12
6
 
13
- See [the host deployment checklist](../chatgpt-sites/host-reference.md#first-deployment).
7
+ Choose postgres or sqlite according to the host's actual SQL executor. Custom handles are app-owned; a structural match alone does not guarantee driver semantics. [Database boundaries](../concepts/runtime-and-adapters.md) defines the supported drivers.
14
8
 
15
- ## Media with only an R2 binding
9
+ The runtime never creates, alters or checks tables. Apply initial `schema.sql` and selected auth/site DDL only to an empty database. For later changes the agent diffs regenerated SQL, writes and rehearses a host-native migration on a copy, and applies it before publishing new code. Destructive/type changes require explicit data migration; Mantle drops nothing.
16
10
 
17
- See [R2 media integration](../chatgpt-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](../chatgpt-sites/host-reference.md#remote-mcp-is-a-separate-gate).
22
-
23
- ## Capability boundaries
24
-
25
- See [host capability boundaries](../chatgpt-sites/host-reference.md#capability-boundaries).
26
-
27
- ## Source
28
-
29
- See [integration sources](../chatgpt-sites/host-reference.md#source).
11
+ Use the Site's native publication workflow. Configure trusted origin and verified identity there; never construct a system/admin Caller from browser input. Serving Admin also requires its app-built assets and shell security contract. Scheduling and storage provisioning belong to the host.