@aotter/mantle 0.2.0-alpha.9 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +9 -3
  2. package/dist/auth/adapter.js +1 -28
  3. package/dist/auth/admin.js +2 -5
  4. package/dist/auth/caller.js +0 -27
  5. package/dist/auth/consent.js +100 -0
  6. package/dist/auth/hardening.js +0 -7
  7. package/dist/auth/identity.js +0 -11
  8. package/dist/auth/oauth.js +0 -32
  9. package/dist/auth/resource-seeds.js +0 -2
  10. package/dist/auth/route-http.js +33 -0
  11. package/dist/auth/routes.js +5 -151
  12. package/dist/build/compiler.d.ts +19 -609
  13. package/dist/build/compiler.js +3 -6896
  14. package/dist/build/spec/domain/model/SqlIr.d.ts +2 -0
  15. package/dist/build/spec/domain/model/UserFields.d.ts +1 -0
  16. package/dist/bun/assets.js +0 -2
  17. package/dist/domain/adminOrder.js +0 -2
  18. package/dist/domain/columns.js +7 -20
  19. package/dist/domain/describe/chunk-SGMRVDGE.js +1 -0
  20. package/dist/domain/describe/index.js +1 -115
  21. package/dist/domain/describe/target-hash.js +1 -12
  22. package/dist/domain/entry.js +0 -14
  23. package/dist/domain/generated-tools.js +0 -1
  24. package/dist/domain/input.js +0 -2
  25. package/dist/domain/paging.js +0 -17
  26. package/dist/domain/recipes.js +10 -7
  27. package/dist/domain/userReferences.js +0 -1
  28. package/dist/domain/wireScalars.js +0 -6
  29. package/dist/infrastructure/adminEntries.js +10 -7
  30. package/dist/infrastructure/adminPaging.js +55 -57
  31. package/dist/infrastructure/artifacts.js +0 -4
  32. package/dist/infrastructure/authSchema.js +0 -9
  33. package/dist/infrastructure/bundle.js +2 -30
  34. package/dist/infrastructure/cli.js +25 -11
  35. package/dist/infrastructure/generate.js +9 -16
  36. package/dist/infrastructure/generateAuth.js +1 -9
  37. package/dist/infrastructure/help.js +55 -0
  38. package/dist/infrastructure/init.js +50 -0
  39. package/dist/infrastructure/inputs.js +0 -9
  40. package/dist/infrastructure/mounts.js +22 -0
  41. package/dist/infrastructure/wrangler.js +0 -6
  42. package/dist/kernel/authorization.js +0 -1
  43. package/dist/kernel/binds.d.ts +16 -4
  44. package/dist/kernel/binds.js +4 -10
  45. package/dist/kernel/caller-identity.d.ts +0 -3
  46. package/dist/kernel/caller-identity.js +0 -15
  47. package/dist/kernel/caller-mode.d.ts +4 -0
  48. package/dist/kernel/caller-mode.js +14 -0
  49. package/dist/kernel/contracts.d.ts +6 -10
  50. package/dist/kernel/contracts.js +5 -41
  51. package/dist/kernel/cron.js +0 -4
  52. package/dist/kernel/dialect/common.js +0 -3
  53. package/dist/kernel/dialect/postgres.js +0 -2
  54. package/dist/kernel/dialect/sqlite.js +1 -6
  55. package/dist/kernel/drain.js +2 -8
  56. package/dist/kernel/driver/bun-sqlite.js +0 -4
  57. package/dist/kernel/driver/d1.js +0 -1
  58. package/dist/kernel/driver/postgres.js +2 -17
  59. package/dist/kernel/driver/transaction-context.js +0 -1
  60. package/dist/kernel/email.js +0 -4
  61. package/dist/kernel/host-access.js +2 -3
  62. package/dist/kernel/host-caller.d.ts +1 -1
  63. package/dist/kernel/host-caller.js +3 -6
  64. package/dist/kernel/json-body.js +0 -2
  65. package/dist/kernel/mcp-apps.js +0 -5
  66. package/dist/kernel/mcp-call.js +0 -1
  67. package/dist/kernel/mcp-handler.js +2 -26
  68. package/dist/kernel/mcp.js +0 -5
  69. package/dist/kernel/media.js +0 -18
  70. package/dist/kernel/paging.js +1 -9
  71. package/dist/kernel/publishing.js +3 -4
  72. package/dist/kernel/r2-media.js +0 -7
  73. package/dist/kernel/rest-dispatch.js +0 -1
  74. package/dist/kernel/rest-router.js +2 -3
  75. package/dist/kernel/rest.js +0 -3
  76. package/dist/kernel/scalars.js +1 -9
  77. package/dist/kernel/search.js +0 -1
  78. package/dist/kernel/site.js +0 -2
  79. package/dist/kernel/store.d.ts +11 -1
  80. package/dist/kernel/store.js +21 -34
  81. package/dist/kernel/surface-failure.d.ts +10 -0
  82. package/dist/kernel/surface-failure.js +29 -0
  83. package/dist/kernel/surface.js +0 -3
  84. package/dist/kernel/sweep.js +0 -1
  85. package/dist/kernel/translations.js +0 -1
  86. package/dist/kernel/user-caller.js +2 -8
  87. package/dist/kernel/user-fields.d.ts +3 -2
  88. package/dist/kernel/user-fields.js +9 -9
  89. package/dist/kernel/validation.js +0 -8
  90. package/dist/kernel/web-gateway.js +0 -2
  91. package/dist/kernel/web-head.js +0 -1
  92. package/dist/kernel/web-index.js +0 -1
  93. package/dist/kernel/web-response.js +0 -1
  94. package/dist/kernel/web-serve.js +97 -0
  95. package/dist/kernel/web.d.ts +1 -1
  96. package/dist/kernel/web.js +10 -91
  97. package/dist/kernel/where.js +0 -6
  98. package/dist/usecase/adminBindings.js +30 -21
  99. package/dist/usecase/adminClient.js +0 -21
  100. package/dist/usecase/adminDescriptors.js +2 -9
  101. package/dist/usecase/generate.js +7 -12
  102. package/dist/usecase/handlers.js +0 -3
  103. package/dist/usecase/mcp-media.js +0 -1
  104. package/dist/usecase/mcp.js +0 -4
  105. package/dist/usecase/paging.js +1 -3
  106. package/dist/usecase/publishing.js +0 -4
  107. package/dist/usecase/rest.js +0 -1
  108. package/dist/usecase/site.js +0 -1
  109. package/dist/usecase/stores.js +4 -14
  110. package/dist/usecase/tool-version.js +0 -3
  111. package/dist/usecase/translations.js +0 -2
  112. package/dist/usecase/validation.js +0 -20
  113. package/docs/examples/README.md +13 -5
  114. package/docs/examples/app-shell/README.md +38 -7
  115. package/docs/examples/app-shell/accept.mjs +91 -0
  116. package/docs/examples/app-shell/gitignore +7 -0
  117. package/docs/examples/app-shell/package.json +7 -6
  118. package/docs/examples/app-shell/smoke-kit.mjs +29 -15
  119. package/docs/examples/app-shell/src/routes.ts +6 -3
  120. package/docs/examples/app-shell/src/service.ts +2 -1
  121. package/docs/examples/club-events/README.md +7 -2
  122. package/docs/examples/club-events/manifests/club-events.yaml +15 -0
  123. package/docs/examples/club-events/smoke.mjs +7 -1
  124. package/docs/examples/purchasing/README.md +12 -3
  125. package/docs/examples/purchasing/manifests/purchasing.yaml +21 -4
  126. package/docs/examples/purchasing/smoke.mjs +14 -0
  127. package/docs/examples/recipes/README.md +5 -3
  128. package/docs/handbook/cloudflare/service-entry.md +3 -0
  129. package/docs/handbook/examples/hub.md +1 -1
  130. package/docs/handbook/guides/agent-setup.md +15 -10
  131. package/docs/handbook/guides/agent-workflow-acceptance.md +113 -103
  132. package/docs/handbook/guides/local-testing.md +40 -0
  133. package/docs/handbook/guides/native-admin.md +25 -6
  134. package/docs/handbook/guides/native-execution.md +5 -4
  135. package/docs/handbook/guides/public-pages.md +44 -4
  136. package/docs/handbook/guides/sql-patterns.md +266 -0
  137. package/docs/handbook/guides/typed-queries.md +138 -0
  138. package/docs/handbook/navigation.json +16 -0
  139. package/docs/handbook/reference/admin-descriptors.md +4 -1
  140. package/docs/handbook/reference/authorization.md +1 -1
  141. package/docs/handbook/reference/diagnostics.md +6 -1
  142. package/docs/handbook/reference/procedure.md +5 -5
  143. package/docs/handbook/reference/schema.md +10 -4
  144. package/docs/handbook/reference/sql.md +64 -0
  145. package/docs/handbook/reference/surface.md +5 -2
  146. package/docs/handbook/reference/view.md +11 -3
  147. package/docs/handbook/releases/index.md +27 -6
  148. package/docs/handbook/start/agents.md +45 -0
  149. package/docs/handbook/start/project-and-cli.md +6 -0
  150. package/docs/handbook/start/quickstart-worker.md +1 -1
  151. package/docs/skills/develop/SKILL.md +6 -5
  152. package/docs/skills/update/SKILL.md +2 -0
  153. package/package.json +23 -3
  154. package/skills/README.md +1 -1
  155. package/skills/mantle/SKILL.md +39 -120
  156. package/dist/domain/describe/chunk-ZVXBGP7Y.js +0 -424
  157. package/skills/mantle/references/local-testing.md +0 -20
  158. package/skills/mantle/references/web.md +0 -41
  159. package/skills/mantle/references/website.md +0 -51
