@aotter/mantle 0.1.3-alpha.2 → 0.1.3-alpha.3

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 CHANGED
@@ -9,14 +9,14 @@ where agents write config and the runtime carries the complexity.
9
9
 
10
10
  ## Install
11
11
 
12
- Cold start for a new application is the pinned skill, not a bare npm add:
12
+ Cold start for a new application is the install skill, not a bare npm add:
13
13
 
14
14
  ```sh
15
- npx skills add aotter/mantle@v0.1.3-alpha.2 --skill install
15
+ npx skills add aotter/mantle --skill install
16
16
  ```
17
17
 
18
18
  To depend on this package in an existing project, pin the exact version
19
- from `package.json` (currently `0.1.3-alpha.2`):
19
+ from `package.json` (currently `0.1.3-alpha.3`):
20
20
 
21
21
  ```bash
22
22
  npm install @aotter/mantle
@@ -197,7 +197,7 @@ internals or rebuilding Mantle's adapters.
197
197
  Cold start from GitHub or a marketplace host:
198
198
 
199
199
  ```sh
200
- npx skills add aotter/mantle@v0.1.3-alpha.2 --skill install
200
+ npx skills add aotter/mantle --skill install
201
201
  ```
202
202
 
203
203
  That skill interviews, pins this package, then uses the CLI and the
@@ -215,26 +215,28 @@ Install the Mantle Core skill bundle before authoring or maintaining a
215
215
  consumer application. The canonical command is:
216
216
 
217
217
  ```sh
218
- npx skills add aotter/mantle@v0.1.3-alpha.2 --skill install
218
+ npx skills add aotter/mantle --skill install
219
219
  ```
220
220
 
221
- Claude Code and Codex can install the same pinned plugin, then run that skill:
221
+ Claude Code and Codex can install the plugin, then run that skill:
222
222
 
223
223
  ```bash
224
224
  # Claude Code
225
- /plugin marketplace add aotter/mantle@v0.1.3-alpha.2
225
+ /plugin marketplace add aotter/mantle
226
226
  /plugin install mantle@mantle
227
227
 
228
228
  # Codex
229
- codex plugin marketplace add aotter/mantle --ref v0.1.3-alpha.2
229
+ codex plugin marketplace add aotter/mantle
230
230
  codex plugin add mantle@mantle
231
231
  ```
232
232
 
233
233
  Cursor and VS Code Copilot can auto-discover the GitHub repo through
234
234
  `.cursor-plugin/plugin.json` and `.copilot-plugin/plugin.json` after the repo
235
235
  is cloned or opened. Still start from the `npx skills add` sentence (or open
236
- `skills/install/SKILL.md` at tag `v0.1.3-alpha.2`). Do not point a versioned consumer
237
- at a mutable branch.
236
+ `skills/install/SKILL.md`). Untagged `aotter/mantle` resolves to `main`, which
237
+ only advances at a release, so it is always the latest published version. Add
238
+ `@vX.Y.Z` only to reproduce an older project; never point a consumer at
239
+ `develop` or another moving branch.
238
240
 
239
241
  ## Marketplace capability installs
240
242
 
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- export declare const MANTLE_OVERVIEW = "mantle \u2014 compile manifests into a RuntimePlan and typed binding\n\nOverview\n Optional surfaces \u2014 take only what you need. Admin is opt-in. You can ship a\n complete service with no Dev UI, no visitor frontend, and no extra packages.\n\n Minimal \u2014 Spec + generate\n validate + generate compile manifests into a sealed plan and typed binding.\n Embed that binding in an existing host. No Admin, no visitor UI.\n\n Runtime / adapter\n Bind Runtime through an adapter (Cloudflare Worker, Bun, Vercel, or yours).\n HTTP Views, MCP, and Auth work without Admin.\n See docs/examples/host-minimal-worker or\n node_modules/@aotter/mantle/docs/examples/host-minimal-worker.\n\n Opt-in \u2014 Admin / Dev UI\n Add @aotter/mantle-admin and @aotter/mantle-admin-ui only when humans need\n a console. Then re-run generate, bind wrangler ASSETS, and open\n /admin/sign-in (local email OTP via ConsoleEmailSender).\n See docs/examples/host-local-admin-otp or\n node_modules/@aotter/mantle/docs/examples/host-local-admin-otp.\n\n Further (ask the subcommand for details)\n skills project version-matched agent instructions\n emit-openapi OpenAPI 3.1 from HTTP Triggers and Views\n mantle-harness measure indexes and live HTTP (separate binary)\n\nUsage: mantle <subcommand> [options]\n\nSubcommands:\n generate Compile manifests into a typed runtime binding\n validate Static manifest and handler-source validation\n skills Project version-matched Core skills\n emit-openapi Emit OpenAPI 3.1 from Triggers and Views\n\nDocumentation:\n Handbook: node_modules/@aotter/mantle/docs/handbook/ (or docs/handbook/)\n Online: https://mantle.tools/\n Install skill: npx skills add aotter/mantle@v0.1.3-alpha.2 --skill install\n";
2
+ export declare const MANTLE_OVERVIEW = "mantle \u2014 compile manifests into a RuntimePlan and typed binding\n\nOverview\n Optional surfaces \u2014 take only what you need. Admin is opt-in. You can ship a\n complete service with no Dev UI, no visitor frontend, and no extra packages.\n\n Minimal \u2014 Spec + generate\n validate + generate compile manifests into a sealed plan and typed binding.\n Embed that binding in an existing host. No Admin, no visitor UI.\n\n Runtime / adapter\n Bind Runtime through an adapter (Cloudflare Worker, Bun, Vercel, or yours).\n HTTP Views, MCP, and Auth work without Admin.\n See docs/examples/host-minimal-worker or\n node_modules/@aotter/mantle/docs/examples/host-minimal-worker.\n\n Opt-in \u2014 Admin / Dev UI\n Add @aotter/mantle-admin and @aotter/mantle-admin-ui only when humans need\n a console. Then re-run generate, bind wrangler ASSETS, and open\n /admin/sign-in (local email OTP via ConsoleEmailSender).\n See docs/examples/host-local-admin-otp or\n node_modules/@aotter/mantle/docs/examples/host-local-admin-otp.\n\n Further (ask the subcommand for details)\n skills project version-matched agent instructions\n emit-openapi OpenAPI 3.1 from HTTP Triggers and Views\n mantle-harness measure indexes and live HTTP (separate binary)\n\nUsage: mantle <subcommand> [options]\n\nSubcommands:\n generate Compile manifests into a typed runtime binding\n validate Static manifest and handler-source validation\n skills Project version-matched Core skills\n emit-openapi Emit OpenAPI 3.1 from Triggers and Views\n\nDocumentation:\n Handbook: node_modules/@aotter/mantle/docs/handbook/ (or docs/handbook/)\n Online: https://mantle.tools/\n Install skill: npx skills add aotter/mantle --skill install\n";
3
3
  //# sourceMappingURL=main.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":";AAgCA,eAAO,MAAM,eAAe,iyDAwC3B,CAAC"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":";AAgCA,eAAO,MAAM,eAAe,kxDAwC3B,CAAC"}
package/dist/cli/main.js CHANGED
@@ -64,7 +64,7 @@ Subcommands:
64
64
  Documentation:
65
65
  Handbook: node_modules/@aotter/mantle/docs/handbook/ (or docs/handbook/)
66
66
  Online: https://mantle.tools/
67
- Install skill: npx skills add aotter/mantle@v0.1.3-alpha.2 --skill install
67
+ Install skill: npx skills add aotter/mantle --skill install
68
68
  `;
