@aotter/mantle 0.2.0-alpha.8 → 0.2.0-alpha.9
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.
- package/README.md +17 -0
- package/dist/auth/adapter.js +9 -6
- package/dist/auth/routes.js +3 -2
- package/dist/build/compiler.js +1 -1
- package/dist/infrastructure/bundle.js +16 -3
- package/dist/infrastructure/generate.js +9 -1
- package/dist/infrastructure/generateAuth.js +51 -0
- package/dist/infrastructure/wrangler.js +19 -4
- package/dist/kernel/binds.d.ts +0 -3
- package/dist/kernel/contracts.d.ts +1 -1
- package/dist/kernel/contracts.js +4 -4
- package/dist/kernel/dialect/common.d.ts +0 -1
- package/dist/kernel/driver/postgres.js +3 -2
- package/dist/kernel/mcp-apps.js +1 -1
- package/dist/kernel/mcp-handler.js +1 -1
- package/dist/kernel/mcp.d.ts +5 -1
- package/dist/kernel/mcp.js +2 -1
- package/dist/kernel/scalars.js +3 -2
- package/dist/kernel/web-index.js +40 -0
- package/dist/kernel/web.js +3 -37
- package/dist/usecase/publishing.js +1 -1
- package/dist/usecase/site.js +0 -1
- package/dist/usecase/validation.js +8 -0
- package/docs/examples/README.md +1 -1
- package/docs/examples/app-shell/README.md +6 -2
- package/docs/examples/app-shell/package.json +3 -3
- package/docs/examples/app-shell/src/auth.ts +3 -2
- package/docs/examples/app-shell/src/service.ts +2 -2
- package/docs/examples/club-events/manifests/club-events.yaml +7 -0
- package/docs/examples/club-events/src/handlers.ts +1 -1
- package/docs/examples/purchasing/manifests/purchasing.yaml +16 -0
- package/docs/examples/purchasing/src/handlers.ts +1 -1
- package/docs/handbook/cloudflare/authentication.md +21 -4
- package/docs/handbook/cloudflare/chatgpt-sites.md +4 -2
- package/docs/handbook/cloudflare/deploy-and-operate.md +24 -1
- package/docs/handbook/cloudflare/media-r2.md +2 -3
- package/docs/handbook/cloudflare/service-entry.md +14 -4
- package/docs/handbook/concepts/runtime-and-adapters.md +1 -1
- package/docs/handbook/examples/hub.md +1 -1
- package/docs/handbook/guides/agent-setup.md +4 -4
- package/docs/handbook/guides/agent-workflow-acceptance.md +222 -0
- package/docs/handbook/guides/mcp-apps.md +78 -1
- package/docs/handbook/guides/native-admin.md +4 -0
- package/docs/handbook/guides/oauth.md +58 -0
- package/docs/handbook/guides/typed-queries.md +35 -0
- package/docs/handbook/reference/procedure.md +1 -1
- package/docs/handbook/reference/surface.md +6 -2
- package/docs/handbook/reference/trigger.md +9 -0
- package/docs/handbook/releases/index.md +3 -3
- package/docs/handbook/start/project-and-cli.md +9 -1
- package/docs/skills/develop/SKILL.md +43 -16
- package/docs/skills/integrate/SKILL.md +4 -9
- package/docs/skills/provision/SKILL.md +15 -12
- package/docs/skills/update/SKILL.md +19 -18
- package/package.json +2 -2
- package/skills/README.md +1 -1
- package/skills/mantle/SKILL.md +35 -13
- package/skills/mantle/references/local-testing.md +5 -0
- package/skills/mantle/references/website.md +6 -4
- package/docs/skills/develop/mcp-app.md +0 -39
- package/skills/.keep +0 -0
|
@@ -165,6 +165,13 @@ spec:
|
|
|
165
165
|
source: { kind: http, method: POST, path: /api/registrations/cancel }
|
|
166
166
|
target: { procedure: cancel-registration }
|
|
167
167
|
---
|
|
168
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
169
|
+
kind: Trigger
|
|
170
|
+
metadata: { name: cancel-registration-mcp }
|
|
171
|
+
spec:
|
|
172
|
+
source: { kind: mcp, surface: public }
|
|
173
|
+
target: { procedure: cancel-registration }
|
|
174
|
+
---
|
|
168
175
|
# Officers add records through declared Procedures: operational collections have no generic create in Admin.
|
|
169
176
|
# collectionAction puts the Procedure's button and input form in that collection's list header; the admin MCP
|
|
170
177
|
# Trigger is what makes it an Admin operation (and lets the officers' AI do the same).
|
|
@@ -17,7 +17,7 @@ export const handlers: Handlers = {
|
|
|
17
17
|
return await store.procedures["register-write"]({ eventId, member: caller.subject, memberName: user?.name || user?.email || caller.subject });
|
|
18
18
|
} catch (error) {
|
|
19
19
|
// register-write's only Schema check is the capacity one.
|
|
20
|
-
if (error instanceof ContractError && error.code === "CONFLICT" && error.
|
|
20
|
+
if (error instanceof ContractError && error.code === "CONFLICT" && error.fields?.kind === "check" && error.fields.schema === "events")
|
|
21
21
|
throw new ContractError("CONFLICT", "The event is full.", "procedures/register");
|
|
22
22
|
throw error;
|
|
23
23
|
}
|
|
@@ -117,6 +117,7 @@ kind: Procedure
|
|
|
117
117
|
metadata: { name: submit-request }
|
|
118
118
|
spec:
|
|
119
119
|
title: Submit a purchase request
|
|
120
|
+
description: Submit a purchase request against an existing department budget.
|
|
120
121
|
requires: { auth: { all: [ctx.user] } }
|
|
121
122
|
input:
|
|
122
123
|
type: object
|
|
@@ -140,6 +141,7 @@ kind: Procedure
|
|
|
140
141
|
metadata: { name: change-amount }
|
|
141
142
|
spec:
|
|
142
143
|
title: Change the amount of my request
|
|
144
|
+
description: Change the amount of the signed-in member's submitted purchase request.
|
|
143
145
|
requires: { auth: { all: [ctx.user] } }
|
|
144
146
|
restrict: { requests: "author_id = auth.uid()" }
|
|
145
147
|
input:
|
|
@@ -291,11 +293,25 @@ spec:
|
|
|
291
293
|
---
|
|
292
294
|
apiVersion: cms.mantle.aotter.net/v2
|
|
293
295
|
kind: Trigger
|
|
296
|
+
metadata: { name: submit-request-mcp }
|
|
297
|
+
spec:
|
|
298
|
+
source: { kind: mcp, surface: public }
|
|
299
|
+
target: { procedure: submit-request }
|
|
300
|
+
---
|
|
301
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
302
|
+
kind: Trigger
|
|
294
303
|
metadata: { name: change-amount-http }
|
|
295
304
|
spec:
|
|
296
305
|
source: { kind: http, method: POST, path: '/api/requests/{id}/amount' }
|
|
297
306
|
target: { procedure: change-amount }
|
|
298
307
|
---
|
|
308
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
309
|
+
kind: Trigger
|
|
310
|
+
metadata: { name: change-amount-mcp }
|
|
311
|
+
spec:
|
|
312
|
+
source: { kind: mcp, surface: public }
|
|
313
|
+
target: { procedure: change-amount }
|
|
314
|
+
---
|
|
299
315
|
# Admin MCP tools: an AI agent connected to /mcp/admin can approve or reject.
|
|
300
316
|
apiVersion: cms.mantle.aotter.net/v2
|
|
301
317
|
kind: Trigger
|
|
@@ -14,7 +14,7 @@ export const handlers: Handlers = {
|
|
|
14
14
|
} catch (error) {
|
|
15
15
|
// The declared budget check is a business refusal, not a server error. Nothing was written.
|
|
16
16
|
// The approval's only Schema check is the budget one.
|
|
17
|
-
if (error instanceof ContractError && error.code === "CONFLICT" && error.
|
|
17
|
+
if (error instanceof ContractError && error.code === "CONFLICT" && error.fields?.kind === "check" && error.fields.schema === "budgets")
|
|
18
18
|
throw new ContractError("CONFLICT", "The department budget cannot cover this request.", "procedures/approve-request");
|
|
19
19
|
throw error;
|
|
20
20
|
}
|
|
@@ -12,6 +12,8 @@ Mantle supplies the database adapter, schema generation and caller resolution:
|
|
|
12
12
|
|
|
13
13
|
## `auth.ts`
|
|
14
14
|
|
|
15
|
+
> **Verification status:** the GitHub social-provider and magic-link recipes have not yet been exercised end to end against real deployed provider flows.
|
|
16
|
+
|
|
15
17
|
Copy `src/auth.ts` from the [app shell](../../examples/app-shell/README.md), which CI runs from the packed packages;
|
|
16
18
|
installed, it is `node_modules/@aotter/mantle/docs/examples/app-shell/src/auth.ts`. Its essentials:
|
|
17
19
|
|
|
@@ -78,6 +80,7 @@ after success; invitations still work when email is absent.
|
|
|
78
80
|
`databaseHooks.user.create.after` (and user.update.after for a later email verification). Only the first matching signup wins (case-insensitive); email matching requires emailVerified=true (required for bootstrap).
|
|
79
81
|
D1 uses one conditional UPDATE; PostgreSQL uses a transaction advisory lock before UPDATE, avoiding concurrent winners.
|
|
80
82
|
A blocked promotion logs a warning. A GitHub rule requires a configured trusted GitHub provider/profile mapper.
|
|
83
|
+
The promotion runs after Better Auth creates the user, so the first sign-in response (and a cached session payload) can still show the previous role, while authorization re-reads the role on every request and `/admin/api/me` and later sessions show the admin role.
|
|
81
84
|
Helpers run no boot queries or DDL.
|
|
82
85
|
|
|
83
86
|
## Worker entry
|
|
@@ -184,10 +187,24 @@ The default is one instance with `admin` and `user` roles. An app that keeps two
|
|
|
184
187
|
staff and customers) can run one Better Auth instance per population in the same Worker; every Mantle primitive
|
|
185
188
|
takes the instance it works on:
|
|
186
189
|
|
|
187
|
-
- Give each instance its own `basePath`, `advanced.cookiePrefix`, `secret` and table names (`user.modelName`,
|
|
188
|
-
`session.modelName`,
|
|
189
|
-
one `--auth` per module to `mantle generate`; the tables are merged into one migration, and two
|
|
190
|
-
define the same table differently are refused. The native adapter follows each instance's mapping.
|
|
190
|
+
- Give each instance its own `basePath`, `advanced.cookiePrefix`, `secret` and core table names (`user.modelName`,
|
|
191
|
+
`session.modelName`, `account.modelName`, `verification.modelName`). Keep each instance's options in its own
|
|
192
|
+
module and pass one `--auth` per module to `mantle generate`; the tables are merged into one migration, and two
|
|
193
|
+
instances that define the same table differently are refused. The native adapter follows each instance's mapping.
|
|
194
|
+
- Better Auth plugin factories share their schema object, so differing plugin field/model mappings across instances
|
|
195
|
+
in one Worker override each other. Use one of two supported ways:
|
|
196
|
+
1. Separate the instances by core table names only and leave every plugin mapping at its default.
|
|
197
|
+
2. When plugin tables must differ (`jwt`'s `jwks`, `@better-auth/oauth-provider`'s `oauth*` tables), map them
|
|
198
|
+
explicitly in every instance, the default one included, and give each instance its own copy of the plugin's
|
|
199
|
+
schema right after constructing it:
|
|
200
|
+
`const keys = jwt({ schema: { jwks: { modelName: "staff_jwks" } } }); const plugin = { ...keys, schema: structuredClone(keys.schema) };`
|
|
201
|
+
|
|
202
|
+
`mantle generate` refuses anything else when two or more `--auth` instances use the same plugin (`jwt`, `admin`,
|
|
203
|
+
`oauth-provider`, `two-factor`, `phone-number`, `anonymous`, `siwe`, `device-authorization`) and a mapping exists
|
|
204
|
+
while an instance keeps the shared schema object, or two instances resolve a mapping to the same name (generate
|
|
205
|
+
cannot tell an identical mapping from an inherited one, so mapped names must differ between instances). Build
|
|
206
|
+
every instance's plugins in the same order in the Worker as in `--auth`; an instance built earlier than a mapped
|
|
207
|
+
one can still inherit its mapping, which generate cannot see.
|
|
191
208
|
- One `authLoader(load, cookiePrefix)` per instance; call `resolveCaller(request, loader)` with the instance the
|
|
192
209
|
route belongs to, and mount `authRoutes(loader, ...)` per instance (it guards that instance's `/admin/*`). The
|
|
193
210
|
loader takes `cookiePrefix` separately on purpose: it checks the cookie before loading Better Auth, so anonymous
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# Mantle on ChatGPT Sites
|
|
2
2
|
|
|
3
|
+
> **Verification status:** this guide is experimental and has not yet been exercised end to end on a real ChatGPT Site.
|
|
4
|
+
|
|
3
5
|
Treat the Site as an existing TypeScript host. Import selected generated Views/Procedures, pass the host's native handle and verified Caller, and compose only the surfaces it supports. Use [partial integration](../../skills/integrate/SKILL.md); Mantle supplies no Sites-specific identity or deployment adapter.
|
|
4
6
|
|
|
5
7
|
Choose postgres or sqlite according to the host's actual SQL executor. Custom handles are app-owned; a structural match alone does not guarantee driver semantics. [Database boundaries](../concepts/runtime-and-adapters.md) defines the supported drivers.
|
|
6
8
|
|
|
7
|
-
The runtime never creates, alters or checks tables. Apply initial `schema.sql` and selected auth/site DDL only to an empty database. For later changes the agent diffs regenerated SQL, writes and rehearses a host-native migration on a copy, and applies it before publishing new code.
|
|
9
|
+
The runtime never creates, alters or checks tables. Apply initial `schema.sql` and selected auth/site DDL only to an empty database. For later changes the agent diffs regenerated SQL, writes and rehearses a host-native migration on a copy, and applies it before publishing new code. Destructive/type changes require explicit data migration; Mantle drops nothing.
|
|
8
10
|
|
|
9
|
-
Use the Site's native publication workflow. Configure trusted origin and verified identity there; never construct a system/admin Caller from browser input. Serving Admin also requires its
|
|
11
|
+
Use the Site's native publication workflow. Configure trusted origin and verified identity there; never construct a system/admin Caller from browser input. Serving Admin also requires its app-built assets and shell security contract. Scheduling and storage provisioning belong to the host.
|
|
@@ -32,7 +32,7 @@ The runtime never creates, alters or checks tables. Boot and requests run no DDL
|
|
|
32
32
|
|
|
33
33
|
For the first install, apply generated `schema.sql`, including selected auth/site DDL, to an empty database. Apply selected seed SQL explicitly.
|
|
34
34
|
|
|
35
|
-
For every later schema change, the application's coding agent diffs the regenerated `schema.sql`, writes a migration with the host's native tool (`wrangler d1 migrations` or the app's PostgreSQL migration tool), rehearses it on a copy, and applies it before deploying the new code.
|
|
35
|
+
For every later schema change, the application's coding agent diffs the regenerated `schema.sql`, writes a migration with the host's native tool (`wrangler d1 migrations` or the app's PostgreSQL migration tool), rehearses it on a copy, and applies it before deploying the new code.
|
|
36
36
|
|
|
37
37
|
Destructive changes and type changes need explicit data migration. Mantle drops nothing. Initial `schema.sql` is not an upgrade script.
|
|
38
38
|
|
|
@@ -40,6 +40,29 @@ For D1, create a migration with `pnpm exec wrangler d1 migrations create <databa
|
|
|
40
40
|
|
|
41
41
|
For PostgreSQL, use the app's migration tool and deployment credentials. Plan lock waits and long index builds there; request credentials need no DDL privileges. Configure session settings on the role/database as described in [native execution](../guides/native-execution.md#postgresql-settings). Hyperdrive query caching must be disabled for authority and mutation visibility.
|
|
42
42
|
|
|
43
|
+
## Latency placement and D1 read replication
|
|
44
|
+
|
|
45
|
+
Measure these options against the application's real traffic; neither is a
|
|
46
|
+
default latency guarantee. [Smart Placement](https://developers.cloudflare.com/workers/configuration/placement/)
|
|
47
|
+
can move fetch execution closer to observed back-end services:
|
|
48
|
+
|
|
49
|
+
```jsonc
|
|
50
|
+
"placement": { "mode": "smart" }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
For globally distributed, read-heavy D1 traffic, enable
|
|
54
|
+
[read replication](https://developers.cloudflare.com/d1/best-practices/read-replication/)
|
|
55
|
+
on the database and pass one request-scoped `env.DB.withSession(...)` handle to
|
|
56
|
+
the selected generated Views, Procedures and Store. Those D1 entry points accept
|
|
57
|
+
`D1DatabaseSession`. Without the Sessions API, queries continue to use the primary.
|
|
58
|
+
|
|
59
|
+
Use `first-unconstrained` only when a replica snapshot is acceptable,
|
|
60
|
+
`first-primary` when the flow must start from the latest primary state, or carry
|
|
61
|
+
a bookmark between logical sessions for sequential consistency. Reuse the same
|
|
62
|
+
session within that logical flow; all writes still go to the primary. Keep fresh
|
|
63
|
+
authentication/authorization checks and mutation/read-after-write paths on their
|
|
64
|
+
required current-authority path rather than weakening them for replica latency.
|
|
65
|
+
|
|
43
66
|
## Operate
|
|
44
67
|
|
|
45
68
|
Enable Workers Logs and traces in production. The top-level `observability.enabled` alone does not enable traces:
|
|
@@ -20,9 +20,8 @@ Each media purpose in `siteDefaults.media.purposes` lists mime **slots** and a b
|
|
|
20
20
|
`required` entries use `<input accept>` grammar (full mime, comma list, or a subtype shorthand such as `webp`);
|
|
21
21
|
`maxBytes` must cover every mime the slots allow, keyed by the full mime type.
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
or credential management. Import `r2MediaStorage` from `@aotter/mantle/media/r2`:
|
|
23
|
+
Applications own their R2 binding, public bucket URL and S3 credentials; the SDK
|
|
24
|
+
package does not manage them. Import `r2MediaStorage` from `@aotter/mantle/media/r2`:
|
|
26
25
|
|
|
27
26
|
```ts
|
|
28
27
|
const storage = r2MediaStorage({
|
|
@@ -88,8 +88,11 @@ export default {
|
|
|
88
88
|
};
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
Keep the MCP and REST entries together for the normal symmetric path. For a deliberate
|
|
92
|
+
asymmetric exception, remove the omitted surface's imports and array entry. `mcp.ts`
|
|
93
|
+
exists only when the manifest has an MCP surface and requires the documented optional
|
|
94
|
+
MCP peer. An application that deliberately does not mount MCP need not import generated
|
|
95
|
+
`mcp.ts`; outside the entry import graph, it contributes nothing to the bundle.
|
|
93
96
|
Pass the same handlers to each selected surface that exposes ref Procedures.
|
|
94
97
|
Bind site/media/identity capabilities through the selected Admin/MCP
|
|
95
98
|
options; do not expose native auth administrative routes without `authRoutes` or
|
|
@@ -106,12 +109,19 @@ its collection parameter selects only a generated table and visibility policy.
|
|
|
106
109
|
|
|
107
110
|
## Workers assets and secrets
|
|
108
111
|
|
|
112
|
+
`mantle generate --admin --wrangler wrangler.jsonc` sets the selected assets
|
|
113
|
+
directory. A new assets block defaults to `binding: "ASSETS"`, `run_worker_first: true`
|
|
114
|
+
and `html_handling: "none"`. Existing blocks keep their binding and routing; when
|
|
115
|
+
`run_worker_first` is already true, a missing `html_handling` defaults to `none`.
|
|
116
|
+
A different binding needs a custom Admin HTML loader. Deploy the built assets and
|
|
117
|
+
Worker together.
|
|
118
|
+
|
|
109
119
|
`mantle generate cloudflare-d1 generated manifests/*.yaml --auth src/auth.ts --admin`
|
|
110
120
|
updates the split-build Wrangler keys (`main` and `base_dir` under `dist/worker`),
|
|
111
121
|
ensures `nodejs_compat` and defaults to `global_fetch_strictly_public` ([public fetch routing](deploy-and-operate.md#public-fetch-routing))
|
|
112
122
|
and, with `--admin`, `assets.directory`: `mantle bundle` builds Admin into
|
|
113
|
-
`dist/client/admin`, served at `/admin`. Configure
|
|
114
|
-
comments and unrelated settings survive regeneration:
|
|
123
|
+
`dist/client/admin`, served at `/admin`. Configure existing assets blocks explicitly;
|
|
124
|
+
JSONC comments and unrelated settings survive regeneration:
|
|
115
125
|
|
|
116
126
|
```jsonc
|
|
117
127
|
{
|
|
@@ -11,7 +11,7 @@ The runtime never creates, alters or checks tables. Boot and requests run no DDL
|
|
|
11
11
|
|
|
12
12
|
For the first install, apply generated `schema.sql`, including selected auth/site DDL, to an empty database. Apply selected seed SQL explicitly.
|
|
13
13
|
|
|
14
|
-
For every later schema change, the application's coding agent diffs the regenerated `schema.sql`, writes a migration with the host's native tool (`wrangler d1 migrations` or the app's PostgreSQL migration tool), rehearses it on a copy, and applies it before deploying the new code.
|
|
14
|
+
For every later schema change, the application's coding agent diffs the regenerated `schema.sql`, writes a migration with the host's native tool (`wrangler d1 migrations` or the app's PostgreSQL migration tool), rehearses it on a copy, and applies it before deploying the new code.
|
|
15
15
|
|
|
16
16
|
Destructive changes and type changes need explicit data migration. Mantle drops nothing. Initial `schema.sql` is not an upgrade script.
|
|
17
17
|
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
Use the [Worker quickstart](../start/quickstart-worker.md) for a complete native manifest and entry, [typed queries](../guides/typed-queries.md) for embedding, and [service composition](../cloudflare/service-entry.md) for optional surfaces.
|
|
4
4
|
|
|
5
|
-
The runnable [reference service](https://github.com/aotter/mantle/tree/v0.2.0-alpha.
|
|
5
|
+
The runnable [reference service](https://github.com/aotter/mantle/tree/v0.2.0-alpha.9/docs/examples/reference-service) exercises the native package integration. See the [generated public website](../../examples/generated-web/README.md) for a native Worker example.
|
|
@@ -13,21 +13,21 @@ carries.
|
|
|
13
13
|
The Mantle plugin carries one OSS authoring skill, `mantle`, and local
|
|
14
14
|
acceptance references. It does not configure a hosted-service connection.
|
|
15
15
|
Install from the Git tag matching the exact SDK version you selected (currently
|
|
16
|
-
`v0.2.0-alpha.
|
|
16
|
+
`v0.2.0-alpha.9`). Alpha releases integrate on `develop`; an unqualified
|
|
17
17
|
`aotter/mantle` source follows `main` and can install older, incompatible
|
|
18
18
|
instructions. A skill install does not upgrade the application's SDK.
|
|
19
19
|
|
|
20
20
|
For Codex, pin the marketplace checkout:
|
|
21
21
|
|
|
22
22
|
```sh
|
|
23
|
-
codex plugin marketplace add aotter/mantle --ref v0.2.0-alpha.
|
|
23
|
+
codex plugin marketplace add aotter/mantle --ref v0.2.0-alpha.9
|
|
24
24
|
codex plugin add mantle@mantle
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
For Claude Code, first clone the release into a directory you will keep:
|
|
28
28
|
|
|
29
29
|
```sh
|
|
30
|
-
git clone --depth 1 --branch v0.2.0-alpha.
|
|
30
|
+
git clone --depth 1 --branch v0.2.0-alpha.9 https://github.com/aotter/mantle.git mantle-plugin
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
Then, in Claude Code, use that checkout's absolute path:
|
|
@@ -41,7 +41,7 @@ Cursor and GitHub Copilot use their plugin manifests from the same tagged
|
|
|
41
41
|
checkout. Without a plugin host, install only the consumer skill by name:
|
|
42
42
|
|
|
43
43
|
```sh
|
|
44
|
-
npx skills add https://github.com/aotter/mantle/tree/v0.2.0-alpha.
|
|
44
|
+
npx skills add https://github.com/aotter/mantle/tree/v0.2.0-alpha.9 --skill mantle
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
When upgrading the SDK, update the marketplace ref or local tagged checkout and
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Independently accept a user-facing workflow
|
|
2
|
+
|
|
3
|
+
Before reporting any user-facing deliverable as done, exercise it from the
|
|
4
|
+
user's position with a fresh end-user agent. This applies to Admin pages and
|
|
5
|
+
customisations, application pages and web routes, REST APIs used by clients,
|
|
6
|
+
MCP tools and MCP Apps. Generation, typechecking, scripted smoke tests and an
|
|
7
|
+
author's replay remain useful; they do not establish independent acceptance. A
|
|
8
|
+
fresh context is not enough if the author still chooses a workflow that mirrors
|
|
9
|
+
the implementation. Every acceptance round gives the evaluator responsibility
|
|
10
|
+
for choosing realistic tasks and judging the result, not just following a
|
|
11
|
+
prescribed sequence.
|
|
12
|
+
If independent acceptance is not run, report `UNVERIFIED`; if a fresh evaluator
|
|
13
|
+
or authorized test transport is unavailable, report `BLOCKED`; neither outcome
|
|
14
|
+
is done.
|
|
15
|
+
|
|
16
|
+
Use the surface the intended user actually uses: a real browser session for
|
|
17
|
+
Admin and application pages; for REST, an HTTP client plus the public API
|
|
18
|
+
reference and base URL a real client developer receives; an MCP client for
|
|
19
|
+
tools; and the real MCP App host/AppBridge flow for Apps. This is an
|
|
20
|
+
application-authoring practice, not an SDK runtime service or a new test runner.
|
|
21
|
+
Use the project's existing local setup, normal authorized sign-in and isolated
|
|
22
|
+
fixtures. Setup hands over the role's established browser session or client
|
|
23
|
+
token; the evaluator never acquires or widens credentials. Give it only its
|
|
24
|
+
authorized surface. Do not enable extra routes, roles or generic writes to make
|
|
25
|
+
the exercise pass.
|
|
26
|
+
|
|
27
|
+
## Audit MCP tool design before implementation
|
|
28
|
+
|
|
29
|
+
Start with the original user brief, business role and explicit requirements,
|
|
30
|
+
not a list of CRUD endpoints or a task derived from what you implemented.
|
|
31
|
+
Before writing manifests or handlers, record the intended jobs and assumptions
|
|
32
|
+
in the existing design note. Keep the author's capability map separate from
|
|
33
|
+
the brief an evaluator receives: the evaluator chooses its own tasks and
|
|
34
|
+
observable success criteria. Do not expand application scope or authority
|
|
35
|
+
merely to satisfy an evaluator's newly inferred product wish.
|
|
36
|
+
|
|
37
|
+
In the application's existing design note or issue, map each goal step to its
|
|
38
|
+
proposed public capability: discovery inputs, returned IDs and evidence, allowed
|
|
39
|
+
caller, write/transaction boundary and recovery. Mark missing capabilities or
|
|
40
|
+
unresolved contracts as gaps before writing manifests or handlers. Review the
|
|
41
|
+
following rubric against that map:
|
|
42
|
+
|
|
43
|
+
| Design question | Required contract or explicit gap |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| Can the agent locate the right record? | Bounded search/filter inputs, stable IDs, deterministic cursor paging and sensible limits. It must not scan every list or guess an ID from a display label. |
|
|
46
|
+
| Can it choose an eligible relation? | Finding an ID is not proof of business eligibility: verify the role, state and ownership required by the relationship. Return enough labels to distinguish same-name choices before writing. |
|
|
47
|
+
| Can it correct an ordinary mistake? | State how to amend, clear or replace optional fields and relations, and how duplicate records are detected or reconciled. Test omitted, null and empty inputs against that contract; existence and type checks alone do not establish business validity. Express conditional-required inputs with the supported [input rules](../reference/surface.md) before writing handler checks. |
|
|
48
|
+
| Can it read a complete business result? | ID-based detail/readback under the intended caller, including every relation the goal creates or changes. A parent list is not proof of its child/contact/state details. Distinguish absent/inaccessible from incomplete evidence. |
|
|
49
|
+
| Can it verify a create or update? | Return the backend entity ID, actual committed version where applicable, and a safe result summary or a documented authorized readback. State the exact result nesting. Echoed input or the submitted `expectedVersion` is not proof of committed state. Specify PATCH versus full replacement and omitted/null-field semantics so an update cannot erase unseen fields. |
|
|
50
|
+
| What does a version protect? | Name its entity/snapshot scope; a parent version does not imply child rows or aggregates are unchanged. Use the reviewed observed version for OCC, define conflict behavior and preserve the user's proposed changes without silently rebinding/retrying. |
|
|
51
|
+
| Can it complete a business transition? | Name permitted source/destination states, preconditions, related effects and final readback. Prefer an outcome-oriented operation when arbitrary field updates would leave the workflow unfinished. |
|
|
52
|
+
| What commits together? | Define the native Procedure/Store atomic boundary. Separate tool calls are independent; describe partial success and deliberate compensation. Do not imply that an agent can roll back earlier calls or that compensation is always possible. |
|
|
53
|
+
| Can it interpret refusal and failure? | Document success and structured diagnostic/error envelopes, including MCP `isError`; HTTP 200 or a wrapper's transport success is insufficient. Inspect raw MCP diagnostics separately from host-visible wrapper errors; a wrapper label alone does not locate a runtime defect or require global error normalization. Missing relations, stale versions and denied callers need meaningful refusals without leaking another caller's records or native internals. |
|
|
54
|
+
| What happens after a duplicate or uncertain write? | State whether idempotency exists, its key/scope/lifetime when supported, or that repeated successful calls may repeat effects. Provide an authorized reconciliation route for timeout/unknown outcomes; never automatically retry an uncertain write. |
|
|
55
|
+
| Can it explain amounts correctly? | Declare currency, stored/wire units, exact-value representation, rounding and whether null means unknown. Label every ratio's numerator and denominator: cost/budget, sales/budget and gross/sales are different measures. |
|
|
56
|
+
| Does data remain usable without an App? | Keep ordinary reads data-only; attach a renderer deliberately for interactive inspection, with structured/text fallback. Rendering must not write or imply approval. Host catalog refresh, OAuth and real client/UI behavior have separate acceptance; descriptions cannot force generative UI layout. |
|
|
57
|
+
| Does every step respect the user's authority? | Verify least privilege, tenant/owner scope, allowed fields, relation access and negative cases on the server. Tool visibility, annotations, descriptions and UI confirmation never grant permission. Use authorized disposable fixtures and a cleanup owner without widening production writes/deletes. |
|
|
58
|
+
|
|
59
|
+
A design passes this audit only when each required goal step has a supported
|
|
60
|
+
capability and verifiable output/recovery; preserve unresolved gaps and owners.
|
|
61
|
+
The audit is a design review, not execution evidence. Record intentional
|
|
62
|
+
exclusions, but keep an unmet explicit user requirement unresolved until the
|
|
63
|
+
user changes its scope; do not redefine it as optional to make a round pass.
|
|
64
|
+
Reuse the installed version's Views,
|
|
65
|
+
Procedures, Store and surface contracts; a missing business tool is not by
|
|
66
|
+
itself an SDK runtime defect.
|
|
67
|
+
|
|
68
|
+
For simple creates/partial updates, prefer a declared Store-backed `ref`
|
|
69
|
+
Procedure: await the caller-bound Store write, return a safe allowlist with its
|
|
70
|
+
actual returned ID/version, and keep `requires` plus generated input validation
|
|
71
|
+
at invocation. A transaction-bound write result remains provisional until the
|
|
72
|
+
outer transaction commits; publish success only after that boundary completes. Store row policy alone is not Procedure authorization. Define
|
|
73
|
+
omitted versus null patch semantics; never fill unseen fields with defaults.
|
|
74
|
+
Keep SQL Procedures for atomic stock changes, conditional state transitions and
|
|
75
|
+
related writes. A TypeScript pre-read does not replace an atomic WHERE/CHECK;
|
|
76
|
+
D1 supports batch, not an interactive transaction or consuming an earlier batch
|
|
77
|
+
result. See the [Store write guide](typed-queries.md#prefer-store-backed-ref-procedures-for-simple-writes).
|
|
78
|
+
|
|
79
|
+
Then implement the minimum contract and run generated/type, authorization,
|
|
80
|
+
atomicity and other relevant regressions. Follow the independent acceptance
|
|
81
|
+
rounds below before claiming the user-facing work is done. Keep the author's
|
|
82
|
+
capability map and worked call sequence out of the fresh agent's prompt: an MCP
|
|
83
|
+
evaluator must discover the workflow from public tool descriptions and results.
|
|
84
|
+
|
|
85
|
+
## Run independent acceptance rounds until they converge
|
|
86
|
+
|
|
87
|
+
1. **Freeze scope and revision, not the evaluator's itinerary.** Supply the
|
|
88
|
+
original user brief, business role, explicit requirements and exclusions,
|
|
89
|
+
exact application/SDK revision, authorized transport, permitted fake-data
|
|
90
|
+
actions, per-round interaction/time/write budgets, a user-set total round cap of at
|
|
91
|
+
least three (default four) and a fixture cleanup owner. Seed fixtures with
|
|
92
|
+
realistic volume and variety: other owners' records, several states and more
|
|
93
|
+
than one page of results, so evaluators' own goals reach discovery, paging
|
|
94
|
+
and authority. Explicitly authorize synthetic writes only where the user has
|
|
95
|
+
authorized testing. Production writes and external side effects need their
|
|
96
|
+
own authorization.
|
|
97
|
+
2. **Use a new end-user subagent in every round.** Start without inherited
|
|
98
|
+
implementation history. Withhold source, SQL, internal docs, capability maps,
|
|
99
|
+
precomputed IDs, hidden routes or tool names, prescribed interaction order
|
|
100
|
+
and previous reports or fixes. Ask
|
|
101
|
+
the agent to state its own realistic goals and observable success criteria
|
|
102
|
+
before execution, use or discover the user-facing surface and assess whether
|
|
103
|
+
it supports the work. Ask whether it can make the decisions needed for its
|
|
104
|
+
goals, not merely issue successful calls. Do not prescribe the author's
|
|
105
|
+
happy path or require a minimum number of defects. Zero findings is a valid
|
|
106
|
+
observation. Run the evaluator outside the project directory, without Mantle
|
|
107
|
+
skills or project instruction files, with only the intended browser, HTTP
|
|
108
|
+
client, MCP client or MCP App host/AppBridge available, and record that
|
|
109
|
+
isolation in the evidence. For a multi-role workflow, run one
|
|
110
|
+
fresh agent per role in the same round on shared live data in the brief's
|
|
111
|
+
natural order; the later role finds earlier records through the service, and
|
|
112
|
+
the first agent may take a resumed turn to observe the outcome. Each agent
|
|
113
|
+
still chooses its own goals.
|
|
114
|
+
3. **Keep evidence and scope judgments.** Require PASS/FAIL/BLOCKED per chosen
|
|
115
|
+
goal, actual readbacks, rejected writes, recovery, interaction costs and
|
|
116
|
+
design findings with materiality, evidence and the smallest suggested repair.
|
|
117
|
+
Separate a violated explicit requirement, a demonstrated defect and a newly
|
|
118
|
+
inferred product need. Missing capability is a finding, not permission to
|
|
119
|
+
invent interfaces or automatically expand the product. A rendered page,
|
|
120
|
+
HTTP 200, successful tool result or creation ID alone does not prove a
|
|
121
|
+
complete business result. Preserve setup assistance
|
|
122
|
+
and label any coached attempt; do not count it as a clean independent round.
|
|
123
|
+
4. **Repair outside the evaluator's context.** The author retains the full
|
|
124
|
+
findings ledger, classifies application design, SDK/runtime, docs, transport
|
|
125
|
+
setup and host issues separately, then makes the smallest in-scope repair.
|
|
126
|
+
Turn reproduced failures into focused regression checks and run them with
|
|
127
|
+
relevant authorization, OCC and atomicity checks. Keep native guards; do not
|
|
128
|
+
shim away errors or retry uncertain writes. A runtime change needs a minimal
|
|
129
|
+
supported-contract reproduction. Record optional product suggestions and
|
|
130
|
+
unresolved scope questions rather than silently dropping findings.
|
|
131
|
+
5. **Repeat the whole evaluation with a fresh agent.** Freeze the new revision
|
|
132
|
+
and supply the same original brief and authority, with new fixtures. Let
|
|
133
|
+
this evaluator choose its own goals again; do not pass the ledger or steer
|
|
134
|
+
it toward the previous repair. Replaying a known failure remains an author's
|
|
135
|
+
regression check and does not replace independent design evaluation.
|
|
136
|
+
6. **Judge convergence across rounds.** Converge only after two consecutive
|
|
137
|
+
unassisted fresh rounds on the same revision with no new material in-scope
|
|
138
|
+
finding and no unresolved material defect. A finding is material when it
|
|
139
|
+
violates an explicit requirement, makes an evaluator's goal FAIL or BLOCKED,
|
|
140
|
+
or exposes an authority, data-integrity or uncertain-write defect; record
|
|
141
|
+
why any finding was judged non-material. Every repair creates a new revision
|
|
142
|
+
and resets the streak, so hold non-material repairs until after the verdict.
|
|
143
|
+
Each explicit requirement observable from the evaluator's role needs
|
|
144
|
+
evaluator-observed evidence; requirements outside that role's view (another
|
|
145
|
+
caller's isolation or hidden state) are verified by author regressions and
|
|
146
|
+
reported under that label. A role-observable requirement no evaluator reached
|
|
147
|
+
is UNVERIFIED and blocks CONVERGED. If fresh evaluators never reach a required
|
|
148
|
+
path, vary the fixtures, not the brief or the steps, or report that path as
|
|
149
|
+
UNVERIFIED. Repeated success on one narrow path cannot establish broader
|
|
150
|
+
readiness. Keep optional wishes, non-material suggestions and external
|
|
151
|
+
BLOCKED gates visible; no finding count proves absence of defects. When the
|
|
152
|
+
round cap or an external blocker stops the loop, report NOT CONVERGED or
|
|
153
|
+
BLOCKED with the remaining work, not acceptance. Setup cleans isolated
|
|
154
|
+
fixtures in `finally` after every round, including failed/aborted rounds.
|
|
155
|
+
|
|
156
|
+
If a fresh agent or authorized test transport is unavailable, record independent
|
|
157
|
+
acceptance as BLOCKED and report the scripted checks separately. Do not invent
|
|
158
|
+
an independent result. Do not deploy or publish merely to obtain acceptance.
|
|
159
|
+
|
|
160
|
+
## Give the agent a role and the user's brief
|
|
161
|
+
|
|
162
|
+
Fill in a brief like this using the actual user's request and authorized
|
|
163
|
+
scope. Do not replace it with a sequence tailored to existing tools:
|
|
164
|
+
|
|
165
|
+
> You are a [business role] working in [business context]. The user requested:
|
|
166
|
+
> [original brief, explicit requirements and exclusions]. You are authorized
|
|
167
|
+
> to test with fictional data through [user-facing surface and entry point]
|
|
168
|
+
> within [allowed actions and interaction/time/write budgets]; [owner] will clean up
|
|
169
|
+
> the fixtures.
|
|
170
|
+
> Independently define realistic acceptance goals and observable success
|
|
171
|
+
> criteria before testing. Use or discover the supplied interface and assess
|
|
172
|
+
> whether it lets you make the decisions your goals require, not merely perform
|
|
173
|
+
> successful interactions. Report per-goal PASS/FAIL/BLOCKED with readback
|
|
174
|
+
> evidence, design shortcomings, interaction costs and minimal improvements.
|
|
175
|
+
> Distinguish failures within the requested scope from newly inferred product
|
|
176
|
+
> needs; do not invent unsupported interfaces or change the product. Do not inspect
|
|
177
|
+
> source, internal docs or a database, blindly retry uncertain writes, or act
|
|
178
|
+
> outside the supplied authority. Finding no defects is acceptable; report
|
|
179
|
+
> what you actually covered and what remains unverified.
|
|
180
|
+
|
|
181
|
+
The author separately verifies isolation between callers, stale-version
|
|
182
|
+
refusal, unknown-outcome handling and effects hidden from the evaluator's role
|
|
183
|
+
through authorized test fixtures. Do not grant the evaluator extra authority
|
|
184
|
+
just to observe hidden state. Keep author regressions distinct from what the
|
|
185
|
+
fresh agent actually observed.
|
|
186
|
+
|
|
187
|
+
## Record evidence in the existing issue or handoff
|
|
188
|
+
|
|
189
|
+
Use ordinary Markdown; no new receipt schema or framework is required:
|
|
190
|
+
|
|
191
|
+
- Original brief, explicit requirements and exclusions, role, environment,
|
|
192
|
+
per-round budgets and round cap.
|
|
193
|
+
- Setup assistance and what each agent could access; identify distinct rounds.
|
|
194
|
+
- Per round: revision, the evaluator's own goals and criteria, per-goal
|
|
195
|
+
PASS/FAIL/BLOCKED, actual readbacks and interaction references,
|
|
196
|
+
request/page/tool counts
|
|
197
|
+
and negative/recovery attempts, with their differing scope stated.
|
|
198
|
+
- Findings ledger with materiality, owner, disposition, minimal repair and
|
|
199
|
+
regression commands/results.
|
|
200
|
+
- Streak and reset history; coverage of each explicit requirement
|
|
201
|
+
(evaluator-observed, author regression only, or UNVERIFIED); the final
|
|
202
|
+
CONVERGED / NOT CONVERGED / BLOCKED / UNVERIFIED verdict and rounds used. Retain every
|
|
203
|
+
failed report.
|
|
204
|
+
- Fixture cleanup result and every remaining limitation.
|
|
205
|
+
|
|
206
|
+
Keep credentials, raw auth logs, full resource HTML and private operational
|
|
207
|
+
details local/private; publish only sanitized contract evidence. Do not claim a
|
|
208
|
+
call-cost improvement when rounds exercise different negative/discovery cases.
|
|
209
|
+
Version fields usually describe an entity row, not all related aggregates;
|
|
210
|
+
state money units and the denominator of any budget or margin calculation.
|
|
211
|
+
|
|
212
|
+
## Keep surface evidence distinct
|
|
213
|
+
|
|
214
|
+
Evidence from one surface does not prove another. Tool metadata and protocol
|
|
215
|
+
discovery do not prove that an MCP App rendered in its real host through
|
|
216
|
+
AppBridge; a REST response does not prove the Admin or application page; and a
|
|
217
|
+
browser rendering does not prove an external client can use the REST contract.
|
|
218
|
+
Record the actual browser, client or host version, identity, observed interface
|
|
219
|
+
and behavior for the deliverable under acceptance. Keep data results available
|
|
220
|
+
without Apps; descriptions cannot force a host's generative UI layout. Local
|
|
221
|
+
cookie-session acceptance, OAuth client integration and long-term renewal
|
|
222
|
+
likewise require their own evidence when they are part of the requested work.
|
|
@@ -107,6 +107,82 @@ An explicit `uri` keeps application-owned identity. A custom `html` function mus
|
|
|
107
107
|
supply a build-specific `uri`: Core cannot infer its build or safely memoize its
|
|
108
108
|
request-dependent output. A raw `html` string may use the automatic identity.
|
|
109
109
|
|
|
110
|
+
### Keep old conversations working after an upgrade
|
|
111
|
+
|
|
112
|
+
A new URI does not invalidate a host's saved tool definitions or old conversation
|
|
113
|
+
cards. Keep previously deployed pages with `adminMcpApps({ catalogue, previous })`.
|
|
114
|
+
`previous` is an array of `AdminMcpSnapshot` from `@aotter/mantle-admin/mcp`:
|
|
115
|
+
`{ uri, html, csp?, domain?, title? }`. Each `html` includes the original `mantle-catalog`
|
|
116
|
+
script (the page returned by `resources/read`, not the raw bundle). The SDK serves
|
|
117
|
+
its original HTML and metadata at its original URI, filters its catalogue against
|
|
118
|
+
the current caller's tools on every read, and gives only the current build renderer
|
|
119
|
+
ownership. A removed/denied tool gains no authority through an old page.
|
|
120
|
+
|
|
121
|
+
Store these snapshots in the application's source or durable artifact storage;
|
|
122
|
+
an isolate cache or untracked `dist/` is insufficient for a clean CI deployment.
|
|
123
|
+
Keep an explicit retention window. Old pages outside that window and incompatible
|
|
124
|
+
tool renames/input/output changes need an application migration. Never answer an
|
|
125
|
+
old digest with the latest HTML. Host metadata refresh is a development/recovery
|
|
126
|
+
operation, not a normal compatible-upgrade requirement.
|
|
127
|
+
|
|
128
|
+
For the default built loader, share the exact declaration in
|
|
129
|
+
`src/mcp-app-definition.ts` (no `html` or `previous`):
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { mcpAppCatalogues } from '../generated/admin-descriptors.ts';
|
|
133
|
+
export const appDefinition = { catalogue: mcpAppCatalogues.admin };
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Keep a committed `mcp-app-previous.json` at the project root (initially `[]`). Load
|
|
137
|
+
it only inside the service's MCP branch, before constructing the handler, so other
|
|
138
|
+
routes do not parse archived full-page HTML at startup:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
const { default: previous } = await import('../mcp-app-previous.json', { with: { type: 'json' } });
|
|
142
|
+
serveMcpHandler ??= serveMcp(tools, { ...options, apps: {
|
|
143
|
+
admin: adminMcpApps({ ...appDefinition, previous }),
|
|
144
|
+
} });
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Keep custom `renders`, catalogue extensions, `domain` and `title` in the shared
|
|
148
|
+
definition too. Node 22.18+ can read these generated `.ts` modules; a TypeScript
|
|
149
|
+
app using `.ts` import paths enables `allowImportingTsExtensions` with `noEmit`.
|
|
150
|
+
Keep snapshots outside the directory containing `admin.tsx`: Tailwind scans that
|
|
151
|
+
directory, and compiled HTML/JavaScript inside a snapshot can be mistaken for
|
|
152
|
+
utility class names and change subsequent CSS builds.
|
|
153
|
+
|
|
154
|
+
After a successful deployment, archive the **exact asset just deployed** with
|
|
155
|
+
this application-owned `scripts/archive-mcp-app.mjs`, then commit the snapshot
|
|
156
|
+
for the next build. Run it in the deployment script, not as a manual cache fix:
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
160
|
+
import { adminMcpApps } from '@aotter/mantle-admin/mcp';
|
|
161
|
+
import { appDefinition } from '../src/mcp-app-definition.ts';
|
|
162
|
+
|
|
163
|
+
const path = 'mcp-app-previous.json';
|
|
164
|
+
const previous = JSON.parse(readFileSync(path, 'utf8'));
|
|
165
|
+
const context = { env: {}, request: new Request('https://archive.invalid/mcp') };
|
|
166
|
+
const source = adminMcpApps({ ...appDefinition,
|
|
167
|
+
html: readFileSync('dist/client/admin/mcp-app.html', 'utf8') });
|
|
168
|
+
const { resources: [resource] } = typeof source === 'function' ? await source(context) : source;
|
|
169
|
+
const tools = new Set([...Object.keys(appDefinition.catalogue.views),
|
|
170
|
+
...Object.keys(appDefinition.catalogue.actions)]);
|
|
171
|
+
const loaded = typeof resource.html === 'string' ? resource.html
|
|
172
|
+
: await resource.html({ ...context, tools });
|
|
173
|
+
const page = typeof loaded === 'string' ? { html: loaded } : loaded;
|
|
174
|
+
const snapshot = { uri: resource.uri, title: resource.title, ...page,
|
|
175
|
+
...(resource.domain === undefined ? {} : { domain: resource.domain }) };
|
|
176
|
+
// This application's policy: retain the last two deployed pages for future builds.
|
|
177
|
+
writeFileSync(path, JSON.stringify([...previous.filter(p => p.uri !== snapshot.uri), snapshot].slice(-2)) + '\n');
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
This archives static UI/catalogue metadata, never rows, cookies or OAuth tokens.
|
|
181
|
+
Custom loaders must archive their actual immutable resource, including CSP/domain,
|
|
182
|
+
instead of using this default-asset recipe. Compare the archived URI with deployed
|
|
183
|
+
`tools/list`/`resources/read`; a mismatching declaration or stale local build is
|
|
184
|
+
not deployment evidence.
|
|
185
|
+
|
|
110
186
|
Core accepts static contributors or async `(context: { env, request }) => McpApps`
|
|
111
187
|
sources, including mixed arrays. Static declarations are checked at construction;
|
|
112
188
|
async sources resolve after the endpoint's origin and caller gates, then their
|
|
@@ -133,6 +209,8 @@ authorization, caller, origin and database behavior.
|
|
|
133
209
|
|
|
134
210
|
## The Admin App in a host
|
|
135
211
|
|
|
212
|
+
> **Verification status:** automated AppBridge coverage exists, but the compatibility claims in this section have not yet been verified manually in real ChatGPT or Claude hosts.
|
|
213
|
+
|
|
136
214
|
The Admin App speaks MCP Apps `2026-01-26` to the host and asks only for what
|
|
137
215
|
the host advertises in `hostCapabilities`:
|
|
138
216
|
|
|
@@ -175,7 +253,6 @@ full screen, a row action, and a link. Check too that the host advertises
|
|
|
175
253
|
`hostCapabilities.serverTools`; without it the App is read-only. The automated interoperability test
|
|
176
254
|
uses the official `AppBridge`; your app runs its generated version ([Run the compatibility test](#run-the-compatibility-test)).
|
|
177
255
|
|
|
178
|
-
|
|
179
256
|
## Customising Admin for MCP hosts
|
|
180
257
|
|
|
181
258
|
`mantle bundle` builds the Admin MCP App from the same entry as the browser Admin
|
|
@@ -344,6 +344,10 @@ The API advertises capabilities for actually mounted routes, including statistic
|
|
|
344
344
|
|
|
345
345
|
## Acceptance
|
|
346
346
|
|
|
347
|
+
Before reporting Admin pages or customisations as done, follow the [independent
|
|
348
|
+
acceptance guide](agent-workflow-acceptance.md) with a fresh evaluator in the
|
|
349
|
+
real Admin browser session.
|
|
350
|
+
|
|
347
351
|
Use a disposable database for native tests, then exercise actual OTP sign-in,
|
|
348
352
|
the Admin console and member frontend. Check invalid input, another tenant's
|
|
349
353
|
rows, expired rows, publishing transitions, related/translated entries, paging,
|