@aotter/mantle 0.0.11-alpha.63 → 0.0.11-alpha.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +87 -12
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +52 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/generate.d.ts +2 -0
  7. package/dist/generate.d.ts.map +1 -0
  8. package/dist/generate.js +181 -0
  9. package/dist/generate.js.map +1 -0
  10. package/dist/skills.d.ts +2 -0
  11. package/dist/skills.d.ts.map +1 -0
  12. package/dist/skills.js +80 -0
  13. package/dist/skills.js.map +1 -0
  14. package/dist/update.d.ts +2 -0
  15. package/dist/update.d.ts.map +1 -0
  16. package/dist/update.js +387 -0
  17. package/dist/update.js.map +1 -0
  18. package/docs/adr/0001-four-atom-manifest-model.md +6 -7
  19. package/docs/adr/0007-ai-as-primary-author.md +100 -138
  20. package/docs/adr/0008-structured-diagnostic-shape.md +79 -99
  21. package/docs/adr/0009-consumer-supplied-manifests.md +101 -228
  22. package/docs/adr/0012-views-as-public-rest.md +43 -15
  23. package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +43 -17
  24. package/docs/adr/0018-core-starters-repository-boundary.md +155 -0
  25. package/docs/adr/README.md +8 -6
  26. package/docs/cloudflare-low-level-composition.md +94 -0
  27. package/docs/design-atoms.md +59 -57
  28. package/docs/design-references/editorial-blog-2026-05-05.md +7 -7
  29. package/docs/labels.md +1 -1
  30. package/docs/media-uploads.md +1 -1
  31. package/docs/release-process.md +156 -523
  32. package/package.json +9 -6
  33. package/skills/README.md +20 -16
  34. package/skills/develop/SKILL.md +4 -4
  35. package/skills/install/SKILL.md +16 -2
  36. package/skills/plugin/SKILL.md +1 -1
  37. package/skills/provision/SKILL.md +1 -1
  38. package/skills/theme/SKILL.md +12 -10
  39. package/skills/update/SKILL.md +31 -19
  40. package/skills/customize-design/SKILL.md +0 -215
  41. package/skills/extend/SKILL.md +0 -257
