@aotter/mantle 0.2.0-alpha.9 → 0.2.0

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 (159) hide show
  1. package/README.md +9 -3
  2. package/dist/auth/adapter.js +1 -28
  3. package/dist/auth/admin.js +2 -5
  4. package/dist/auth/caller.js +0 -27
  5. package/dist/auth/consent.js +100 -0
  6. package/dist/auth/hardening.js +0 -7
  7. package/dist/auth/identity.js +0 -11
  8. package/dist/auth/oauth.js +0 -32
  9. package/dist/auth/resource-seeds.js +0 -2
  10. package/dist/auth/route-http.js +33 -0
  11. package/dist/auth/routes.js +5 -151
  12. package/dist/build/compiler.d.ts +19 -609
  13. package/dist/build/compiler.js +3 -6896
  14. package/dist/build/spec/domain/model/SqlIr.d.ts +2 -0
  15. package/dist/build/spec/domain/model/UserFields.d.ts +1 -0
  16. package/dist/bun/assets.js +0 -2
  17. package/dist/domain/adminOrder.js +0 -2
  18. package/dist/domain/columns.js +7 -20
  19. package/dist/domain/describe/chunk-SGMRVDGE.js +1 -0
  20. package/dist/domain/describe/index.js +1 -115
  21. package/dist/domain/describe/target-hash.js +1 -12
  22. package/dist/domain/entry.js +0 -14
  23. package/dist/domain/generated-tools.js +0 -1
  24. package/dist/domain/input.js +0 -2
  25. package/dist/domain/paging.js +0 -17
  26. package/dist/domain/recipes.js +10 -7
  27. package/dist/domain/userReferences.js +0 -1
  28. package/dist/domain/wireScalars.js +0 -6
  29. package/dist/infrastructure/adminEntries.js +10 -7
  30. package/dist/infrastructure/adminPaging.js +55 -57
  31. package/dist/infrastructure/artifacts.js +0 -4
  32. package/dist/infrastructure/authSchema.js +0 -9
  33. package/dist/infrastructure/bundle.js +2 -30
  34. package/dist/infrastructure/cli.js +25 -11
  35. package/dist/infrastructure/generate.js +9 -16
  36. package/dist/infrastructure/generateAuth.js +1 -9
  37. package/dist/infrastructure/help.js +55 -0
  38. package/dist/infrastructure/init.js +50 -0
  39. package/dist/infrastructure/inputs.js +0 -9
  40. package/dist/infrastructure/mounts.js +22 -0
  41. package/dist/infrastructure/wrangler.js +0 -6
  42. package/dist/kernel/authorization.js +0 -1
  43. package/dist/kernel/binds.d.ts +16 -4
  44. package/dist/kernel/binds.js +4 -10
  45. package/dist/kernel/caller-identity.d.ts +0 -3
  46. package/dist/kernel/caller-identity.js +0 -15
  47. package/dist/kernel/caller-mode.d.ts +4 -0
  48. package/dist/kernel/caller-mode.js +14 -0
  49. package/dist/kernel/contracts.d.ts +6 -10
  50. package/dist/kernel/contracts.js +5 -41
  51. package/dist/kernel/cron.js +0 -4
  52. package/dist/kernel/dialect/common.js +0 -3
  53. package/dist/kernel/dialect/postgres.js +0 -2
  54. package/dist/kernel/dialect/sqlite.js +1 -6
  55. package/dist/kernel/drain.js +2 -8
  56. package/dist/kernel/driver/bun-sqlite.js +0 -4
  57. package/dist/kernel/driver/d1.js +0 -1
  58. package/dist/kernel/driver/postgres.js +2 -17
  59. package/dist/kernel/driver/transaction-context.js +0 -1
  60. package/dist/kernel/email.js +0 -4
  61. package/dist/kernel/host-access.js +2 -3
  62. package/dist/kernel/host-caller.d.ts +1 -1
  63. package/dist/kernel/host-caller.js +3 -6
  64. package/dist/kernel/json-body.js +0 -2
  65. package/dist/kernel/mcp-apps.js +0 -5
  66. package/dist/kernel/mcp-call.js +0 -1
  67. package/dist/kernel/mcp-handler.js +2 -26
  68. package/dist/kernel/mcp.js +0 -5
  69. package/dist/kernel/media.js +0 -18
  70. package/dist/kernel/paging.js +1 -9
  71. package/dist/kernel/publishing.js +3 -4
  72. package/dist/kernel/r2-media.js +0 -7
  73. package/dist/kernel/rest-dispatch.js +0 -1
  74. package/dist/kernel/rest-router.js +2 -3
  75. package/dist/kernel/rest.js +0 -3
  76. package/dist/kernel/scalars.js +1 -9
  77. package/dist/kernel/search.js +0 -1
  78. package/dist/kernel/site.js +0 -2
  79. package/dist/kernel/store.d.ts +11 -1
  80. package/dist/kernel/store.js +21 -34
  81. package/dist/kernel/surface-failure.d.ts +10 -0
  82. package/dist/kernel/surface-failure.js +29 -0
  83. package/dist/kernel/surface.js +0 -3
  84. package/dist/kernel/sweep.js +0 -1
  85. package/dist/kernel/translations.js +0 -1
  86. package/dist/kernel/user-caller.js +2 -8
  87. package/dist/kernel/user-fields.d.ts +3 -2
  88. package/dist/kernel/user-fields.js +9 -9
  89. package/dist/kernel/validation.js +0 -8
  90. package/dist/kernel/web-gateway.js +0 -2
  91. package/dist/kernel/web-head.js +0 -1
  92. package/dist/kernel/web-index.js +0 -1
  93. package/dist/kernel/web-response.js +0 -1
  94. package/dist/kernel/web-serve.js +97 -0
  95. package/dist/kernel/web.d.ts +1 -1
  96. package/dist/kernel/web.js +10 -91
  97. package/dist/kernel/where.js +0 -6
  98. package/dist/usecase/adminBindings.js +30 -21
  99. package/dist/usecase/adminClient.js +0 -21
  100. package/dist/usecase/adminDescriptors.js +2 -9
  101. package/dist/usecase/generate.js +7 -12
  102. package/dist/usecase/handlers.js +0 -3
  103. package/dist/usecase/mcp-media.js +0 -1
  104. package/dist/usecase/mcp.js +0 -4
  105. package/dist/usecase/paging.js +1 -3
  106. package/dist/usecase/publishing.js +0 -4
  107. package/dist/usecase/rest.js +0 -1
  108. package/dist/usecase/site.js +0 -1
  109. package/dist/usecase/stores.js +4 -14
  110. package/dist/usecase/tool-version.js +0 -3
  111. package/dist/usecase/translations.js +0 -2
  112. package/dist/usecase/validation.js +0 -20
  113. package/docs/examples/README.md +13 -5
  114. package/docs/examples/app-shell/README.md +38 -7
  115. package/docs/examples/app-shell/accept.mjs +91 -0
  116. package/docs/examples/app-shell/gitignore +7 -0
  117. package/docs/examples/app-shell/package.json +7 -6
  118. package/docs/examples/app-shell/smoke-kit.mjs +29 -15
  119. package/docs/examples/app-shell/src/routes.ts +6 -3
  120. package/docs/examples/app-shell/src/service.ts +2 -1
  121. package/docs/examples/club-events/README.md +7 -2
  122. package/docs/examples/club-events/manifests/club-events.yaml +15 -0
  123. package/docs/examples/club-events/smoke.mjs +7 -1
  124. package/docs/examples/purchasing/README.md +12 -3
  125. package/docs/examples/purchasing/manifests/purchasing.yaml +21 -4
  126. package/docs/examples/purchasing/smoke.mjs +14 -0
  127. package/docs/examples/recipes/README.md +5 -3
  128. package/docs/handbook/cloudflare/service-entry.md +3 -0
  129. package/docs/handbook/examples/hub.md +1 -1
  130. package/docs/handbook/guides/agent-setup.md +15 -10
  131. package/docs/handbook/guides/agent-workflow-acceptance.md +113 -103
  132. package/docs/handbook/guides/local-testing.md +40 -0
  133. package/docs/handbook/guides/native-admin.md +25 -6
  134. package/docs/handbook/guides/native-execution.md +5 -4
  135. package/docs/handbook/guides/public-pages.md +44 -4
  136. package/docs/handbook/guides/sql-patterns.md +266 -0
  137. package/docs/handbook/guides/typed-queries.md +138 -0
  138. package/docs/handbook/navigation.json +16 -0
  139. package/docs/handbook/reference/admin-descriptors.md +4 -1
  140. package/docs/handbook/reference/authorization.md +1 -1
  141. package/docs/handbook/reference/diagnostics.md +6 -1
  142. package/docs/handbook/reference/procedure.md +5 -5
  143. package/docs/handbook/reference/schema.md +10 -4
  144. package/docs/handbook/reference/sql.md +64 -0
  145. package/docs/handbook/reference/surface.md +5 -2
  146. package/docs/handbook/reference/view.md +11 -3
  147. package/docs/handbook/releases/index.md +27 -6
  148. package/docs/handbook/start/agents.md +45 -0
  149. package/docs/handbook/start/project-and-cli.md +6 -0
  150. package/docs/handbook/start/quickstart-worker.md +1 -1
  151. package/docs/skills/develop/SKILL.md +6 -5
  152. package/docs/skills/update/SKILL.md +2 -0
  153. package/package.json +23 -3
  154. package/skills/README.md +1 -1
  155. package/skills/mantle/SKILL.md +39 -120
  156. package/dist/domain/describe/chunk-ZVXBGP7Y.js +0 -424
  157. package/skills/mantle/references/local-testing.md +0 -20
  158. package/skills/mantle/references/web.md +0 -41
  159. package/skills/mantle/references/website.md +0 -51