@@ -171,6 +171,20 @@ try {
171
171
  assert.deepEqual(received.map(order => order.reference), [laptop, projector]);
172
172
  assert.deepEqual(await statusAndOrder(projector), { status: "ordered", supplierOrderId: "SUP-2" });
173
173
  step("daily cron retries the failed supplier order (system caller) and emails every admin about requests pending more than 3 days, and only those");
174
+
175
+ const small = await call("POST", "/api/requests", { cookie: bob, body: { title: "Pens", amount: 50, department: "Engineering" } });
176
+ assert.equal(small.status, 200, JSON.stringify(small.body));
177
+ const pens = small.body.results[0][0];
178
+ assert.equal(pens.requestStatus, "approved");
179
+ const spent = await remaining("Engineering");
180
+ assert.deepEqual((await ledgerRows()).filter(r => r.requestId === pens.id), [{ requestId: pens.id, amount: 50, remainingAfter: spent }]);
181
+ await query(`UPDATE budgets SET remaining = 20 WHERE department = 'Design'`);
182
+ const ledgerBefore = (await ledgerRows()).length, requestsBefore = (await query("SELECT count(*) AS n FROM requests"))[0].n;
183
+ assert.equal((await call("POST", "/api/requests", { cookie: alice, body: { title: "Tape", amount: 30, department: "Design" } })).status, 409);
184
+ assert.equal(await remaining("Design"), 20);
185
+ assert.equal((await ledgerRows()).length, ledgerBefore);
186
+ assert.equal((await query("SELECT count(*) AS n FROM requests"))[0].n, requestsBefore);
187
+ step("CASE approves a request of 100 or less at submission: the status, the budget and the ledger row commit together, and one that would overspend is refused with nothing written");
174
188
  }, { vars: { EMPLOYEE_DOMAIN: "example.test", SUPPLIER_API_URL: `http://127.0.0.1:${supplier.address().port}/orders`, SUPPLIER_WEBHOOK_SECRET: SECRET } });
