@aotter/mantle 0.2.0 → 0.2.1-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/README.md +6 -2
  2. package/dist/build/compiler.d.ts +6 -6
  3. package/dist/build/compiler.js +6 -3
  4. package/dist/build/spec/domain/model/ManifestGrammar.d.ts +39 -1
  5. package/dist/build/spec/domain/model/RuntimePlan.d.ts +16 -3
  6. package/dist/build/spec/domain/model/SqlIr.d.ts +30 -0
  7. package/dist/build/spec/kernel/diagnostic.d.ts +1 -1
  8. package/dist/domain/describe/chunk-CW7X5Q5T.js +1 -0
  9. package/dist/domain/describe/index.d.ts +1 -0
  10. package/dist/domain/describe/index.js +1 -1
  11. package/dist/domain/describe/match.d.ts +19 -0
  12. package/dist/domain/describe/public.d.ts +3 -0
  13. package/dist/domain/describe/public.js +1 -0
  14. package/dist/infrastructure/adminEntries.js +19 -3
  15. package/dist/infrastructure/bundle.js +43 -8
  16. package/dist/infrastructure/cli.js +1 -1
  17. package/dist/infrastructure/generate.js +17 -3
  18. package/dist/kernel/binds.d.ts +18 -3
  19. package/dist/kernel/binds.js +1 -1
  20. package/dist/kernel/contracts.d.ts +7 -0
  21. package/dist/kernel/contracts.js +9 -0
  22. package/dist/kernel/driver/bun-sqlite.js +3 -3
  23. package/dist/kernel/driver/d1.d.ts +1 -1
  24. package/dist/kernel/driver/d1.js +6 -3
  25. package/dist/kernel/driver/postgres.d.ts +2 -1
  26. package/dist/kernel/driver/postgres.js +11 -6
  27. package/dist/kernel/mcp-apps.d.ts +3 -2
  28. package/dist/kernel/mcp-apps.js +7 -2
  29. package/dist/kernel/mcp-handler.js +22 -9
  30. package/dist/kernel/mcp.d.ts +18 -0
  31. package/dist/kernel/mcp.js +1 -0
  32. package/dist/kernel/rest-router.js +3 -5
  33. package/dist/kernel/rest.d.ts +2 -1
  34. package/dist/kernel/rest.js +1 -0
  35. package/dist/kernel/route-pattern.d.ts +26 -0
  36. package/dist/kernel/route-pattern.js +34 -0
  37. package/dist/kernel/store.d.ts +15 -1
  38. package/dist/kernel/store.js +51 -18
  39. package/dist/usecase/adminBindings.js +2 -2
  40. package/dist/usecase/adminClient.js +4 -1
  41. package/dist/usecase/generate.js +7 -5
  42. package/dist/usecase/handlers.js +1 -1
  43. package/dist/usecase/publishing.js +20 -7
  44. package/dist/usecase/rest.js +5 -5
  45. package/dist/usecase/stores.js +8 -3
  46. package/docs/examples/README.md +1 -1
  47. package/docs/examples/app-shell/README.md +58 -10
  48. package/docs/examples/app-shell/package.json +6 -6
  49. package/docs/examples/club-events/manifests/club-events.yaml +2 -0
  50. package/docs/examples/generated-web/seed.sql +5 -4
  51. package/docs/examples/purchasing/manifests/purchasing.yaml +3 -0
  52. package/docs/handbook/cloudflare/media-r2.md +6 -3
  53. package/docs/handbook/cloudflare/service-entry.md +2 -0
  54. package/docs/handbook/concepts/lifecycle-and-locales.md +21 -1
  55. package/docs/handbook/examples/hub.md +1 -1
  56. package/docs/handbook/guides/admin-ui.md +1 -1
  57. package/docs/handbook/guides/agent-setup.md +13 -8
  58. package/docs/handbook/guides/agent-workflow-acceptance.md +129 -19
  59. package/docs/handbook/guides/local-testing.md +50 -5
  60. package/docs/handbook/guides/mcp-apps.md +40 -0
  61. package/docs/handbook/guides/native-admin.md +218 -11
  62. package/docs/handbook/guides/native-execution.md +5 -4
  63. package/docs/handbook/guides/revisions.md +584 -0
  64. package/docs/handbook/guides/size-and-startup.md +140 -0
  65. package/docs/handbook/guides/sql-patterns.md +47 -1
  66. package/docs/handbook/guides/typed-queries.md +8 -3
  67. package/docs/handbook/navigation.json +4 -0
  68. package/docs/handbook/reference/authorization.md +2 -0
  69. package/docs/handbook/reference/describe.md +56 -4
  70. package/docs/handbook/reference/diagnostics.md +12 -3
  71. package/docs/handbook/reference/procedure.md +4 -4
  72. package/docs/handbook/reference/schema.md +71 -7
  73. package/docs/handbook/reference/sql.md +2 -1
  74. package/docs/handbook/reference/surface.md +13 -2
  75. package/docs/handbook/reference/trigger.md +12 -0
  76. package/docs/handbook/releases/index.md +75 -1
  77. package/docs/handbook/start/agents.md +6 -2
  78. package/docs/handbook/start/project-and-cli.md +4 -0
  79. package/docs/handbook/start/quickstart-worker.md +1 -1
  80. package/docs/skills/develop/SKILL.md +15 -3
  81. package/docs/skills/integrate/SKILL.md +3 -1
  82. package/docs/skills/performance/SKILL.md +169 -0
  83. package/docs/skills/update/SKILL.md +9 -5
  84. package/docs/upgrade-0.1-to-0.2.md +151 -39
  85. package/package.json +10 -11
  86. package/skills/README.md +6 -2
  87. package/skills/mantle/SKILL.md +19 -5
  88. package/dist/domain/describe/chunk-SGMRVDGE.js +0 -1
  89. package/dist/domain/describe/target-hash.js +0 -1