@@ -38,7 +38,7 @@ A string `pattern` needs a `maxLength`: generation bounds each regex's backtrack
38
38
  | object/array | JSON text | jsonb |
39
39
  | geo | lat/lng columns, R*Tree | lat/lng columns |
40
40
 
41
- System columns include id, version, created_at, updated_at and author_id; publishing adds status/published_at. Do not declare reserved fields. Manifest native column names in indexes use camelCase (`createdAt`); generated Store/SQL use storage names (`created_at`). Scope is omitted from returned rows.
41
+ System columns include id, version, created_at, updated_at and author_id; publishing adds status/published_at. Initial DDL declares a field NOT NULL when an `operational` Schema lists it in `required` and it does not allow null; `default` never does, and no column gets a DDL `DEFAULT` ([server-maintained fields](#admin-uischema)). Do not declare reserved fields. Manifest native column names in indexes use camelCase (`createdAt`); generated Store/SQL use storage names (`created_at`). Scope is omitted from returned rows.
42
42
 
43
43
  Declared date/date-time outputs are SQL-formatted strings (`YYYY-MM-DD`, UTC ISO with six fractional digits). Computed timestamps remain native. Inputs and other output scalars follow [the native contract](../guides/native-execution.md). Date conversion through JS Date loses microseconds.
44
44
 
@@ -76,9 +76,11 @@ The runtime never creates, alters or checks tables. Initial DDL is for an empty
76
76
 
77
77
  ## Admin uiSchema
78
78
 
79
- A property marked `readOnly: true` in the JSON Schema is server-maintained (a counter, a total): Admin's generic create stores its `default` whatever the input says, Admin edits ignore it, and only Procedures (or server code through the Store) change it afterwards. JSON Schema defaults are applied by Admin and the Store, not by the database: a SQL Procedure that inserts the row sets the field itself. A root `readOnly: true` makes the whole Schema read-only in Admin.
79
+ A property marked `readOnly: true` in the JSON Schema is server-maintained (a counter, a total): Admin's generic create stores its `default` whatever the input says, Admin edits ignore it, and only Procedures (or server code through the Store) change it afterwards. JSON Schema defaults are applied by Admin's generic create only, not by the Store or the database: initial DDL emits no `DEFAULT` for a field, so server code through the Store and a SQL Procedure that inserts the row set the field themselves. A root `readOnly: true` makes the whole Schema read-only in Admin: a generic Admin write is refused with 403 `AUTH_DENIED` (not a 409 version conflict).
80
80
 
81
- `fields.<name>.widget` selects a built-in control (`textarea`); `list.primaryField`, `list.columns` and enum `filterField` select list presentation; `nav.parentField` chooses the parent a child folds under (required when two or more required `id` references exist) and `nav.standalone` lists the child in the sidebar; `nav.group/order/hidden` place it in the sidebar ([sidebar](../guides/native-admin.md#sidebar)). `list.columns` must not repeat `list.primaryField` or itself (`SCHEMA_UI_INVALID`). Generation validates these against declared fields/indexes. Components that render a field, cell or record aside are not manifest references: they live in `defineAdmin` ([customising Admin](../guides/native-admin.md#customising-admin)), and a manifest that still names one (`widget: <extension>/<contribution>`, `widget` `options`, `list.cells`, `panels`) is refused. JSON Schema root `schema.readOnly: true` disables generic Admin writes, not declared Procedures; `uiSchema.readOnly` is rejected with `SCHEMA_UI_INVALID`. `x-mantle-ref` relates a field to another Schema id or single-field unique field.
81
+ `default` does not make a column NOT NULL; `required` does (in an `operational` Schema). For a server-maintained value that is never NULL, list it in both, `required: [name, total]` with `total: {type: integer, readOnly: true, default: 0}`: initial DDL declares `total` NOT NULL, Store and View row types read it as `number` rather than `number | null`, Admin's create still fills the default when the client omits it, and the Store's insert type requires server code to pass it. Without `required` the column stays nullable, and an INSERT that omits it stores NULL. Adding `required` to an existing table is a [migration](../concepts/runtime-and-adapters.md#schema-installation-and-migrations): backfill NULLs, then add NOT NULL.
82
+
83
+ `fields.<name>.widget` selects a built-in control (`textarea`); `list.primaryField`, `list.columns` and enum `filterField` select list presentation (operational Schemas only; a publishing Schema's list shows its title field and status); `nav.parentField` chooses the parent a child folds under (required when two or more required `id` references exist) and `nav.standalone` lists the child in the sidebar; `nav.group/order/hidden` place it in the sidebar ([sidebar](../guides/native-admin.md#sidebar)). `list.columns` must not repeat `list.primaryField` or itself (`SCHEMA_UI_INVALID`). Generation validates these against declared fields/indexes. Components that render a field, cell or record aside are not manifest references: they live in `defineAdmin` ([customising Admin](../guides/native-admin.md#customising-admin)), and a manifest that still names one (`widget: <extension>/<contribution>`, `widget` `options`, `list.cells`, `panels`) is refused. JSON Schema root `schema.readOnly: true` disables generic Admin writes, not declared Procedures; `uiSchema.readOnly` is rejected with `SCHEMA_UI_INVALID`. `x-mantle-ref` relates a field to another Schema id or single-field unique field. It is not a foreign key: the database does not check that the referenced row exists, and deleting that row leaves the reference dangling. Check existence in the write (a JOIN or `EXISTS` in the Procedure) and guard deletes with `NOT EXISTS` ([SQL patterns](../guides/sql-patterns.md)).
82
84
 
83
85
  See [Admin](../guides/admin-ui.md) and [diagnostics](diagnostics.md).
84
86
 
@@ -88,7 +90,11 @@ See [Admin](../guides/admin-ui.md) and [diagnostics](diagnostics.md).
88
90
 
89
91
  A caller whose value for a field is `{ all: true }` drops that field's term only; the other terms still apply. Generated SQL holds one statement per target and selects the form for each caller at run time; values are always bound parameters. Neither roles nor the admin layer widen a field scope or a translation parent's visibility. A Schema whose `x-mantle-ref` points to a field-scoped Schema must carry the same scope terms.
90
92
 
91
- For inserts, one allowed partition autofills a missing value; multiple values require an explicit allowed partition, and missing/empty values fail with `AUTH_DENIED`. Callers holding `{ all: true }` and system callers always provide an explicit partition. Procedure INSERT/upsert is trusted-only; use the scoped Store for user writes. The partition is never writable through SET, including upsert and native Admin patch. An excluded id behaves like a missing row: reads return not found; Store update/delete and row-classified Procedure UPDATE/DELETE affecting zero rows return HTTP 409 (`CONFLICT`). Native Admin's visible-row pre-read returns 404 for either id. SQLite binds a JSON array; PostgreSQL binds a quoted, escaped array. Public Views over field scope warn that anonymous callers are refused.
93
+ For inserts, one allowed partition autofills a missing value; multiple values require an explicit allowed partition, and missing/empty values fail with `AUTH_DENIED`. Callers holding `{ all: true }` and system callers always provide an explicit partition. A Procedure `INSERT … VALUES` follows the same rule for user callers, checked before any SQL runs: leave the partition column out to autofill a caller's single value, or take it from an input declared with the column's own type (`VALUES (input.region, …)`) or a matching literal, which must be inside the caller's set; any other expression there is refused. A Procedure that callers holding several values or `{ all: true }` use, such as an Admin create action, takes the partition as an optional input. `INSERT … SELECT` into a field-scoped Schema, and an insert into a Schema the target `restrict`s, run only for system callers, which this check does not cover: they name the partition themselves; use the scoped Store for user writes there. The partition is never writable through SET, including upsert and native Admin patch. An excluded id behaves like a missing row: reads return not found; Store update/delete and row-classified Procedure UPDATE/DELETE affecting zero rows return HTTP 409 (`CONFLICT`). Native Admin's visible-row pre-read returns 404 for either id. SQLite binds a JSON array; PostgreSQL binds a quoted, escaped array. Public Views over field scope warn that anonymous callers are refused.
94
+
95
+ #### References to a scoped Schema
96
+
97
+ A non-null `x-mantle-ref` value that points to a scoped Schema (field or owner scope) must name a row of that Schema the caller can read, matched on `id` or on the object form's `field`, and that row must have the same value as the child for every scope field the two share (`ADR-0034`: a reference between scoped Schemas stays inside one owner or partition). A Store insert or update, a Procedure `INSERT … VALUES`, `UPDATE … SET` or `DO UPDATE SET`, and native Admin's generic create and patch check it in the same statement; a missing, unreadable or other-partition parent behaves like a missing row: a single-row statement returns HTTP 409 (`CONFLICT`), and an `ON CONFLICT` insert or a set update writes nothing for that row. A caller holding several values or `{ all: true }` can still not point a child in one partition at a parent in another. `INSERT … SELECT` that writes such a reference runs only for system callers. A caller's `UPDATE … SET` or `DO UPDATE SET` of such a reference takes no subquery, which could read the parent's columns as its own. Trusted writes (system callers, `runtime.store`) are not checked, like the other row rules. NULL is always allowed; deleting a parent does not touch its children.
92
98
 
93
99
  ## Reserved entry columns
94
100
 
@@ -0,0 +1,64 @@
1
+ ---
2
+ description: The SQL that Views and Procedures accept on SQLite/D1 and on PostgreSQL, and what each refuses with which diagnostic.
3
+ ---
4
+ # SQL accepted by each dialect
5
+
6
+ Views and Procedures share one allowlist with two profiles. SQLite and D1 run the base profile; PostgreSQL runs the
7
+ base profile plus the additions below. Anything not listed is refused at generation with a code (`SQL_UNSUPPORTED`,
8
+ `SQL_FUNCTION`, `SQL_SHAPE`, `SQL_TYPE`) and a source position, so generation is the authority for each target. For
9
+ worked examples see [SQL patterns](../guides/sql-patterns.md).
10
+
11
+ ## Both dialects
12
+
13
+ | Area | Accepted |
14
+ | --- | --- |
15
+ | Statements | `SELECT`, `INSERT … VALUES` / `INSERT … SELECT`, `UPDATE … SET … WHERE`, `DELETE … WHERE`, each with `RETURNING`; `WITH` (non-recursive) on a `SELECT` |
16
+ | Upsert | `ON CONFLICT (<unique index columns>) DO NOTHING` / `DO UPDATE SET …`, with `excluded.<column>` |
17
+ | Joins | `INNER JOIN`, `LEFT JOIN`; a comma join only for a row source (`json_each`, or PostgreSQL's below) |
18
+ | Clauses | `WHERE`, `GROUP BY`, `HAVING`, `ORDER BY`, `LIMIT` |
19
+ | Predicates | `=`, `<>`, `!=`, `<`, `>`, `<=`, `>=`, `AND`, `OR`, `NOT`, `IS [NOT] NULL`, `IS [NOT] DISTINCT FROM`, `[NOT] BETWEEN`, `IN (list)`, `IN (subquery)`, `LIKE`, `[NOT] EXISTS (subquery)` |
20
+ | Expressions | `+ - * / %`, `\|\|`, `->>` (JSON field), `CASE`, `COALESCE`, `NULLIF`, scalar subqueries, `CAST(… AS text \| int4 \| int8 \| float8 \| bool \| timestamptz \| date \| numeric \| interval)` |
21
+ | Aggregates | `count`, `sum`, `min`, `max`, `avg` |
22
+ | Windows | `row_number()`, `rank()`, `sum()`, `count()` with `OVER (PARTITION BY … ORDER BY …)` and the default frame |
23
+ | Scalar functions | `lower`, `upper`, `length`, `abs`, `round`, `substr`, `replace`, `trim`, `ltrim`, `rtrim`, `instr` |
24
+ | Time | `now()`, `date_trunc('hour' \| 'day' \| 'week' \| 'month' \| 'year', …)`, `extract(year \| month \| day \| dow \| hour FROM …)` |
25
+ | Mantle | `auth.uid()`, `input.<name>`, `mantle.search`, `mantle.search_rank`, `mantle.near`, `mantle.distance` ([features](features.md)) |
26
+
27
+ ## SQLite and D1 only
28
+
29
+ | Accepted | PostgreSQL instead |
30
+ | --- | --- |
31
+ | `json_each(<array>)` as a row source (`FROM json_each(input.items) j`, `j.value ->> 'field'`) | `jsonb_array_elements(…) AS j(value)` |
32
+ | `json_group_array`, `json_group_object` | `jsonb_agg`, `jsonb_object_agg` |
33
+ | `json_extract`, `json_set`, `json_insert`, `json_remove`, `json_array_length`; build an object with `json_set('{}', '$.k', v, …)` (`json_object` is refused) | `jsonb_build_object`, `jsonb_build_array` and the jsonb operators |
34
+ | `typeof`, `hex` | refused (`typeof` is SQLite's; PostgreSQL has `pg_typeof`) |
35
+
36
+ An integer `CAST` of anything but a literal is refused (`SQL_TYPE`): SQLite truncates where PostgreSQL rounds. Write
37
+ `round(x)`; `->>` already returns a JSON number as a number.
38
+
39
+ ## PostgreSQL additions
40
+
41
+ | Area | Accepted |
42
+ | --- | --- |
43
+ | Set operations | `UNION [ALL]`, `INTERSECT`, `EXCEPT`, inside a `WITH` or a subquery (the View's own SELECT is the one that pages) |
44
+ | Relations | `LATERAL` subqueries, `WITH RECURSIVE`, `MATERIALIZED` / `NOT MATERIALIZED` CTEs |
45
+ | Row sources | `jsonb_array_elements`, `jsonb_array_elements_text`, `jsonb_each`, `jsonb_each_text`, with `WITH ORDINALITY` |
46
+ | Operators | `ILIKE`, `~`, `~*`, `!~`, `!~*`, jsonb `->`, `#>`, `#>>`, `@>`, `<@`, `?`, `?\|`, `?&` |
47
+ | Aggregates | `string_agg`, `jsonb_agg`, `jsonb_object_agg`, with `FILTER (WHERE …)` and `ORDER BY` inside the call |
48
+ | Windows | also `avg`, `min`, `max`, `lag`, `lead`, `first_value`, `last_value`, `dense_rank`; frames except `GROUPS` and `EXCLUDE` |
49
+ | Functions | `GREATEST`, `LEAST`, `floor`, `ceil`, `sqrt`, `power`, `split_part`, `timezone`, `jsonb_build_object`, `jsonb_build_array`, `jsonb_strip_nulls`, `to_jsonb`, `jsonb_typeof`, `CAST(… AS jsonb)` |
50
+ | Time | `date_trunc` also `'minute'`, `'quarter'`; `extract` also `minute`, `quarter`, `week`, `isoyear`, `isodow`, `doy`, `epoch` |
51
+
52
+ `jsonb_agg` of no rows is `NULL`: write `COALESCE(jsonb_agg(…), CAST('[]' AS jsonb))` for an empty array.
53
+
54
+ ## Refused on every target
55
+
56
+ - `OFFSET`: page with the generated cursor (`limit`, `cursor`) instead.
57
+ - `RIGHT` and `FULL` joins, `UPDATE … FROM`, `DELETE … USING`, DDL and any function not listed.
58
+ - `DISTINCT` with `ORDER BY`: the paging key Mantle appends would change what is distinct; use `GROUP BY`.
59
+ - A program of more than 2000 syntax nodes.
60
+
61
+ Two write rules come from Mantle, not the dialect ([Procedure](procedure.md)). A row-classified `UPDATE`/`DELETE`
62
+ (`WHERE id = input.id …`) must change exactly one row, otherwise `CONFLICT`. A set form (`WHERE id IN (SELECT …)`,
63
+ `INSERT … SELECT`) may change none, so write a conditional write that may do nothing in that form. Scope, version,
64
+ timestamps and author columns are assigned by Mantle, never by your SQL.
@@ -233,7 +233,7 @@ surface its generated `publish_<schema>`, `unpublish_<schema>` and
233
233
  `archive_<schema>` tools use the same snake-case naming rule, are admin-only
234
234
  and take `{ id, expectedVersion }` through the shared generated validation.
235
235
 
236
- `/mcp/admin` admits only admins: `isAdmin(caller)` must hold. Better Auth’s admin plugin `adminRoles` defines this layer (default `["admin"]`); the application supplies its verified Caller. MCP bodies are limited
236
+ `/mcp/admin` admits only admins: `isAdmin(caller)` must hold. It lists only admin-surface tools; public Views and Procedures stay on `/mcp`, and generation never copies them across. An admin's AI that changes records also needs admin Views to find them and read back the committed row and its version (search, detail), declared by the app; the [acceptance design audit](../guides/agent-workflow-acceptance.md#audit-mcp-tool-design-before-implementation) checks this. Better Auth’s admin plugin `adminRoles` defines this layer (default `["admin"]`); the application supplies its verified Caller. MCP bodies are limited
237
237
  to 1 MiB (1,048,576 bytes). Resource metadata challenges use the origin of
238
238
  `request.url`; behind a proxy, the app must pass a Request with its public
239
239
  origin so the audience and metadata URL point to the client-visible service.
@@ -352,7 +352,10 @@ REST and MCP View pages expose declared JSON columns as JSON values and declared
352
352
  boolean columns as booleans, including explicitly authored SQL aliases. SQLite
353
353
  and D1 conversions are compiled into only the selected boundary pages;
354
354
  PostgreSQL already-native JSON/boolean values pass through. SQL NULL stays null and absent
355
- fields stay absent. Expressions without declared column lineage stay native.
355
+ fields stay absent. A computed output that is a JSON document by construction
356
+ (`json_group_array`, `json_set`, `jsonb_agg`, `jsonb_build_object` and the like, a scalar
357
+ subquery of one, or a `COALESCE` of those) is converted like a declared JSON column, so it
358
+ is a JSON value on every target. Other expressions without declared column lineage stay native.
356
359
  Malformed persisted JSON becomes a redacted internal failure. The conversion
357
360
  adds no database operations and does not mutate native query rows.
358
361
 
@@ -1,12 +1,14 @@
1
1
  # View
2
2
 
3
- A View requires `surface: public | admin | internal` and one `sql` SELECT. Optional keys include title, description, input, requires, restrict, uiSchema (admin only), and cache. The app imports its generated typed function and supplies a native handle, verified Caller and driver-ready input.
3
+ A View requires `surface: public | admin | internal` and one `sql` SELECT. Optional keys include title, description, input, requires, restrict, uiSchema (admin only), and cache. The app imports its generated typed function and supplies a native handle, verified Caller and driver-ready input; a JSON input (an array read by `json_each(input.ids)`) takes its authored array or object, which the generated query serializes.
4
4
 
5
5
  ## SQL
6
6
 
7
7
  PostgreSQL syntax is parsed and checked at generation. Reads may reference declared Schemas, CTEs and reusable internal Views; no auth/system tables or arbitrary external relations. The compiler adds scope, TTL, publication and translation-parent visibility at every relation. SQL policy is not injected at request time.
8
8
 
9
- SQLite supports the compiler's subset: scalar expressions, comparisons, AND/OR/NOT, CASE, COALESCE, supported casts, INNER/LEFT JOIN, subqueries, grouping/aggregates, supported windows, ordered LIMIT, date operations and declared Mantle search/geo functions. PostgreSQL additionally supports recursive CTEs, lateral relations, more window/aggregate forms, ILIKE and jsonb operators where the allowlist permits them. Unsupported SQL fails with a source position; generation is the authority for each target.
9
+ The SQL each dialect accepts, for Views and Procedures alike, is listed in [the SQL reference](sql.md); [SQL patterns](../guides/sql-patterns.md) shows the common ones. Unsupported SQL fails with a source position; generation is the authority for each target.
10
+
11
+ `*` and `t.*` expand to the Schema's declared fields only. Select `id`, `version`, `created_at` and `updated_at` by name when a client needs them (a write's `expectedVersion`, a detail link, keyset paging).
10
12
 
11
13
  OFFSET, arbitrary functions/relations, DDL and transaction control are not a read escape hatch. Use keyset paging. Select latitude/longitude explicitly instead of native geo projections.
12
14
 
@@ -16,6 +18,12 @@ OFFSET, arbitrary functions/relations, DDL and transaction control are not a rea
16
18
 
17
19
  ## Search and totals
18
20
 
21
+ `mantle.search(<alias>, <text>)` matches the Schema's declared `searchableFields`, and `mantle.search_rank(<alias>)`
22
+ orders by relevance; qualify both with `mantle.`. An optional search reads
23
+ `WHERE p.active AND (input.q IS NULL OR mantle.search(p, input.q))`, and a ranked one
24
+ `WHERE mantle.search(p, input.q) ORDER BY mantle.search_rank(p), p.id` (see `examples/reference-service` `catalog.yaml`
25
+ for cursor paging over the rank).
26
+
19
27
  Views using `mantle.search`, Store search and Admin list search share the dialect search: SQLite/D1 use FTS for queries of 3 or more characters (shorter queries use LIKE); PostgreSQL uses ILIKE over the declared fields and scans by default. A View does not make PostgreSQL search faster. For large PostgreSQL tables, the application's agent adds trigram indexes in a migration, which ILIKE then uses with no code change:
20
28
 
21
29
  ```sql
@@ -27,7 +35,7 @@ Store keeps `find`/`first` search and `count`. `count` scans every matching row;
27
35
 
28
36
  ## Results and paging
29
37
 
30
- Each SELECT output needs a unique static name. Unquoted SQL identifiers fold to lower case; use an explicit quoted alias to preserve case (`r.reqStatus AS "reqStatus"`). Generation warns when an unaliased direct declared field would use its lower-case storage name. Declared-column lineage supplies generated types; computed outputs remain native/unknown. SQL formats declared date-time/date outputs, while computed timestamps retain driver values. See [native execution](../guides/native-execution.md).
38
+ Each SELECT output needs a unique static name. Unquoted SQL identifiers fold to lower case; use an explicit quoted alias to preserve case (`r.reqStatus AS "reqStatus"`). Generation warns when an unaliased direct declared field would use its lower-case storage name. Declared-column lineage supplies generated types; computed outputs remain native/unknown, except a top-level output that is a JSON document by construction (a JSON aggregate or builder, a scalar subquery of one, or a `COALESCE` of those; see [SQL patterns](../guides/sql-patterns.md#a-row-with-its-children-in-one-read)), which is typed JSON. An output is non-null (`string`, not `string | null`) only when it reads unchanged a column its table declares NOT NULL (`id`, `version`, `created_at`, `updated_at`, a publishing `status`, or a [required](schema.md#fields-and-storage) field of an operational Schema) directly or through FROM subqueries; the right side of a LEFT JOIN, an aggregate, a column beside an aggregate without GROUP BY, a CTE and an expression stay nullable. SQL formats declared date-time/date outputs, while computed timestamps retain driver values. See [native execution](../guides/native-execution.md).
31
39
 
32
40
  Bare `views/<encoded-name>.ts` returns row arrays. Proved pageable exposed/internal Views also receive native `paged/views/*`, returning `{ rows, nextCursor? }`. Selected REST/MCP boundaries use separate `paged/wire/views/*` SQL when declared integers require exact transport projections; bare modules also export `WireRow`. Indexed ordering after fixed equality prefixes ([index prefixes](schema.md#index-prefixes-for-paged-views)), non-null typed keys, complete groups and singleton joins/lookups are checked at generation. Unsupported internal paging retains its bare View; exposure requires a supported page contract. Aggregates still scan/group their sources: inspect EXPLAIN.
33
41
 
@@ -15,15 +15,36 @@ From 0.2.0-alpha.7, Mantle publishes three packages together at one version:
15
15
  and `@aotter/mantle-admin` (optional Admin server, SPA and MCP App).
16
16
  Pin every package you use to the same exact version and upgrade them together.
17
17
 
18
- ## 0.2.0 — in preparation
19
-
20
- ### Upgrading to 0.2.0-alpha.9
18
+ ## 0.2.0 (2026-10-19)
19
+
20
+ The first stable native 0.2 release compiles closed v2 manifests to SQL and
21
+ TypeScript for Bun SQLite/PostgreSQL and Cloudflare D1/PostgreSQL. It ships
22
+ three matching packages: Core, the pure UI kit and the optional application-built
23
+ Admin. Applications own routing, verified identity, database handles, migrations
24
+ and deployment; selected REST, MCP, public pages, publishing, translations and
25
+ media compose around the generated operations.
26
+
27
+ This is a breaking upgrade from 0.1.x: follow the [0.1-to-0.2 migration
28
+ guide](../../upgrade-0.1-to-0.2.md), use a new 0.2 database and explicitly move
29
+ data. From alpha.11, pin the three selected packages to 0.2.0, regenerate and
30
+ review owned output; the stable version commit changes release metadata and
31
+ documentation rather than adding another runtime API.
32
+
33
+ [GitHub release](https://github.com/aotter/mantle/releases/tag/v0.2.0) is the
34
+ complete change history. Known follow-ups remain tracked: [Admin/authoring
35
+ refinements](https://github.com/aotter/mantle/issues/1752), [production social
36
+ and magic-link recipe verification](https://github.com/aotter/mantle/issues/1273)
37
+ and [real Sites guide verification](https://github.com/aotter/mantle/issues/1272).
38
+ Those provider-specific paths are not claimed as verified by local OTP or
39
+ native-host tests.
40
+
41
+ ### Upgrading to 0.2.0
21
42
 
22
43
  Pin every selected `@aotter/mantle`, `@aotter/mantle-ui` and
23
- `@aotter/mantle-admin` dependency to `0.2.0-alpha.9`, refresh the lockfile,
44
+ `@aotter/mantle-admin` dependency to `0.2.0`, refresh the lockfile,
24
45
  then regenerate and check the application's owned artifacts. Keep existing
25
46
  production migrations application-owned; generated initial DDL is for an empty
26
- database. This alpha includes breaking Admin authoring and paging changes.
47
+ database. This release includes breaking Admin authoring and paging changes.
27
48
 
28
49
  1. **Build output and host configuration.** `mantle bundle` writes server
29
50
  modules to `dist/worker` and static files to `dist/client`, including
@@ -112,7 +133,7 @@ Existing alpha.6 applications require a source migration:
112
133
  application code. Review [authorization and Caller wiring](../concepts/runtime-and-adapters.md)
113
134
  and regenerate all owned artifacts before running `generate --check`.
114
135
 
115
- The [reference Worker](https://github.com/aotter/mantle/tree/v0.2.0-alpha.9/docs/examples/reference-service) and
136
+ The [reference Worker](https://github.com/aotter/mantle/tree/v0.2.0/docs/examples/reference-service) and
116
137
  [app shell](../../examples/app-shell/) demonstrate the current composition.
117
138
  The following earlier-alpha notes are historical; the current handbook and
118
139
  the native migration above supersede their runtime and CLI instructions.
@@ -0,0 +1,45 @@
1
+ ---
2
+ description: The entry point `mantle help` prints first: choose a complete app or generation only, use examples as references, accept before done.
3
+ ---
4
+ # Build with an agent
5
+
6
+ `mantle help` prints this page. `mantle help <topic>` prints one handbook page and ends with the commands for its next page and for every page it links; run those to read further.
7
+ Topics are the handbook paths below (for example `guides/native-admin`) or their last segment when unique (`native-admin`).
8
+ The installed package's `docs/` is the source of every topic, so the text always matches the installed version.
9
+
10
+ ## Choose how much Mantle owns
11
+
12
+ | You want | Do |
13
+ | --- | --- |
14
+ | A complete application | `mantle init [directory]`, then follow its `README.md`. It writes the app shell once: Better Auth email OTP with `admin` and `user`, the Admin console, MCP for both roles, REST, and a member `/sign-in` page. Existing files stop it; `--dry-run` lists what it would write. |
15
+ | Only typed SQL and bindings inside an app you already have | Skip `init`. Write manifests and run `mantle generate <target> <output-directory> <manifest>...`; see `mantle help project-and-cli` and `mantle help typed-queries`. |
16
+
17
+ `init` writes the `cloudflare-d1` shell, the default target. `bun-sqlite`, `bun-postgres` and `cloudflare-postgres`
18
+ generate the same bindings; their app entry is yours (`mantle help service-entry`).
19
+
20
+ `init` writes app code you then own; it never runs again. `generate` writes only `generated/` and Wrangler build keys,
21
+ and overwrites them on every run. When `generate` sees a selected surface (REST, MCP, Admin) whose generated module
22
+ the app entry (`--entry`) never imports, it warns and names the topic that shows the wiring.
23
+
24
+ ## Build from a product brief
25
+
26
+ 1. `mantle init`, install, then read the app's `README.md` and search for `AGENT:`.
27
+ 2. Map the brief to the [four atoms](../concepts/four-atoms.md): Schemas, Views, Procedures, Triggers.
28
+ Decide what each role (`admin`, `user`) does on each surface: Admin, MCP, REST and any visitor pages.
29
+ 3. For each need, find the pattern in `mantle help examples` (the **Combining** table) and read that example's
30
+ manifest and source. Examples are references: take the pattern into your own manifests; do not copy an example
31
+ directory into the app.
32
+ - Business rules (status transitions, approval thresholds, stock, guards, several rows that commit together)
33
+ belong in SQL Procedures, not in handler code around separate writes: `mantle help sql-patterns`, and
34
+ `mantle help sql` for the SQL each dialect accepts.
35
+ - Visitor pages (anything anonymous and indexable) follow `mantle help public-pages`: server-rendered HTML,
36
+ canonical and Open Graph tags, JSON-LD and an answer block on detail pages, Markdown alternates, `/sitemap.xml`,
37
+ `/robots.txt` and `/llms.txt`. A brief that names a site, shop or catalogue for visitors includes all of them.
38
+ 4. `pnpm generate`, implement every handler it lists, `pnpm check`, then `pnpm smoke`.
39
+ 5. Before reporting the app or a feature done, run [role-only acceptance](../guides/agent-workflow-acceptance.md)
40
+ (`mantle help agent-workflow-acceptance`). Scripted smoke is not acceptance. `pnpm accept` starts the app and hands
41
+ each role to a fresh evaluator as a URL that carries that role's session.
42
+
43
+ The first verified sign-in with `ADMIN_EMAIL` becomes the admin. Further administrators are added in Admin:
44
+ **More → Administrators** invites by email, and **More → Members** grants Admin to an existing member.
45
+ [Authentication](../cloudflare/authentication.md#identity-helpers) documents the same operations as an API.
@@ -23,10 +23,16 @@ tool names or writing manifests/handlers.
23
23
  ## Commands
24
24
 
25
25
  ```sh
26
+ mantle help [topic] # these docs, one page at a time; start here
27
+ mantle init [directory] [--dry-run] # write the cloudflare-d1 app shell once; never overwrites
26
28
  mantle generate <target> <output-directory> <manifest>... [options]
27
29
  mantle bundle <entry> <outdir> [--assets <dir>]
28
30
  ```
29
31
 
32
+ `init` writes application code the app then owns. `generate` writes only generated output and Wrangler build keys,
33
+ on every run; after writing, it follows the `--entry` file's imports and warns for each selected REST, MCP or Admin
34
+ module the entry never imports.
35
+
30
36
  | Target | Dialect | Handle |
31
37
  | --- | --- | --- |
32
38
  | `bun-sqlite` | sqlite | bun:sqlite Database |
@@ -3,7 +3,7 @@ description: Generate a small native D1 service and mount an application-owned W
3
3
  ---
4
4
  # Quickstart: a service on Workers
5
5
 
6
- Create an application outside the SDK checkout. Install an exact released `@aotter/mantle` version, TypeScript, Wrangler and Cloudflare Worker types; `node_modules/@aotter/mantle/docs/examples/app-shell/package.json` lists the versions this release is tested with. Read that installed version's handbook; this page describes the native 0.2.0 contract.
6
+ Create an application outside the SDK checkout. Install stable `@aotter/mantle@0.2.0` exactly, TypeScript, Wrangler and Cloudflare Worker types; `node_modules/@aotter/mantle/docs/examples/app-shell/package.json` lists the versions this release is tested with. Read that installed version's handbook; this page describes the native 0.2.0 contract.
7
7
 
8
8
  Save `manifests/tickets.yaml`:
9
9
 
@@ -22,11 +22,12 @@ for the generate warning and deliberate asymmetry exception.
22
22
  Keep optional modules out of unused entry imports and auth dynamically loaded.
23
23
  Run actual public/member/admin HTTP, MCP and UI scenarios; verify stored state,
24
24
  visibility, OCC and restart persistence, not just a 200 response.
25
- Before reporting any user-facing work as done, follow the [independent
26
- acceptance guide](../../handbook/guides/agent-workflow-acceptance.md): run it
27
- with a fresh agent on the surface its user uses. This is not limited to MCP; if
28
- it was not run, say so explicitly and report the work as UNVERIFIED rather than
29
- done. For MCP workflows, also run the guide's design audit before implementing
25
+ Before reporting any new app or feature as done, follow the [independent
26
+ acceptance guide](../../handbook/guides/agent-workflow-acceptance.md): write a
27
+ private rubric, then give one fresh agent per role (`admin`, `user`) only its
28
+ role and entry points, across every surface it uses. This is not limited to
29
+ MCP; if it was not run, say so explicitly and report the work as UNVERIFIED
30
+ rather than done. For MCP workflows, also run the guide's design audit before implementing
30
31
  tools.
31
32
 
32
33
  For native Admin, read the [current guide](../../handbook/guides/native-admin.md):
@@ -20,6 +20,8 @@ For 0.1.x, follow [Upgrade 0.1 to 0.2](../../upgrade-0.1-to-0.2.md): give the
20
20
  against a 0.1.x database. Between 0.2 versions, apply reviewed migrations
21
21
  rehearsed on a copy.
22
22
 
23
+ For the stable 0.2 line, pin every selected Mantle package to `0.2.0`; do not upgrade an existing application by following a moving dist-tag.
24
+
23
25
  Pin exact target SDK/peer versions and read its release notes and Admin build
24
26
  instructions. Core-to-native replaces app imports/wiring; it is not
25
27
  a package-only update. For 0.2-to-0.2 updates, preserve users, sessions, content
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aotter/mantle",
3
- "version": "0.2.0-alpha.9",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "engines": {
@@ -106,7 +106,24 @@
106
106
  "wrangler": "4.145.0",
107
107
  "vitest": "^5.0.1"
108
108
  },
109
- "description": "Mantle: a manifest-driven service core. Store authored as SQL, surfaces as Fetch functions, and the mantle CLI.",
109
+ "description": "Agent-native app SDK and backend compiler: YAML manifests to native SQL (SQLite, Cloudflare D1, PostgreSQL) and typed TypeScript; optional REST and MCP.",
110
+ "keywords": [
111
+ "mantle",
112
+ "agent-native",
113
+ "sdk",
114
+ "backend",
115
+ "yaml",
116
+ "code-generation",
117
+ "sql",
118
+ "sqlite",
119
+ "postgresql",
120
+ "cloudflare-d1",
121
+ "cloudflare-workers",
122
+ "bun",
123
+ "typescript",
124
+ "mcp",
125
+ "model-context-protocol"
126
+ ],
110
127
  "license": "Apache-2.0",
111
128
  "homepage": "https://mantle.tools/",
112
129
  "repository": {
@@ -114,13 +131,16 @@
114
131
  "url": "git+https://github.com/aotter/mantle.git",
115
132
  "directory": "packages/mantle"
116
133
  },
134
+ "bugs": {
135
+ "url": "https://github.com/aotter/mantle/issues"
136
+ },
117
137
  "publishConfig": {
118
138
  "access": "public",
119
139
  "tag": "alpha",
120
140
  "registry": "https://registry.npmjs.org"
121
141
  },
122
142
  "scripts": {
123
- "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.lib.json && node scripts/bundle-compiler.mjs && node scripts/prune-declarations.mjs",
143
+ "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.lib.json && node ../../scripts/strip-comments.mjs tsconfig.lib.json && node scripts/bundle-compiler.mjs && node scripts/prune-declarations.mjs",
124
144
  "typecheck": "tsc --noEmit -p tsconfig.json",
125
145
  "test": "bun test tests/*.test.ts && bun run test:compiler",
126
146
  "generate": "bun src/infrastructure/cli.ts",
package/skills/README.md CHANGED
@@ -27,7 +27,7 @@ release tag as the application's SDK; alpha releases on `develop` can be newer
27
27
  than the default `main` branch. For a skill-only installation:
28
28
 
29
29
  ```sh
30
- npx skills add https://github.com/aotter/mantle/tree/v0.2.0-alpha.9 --skill mantle
30
+ npx skills add https://github.com/aotter/mantle/tree/v0.2.0 --skill mantle
31
31
  ```
32
32
 
33
33
  The explicit skill name excludes repository maintainer skills. The manifests
@@ -17,9 +17,19 @@ authority: `node_modules/@aotter/mantle/README.md` and
17
17
  `node_modules/@aotter/mantle/docs/`. Prefer them over this file, the SDK
18
18
  checkout, or any online copy.
19
19
 
20
+ ## Start with `mantle help`
21
+
22
+ Run `mantle help` first (in a project: `npx mantle help`; before one exists:
23
+ `npx @aotter/mantle@<exact version> help`). It prints the installed version's
24
+ entry page and every topic; `mantle help <topic>` prints one page with the next
25
+ and linked topics as commands. Follow it rather than browsing the SDK checkout
26
+ or an online copy.
27
+
20
28
  ## Find the project and its version
21
29
 
22
30
  Work in the user's application, outside the SDK checkout. Read package.json, the lockfile, installed SDK README and node_modules/@aotter/mantle/docs/. Those files define the exact version's contract. Preserve existing identity, handles, routes and resources.
31
+ For existing older applications, use their installed docs until the user requests an upgrade; if an older version
32
+ lacks a guide named here, record the missing guidance and keep that version's contract.
23
33
 
24
34
  ## Choose the installed package skill
25
35
 
@@ -29,129 +39,38 @@ Work in the user's application, outside the SDK checkout. Read package.json, the
29
39
  - Read `node_modules/@aotter/mantle/docs/skills/provision/SKILL.md` only for an authorized production deployment.
30
40
  - Read `node_modules/@aotter/mantle/docs/skills/media-gc/SKILL.md` only to audit or remove stale uncommitted media uploads.
31
41
 
32
- For a new project, install an exact published SDK version with matching selected Admin/UI packages. Node must be 22.22 or newer. Add TypeScript and target types; Workers also need Wrangler. `node_modules/@aotter/mantle/docs/examples/app-shell/package.json` lists the Wrangler, TypeScript and type-package versions this release is tested with. Optional auth/MCP peers use the installed SDK's package.json versions. Read the installed handbook's start/project-and-cli.md before choosing a target.
33
-
34
- For a new Cloudflare app, copy `node_modules/@aotter/mantle/docs/examples/app-shell/` first, then the closest example from `docs/examples/README.md` on top of it, and edit from there: manifests, `src/handlers.ts`, `src/routes.ts`, `src/schedule.ts`, `smoke.mjs`. Search for `AGENT:` to find what the app must fill in; the example pages are unstyled on purpose, so styling them is part of that work. Before reporting done, resolve each `AGENT:` or name the ones you left. Keep the two roles `admin` and `user` unless the user asks for more.
35
-
36
- Before reporting any user-facing work as done, follow the installed
37
- `node_modules/@aotter/mantle/docs/handbook/guides/agent-workflow-acceptance.md`:
38
- run independent acceptance with a fresh agent on the surface its user uses.
39
- This is not limited to MCP; if it was not run, say so explicitly and report the
40
- work as UNVERIFIED rather than done. For MCP workflows, also run the guide's
41
- design audit before implementing tools. This workflow practice applies to every
42
- installed SDK version: if its guide is older or MCP-scoped, apply its fresh-agent
43
- round loop to the user-facing surface actually built. If an older installed
44
- version lacks this guide, record the missing guidance and preserve that version's
45
- contract; do not silently upgrade or substitute checkout/online docs.
46
-
47
- For existing older applications, use their installed docs until the user requests an upgrade. For partial adoption read node_modules/@aotter/mantle/docs/skills/integrate/SKILL.md.
48
-
49
- ## Generated native applications
50
-
51
- Follow the installed (or workspace-linked) package README for generation, Workers
52
- builds and D1 Sessions; the app owns its entry and routing. Read that
53
- version's `docs/handbook/reference/features.md` before choosing declarations. Keep auth behind
54
- `authLoader(() => import(...))` to pass the startup guard. Install only the exact
55
- released version shown by the installed/target package
56
- contract. Replacing Core needs explicit app wiring and reviewed deployment
57
- migrations; never use initial schema.sql as an in-place production upgrade.
58
- Pass outgoing `responseHeaders` to `resolveCaller` so Better Auth's session cookie
59
- can slide. Native auth adapters use joins by default; keep the joined primary
60
- session/user check, including before any refresh from secondary storage.
61
- Only positively classified View/web reads may pass `readOnly: true`, with official
62
- cookieCache `{enabled:true,maxAge:60,refreshCache:false,version:"read-60-v1"}` and
63
- no KV. Bump version when enabling/changing the policy; writes, Admin, consent,
64
- unknown/ref/media calls stay fresh. Read the installed auth docs before MCP caching.
65
-
66
- Use the workflow **generate → implement listed handlers → check**. In an app
67
- copied from the installed app shell, its scripts are the canonical commands:
68
-
69
- ```sh
70
- pnpm generate
71
- pnpm check
72
- ```
73
- Implement every printed `Handlers` key
74
- and pass the implementations at `store(db, caller, { handlers })` (and selected
75
- surface options).
76
- Generated Store metadata initializes on its first use, once per module. Bind
77
- `store(db, caller, options?)` for the current request; never reuse one caller's
78
- bound Store for another caller.
79
-
80
- Use `paged/views/<encoded-name>.ts` for bounded cursor walks, including internal
81
- Views that satisfy the installed package's generation-time paging proofs.
82
- Call its default export with `(db, caller, input, { limit?, cursor? })`; it returns
83
- `{ rows, nextCursor? }`. Internal paging creates no public route/tool. Keep bare
84
- Views for authored-query row arrays, and inspect native EXPLAIN/source indexes
85
- when grouping: cursor paging does not eliminate aggregation work.
86
- Bare Views, Store and SQL Procedure results keep driver-native scalar values.
87
- Selected REST/MCP/Admin boundaries return target-identical `WireRow` types:
88
- declared integers become safe numbers or exact decimal strings, SQLite/D1
89
- JSON/booleans become JSON values/booleans, and compiler-declared decimals become
90
- strings. Selected SQL reads integer text before driver rounding; it cannot repair
91
- already-rounded writes. Use exported `WireRow` for clients and `Row` for bare
92
- queries. Computed/ref results remain app-owned.
93
-
94
- Commit everything `mantle generate` writes, including generated TypeScript, SQL and
95
- Wrangler edits; keep bundle `dist/` untracked. Never hand-edit generated files.
96
- Resolve conflicts by merging manifests/options and regenerating. Add the printed
97
- `.gitattributes` line and run the same generate command with `--check` in CI.
98
- Compose the app-owned `fetch` using `docs/handbook/cloudflare/service-entry.md`: auth/consent, Admin,
99
- MCP, REST behind one request-local origin-checked resolver; keep heavy auth dynamic.
100
- Pair every HTTP Trigger with an MCP Trigger by default; see `docs/handbook/reference/trigger.md` for the generate warning and deliberate asymmetry exception.
101
-
102
- For selected native Admin, follow the version-matched
103
- `docs/handbook/guides/native-admin.md`: select `--admin` (or `admin: true`), then
104
- build and serve the application-owned Admin with `mantle bundle` as that guide
105
- describes.
106
- Admin reads share generated whitelisted native View templates, writes use native
107
- Procedure fragments and generated checks. Ref operations retain typed `ctx.store`
108
- at request time. Verify member/frontend and admin/Admin OTP, tenant/privacy/TTL,
109
- publishing, paging, statistics, exact OCC, ref effects and the actual console;
110
- never retry unknown 503 writes automatically. Read the installed Admin README
111
- for raw-body limits, CSP/assets, 22 languages and the developer workspace.
112
- Use only versions actually published, or the explicitly linked workspace packages.
113
-
114
- For generated native applications, optional site settings/media are selected
115
- with `--site <module>` exporting `siteDefaults`. Apply the emitted deployment DDL;
116
- wire application-owned object storage as documented in `docs/handbook/cloudflare/media-r2.md`.
117
-
118
- For a generated public website, follow [web mounting and acceptance](references/web.md).
119
- The app renders HTML and inserts `site.head()`; generated `web.ts` serves the
120
- indexes and Markdown alternates.
121
-
122
- ## Complete websites
123
-
124
- For a requested complete website, apply [website defaults](references/website.md)
125
- without asking the user to enumerate OTP, media, Admin or SEO. Defaults are
126
- acceptance requirements, not evidence that a missing renderer is implemented.
127
- Do not report a complete website with blocked required checks.
128
-
129
- Run the [local HTTP acceptance](references/local-testing.md) for handlers and selected interfaces; compilation does not execute their behavior. Account for every declared interface as verified, intentionally not exposed, or deferred/unverified with a reason in the existing application handoff. An early preview may defer remote MCP; preserve that pending work. WebMCP, cookie-session discovery and OAuth Bearer/client integration are separate checks.
130
-
131
- ## Self-hosted deployment
132
-
133
- Deploy only when the user requests it. Follow the application's selected host
134
- and the installed `docs/skills/provision/SKILL.md`; use the platform's native
135
- workflow. Local project creation alone is not deployment authorization.
42
+ ## New application
136
43
 
137
- ## Existing project and handoff
44
+ Node must be 22.22 or newer. Stable 0.2.0 is available as `@aotter/mantle@0.2.0`; pin selected Mantle packages to that exact version. Create it with `npx @aotter/mantle@<exact version> init <directory>`, then follow
45
+ `mantle help` ("Build from a product brief") and the app's `README.md`. Without the user listing them, a complete app
46
+ includes:
47
+
48
+ - Email OTP sign-in, the two roles `admin` and `user`, Admin for `admin`, MCP for both roles (`/mcp`, `/mcp/admin`)
49
+ and REST: the shell already wires them; keep them.
50
+ - Real business rules in SQL Procedures (`mantle help sql-patterns`), not in handler code around separate writes.
51
+ - For visitor pages, everything in `mantle help public-pages` (SEO, AEO, sitemap, robots, `llms.txt`), styled.
52
+ - Local HTTP checks (`mantle help local-testing`), then independent acceptance with `pnpm accept`
53
+ (`mantle help agent-workflow-acceptance`).
138
54
 
139
- Preserve the user's manifests, `src/` files, identity, package versions and
140
- provider resources. The app writes its entry (`src/worker.ts` by default;
141
- `--entry` selects another). Workers generation creates or updates Wrangler's
142
- split-build settings and public-fetch compatibility flags. Run the same generation command (target, output directory,
143
- manifests and options) with `--check`, and the project's own tests,
144
- before and after a change.
55
+ Take patterns from `mantle help examples` into the app's own manifests and `src/`; do not copy an example directory
56
+ over the app. Resolve each `AGENT:` or name the ones you left. Generation only, inside an app you already have: skip
57
+ `init` and use `mantle generate` (`mantle help project-and-cli`).
145
58
 
146
- To add Mantle to an application that has its own code, users and data, read
147
- `node_modules/@aotter/mantle/docs/skills/integrate/SKILL.md` first.
59
+ ## Accept before reporting done
148
60
 
149
- Report the project path, the exact Mantle version, what you changed, the checks
150
- you ran with their results, the independent acceptance verdict (CONVERGED / NOT
151
- CONVERGED / BLOCKED / UNVERIFIED), and anything left for the user.
61
+ Before reporting any new app or feature as done, follow `mantle help agent-workflow-acceptance`: a private rubric,
62
+ then one fresh agent per role given only its role and the URLs `pnpm accept` prints, at most three rounds. If it was
63
+ not run, report the work as UNVERIFIED rather than done; if it could not run, BLOCKED.
64
+
65
+ ## Deployment
66
+
67
+ Deploy only when the user requests it, following the installed `docs/skills/provision/SKILL.md` and the selected
68
+ host's native workflow. Local project creation alone is not deployment authorization.
69
+
70
+ ## Existing project and handoff
152
71
 
153
- ## Data paths
72
+ Preserve the user's manifests, `src/` files, identity, package versions and provider resources. Run the project's
73
+ generate command with `--check` and its own tests before and after a change.
154
74
 
155
- Follow the installed `docs/handbook/guides/typed-queries.md` for Store,
156
- Procedure and View data paths; the service composition guide owns request-local
157
- Caller binding.
75
+ Report the project path, the exact Mantle version, what you changed, the checks you ran with their results, the
76
+ independent acceptance verdict (CONVERGED / BLOCKED / UNVERIFIED), and anything left for the user.