175
189
  } finally {
176
190
  supplier.close();
@@ -25,11 +25,13 @@ Search for `AGENT:` for what an app must still provide: layout and styling, cach
25
25
  - Need members to see or edit only their own rows? Add club-events' `restrict: { recipes: "author_id = auth.uid()" }` on a View.
26
26
  - Need review before publishing, or an AI that approves? Add purchasing's guarded status transitions and admin MCP.
27
27
 
28
- ## Copy
28
+ ## Run it
29
+
30
+ This runs the example by itself, to read and try. For your own app, run `mantle init` and take only the patterns you need into its manifests and `src/` (`mantle help examples`).
29
31
 
30
32
  ```sh
31
- cp -R node_modules/@aotter/mantle/docs/examples/app-shell/. my-app/
32
- cp -R node_modules/@aotter/mantle/docs/examples/recipes/. my-app/
33
+ npx mantle init my-app
34
+ cp -R node_modules/@aotter/mantle/docs/examples/recipes/. my-app/ # run the example as is
33
35
  cd my-app && pnpm install && pnpm generate && pnpm types && pnpm check
34
36
  ```
35
37
 
@@ -81,6 +81,9 @@ export default {
81
81
  if (shell) return shell;
82
82
  // Your public frontend/web.ts routes can use an anonymous Caller without
83
83
  // loading auth; their generated SQL still controls publication visibility.
84
+ // Mounted here, after every Mantle surface, a root catch-all page (/:slug)
85
+ // cannot shadow /admin, /mcp or REST. Mount them earlier only behind a
86
+ // guard that skips those paths, as the app shell does.
84
87
  return new Response('Not found', { status: 404, headers: { 'cache-control': 'no-store' } });
85
88
  } catch (error) { return surfaceFailure(error); }
86
89
  });
@@ -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.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.
5
+ The runnable [reference service](https://github.com/aotter/mantle/tree/v0.2.0/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,20 @@ 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.9`). Alpha releases integrate on `develop`; an unqualified
17
- `aotter/mantle` source follows `main` and can install older, incompatible
18
- instructions. A skill install does not upgrade the application's SDK.
16
+ `v0.2.0`). Pin the tag to keep instructions aligned with the application's
17
+ SDK. A skill install does not upgrade the application's SDK.
19
18
 
20
19
  For Codex, pin the marketplace checkout:
21
20
 
22
21
  ```sh
23
- codex plugin marketplace add aotter/mantle --ref v0.2.0-alpha.9
22
+ codex plugin marketplace add aotter/mantle --ref v0.2.0
24
23
  codex plugin add mantle@mantle
25
24
  ```
26
25
 
27
26
  For Claude Code, first clone the release into a directory you will keep:
28
27
 
29
28
  ```sh
30
- git clone --depth 1 --branch v0.2.0-alpha.9 https://github.com/aotter/mantle.git mantle-plugin
29
+ git clone --depth 1 --branch v0.2.0 https://github.com/aotter/mantle.git mantle-plugin
31
30
  ```
32
31
 
33
32
  Then, in Claude Code, use that checkout's absolute path:
