@aotter/mantle 0.2.0 → 0.2.1-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/README.md +6 -2
  2. package/dist/build/compiler.d.ts +6 -6
  3. package/dist/build/compiler.js +6 -3
  4. package/dist/build/spec/domain/model/ManifestGrammar.d.ts +39 -1
  5. package/dist/build/spec/domain/model/RuntimePlan.d.ts +16 -3
  6. package/dist/build/spec/domain/model/SqlIr.d.ts +30 -0
  7. package/dist/build/spec/kernel/diagnostic.d.ts +1 -1
  8. package/dist/domain/describe/chunk-CW7X5Q5T.js +1 -0
  9. package/dist/domain/describe/index.d.ts +1 -0
  10. package/dist/domain/describe/index.js +1 -1
  11. package/dist/domain/describe/match.d.ts +19 -0
  12. package/dist/domain/describe/public.d.ts +3 -0
  13. package/dist/domain/describe/public.js +1 -0
  14. package/dist/infrastructure/adminEntries.js +19 -3
  15. package/dist/infrastructure/bundle.js +43 -8
  16. package/dist/infrastructure/cli.js +1 -1
  17. package/dist/infrastructure/generate.js +17 -3
  18. package/dist/kernel/binds.d.ts +18 -3
  19. package/dist/kernel/binds.js +1 -1
  20. package/dist/kernel/contracts.d.ts +7 -0
  21. package/dist/kernel/contracts.js +9 -0
  22. package/dist/kernel/driver/bun-sqlite.js +3 -3
  23. package/dist/kernel/driver/d1.d.ts +1 -1
  24. package/dist/kernel/driver/d1.js +6 -3
  25. package/dist/kernel/driver/postgres.d.ts +2 -1
  26. package/dist/kernel/driver/postgres.js +11 -6
  27. package/dist/kernel/mcp-apps.d.ts +3 -2
  28. package/dist/kernel/mcp-apps.js +7 -2
  29. package/dist/kernel/mcp-handler.js +22 -9
  30. package/dist/kernel/mcp.d.ts +18 -0
  31. package/dist/kernel/mcp.js +1 -0
  32. package/dist/kernel/rest-router.js +3 -5
  33. package/dist/kernel/rest.d.ts +2 -1
  34. package/dist/kernel/rest.js +1 -0
  35. package/dist/kernel/route-pattern.d.ts +26 -0
  36. package/dist/kernel/route-pattern.js +34 -0
  37. package/dist/kernel/store.d.ts +15 -1
  38. package/dist/kernel/store.js +51 -18
  39. package/dist/usecase/adminBindings.js +2 -2
  40. package/dist/usecase/adminClient.js +4 -1
  41. package/dist/usecase/generate.js +7 -5
  42. package/dist/usecase/handlers.js +1 -1
  43. package/dist/usecase/publishing.js +20 -7
  44. package/dist/usecase/rest.js +5 -5
  45. package/dist/usecase/stores.js +8 -3
  46. package/docs/examples/README.md +1 -1
  47. package/docs/examples/app-shell/README.md +58 -10
  48. package/docs/examples/app-shell/package.json +6 -6
  49. package/docs/examples/club-events/manifests/club-events.yaml +2 -0
  50. package/docs/examples/generated-web/seed.sql +5 -4
  51. package/docs/examples/purchasing/manifests/purchasing.yaml +3 -0
  52. package/docs/handbook/cloudflare/media-r2.md +6 -3
  53. package/docs/handbook/cloudflare/service-entry.md +2 -0
  54. package/docs/handbook/concepts/lifecycle-and-locales.md +21 -1
  55. package/docs/handbook/examples/hub.md +1 -1
  56. package/docs/handbook/guides/admin-ui.md +1 -1
  57. package/docs/handbook/guides/agent-setup.md +13 -8
  58. package/docs/handbook/guides/agent-workflow-acceptance.md +129 -19
  59. package/docs/handbook/guides/local-testing.md +50 -5
  60. package/docs/handbook/guides/mcp-apps.md +40 -0
  61. package/docs/handbook/guides/native-admin.md +218 -11
  62. package/docs/handbook/guides/native-execution.md +5 -4
  63. package/docs/handbook/guides/revisions.md +584 -0
  64. package/docs/handbook/guides/size-and-startup.md +140 -0
  65. package/docs/handbook/guides/sql-patterns.md +47 -1
  66. package/docs/handbook/guides/typed-queries.md +8 -3
  67. package/docs/handbook/navigation.json +4 -0
  68. package/docs/handbook/reference/authorization.md +2 -0
  69. package/docs/handbook/reference/describe.md +56 -4
  70. package/docs/handbook/reference/diagnostics.md +12 -3
  71. package/docs/handbook/reference/procedure.md +4 -4
  72. package/docs/handbook/reference/schema.md +71 -7
  73. package/docs/handbook/reference/sql.md +2 -1
  74. package/docs/handbook/reference/surface.md +13 -2
  75. package/docs/handbook/reference/trigger.md +12 -0
  76. package/docs/handbook/releases/index.md +75 -1
  77. package/docs/handbook/start/agents.md +6 -2
  78. package/docs/handbook/start/project-and-cli.md +4 -0
  79. package/docs/handbook/start/quickstart-worker.md +1 -1
  80. package/docs/skills/develop/SKILL.md +15 -3
  81. package/docs/skills/integrate/SKILL.md +3 -1
  82. package/docs/skills/performance/SKILL.md +169 -0
  83. package/docs/skills/update/SKILL.md +9 -5
  84. package/docs/upgrade-0.1-to-0.2.md +151 -39
  85. package/package.json +10 -11
  86. package/skills/README.md +6 -2
  87. package/skills/mantle/SKILL.md +19 -5
  88. package/dist/domain/describe/chunk-SGMRVDGE.js +0 -1
  89. package/dist/domain/describe/target-hash.js +0 -1
@@ -1,26 +1,141 @@
1
1
  # Upgrading from 0.1.x to 0.2.0 (for AI agents)
2
2
 