@@ -8,10 +8,13 @@ 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
9
  the implementation. Every acceptance round gives the evaluator only a
10
10
  role and the entry points that role uses; it derives realistic tasks itself
11
- and the author scores them against a private rubric.
11
+ and the author scores them against a private rubric. The first delivery gets
12
+ full acceptance; after a repair, the change decides what
13
+ [re-runs](#re-verify-what-a-repair-touches).
12
14
  If independent acceptance is not run, report `UNVERIFIED`; if a fresh evaluator
13
15
  or authorized test transport is unavailable, report `BLOCKED`; neither outcome
14
- is done.
16
+ is done. Either way, report the evidence you do have in the
17
+ [layered handoff](#report-done-ness-in-layers).
15
18
 
16
19
  Use the surface the intended user actually uses: a real browser session for
17
20
  Admin and application pages; for REST, an HTTP client plus the public API
@@ -110,7 +113,9 @@ the evidence.
110
113
  (at least once after a first look, again after its first attempts), executes
111
114
  it, and reports PASS/FAIL/BLOCKED per goal with readbacks, rejected writes,
112
115
  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
116
+ 4. **One fresh agent per role, every surface.** In the first round, and in any
117
+ round the [re-verification rule](#re-verify-what-a-repair-touches) widens
118
+ to all of them, cover every role and surface. For the default two roles, run
114
119
  an `admin` evaluator and a `user` evaluator in the same round on shared live
115
120
  data, in the brief's natural order. Between them they cover every surface
116
121
  the app exposes to each role: Admin for `admin`, pages for visitors and
@@ -124,24 +129,80 @@ the evidence.
124
129
  discoverable), P2 (friction) or P3 (suggestion), with evidence and the
125
130
  smallest repair. Separate a violated explicit requirement, a demonstrated
126
131
  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
132
+ the product. A goal the evaluator's own environment stopped (its tool
133
+ approval, sandbox or network, not an app refusal) is not a finding: record
134
+ it as an [environment block](#report-done-ness-in-layers) and rerun that
135
+ role once it is lifted.
136
+ 6. **Repair outside the evaluator's context, then re-verify what it touches.**
137
+ The author keeps the findings ledger, makes the smallest in-scope repair,
138
+ adds focused regressions (authorization, OCC, atomicity), and freezes a new
139
+ revision. The next round re-runs the roles and surfaces the
140
+ [re-verification rule](#re-verify-what-a-repair-touches) names, with new
141
+ agents and new fixtures, and passes them nothing from earlier rounds.
142
+ 7. **Converge or stop.** A round converges when it has no P0 or P1 and
143
+ evaluator-observed results cover every rubric item visible to their roles:
144
+ from this round, or reused from an earlier round under the
145
+ re-verification rule. Items outside any role's view (another caller's
146
+ isolation, hidden state) need author regressions, reported under that
147
+ label. Run at most three
137
148
  rounds. If the third still has a P0 or P1 or an uncovered rubric item, report
138
149
  BLOCKED with the remaining ledger, not acceptance. Setup cleans fixtures in
139
150
  `finally` after every round, including aborted ones.
140
151
 
141
152
  If a fresh agent or authorized test transport is unavailable, record independent
142
- acceptance as BLOCKED and report the scripted checks separately. Do not invent
153
+ acceptance as BLOCKED and report the scripted checks on their own
154
+ [layers](#report-done-ness-in-layers). Do not invent
143
155
  an independent result. Do not deploy or publish merely to obtain acceptance.
144
156
 
157
+ Acceptance proves the behavior, not its speed. Once a round converges, the
158
+ [performance skill](../../skills/performance/SKILL.md) decides which performance
159
+ checks apply and holds them to their targets; a performance repair that changes
160
+ what a role sees or can do returns here, scoped by the
161
+ [re-verification rule](#re-verify-what-a-repair-touches).
162
+
163
+ ## Re-verify what a repair touches
164
+
165
+ The first delivery always gets full acceptance: every role, every surface.
166
+ After a repair, the change itself decides what runs again, not the finding
167
+ that prompted it. Rounds before convergence are not accepted, so diff against
168
+ evidence, not rounds: for each item you want to reuse, diff the current
169
+ revision against the revision it was observed at, and pick the widest row that
170
+ diff matches. A chain of repairs adds up; a page-only repair followed by a
171
+ handler repair has to re-run what the handler repair reaches.
172
+
173
+ | The repair changes | Re-run with fresh evaluators | May reuse |
174
+ | --- | --- | --- |
175
+ | Only presentation: page or Admin layout, CSS, copy, client-side display | Blind acceptance of the affected UI, for each role that uses it. A shared component or layout affects every page that renders it. | REST and MCP evidence from the same business-rule version |
176
+ | A public contract: a tool or route description, input, output or error shape; the generated client or other `generated/` output it changes | That surface for each role that uses it, plus every UI that consumes it | Other surfaces at the same business-rule version |
177
+ | Authentication, sessions, roles, `requires`, row or tenant scope | Every surface that the changed rule guards, for every affected role | Nothing those surfaces depend on |
178
+ | A SQL Procedure or View, a `uniqueIndexes` entry, a check or lock, a migration that changes results, a handler's business rule | Every surface that reads or writes that data | Nothing those surfaces depend on |
179
+ | A shared transport: the service entry, routing, the HTTP or MCP mount, a shared error envelope | Every surface on that transport | Nothing on that transport |
180
+
181
+ The business-rule version is the revision of the manifests, SQL, migrations,
182
+ handlers, authorization and service entry. A repair that changes none of
183
+ them keeps that version, so evidence collected at it still stands for
184
+ surfaces the repair did not reach. A repair that changes any of them
185
+ starts a new business-rule version, and looking at the UI alone is not
186
+ enough. One exception: a repair that only adds or drops a non-unique Schema
187
+ `indexes` entry and its migration keeps the version. It changes speed, not
188
+ which rows a read returns or which writes are refused. A `uniqueIndexes`
189
+ entry, a check or a lock does change which writes succeed, so it falls in the
190
+ SQL row. When the diff is unclear, widen.
191
+
192
+ Every row still runs the author's regressions on the new revision (layers 1
193
+ and 2 of the [layered report](#report-done-ness-in-layers)): `pnpm check`,
194
+ `pnpm smoke`, `pnpm test` and the focused regressions for the repair.
195
+
196
+ A re-run evaluator is as fresh as the first: a new agent, new fixtures, only
197
+ its role and the entry points under re-test. Do not tell it what was
198
+ repaired, what to check or what an earlier evaluator did. No previous
199
+ answers, rubric or precomputed steps.
200
+
201
+ For every rubric item that was not re-run, record the round and revision its
202
+ evidence comes from and why the changes since that revision could not affect
203
+ it. Never report a
204
+ reused item as observed in a later round.
205
+
145
206
  ## Hand each role to its evaluator
146
207
 
147
208
  Setup signs each role in; the evaluator receives the role's access, never a credential it could reuse or widen.
@@ -185,7 +246,8 @@ Fill this in per role. It names the role and the doors, never the jobs:
185
246
  > first look at the interface and again after your first attempts. Then do the
186
247
  > work and report each goal as PASS/FAIL/BLOCKED with what you read back,
187
248
  > 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
249
+ > helped. For BLOCKED, say whether the service refused you or your own tools
250
+ > or permissions stopped you. Report what you covered and what you could not reach; finding nothing
189
251
  > wrong is acceptable.
190
252
 
191
253
  The author separately verifies isolation between callers, stale-version
@@ -194,6 +256,50 @@ through authorized test fixtures. Do not grant the evaluator extra authority
194
256
  just to observe hidden state. Keep author regressions distinct from what the
195
257
  fresh agent actually observed.
196
258
 
259
+ ## Report done-ness in layers
260
+
261
+ Report a handoff in four layers, in this order, each for the exact commit. Each
262
+ layer proves something the others do not; a passing layer neither replaces nor
263
+ is invalidated by another layer's result.
264
+
265
+ | Layer | What it proves | Evidence |
266
+ | --- | --- | --- |
267
+ | 1. Generation and types | Manifests compile, `generated/` is current, the app typechecks and builds. No handler runs. | `pnpm check` (includes `generate --check`), `pnpm build` |
268
+ | 2. Business rules and permissions | Handlers on a fresh local D1 over HTTP: each operation at its edges, refusals, caller isolation, visitor pages, MCP discovery, the Admin MCP App loading. Author's evidence. | `pnpm smoke`, `pnpm test`; [Local HTTP checks](local-testing.md) |
269
+ | 3. Independent acceptance, per role | A fresh agent given only its role and entry points finds and finishes that role's work on each surface it uses. | Per role: the [rounds](#run-role-only-acceptance-rounds-until-they-converge) verdict and surfaces covered, naming the round and revision of any [reused](#re-verify-what-a-repair-touches) evidence |
270
+ | 4. External platform | A real MCP host connects over OAuth; the production origin serves the deployed app. Local cookie sessions prove neither. | Host and version, identity, origin, observed result |
271
+
272
+ Give each layer, and each role on layer 3, one status:
273
+
274
+ - **verified**: the evidence above, with its command or host and result.
275
+ - **failed**: the product is wrong. Name the failing check or finding.
276
+ - **BLOCKED**: the layer could not run. Give the reason, what refused or is
277
+ missing, and what would unblock it.
278
+ - **not applicable**: with the reason. Layer 4 is not applicable unless the
279
+ requested work includes an external host connection or a deployment.
280
+
281
+ A layer nobody attempted is UNVERIFIED, as above. A block from the execution
282
+ environment (an agent's approval policy refusing fictional writes, no browser,
283
+ no network, a sandbox) is BLOCKED on that layer only. It is never a failed
284
+ layer or a product finding, and it does not invalidate the evidence on other
285
+ layers. Do not work around it by widening the evaluator's authority, faking a
286
+ result or deploying; ask whoever controls the environment and rerun only that
287
+ layer on the same revision.
288
+
289
+ The work is done only when layers 1 to 3 are verified for every role and
290
+ surface, and layer 4 is verified or not applicable. Partial evidence is still
291
+ reported, layer by layer; it is not done:
292
+
293
+ ```markdown
294
+ | Layer | Status | Evidence |
295
+ | --- | --- | --- |
296
+ | 1. Generation and types | verified | `pnpm check`, `pnpm build` at <commit> |
297
+ | 2. Business rules and permissions | verified | `pnpm smoke` (<n> checks), `pnpm test` |
298
+ | 3. Acceptance: admin | BLOCKED: evaluator's approval policy refused to create fictional records; needs <permission> | round 1 stopped at goal <n> |
299
+ | 3. Acceptance: user | verified: CONVERGED in round <n> | member pages in round <n>; MCP reused from round <m> at <revision> (page-only repair) |
300
+ | 4. External platform | not applicable: no deployment or external host requested | |
301
+ ```
302
+
197
303
  ## Record evidence in the existing issue or handoff
198
304
 
199
305
  Use ordinary Markdown; no new receipt schema or framework is required:
@@ -201,16 +307,20 @@ Use ordinary Markdown; no new receipt schema or framework is required:
201
307
  - Original brief, the private rubric, roles, environment, per-round budgets
202
308
  and round cap.
203
309
  - Setup assistance and what each agent could access; identify distinct rounds.
204
- - Per round: revision, each evaluator's derived goals (first list and
310
+ - Per round: revision, business-rule version, the roles and surfaces re-run
311
+ and why (the [re-verification rule](#re-verify-what-a-repair-touches) row),
312
+ each evaluator's derived goals (first list and
205
313
  refinements) and criteria, the gaps between them and the rubric, per-goal
206
314
  PASS/FAIL/BLOCKED, actual readbacks and interaction references,
207
315
  request/page/tool counts
208
316
  and negative/recovery attempts, with their differing scope stated.
209
317
  - Findings ledger with P0–P3, owner, disposition, minimal repair and
210
318
  regression commands/results.
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.
319
+ - Coverage of each rubric item (evaluator-observed in this round,
320
+ evaluator-observed and reused from round `<n>` at `<revision>` with the reason,
321
+ author regression only, or UNVERIFIED); the final CONVERGED / BLOCKED / UNVERIFIED verdict and rounds
322
+ used, which is layer 3 of the [layered report](#report-done-ness-in-layers).
323
+ Retain every failed report.
214
324
  - Fixture cleanup result and every remaining limitation.
215
325
 
216
326
  Keep credentials, raw auth logs, full resource HTML and private operational
@@ -7,8 +7,9 @@ description: Scripted local checks before acceptance - handlers over real HTTP,
7
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
8
  `smoke.mjs` on the shell's `smoke-kit.mjs` (`pnpm smoke`): it applies `generated/schema.sql` and `seed.sql`, starts
9
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.
10
+ These checks are the author's evidence: layers 1 and 2 of the
11
+ [layered handoff report](agent-workflow-acceptance.md#report-done-ness-in-layers). [Independent
12
+ acceptance](agent-workflow-acceptance.md) comes after them and does not replace them, nor they it.
12
13
 
13
14
  ## What smoke covers
14
15
 
@@ -26,15 +27,59 @@ does not replace them, nor they it.
26
27
  - **Admin:** `pnpm build`, then `pnpm test` (`generated/admin-mcp-app.check.mjs`) loads the Admin MCP App in an MCP
27
28
  host and fails on a network error, console error or exception. It needs `playwright-core` and Chrome
28
29
  (`npx playwright-core install chromium`, or `MANTLE_TEST_CHROMIUM=<executable>`). With no browser it fails: report
29
- it as BLOCKED, not skipped.
30
+ that check as BLOCKED on layer 2, not skipped and not as a product failure.
30
31
 
31
32
  Better Auth allows 3 one-time-code requests per minute per client IP; the kit gives each user its own
32
33
  `cf-connecting-ip`. Credentials come only from the kit's sign-in: a check never scrapes another session or builds a
33
34
  Caller by hand.
34
35
 
36
+ ## Seed data
37
+
38
+ Create test records through the app itself: in `smoke.mjs` or the `pnpm accept` fixtures module, call the app's own
39
+ REST, MCP or Admin operations as a signed-in user (`call("POST", "/admin/api/operations/create-event", { cookie: admin,
40
+ body })`), or insert through the Store (`store(db, caller).collection("events").insert(...)`). Mantle then fills every
41
+ system column (`id`, `version`, `created_at`, `updated_at`, `author_id`, and on a publishing Schema `status` and
42
+ `published_at` through its publish Procedure) and enforces the Schema's checks, scope and references, exactly as for
43
+ real data.
44
+
45
+ Keep `seed.sql` for static reference data that no surface writes, such as categories or department budgets.
46
+ `pnpm db:seed` and `smoke-kit.mjs` apply it as native SQL, which bypasses all of the above, so the seed sets each
47
+ system column itself:
48
+
49
+ | Column | SQLite and D1 (`cloudflare-d1`, `bun-sqlite`) | PostgreSQL | If the seed omits it |
50
+ |---|---|---|---|
51
+ | `id` | text, unique | text, unique | the insert fails |
52
+ | `version` | `1` | `1` | `1` |
53
+ | `created_at` | integer microseconds since the Unix epoch | `timestamptz` | the insert fails |
54
+ | `updated_at` | integer microseconds; the same as `created_at` | `timestamptz` | SQLite and D1 store `0`: Admin shows 1970-01-01. PostgreSQL stores `now()` |
55
+ | `author_id` | the creator's user id, or `NULL` for system rows | the same | `NULL`: under `restrict: { <schema>: "author_id = auth.uid()" }` no member sees the row |
56
+ | `status` (publishing) | `'draft'`, `'published'` or `'archived'` | the same | `'draft'` |
57
+ | `published_at` (publishing) | microseconds when `status` is `'published'`, otherwise `NULL` | `timestamptz`, otherwise `NULL` | `NULL` |
58
+
59
+ A field- or owner-scoped Schema also needs its scope column. Use fixed instants for repeatable fixtures
60
+ (`1767225600000000` is 2026-01-01T00:00:00Z), or the moment the seed runs (`unixepoch() * 1000000` on SQLite and
61
+ D1, `now()` on PostgreSQL). For a publishing `posts` Schema on D1:
62
+
63
+ ```sql
64
+ INSERT INTO posts (id, version, created_at, updated_at, author_id, status, published_at, title, body) VALUES
65
+ ('welcome', 1, 1767225600000000, 1767225600000000, NULL, 'published', 1767225600000000, 'Welcome', '# Welcome'),
66
+ ('draft-note', 1, unixepoch() * 1000000, unixepoch() * 1000000, NULL, 'draft', NULL, 'Draft', '# Draft');
67
+ ```
68
+
69
+ The same rows on PostgreSQL:
70
+
71
+ ```sql
72
+ INSERT INTO posts (id, version, created_at, updated_at, author_id, status, published_at, title, body) VALUES
73
+ ('welcome', 1, '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z', NULL, 'published', '2026-01-01T00:00:00Z', 'Welcome', '# Welcome'),
74
+ ('draft-note', 1, now(), now(), NULL, 'draft', NULL, 'Draft', '# Draft');
75
+ ```
76
+
77
+ A seed is local test data; never apply it to an existing production database.
78
+
35
79
  ## Account for every surface
36
80
 
37
81
  In the app's README or handoff, list each declared surface (Admin, each MCP endpoint, REST, each visitor page type)
38
82
  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.
83
+ sessions do not prove an external MCP host's OAuth connection or a production origin; those are layer 4. Record the
84
+ commands and their results for the exact commit in the layered report, fix failures and rerun the affected checks. A
85
+ check the environment prevented (no browser, no network, a refused approval) is BLOCKED with its reason, not failed.
@@ -33,6 +33,46 @@ passed through as the resource's `_meta.ui`. `domain` is the dedicated sandbox
33
33
  origin for hosts that serve each App from its own origin; use the form that
34
34
  host documents.
35
35
 
36
+ ### Tool metadata and result `_meta`
37
+
38
+ A hand-built kernel `Tool` may set `meta`, merged into its `_meta` in
39
+ `tools/list` with what App links produce: the top level shallowly, `ui` one key
40
+ deep. Use it for keys such as `ui.visibility`, `ui.resourceUri` with
41
+ `openai/outputTemplate`, or `openai/fileParams`. A value that contradicts an App
42
+ link (another `ui.resourceUri` than the `renders` resource, or a visibility other
43
+ than `["app"]` on an `appOnly` tool) fails when `mcp()` is constructed.
44
+ `meta: { ui: { visibility: ["app"] } }` hides any tool, a Procedure included,
45
+ from clients that declare no MCP Apps support, as `appOnly` does for Views. It
46
+ is a listing hint only: `tools/call` still runs every identity, scope, `check`
47
+ and `authorize` decision, so an App-only write is authorized like any other.
48
+
49
+ A `run` may return `withResultMeta(data, meta)` from `@aotter/mantle/mcp`. The
50
+ result's text and `structuredContent` are built from `data` exactly as before;
51
+ `meta` goes only into the result `_meta`, beside `net.aotter.mantle/tool`, which
52
+ it cannot replace. Hosts give result `_meta` to the App, not the model, so a
53
+ signed upload URL belongs there:
54
+
55
+ ```ts
56
+ import { withResultMeta, type Tool } from '@aotter/mantle/mcp';
57
+
58
+ declare function signUpload(caller: unknown): Promise<{ id: string; url: string; headers: Record<string, string> }>;
59
+
60
+ const uploadTarget: Tool<unknown> = {
61
+ name: 'upload_target', surface: 'public', description: 'Prepare an upload.', closed: true,
62
+ meta: { ui: { visibility: ['app'] } },
63
+ inputSchema: { type: 'object', additionalProperties: false }, validate: raw => raw as Record<string, unknown>,
64
+ run: async (_db, caller) => {
65
+ const upload = await signUpload(caller);
66
+ return withResultMeta({ id: upload.id }, { uploadUrl: upload.url, requiredHeaders: upload.headers });
67
+ },
68
+ };
69
+ ```
70
+
71
+ `callTool` and `callToolLazy` return the wrapper itself, `{ data, _meta }`.
72
+ A caller that forwards a result to a model, such as an application's own agent,
73
+ must forward only `data` and drop `_meta`, or the signed URL leaks through that
74
+ path. A thrown error still produces the standard diagnostics result.
75
+
36
76
  ## Package and application ownership
37
77
 
38
78
  Core (`@aotter/mantle`) owns tools, resources, endpoint links and server authorization,