@@ -41,7 +40,7 @@ Cursor and GitHub Copilot use their plugin manifests from the same tagged
41
40
  checkout. Without a plugin host, install only the consumer skill by name:
42
41
 
43
42
  ```sh
44
- npx skills add https://github.com/aotter/mantle/tree/v0.2.0-alpha.9 --skill mantle
43
+ npx skills add https://github.com/aotter/mantle/tree/v0.2.0 --skill mantle
45
44
  ```
46
45
 
47
46
  When upgrading the SDK, update the marketplace ref or local tagged checkout and
@@ -54,10 +53,16 @@ project and copies no handbook.
54
53
 
55
54
  ## 2. Install the SDK, then read its own docs
56
55
 
57
- Use the version the project already pins. For a new project, choose a 0.2.x
58
- version with `npm view @aotter/mantle dist-tags` and install it with an exact
59
- version; every `@aotter/mantle*` package stays at that same version. Then
60
- read, from the application:
56
+ Use the version the project already pins. For a new project, install stable
57
+ 0.2.0 exactly:
58
+
59
+ ```sh
60
+ pnpm add --save-exact @aotter/mantle@0.2.0
61
+ ```
62
+
63
+ Add optional `@aotter/mantle-ui@0.2.0` and `@aotter/mantle-admin@0.2.0` only
64
+ when the selected surfaces need them. Every selected `@aotter/mantle*` package
65
+ stays at the same exact version. Read, from the application:
61
66
 
