@aotter/mantle 0.2.0 → 0.2.1-alpha.1
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 +6 -2
- package/dist/build/compiler.d.ts +4 -4
- package/dist/build/compiler.js +6 -3
- package/dist/build/spec/domain/model/ManifestGrammar.d.ts +39 -1
- package/dist/build/spec/domain/model/RuntimePlan.d.ts +16 -3
- package/dist/build/spec/domain/model/SqlIr.d.ts +30 -0
- package/dist/build/spec/kernel/diagnostic.d.ts +1 -1
- package/dist/domain/describe/chunk-P6PP6VVV.js +1 -0
- package/dist/domain/describe/index.d.ts +1 -0
- package/dist/domain/describe/index.js +1 -1
- package/dist/domain/describe/match.d.ts +19 -0
- package/dist/domain/describe/target-hash.js +1 -1
- package/dist/infrastructure/adminEntries.js +19 -3
- package/dist/infrastructure/bundle.js +43 -8
- package/dist/infrastructure/cli.js +1 -1
- package/dist/infrastructure/generate.js +17 -3
- package/dist/kernel/binds.d.ts +18 -3
- package/dist/kernel/binds.js +1 -1
- package/dist/kernel/contracts.d.ts +7 -0
- package/dist/kernel/contracts.js +9 -0
- package/dist/kernel/driver/bun-sqlite.js +3 -3
- package/dist/kernel/driver/d1.d.ts +1 -1
- package/dist/kernel/driver/d1.js +6 -3
- package/dist/kernel/driver/postgres.d.ts +2 -1
- package/dist/kernel/driver/postgres.js +11 -6
- package/dist/kernel/mcp-handler.js +2 -1
- package/dist/kernel/rest-router.js +3 -5
- package/dist/kernel/rest.d.ts +2 -1
- package/dist/kernel/rest.js +1 -0
- package/dist/kernel/route-pattern.d.ts +26 -0
- package/dist/kernel/route-pattern.js +34 -0
- package/dist/kernel/store.d.ts +15 -1
- package/dist/kernel/store.js +51 -18
- package/dist/usecase/adminBindings.js +2 -2
- package/dist/usecase/adminClient.js +4 -1
- package/dist/usecase/generate.js +7 -5
- package/dist/usecase/handlers.js +1 -1
- package/dist/usecase/publishing.js +20 -7
- package/dist/usecase/rest.js +5 -5
- package/dist/usecase/stores.js +8 -3
- package/docs/examples/README.md +1 -1
- package/docs/examples/app-shell/README.md +58 -10
- package/docs/examples/app-shell/package.json +6 -6
- package/docs/examples/club-events/manifests/club-events.yaml +2 -0
- package/docs/examples/generated-web/seed.sql +5 -4
- package/docs/examples/purchasing/manifests/purchasing.yaml +3 -0
- package/docs/handbook/cloudflare/media-r2.md +6 -3
- package/docs/handbook/cloudflare/service-entry.md +2 -0
- package/docs/handbook/concepts/lifecycle-and-locales.md +21 -1
- package/docs/handbook/examples/hub.md +1 -1
- package/docs/handbook/guides/admin-ui.md +1 -1
- package/docs/handbook/guides/agent-setup.md +13 -8
- package/docs/handbook/guides/agent-workflow-acceptance.md +129 -19
- package/docs/handbook/guides/local-testing.md +50 -5
- package/docs/handbook/guides/native-admin.md +218 -11
- package/docs/handbook/guides/native-execution.md +5 -4
- package/docs/handbook/guides/revisions.md +584 -0
- package/docs/handbook/guides/size-and-startup.md +140 -0
- package/docs/handbook/guides/sql-patterns.md +47 -1
- package/docs/handbook/guides/typed-queries.md +8 -3
- package/docs/handbook/navigation.json +4 -0
- package/docs/handbook/reference/authorization.md +2 -0
- package/docs/handbook/reference/describe.md +42 -0
- package/docs/handbook/reference/diagnostics.md +12 -3
- package/docs/handbook/reference/procedure.md +4 -4
- package/docs/handbook/reference/schema.md +71 -7
- package/docs/handbook/reference/sql.md +2 -1
- package/docs/handbook/reference/surface.md +3 -2
- package/docs/handbook/reference/trigger.md +12 -0
- package/docs/handbook/releases/index.md +44 -1
- package/docs/handbook/start/agents.md +6 -2
- package/docs/handbook/start/project-and-cli.md +4 -0
- package/docs/handbook/start/quickstart-worker.md +1 -1
- package/docs/skills/develop/SKILL.md +15 -3
- package/docs/skills/integrate/SKILL.md +3 -1
- package/docs/skills/performance/SKILL.md +169 -0
- package/docs/skills/update/SKILL.md +9 -5
- package/docs/upgrade-0.1-to-0.2.md +151 -39
- package/package.json +9 -9
- package/skills/README.md +6 -2
- package/skills/mantle/SKILL.md +19 -5
- package/dist/domain/describe/chunk-SGMRVDGE.js +0 -1
package/dist/usecase/stores.js
CHANGED
|
@@ -3,13 +3,14 @@ import { declaredType, inputType, nativeType } from "../domain/columns.js";
|
|
|
3
3
|
import { q } from "../domain/entry.js";
|
|
4
4
|
import { expand, SCOPE, TTL } from "../kernel/store.js";
|
|
5
5
|
import { refChecks, userScopeTerm } from '../build/compiler.js';
|
|
6
|
+
import { publishRequired } from "./publishing.js";
|
|
6
7
|
const SCALAR = new Set(["text", "real", "float8", "integer", "int4", "int8", "bool", "timestamptz", "date"]);
|
|
7
8
|
const core = (s, schemas) => {
|
|
8
9
|
const update = writePolicy(s, "update", { lock: true });
|
|
9
10
|
return { caller: rowPolicy(s, "caller", schemas), trusted: rowPolicy(s, "trusted", schemas), fills: writePolicy(s, "insert").fills, sets: update.sets, lock: update.lock };
|
|
10
11
|
};
|
|
11
12
|
const probe = { scope: SCOPE, ttl: TTL, ttlSeconds: 0, publishing: true };
|
|
12
|
-
export function emitStores(schemas, isSqlite, newId, refuse, required = false) {
|
|
13
|
+
export function emitStores(schemas, isSqlite, newId, refuse, required = false, locks = {}) {
|
|
13
14
|
const all = Object.values(schemas).sort((a, b) => a.name < b.name ? -1 : 1);
|
|
14
15
|
if (!all.length && !required)
|
|
15
16
|
return undefined;
|
|
@@ -29,13 +30,17 @@ export function emitStores(schemas, isSqlite, newId, refuse, required = false) {
|
|
|
29
30
|
const ref = refChecks(s, schemas);
|
|
30
31
|
const fact = {
|
|
31
32
|
...(s.userScopes?.length ? { u: s.userScopes.map(u => userScopeTerm(u, true)) } : {}),
|
|
32
|
-
...(s.scope ? { s: s.scope } : {}), ...(s.ttl ? { ttl: [s.ttl, s.ttlSeconds ?? 0] } : {}), ...(s.publishing ? { pub: 1 } : {}),
|
|
33
|
+
...(s.scope ? { s: s.scope } : {}), ...(s.ttl ? { ttl: [s.ttl, s.ttlSeconds ?? 0] } : {}), ...(s.publishing ? { pub: 1, ...(publishRequired(s).length ? { req: Object.fromEntries(publishRequired(s)) } : {}) } : {}),
|
|
33
34
|
...(ts.length ? { ts } : {}), ...(dates.length ? { d: dates } : {}),
|
|
34
35
|
...(s.search?.length ? { search: s.search } : {}),
|
|
35
36
|
...(ref.length ? { ref } : {}),
|
|
37
|
+
...(locks[s.name.toLowerCase()]?.length ? { lock: locks[s.name.toLowerCase()] } : {}),
|
|
36
38
|
...(parent ? { parent: { on: s.translates.on.toLowerCase(), ...(parent.scope ? { scope: [parent.scope, s.scope] } : {}), table: s.translates.parent.toLowerCase(), visible: rowPolicy(s, "caller", schemas).find(t => t[1] === "parent"), caller: rowPolicy(parent, "caller", schemas), trusted: rowPolicy(parent, "trusted", schemas), fields: inherited, ts: inherited.filter(c => parent.fields[c] === "timestamptz"), d: inherited.filter(c => parent.fields[c] === "date") } } : {}),
|
|
37
39
|
};
|
|
38
40
|
const expected = q([core(s, schemas), writePolicy(s, "delete", { lock: true }).lock]), got = q([expand(rules, fact), rules.lock]);
|
|
41
|
+
for (const verb of ["update", "delete"])
|
|
42
|
+
if (q(writePolicy(s, verb).locks.map(l => l[0])) !== q((fact.lock ?? []).filter(l => l.on.includes(verb)).map(l => l.name)))
|
|
43
|
+
refuse(`schemas/${s.name}`, `Core's ${verb} locks for this Schema differ from the Store's.`);
|
|
39
44
|
if (expected !== got)
|
|
40
45
|
refuse(`schemas/${s.name}`, `Core's row rules for this Schema differ from the shared rules (${expected} vs ${got}); the generic Store cannot serve it.`);
|
|
41
46
|
const indexed = new Set([...(s.publishing ? [[...(s.scope ? [s.scope] : []), "published_at", "id"]] : []), ...(s.indexes ?? []), ...(s.unique ?? [])].map(i => i[0] === s.scope ? i[1] : i[0]).filter((c) => !!c));
|
|
@@ -45,7 +50,7 @@ export function emitStores(schemas, isSqlite, newId, refuse, required = false) {
|
|
|
45
50
|
const scalar = (c) => SCALAR.has(type(c));
|
|
46
51
|
const writable = declared.filter(c => !(s.userScopes ?? []).some(u => u.column === c) && (scalar(c) || type(c) === "json"));
|
|
47
52
|
const input = (c) => `${inputType(type(c))}${nonNull(c) ? "" : " | null"}`;
|
|
48
|
-
tables.push(`${key(s.name)}: { row: ${of([...names, ...geo], c => c === "id" ? "string" : nativeType(geo.includes(c) ? "real" : type(c), isSqlite, nonNull(c)))}${inherited.length ? ` & { ${inherited.map(c => `${key(c)}?: ${nativeType(inheritedType(c), isSqlite)};`).join(" ")} }` : ""}; fields: ${of(names.filter(scalar), c => inputType(type(c)))}; set: ${of(writable, input)}${s.userScopes?.length ? `; insert: ${of([...writable, ...s.userScopes.map(u => u.column)], input)}` : ""}; required: ${union(s.publishing ? [] : writable.filter(c => required.has(s.names?.[c] ?? c)))}; order: ${union(["id", ...names.filter(c => c !== "id" && scalar(c) && indexed.has(c) && (nonNull(c) || s.publishing && c === "published_at"))])} }`);
|
|
53
|
+
tables.push(`${key(s.name)}: { row: ${of([...names, ...geo], c => c === "id" ? "string" : c === "version" ? "number" : nativeType(geo.includes(c) ? "real" : type(c), isSqlite, nonNull(c)))}${inherited.length ? ` & { ${inherited.map(c => `${key(c)}?: ${nativeType(inheritedType(c), isSqlite)};`).join(" ")} }` : ""}; fields: ${of(names.filter(scalar), c => inputType(type(c)))}; set: ${of(writable, input)}${s.userScopes?.length ? `; insert: ${of([...writable, ...s.userScopes.map(u => u.column)], input)}` : ""}; required: ${union(s.publishing ? [] : writable.filter(c => required.has(s.names?.[c] ?? c)))}; order: ${union(["id", ...names.filter(c => c !== "id" && scalar(c) && indexed.has(c) && (nonNull(c) || s.publishing && c === "published_at"))])} }`);
|
|
49
54
|
facts.push(`${q(s.name)}: ${q(fact)}`);
|
|
50
55
|
}
|
|
51
56
|
return { tables: `{ ${tables.join("; ")} }`, rules: q(rules), facts: `{ ${facts.join(", ")} }` };
|
package/docs/examples/README.md
CHANGED
|
@@ -39,5 +39,5 @@ shell's allow-all `src/signup.ts`, unless sign-up should be invite-only. Purchas
|
|
|
39
39
|
|
|
40
40
|
Every example uses two roles: `admin` (the admin layer) and `user`.
|
|
41
41
|
|
|
42
|
-
[Reference service](https://github.com/aotter/mantle/tree/v0.2.
|
|
42
|
+
[Reference service](https://github.com/aotter/mantle/tree/v0.2.1-alpha.1/docs/examples/reference-service) is the release smoke test for packed packages, not a starting point. Its source lives in the tagged repository; it is not included in the npm docs.
|
|
43
43
|
Projects on 0.1.x move with [the upgrade guide](../upgrade-0.1-to-0.2.md).
|
|
@@ -12,15 +12,49 @@ cd my-app
|
|
|
12
12
|
pnpm install
|
|
13
13
|
# add manifests/*.yaml (mantle help four-atoms; patterns: mantle help examples)
|
|
14
14
|
pnpm generate # writes generated/ and lists any `handler: { ref }` to implement in src/handlers.ts
|
|
15
|
-
pnpm types && pnpm check
|
|
15
|
+
pnpm types && pnpm check # each builds once (see Which steps build)
|
|
16
16
|
cp .dev.vars.example .dev.vars
|
|
17
|
-
pnpm db:init #
|
|
18
|
-
pnpm dev # one-time codes print to this log
|
|
19
|
-
pnpm smoke # your smoke.mjs on smoke-kit.mjs (see Files you edit)
|
|
20
|
-
pnpm accept #
|
|
21
|
-
pnpm test # Admin MCP App check: needs Chrome (npx playwright-core install chromium, or MANTLE_TEST_CHROMIUM=<executable>)
|
|
17
|
+
pnpm db:init # optional seed.sql for static reference data, then pnpm db:seed (see Seed data)
|
|
18
|
+
pnpm dev # builds, rebuilds on change; one-time codes print to this log
|
|
19
|
+
pnpm smoke # builds; your smoke.mjs on smoke-kit.mjs (see Files you edit)
|
|
20
|
+
pnpm accept # builds; hands each role to a fresh evaluator (see Independent acceptance)
|
|
21
|
+
pnpm test # Admin MCP App check of the last build: needs Chrome (npx playwright-core install chromium, or MANTLE_TEST_CHROMIUM=<executable>)
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
### Which steps build
|
|
25
|
+
|
|
26
|
+
`wrangler.jsonc` sets `mantle bundle src/index.ts dist` as Wrangler's custom build. These steps run it (marked
|
|
27
|
+
**builds**); the others do not:
|
|
28
|
+
|
|
29
|
+
| Step | Builds? |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `pnpm generate`, `pnpm db:init`, `pnpm db:seed` | no |
|
|
32
|
+
| `pnpm test` | no; it tests the last build |
|
|
33
|
+
| `pnpm types` (`wrangler types`) | **builds**: Wrangler reads the built entry's exports for Durable Object and service binding types |
|
|
34
|
+
| `pnpm check` | **builds** once, in `wrangler types --check`; `generate --check` and `tsc` do not |
|
|
35
|
+
| `pnpm build` | **builds** |
|
|
36
|
+
| `pnpm dev` | **builds** at start, then again on every change under `src/` or `generated/` |
|
|
37
|
+
| `pnpm smoke`, `pnpm accept` | **build** once each: both start `wrangler dev` |
|
|
38
|
+
|
|
39
|
+
Wrangler has no option to skip the custom build for `wrangler types`. The Admin console is rebuilt only when the
|
|
40
|
+
installed Admin packages change or the app has `src/admin.tsx`, so a repeated build mostly rebuilds the Worker.
|
|
41
|
+
|
|
42
|
+
First run, after `pnpm install` and your manifests: `pnpm generate`, `pnpm types`, `pnpm check`,
|
|
43
|
+
`cp .dev.vars.example .dev.vars` (`pnpm dev` needs the secrets `wrangler.jsonc` requires), `pnpm db:init`, then
|
|
44
|
+
`pnpm dev` or `pnpm smoke`. Right after `pnpm types`, `pnpm check` repeats its build; run it anyway, because it is the
|
|
45
|
+
same check CI and hand-off use.
|
|
46
|
+
|
|
47
|
+
Routine edit, with `pnpm dev` running:
|
|
48
|
+
|
|
49
|
+
1. Edit `manifests/*.yaml`, then `pnpm generate`. Wrangler does not watch `manifests/`, so the last build keeps
|
|
50
|
+
serving until something under `src/` or `generated/` changes. From the next change under `src/` until you
|
|
51
|
+
generate, each change prints one line, `Generated output in generated is stale (...)`, and the dev server holds
|
|
52
|
+
requests instead of running stale code. Writing `generated/` rebuilds. If you undo the manifest edit instead,
|
|
53
|
+
nothing rebuilds until the next change under `src/` or a restart of `pnpm dev`.
|
|
54
|
+
2. `pnpm types` only when `generate` or you changed `wrangler.jsonc` (bindings, secrets, vars).
|
|
55
|
+
3. Edits under `src/` rebuild by themselves.
|
|
56
|
+
4. Before handing off: `pnpm check`, then `pnpm smoke`.
|
|
57
|
+
|
|
24
58
|
npm works too (`npm install`, `npm run …`). `package.json` pins `@better-auth/core` and `@better-auth/utils` even though
|
|
25
59
|
the shell does not import them: they are the exact versions the SDK's Better Auth peers require, and without them a fresh
|
|
26
60
|
install resolves newer ones (npm ERESOLVE, or a second Better Auth type tree under pnpm). Keep every Better Auth package
|
|
@@ -47,6 +81,19 @@ HTTP-only Procedures will keep generating with a warning.
|
|
|
47
81
|
|
|
48
82
|
Search for `AGENT:` to find what an app must fill in.
|
|
49
83
|
|
|
84
|
+
## Seed data
|
|
85
|
+
|
|
86
|
+
Create test records through the app: your `smoke.mjs` or an `accept.mjs` fixtures module calls the app's own REST, MCP
|
|
87
|
+
or Admin operations as a signed-in user, so Mantle fills `id`, `version`, `created_at`, `updated_at`, `author_id`
|
|
88
|
+
and the publishing `status`/`published_at`. Keep `seed.sql` for static reference data no surface writes; native SQL
|
|
89
|
+
must set those columns itself, timestamps as integer microseconds on D1. An omitted `updated_at` stores `0`, which
|
|
90
|
+
Admin shows as 1970. `mantle help local-testing` (Seed data) has a complete seed:
|
|
91
|
+
|
|
92
|
+
```sql
|
|
93
|
+
INSERT INTO categories (id, version, created_at, updated_at, name) VALUES
|
|
94
|
+
('books', 1, 1767225600000000, 1767225600000000, 'Books');
|
|
95
|
+
```
|
|
96
|
+
|
|
50
97
|
## Local testing notes
|
|
51
98
|
|
|
52
99
|
- Better Auth allows 3 OTP requests per minute per client IP. `smoke-kit.mjs` gives each user its own
|
|
@@ -60,7 +107,8 @@ Search for `AGENT:` to find what an app must fill in.
|
|
|
60
107
|
`mantle help local-testing` lists what the smoke covers: every Procedure at its edges, each caller's isolation,
|
|
61
108
|
visitor pages without JavaScript, MCP discovery and Admin's MCP App.
|
|
62
109
|
Scripted smoke is not independent acceptance; before reporting a user-facing
|
|
63
|
-
deliverable done, follow the independent acceptance guide: `mantle help agent-workflow-acceptance`.
|
|
110
|
+
deliverable done, follow the independent acceptance guide: `mantle help agent-workflow-acceptance`. Hand off with
|
|
111
|
+
its layered report ("Report done-ness in layers"): smoke and `pnpm test` are layer 2, `pnpm accept` rounds layer 3.
|
|
64
112
|
|
|
65
113
|
### Independent acceptance
|
|
66
114
|
|
|
@@ -115,7 +163,7 @@ and compares exact names without calling business tools. JSON/SSE protocol failu
|
|
|
115
163
|
unexpected/missing tools and repeated/excessive pagination fail; it never obtains credentials or mounts routes.
|
|
116
164
|
|
|
117
165
|
This local cookie test does not prove OAuth Bearer or an external client's connection. Test those separately
|
|
118
|
-
with supplied credentials and record their results. For each declared interface,
|
|
119
|
-
report must say verified (command, identity and result), intentionally not exposed,
|
|
120
|
-
reason. WebMCP-only applications remain valid; an interrupted preview must retain the remote acceptance item
|
|
166
|
+
with supplied credentials and record their results as layer 4 of the layered report. For each declared interface,
|
|
167
|
+
the existing handoff/README/test report must say verified (command, identity and result), intentionally not exposed,
|
|
168
|
+
or deferred/unverified with a reason. WebMCP-only applications remain valid; an interrupted preview must retain the remote acceptance item
|
|
121
169
|
as deferred rather than silently claiming completion.
|
|
@@ -15,27 +15,27 @@
|
|
|
15
15
|
"accept": "node accept.mjs"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@aotter/mantle": "0.2.
|
|
19
|
-
"@aotter/mantle-ui": "0.2.
|
|
18
|
+
"@aotter/mantle": "0.2.1-alpha.1",
|
|
19
|
+
"@aotter/mantle-ui": "0.2.1-alpha.1",
|
|
20
20
|
"@better-auth/cimd": "1.7.7",
|
|
21
21
|
"@better-auth/core": "1.7.7",
|
|
22
22
|
"@better-auth/mcp": "1.7.7",
|
|
23
23
|
"@better-auth/oauth-provider": "1.7.7",
|
|
24
24
|
"@better-auth/utils": "0.4.2",
|
|
25
25
|
"@modelcontextprotocol/ext-apps": "2.0.0",
|
|
26
|
-
"@modelcontextprotocol/server": "2.
|
|
26
|
+
"@modelcontextprotocol/server": "2.3.1",
|
|
27
27
|
"better-auth": "1.7.7",
|
|
28
|
-
"@aotter/mantle-admin": "0.2.
|
|
28
|
+
"@aotter/mantle-admin": "0.2.1-alpha.1",
|
|
29
29
|
"react": "19.3.0",
|
|
30
30
|
"react-dom": "19.3.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@cloudflare/workers-types": "5.
|
|
33
|
+
"@cloudflare/workers-types": "5.20261027.1",
|
|
34
34
|
"@types/node": "^26",
|
|
35
35
|
"@types/react": "^19.3.0",
|
|
36
36
|
"playwright-core": "1.62.1",
|
|
37
37
|
"typescript": "^6.0.3",
|
|
38
|
-
"wrangler": "4.
|
|
38
|
+
"wrangler": "4.153.0"
|
|
39
39
|
},
|
|
40
40
|
"engines": {
|
|
41
41
|
"node": ">=22.22"
|
|
@@ -9,6 +9,7 @@ spec:
|
|
|
9
9
|
# It also refuses an officer lowering capacity below the seats already taken.
|
|
10
10
|
checks: ["seats_taken <= capacity"]
|
|
11
11
|
indexes: [[startsAt, id]]
|
|
12
|
+
searchableFields: [title]
|
|
12
13
|
uiSchema: { list: { primaryField: title, columns: [startsAt, capacity, seats_taken, closed] } }
|
|
13
14
|
schema:
|
|
14
15
|
type: object
|
|
@@ -54,6 +55,7 @@ spec:
|
|
|
54
55
|
uniqueIndexes: [[email]]
|
|
55
56
|
# Better Auth lowercases emails; the check refuses a mixed-case entry that would never match.
|
|
56
57
|
checks: ["email = lower(email)"]
|
|
58
|
+
searchableFields: [email, name]
|
|
57
59
|
uiSchema: { list: { primaryField: email, columns: [name] } }
|
|
58
60
|
schema:
|
|
59
61
|
type: object
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
-- Local fixture only. Production publishing goes through the generated Procedures.
|
|
2
|
+
-- Every system column is set: timestamps are microseconds since the Unix epoch (2026-01-01T00:00:00Z); 0 would show as 1970.
|
|
2
3
|
INSERT INTO posts (id, version, created_at, updated_at, title, body, status, published_at) VALUES
|
|
3
|
-
('home', 1,
|
|
4
|
-
('hello', 1,
|
|
5
|
-
('second', 1,
|
|
6
|
-
('draft', 1,
|
|
4
|
+
('home', 1, 1767225600000000, 1767225600000000, 'Posts', '# Posts — browse the HTML index for articles.', 'published', 1767225600000000),
|
|
5
|
+
('hello', 1, 1767225600000000, 1767225600000000, 'Hello <world>', '# Hello world', 'published', 1767225600000000),
|
|
6
|
+
('second', 1, 1767225600000000, 1767225600000000, 'Second post', '# Second post', 'published', 1767225600000000),
|
|
7
|
+
('draft', 1, 1767225600000000, 1767225600000000, 'Hidden draft', '# Draft', 'draft', NULL);
|
|
@@ -9,6 +9,7 @@ spec:
|
|
|
9
9
|
uniqueIndexes: [[department]]
|
|
10
10
|
# An approval that would overspend fails this check and the whole approval rolls back.
|
|
11
11
|
checks: ["remaining >= 0"]
|
|
12
|
+
searchableFields: [department]
|
|
12
13
|
uiSchema: { list: { primaryField: department, columns: [remaining] } }
|
|
13
14
|
schema:
|
|
14
15
|
type: object
|
|
@@ -25,6 +26,7 @@ spec:
|
|
|
25
26
|
title: Purchase requests
|
|
26
27
|
lifecycle: operational
|
|
27
28
|
indexes: [[authorId, createdAt, id], [requestStatus, createdAt, id]]
|
|
29
|
+
searchableFields: [title, department]
|
|
28
30
|
# Sidebar: in the Approvals group (src/admin.tsx), after the queue of requests waiting for a decision.
|
|
29
31
|
uiSchema: { list: { primaryField: title, columns: [department, amount, requestStatus], filterField: requestStatus }, nav: { group: approvals, order: 2 } }
|
|
30
32
|
schema:
|
|
@@ -50,6 +52,7 @@ spec:
|
|
|
50
52
|
indexes: [[createdAt, id]]
|
|
51
53
|
# One row per spending request.
|
|
52
54
|
uniqueIndexes: [[requestId]]
|
|
55
|
+
searchableFields: [department]
|
|
53
56
|
uiSchema: { list: { primaryField: department, columns: [amount, remainingAfter, requestId] } }
|
|
54
57
|
schema:
|
|
55
58
|
type: object
|
|
@@ -44,8 +44,11 @@ An existing S3 signer (for example an application already using aws4fetch) fits
|
|
|
44
44
|
this port. This package adds no AWS SDK. The signer must return a SigV4
|
|
45
45
|
query-signed PUT URL and preserve `X-Amz-Expires`; never give its credentials to
|
|
46
46
|
the client. Configure bucket CORS to allow PUT from the application's origins
|
|
47
|
-
and the returned Content-Type header.
|
|
48
|
-
|
|
47
|
+
and the returned Content-Type header. Uploads go to `uploads/<purpose>/<group>/<role>.<ext>` and each commit copies them
|
|
48
|
+
to `committed/<group>/<attempt>/<role>.<ext>`. Serve the `committed/` prefix on the public domain. Keep `uploads/`
|
|
49
|
+
private/unserved on the public domain and add a bucket lifecycle rule for abandoned upload keys. Mantle 0.1.x wrote
|
|
50
|
+
`<purpose>/<group>/<role>.<ext>` keys (`<group>/<role>.<ext>` when the purpose was not a plain slug); an asset reads its keys from the stored `storageKey` and `publicUrl` of each
|
|
51
|
+
variant, so 0.1.x assets keep working while their prefixes stay served.
|
|
49
52
|
|
|
50
53
|
Commit checks every object's MIME and size, then rechecks the exact GET
|
|
51
54
|
response used for the copy. Public keys cannot be reached by an upload URL. Each commit attempt has a separate
|
|
@@ -54,7 +57,7 @@ loser can neither overwrite nor remove the winner's objects. After SQL selects
|
|
|
54
57
|
the durable asset, the losing attempt removes only its own unselected copies.
|
|
55
58
|
Upload sources remain until the database commit succeeds, so an uncertain DB
|
|
56
59
|
write can be reconciled/retried with the same UUID. Failed object deletion keeps
|
|
57
|
-
the media row; retry the same id. An upload group expires after 15 minutes. Asset `createdAt` is the upload-group creation time
|
|
60
|
+
the media row; retry the same id. An upload group expires after 15 minutes. Asset `createdAt` (`media_assets.created_at`) is the upload-group creation time in epoch milliseconds, not the later commit time; entry timestamps on D1 are microseconds ([reserved entry columns](../reference/schema.md#reserved-entry-columns)).
|
|
58
61
|
No R2 upload or deployment is performed by generation.
|
|
59
62
|
|
|
60
63
|
A positively identified native rollback deletes only that attempt's copied
|
|
@@ -22,6 +22,8 @@ under `secrets.required`, run `pnpm exec wrangler types --include-runtime=false`
|
|
|
22
22
|
add `worker-configuration.d.ts` to `tsconfig.json` and commit it.
|
|
23
23
|
`@cloudflare/workers-types` still supplies runtime types. Rerun after every binding
|
|
24
24
|
change; `wrangler types --include-runtime=false --check` fails on drift.
|
|
25
|
+
Both run the custom build (`mantle bundle`), because Wrangler reads the built entry's exports
|
|
26
|
+
for Durable Object and service binding types; Wrangler has no option to skip it.
|
|
25
27
|
Once `secrets.required` is set, `wrangler dev` loads only those keys from
|
|
26
28
|
`.dev.vars`/`.env`, so declare every secret the app reads.
|
|
27
29
|
|
|
@@ -2,7 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
Schemas default to `operational`: rows are live immediately and edit in place. Opt-in `publishing` adds `status` (draft/published/archived), nullable `published_at` and a scope-aware `(published_at, id)` index.
|
|
4
4
|
|
|
5
|
-
Generated `<table>.publish`, `.unpublish` and `.archive` Procedures take `{ id, expectedVersion }`, require admin/system and perform one atomic UPDATE. Publish checks required fields, visibility, TTL, source state and OCC. A failed guard is `CONFLICT`. Store cannot write status/published_at directly. Admin
|
|
5
|
+
Generated `<table>.publish`, `.unpublish` and `.archive` Procedures take `{ id, expectedVersion }`, require admin/system and perform one atomic UPDATE. Publish checks required fields, visibility, TTL, source state and OCC. A failed guard is `CONFLICT`. Store cannot write status/published_at directly. By default Admin's generic patch edits drafts only (published and archived entries are unpublished first), and a published entry is unpublished before it is deleted; both are an implicit lock named `publishing` that Admin shows before the edit, and neither applies to the transitions. A Store update may edit a published row in place but cannot clear a field `publish` requires: setting one to null is `INPUT_VALIDATION_FAILED` for every caller. Fields it leaves alone are not re-checked, so adding a field to `required` later does not block updates of posts published before it.
|
|
6
|
+
|
|
7
|
+
To let admins correct a published entry in place, opt in with `publishing: { liveEdits: true }`. A personal blog needs nothing more than one Schema:
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
11
|
+
kind: Schema
|
|
12
|
+
metadata: { name: posts }
|
|
13
|
+
spec:
|
|
14
|
+
title: Posts
|
|
15
|
+
lifecycle: publishing
|
|
16
|
+
publishing: { liveEdits: true }
|
|
17
|
+
schema:
|
|
18
|
+
type: object
|
|
19
|
+
required: [title, body]
|
|
20
|
+
properties:
|
|
21
|
+
title: { type: string }
|
|
22
|
+
body: { type: string }
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Admin then edits a published post with a "changes go live on save" notice. The save goes live on commit, `published_at` is kept and `updated_at` moves. The merged post must still pass `publish`'s checks, so emptying its title is refused with `INPUT_VALIDATION_FAILED`. An archived post stays draft-only: restore it to edit it. Deleting a published post still requires unpublishing, and [declared locks](../reference/schema.md#locks) still apply. `publishing` is refused on an operational Schema, which already edits in place. To prepare changes to published content without exposing them, split it into a publishing parent and operational revisions instead ([edit published content without downtime](../guides/revisions.md)).
|
|
6
26
|
|
|
7
27
|
Public Views see published rows only. Other reads expose drafts only to admin/system. This policy applies to joins and nested queries too.
|
|
8
28
|
|
|
@@ -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.
|
|
5
|
+
The runnable [reference service](https://github.com/aotter/mantle/tree/v0.2.1-alpha.1/docs/examples/reference-service) exercises the native package integration. See the [generated public website](../../examples/generated-web/README.md) for a native Worker example.
|
|
@@ -6,7 +6,7 @@ Schema `uiSchema` selects fields, widgets, list columns, enum tabs and child nav
|
|
|
6
6
|
|
|
7
7
|
Admin Views provide custom read lists; their SELECT outputs, column lineage and `uiSchema.list` define display/search/filter metadata. Procedures and MCP admin Triggers define operations with declared authorization, input checks and optional row targets. A hidden button never authorizes an operation.
|
|
8
8
|
|
|
9
|
-
Generic publishing edits apply to drafts; publishing transitions retain their atomic guards and OCC. Read-only collections refuse generic edits while declared Procedures can still run. Ref operations receive the caller/transaction-bound Store.
|
|
9
|
+
Generic publishing edits apply to drafts unless the Schema sets `publishing: { liveEdits: true }` ([lifecycle](../concepts/lifecycle-and-locales.md)); publishing transitions retain their atomic guards and OCC. Read-only collections refuse generic edits while declared Procedures can still run. Ref operations receive the caller/transaction-bound Store.
|
|
10
10
|
|
|
11
11
|
Use [`defineAdmin` components](native-admin.md#customising-admin) for app-owned cells, inputs, record asides and actions; server work is a Procedure, a View or an application route. Keep exact conflict/uncertain-outcome handling in the shared UI controller.
|
|
12
12
|
|
|
@@ -13,20 +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.
|
|
16
|
+
`v0.2.1-alpha.1`). Pin the tag to keep instructions aligned with the application's
|
|
17
17
|
SDK. A skill install does not upgrade the application's SDK.
|
|
18
18
|
|
|
19
19
|
For Codex, pin the marketplace checkout:
|
|
20
20
|
|
|
21
21
|
```sh
|
|
22
|
-
codex plugin marketplace add aotter/mantle --ref v0.2.
|
|
22
|
+
codex plugin marketplace add aotter/mantle --ref v0.2.1-alpha.1
|
|
23
23
|
codex plugin add mantle@mantle
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
For Claude Code, first clone the release into a directory you will keep:
|
|
27
27
|
|
|
28
28
|
```sh
|
|
29
|
-
git clone --depth 1 --branch v0.2.
|
|
29
|
+
git clone --depth 1 --branch v0.2.1-alpha.1 https://github.com/aotter/mantle.git mantle-plugin
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
Then, in Claude Code, use that checkout's absolute path:
|
|
@@ -40,9 +40,12 @@ Cursor and GitHub Copilot use their plugin manifests from the same tagged
|
|
|
40
40
|
checkout. Without a plugin host, install only the consumer skill by name:
|
|
41
41
|
|
|
42
42
|
```sh
|
|
43
|
-
npx skills add
|
|
43
|
+
npx skills add aotter/mantle@mantle
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
Without a version it follows `main`, the latest stable release. To match the
|
|
47
|
+
application's SDK exactly, add its release tag: `npx skills add aotter/mantle#v0.2.1-alpha.1@mantle`.
|
|
48
|
+
|
|
46
49
|
When upgrading the SDK, update the marketplace ref or local tagged checkout and
|
|
47
50
|
refresh/reinstall the plugin using the host's native workflow. Verify the loaded
|
|
48
51
|
manifest version and skill source. Do not reset a checkout containing local edits.
|
|
@@ -53,14 +56,15 @@ project and copies no handbook.
|
|
|
53
56
|
|
|
54
57
|
## 2. Install the SDK, then read its own docs
|
|
55
58
|
|
|
56
|
-
Use the version the project already pins. For a new project, install
|
|
57
|
-
0.2.0
|
|
59
|
+
Use the version the project already pins. For a new project, install this
|
|
60
|
+
handbook's version exactly. 0.2.1-alpha.1 is a prerelease; stable 0.2.0 remains the
|
|
61
|
+
production line, and its own tagged handbook describes it.
|
|
58
62
|
|
|
59
63
|
```sh
|
|
60
|
-
pnpm add --save-exact @aotter/mantle@0.2.
|
|
64
|
+
pnpm add --save-exact @aotter/mantle@0.2.1-alpha.1
|
|
61
65
|
```
|
|
62
66
|
|
|
63
|
-
Add optional `@aotter/mantle-ui@0.2.
|
|
67
|
+
Add optional `@aotter/mantle-ui@0.2.1-alpha.1` and `@aotter/mantle-admin@0.2.1-alpha.1` only
|
|
64
68
|
when the selected surfaces need them. Every selected `@aotter/mantle*` package
|
|
65
69
|
stays at the same exact version. Read, from the application:
|
|
66
70
|
|
|
@@ -81,6 +85,7 @@ the skill's GitHub copy and over any online handbook.
|
|
|
81
85
|
| `docs/skills/update/SKILL.md` | upgrading `@aotter/mantle`, including from 0.1.x |
|
|
82
86
|
| `docs/skills/provision/SKILL.md` | deploying, with production sign-in |
|
|
83
87
|
| `docs/skills/media-gc/SKILL.md` | auditing or removing stale R2 uploads |
|
|
88
|
+
| `docs/skills/performance/SKILL.md` | after acceptance converges, or when a page, first request or query is slow |
|
|
84
89
|
|
|
85
90
|
0.2.0 has no `mantle skills` command: nothing is copied into the project.
|
|
86
91
|
An agent reads these files from `node_modules` when the task calls for them.
|
|
@@ -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.**
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
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.
|
|
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,
|
|
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
|
|
212
|
-
|
|
213
|
-
|
|
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
|