@@ -0,0 +1,155 @@
1
+ # ADR-0018: Keep Core and public starters in separate repositories
2
+
3
+ **Status:** Accepted for now; revisit only under the triggers below
4
+
5
+ **Date:** 2026-08-02
6
+
7
+ **Related:** [#542](https://github.com/aotter/mantle/issues/542),
8
+ [#191](https://github.com/aotter/mantle/issues/191),
9
+ [#97](https://github.com/aotter/mantle/issues/97),
10
+ [#99](https://github.com/aotter/mantle/issues/99)
11
+
12
+ ## Context
13
+
14
+ Core and the public starter source currently form a producer-consumer
15
+ boundary:
16
+
17
+ ```text
18
+ Core (SDK producer)
19
+ -> published npm contract
20
+ mantle-starters (external consumer and bundle producer)
21
+ -> immutable provision bundle
22
+ mantle-landing (provisioner)
23
+ ```
24
+
25
+ The repositories were originally split because premium starters needed a
26
+ private ACL. That reason does not determine where public starters must live,
27
+ and the private premium repository remains a stub. A later decision, #191,
28
+ made Core, starters, and landing mirror the same version and connected them
29
+ with an automated release fanout.
30
+
31
+ The fanout accumulated real costs: release-only commits, `main`/`develop`
32
+ backports, fallback tag creation, commit-subject detection, cross-repository
33
+ credentials, and a Core-skill drift check that can skip when the sibling
34
+ checkout is unavailable. Issue #542 therefore proposed moving public starters
35
+ back into this repository.
36
+
37
+ That operational pain does not by itself prove source cohesion. The separate
38
+ starter repository now provides a useful invariant that did not exist in the
39
+ original rationale: the canonical generated-site fixture consumes published
40
+ SDK packages and cannot silently link Core workspace packages.
41
+
42
+ A heuristic review of merged PRs from 2026-06-03 through 2026-08-02 also did
43
+ not show sustained majority co-change. After excluding release, promotion,
44
+ backport, and dependency PRs, 9 of 64 Core PRs explicitly referenced starters,
45
+ and 13 of 79 starter PRs explicitly referenced Core. Explicit references
46
+ undercount forced adaptations, so these numbers are directional rather than a
47
+ permanent threshold. They do show that release noise is not a sufficient proxy
48
+ for product coupling.
49
+
50
+ ## Decision
51
+
52
+ Keep `aotter/mantle` and `aotter/mantle-starters` separate for now.
53
+
54
+ The boundary is a release-contract boundary, not an ACL boundary:
55
+
56
+ - Core produces versioned npm artifacts.
57
+ - Starters validate those artifacts as an external consumer and produce
58
+ immutable provision bundles.
59
+ - Landing consumes the released bundle and provisions end-user repositories.
60
+
61
+ Do not merge or archive the starters repository until the following no-regret
62
+ work has landed:
63
+
64
+ 1. **Pre-publish downstream harness.** Validate starters against the exact SDK
65
+ tarballs that would be published, preferably through an ephemeral registry,
66
+ before a release tag exists.
67
+ 2. **Fail-closed Core-skill drift check.** Compare starter-vendored skills with
68
+ the installed SDK package in normal starter CI; never depend on an optional
69
+ sibling checkout.
70
+ 3. **One release controller.** Replace the bump/tag/dispatch/fallback chain and
71
+ remove avoidable `main`/`develop` backport noise without moving source code.
72
+ 4. **Repository-agnostic updates.** Make generated sites resolve a configured
73
+ bundle base URL instead of hard-coding `aotter/mantle-starters`.
74
+
75
+ Issue #542 remains the work and reconsideration tracker. This ADR is the
76
+ canonical explanation of the repository-boundary decision. The current
77
+ mechanics belong in `docs/release-process.md`; implementation details should
78
+ not be duplicated here.
79
+
80
+ ## Reconsideration triggers
81
+
82
+ Re-evaluate a monorepo after the four items above land if either signal persists:
83
+
84
+ - the release path still needs at least two special-case workaround fixes per
85
+ quarter, such as fallback tags, backports, or commit-message detectors; or
86
+ - SDK contract changes require same-release starter adaptations more often than
87
+ roughly once per month, making atomic cross-repository work a recurring cost.
88
+
89
+ A future merge proposal must also resolve:
90
+
91
+ - how the same tagged starter version is validated against the exact package
92
+ artifacts before npm publication;
93
+ - a real last-legacy-version to first-new-location update for an existing site;
94
+ - landing dispatch and release credentials, which do not disappear merely by
95
+ moving starters;
96
+ - premium-repository direction;
97
+ - nested pnpm/Dependabot/lockfile CI, licensing, open issues, and repository
98
+ history migration.
99
+
100
+ These are decision inputs, not a disguised permanent prohibition. A monorepo
101
+ is appropriate if its atomicity benefit remains material after the release
102
+ machinery is simplified.
103
+
104
+ ## Consequences
105
+
106
+ ### Positive
107
+
108
+ - The published-package consumer guarantee remains structural rather than
109
+ simulated by workspace exclusions and realpath assertions.
110
+ - The four improvements reduce risk and complexity under either eventual
111
+ repository shape.
112
+ - Existing generated-site update URLs remain valid while the updater contract
113
+ is made portable.
114
+ - Starter-only product work keeps an independent source boundary.
115
+
116
+ ### Negative
117
+
118
+ - Cross-repository changes cannot land in one atomic PR.
119
+ - A release event still crosses repository boundaries and needs a narrowly
120
+ scoped credential or GitHub App.
121
+ - Release noise remains until the controller and branch flow are simplified.
122
+ - Core-skill drift remains possible until the fail-closed check lands.
123
+
124
+ ## Alternatives
125
+
126
+ ### Merge immediately
127
+
128
+ Rejected. The proposed release order cannot both start from an immutable tag
129
+ and regenerate a same-version registry-backed starter lockfile after publish.
130
+ Existing generated sites also cannot cross the hard-coded repository boundary
131
+ without a bridge.
132
+
133
+ ### Keep the repositories separate without simplifying the release path
134
+
135
+ Rejected. The current fanout complexity and silent skill-check escape hatch are
136
+ real defects; retaining the boundary does not justify retaining those defects.
137
+
138
+ ### Declare that the repositories must never merge
139
+
140
+ Rejected. The current boundary is valuable, but it is replaceable with explicit
141
+ and tested invariants if future co-change and release evidence justify the cost.
142
+
143
+ ## How to apply
144
+
145
+ - Treat starters as a downstream SDK consumer in CI and release design.
146
+ - Do not add starter projects to the Core pnpm workspace as a shortcut for
147
+ cross-contract validation.
148
+ - Track the four prerequisite changes and future merge evidence in #542.
149
+ - Any future repository-move proposal must supersede this ADR and #191
150
+ explicitly, with the transition tests listed above.
151
+
152
+ ## Implementation status
153
+
154
+ The repository-boundary decision is active. The four no-regret improvements are
155
+ tracked from #542 and may land independently; none requires a repository move.
@@ -8,15 +8,17 @@ Records of *why* mantle ended up shaped this way. The numbering preserves POC AD
8
8
  |---|---|---|
9
9
  | [0001](0001-four-atom-manifest-model.md) | Four-atom manifest model (Schema / View / Procedure / Trigger). Folds POC ADR-0005 (grammar discipline) and POC ADR-0006 (multi-doc YAML). | Accepted (refreshed) |
10
10
  | [0002](0002-closed-enums-for-bindings.md) | Closed enums for `x-mantle-bind` and `ctx.*` predicates. | Accepted (refreshed) |
11
- | [0007](0007-ai-as-primary-author.md) | AI is the primary author of consumer projects; SDK contract is the three feedback loops + structured diagnostics. Folds POC ADR-0013 (role-split surfaces). | Accepted (refreshed) |
12
- | [0008](0008-structured-diagnostic-shape.md) | Diagnostic shape: code, phase, severity, path, value, expected, message, candidates, suggestion. zod-translation per PR #81. | Accepted (refreshed) |
13
- | [0009](0009-consumer-supplied-manifests.md) | Consumers ship their own manifest YAML. SDK parses + caches; never embeds. | Accepted (refreshed) |
11
+ | [0007](0007-ai-as-primary-author.md) | AI is the primary author of consumer projects; SDK contract is three pre-serve feedback loops, runtime diagnostics, and coder/operator role surfaces. | Accepted + amended |
12
+ | [0008](0008-structured-diagnostic-shape.md) | Diagnostic shape for validate/boot/runtime failures, with a reserved consumer-test phase; measured harnesses keep purpose-shaped reports. | Accepted + amended |
13
+ | [0009](0009-consumer-supplied-manifests.md) | Consumers own manifest YAML; the installed CLI emits the parser-free runtime module and handler types. Core ships no application manifests. | Accepted + amended |
14
14
  | [0010](0010-locale-and-translates.md) | Locale 3-layer (manifest / D1 site_config / data field) + translates pattern. Boot decoupled from `site_config` (issue #60 fix). | Accepted (refreshed) |
15
15
  | [0011](0011-adapter-port-spec.md) | Adapter port spec. Required runtime ports plus optional feature ports. CF impl + Netlify stub. | Accepted (new) |
16
- | [0012](0012-views-as-public-rest.md) | Views auto-expose `GET /api/views/<name>` as the public REST read surface. Schemas never get a public REST endpoint. Filter comparison values accept `{ $param: <name> }`; `?page=&show=` reserved for pagination. | Accepted (new) |
16
+ | [0012](0012-views-as-public-rest.md) | Views auto-expose matching REST and `query_view_*` MCP reads on their declared `public` or `staff` surface. Schemas never get a public REST endpoint. | Accepted + amended |
17
17
  | [0013](0013-agent-provisioned-consumer-projects.md) | Historical agent-provisioned consumer projects path. Superseded for first launch by landing provision bundles. | Superseded |
18
- | [0014](0014-auth-better-auth-and-multi-tenant-mcp.md) | Better Auth for staff sign-in (D1 session); the MCP OAuth surface carves out to `@cloudflare/workers-oauth-provider` (KV grant store) at top level. The two meet at `/oauth/authorize` where the consent handler reads the Better Auth session. MCP splits into `/mcp/staff` (write, admin-role) and `/mcp` (read, any signed-in). Scope advertised as `["mcp"]` (single non-colon) because claude.ai rejects colon-shaped scopes. Auth port disappears; runtime takes Better Auth instance directly. See § "Amendment 2026-05-15". | Accepted + amended |
18
+ | [0014](0014-auth-better-auth-and-multi-tenant-mcp.md) | The Cloudflare adapter owns the curated Better Auth identity/session facade and top-level `@cloudflare/workers-oauth-provider` MCP transport. Both normalize verified callers into runtime context; mutable staff role and target authorization are re-evaluated per call. | Accepted + amended |
19
19
  | [0016](0016-site-semantic-layer.md) | Site semantic layer: `AGENTS.md` (cross-tool entry) + `.mantle/launch-state.json` (deterministic install context). The older `mantle/site.md` letter surface is suspended from first-run scaffolds. | Accepted (slimmed) |
20
+ | [0017](0017-media-multi-variant-agent-side-optimization.md) | Multi-variant media assets with agent-side optimization and asset-id entry references. | Accepted |
21
+ | [0018](0018-core-starters-repository-boundary.md) | Core produces published SDK artifacts; the separate starters repository validates them as an external consumer. Revisit after release-contract simplification. | Accepted for now |
20
22
 
21
23
  ## Reading order
22
24
 
@@ -50,7 +52,7 @@ The rebuild's ADR-0011 (new) is the most load-bearing addition — the POC accum
50
52
 
51
53
  ## Contributing a new ADR
52
54
 
53
- 1. Pick the next number (currently 0017).
55
+ 1. Pick the next number (currently 0019).
54
56
  2. File: `docs/adr/<NNNN>-<kebab-title>.md`.
55
57
  3. Sections: Status, Date, Context, Decision, Consequences, Alternatives, How to apply, Implementation status.
56
58
  4. Link from this README's table.
@@ -0,0 +1,94 @@
1
+ # Low-level Cloudflare Worker composition
2
+
3
+ Use `createMantleWorker` unless the application must own the top-level Worker
4
+ assembly. This copyable fixture keeps Mantle's standard bindings, Auth,
5
+ Admin/API routes, OAuth/MCP dispatch, cache policy and redacted error boundary,
6
+ while adding one application-owned post-response Queue audit across every route.
7
+
8
+ ```ts
9
+ import { Hono } from "hono";
10
+ import {
11
+ createCmsRef,
12
+ createConventionalAuth,
13
+ createConventionalBindings,
14
+ createMcpApiHandler,
15
+ createOAuthProvider,
16
+ mountAuthorize,
17
+ mountServerEndpoints,
18
+ runMantleWorkerRequest,
19
+ setupIncompleteAuthResponse,
20
+ type MantleCloudflareEnv,
21
+ } from "@aotter/mantle/cloudflare";
22
+ import { manifest } from "../.mantle/generated/site.js";
23
+
24
+ interface Env extends MantleCloudflareEnv {
25
+ readonly AUDIT_QUEUE: Queue<{
26
+ readonly kind: "request-complete";
27
+ readonly path: string;
28
+ readonly status: number;
29
+ }>;
30
+ }
31
+
32
+ let assembled: ReturnType<typeof assemble> | undefined;
33
+
34
+ export default {
35
+ fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
36
+ return runMantleWorkerRequest(async () => {
37
+ assembled ??= assemble(env);
38
+ const incomplete = await setupIncompleteAuthResponse(request, assembled.auth);
39
+ const response = incomplete ?? await assembled.fetch(request, env, ctx);
40
+ ctx.waitUntil(env.AUDIT_QUEUE.send({
41
+ kind: "request-complete",
42
+ path: new URL(request.url).pathname,
43
+ status: response.status,
44
+ }));
45
+ return response;
46
+ });
47
+ },
48
+ } satisfies ExportedHandler<Env>;
49
+
50
+ function assemble(env: Env) {
51
+ const bindings = createConventionalBindings(env);
52
+ const auth = createConventionalAuth(env);
53
+ const ref = createCmsRef({ manifests: manifest, bindings, auth });
54
+ const app = new Hono<{ Bindings: Env }>();
55
+
56
+ mountServerEndpoints(app, ref);
57
+ mountAuthorize(app, { auth, loginPath: "/admin/sign-in" });
58
+ app.get("/cache-probe", () => new Response("public", {
59
+ headers: { "cache-control": "public, s-maxage=60" },
60
+ }));
61
+
62
+ const provider = createOAuthProvider<Env>({
63
+ defaultHandler: {
64
+ fetch: (request, workerEnv, ctx) => app.fetch(request, workerEnv, ctx),
65
+ },
66
+ apiHandlers: {
67
+ "/mcp/staff": createMcpApiHandler<Env>({ ref, surface: "staff" }),
68
+ "/mcp": createMcpApiHandler<Env>({ ref, surface: "public" }),
69
+ },
70
+ });
71
+ return { auth, fetch: provider.fetch.bind(provider) };
72
+ }
73
+ ```
74
+
75
+ Keep the conventional `DB`, `KV` and `OAUTH_KV` bindings and
76
+ `nodejs_compat`; add the Queue producer in `wrangler.jsonc`:
77
+
78
+ ```jsonc
79
+ {
80
+ "compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
81
+ "queues": {
82
+ "producers": [
83
+ { "binding": "AUDIT_QUEUE", "queue": "my-site-audit" }
84
+ ]
85
+ }
86
+ }
87
+ ```
88
+
89
+ After copying, the Worker entry, request audit, custom route and Queue contract
90
+ belong to the application. Mantle still owns the imported adapters and
91
+ standard route behavior; update them through the package version. Do not copy
92
+ their source or replace Auth, MCP or cache handling locally. A copied
93
+ composition has no automatic merge path back to `createMantleWorker`; keep it
94
+ only while the custom top-level lifecycle remains necessary.
@@ -5,19 +5,19 @@
5
5
  >
6
6
  > **Status**: v0.1 grammar lock. Atoms are shipped; rich sub-spec
7
7
  > grammar (policies, recursive views, temporal predicates, quotas,
8
- > projection triggers, builtin handler ops, lifecycle Triggers) is
8
+ > projection triggers, cron/queue sources, and extended lifecycle hooks) is
9
9
  > reserved as **DRAFT** — see "Future grammar" appendix. Editorial
10
10
  > lifecycle is the one shipped grammar key whose runtime is **deferred
11
- > to v0.1.x**: the boot validator accepts the key shape but rejects
12
- > `lifecycle: editorial` with a clear "v0.1.x" diagnostic until the
13
- > approval-queue runtime lands.
11
+ > to v0.1.x**: parser and boot accept the shape, while
12
+ > `request_publish` rejects it with `LIFECYCLE_NOT_IN_V010` until the approval
13
+ > queue lands. Do not use editorial for a v0.1 publishing workflow.
14
14
  >
15
15
  > **This is the reference manual** — what the system is. For *why* it
16
16
  > ended up this shape (alternatives considered, trade-offs accepted),
17
17
  > see the Architecture Decision Records under [`docs/adr/`](adr/README.md).
18
18
  > For the SDK's contract with its primary author (CLI feedback loops,
19
- > error catalog, test recipes), see
20
- > [`docs/authoring-contract.md`](authoring-contract.md).
19
+ > structured diagnostics, deterministic authoring), see
20
+ > [ADR-0007](adr/0007-ai-as-primary-author.md).
21
21
 
22
22
  ## TL;DR
23
23
 
@@ -31,7 +31,7 @@ primitives Postgres has shipped for 30 years.
31
31
  | Our atom | Postgres equivalent | Externally exposed by itself? | Has user code? |
32
32
  |---|---|---|---|
33
33
  | **`Schema`** | `CREATE TABLE` | no (manipulated via View / Procedure) | no |
34
- | **`View`** | `CREATE VIEW` | **yes** (auto-mounted at `GET /api/views/<name>` — `SELECT FROM` analogue, see ADR-0012) | no |
34
+ | **`View`** | `CREATE VIEW` | **yes** (auto-mounted on its declared public/staff REST and MCP surface; see ADR-0012) | no |
35
35
  | **`Procedure`** | `CREATE FUNCTION ... LANGUAGE plpgsql` | **no** (transport-agnostic; needs a `Trigger` to bind it) | **yes — handler ref to consumer's TS file** |
36
36
  | **`Trigger`** | `CREATE TRIGGER` + `pg_cron` + PostgREST route + `LISTEN/NOTIFY` | yes (the binding atom — turns Procedures into HTTP endpoints, cron jobs, MCP tools, lifecycle hooks) | no |
37
37
 
@@ -73,7 +73,7 @@ A logical feature commonly bundles a Procedure + a Trigger (and often a
73
73
  Schema and a View). Put related atoms in one file separated by `---`:
74
74
 
75
75
  ```yaml
76
- # starters/blog/manifests/contact.yaml
76
+ # manifests/contact.yaml
77
77
  apiVersion: cms.mantle.aotter.net/v1
78
78
  kind: Procedure
79
79
  metadata: { name: send-contact-message }
@@ -112,7 +112,7 @@ metadata: { name: posts }
112
112
  spec:
113
113
  title: Posts # required: human-readable label for the admin UI
114
114
  localized: true # opt-in: row carries data.locale (ADR-0010)
115
- lifecycle: simple # v0.1.0 only ships 'simple'; 'editorial' is reserved (see Lifecycle below)
115
+ lifecycle: simple # default; 'none' is operational, 'editorial' is reserved
116
116
  schema:
117
117
  $schema: https://json-schema.org/draft/2020-12/schema
118
118
  type: object
@@ -157,12 +157,11 @@ entry's state machine.
157
157
  in v0.1.0.**
158
158
  - `editorial` — the six-state machine with an approval queue
159
159
  (`draft → review → approved → scheduled → published → archived`,
160
- with `published` returnable to `draft` for republish). **Grammar
161
- key is reserved; the runtime is on the v0.1.x
162
- roadmap.** v0.1.0's boot validator rejects `lifecycle: editorial`
163
- with the diagnostic `LIFECYCLE_NOT_IN_V010` and a message
164
- pointing at the v0.1.x roadmap. Authors should not write
165
- `lifecycle: editorial` in v0.1.0 manifests; it will fail boot.
160
+ with `published` returnable to `draft` for republish). The grammar and
161
+ state-machine vocabulary are reserved for forward compatibility, but the
162
+ approval/request-publish runtime is on the v0.1.x roadmap. In v0.1,
163
+ `request_publish` rejects with `LIFECYCLE_NOT_IN_V010`; do not declare
164
+ editorial for a current publishing workflow.
166
165
  - `none` — **operational records**, not authored content: orders,
167
166
  inventory snapshots, grant/audit rows — anything written by
168
167
  Procedures as a side effect rather than drafted by a person. No
@@ -291,12 +290,14 @@ appendix.
291
290
  **Postgres analogue**: `CREATE TABLE posts (id UUID PRIMARY KEY, ...,
292
291
  UNIQUE (slug, locale));`
293
292
 
294
- ### 2. `View` — the read surface (auto-exposed)
293
+ ### 2. `View` — the read surface (auto-exposed by surface)
295
294
 
296
- A named, declarative read over Schemas. **Auto-mounted** at
297
- `GET /api/views/<name>` by the SDK — no Trigger required, just like
298
- `SELECT FROM view_name` in Postgres works without a separate route
299
- declaration. See ADR-0012 for the full design rationale.
295
+ A named, declarative read over Schemas. No Trigger is required. A View with
296
+ no `spec.surface` (or `surface: public`) mounts at
297
+ `GET /api/views/<name>` and becomes `query_view_<name>` on `/mcp`.
298
+ `surface: staff` instead mounts at `GET /admin/api/views/<name>` behind the
299
+ staff gate and appears only on `/mcp/staff`. See ADR-0012 for the full design
300
+ rationale.
300
301
 
301
302
  ```yaml
302
303
  apiVersion: cms.mantle.aotter.net/v1
@@ -334,7 +335,7 @@ spec:
334
335
  limit: 100
335
336
  ```
336
337
 
337
- Public callers paginate via reserved query-string knobs `?page=&show=`
338
+ REST callers paginate via reserved query-string knobs `?page=&show=`
338
339
  (1-indexed page, server caps `show` at `View.spec.limit`). Reserved
339
340
  names — `page` / `show` / `cursor` — must NOT appear in
340
341
  `spec.params.properties` (the parser rejects with
@@ -405,9 +406,12 @@ spec:
405
406
  ```
406
407
 
407
408
  ```ts
408
- // consumer's TS at boot
409
+ // src/mantle/config.ts
409
410
  import { sendContactMessage } from "./handlers/send-contact-message";
410
- sdk.registerHandler("send-contact-message", sendContactMessage);
411
+
412
+ export const handlers = {
413
+ "send-contact-message": sendContactMessage,
414
+ };
411
415
  ```
412
416
 
413
417
  **v0.1 `requires.auth`**: `{ all: [<predicate>] }` only. Predicates:
@@ -442,11 +446,10 @@ the consumer guard handler, not in a new atom or Core repository. See
442
446
 
443
447
  **v0.1.0 `handler.kind`**: `ref` (author-supplied function) or
444
448
  `builtin` (SDK-supplied CRUD shortcut). For `builtin`, declare
445
- `op: <create | update | upsert | delete>` and `schema: <Schema name>`
446
- in place of `ref`. The runtime dispatch path is implemented by
447
- `InvokeBuiltinUseCase`; the feature-named diagnostic remains as a
448
- defense-in-depth guard if a builtin Procedure reaches an unsupported
449
- runtime path.
449
+ `op: <create | update | upsert | delete | archive>` and
450
+ `schema: <Schema name>` in place of `ref`. The runtime dispatch path is
451
+ implemented by `InvokeBuiltinUseCase`; parser and boot validation fail closed
452
+ on unknown ops, Schemas, or incompatible lifecycle use.
450
453
 
451
454
  **Postgres analogue**: `CREATE FUNCTION send_contact_message(input
452
455
  JSONB) RETURNS JSONB LANGUAGE plpgsql AS $$ ... $$;`. PG functions are
@@ -467,7 +470,7 @@ kind: Trigger
467
470
  metadata: { name: contact-http }
468
471
  spec:
469
472
  source:
470
- kind: http # v0.1 ONLY supports http source
473
+ kind: http # v0.1 also supports mcp and lifecycle
471
474
  method: POST # POST | PUT | PATCH | DELETE
472
475
  path: /api/contact # OpenAPI {param} syntax for path params
473
476
  # path params auto-bind to identically-named input fields
@@ -484,7 +487,7 @@ shared.
484
487
  **v0.1 `Trigger.source.kind`**: `http` (public endpoint), `mcp` (named
485
488
  tool on `surface: public | staff`), or `lifecycle` (entry-writer hook). For `lifecycle`, declare `schema`,
486
489
  `on: [<hook>, ...]` from `LifecycleHook`, and optional `errorPolicy`
487
- (`abort` rejects only on `before_*` hooks; `continue` is the default).
490
+ (`abort` is the `before_*` default; `continue` is the `after_*` default).
488
491
  Lifecycle hooks are wired through `LifecycleHookingEntryRepository`, so
489
492
  MCP, admin, and builtin write paths share the same hook behavior.
490
493
 
@@ -494,8 +497,8 @@ MCP, admin, and builtin write paths share the same hook behavior.
494
497
  The state-machine "lifecycle" from the Schema atom
495
498
  (`Schema.spec.lifecycle: simple | editorial`) is a separate domain
496
499
  that shares the word. The Schema setting governs which states an
497
- entry can be in; lifecycle Triggers (when they ship; see Future
498
- grammar) govern what fires around mutations.
500
+ entry can be in; shipped lifecycle Triggers govern what fires around
501
+ mutations.
499
502
 
500
503
  **Postgres analogue**: `CREATE TRIGGER ... AFTER INSERT ON posts
501
504
  EXECUTE FUNCTION ...` (lifecycle); `pg_cron` extension (cron); plus
@@ -771,10 +774,10 @@ narrow at v0.1.0 and grows in two tiers:
771
774
  1. **v0.1.0 shipped** — grammar parses and runtime behavior is wired
772
775
  in the current rebuild.
773
776
  2. **v0.1.x committed** — on the patch-release roadmap. Spec is
774
- documented; implementation lands within the v0.1 series. Boot
775
- validator rejects these keys with a code naming the feature.
777
+ documented; implementation lands within the v0.1 series. The unsupported
778
+ runtime path fails closed with a code naming the feature.
776
779
  3. **DRAFT (v0.2+)** — speculative, gated by concrete consumer
777
- demand. Boot validator rejects with `DRAFT_KEY_USED`. May or may
780
+ demand. Parser/static validation rejects with `DRAFT_KEY_USED`. May or may
778
781
  not ship — depends on whether real use cases apply pressure.
779
782
 
780
783
  ### v0.1.0 shipped
@@ -790,7 +793,7 @@ Full shape lives further down.
790
793
 
791
794
  Grammar lives in v0.1.0. Runtime is the
792
795
  `InvokeBuiltinUseCase` that dispatches `op: create | update | upsert
793
- | delete` against the entry-writer chokepoint with `x-mantle-bind`
796
+ | delete | archive` against the entry-writer chokepoint with `x-mantle-bind`
794
797
  stamping and `input ∩ Schema.properties` projection. Full shape lives
795
798
  further down.
796
799
 
@@ -801,19 +804,18 @@ surface. Procedures/Views share `ctx.auth`/scope predicates and optional guard
801
804
  orchestration across REST and MCP. Staff role is loaded live for each protected
802
805
  call; staff Views remain absent and un-callable on public MCP.
803
806
 
804
- ### v0.1.x committed
807
+ ### Detailed shipped grammar and v0.1.x reservation
805
808
 
806
809
  > The `handler.kind: builtin` and `Trigger.source.kind: lifecycle`
807
- > sections below now describe shipped v0.1.0 behavior. The
808
- > `Schema.spec.lifecycle: editorial` subsection remains v0.1.x-
809
- > committed proper.
810
+ > sections below describe shipped v0.1.0 behavior. Only the
811
+ > `Schema.spec.lifecycle: editorial` subsection remains v0.1.x-committed.
810
812
 
811
813
  #### `Schema.spec.lifecycle: editorial` runtime
812
814
 
813
- Grammar key already accepted in v0.1.0 (writes parse) but the boot
814
- validator emits `LIFECYCLE_NOT_IN_V010` because the approval-queue
815
- runtime ships in v0.1.x. When v0.1.x lands, the same manifest
816
- deploys without changes — that's why the key is reserved now.
815
+ Grammar and boot already accept the key, but `request_publish` emits
816
+ `LIFECYCLE_NOT_IN_V010` because the approval-queue runtime ships in v0.1.x.
817
+ When that runtime lands, the same manifest can use the publish workflow without
818
+ a grammar change.
817
819
 
818
820
  #### `handler.kind: builtin` — thin shortcut over the storage adapter for trivial CRUD-shaped Procedures
819
821
 
@@ -832,11 +834,11 @@ spec:
832
834
 
833
835
  | op | Behavior |
834
836
  |---|---|
835
- | `create` | INSERT a new row. Project `input ∩ Schema.spec.schema.properties`; stamp `x-mantle-bind` fields; status='draft'; generated id. |
837
+ | `create` | INSERT a new row. Project `input ∩ Schema.spec.schema.properties`; stamp `x-mantle-bind` fields; generated id; status is `draft`, or immediately `published` for `lifecycle: none`. |
836
838
  | `update` | UPDATE in place. `input.id` + `input.expectedVersion` (OCC) required. Bumps version. |
837
839
  | `upsert` | If `input.id` resolves, behaves as `update`; else as `create`. |
838
840
  | `delete` | Hard DELETE by id. |
839
- | `archive` | Soft-archive (status='archived'). Editorial-lifecycle Schemas only; on `simple` Schemas this is a parse error. (Editorial runtime ships in v0.1.x — `archive` becomes available the same release.) |
841
+ | `archive` | Soft-archive (status='archived'). The manifest validator permits this builtin only for `editorial` Schemas; the archive transition itself is runtime-wired, while editorial approval/request-publish remains deferred. |
840
842
 
841
843
  The Procedure's `input` is the contract with the *caller*. It MAY
842
844
  declare fields the Schema does not (e.g. a Turnstile token). The
@@ -876,8 +878,8 @@ spec:
876
878
  | `after_update` | After UPDATE. Default best-effort. |
877
879
  | `before_delete` | Before DELETE. Throw cancels. |
878
880
  | `after_delete` | After DELETE. Default best-effort. |
879
- | `before_publish` | Before status flips to `published`. Editorial Schemas only. |
880
- | `after_publish` | After status flips to `published`. Editorial Schemas only. |
881
+ | `before_publish` | Before any supported status transition to `published` (the shipped workflow is `simple`). |
882
+ | `after_publish` | After any supported status transition to `published` (the shipped workflow is `simple`). |
881
883
 
882
884
  **Atomicity defaults by phase**:
883
885
  - `before_*`: `errorPolicy: abort`. Handler throw cancels the
@@ -921,15 +923,15 @@ Cloudflare wiring, the 128 KB platform limit, retry/DLQ configuration,
921
923
  idempotency, and legacy-envelope draining are specified in
922
924
  [Deferred lifecycle hooks on Cloudflare Queues](deferred-lifecycle-queues.md).
923
925
 
924
- **Editorial-lifecycle hooks** (`before_publish`, `after_publish`)
925
- depend on the `lifecycle: editorial` runtime, which ships in the
926
- same v0.1.x cut.
926
+ `before_publish` and `after_publish` already wrap the shipped simple publish
927
+ transition. When editorial approval lands, the same hooks wrap its final
928
+ transition to `published`; no second hook grammar is planned.
927
929
 
928
930
  ### DRAFT (v0.2+, speculative)
929
931
 
930
932
  Each item below lands when the first concrete real-world use case
931
- forces it, not on speculation. Today, do not implement; the boot
932
- validator rejects with `DRAFT_KEY_USED`.
933
+ forces it, not on speculation. Today, do not implement; parser/static
934
+ validation rejects it with `DRAFT_KEY_USED`.
933
935
 
934
936
  #### Schema future
935
937
  - **`x-mantle-ref` auto-lift to virtual column** — when a property
@@ -983,13 +985,13 @@ validator rejects with `DRAFT_KEY_USED`.
983
985
  #### Trigger future
984
986
  - **`source.kind: cron`** with `expr:` — scheduled invocation.
985
987
  - **`source.kind: queue`** — async fan-out / message-driven invocation.
986
- - **`source.kind: lifecycle.foo`** — DRAFT extensions to the v0.1.x
988
+ - **`source.kind: lifecycle.foo`** — DRAFT extensions to the shipped
987
989
  lifecycle hooks (e.g. `before_archive`, `after_request_publish`).
988
- The 8 hooks listed in the v0.1.x committed section are the floor,
990
+ The 8 hooks listed in the detailed shipped section are the floor,
989
991
  not the ceiling.
990
992
 
991
- (Full lifecycle Trigger spec for the 8 v0.1.x-committed hooks lives
992
- in the v0.1.x committed section above. The remaining DRAFT items
993
+ (Full lifecycle Trigger spec for the 8 shipped hooks lives
994
+ in the detailed section above. The remaining DRAFT items
993
995
  below are the speculative v0.2+ shapes that haven't yet been promoted
994
996
  to a committed roadmap.)
995
997
 
@@ -5,9 +5,9 @@ This document preserves the visual system from the retired
5
5
  it looked like a maintained template, but the design work is still useful as a
6
6
  reference for future themes.
7
7
 
8
- Use this as a design specimen, not as implementation guidance. Current starters
9
- should keep using the maintained theme stack in `starters/blog/src/theme.default`
10
- and `starters/blog/src/theme`.
8
+ Use this as a design specimen, not as implementation guidance. Apply it through
9
+ the version-matched `mantle:theme` workflow and the generated project's owned
10
+ theme paths, such as `styles/globals.css`, `components/`, and `src/web/`.
11
11
 
12
12
  ## Design Thesis
13
13
 
@@ -209,10 +209,10 @@ will drift from the maintained SDK packages.
209
209
  Revive it as one of these:
210
210
 
211
211
  - A documented visual preset selectable during agent seeding.
212
- - A `theme.default` variant in the maintained blog starter.
212
+ - A project-owned theme preset applied through `mantle:theme`.
213
213
  - A generated design prompt that tells the provisioning agent how to style a
214
214
  user's own brand.
215
215
 
216
- Do not bring it back under `starters/_archive`. The repo should not contain
217
- frozen runnable starters because agents treat runnable code as current product
218
- surface.
216
+ Do not bring it back as a runnable starter in this SDK repository. Agents treat
217
+ runnable code as current product surface, so the preserved design belongs only
218
+ in this reference.
package/docs/labels.md CHANGED
@@ -29,7 +29,7 @@ For package README or package-local docs changes, prefer the package area label
29
29
  | `area:runtime` | `packages/mantle-runtime` behavior, ports, use cases, dispatcher, render, MCP runtime. |
30
30
  | `area:spec` | `packages/mantle-spec`, manifest parsing, validation, diagnostics, CLI, spec types. |
31
31
  | `area:cf` | `packages/adapters/cloudflare`, Workers adapter, D1/KV/ASSETS wiring, Cloudflare deploy behavior. |
32
- | `area:starters` | `starters/*` consumer templates and starter validation. |
32
+ | `area:starters` | External `aotter/mantle-starters` integration, release fanout, and the local moved-starter stub. |
33
33
  | `area:skills` | `skills/*` agent briefs and install/extend/provision workflows. |
34
34
  | `area:admin-ui` | `packages/mantle-admin-ui` React admin SPA. |
35
35
  | `area:docs` | Repo-wide human docs, governance docs, ADR text, release docs, root README content, and cross-cutting documentation work. |
@@ -75,7 +75,7 @@ wrangler secret put R2_ACCESS_KEY_ID
75
75
  wrangler secret put R2_SECRET_ACCESS_KEY
76
76
  ```
77
77
 
78
- ## `src/mantleConfig.ts`
78
+ ## `src/mantle/config.ts`
79
79
 
80
80
  ```ts
81
81
  import { R2MediaStorage, type CmsConfig } from "@aotter/mantle/cloudflare";