62
67
  ```text
63
68
  node_modules/@aotter/mantle/README.md
@@ -6,9 +6,9 @@ customisations, application pages and web routes, REST APIs used by clients,
6
6
  MCP tools and MCP Apps. Generation, typechecking, scripted smoke tests and an
7
7
  author's replay remain useful; they do not establish independent acceptance. A
8
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.
9
+ the implementation. Every acceptance round gives the evaluator only a
10
+ role and the entry points that role uses; it derives realistic tasks itself
11
+ and the author scores them against a private rubric.
12
12
  If independent acceptance is not run, report `UNVERIFIED`; if a fresh evaluator
13
13
  or authorized test transport is unavailable, report `BLOCKED`; neither outcome
14
14
  is done.
@@ -29,8 +29,8 @@ the exercise pass.
29
29
  Start with the original user brief, business role and explicit requirements,
30
30
  not a list of CRUD endpoints or a task derived from what you implemented.
31
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
32
+ in the existing design note. The capability map feeds the private rubric
33
+ (below); an evaluator receives only its role and chooses its own tasks and
34
34
  observable success criteria. Do not expand application scope or authority
35
35
  merely to satisfy an evaluator's newly inferred product wish.
36
36
 
@@ -82,101 +82,111 @@ rounds below before claiming the user-facing work is done. Keep the author's
82
82
  capability map and worked call sequence out of the fresh agent's prompt: an MCP
83
83
  evaluator must discover the workflow from public tool descriptions and results.
84
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.
85
+ ## Run role-only acceptance rounds until they converge
86
+
87
+ The evaluator gets a role, not a task list. It works out what that role needs
88
+ to get done from the interface itself; what it derives, and what it misses, is
89
+ the evidence.
90
+
91
+ 1. **Write a private rubric first.** From the original brief, the author lists
92
+ what each role must be able to do on each surface (Admin, application
93
+ pages, REST, MCP), with observable success criteria, explicit exclusions
94
+ and the authority each role must not have. The rubric stays with the author;
95
+ no evaluator ever sees it, its wording or its order.
96
+ 2. **Hand each evaluator only its role.** Supply the business role and context
97
+ in one or two sentences, the entry points that role really uses (Admin URL,
98
+ site URL, REST base URL and API reference, MCP endpoint), a signed-in browser
99
+ session or client token from setup, the interaction/time/write budget,
100
+ permitted fake-data actions and what is forbidden (source, SQL, internal
101
+ docs, the database, other roles' credentials, blind retries of uncertain
102
+ writes). Withhold the brief's requirement list, capability maps, tool names,
103
+ precomputed IDs and earlier reports. Seed realistic fixtures: other owners'
104
+ records, several states and more than one page of results. Run the evaluator
105
+ outside the project directory, without Mantle skills or project instruction
106
+ files, and record that isolation.
107
+ 3. **Let it derive the work, then do it.** Before any interaction the evaluator
108
+ writes down what this role would need to accomplish and how it would know
109
+ each one succeeded. It then refines that list as it discovers the interface
110
+ (at least once after a first look, again after its first attempts), executes
111
+ it, and reports PASS/FAIL/BLOCKED per goal with readbacks, rejected writes,
112
+ recovery attempts and interaction counts. Zero findings is a valid result.
113
+ 4. **One fresh agent per role, every surface.** For the default two roles, run
114
+ an `admin` evaluator and a `user` evaluator in the same round on shared live
115
+ data, in the brief's natural order. Between them they cover every surface
116
+ the app exposes to each role: Admin for `admin`, pages for visitors and
117
+ members, REST for client developers, MCP for both roles' AI clients. A
118
+ surface no evaluator reached stays UNVERIFIED.
119
+ 5. **Score against the rubric outside the evaluator.** The author compares the
120
+ evaluator's derived goals and results with the rubric. A rubric item the
121
+ evaluator never thought of is itself a finding: the interface did not make
122
+ that job discoverable. Classify every finding P0 (data loss, authority or
123
+ integrity defect, uncertain write), P1 (a rubric item FAIL, BLOCKED or not
124
+ discoverable), P2 (friction) or P3 (suggestion), with evidence and the
125
+ smallest repair. Separate a violated explicit requirement, a demonstrated
126
+ defect and a newly inferred product wish; a wish is not permission to grow
127
+ the product.
128
+ 6. **Repair outside the evaluator's context, then start over.** The author keeps
129
+ the findings ledger, makes the smallest in-scope repair, adds focused
130
+ regressions (authorization, OCC, atomicity), and freezes a new revision.
131
+ The next round uses new agents and new fixtures and passes them nothing
132
+ from earlier rounds.
133
+ 7. **Converge or stop.** A round converges when it has no P0 or P1 and the
134
+ evaluators' observed results cover every rubric item visible to their roles;
135
+ items outside any role's view (another caller's isolation, hidden state)
136
+ need author regressions, reported under that label. Run at most three
137
+ rounds. If the third still has a P0 or P1 or an uncovered rubric item, report
138
+ BLOCKED with the remaining ledger, not acceptance. Setup cleans fixtures in
139
+ `finally` after every round, including aborted ones.
155
140
 
156
141
  If a fresh agent or authorized test transport is unavailable, record independent
157
142
  acceptance as BLOCKED and report the scripted checks separately. Do not invent
158
143
  an independent result. Do not deploy or publish merely to obtain acceptance.
159
144
 
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.
145
+ ## Hand each role to its evaluator
146
+
147
+ Setup signs each role in; the evaluator receives the role's access, never a credential it could reuse or widen.
148
+ This is the authorized test transport the BLOCKED rule above refers to.
149
+
150
+ - **Local session URL (the app shell's default).** `pnpm accept` in an app written by `mantle init` starts the app
151
+ on a fresh local D1, signs the admin in, runs an optional fixtures module, signs the user in and prints one local
152
+ URL per role. Each URL forwards every request with that role's session and refuses `/api/auth/*`. Give the
153
+ evaluator only its URL: MCP at `<url>/mcp` (user) or `<url>/mcp/admin` (admin) for any streamable HTTP MCP client,
154
+ Admin at `<url>/admin` and member pages at `<url>/` in a browser, REST at `<url>/` with no credential. An evaluator
155
+ without an MCP client uses `node accept.mjs mcp <endpoint> [tool [json]]`, which prints the raw result including
156
+ `isError`. The app README's "Independent acceptance" section shows the fixtures module.
157
+ - **An external MCP host over OAuth.** To accept the connection a real client uses (Claude, ChatGPT or another host
158
+ that supports MCP OAuth), deploy or tunnel the app as you would for that host, add `<origin>/mcp` or
159
+ `<origin>/mcp/admin` to the host in setup, and complete sign-in and consent as the role's fictional identity. The
160
+ evaluator then works in that already-connected host. Its token stays in the host; the evaluator neither sees nor
161
+ requests it. Record this as OAuth evidence, separate from the local session URL (see
162
+ [OAuth](oauth.md)).
163
+
164
+ Either way, the evaluator's prompt names the URL or the connected host, never a cookie, token or sign-in step.
165
+
166
+ If you cannot start a fresh agent yourself (for example, you are a subagent without that tool), prepare everything
167
+ else: the rubric, the fixtures module, `pnpm accept` running, and one filled-in role prompt per role (below) naming
168
+ its URL. Hand the prompts to whoever can start agents (the person or the orchestrating agent), keep the app running,
169
+ and report acceptance as BLOCKED with the prompts attached until their reports come back. Score those reports as
170
+ usual.
171
+
172
+ ## Give the agent a role
173
+
174
+ Fill this in per role. It names the role and the doors, never the jobs:
175
+
176
+ > You are [role, for example "a purchasing manager"] at [business context in
177
+ > one sentence]. You use [entry points for this role from `pnpm accept`: Admin
178
+ > URL, site URL, REST base URL and reference, MCP endpoint]; setup has already
179
+ > signed you in as [fictional identity]. Budget: [interactions/time/writes]. You may create and
180
+ > change fictional records; [owner] cleans up. Do not read source, internal docs
181
+ > or a database, use anyone else's access, or retry a write whose outcome you
182
+ > do not know.
183
+ > First, write down what someone in this role needs to get done with this
184
+ > service and how you would tell each one worked. Refine that list after a
185
+ > first look at the interface and again after your first attempts. Then do the
186
+ > work and report each goal as PASS/FAIL/BLOCKED with what you read back,
187
+ > what was refused, what it cost you, and the smallest change that would have
188
+ > helped. Report what you covered and what you could not reach; finding nothing
189
+ > wrong is acceptable.
180
190
 
181
191
  The author separately verifies isolation between callers, stale-version
182
192
  refusal, unknown-outcome handling and effects hidden from the evaluator's role
@@ -188,19 +198,19 @@ fresh agent actually observed.
188
198
 
189
199
  Use ordinary Markdown; no new receipt schema or framework is required:
190
200
 
191
- - Original brief, explicit requirements and exclusions, role, environment,
192
- per-round budgets and round cap.
201
+ - Original brief, the private rubric, roles, environment, per-round budgets
202
+ and round cap.
193
203
  - Setup assistance and what each agent could access; identify distinct rounds.
194
- - Per round: revision, the evaluator's own goals and criteria, per-goal
204
+ - Per round: revision, each evaluator's derived goals (first list and
205
+ refinements) and criteria, the gaps between them and the rubric, per-goal
195
206
  PASS/FAIL/BLOCKED, actual readbacks and interaction references,
196
207
  request/page/tool counts
197
208
  and negative/recovery attempts, with their differing scope stated.
198
- - Findings ledger with materiality, owner, disposition, minimal repair and
209
+ - Findings ledger with P0–P3, owner, disposition, minimal repair and
199
210
  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.
211
+ - Coverage of each rubric item (evaluator-observed, author regression only, or
212
+ UNVERIFIED); the final CONVERGED / BLOCKED / UNVERIFIED verdict and rounds
213
+ used. Retain every failed report.
204
214
  - Fixture cleanup result and every remaining limitation.
205
215
 
206
216
  Keep credentials, raw auth logs, full resource HTML and private operational
@@ -0,0 +1,40 @@
1
+ ---
2
+ description: Scripted local checks before acceptance - handlers over real HTTP, public pages without JavaScript, MCP discovery, Admin's MCP App, and how to account for every surface.
3
+ ---
4
+ # Local HTTP checks
5
+
6
+ `pnpm check` proves that the manifests compile and the app typechecks; it runs no handler. Before you report an app
7
+ or a feature ready, run it on a fresh local D1 and drive it over HTTP. In an app written by `mantle init`, that is
8
+ `smoke.mjs` on the shell's `smoke-kit.mjs` (`pnpm smoke`): it applies `generated/schema.sql` and `seed.sql`, starts
9
+ `wrangler dev --local`, signs users in with the console email code and deletes everything afterwards.
10
+ These checks are the author's evidence. [Independent acceptance](agent-workflow-acceptance.md) comes after them and
11
+ does not replace them, nor they it.
12
+
13
+ ## What smoke covers
14
+
15
+ - **Every Procedure and HTTP Trigger the app declares:** valid input with the expected status and body (a status
16
+ below 500 is not success), invalid input, an anonymous call to a protected operation, the other role, and the
17
+ stored result read back. Use only the generated routes, inputs and permissions.
18
+ - **Business rules at their edges:** the oversell, the stale `expectedVersion`, the second approval, a refusal that
19
+ must leave nothing behind (check the rows with the kit's `sql`), and two concurrent calls where a race matters.
20
+ - **Each caller sees only its own rows:** a second member's records are absent from lists and refused by id.
21
+ - **Visitor pages:** the checks in [public pages](public-pages.md#check-before-you-report-done), requested without
22
+ JavaScript.
23
+ - **MCP:** `assertMcpDiscovery(endpoint, { tools, headers })` from `smoke-kit.mjs` for `/mcp` and `/mcp/admin`, with
24
+ the expected tools taken from generated code (the app-shell README shows it), plus a separate assertion that an
25
+ anonymous call and a member calling `/mcp/admin` are refused. Discovery needs no business `tools/call`.
26
+ - **Admin:** `pnpm build`, then `pnpm test` (`generated/admin-mcp-app.check.mjs`) loads the Admin MCP App in an MCP
27
+ host and fails on a network error, console error or exception. It needs `playwright-core` and Chrome
28
+ (`npx playwright-core install chromium`, or `MANTLE_TEST_CHROMIUM=<executable>`). With no browser it fails: report
29
+ it as BLOCKED, not skipped.
30
+
31
+ Better Auth allows 3 one-time-code requests per minute per client IP; the kit gives each user its own
32
+ `cf-connecting-ip`. Credentials come only from the kit's sign-in: a check never scrapes another session or builds a
33
+ Caller by hand.
34
+
35
+ ## Account for every surface
36
+
37
+ In the app's README or handoff, list each declared surface (Admin, each MCP endpoint, REST, each visitor page type)
38
+ as verified (command, identity and result), intentionally not exposed, or UNVERIFIED with a reason. Local cookie
39
+ sessions do not prove an external MCP host's OAuth connection or a production origin; record those separately.
40
+ Record the commands and their results for the exact commit, fix failures and rerun the affected checks.
@@ -85,6 +85,9 @@ All paths below start with `/admin/api` and require an admin session; mutations
85
85
  also require the shared same-origin check. Mount `entries(...)` for collection
86
86
  routes. Send JSON with `Content-Type: application/json`.
87
87
 
88
+ These routes serve Admin's console with its session cookie; they are not a client API. A REST client reads back what it
89
+ wrote through the app's own Views with HTTP Triggers (`mantle help trigger`).
90
+
88
91
  | Method and path | Request / response |
89
92
  | --- | --- |
90
93
  | `POST /entries` | Body `{ collection, data }`; creates an entry. |
@@ -92,10 +95,15 @@ routes. Send JSON with `Content-Type: application/json`.
92
95
  | `PATCH /entries/{id}?collection=<schema>` | Body `{ data, expectedVersion }`; sends the observed version, not the next one. Publishing entries must be drafts. |
93
96
  | `POST /entries/{id}/publish?collection=<schema>` | Body `{ expectedVersion }`; validates required data before publishing. |
94
97
  | `POST /entries/{id}/unpublish?collection=<schema>` | Body `{ expectedVersion }`; returns the entry to draft. |
95
- | `POST /operations/{name}` | Body is the Procedure's input; runs an Admin operation (a Procedure with `requires: ctx.admin` or a `target`). |
98
+ | `POST /operations/{name}` | Body is the Procedure's input; runs an Admin operation (a Procedure with `requires: ctx.admin` or a `target`). Returns `{ ok: true, output }`, where `output` is what REST and MCP return (`{ results }` for a SQL Procedure). |
96
99
  | `GET /views/{name}` | Generated admin View query parameters plus `limit`/`cursor`; returns `{ rows, nextCursor? }`. |
97
100
  | `GET /view-batch?names=a,b` | One to 20 comma-separated admin View names, deduplicated; reads the first row of each as the caller, with the single route's checks (`requires`, scope, TTL, `authorize`), and returns `{ views: { <name>: { rows } \| { unavailable: true } \| { error: true } } }`. A refused View and a missing one are the same `{ unavailable: true }`; a View that cannot answer with no input is `{ error: true }`. Neither fails the batch. Empty or more than 20 names is a 400, before the database is acquired. |
98
101
  | `GET /me` | Returns `{ userId, role, admin, login, image }` for the signed-in caller. |
102
+ | `GET /admin` | Administrators and pending invitations, plus `adminRoles` (mounted with `identity`). |
103
+ | `GET /members?search=&limit=&cursor=&direction=` | Pages members (1–100), searching id, name and email. |
104
+ | `PATCH /admin/{id}/role` | Body `{ role }`: an admin role name grants Admin, `null` removes it. You cannot change your own role. |
105
+ | `POST /admin/invitations` | Body `{ email, role }`; invites an administrator by email. Returns `{ ok, userId, emailSent }`. |
106
+ | `DELETE /admin/invitations/{id}` | Revokes an invitation nobody has signed in to yet (otherwise 409). |
99
107
 
100
108
  Entry create/read/patch/transitions return
101
109
  `{ collection, entry, parentEntryId, parentEntryTitle, related }`; the entry has
@@ -296,7 +304,9 @@ Generated full/partial input checks validate creates, edits, operations and
296
304
  filters. Plain titled string `oneOf` choices use the same enum checks; general
297
305
  or constrained alternatives remain refused. Draft creates can be incomplete;
298
306
  publish checks required data. Publishing edits apply only to drafts. Read-only
299
- collections refuse generic edits while their declared Procedures can still run.
307
+ collections refuse generic creates, edits, deletes and transitions with 403 `AUTH_DENIED`
308
+ ("This record is read-only; use its operations.") before any read or write, while
309
+ their declared Procedures can still run.
300
310
 
301
311
  Writes use compiler-printed native Procedure frames, bind recipes and only the
302
312
  supplied field fragments. They do not update omitted columns or run a schema
@@ -371,7 +381,12 @@ field and stored value. The server resolves them for the caller with one page qu
371
381
  target. A missing, deleted or unreadable target is absent, so the three look the same: Admin shows the exact ID with copy.
372
382
  A resolved title links to the related record. Admin Views and the MCP App have no such step, since their rows come from the
373
383
  View itself; a View that wants a title selects it (join the referenced table, within its own `requires`). `/bootstrap?collection=...` can include
374
- the first requested list page. CSV exports use `/entries/export` and
384
+ the first requested list page.
385
+ In the entry editor and every operation form (including a `collectionAction` create), a string field with
386
+ `x-mantle-ref` to another Admin collection is a searchable picker: it lists the target through the same `/entries` route
387
+ (20 records per search, read as the caller), shows each record's title and stores the referenced value (`id`, or the
388
+ ref's unique field). A target the caller cannot list (403/404) or that is not an Admin collection keeps the plain text
389
+ input. The picker is a convenience; the server still validates the value. CSV exports use `/entries/export` and
375
390
  `/views/{name}/export`; native browser downloads stream paged server output.
376
391
  A failure after headers aborts the stream, so a download can fail after partial
377
392
  bytes have arrived. Existing databases need an app-owned migration for the
@@ -516,7 +531,7 @@ placement on a Schema without a sidebar entry of its own: a translation, or a fo
516
531
  [`standalone`](#collection-navigation-and-operation-placement).
517
532
  The purchasing example (`node_modules/@aotter/mantle/docs/examples/purchasing/`) puts its approval queue and requests in an Approvals group.
518
533
 
519
- Operational collections have no generic create or draft: officers add records through a Procedure placed with `collectionAction` (the list header shows its button and input form), so every write is a declared, validated program. For a user-field scoped Schema, SQL INSERT is system-only, so that Procedure is a ref handler that inserts through `ctx.store` ([procedure.md](../reference/procedure.md)).
534
+ Operational collections have no generic create or draft: officers add records through a Procedure placed with `collectionAction` (the list header shows its button and input form), so every write is a declared, validated program. For a user-field scoped Schema the Procedure can stay SQL; see [field scope](../reference/schema.md#field-scope) for the partition input.
520
535
 
521
536
  Admin operations are Procedures exposed by an admin MCP Trigger. Declare
522
537
  `Procedure.spec.uiSchema.collectionAction: <schema-name>` to show the operation
@@ -527,9 +542,13 @@ Names such as `approve` never determine placement. An operation without a row
527
542
  target or collection action appears on the Operations page, whose sidebar link
528
543
  appears whenever such operations exist.
529
544
 
545
+ A record's page shows its row operations as labeled buttons in the header (more than three: two buttons and a
546
+ "More actions" menu); list rows keep them in an "Actions for …" menu.
530
547
  Generated row metadata includes simple AND-ed equality/IN literal conditions
531
- from the target-row UPDATE/DELETE WHERE. The menu hides known mismatches; missing
532
- preview values and unsupported predicates leave actions available. These are
548
+ from the target-row UPDATE/DELETE WHERE, and an operation whose conditions the
549
+ row does not meet is hidden: with `WHERE … AND orderStatus = 'approved'`, "Mark
550
+ fulfilled" appears only once the order is approved. Missing preview values and
551
+ unsupported predicates leave actions available. These are
533
552
  presentation hints: the Procedure still enforces its WHERE and authorization.
534
553
  Conflicts show a localized refresh message with the technical diagnostic in
535
554
  Details. Successful actions show a short summary and changes, with raw output
@@ -25,8 +25,8 @@ query. Authored output names should be emitted into SQL where valid, avoiding
25
25
  an extra per-row projection merely to repair names already known at generation.
26
26
 
27
27
  Selected REST/MCP/Admin boundaries use static generated column lineage. Declared
28
- integers (including aliased system versions) are `number | string | null` on every
29
- target: safe integers become numbers; unsafe integers remain exact decimal text.
28
+ integers (including aliased system versions) are `number | string` on every
29
+ target, `| null` unless the View proves the column NOT NULL ([nullability](../reference/view.md#results-and-paging)): safe integers become numbers; unsafe integers remain exact decimal text.
30
30
  Selected SQL projections cast integers and hidden integer cursor keys to text
31
31
  before the driver reads them, preserving stored int64 values on SQLite/D1 and
32
32
  PostgreSQL. Values already rounded when written
@@ -34,7 +34,8 @@ PostgreSQL. Values already rounded when written
34
34
  `views/<name>.ts` exports both driver-native `Row` and `WireRow` (declared wire fields agree across targets);
35
35
  `paged/views/<name>.ts` keeps native `Row` rows; REST/MCP use separate
36
36
  `paged/wire/views/<name>.ts` SQL and convert at their boundary. SQLite/D1 declared JSON
37
- and boolean columns become JSON values and booleans, preserving aliases and nulls;
37
+ and boolean columns, and computed outputs that are JSON documents by construction
38
+ (JSON aggregates and builders), become JSON values and booleans, preserving aliases and nulls;
38
39
  PostgreSQL already returns those native values.
39
40
  Declared real/float outputs remain numbers. Computed outputs without proved lineage,
40
41
  SQL Procedures and app-owned ref results retain native values and types.
@@ -77,7 +78,7 @@ microseconds and days while PostgreSQL receives the canonical text. Computed val
77
78
  PostgreSQL through Bun.SQL that is a `Date`, which drops microseconds. Other
78
79
  declared scalars are driver-native.
79
80
 
80
- Driver-ready JSON parameters are text on every target (a JavaScript array would otherwise be bound as a PostgreSQL array), so the application serializes them. Bare PG SQL declares each JSON parameter as text
81
+ JSON parameters are text on every target (a JavaScript array would otherwise be bound as a PostgreSQL array): an input field read as JSON takes its authored array or object, which the generated query serializes, and a string is bound as it is. Bare PG SQL declares each JSON parameter as text
81
82
  (`CAST(CAST($n AS text) AS jsonb)`), so Bun.SQL sends the text verbatim instead of
82
83
  JSON-encoding it again; generation refuses a JSON parameter used any other way. SQLite bool/date/timestamp/numeric parameters use their declared
83
84
  storage representations. Bound optional fields become explicit required values,