3
- For **0.2.0-alpha.7 and later**, use the
4
- [native SDK migration](handbook/releases/index.md#native-sdk-migration-alpha7),
5
- [current CLI](handbook/start/project-and-cli.md) and
6
- [app shell](examples/app-shell/). The application owns routing,
7
- Auth, native handles and database migrations; the former `createMantle` runtime,
8
- host presets, boot-time convergence and CLI flags below have been replaced.
9
- Install the same exact version of `@aotter/mantle`, `@aotter/mantle-ui` and,
10
- when selecting Admin, `@aotter/mantle-admin`. Start with a new database for a
11
- 0.1.x migration and preserve every authorization, scope and OCC requirement.
12
- The remainder describes the earlier alpha contract for historical reference.
13
-
14
- No codemod, and no in-place upgrade of a 0.1.x database. A coding agent moves
15
- one project by hand with this guide. When it and
16
- [ADR-0032](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0032-store-centric-core-and-service-contract.md),
17
- [ADR-0033](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0033-storage-converges-to-the-plan.md),
18
- [ADR-0034](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0034-store-is-authored-as-sql.md),
19
- [ADR-0035](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0035-sql-dialects.md),
20
- [ADR-0036](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0036-host-and-dialect.md) or
21
- [ADR-0038](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0038-bun-native-host.md) disagree, the ADR wins.
22
-
23
- ## Rules
3
+ 0.2.0 replaces 0.1.x's grammar, runtime and storage. There is no codemod, no
4
+ migration tool and no in-place upgrade of a 0.1.x database: a coding agent
5
+ rebuilds the project on 0.2.0 and plans a reviewed data move for it.
6
+
7
+ 1. **Rebuild the project on 0.2.0** with the current handbook: the
8
+ [0.2.0 release notes](handbook/releases/index.md#upgrading-to-020) and the
9
+ [native SDK migration](handbook/releases/index.md#native-sdk-migration-alpha7),
10
+ the [CLI and generated ownership](handbook/start/project-and-cli.md), the
11
+ [app shell](examples/app-shell/) and the [Schema](handbook/reference/schema.md),
12
+ [View](handbook/reference/view.md) and [Procedure](handbook/reference/procedure.md)
13
+ references. The application owns routing, Auth, native handles and database
14
+ migrations. Install the same exact version of `@aotter/mantle`,
15
+ `@aotter/mantle-ui` and, when selecting Admin, `@aotter/mantle-admin`.
16
+ Preserve every authorization, scope and OCC requirement.
17
+ 2. **Move the data** into a new 0.2 database, as
18
+ [Moving data from 0.1.x](#moving-data-from-01x) describes.
19
+
20
+ The [appendix](#appendix-earlier-02-alpha-contract-historical) maps 0.1.x
21
+ manifests to an earlier 0.2 alpha. Its runtime, CLI, handler and data
22
+ instructions are out of date; use it only to recognise what a 0.1.x construct
23
+ meant, never as instructions.
24
+
25
+ ## Moving data from 0.1.x
26
+
27
+ These facts come from the 0.1.4 and 0.2.0 sources. Before relying on one,
28
+ check it against the application's own generated `schema.sql` and its 0.1.4
29
+ export; where they disagree, trust them and stop to ask. Write the plan down
30
+ for human review before it touches production data.
31
+
32
+ - **Rehearse on a copy.** Load a copy of the export into a scratch 0.2
33
+ database, check the result through the app (Views, Admin, sign-in), and only
34
+ then run the reviewed load once into the fresh production database, before
35
+ it takes traffic.
36
+ - **Never boot 0.2 against a 0.1.x database.** Apply the generated `schema.sql`
37
+ (it includes the site tables when the site module is selected) to an empty
38
+ database, then load rows into it.
39
+ - **Keep data files out of the repository.** Exports, dumps and copies hold
40
+ user emails and content: keep them outside the working tree or ignored, and
41
+ never commit them.
42
+
43
+ ### Writing entries: the Store or SQL
44
+
45
+ - **Through the SDK.** The generated `store(db, caller)`, or `ctx.store` in a
46
+ `ref` handler, with a trusted caller such as `{ kind: "system", reason }`
47
+ ([authorization](handbook/concepts/authorization.md)). It accepts no value
48
+ for `id`, `version`, `created_at`, `updated_at`, `author_id`, `status`,
49
+ `published_at` or the scope column, for any caller: it sets them itself, so new IDs, versions,
50
+ timestamps and authors replace the old ones. Publishing goes through the
51
+ generated `<schema>.publish` Procedure, which sets `published_at` to now
52
+ ([lifecycle](handbook/concepts/lifecycle-and-locales.md)). With new IDs,
53
+ remap every `x-mantle-ref`, every `translates.on` key and any ID embedded
54
+ in JSON; `x-mantle-ref` is not a foreign key, so a missed one dangles
55
+ quietly.
56
+ - **Through SQL**, to keep IDs, history and authors: `INSERT` the rows with
57
+ every native column set, as
58
+ [reserved entry columns](handbook/reference/schema.md#reserved-entry-columns)
59
+ lists. This bypasses row policy, so review it like a migration.
60
+
61
+ ### Entry columns on D1
62
+
63
+ | 0.1.4 | 0.2.0 |
64
+ |---|---|
65
+ | `_mantle_id` (UUID text) | `id` (text, unique) |
66
+ | `_mantle_version` (an OCC counter) | `version` |
67
+ | `_mantle_created_at`, `_mantle_updated_at` (epoch **milliseconds**) | `created_at`, `updated_at` (epoch **microseconds**: multiply by 1000). Set `updated_at`: it defaults to `0`, which Admin shows as 1970 |
68
+ | `_mantle_author_id` | `author_id`, the Better Auth `user.id` |
69
+ | `_mantle_status` (on every table) | `status` (`draft`, `published`, `archived`) on publishing Schemas only |
70
+ | none | `published_at` (publishing Schemas): 0.1.4 has no source; choose a value for each published row, `NULL` otherwise |
71
+ | the `scope` field | the same column, text, not null |
72
+ | `date-time` field, ISO text | integer microseconds since the epoch |
73
+ | `date` field, text | integer days since 1970-01-01 |
74
+ | object field (JSON text) | 0.1.4 had no geo type; if you remodel a position as a `format: geo` field, it is two columns, `<field>_lat` and `<field>_lng` |
75
+
76
+ - 0.2 tables on D1 are `STRICT`: a value of the wrong type fails the insert
77
+ instead of being stored.
78
+ - A `numeric(p,s)` field is an integer scaled by 10^s on D1: rescale a 0.1.4
79
+ REAL value before loading it.
80
+ - Schema `checks` run as `BEFORE INSERT` and `BEFORE UPDATE` triggers, so
81
+ loaded rows are checked.
82
+ - Full-text search and geo index tables are maintained by triggers: insert
83
+ only into the Schema tables.
84
+
85
+ ### Better Auth
86
+
87
+ 0.1.4 used Better Auth 1.7.2 with hand-written DDL; 0.2.0 uses 1.7.7, with the
88
+ tables `mantle generate --auth src/auth.ts` adds to `schema.sql`
89
+ ([auth](handbook/cloudflare/authentication.md)).
90
+
91
+ - The 0.2 caller subject is the Better Auth `user.id`. Keep user IDs, so that
92
+ `author_id` and owner scopes still name the right people.
93
+ - Copy `user` and `account` with explicit column lists on both sides; both
94
+ versions store dates as ISO text and booleans as 0/1 on D1. 0.1.4's
95
+ `account` has an `issuer` column and `UNIQUE(issuer, accountId)` that the
96
+ 0.2 table may not have; compare the two DDLs before copying.
97
+ - 0.1.4 roles `owner`, `editor` and `contributor` have no 0.2 meaning. The
98
+ admin layer is Better Auth's admin plugin `adminRoles` (default
99
+ `["admin"]`) and `adminUserIds`
100
+ ([authorization](handbook/reference/authorization.md)); decide each mapping
101
+ deliberately.
102
+ - A 0.1.4 `githubLogin` column must be declared in `user.additionalFields`
103
+ to keep it.
104
+ - Leave sessions, verification rows and OAuth tokens behind; users sign in
105
+ again. Mantle re-creates its OAuth resource rows from configuration
106
+ ([OAuth](handbook/guides/oauth.md)). Check `jwks`, OAuth clients and
107
+ consents against Better Auth 1.7.7 and your secrets and URLs before copying
108
+ any.
109
+ - The admin MCP endpoint moved from `/mcp/staff` to `/mcp/admin`; update MCP
110
+ client URLs.
111
+
112
+ ### Media
113
+
114
+ - `media_assets` has the same columns in 0.2, and its `created_at` stays in
115
+ epoch **milliseconds**. Copy the rows as they are.
116
+ - Each variant keeps its stored `storageKey` and `publicUrl`, so 0.1.x objects
117
+ in the same bucket keep working while their prefixes stay served
118
+ ([media](handbook/cloudflare/media-r2.md)).
119
+ - Do not carry over `pending_media_uploads`; an upload expires after 15
120
+ minutes.
121
+ - `site_config` keeps its `key`/`value` columns; compare its keys with the
122
+ generated `site-seed.sql` before copying.
123
+
124
+ ## Appendix: earlier 0.2 alpha contract (historical)
125
+
126
+ > **Historical, do not follow.** This appendix describes an earlier 0.2
127
+ > alpha: `createMantle`, `runtime.store`, host presets, boot-time convergence
128
+ > and CLI flags that 0.2.0 does not have. The current handbook and
129
+ > [Moving data from 0.1.x](#moving-data-from-01x) replace it. Where it and
130
+ > [ADR-0032](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0032-store-centric-core-and-service-contract.md),
131
+ > [ADR-0033](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0033-storage-converges-to-the-plan.md),
132
+ > [ADR-0034](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0034-store-is-authored-as-sql.md),
133
+ > [ADR-0035](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0035-sql-dialects.md),
134
+ > [ADR-0036](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0036-host-and-dialect.md) or
135
+ > [ADR-0038](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0038-bun-native-host.md)
136
+ > disagreed, the ADR won.
137
+
138
+ ### Rules
24
139
 
25
140
  - Work on a branch. Keep the 0.1.x project running until the 0.2.0 one passes
26
141
  its checks.
@@ -32,10 +147,10 @@ one project by hand with this guide. When it and
32
147
  refused in 0.2.0 and stamps nothing, so each one becomes `scope` or an
33
148
  explicit value in the write (section 3).
34
149
  - Give the 0.2.0 service a new database; never boot it against the 0.1.x
35
- one. Move data by export and import (section 5).
150
+ one. Move data as [Moving data from 0.1.x](#moving-data-from-01x) says.
36
151
  - `mantle generate --check` is the gate. Never hand-edit `.mantle/generated/`.
37
152
 
38
- ## 1. Packages
153
+ ### 1. Packages
39
154
 
40
155
  | 0.1.x | 0.2.0 |
41
156
  |---|---|
@@ -46,7 +161,7 @@ Remove every old package and install `@aotter/mantle` at the exact target
46
161
  version. `mantle generate` names any other package it needs, with the install
47
162
  command; it never installs anything.
48
163
 
49
- ## 2. `mantle.config.json`
164
+ ### 2. `mantle.config.json`
50
165
 
51
166
  0.1.4 had none; 0.1.5-alpha.1 wrote a `version: 1` one. Write version 2:
52
167
 
@@ -72,7 +187,7 @@ command; it never installs anything.
72
187
  - `host` and `dialect` are chosen on the first `mantle generate` (`--host`,
73
188
  `--dialect`); a rerun that asks for another is refused.
74
189
 
75
- ## 3. Manifests
190
+ ### 3. Manifests
76
191
 
77
192
  Set `apiVersion: cms.mantle.aotter.net/v2` on every document; a v1 document
78
193
  is refused.
@@ -81,7 +196,7 @@ is refused.
81
196
  `created_at`, `updated_at`, `author_id`. (`ctx.store` JSON keeps the
82
197
  camelCase names.)
83
198
 
84
- ### Schema
199
+ #### Schema
85
200
 
86
201
  | 0.1.x | 0.2.0 |
87
202
  |---|---|
@@ -113,7 +228,7 @@ which also accepts leading, trailing and doubled hyphens. On a Schema field
113
228
  that must not have them, add a `checks` entry:
114
229
  `"slug NOT LIKE '-%' AND slug NOT LIKE '%-' AND slug NOT LIKE '%--%'"`.
115
230
 
116
- ### View
231
+ #### View
117
232
 
118
233
  A View is one SQL `SELECT` in `spec.sql`, with `input` for its parameters.
119
234
  The SQL is PostgreSQL syntax: it names declared Schemas (never
@@ -134,14 +249,14 @@ Schema reference; do not repeat them.
134
249
  | free-text search over `searchableFields` | `WHERE mantle.search(p, input.q) ORDER BY mantle.search_rank(p)`; an unqualified `search` is refused |
135
250
  | `page` / `show` on the wire | `limit` / `cursor`, `nextCursor` in the response |
136
251
 
137
- ### Procedure
252
+ #### Procedure
138
253
 
139
254
  0.1.x builtin ops are `create`, `update`, `upsert`, `delete` and `archive`.
140
255
 
141
256
  | 0.1.x | 0.2.0 |
142
257
  |---|---|
143
258
  | builtin `create` | `handler: { sql: "INSERT INTO s (a, b) VALUES (input.a, input.b) RETURNING id" }` (on a `publishing` Schema the row starts as a draft, as before) |
144
- | builtin `update` (always version-checked, merges over the stored row) | `UPDATE s SET a = COALESCE(input.a, a), … WHERE id = input.id AND version = input.expectedVersion`. Keep both: without the version the lock is lost; without `COALESCE` an omitted optional field is cleared |
259
+ | builtin `update` (always version-checked, merges over the stored row) | `UPDATE s SET a = COALESCE(input.a, a), … WHERE id = input.id AND version = input.expectedVersion`. Keep both: without the version the lock is lost; without `COALESCE` an omitted optional field is cleared. `COALESCE` also means `null` cannot clear a field; give clearing its own flag input ([clear flag](handbook/guides/sql-patterns.md#change-only-the-fields-given-coalesce), [leave out, replace or clear](handbook/guides/native-admin.md#leave-out-replace-or-clear)) |
145
260
  | builtin `upsert` with `match` | `INSERT … ON CONFLICT (<match columns>) DO UPDATE SET x = EXCLUDED.x`; the conflict columns are a unique index; on a scoped Schema the scope field may be named or left out, Store adds it |
146
261
  | builtin `delete` | `DELETE FROM s WHERE id = input.id AND version = input.expectedVersion` |
147
262
  | builtin `archive` | a `ref` handler: `ctx.store.write([{ update: "s", set: { status: "archived" }, where: { id }, lock: expectedVersion }])` (SQL cannot set `status`) |
@@ -153,7 +268,7 @@ timestamps. On a scoped Schema an `INSERT` names no `id`. Several statements
153
268
  in one `sql` apply together or not at all. `requires.auth` and
154
269
  `requires.guard` are unchanged.
155
270
 
156
- ### Trigger
271
+ #### Trigger
157
272
 
158
273
  - `schedule` cron is POSIX: weekday 0 = Sunday. 0.1.x used Cloudflare's
159
274
  numbering (1 = Sunday), so subtract 1 from every weekday number, in ranges
@@ -164,7 +279,7 @@ in one `sql` apply together or not at all. `requires.auth` and
164
279
  `continue` now blocks the write), an after hook is best effort.
165
280
  - `http`, `mcp` and other `lifecycle` fields keep their shape.
166
281
 
167
- ## 4. Handler code
282
+ ### 4. Handler code
168
283
 
169
284
  A `ref` handler is `(input, ctx) => output` in the service's `handlers` map.
170
285
  The generated types list exactly the plan's refs.
@@ -184,7 +299,7 @@ The generated types list exactly the plan's refs.
184
299
  `ctx.store` is scoped to the caller. `runtime.store` is not scoped; use it
185
300
  only in trusted host code.
186
301
 
187
- ## 5. The Worker and the data
302
+ ### 5. The Worker and the data
188
303
 
189
304
  1. Move the old `src/`, `wrangler.jsonc` (or `.toml`) and `tsconfig.json`
190
305
  aside. `mantle generate` writes the preset (`src/*.ts`, `tsconfig.json`,
@@ -197,13 +312,10 @@ only in trusted host code.
197
312
  4. Admin MCP moves from `/mcp/staff` to `/mcp/admin`. Update MCP client URLs.
198
313
  It admits only the admin layer classified by Better Auth’s admin plugin
199
314
  `adminRoles` (default `["admin"]`), and requires the `mcp` scope for tokens.
200
- 5. Import data into the new database through `runtime.store`. A scoped row
201
- goes through `runtime.store.as(<its owner's caller>)`, so the scope field is
202
- the owner. `status`, `created_at` and `author_id` cannot be imported:
203
- publish with a second update that sets `status`, and accept new
204
- timestamps and author.
315
+ 5. Data: this step described an alpha API that 0.2.0 does not have. Follow
316
+ [Moving data from 0.1.x](#moving-data-from-01x) instead.
205
317
 
206
- ## 6. CLI
318
+ ### 6. CLI
207
319
 
208
320
  | 0.1.x | 0.2.0 |
209
321
  |---|---|
@@ -212,7 +324,7 @@ only in trusted host code.
212
324
  | `mantle generate --output`, `--namespace` | removed: output is always `.mantle/generated/` |
213
325
  | `mantle skills`, the `mantle-harness` bin | removed |
214
326
 
215
- ## 7. Verify
327
+ ### 7. Verify
216
328
 
217
329
  1. `mantle generate` and `mantle generate --check` exit 0.
218
330
  2. The project typechecks against the generated `mantle.ts`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aotter/mantle",
3
- "version": "0.2.0",
3
+ "version": "0.2.1-alpha.2",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "engines": {
@@ -20,9 +20,9 @@
20
20
  "jsonc-parser": "3.3.1",
21
21
  "@pgsql/types": "18.0.0",
22
22
  "libpg-query": "18.1.5",
23
- "pgsql-deparser": "18.3.8",
23
+ "pgsql-deparser": "18.3.10",
24
24
  "yaml": "^2.9.1",
25
- "zod": "^4.6.5"
25
+ "zod": "^4.6.7"
26
26
  },
27
27
  "exports": {
28
28
  ".": "./dist/infrastructure/generate.js",
@@ -68,11 +68,10 @@
68
68
  "./json-body": "./dist/kernel/json-body.js",
69
69
  "./generate/auth": "./dist/infrastructure/generateAuth.js",
70
70
  "./package.json": "./package.json",
71
- "./describe": "./dist/domain/describe/index.js",
72
- "./target-hash": "./dist/domain/describe/target-hash.js"
71
+ "./describe": "./dist/domain/describe/public.js"
73
72
  },
74
73
  "peerDependencies": {
75
- "@modelcontextprotocol/server": "2.2.0",
74
+ "@modelcontextprotocol/server": "2.3.1",
76
75
  "@better-auth/core": "1.7.7",
77
76
  "@better-auth/oauth-provider": "1.7.7",
78
77
  "better-auth": "1.7.7"
@@ -92,19 +91,19 @@
92
91
  }
93
92
  },
94
93
  "devDependencies": {
95
- "@modelcontextprotocol/client": "2.2.0",
96
- "@modelcontextprotocol/server": "2.2.0",
94
+ "@modelcontextprotocol/client": "2.3.1",
95
+ "@modelcontextprotocol/server": "2.3.1",
97
96
  "@better-auth/core": "1.7.7",
98
97
  "@better-auth/oauth-provider": "1.7.7",
99
98
  "@better-auth/test-utils": "1.7.7",
100
- "@cloudflare/workers-types": "^5.20261008.1",
99
+ "@cloudflare/workers-types": "^5.20261027.1",
101
100
  "@types/node": "^26",
102
101
  "better-auth": "1.7.7",
103
102
  "bun-types": "1.4.2",
104
103
  "postgres": "3.4.9",
105
104
  "typescript": "^6.0.3",
106
- "wrangler": "4.145.0",
107
- "vitest": "^5.0.1"
105
+ "wrangler": "4.153.0",
106
+ "vitest": "^5.0.4"
108
107
  },
109
108
  "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
109
  "keywords": [
package/skills/README.md CHANGED
@@ -18,6 +18,7 @@ distributions (ADR-0032 decision 13):
18
18
  | [`update`](../docs/skills/update/SKILL.md) | Upgrade `@aotter/mantle`, including from 0.1.x. |
19
19
  | [`provision`](../docs/skills/provision/SKILL.md) | Ship to Cloudflare Workers or ChatGPT Sites with production sign-in. |
20
20
  | [`media-gc`](../docs/skills/media-gc/SKILL.md) | Audit or remove stale, uncommitted R2 uploads. |
21
+ | [`performance`](../docs/skills/performance/SKILL.md) | Make an accepted app fast: Lighthouse, cold start, backend load, indexes and Views. Runs after acceptance converges. |
21
22
 
22
23
  ## Install
23
24
 
@@ -27,10 +28,12 @@ release tag as the application's SDK; alpha releases on `develop` can be newer
27
28
  than the default `main` branch. For a skill-only installation:
28
29
 
29
30
  ```sh
30
- npx skills add https://github.com/aotter/mantle/tree/v0.2.0 --skill mantle
31
+ npx skills add aotter/mantle@mantle
31
32
  ```
32
33
 
33
- The explicit skill name excludes repository maintainer skills. The manifests
34
+ Without a version it follows `main`, the latest stable release; pin the SDK's
35
+ release tag with `npx skills add aotter/mantle#v0.2.1-alpha.2@mantle`.
36
+ The explicit skill name (`@mantle`) excludes repository maintainer skills. The manifests
34
37
  are generated by `scripts/sync-plugin-manifests.mjs` and checked with `--check`.
35
38
  When upgrading, update the marketplace ref or local tagged checkout and refresh
36
39
  the plugin. Verify the loaded manifest version; Claude Code's plugin version
@@ -53,6 +56,7 @@ each other: `metadata.projection` is `plugin` for `skills/mantle` and
53
56
  | `update` | an SDK upgrade | never overwrite application files; 0.1.x moves by the upgrade guide to a new database | package | — |
54
57
  | `provision` | ship to production | secrets never in source, chat or logs; a new database for 0.2; self-hosting is always an option | package | Creates remote resources and handles production secrets; opt-in only. |
55
58
  | `media-gc` | stale R2 uploads | audit by default; exact keys only, never a prefix delete; re-audit before applying | package | Destructive remote object deletion and Cloudflare-specific; opt-in only. |
59
+ | `performance` | converged acceptance, a slow page, first request or query | accepted behavior only; never trade scope, authorization, OCC or atomicity for speed; local unless deployment is authorized | package | — |
56
60
 
57
61
  ## Audience
58
62
 
@@ -38,6 +38,7 @@ lacks a guide named here, record the missing guidance and keep that version's co
38
38
  - Read `node_modules/@aotter/mantle/docs/skills/update/SKILL.md` to upgrade Mantle, including from 0.1.x.
39
39
  - Read `node_modules/@aotter/mantle/docs/skills/provision/SKILL.md` only for an authorized production deployment.
40
40
  - Read `node_modules/@aotter/mantle/docs/skills/media-gc/SKILL.md` only to audit or remove stale uncommitted media uploads.
41
+ - Read `node_modules/@aotter/mantle/docs/skills/performance/SKILL.md` after acceptance converges, or when a page, first request or query is slow.
41
42
 
42
43
  ## New application
43
44
 
@@ -50,7 +51,7 @@ includes:
50
51
  - Real business rules in SQL Procedures (`mantle help sql-patterns`), not in handler code around separate writes.
51
52
  - For visitor pages, everything in `mantle help public-pages` (SEO, AEO, sitemap, robots, `llms.txt`), styled.
52
53
  - Local HTTP checks (`mantle help local-testing`), then independent acceptance with `pnpm accept`
53
- (`mantle help agent-workflow-acceptance`).
54
+ (`mantle help agent-workflow-acceptance`), then the installed `docs/skills/performance/SKILL.md`.
54
55
 
55
56
  Take patterns from `mantle help examples` into the app's own manifests and `src/`; do not copy an example directory
56
57
  over the app. Resolve each `AGENT:` or name the ones you left. Generation only, inside an app you already have: skip
@@ -59,8 +60,19 @@ over the app. Resolve each `AGENT:` or name the ones you left. Generation only,
59
60
  ## Accept before reporting done
60
61
 
61
62
  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.
63
+ then one fresh agent per role given only its role and the URLs `pnpm accept` prints, at most three rounds. The first
64
+ delivery covers every role and surface; after a repair, the guide's "Re-verify what a repair touches" rule decides what
65
+ re-runs: a page-only repair re-runs the affected UI, while auth, scope, SQL Procedure, unique index or shared-transport changes widen
66
+ to every surface they reach. State the round and revision of any evidence you reuse. If it was
67
+ not run, report the work as UNVERIFIED rather than done; if it could not run, BLOCKED. Report status per layer as that
68
+ guide's "Report done-ness in layers" defines; a block from your environment (approval policy, missing browser,
69
+ network) is BLOCKED on its layer, not a product failure, and does not discard the other layers.
70
+
71
+ Once acceptance is CONVERGED, read the installed `docs/skills/performance/SKILL.md`: it decides which of front end,
72
+ cold start and backend load apply, and runs them before you report done. This is the one exception to installed docs
73
+ winning: if the installed package predates that skill, read
74
+ [the 0.2.1 copy](https://github.com/aotter/mantle/blob/v0.2.1/docs/skills/performance/SKILL.md) and the pages it links
75
+ from the installed docs with `mantle help <topic>`.
64
76
 
65
77
  ## Deployment
66
78
 
@@ -72,5 +84,7 @@ host's native workflow. Local project creation alone is not deployment authoriza
72
84
  Preserve the user's manifests, `src/` files, identity, package versions and provider resources. Run the project's
73
85
  generate command with `--check` and its own tests before and after a change.
74
86
 
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.
87
+ Report the project path, the exact Mantle version, what you changed, the four-layer status with each layer's checks
88
+ and results (the independent acceptance verdict, CONVERGED / BLOCKED / UNVERIFIED, is layer 3), the performance verdict
89
+ (PASS / PARTIAL / SKIPPED) with its before and after numbers, and anything left for the user, including what would
90
+ unblock a BLOCKED layer.
@@ -1 +0,0 @@
1
- function resolveLocalizedText(value,preferred,canonical2){if(value==null)return null;if(typeof value=="string")return value;if(Object.prototype.hasOwnProperty.call(value,preferred))return value[preferred];if(canonical2&&Object.prototype.hasOwnProperty.call(value,canonical2))return value[canonical2];let firstKey=Object.keys(value)[0];return firstKey!==void 0?value[firstKey]:null}var INPUT_RULE_KEYWORDS=["dependentRequired","anyOf","if","then","else"];function lookupLocalRef(root,ref){if(typeof ref!="string"||!ref.startsWith("#/"))return;let value=root;try{for(let key of decodeURIComponent(ref.slice(2)).split("/").map(key2=>key2.replace(/~1/g,"/").replace(/~0/g,"~")))value=value&&typeof value=="object"&&Object.hasOwn(value,key)?value[key]:void 0}catch{return}return value&&typeof value=="object"?value:void 0}function rootRefChain(input){let chain=[];for(let schema=input;schema!==void 0;schema=lookupLocalRef(input,schema.$ref)){if(chain.includes(schema))return;if(chain.push(schema),schema.$ref===void 0)return chain}}function mcpToolNameSegment(name){return name.toLowerCase().replace(/-/g,"_")}function toolTitle(name){let words=name.replace(/([a-z0-9])([A-Z])/g,"$1 $2").replace(/[-_.\s]+/g," ").trim().toLowerCase();return words.charAt(0).toUpperCase()+words.slice(1)}var viewAnnotations={readOnlyHint:!0,destructiveHint:!1};function procedureAnnotations(p){let readOnlyHint=p.mcp?.readOnlyHint??!1;return{readOnlyHint,destructiveHint:!readOnlyHint&&("ref"in p.handler||overwrites(p.handler.sql.stmts)),...p.mcp}}var overwrites=node=>!!node&&typeof node=="object"&&("DeleteStmt"in node||"UpdateStmt"in node||node.onConflictClause?.action==="ONCONFLICT_UPDATE"||Object.values(node).some(overwrites));function mcpTools(plan,surface,locale="en"){let text=t=>resolveLocalizedText(t,locale,"en")??void 0,tools=new Map,add=tool=>{let prior=tools.get(tool.name);if(prior&&(prior.kind!==tool.kind||prior.source!==tool.source))throw new TypeError(`MCP tool '${tool.name}' comes from both '${prior.source}' and '${tool.source}'.`);tools.set(tool.name,tool)};for(let t of Object.values(plan.triggers)){if(t.source.kind!=="mcp"||t.source.surface!==surface)continue;let p=plan.procedures[t.procedure],title=text(p.title);add({name:mcpToolNameSegment(t.procedure),kind:"procedure",source:t.procedure,title:title??toolTitle(t.procedure),description:text(p.description)??title??t.procedure,inputSchema:p.input,...p.output.type==="object"?{outputSchema:p.output}:{},annotations:procedureAnnotations(p),requires:p.requires})}for(let[name,v]of Object.entries(plan.views)){if(v.surface!==surface)continue;let chain=(v.input?rootRefChain(v.input)??[v.input]:[]).reverse(),props=Object.assign({},...chain.map(s=>s.properties??{})),required=[...new Set(chain.flatMap(s=>s.required??[]))];if("limit"in props||"cursor"in props)throw new TypeError(`View '${name}' declares an input named limit or cursor; the MCP tool reserves both for paging.`);let rules=chain.map(s=>Object.fromEntries(INPUT_RULE_KEYWORDS.flatMap(k=>s[k]===void 0?[]:[[k,s[k]]]))).filter(r=>Object.keys(r).length),defs=v.input?referencedDefs(v.input,props):{},title=text(v.title);add({name:mcpToolNameSegment(name),kind:"view",source:name,title:title??toolTitle(name),description:text(v.description)??title??name,inputSchema:{type:"object",properties:{...props,limit:{type:"integer",minimum:1,maximum:500},cursor:{type:"string"}},...required.length?{required}:{},...rules.length>1?{allOf:rules}:rules[0],...Object.keys(defs).length?{$defs:defs}:{}},annotations:viewAnnotations,requires:v.requires})}return[...tools.values()]}function referencedDefs(root,node,found={}){if(!node||typeof node!="object")return found;let ref=node.$ref,[head,segment]=typeof ref=="string"&&ref.startsWith("#/")?decoded(ref.slice(2)).split("/"):[],key=head==="$defs"?segment?.replace(/~1/g,"/").replace(/~0/g,"~"):void 0;key!==void 0&&!Object.hasOwn(found,key)&&root.$defs&&Object.hasOwn(root.$defs,key)&&(found[key]=root.$defs[key],referencedDefs(root,found[key],found));for(let child of Object.values(node))referencedDefs(root,child,found);return found}var decoded=pointer=>{try{return decodeURIComponent(pointer)}catch{return""}};function canonical(v){return Array.isArray(v)?`[${v.map(canonical).join(",")}]`:v&&typeof v=="object"?`{${Object.keys(v).sort().filter(k=>v[k]!==void 0).map(k=>`${JSON.stringify(k)}:${canonical(v[k])}`).join(",")}}`:JSON.stringify(v)??"null"}var publishingTransitions=[["publish",["draft"],"published"],["unpublish",["published","archived"],"draft"],["archive",["draft","published"],"archived"]];var publishingInput={type:"object",properties:{id:{type:"string",minLength:1},expectedVersion:{type:"integer",minimum:1}},required:["id","expectedVersion"],additionalProperties:!1};function mediaDefinitions(){let define=(name,description,inputSchema,action,readOnlyHint=!1)=>({name,description,inputSchema,action,annotations:{readOnlyHint,destructiveHint:action==="delete",openWorldHint:!0}}),string={type:"string"},texts={alt:{type:"string",maxLength:1e3},caption:{type:"string",maxLength:1e3}},input=(properties,required=[])=>({type:"object",properties,required,additionalProperties:!1});return[define("get_media_upload_policy","Read declared image purpose MIME slots and byte caps before preparing variants or requesting an upload. Optimization runs locally, never in the host.",input({}),"policy",!0),define("create_media_upload","Create short-lived PUT capabilities for image variants. Read site media purpose policy first; optimize images locally. Obtain attachment bytes yourself, then PUT each variant with its requiredHeaders and call commit_media_upload. Never store upload URLs as published images or ask the user to run terminal commands.",input({filename:{type:"string",maxLength:255},purpose:string,variants:{type:"array",minItems:1,items:input({mimeType:string,byteSize:{type:"integer",minimum:1},role:{type:"string",enum:["primary","alternate","fallback"]}},["mimeType","byteSize","role"])},...texts},["filename","purpose","variants"]),"create"),define("commit_media_upload","Verify all uploaded variants and commit one permanent media asset. Store the returned asset id in fields referencing media_assets.id, never a temporary upload URL. Unknown or expired upload groups require a new create_media_upload.",input({uploadGroupId:string,...texts},["uploadGroupId"]),"commit"),define("list_media_assets","List committed image assets, newest first, with their permanent variants and alt/caption. Search matches id, alt or caption; reuse only a returned cursor.",input({limit:{type:"integer",minimum:1,maximum:500},cursor:string,search:string}),"list",!0),define("get_media_asset","Read one committed media asset and its permanent variant URLs.",input({id:string},["id"]),"get",!0),define("update_media_asset","Update committed asset alt or caption; omitted fields remain unchanged and empty strings clear text.",input({id:string,...texts},["id"]),"update"),define("delete_media_asset","Delete an asset's objects and library record. Check application references first. A partial failure can be retried using the same id.",input({id:string},["id"]),"delete")]}function inspectSql(value){let schemas=new Set,readSchemas=new Set,writes=[],readOnly=!0,walk=v=>{if(!v||typeof v!="object")return;let n=v;n.RangeVar?.mantle==="table"&&(schemas.add(n.RangeVar.relname.toLowerCase()),readSchemas.add(n.RangeVar.relname.toLowerCase()));for(let kind of["InsertStmt","UpdateStmt","DeleteStmt"])n[kind]&&(readOnly=!1,n[kind].relation?.relname&&writes.push({schema:n[kind].relation.relname.toLowerCase(),operation:kind==="InsertStmt"?"create":kind==="UpdateStmt"?"update":"delete"}),n[kind].relation?.relname&&(schemas.add(n[kind].relation.relname.toLowerCase()),kind!=="InsertStmt"&&readSchemas.add(n[kind].relation.relname.toLowerCase())));Object.values(n).forEach(walk)};return walk(value),{schemas:[...schemas].sort(),readSchemas:[...readSchemas].sort(),readOnly,writes}}function mergeRestrict(policies,conjunctive){return Object.fromEntries([...new Set(policies.flatMap(p=>Object.keys(p)))].sort().map(schema=>{let rows=policies.map(p=>p[schema]??{terms:[]}),terms=[...new Map(rows.flatMap(p=>p.terms).map(t=>[canonical(t),t])).values()].filter(t=>conjunctive||rows.every(p=>p.terms.some(v=>canonical(v)===canonical(t)))),owner=rows.find(p=>p.owner)?.owner;return[schema,{...owner&&(conjunctive||rows.every(p=>p.owner===owner))?{owner}:{},terms}]}))}function inspectBehavior(plan,ir,opaque,requires,restrict,writes){let inspected=inspectSql(ir);writes&&(inspected.writes=[...writes],inspected.schemas.push(...writes.map(w=>w.schema)),inspected.readSchemas.push(...writes.filter(w=>w.operation!=="create").map(w=>w.schema)));let dependencies=[],policies=[restrict??{}],procedures=new Set,hooks=new Set,visit=name=>{if(procedures.has(name))return;procedures.add(name);let p=plan.procedures[name];if(!p)throw new TypeError(`Unknown dependency '${name}'.`);dependencies.push({procedure:name,declaration:p}),policies.push(p.restrict??{}),opaque||="ref"in p.handler;let g=inspectSql("sql"in p.handler?p.handler.sql.stmts:void 0);inspected.schemas.push(...g.schemas),inspected.readSchemas.push(...g.readSchemas),inspected.writes.push(...g.writes),inspected.readOnly&&=g.readOnly,p.requires?.guard&&visit(p.requires.guard.procedure)};for(requires?.guard&&visit(requires.guard.procedure);;){let before=hooks.size;for(let[id,t]of Object.entries(plan.triggers).sort(([a],[b])=>a<b?-1:a>b?1:0)){if(t.source.kind!=="lifecycle"||hooks.has(id))continue;let source=t.source;!opaque&&!inspected.writes.some(w=>w.schema===source.schema.toLowerCase()&&source.on.some(on=>on.endsWith(`_${w.operation}`)))||(hooks.add(id),dependencies.push({trigger:id,source}),visit(t.procedure))}if(hooks.size===before)break}let related=names=>{let all=new Set(opaque?Object.keys(plan.schemas):names);for(let name of all){let parent=plan.schemas[name]?.translates?.parent.toLowerCase();parent&&all.add(parent)}return[...all].sort()};return{...inspected,schemas:related(inspected.schemas),readSchemas:related(inspected.readSchemas),opaque,restrict:mergeRestrict(policies,!0),dependencies}}var objectInput={type:"object",properties:{}},pathTemplate=path=>path.replace(/\{([^}]+)\}/g,":$1"),adminRoutes=["GET /me","GET /site","GET /collections","GET /views-manifest","GET /operations","GET /webmcp","POST /webmcp/:tool","GET /bootstrap","GET /views/:name/export","GET /views/:name","GET /view-batch","GET /developer/views/:name","POST /operations/:name","GET /developer-console","GET /collections/:name/statistics","GET /entries","GET /entries/export","GET /entries/:id","POST /entries","PATCH /entries/:id","POST /entries/:id/publish","POST /entries/:id/unpublish","POST /entries/:id/archive","DELETE /entries/:id","GET /site-settings","PATCH /site-settings","POST /media/uploads","POST /media/uploads/:groupId/commit","GET /media","GET /media/:id","PATCH /media/:id","DELETE /media/:id","GET /admin","GET /members","PATCH /admin/:id/role","POST /admin/invitations","DELETE /admin/invitations/:id"];function targetDefinitions(plan){let out=[],tools=[...mcpTools(plan,"public"),...mcpTools(plan,"admin")];for(let[kind,entries]of[["view",plan.views],["procedure",plan.procedures]])for(let[name,p]of Object.entries(entries).sort(([a],[b])=>a<b?-1:a>b?1:0)){let view=kind==="view"?plan.views[name]:void 0,proc=kind==="procedure"?plan.procedures[name]:void 0,ir=view?.stmts??(proc&&"sql"in proc.handler?proc.handler.sql.stmts:void 0),inspected=inspectBehavior(plan,ir,!!proc&&"ref"in proc.handler,p.requires,p.restrict),tool=tools.find(t=>t.kind===kind&&t.source===name),title=resolveLocalizedText(p.title,"en","en")??name,triggers=Object.entries(plan.triggers).filter(([,t])=>kind==="procedure"&&t.procedure===name).sort(([a],[b])=>a<b?-1:a>b?1:0).map(([id,t])=>({id,source:t.source})),surfaces=[...new Set([...view?[view.surface,"admin",...view.surface==="public"?["rest"]:[]]:[],...triggers.map(t=>t.source.kind==="mcp"?t.source.surface:t.source.kind==="http"?"rest":t.source.kind)])].sort();surfaces.length||surfaces.push("internal"),out.push({id:`${kind}:${name}`,name:tool?.name??mcpToolNameSegment(name),kind,surface:view?.surface??(surfaces.includes("public")||surfaces.includes("rest")?"public":surfaces.includes("admin")?"admin":"internal"),surfaces,triggers,title,description:resolveLocalizedText(p.description,"en","en")??title,inputSchema:tool?.inputSchema??view?.input??proc?.input??objectInput,annotations:tool?.annotations??{},requires:p.requires,restrict:inspected.restrict,schemas:inspected.schemas,readSchemas:inspected.readSchemas,readOnly:!inspected.opaque&&inspected.readOnly,opaque:inspected.opaque,trustedOnly:!!proc?.trustedOnly,variants:[],behavior:{declaration:p,dependencies:inspected.dependencies}})}let addRoute=(id,dependencies,inputSchema=objectInput)=>{let admin=id.startsWith("admin:");out.push({id,name:id.replace(/[^a-zA-Z0-9_-]/g,"_"),title:id,description:id,kind:admin?"admin":"rest",surface:admin?"admin":"public",surfaces:[admin?"admin":"rest"],triggers:dependencies.flatMap(d=>d.triggers),inputSchema,annotations:{},requires:admin?{auth:{all:["ctx.admin"]}}:dependencies[0]?.requires,restrict:mergeRestrict(dependencies.map(d=>d.restrict),!1),schemas:[...new Set(dependencies.flatMap(d=>d.schemas))].sort(),readSchemas:[...new Set(dependencies.flatMap(d=>d.readSchemas))].sort(),readOnly:dependencies.length>0&&dependencies.every(d=>d.readOnly)&&(!admin||id.startsWith("admin:GET ")||id==="admin:POST /operations/:name"||id==="admin:POST /webmcp/:tool"),opaque:dependencies.some(d=>d.opaque),trustedOnly:dependencies.some(d=>d.trustedOnly),variants:dependencies,behavior:dependencies.map(d=>({id:d.id,behavior:d.behavior,surfaces:d.surfaces,triggers:d.triggers,input:d.inputSchema,requires:d.requires,restrict:d.restrict,trustedOnly:d.trustedOnly}))})},declarations=[...out],adminOperations=new Set(mcpTools(plan,"admin").filter(t=>t.kind==="procedure").map(t=>`procedure:${t.source}`));for(let d of declarations)d.kind==="view"&&d.surfaces.includes("public")&&addRoute(`rest:GET /api/views/${d.id.slice(5)}`,[d],d.inputSchema);for(let[,t]of Object.entries(plan.triggers).sort(([a],[b])=>a<b?-1:a>b?1:0))if(t.source.kind==="http"){let d=declarations.find(d2=>d2.id===`procedure:${t.procedure}`);addRoute(`rest:${t.source.method} ${pathTemplate(t.source.path)}`,[d],d.inputSchema)}let generated=[];for(let[schema,s]of Object.entries(plan.schemas).sort(([a],[b])=>a<b?-1:a>b?1:0))if(s.publishing)for(let[verb,from,to]of publishingTransitions){addRoute(`procedure:${s.name}.${verb}`,[],publishingInput);let d=out.at(-1),inspected=inspectBehavior(plan,void 0,!1,void 0,void 0,[{schema,operation:verb==="publish"?"publish":"update"}]);Object.assign(d,{kind:"procedure",name:mcpToolNameSegment(`${verb}_${s.name}`),surface:"admin",surfaces:["admin"],requires:{auth:{all:["ctx.admin"]}},schemas:[...new Set([schema,...inspected.schemas])].sort(),readSchemas:[...new Set([schema,...inspected.readSchemas])].sort(),opaque:inspected.opaque,behavior:{schema,verb,from,to,dependencies:inspected.dependencies}}),generated.push(d)}for(let tool of mediaDefinitions()){addRoute(`admin:tool:${tool.name}`,[],tool.inputSchema);let d=out.at(-1);Object.assign(d,{name:tool.name,readOnly:tool.annotations.readOnlyHint,opaque:!tool.annotations.readOnlyHint,annotations:tool.annotations,behavior:{tool,schemas:plan.schemas}}),generated.push(d)}for(let route of adminRoutes){let deps=route.includes("/views/:")||route==="GET /view-batch"?declarations.filter(d2=>d2.kind==="view"&&(route.includes("/developer/")||plan.views[d2.id.slice(5)]?.surface==="admin")):route.includes("/operations/:")?declarations.filter(d2=>adminOperations.has(d2.id)):route==="POST /webmcp/:tool"?declarations.filter(d2=>mcpTools(plan,"admin").some(t=>`${t.kind}:${t.source}`===d2.id)).concat(generated):[],generic=route.includes("/entries")||route.includes("/statistics")||route==="GET /bootstrap";addRoute(`admin:${route}`,deps,deps.length&&route!=="GET /view-batch"?{...objectInput,anyOf:deps.map(d2=>({type:"object",properties:{[route.includes("/webmcp/")?"tool":"name"]:{type:"string",const:route.includes("/webmcp/")?d2.name:d2.id.slice(d2.kind==="view"?5:10)}}}))}:objectInput);let d=out.at(-1);if(generic){let operation=route==="POST /entries"?"create":route==="DELETE /entries/:id"?"delete":route.startsWith("GET ")?void 0:route.endsWith("/publish")?"publish":"update",inspected=inspectBehavior(plan,void 0,!1,void 0,void 0,operation?Object.keys(plan.schemas).map(schema=>({schema,operation})):[]);d.schemas=Object.keys(plan.schemas).sort(),d.readSchemas=d.schemas,d.opaque=inspected.opaque,d.behavior={schemas:plan.schemas,dependencies:inspected.dependencies}}}return[...new Map(out.map(d=>[d.id,d])).values()].sort((a,b)=>a.id<b.id?-1:a.id>b.id?1:0)}async function hash(value){let digest=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(canonical(value)));return[...new Uint8Array(digest)].map(b=>b.toString(16).padStart(2,"0")).join("")}function hashDefinition(plan,d){return hash({id:d.id,surfaces:d.surfaces,triggers:d.triggers,behavior:d.behavior,input:d.inputSchema,requires:d.requires,restrict:d.restrict,dialect:plan.dialect,schemas:Object.fromEntries(d.schemas.map(name=>[name,plan.schemas[name]])),userFields:plan.access?.userFields,readOnly:d.readOnly,opaque:d.opaque,trustedOnly:d.trustedOnly})}async function targetHash(plan,targetId){let target=targetDefinitions(plan).find(d=>d.id===targetId);if(!target)throw new TypeError(`Unknown target '${targetId}'.`);return hashDefinition(plan,target)}export{canonical,targetDefinitions,hash,hashDefinition,targetHash};
@@ -1 +0,0 @@
1
- import{canonical,hash,hashDefinition,targetHash}from"./chunk-SGMRVDGE.js";export{canonical,hash,hashDefinition,targetHash};