69
69
  main().then((code) => {
70
70
  process.exitCode = code;
@@ -1,6 +1,6 @@
1
1
  # ADR-0024: Materialize each Schema as a native storage table
2
2
 
3
- **Status:** Accepted
3
+ **Status:** Accepted; the clauses allowing authored data to use `id`, `status` or `createdAt` are superseded by [ADR-0025](0025-reserved-native-columns-and-published-only-public-views.md) (2026-09-22)
4
4
 
5
5
  **Date:** 2026-09-18
6
6
 
@@ -58,9 +58,11 @@ identifier quoted. A Schema table uses its authored Schema name. SQLite
58
58
  preparation rejects collisions with selected infrastructure tables or views,
59
59
  including auth tables; this check belongs to the concrete deployment because
60
60
  the portable Manifest cannot know which optional infrastructure a host binds.
61
- Internal Mantle tables and columns use the reserved `_mantle_` prefix. The
62
- prefix prevents Mantle metadata from stealing ordinary authored names such as
63
- `id`, `status`, or `createdAt`.
61
+ Internal Mantle tables and columns use the reserved `_mantle_` prefix, so
62
+ Mantle metadata never collides with authored column names. *Superseded in
63
+ part by ADR-0025:* authored data may no longer use the six native column
64
+ names (`id`, `status`, `version`, `createdAt`, `updatedAt`, `authorId`); the
65
+ prefix remains for every other authored name.
64
66
 
65
67
  The SQLite adapter no longer creates `entries`, generated field columns, or
66
68
  Schema projection views. There is no dual write, compatibility view, or
@@ -247,8 +249,9 @@ Reviewed after implementation against ADR-0019, the semantic storage ports,
247
249
  Builder preview persistence, and Mantle Cloud's retryable deployment state.
248
250
  The review found and corrected three boundary mistakes before acceptance:
249
251
 
250
- - authored data may legitimately contain `id`, `status`, or `createdAt`, so
251
- physical envelope columns require the `_mantle_` prefix;
252
+ - authored data was allowed to contain `id`, `status`, or `createdAt`, so
253
+ physical envelope columns require the `_mantle_` prefix (the prefix stays;
254
+ ADR-0025 later reserved those names for authored data as well);
252
255
  - Builder should expose draft storage risk without pretending it knows the
253
256
  deployed baseline; Cloud shows the exact immutable artifact, while CI and
254
257
  Cloud/D1 own executable DDL verification and preview stays on IndexedDB;
@@ -0,0 +1,105 @@
1
+ # ADR-0025: Reserved native column names and published-only public Views
2
+
3
+ **Status:** Accepted
4
+
5
+ **Date:** 2026-09-22
6
+
7
+ **Related:** [#962](https://github.com/aotter/mantle/issues/962),
8
+ [#1007](https://github.com/aotter/mantle/issues/1007),
9
+ [#1008](https://github.com/aotter/mantle/issues/1008),
10
+ [#1009](https://github.com/aotter/mantle/issues/1009),
11
+ [#1010](https://github.com/aotter/mantle/issues/1010),
12
+ [ADR-0022](0022-caller-observed-version-occ.md),
13
+ [ADR-0024](0024-manifest-native-schema-tables.md) (partially superseded: its clauses allowing authored `id`, `status`, `createdAt`)
14
+
15
+ ## Context
16
+
17
+ Every entry carries six native columns outside `data`: `id`, `status`,
18
+ `version`, `createdAt`, `updatedAt`, `authorId`. Before this decision the
19
+ grammar treated their names inconsistently:
20
+
21
+ - A Schema data property could reuse one of the names. The SQLite dialect then
22
+ resolved the name to the data column first; IndexedDB resolved it to the
23
+ native field first. The same View read different columns on different
24
+ storage.
25
+ - `Schema.spec.indexes` could not reference the names at all
26
+ (`SCHEMA_INDEX_INVALID`), while the runtime's own indexes lead with
27
+ `_mantle_status`.
28
+ - A public View over a `publishing` Schema hid drafts only when the author
29
+ wrote `filter: eq status published`. The handbook promised drafts are
30
+ unreadable until published; nothing enforced it on REST, MCP
31
+ `query_view_*` or WebMCP, which share one compiled plan.
32
+
33
+ The #962 soak measured the consequence of the second point: the list View
34
+ read 14,940 rows per request on a 10,991-row D1 table. Without planner
35
+ statistics, which production SQLite-family storage never has, SQLite chose
36
+ the runtime's `(_mantle_status, _mantle_updated_at, _mantle_id)` index for the
37
+ status equality and sorted every published row in a temporary B-tree; the
38
+ declared `[publishedAt]` index was never used, and the grammar refused the
39
+ `(status, publishedAt)` index that would have served the query.
40
+
41
+ Two alternatives were considered and rejected during review (#1008):
42
+
43
+ - **Core derives indexes from Views.** Rejected: the manifest is a deployment
44
+ spec; Mantle Core is not a query optimizer, and a derived index has no
45
+ owner when a View is deleted.
46
+ - **Explicit object references** such as `{ meta: status }` /
47
+ `{ field: publishedAt, direction: desc }`. Deferred: it adds a grammar shape
48
+ to solve an ambiguity that reserving the names removes, and no shipped View
49
+ needs mixed-direction ordering. It remains the upgrade path.
50
+
51
+ A data-first resolution rule with only `status` reserved was also rejected: an
52
+ index declared as `[[createdAt]]` would silently change meaning when a data
53
+ property `createdAt` is added later, while the index identity (field-name
54
+ hex) kept the old physical index.
55
+
56
+ ## Decision
57
+
58
+ 1. **The six native column names are reserved.** A Schema data property may
59
+ not use them; `mantle validate` fails closed with
60
+ `INVALID_MANIFEST_ENVELOPE` at `/spec/schema/properties/<name>`, the same
61
+ mechanism ADR-0022 uses for `expectedVersion`. Domain names replace them
62
+ (`submittedAt`, `orderStatus`, `submittedBy`).
63
+ 2. **Every storage resolves a native name to the native column, first.** With
64
+ shadowing rejected this is a no-op for valid manifests; it is stated so the
65
+ SQLite dialect, IndexedDB and the index DDL cannot drift apart again.
66
+ 3. **`Schema.spec.indexes` may reference native names**; the SQLite dialect
67
+ maps them to `_mantle_*` columns in `CREATE INDEX`. `uniqueIndexes` may not:
68
+ native columns carry no domain uniqueness. Tuples stay `string[][]`; no
69
+ direction, no object form.
70
+ 4. **The author declares the access path; Mantle measures it.** A public View
71
+ over a `publishing` Schema always carries `status = published`, so its
72
+ index leads with `status` (`indexes: [[status, publishedAt]]`). Core does
73
+ not synthesize that index. The harness and the D1 inspector (#1010) report
74
+ whether the declared index is used.
75
+ 5. **Public Views over `publishing` Schemas read published rows only, fail
76
+ closed.** The linker injects `status = published` into the compiled plan
77
+ whether or not the manifest writes it; an authored identical predicate is
78
+ redundant and kept; any other `status` comparison on such a View is
79
+ `VIEW_PUBLIC_STATUS_INVALID`. Staff Views, `operational` Schemas and SQL
80
+ Views are untouched.
81
+
82
+ ## Consequences
83
+
84
+ - ADR-0024's statement that authored data may legitimately use `id`, `status`
85
+ or `createdAt` no longer holds; its `_mantle_` physical prefix is unchanged
86
+ and still guards every other authored name.
87
+
88
+ - Breaking for manifests that declared a data property with one of the six
89
+ names; the official examples and tests were renamed (#1009). Storage is
90
+ additive: the renamed property is a new column and the old one stays until
91
+ an explicit migration. The 0.1.3 release entry carries the upgrade note.
92
+ - The semantic fingerprint of any plan with a public publishing View changes
93
+ once (the injected predicate); `mantle generate` regenerates it.
94
+ - Index identities of existing declarations are unchanged; native-name tuples
95
+ get new identities. Stale indexes left behind by redeclaration are tracked
96
+ separately (#1011).
97
+ - Verified on real D1 without `ANALYZE`: the list View reads 50 rows per
98
+ request with `[status, publishedAt]` and 10,000 without it (#1014).
99
+
100
+ ## Where this lives
101
+
102
+ The handbook is the single source: [`reference/schema.md`](../handbook/reference/schema.md)
103
+ (reserved columns, index rules), [`reference/view.md`](../handbook/reference/view.md)
104
+ (surfaces). Skills and CLI diagnostics point there; they do not restate the
105
+ rules.
@@ -23,7 +23,8 @@ Records of *why* mantle ended up shaped this way. The numbering preserves POC AD
23
23
  | [0020](0020-builtin-handler-contracts-and-matched-upsert.md) | Static builtin handler contracts and natural-key matched upsert (`handler.match`). | Accepted + amended by 0022 |
24
24
  | [0021](0021-retire-starter-scaffolding.md) | Direct authoring; retire Starter scaffolding. | Accepted; supersedes 0018 |
25
25
  | [0022](0022-caller-observed-version-occ.md) | Caller-observed `expectedVersion` for Admin bind and builtin upsert OCC. | Accepted |
26
- | [0024](0024-manifest-native-schema-tables.md) | Materialize each Schema as a native storage table and deploy reviewed migration artifacts. | Accepted |
26
+ | [0024](0024-manifest-native-schema-tables.md) | Materialize each Schema as a native storage table and deploy reviewed migration artifacts. | Accepted; authored-native-name clauses superseded by 0025 |
27
+ | [0025](0025-reserved-native-columns-and-published-only-public-views.md) | Reserve the six native column names, let `indexes` reference them, and compile public publishing Views published-only. | Accepted |
27
28
 
28
29
  ## Reading order
29
30
 
@@ -1,9 +1,9 @@
1
1
  # Task-specific agent prompts
2
2
 
3
- Cold start from GitHub or a marketplace host is one pinned skill:
3
+ Cold start from GitHub or a marketplace host is the install skill:
4
4
 
5
5
  ```sh
6
- npx skills add aotter/mantle@v0.1.3-alpha.2 --skill install
6
+ npx skills add aotter/mantle --skill install
7
7
  ```
8
8
 
9
9
  Copy one block into a coding agent after that skill is present. Paths below
@@ -20,7 +20,7 @@ manifests exist. Admin is opt-in.
20
20
  ```text
21
21
  Interview me about the service: host, who uses it, whether humans need a
22
22
  Dev UI, and whether we only embed Spec/Runtime. If the install skill is
23
- missing, run npx skills add aotter/mantle@v0.1.3-alpha.2 --skill install. Read the
23
+ missing, run npx skills add aotter/mantle --skill install. Read the
24
24
  install skill and npx --no-install mantle --help, then
25
25
  handbook/start/project-and-cli.md.
26
26
  Use examples/README.md as the examples index; copy builtin-* Manifests only
@@ -30,7 +30,7 @@ spec:
30
30
  name: { type: string, minLength: 1, maxLength: 120 }
31
31
  email: { type: string, format: email }
32
32
  message: { type: string, minLength: 1, maxLength: 2000 }
33
- createdAt: { type: number, x-mcp-hint: timestamp-ms, x-mantle-bind: now }
33
+ submittedAt: { type: number, x-mcp-hint: timestamp-ms, x-mantle-bind: now }
34
34
  ---
35
35
  apiVersion: cms.mantle.aotter.net/v1
36
36
  kind: View
@@ -40,9 +40,9 @@ spec:
40
40
  title: Recent requests
41
41
  surface: staff
42
42
  from: requests
43
- fields: [id, name, email, message, createdAt]
43
+ fields: [id, name, email, message, submittedAt]
44
44
  orderBy:
45
- - { field: createdAt, direction: desc }
45
+ - { field: submittedAt, direction: desc }
46
46
  limit: 50
47
47
  ---
48
48
  apiVersion: cms.mantle.aotter.net/v1
@@ -80,7 +80,7 @@ spec:
80
80
  target: { procedure: submit-request }
81
81
  ```
82
82
 
83
- `createdAt` is stamped by the server (`x-mantle-bind: now`); a caller-supplied value is ignored. The builtin `create` projects `input ∩ Schema.properties`.
83
+ `submittedAt` is stamped by the server (`x-mantle-bind: now`); a caller-supplied value is ignored. It lives in `data` next to the native `createdAt` column so the timestamp travels with the record through MCP output and exports; data properties may not reuse the native column names. The builtin `create` projects `input ∩ Schema.properties`.
84
84
 
85
85
  ## Worker and handlers
86
86
 
@@ -109,7 +109,7 @@ curl -sS -X POST http://localhost:8787/api/requests \
109
109
  "collection": "requests",
110
110
  "status": "published",
111
111
  "version": 1,
112
- "data": { "name": "Ada", "email": "ada@example.test", "message": "Please call me back.", "createdAt": 1788879363492 },
112
+ "data": { "name": "Ada", "email": "ada@example.test", "message": "Please call me back.", "submittedAt": 1788879363492 },
113
113
  "authorId": null,
114
114
  "createdAt": 1788879363492,
115
115
  "updatedAt": 1788879363492
@@ -97,7 +97,7 @@ spec:
97
97
  title: { type: string }
98
98
  email: { type: string, format: email }
99
99
  note: { type: string }
100
- createdAt: { type: integer, x-mcp-hint: timestamp-ms, x-mantle-bind: now }
100
+ suggestedAt: { type: integer, x-mcp-hint: timestamp-ms, x-mantle-bind: now }
101
101
  ---
102
102
  apiVersion: cms.mantle.aotter.net/v1
103
103
  kind: Procedure
@@ -31,7 +31,7 @@ spec:
31
31
  requestedFor: { type: string, description: Requested date, time, or slot. }
32
32
  partySize: { type: integer, minimum: 1 }
33
33
  note: { type: string, maxLength: 1000 }
34
- createdAt: { type: number, x-mcp-hint: timestamp-ms, x-mantle-bind: now }
34
+ submittedAt: { type: number, x-mcp-hint: timestamp-ms, x-mantle-bind: now }
35
35
  ---
36
36
  apiVersion: cms.mantle.aotter.net/v1
37
37
  kind: View
@@ -41,9 +41,9 @@ spec:
41
41
  title: Reservation queue
42
42
  surface: staff
43
43
  from: reservations
44
- fields: [id, name, email, requestedFor, partySize, note, createdAt]
44
+ fields: [id, name, email, requestedFor, partySize, note, submittedAt]
45
45
  orderBy:
46
- - { field: createdAt, direction: desc }
46
+ - { field: submittedAt, direction: desc }
47
47
  limit: 50
48
48
  ---
49
49
  apiVersion: cms.mantle.aotter.net/v1
@@ -82,7 +82,7 @@ spec:
82
82
  target: { procedure: submit-reservation }
83
83
  ```
84
84
 
85
- `createdAt` is stamped by the server (`x-mantle-bind: now`); a caller-supplied value is ignored. `requestedFor` is a free string on purpose: this example does not impose a calendar model. The staff View orders by `createdAt`, so the newest request is first regardless of the requested slot.
85
+ `submittedAt` is stamped by the server (`x-mantle-bind: now`); a caller-supplied value is ignored. `requestedFor` is a free string on purpose: this example does not impose a calendar model. The staff View orders by `submittedAt`, so the newest request is first regardless of the requested slot.
86
86
 
87
87
  ## Worker and handlers
88
88
 
@@ -113,7 +113,7 @@ curl -sS -X POST http://localhost:8787/api/reservations \
113
113
  "collection": "reservations",
114
114
  "status": "published",
115
115
  "version": 1,
116
- "data": { "name": "Ada", "email": "ada@example.test", "requestedFor": "2026-10-03T19:00:00+08:00", "partySize": 4, "createdAt": 1788879363492 },
116
+ "data": { "name": "Ada", "email": "ada@example.test", "requestedFor": "2026-10-03T19:00:00+08:00", "partySize": 4, "submittedAt": 1788879363492 },
117
117
  "authorId": null,
118
118
  "createdAt": 1788879363492,
119
119
  "updatedAt": 1788879363492
@@ -30,7 +30,7 @@ spec:
30
30
  name: { type: string, minLength: 1, maxLength: 120 }
31
31
  email: { type: string, format: email }
32
32
  message: { type: string, minLength: 1, maxLength: 2000 }
33
- createdAt: { type: number, x-mcp-hint: timestamp-ms, x-mantle-bind: now }
33
+ submittedAt: { type: number, x-mcp-hint: timestamp-ms, x-mantle-bind: now }
34
34
  ---
35
35
  apiVersion: cms.mantle.aotter.net/v1
36
36
  kind: View
@@ -40,9 +40,9 @@ spec:
40
40
  title: Recent requests
41
41
  surface: staff
42
42
  from: requests
43
- fields: [id, name, email, message, createdAt]
43
+ fields: [id, name, email, message, submittedAt]
44
44
  orderBy:
45
- - { field: createdAt, direction: desc }
45
+ - { field: submittedAt, direction: desc }
46
46
  limit: 50
47
47
  ---
48
48
  apiVersion: cms.mantle.aotter.net/v1
@@ -264,7 +264,7 @@ curl -sS -X POST http://localhost:8787/api/requests \
264
264
  "collection": "requests",
265
265
  "status": "published",
266
266
  "version": 1,
267
- "data": { "name": "Ada", "email": "ada@example.test", "message": "Please call me back.", "createdAt": 1788879363492 },
267
+ "data": { "name": "Ada", "email": "ada@example.test", "message": "Please call me back.", "submittedAt": 1788879363492 },
268
268
  "authorId": null,
269
269
  "createdAt": 1788879363492,
270
270
  "updatedAt": 1788879363492
@@ -76,7 +76,7 @@ Parsing is all-or-nothing: one error in one document means no `ParsedManifestSet
76
76
 
77
77
  ## Schema — the entity
78
78
 
79
- A Schema declares one collection: the JSON Schema for each entry's `data`, its indexes, its Admin presentation and its [lifecycle mode](./lifecycle-and-locales.md). Entries also carry the native columns `id`, `status`, `version`, `createdAt`, `updatedAt` and `authorId` outside `data`.
79
+ A Schema declares one collection: the JSON Schema for each entry's `data`, its indexes, its Admin presentation and its [lifecycle mode](./lifecycle-and-locales.md). Entries also carry six [native columns](../reference/schema.md#reserved-entry-columns) outside `data`.
80
80
 
81
81
  ```yaml
82
82
  apiVersion: cms.mantle.aotter.net/v1
@@ -9,7 +9,7 @@ Two per-Schema decisions shape how an entry behaves for its whole life: which st
9
9
 
10
10
  `spec.lifecycle` is `publishing` (the default) or `operational`. The modes mix freely inside one site.
11
11
 
12
- **`publishing`** is for content a person stages and then releases: posts, pages, announcements, product copy. The point of the mode is that a draft exists and is not readable by the public until someone publishes it.
12
+ **`publishing`** is for content a person stages and then releases: posts, pages, announcements, product copy. The point of the mode is that a draft exists and is not readable by the public until someone publishes it. The runtime enforces this: a public declarative View over a publishing Schema is compiled with `status = published` even when the manifest omits the filter ([View reference](../reference/view.md#surfaces)).
13
13
 
14
14
  **`operational`** is for records written as a side effect rather than drafted: submissions, inquiries, orders, inventory snapshots, grant and audit rows. The point of the mode is that the row is real the moment it is created. Declare it on any Schema whose rows a human should inspect and correct, never stage and publish.
15
15
 
@@ -91,14 +91,14 @@ pnpm exec mantle skills --check
91
91
 
92
92
  This copies every skill the installed package marks `projection: project` — the develop skill among them — into matching `.agents/skills/mantle-*` and `.claude/skills/mantle-*` paths. Both layouts receive identical bytes; `--check` detects drift without writing. Skills that act destructively or target one platform stay out of that set and are opt-in. Manifest generation never rewrites agent instructions.
93
93
 
94
- For Claude Code, the same bundle is installable from the plugin marketplace at the published pin (or the exact version in `package.json`):
94
+ The same bundle is installable from the plugin marketplace:
95
95
 
96
96
  ```sh
97
97
  # Canonical
98
- npx skills add aotter/mantle@v0.1.3-alpha.2 --skill install
98
+ npx skills add aotter/mantle --skill install
99
99
 
100
100
  # Claude Code — two separate prompts
101
- /plugin marketplace add aotter/mantle@v0.1.3-alpha.2
101
+ /plugin marketplace add aotter/mantle
102
102
  /plugin install mantle@mantle
103
103
  ```
104
104
 
@@ -97,7 +97,7 @@ a native column. Declared `indexes` and `uniqueIndexes` become B-tree indexes
97
97
  over those columns; Core-compiled projections, filters and ordering reference
98
98
  the same columns directly.
99
99
 
100
- Declare the **smallest ordered index justified by the measured path**, and respect SQLite's leftmost-prefix rule. An index on `[locale, publishedAt]` serves `WHERE locale = ?`, `WHERE locale = ? AND publishedAt > ?`, and `WHERE locale = ? ORDER BY publishedAt`. It does not serve `WHERE publishedAt > ?` alone. If a second hot path needs a different leading field, that is a second index — not a reason to enumerate every permutation, since each index costs storage and slows every write.
100
+ Declare the **smallest ordered index justified by the measured path**, and respect SQLite's leftmost-prefix rule. Equality columns go first, the ordered column last. A public View over a publishing Schema always carries `status = published`, so its index leads with `status`: `[status, locale, publishedAt]` serves `WHERE status = ? AND locale = ?`, `… AND publishedAt > ?`, and `… ORDER BY publishedAt`. It does not serve `WHERE publishedAt > ?` alone, and `[publishedAt]` alone does not serve the published list — without planner statistics SQLite prefers the status equality and sorts in a temporary B-tree. If a second hot path needs a different leading field, that is a second index — not a reason to enumerate every permutation, since each index costs storage and slows every write.
101
101
 
102
102
  ```sh
103
103
  pnpm exec mantle-harness indexes --require-public --format text
@@ -139,7 +139,7 @@ spec:
139
139
  limit: 50
140
140
  ```
141
141
 
142
- The source Schema declares `indexes: [[locale, publishedAt]]`. The `gte publishedAt 0` clause is what keeps the ordered column inside the indexed range rather than forcing a sort. Omitting `locale` returns 400.
142
+ The source Schema declares `indexes: [[status, locale, publishedAt]]`; the `status` predicate is present whether or not the View writes it. The `gte publishedAt 0` clause is what keeps the ordered column inside the indexed range rather than forcing a sort. Omitting `locale` returns 400.
143
143
 
144
144
  ## Example: a staff SQL report
145
145
 
@@ -50,7 +50,7 @@ Raised by the parser, the graph validator and the code generator. `mantle valida
50
50
 
51
51
  | Code | Meaning | HTTP |
52
52
  |---|---|---|
53
- | `INVALID_MANIFEST_ENVELOPE` | Wrong `apiVersion`, unknown key at a known level, wrong value shape, a YAML syntax or alias-limit failure, or a Schema data property named `expectedVersion` (reserved Procedure OCC token; ADR-0022). | — |
53
+ | `INVALID_MANIFEST_ENVELOPE` | Wrong `apiVersion`, unknown key at a known level, wrong value shape, a YAML syntax or alias-limit failure, a Schema data property named `expectedVersion` (reserved Procedure OCC token; ADR-0022), or a Schema data property named after a native entry column (`id`, `status`, `version`, `createdAt`, `updatedAt`, `authorId`). | — |
54
54
  | `DUPLICATE_NAME` | Two documents of the same kind declare the same `metadata.name`. | — |
55
55
  | `VIEW_FROM_UNKNOWN_SCHEMA` | `spec.from` names no declared Schema. | — |
56
56
  | `VIEW_FIELD_NOT_IN_SCHEMA` | A `fields` entry or `orderBy[i].field` is neither a Schema property nor a reserved entry column. | — |
@@ -64,6 +64,7 @@ Raised by the parser, the graph validator and the code generator. `mantle valida
64
64
  | `VIEW_FILTER_CTX_USER_REF_REQUIRES_INDEX` | The bound field is not the leftmost field of a declared index. | — |
65
65
  | `VIEW_ORDERBY_INVALID` | An `orderBy` entry has the wrong shape or an unknown `direction`. | — |
66
66
  | `VIEW_UI_INVALID` | A View `uiSchema` key is unknown, used on a public View, or names an unknown output field. | — |
67
+ | `VIEW_PUBLIC_STATUS_INVALID` | A public View over a `publishing` Schema compares `status` to anything but `published`. The runtime always reads published rows only on that surface, so the filter can only contradict it. | — |
67
68
  | `REQUIRED_FIELD_UNKNOWN` | A `required` entry of `spec.schema` is not declared under `properties`. | — |
68
69
  | `INVALID_PATTERN` | A `pattern` does not compile as a JavaScript regular expression. | — |
69
70
  | `JSON_SCHEMA_UNSUPPORTED` | A JSON Schema keyword outside the accepted subset. | — |
@@ -108,7 +108,7 @@ Names containing `-` must be double-quoted when used as tables in a `sql` View (
108
108
 
109
109
  | Namespace | Reserved | Effect |
110
110
  |---|---|---|
111
- | Entry columns | `id`, `status`, `version`, `createdAt`, `updatedAt`, `authorId` | Native on every Schema. Cannot appear in `indexes` or `uniqueIndexes` (`SCHEMA_INDEX_INVALID`). Valid in View `fields`, `filter`, `orderBy` and `uiSchema.list`. Avoid declaring data properties with these names; SQL Views project the native column, not the data field. |
111
+ | Entry columns | `id`, `status`, `version`, `createdAt`, `updatedAt`, `authorId` | Native on every Schema; a data property may not reuse the name (`INVALID_MANIFEST_ENVELOPE`). Where they may appear in Views and indexes: [Schema reference](./schema.md#reserved-entry-columns) (ADR-0025). |
112
112
  | Data field | `locale` | A non-localized Schema that declares `properties.locale` is rejected; use a domain name such as `orderLocale`. On a localized Schema the runtime requires `data.locale` on writes. |
113
113
  | Data field | `expectedVersion` | Reserved Procedure OCC wire name. A Schema that declares `spec.schema.properties.expectedVersion` is `INVALID_MANIFEST_ENVELOPE` (ADR-0022). New reserved names need an ADR. |
114
114
  | View params | `page`, `show`, `cursor` | Owned by the runtime for pagination. Declaring them under `params.properties` is `VIEW_PARAMS_RESERVED_NAME`. |
@@ -14,7 +14,7 @@ A Schema declares one collection: the JSON Schema for each entry's `data`, its i
14
14
  | `schema` | JSON Schema 2020-12 | yes | — | Must be an object. Walked by the [subset validator](#json-schema-subset). |
15
15
  | `uiSchema` | object | no | — | Accepts `fields`, `list`, and `nav`. Violations are `SCHEMA_UI_INVALID`. |
16
16
  | `uniqueIndexes` | `string[][]` | no | `[]` | Ordered tuples of top-level scalar fields. See [Indexes](#indexes). |
17
- | `indexes` | `string[][]` | no | `[]` | Ordered non-unique tuples. Must not repeat a `uniqueIndexes` tuple. |
17
+ | `indexes` | `string[][]` | no | `[]` | Ordered non-unique tuples of data fields and native entry columns. Must not repeat a `uniqueIndexes` tuple. |
18
18
  | `searchableFields` | `string[]` | no | `[]` | Top-level string fields for Admin and Staff MCP substring search. |
19
19
  | `localized` | boolean | no | `false` | When `false`, a `locale` property is rejected. Must be a boolean. |
20
20
  | `translates` | `{ parent, on }` | no | — | Marks a translation child. Requires `localized: true`. |
@@ -22,7 +22,7 @@ A Schema declares one collection: the JSON Schema for each entry's `data`, its i
22
22
 
23
23
  ### Reserved entry columns
24
24
 
25
- Every entry carries `id`, `status`, `version`, `createdAt`, `updatedAt` and `authorId` as native columns outside `data`. They cannot be indexed (`SCHEMA_INDEX_INVALID`) but are valid in View `fields`, `filter`, `orderBy` and `uiSchema.list`. `locale` is a reserved data field: only a localized Schema may declare it, and the runtime requires it on writes to a localized Schema. Do not name data properties after the native columns; SQL Views project the native column. Do not declare `expectedVersion` under `spec.schema.properties` — that name is the reserved Procedure OCC token; validate fails closed with `INVALID_MANIFEST_ENVELOPE` (ADR-0022). New reserved Procedure input names need an ADR.
25
+ Every entry carries `id`, `status`, `version`, `createdAt`, `updatedAt` and `authorId` as native columns outside `data`. They are valid in View `fields`, `filter`, `orderBy` and `uiSchema.list`, and `indexes` may include them (`uniqueIndexes` may not). A data property may not reuse one of these names: validate fails closed with `INVALID_MANIFEST_ENVELOPE` at `/spec/schema/properties/<name>`, because SQLite-family and IndexedDB storage would otherwise resolve the name differently and an index declared today could change meaning when a same-named property is added later. Use a domain name instead (`submittedAt`, `orderStatus`, `submittedBy`); the native column is still there and still readable (ADR-0025). `locale` is a reserved data field: only a localized Schema may declare it, and the runtime requires it on writes to a localized Schema. SQL Views project the native column. Do not declare `expectedVersion` under `spec.schema.properties` — that name is the reserved Procedure OCC token; validate fails closed with `INVALID_MANIFEST_ENVELOPE` (ADR-0022). New reserved Procedure input names need an ADR.
26
26
 
27
27
  ## Example
28
28
 
@@ -191,13 +191,21 @@ Keep implementation-detail children fold-only. Use `nav.standalone: true` when s
191
191
  | When any index is declared, `metadata.name` matches `/^[A-Za-z][A-Za-z0-9_.-]*$/`. | `SCHEMA_INDEX_INVALID` at `/metadata/name` |
192
192
  | Each tuple is a non-empty array of strings. | `INVALID_MANIFEST_ENVELOPE` (shape) or `SCHEMA_INDEX_INVALID` (empty) |
193
193
  | No field repeats within a tuple; field names match the same safe pattern. | `SCHEMA_INDEX_INVALID` |
194
- | Fields are not reserved entry columns. | `SCHEMA_INDEX_INVALID` |
195
- | Fields are exact top-level keys of `properties`. | `UNIQUE_INDEX_FIELD_UNKNOWN` or `SCHEMA_INDEX_FIELD_UNKNOWN` |
194
+ | `uniqueIndexes` fields are data properties. `indexes` fields may also be the native columns `id`, `status`, `version`, `createdAt`, `updatedAt`, `authorId`, which map to their `_mantle_*` columns. | `SCHEMA_INDEX_INVALID` |
195
+ | Data fields are exact top-level keys of `properties`. | `UNIQUE_INDEX_FIELD_UNKNOWN` or `SCHEMA_INDEX_FIELD_UNKNOWN` |
196
196
  | Fields are indexable scalars: exactly one non-null type, optionally nullable. `string` maps to TEXT, `integer` and `boolean` to INTEGER, `number` to REAL. | `SCHEMA_INDEX_INVALID` |
197
197
  | No tuple repeats within a list; `indexes` does not repeat a `uniqueIndexes` tuple. | `SCHEMA_INDEX_INVALID` |
198
198
 
199
199
  On SQLite storage each Schema is a native table and every tuple becomes an
200
200
  index over its native field columns; queries benefit from a leftmost prefix.
201
+
202
+ A public View over a `publishing` Schema is compiled with `status = published`
203
+ whether or not the manifest writes it ([View surfaces](./view.md#surfaces)), so
204
+ its hot path always starts with an equality on `status`. Lead the index with
205
+ it, then the ordered field: `indexes: [[status, publishedAt]]`. A bare
206
+ `[[publishedAt]]` does not serve that query on production SQLite (no planner
207
+ statistics), which sorts every published row in a temporary B-tree instead
208
+ (#962). Declare the index the query needs; Mantle does not derive one (ADR-0025).
201
209
  Unique indexes are also checked before every write; a conflicting row is
202
210
  `CONFLICT`. After the first deployment, adding, removing, reordering, or changing
203
211
  any `uniqueIndexes` tuple is destructive and requires rebuilding the instance
@@ -206,6 +206,8 @@ Admin applies search and filters before pagination, rejecting a search term or f
206
206
  | `public` | `GET /api/views/<name>`, plus a catalog at `GET /api/views` | `query_view_<segment>` on `/mcp` | Also mounted at `GET /admin/api/views/<name>` and `/export` behind the staff gate |
207
207
  | `staff` | `GET /admin/api/views/<name>` and `/admin/api/views/<name>/export` — not mounted publicly | `query_view_<segment>` on `/mcp/staff` | Report sidebar |
208
208
 
209
+ A `public` declarative View over a `publishing` Schema reads **published rows only**, on every transport. The runtime adds `status = published` to the compiled query whether or not the filter spells it out; writing it is allowed and redundant, and comparing `status` to any other value is rejected at validate time (`VIEW_PUBLIC_STATUS_INVALID`). Staff Views see every status. `operational` Schemas create rows as `published`, so nothing is added. SQL Views (`spec.sql`) are the author's own statement and receive no injected predicate. Decision record: ADR-0025.
210
+
209
211
  `<segment>` is `metadata.name` lower-cased with `-` replaced by `_`. Two Views that mangle to the same segment collide with `MCP_TOOL_NAME_COLLISION`. Admin also serves the manifest listing `GET /admin/api/views-manifest`. Surface choice is visibility, not authorization: `requires` still gates every call on both transports. See [Surfaces](./surface.md) and [MCP and agents](../concepts/mcp-and-agents.md).
210
212
 
211
213
  The MCP `inputSchema` is `params.properties` plus `page` and `show` as optional numbers, carrying `params.required` through unchanged; the tool is annotated `readOnlyHint: true`.
@@ -5,10 +5,10 @@ description: The files you own in a Mantle project, every mantle and mantle-harn
5
5
 
6
6
  This page describes a directly authored Mantle project: which files are yours, what the installed CLI does to them, and the loop you run before every commit. Surfaces are optional — take only what you need. [The minimal Worker](./quickstart-worker.md) is Spec + adapter without Admin. [Local Admin](./quickstart-admin.md) is the opt-in Dev UI path when humans need a console.
7
7
 
8
- Cold start from GitHub or a marketplace host is the pinned install skill, not this page:
8
+ Cold start from GitHub or a marketplace host is the install skill, not this page:
9
9
 
10
10
  ```sh
11
- npx skills add aotter/mantle@v0.1.3-alpha.2 --skill install
11
+ npx skills add aotter/mantle --skill install
12
12
  ```
13
13
 
14
14
  ## You own the project
@@ -98,20 +98,19 @@ Run the harness after any change to a Schema index, View filter or ordering, or
98
98
 
99
99
  `mantle skills` projects the skills the installed package marks `projection: project`. At this version those are `develop`, `plugin`, `theme` and `update`; `install`, `media-gc` and `provision` stay opt-in because they create projects, delete remote objects or handle production secrets. Both tool layouts receive identical bytes. Generation never rewrites these files.
100
100
 
101
- Install the version-matched plugin bundle in the agent host. Cold start uses
102
- the published pin; an already-installed project uses the exact version from
103
- `package.json` (currently `0.1.3-alpha.2`):
101
+ Install the plugin bundle in the agent host. Cold start uses
102
+ the install skill; an already-installed project pins packages from `package.json`:
104
103
 
105
104
  ```sh
106
105
  # Canonical
107
- npx skills add aotter/mantle@v0.1.3-alpha.2 --skill install
106
+ npx skills add aotter/mantle --skill install
108
107
 
109
108
  # Claude Code — two separate prompts
110
- /plugin marketplace add aotter/mantle@v0.1.3-alpha.2
109
+ /plugin marketplace add aotter/mantle
111
110
  /plugin install mantle@mantle
112
111
 
113
112
  # Codex
114
- codex plugin marketplace add aotter/mantle --ref v0.1.3-alpha.2
113
+ codex plugin marketplace add aotter/mantle
115
114
  codex plugin add mantle@mantle
116
115
  ```
117
116
 
@@ -15,9 +15,11 @@ No task implicitly authorizes publication; no manual package/tag writer exists.
15
15
  |---|---|---|
16
16
  | Reviewed source; unused version | Core source/packed-consumer gates, then immutable Core tag | Exact canonical merged PR SHA and version required |
17
17
  | Tag exists; registry candidates partial | Existing npm/GPR publication steps | Verify existing artifact identity; publish missing versions only |
18
+ | Tag exists and all eleven npmjs packages already exist | Metadata verify, then channel promote and the GitHub release | Skip pack and immutable tarball compare. The controller tip must not rebuild published artifact identity. npm integrity metadata still has to be `sha512`. The public-registry Worker gate stays skipped |
19
+ | Tag exists on an ancestor of the dispatched tip; tip package versions still match | Resolve binds release identity to the tag SHA; later steps stay the existing writers | Controller-only recovery. Do not retag. The canonical merged-PR check uses the tag SHA. Fail when the tip version differs or the tag commit is not an ancestor |
18
20
  | Registry candidates verified | Public-registry reference consumer gate | No mutation; failure leaves public channels and `mantle-release` unchanged |
19
- | Consumer passes | That registry's promote step: monotonic channel add, then `dist-tag rm` of `mantle-release` only | Same version is a no-op; older runs cannot move a channel backward. Removal runs only after that package's promote loop, and only when `mantle-release` points at this version. A missing tag is a no-op. A tag pointing at another version is left for that version's promote step. `alpha` / `beta` / `rc` / `latest` are never removed |
20
- | Channels promoted or preserved, and the temp tag cleared or left | GitHub release step | Existing release identity or fail |
21
+ | Consumer passes | That registry's promote step: monotonic channel add for every package | Same version is a no-op; older runs cannot move a channel backward. Public channels are only `alpha`, `beta`, `rc`, and `latest`. `mantle-release` is never removed |
22
+ | Channels promoted or preserved | GitHub release step | Existing release identity or fail. Leftover `mantle-release` pointing at the last published candidate is expected |
21
23
 
22
24
  The public-registry gate uses a disposable copy of the directly authored
23
25
  `docs/examples/host-minimal-worker` reference, installs the exact candidate, then
@@ -39,41 +41,25 @@ foundational blocker returns to the state table and the user for a scope
39
41
  decision instead of starting another local redesign loop.
40
42
 
41
43
  Invariants: immutable versions/tags retain their identity; registry integrity
42
- and the published-consumer gate precede public channel promotion and any
43
- removal of `mantle-release` by the release controller; retries cannot move
44
- channels backward. No downstream mutation, unpublish or rollback is
45
- introduced. The runnable release-order check guards these transitions.
44
+ and the published-consumer gate precede public channel promotion; retries
45
+ cannot move channels backward. No downstream mutation, unpublish, dist-tag
46
+ removal, or rollback is introduced. The runnable release-order check guards
47
+ these transitions.
46
48
 
47
49
  Mutation boundaries during a release: `Publish to npmjs` and `Mirror to
48
- GitHub Packages` may attach `mantle-release` while publishing a version.
49
- `Promote npmjs channel tags` is the only release step that moves npmjs
50
- channels or removes that tag for the version being released. `Promote
51
- GitHub Packages channel tags` is the only release step that does the same
52
- for GitHub Packages. Recovery of a partial release reruns that same
53
- controller and version. It does not call the cleanup workflow.
54
-
55
- The cleanup workflow is a separate writer for one case the controller
56
- cannot cover: a release commit that predates temp-tag removal still leaves
57
- `mantle-release` behind, and a personal npm token that is `read-write` on
58
- `npm access` can still receive 403 on dist-tag DELETE.
59
- `.github/workflows/remove-mantle-release-dist-tag.yml` uses the Actions
60
- `NPM_TOKEN` and `GITHUB_TOKEN`. It is not a release controller and not a
61
- recovery path.
62
-
63
- | State | Sole next writer | Retry / invariant |
64
- |---|---|---|
65
- | Leftover `mantle-release` after `alpha`, `beta`, `rc`, or `latest` already points at that version | `remove-mantle-release-dist-tag`, only when `confirm` is `remove-mantle-release` | Missing tag is a no-op. Only `mantle-release` is removed. Before and after dist-tags are printed and compared; every other tag is unchanged |
66
- | `mantle-release` points at a version no consumer channel has | No deletion | The job fails and leaves the tag. Channel moves stay on the release promote step |
67
- | Confirm string is anything else | No registry call | The job fails before reading or editing tags |
68
-
69
- It shares the `release-controller` concurrency group with
70
- `cancel-in-progress: false`, so it waits out an in-progress release instead
71
- of deleting `mantle-release` between publish and channel promotion. After
72
- this file is on `develop`:
73
-
74
- ```sh
75
- gh workflow run remove-mantle-release-dist-tag --ref develop -f confirm=remove-mantle-release
76
- ```
50
+ GitHub Packages` attach the internal candidate dist-tag `mantle-release`
51
+ while publishing a version. `Promote npmjs channel tags` is the only
52
+ release step that moves npmjs public channels. `Promote GitHub Packages
53
+ channel tags` is the only release step that moves GitHub Packages public
54
+ channels. Neither step removes `mantle-release`. Recovery of a partial
55
+ release reruns that same controller and version.
56
+
57
+ `mantle-release` is not a public channel. It stays forever as an internal
58
+ candidate tag pointing at the last published candidate. Official channels
59
+ are only `alpha`, `beta`, `rc`, and `latest`. Consumers must not install
60
+ `mantle-release`. Deleting it after promotion is not part of the release:
61
+ dist-tag DELETE only added failure and re-run state, and Actions
62
+ `NPM_TOKEN` 403s on that DELETE.
77
63
 
78
64
  ## Branches and channels
79
65
 
@@ -85,8 +71,10 @@ gh workflow run remove-mantle-release-dist-tag --ref develop -f confirm=remove-m
85
71
  | `X.Y.Z` | `main` | `latest` | release |
86
72
 
87
73
  - The controller derives the source branch from the version: `-alpha` means
88
- `develop`, anything else means `main`. It refuses a commit that is not that
89
- branch's tip or not the merge commit of exactly one PR into that branch.
74
+ `develop`, anything else means `main`. An untagged dispatch must be that
75
+ branch's tip and the merge commit of exactly one PR into that branch.
76
+ When the version tag already exists on an ancestor of the tip, Resolve
77
+ recovers from that tag SHA; see Recovery.
90
78
  `scripts/release-tag-order.mjs` rejects any other prerelease identifier.
91
79
  - `develop` is where every change integrates first, so it is the base for all
92
80
  work despite `main` being the repository's default branch on GitHub. `main`
@@ -95,10 +83,13 @@ gh workflow run remove-mantle-release-dist-tag --ref develop -f confirm=remove-m
95
83
  approval, resolved threads and a current-base `Typecheck + tests` check.
96
84
  - Stable is the only release that moves `latest`. A prerelease channel keeps
97
85
  its last version when a later stable publishes.
98
- - Publish uses `--tag mantle-release`, so publication does not move
99
- `alpha`, `beta`, `rc`, or `latest`. After the public-registry consumer
100
- gate, each registry's promote step moves the real channel and then removes
101
- `mantle-release` when that tag points at this version.
86
+ - Publish uses `--tag mantle-release`. `mantle-release` is an internal
87
+ candidate dist-tag, not a public channel. Consumers must not install it.
88
+ Official channels are only `alpha`, `beta`, `rc`, and `latest`.
89
+ Publication does not move those channels. After the public-registry
90
+ consumer gate, each registry's promote step moves every package's real
91
+ channel and leaves `mantle-release` pointing at the last published
92
+ candidate.
102
93
 
103
94
  ## Prepare and run
104
95
 
@@ -118,6 +109,14 @@ gh workflow run remove-mantle-release-dist-tag --ref develop -f confirm=remove-m
118
109
  git grep -n "$OLD" -- ':!pnpm-lock.yaml' ':!**/package-lock.json'
119
110
  ```
120
111
 
112
+ Leave the consumer cold-start entry **untagged**: `npx skills add
113
+ aotter/mantle --skill install`, `/plugin marketplace add aotter/mantle`,
114
+ `codex plugin marketplace add aotter/mantle` and the plugin-manifest
115
+ descriptions carry no `@v…` / `--ref`. Untagged resolves to `main`, which
116
+ only moves at a release, so it is always the latest published version
117
+ (#995, #998). Only "installed version" statements such as
118
+ `(currently \`X.Y.Z\`)` are bumped.
119
+
121
120
  3. Review API compatibility and migration instructions for actual consumers.
122
121
  Frozen legacy consumers stay on their pinned version; do not make them
123
122
  follow new Core.
@@ -212,8 +211,9 @@ registries. Existing artifacts on retry must have matching integrity.
212
211
 
213
212
  Completion requires the Core tag SHA, all eleven npmjs/GPR packages, exact
214
213
  integrity, no workspace dependencies, a passing public-registry Worker gate,
215
- correct channel tags, no `mantle-release` tag left on this version, and the
216
- GitHub release. Retain run links and gate evidence.
214
+ correct channel tags, and the GitHub release. Leftover `mantle-release`
215
+ pointing at the last published candidate is expected. Retain run links and
216
+ gate evidence.
217
217
  This does not prove stable production soak or upgrade safety; the version's
218
218
  release-gate issue owns those acceptance requirements. An agent acceptance run
219
219
  uses only the version-matched authoring instructions, not an SDK checkout or
@@ -225,6 +225,19 @@ Rerun the same controller commit/version for a transient or verified partial
225
225
  transition. Existing tags/artifacts must match; newer channels stay put. Fail
226
226
  on identity disagreement instead of guessing. A wrong public artifact needs
227
227
  a new version; never force-retag, overwrite or reuse a published version.
228
+
229
+ After a controller-only fix lands on the tip, re-dispatch the same version
230
+ from the source branch. Resolve recovers using the existing tag SHA when
231
+ that commit is an ancestor of the tip and package versions on the tip still
232
+ match. The tag owns the release SHA; the tip only carries controller fixes.
233
+ Tagged recovery skips the Core source check because that tree was already
234
+ released from the immutable tag. When `tag_exists` and all eleven npmjs
235
+ packages already exist at that version, that same existence check skips
236
+ packing, immutable tarball comparison, and the public-registry Worker gate.
237
+ The controller tip must not rebuild an artifact whose identity is already
238
+ published. Verify npm metadata still runs. The Worker gate stays skipped
239
+ because the first run already passed it before promotion.
240
+
228
241
  Unpublish is reserved for actual secret/private-file exposure, never routine
229
242
  fixes. Infrastructure renames require their explicit config diff and live
230
243
  smoke; CI alone cannot prove provider identity.
@@ -6,7 +6,7 @@ Runtime. This Spec-only path is allowed by
6
6
  [ADR-0019](adr/0019-sealed-manifest-runtime-pipeline.md), not a new adapter,
7
7
  manifest grammar, or fork of Core.
8
8
 
9
- This recipe targets `0.1.3-alpha.2`. Pin the package, record the tested version, and
9
+ This recipe targets `0.1.3-alpha.3`. Pin the package, record the tested version, and
10
10
  rerun compatibility checks when upgrading.
11
11
 
12
12
  ## What stays with the host
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aotter/mantle",
3
- "version": "0.1.3-alpha.2",
3
+ "version": "0.1.3-alpha.3",
4
4
  "description": "Embeddable Mantle Core umbrella with Spec and Runtime; Web, Admin, Auth, Bun, Vercel, Cloudflare, and Admin UI are optional peer packages.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://mantle.tools/",
@@ -83,8 +83,8 @@
83
83
  "README.md"
84
84
  ],
85
85
  "dependencies": {
86
- "@aotter/mantle-runtime": "0.1.3-alpha.2",
87
- "@aotter/mantle-spec": "0.1.3-alpha.2"
86
+ "@aotter/mantle-runtime": "0.1.3-alpha.3",
87
+ "@aotter/mantle-spec": "0.1.3-alpha.3"
88
88
  },
89
89
  "peerDependencies": {
90
90
  "aws4fetch": "^1.0.20",
@@ -92,13 +92,13 @@
92
92
  "hono": "^4.12.0",
93
93
  "@libsql/client": "^0.17.4",
94
94
  "zod": "^4.5.0",
95
- "@aotter/mantle-auth": "0.1.3-alpha.2",
96
- "@aotter/mantle-admin": "0.1.3-alpha.2",
97
- "@aotter/mantle-cloudflare": "0.1.3-alpha.2",
98
- "@aotter/mantle-admin-ui": "0.1.3-alpha.2",
99
- "@aotter/mantle-bun": "0.1.3-alpha.2",
100
- "@aotter/mantle-web": "0.1.3-alpha.2",
101
- "@aotter/mantle-vercel": "0.1.3-alpha.2"
95
+ "@aotter/mantle-admin": "0.1.3-alpha.3",
96
+ "@aotter/mantle-bun": "0.1.3-alpha.3",
97
+ "@aotter/mantle-admin-ui": "0.1.3-alpha.3",
98
+ "@aotter/mantle-cloudflare": "0.1.3-alpha.3",
99
+ "@aotter/mantle-auth": "0.1.3-alpha.3",
100
+ "@aotter/mantle-web": "0.1.3-alpha.3",
101
+ "@aotter/mantle-vercel": "0.1.3-alpha.3"
102
102
  },
103
103
  "peerDependenciesMeta": {
104
104
  "@aotter/mantle-admin": {
@@ -144,13 +144,13 @@
144
144
  "typescript": "^6.0.3",
145
145
  "vitest": "^4.1.11",
146
146
  "zod": "^4.5.4",
147
- "@aotter/mantle-admin": "0.1.3-alpha.2",
148
- "@aotter/mantle-auth": "0.1.3-alpha.2",
149
- "@aotter/mantle-bun": "0.1.3-alpha.2",
150
- "@aotter/mantle-admin-ui": "0.1.3-alpha.2",
151
- "@aotter/mantle-web": "0.1.3-alpha.2",
152
- "@aotter/mantle-cloudflare": "0.1.3-alpha.2",
153
- "@aotter/mantle-vercel": "0.1.3-alpha.2"
147
+ "@aotter/mantle-admin": "0.1.3-alpha.3",
148
+ "@aotter/mantle-auth": "0.1.3-alpha.3",
149
+ "@aotter/mantle-admin-ui": "0.1.3-alpha.3",
150
+ "@aotter/mantle-bun": "0.1.3-alpha.3",
151
+ "@aotter/mantle-cloudflare": "0.1.3-alpha.3",
152
+ "@aotter/mantle-vercel": "0.1.3-alpha.3",
153
+ "@aotter/mantle-web": "0.1.3-alpha.3"
154
154
  },
155
155
  "engines": {
156
156
  "node": ">=22"
package/skills/README.md CHANGED
@@ -58,26 +58,25 @@ contracts.
58
58
 
59
59
  ## Source-repository marketplace install
60
60
 
61
- Cold start is the pinned install skill. Other marketplace hosts are pointers
62
- to the same pin:
61
+ Cold start is the install skill. Other marketplace hosts are pointers
62
+ to the same entry:
63
63
 
64
64
  ```sh
65
- npx skills add aotter/mantle@v0.1.3-alpha.2 --skill install
65
+ npx skills add aotter/mantle --skill install
66
66
  ```
67
67
 
68
68
  ```bash
69
69
  # Claude Code — two separate prompts
70
- /plugin marketplace add aotter/mantle@v0.1.3-alpha.2
70
+ /plugin marketplace add aotter/mantle
71
71
  /plugin install mantle@mantle
72
72
 
73
73
  # Codex
74
- codex plugin marketplace add aotter/mantle --ref v0.1.3-alpha.2
74
+ codex plugin marketplace add aotter/mantle
75
75
  codex plugin add mantle@mantle
76
76
  ```
77
77
 
78
- Then follow the install skill to the CLI and handbook. Do not use an untagged
79
- `aotter/mantle` marketplace add as the official entry. After packages are
80
- pinned, `mantle skills` projects the installed package's own skills into the
78
+ Then follow the install skill to the CLI and handbook. After packages are
79
+ installed, `mantle skills` projects the installed package's own skills into the
81
80
  project, and `mantle skills --check` fails on drift.
82
81
 
83
82
  Cursor and GitHub Copilot read their manifests from the repository directly.
@@ -97,7 +96,7 @@ package. Two audiences, two artifacts.
97
96
 
98
97
  ## Discoverability
99
98
 
100
- The skills target ADR-0007's "AI as primary author" thesis: agents reach these files by URL when the user invokes them by intent ("install mantle", "develop my Mantle site", "deploy"). Official cold start is `npx skills add aotter/mantle@v0.1.3-alpha.2 --skill install`. Point the agent at tag `v0.1.3-alpha.2` or pass the version-matched markdown content directly.
99
+ The skills target ADR-0007's "AI as primary author" thesis: agents reach these files by URL when the user invokes them by intent ("install mantle", "develop my Mantle site", "deploy"). Official cold start is `npx skills add aotter/mantle --skill install`. Point the agent at the repository or pass the version-matched markdown content directly.
101
100
 
102
101
  ## Conventions
103
102
 
@@ -166,7 +166,9 @@ pnpm exec mantle-harness indexes --require-public --format text
166
166
  The check uses crowded real SQLite and the shipped compiler. It complements
167
167
  `pnpm validate`; it does not replace correctness validation. Declare the
168
168
  smallest ordered index justified by the measured path and respect SQLite's
169
- leftmost-prefix rule. Do not change user-visible filter or ordering semantics
169
+ leftmost-prefix rule; which columns an index may name and how a public View's
170
+ status predicate shapes it is in the handbook (`reference/schema.md#indexes`).
171
+ Do not change user-visible filter or ordering semantics
170
172
  just to make the gate pass. Do not add every permutation or cache every read.
171
173
 
172
174
  For relevant Cloudflare serving changes, start the project and sample the