@aotter/mantle 0.1.0-alpha.7 → 0.1.0-alpha.8

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 (106) hide show
  1. package/README.md +68 -30
  2. package/dist/admin.d.ts +2 -0
  3. package/dist/admin.d.ts.map +1 -0
  4. package/dist/admin.js +2 -0
  5. package/dist/admin.js.map +1 -0
  6. package/dist/bun.d.ts +2 -0
  7. package/dist/bun.d.ts.map +1 -0
  8. package/dist/bun.js +2 -0
  9. package/dist/bun.js.map +1 -0
  10. package/dist/cli/create.d.ts +2 -0
  11. package/dist/cli/create.d.ts.map +1 -0
  12. package/dist/cli/create.js +243 -0
  13. package/dist/cli/create.js.map +1 -0
  14. package/dist/cli/generate.d.ts.map +1 -0
  15. package/dist/cli/generate.js +101 -0
  16. package/dist/cli/generate.js.map +1 -0
  17. package/dist/cli/harness.d.ts +3 -0
  18. package/dist/cli/harness.d.ts.map +1 -0
  19. package/dist/{harness-cli.js → cli/harness.js} +13 -8
  20. package/dist/cli/harness.js.map +1 -0
  21. package/dist/cli/main.d.ts +3 -0
  22. package/dist/cli/main.d.ts.map +1 -0
  23. package/dist/{cli.js → cli/main.js} +6 -8
  24. package/dist/cli/main.js.map +1 -0
  25. package/dist/cli/skills.d.ts +3 -0
  26. package/dist/cli/skills.d.ts.map +1 -0
  27. package/dist/{skills.js → cli/skills.js} +51 -6
  28. package/dist/cli/skills.js.map +1 -0
  29. package/dist/cli/update.d.ts.map +1 -0
  30. package/dist/{update.js → cli/update.js} +72 -46
  31. package/dist/cli/update.js.map +1 -0
  32. package/dist/codegen/emitMantleModule.d.ts +16 -0
  33. package/dist/codegen/emitMantleModule.d.ts.map +1 -0
  34. package/dist/codegen/emitMantleModule.js +217 -0
  35. package/dist/codegen/emitMantleModule.js.map +1 -0
  36. package/dist/codegen.d.ts +2 -0
  37. package/dist/codegen.d.ts.map +1 -0
  38. package/dist/codegen.js +2 -0
  39. package/dist/codegen.js.map +1 -0
  40. package/dist/provision/renderProvisionBundle.d.ts +70 -0
  41. package/dist/provision/renderProvisionBundle.d.ts.map +1 -0
  42. package/dist/provision/renderProvisionBundle.js +367 -0
  43. package/dist/provision/renderProvisionBundle.js.map +1 -0
  44. package/dist/provision.d.ts +2 -0
  45. package/dist/provision.d.ts.map +1 -0
  46. package/dist/provision.js +2 -0
  47. package/dist/provision.js.map +1 -0
  48. package/dist/vercel-libsql.d.ts +2 -0
  49. package/dist/vercel-libsql.d.ts.map +1 -0
  50. package/dist/vercel-libsql.js +2 -0
  51. package/dist/vercel-libsql.js.map +1 -0
  52. package/dist/vercel.d.ts +2 -0
  53. package/dist/vercel.d.ts.map +1 -0
  54. package/dist/vercel.js +2 -0
  55. package/dist/vercel.js.map +1 -0
  56. package/dist/web.d.ts +2 -0
  57. package/dist/web.d.ts.map +1 -0
  58. package/dist/web.js +2 -0
  59. package/dist/web.js.map +1 -0
  60. package/docs/adapter-guide.md +88 -45
  61. package/docs/adr/0001-four-atom-manifest-model.md +11 -3
  62. package/docs/adr/0007-ai-as-primary-author.md +3 -3
  63. package/docs/adr/0009-consumer-supplied-manifests.md +9 -4
  64. package/docs/adr/0011-adapter-port-spec.md +18 -4
  65. package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +111 -6
  66. package/docs/adr/0018-core-starters-repository-boundary.md +22 -1
  67. package/docs/adr/0019-sealed-manifest-runtime-pipeline.md +204 -0
  68. package/docs/adr/README.md +12 -10
  69. package/docs/api-mcp-authorization.md +21 -42
  70. package/docs/assets/mantle-admin-operations.png +0 -0
  71. package/docs/assets/mantle-hero.jpg +0 -0
  72. package/docs/auth-hosting-model.md +5 -5
  73. package/docs/cloudflare-low-level-composition.md +30 -18
  74. package/docs/deferred-lifecycle-queues.md +20 -38
  75. package/docs/design-atoms.md +31 -133
  76. package/docs/labels.md +1 -1
  77. package/docs/media-uploads.md +2 -2
  78. package/docs/migration-0.1.2.md +45 -0
  79. package/docs/performance-harness.md +4 -4
  80. package/docs/release-process.md +72 -22
  81. package/docs/sealed-pipeline-ownership.md +100 -0
  82. package/package.json +84 -14
  83. package/skills/README.md +38 -7
  84. package/skills/develop/SKILL.md +7 -5
  85. package/skills/install/SKILL.md +26 -25
  86. package/skills/media-gc/SKILL.md +6 -0
  87. package/skills/plugin/SKILL.md +1 -0
  88. package/skills/provision/SKILL.md +2 -0
  89. package/skills/theme/SKILL.md +1 -0
  90. package/skills/update/SKILL.md +1 -0
  91. package/dist/cli.d.ts +0 -3
  92. package/dist/cli.d.ts.map +0 -1
  93. package/dist/cli.js.map +0 -1
  94. package/dist/generate.d.ts.map +0 -1
  95. package/dist/generate.js +0 -226
  96. package/dist/generate.js.map +0 -1
  97. package/dist/harness-cli.d.ts +0 -3
  98. package/dist/harness-cli.d.ts.map +0 -1
  99. package/dist/harness-cli.js.map +0 -1
  100. package/dist/skills.d.ts +0 -2
  101. package/dist/skills.d.ts.map +0 -1
  102. package/dist/skills.js.map +0 -1
  103. package/dist/update.d.ts.map +0 -1
  104. package/dist/update.js.map +0 -1
  105. /package/dist/{generate.d.ts → cli/generate.d.ts} +0 -0
  106. /package/dist/{update.d.ts → cli/update.d.ts} +0 -0
@@ -25,7 +25,7 @@ primitives Postgres has shipped for 30 years.
25
25
  |---|---|---|---|
26
26
  | **`Schema`** | `CREATE TABLE` | no (manipulated via View / Procedure) | no |
27
27
  | **`View`** | `CREATE VIEW` | **yes** (auto-mounted on its declared public/staff REST and MCP surface; see ADR-0012) | no |
28
- | **`Procedure`** | `CREATE FUNCTION ... LANGUAGE plpgsql` | **no** (transport-agnostic; needs a `Trigger` to bind it) | **yes — handler ref to consumer's TS file** |
28
+ | **`Procedure`** | `CREATE FUNCTION ... LANGUAGE plpgsql` | **no** (transport-agnostic; needs a `Trigger` to bind it) | **yes — handler ref to the consumer's registry** |
29
29
  | **`Trigger`** | `CREATE TRIGGER` + route/tool binding | yes (the binding atom — turns Procedures into HTTP endpoints, MCP tools, and lifecycle hooks) | no |
30
30
 
31
31
  The **read/write asymmetry is by design** and matches HTTP safe-vs-unsafe
@@ -45,15 +45,14 @@ these four plus your own TypeScript.
45
45
  apiVersion: cms.mantle.aotter.net/v1
46
46
  kind: Schema | View | Procedure | Trigger
47
47
  metadata:
48
- name: posts # required, kebab-case, unique within deployment
48
+ name: posts # required, non-empty, unique within its kind
49
49
  spec:
50
50
  ... # kind-specific
51
51
  ```
52
52
 
53
- There is **no `namespace` field**. Resource names are unique within the
54
- deployment. K8s-style namespaces are a "premature scale" abstraction for
55
- our scope; SaaS multi-tenancy belongs in the consumer's app layer with a
56
- `tenant_id` column gated by RBAC, not in the SDK metadata layer.
53
+ There is **no manifest `namespace` field**. Resource names are unique within
54
+ their kind, so a Schema and View may share a name. SaaS multi-tenancy belongs
55
+ in the consumer's app layer, not in manifest metadata.
57
56
 
58
57
  ### Multi-doc YAML — keeping file count down
59
58
 
@@ -65,7 +64,7 @@ A logical feature commonly bundles a Procedure + a Trigger (and often a
65
64
  Schema and a View). Put related atoms in one file separated by `---`:
66
65
 
67
66
  ```yaml
68
- # manifests/site.yaml
67
+ # manifests/contact.yml
69
68
  apiVersion: cms.mantle.aotter.net/v1
70
69
  kind: Procedure
71
70
  metadata: { name: send-contact-message }
@@ -85,8 +84,9 @@ spec:
85
84
  target: { procedure: send-contact-message }
86
85
  ```
87
86
 
88
- One fixed file, two documents. Add every atom to `manifests/site.yaml` with
89
- `---`; the loader rejects other manifest filenames.
87
+ Use one file or split features across files. Mantle reads every immediate
88
+ `.yaml` and `.yml` file in `./manifests` by default; `---` separates multiple
89
+ documents in one file.
90
90
 
91
91
  ## What each atom is for (v0.1 minimum essential grammar)
92
92
 
@@ -128,6 +128,10 @@ spec:
128
128
  in the user's primary language (the install-time chosen locale); the
129
129
  SPA shows this everywhere instead of `metadata.name`.
130
130
 
131
+ Schema titles and optional descriptions accept either a string or a locale
132
+ map such as `{ en: Products, "zh-TW": 商品 }`. Procedure titles and
133
+ descriptions and View titles use the same shape; all are optional.
134
+
131
135
  **`spec.searchableFields`** — optional allowlist of top-level string
132
136
  properties used by Admin and Staff MCP substring search. Entry id is always
133
137
  searchable. This is separate from `indexes`; ordinary B-tree indexes do not
@@ -208,9 +212,9 @@ extensions. Three are part of `cms.mantle.aotter.net/v1`:
208
212
  > See ADR-0002 for rationale — why this is a closed enum and not an
209
213
  > open expression language.
210
214
 
211
- Marks a property as **server-controlled**. The SDK fills the value at
212
- write time; the caller MUST NOT supply it (callers who try to set a
213
- bound field get `INPUT_VALIDATION_FAILED`). Eliminates the entire
215
+ Marks a property as **server-controlled**. On create, the SDK overwrites any
216
+ caller value with the bound value; updates preserve the existing stamp. This
217
+ eliminates the entire
214
218
  class of handler-side `userId: ctx.user.id` boilerplate, and gives
215
219
  the dispatcher an authoritative "who/when" tag without trusting the
216
220
  wire.
@@ -221,13 +225,11 @@ wire.
221
225
  |---|---|---|
222
226
  | `ctx.user` | UUID of the signed-in end-user (from session cookie); `null` if anonymous | row ownership: `authorId`, `submittedBy`, `creatorId` |
223
227
  | `ctx.staff` | UUID of the staff member acting (when a staff session is active); `null` for end-user-only paths | audit trail: `approvedBy`, `moderatedBy`, `grantedBy` |
224
- | `now` | Server timestamp at write (ISO-8601 string with timezone) | `createdAt`, `submittedAt`, `grantedAt` |
228
+ | `now` | Server timestamp at write (Unix epoch milliseconds) | `createdAt`, `submittedAt`, `grantedAt` |
225
229
 
226
230
  **v0.1 stamping behavior**:
227
231
  - Stamped on `INSERT` only.
228
- - Caller-supplied value: rejected at input validation. The Procedure's
229
- effective input schema strips bound fields before checking caller
230
- input, so callers can't accidentally send them.
232
+ - Caller-supplied create value: ignored and replaced by the server stamp.
231
233
  - Visible in View output: yes, as ordinary columns. No special masking.
232
234
 
233
235
  **Why a closed enum** (highest-leverage discipline in the spec): bind
@@ -241,9 +243,9 @@ named bounds the semantic surface forever.
241
243
  properties:
242
244
  title: { type: string, minLength: 1, maxLength: 200 }
243
245
  authorId: { type: string, format: uuid, x-mantle-bind: ctx.user }
244
- createdAt: { type: string, format: date-time, x-mantle-bind: now }
246
+ createdAt: { type: integer, x-mcp-hint: timestamp-ms, x-mantle-bind: now }
245
247
  # caller sends only: { title }
246
- # SDK stamps: { title, authorId: <session UUID>, createdAt: <now ISO> }
248
+ # SDK stamps: { title, authorId: <session UUID>, createdAt: <epoch ms> }
247
249
  ```
248
250
 
249
251
  #### `x-mantle-ref: <other-schema-name>` — cross-Schema reference
@@ -582,9 +584,8 @@ That split is intentional and load-bearing.
582
584
  draft-2020-12 JSON Schema documents. This is what AI authors and
583
585
  human authors write, what the admin UI feeds JSON Forms, and what
584
586
  the OpenAPI emitter relays unchanged.
585
- - **Runtime engine** = zod (v3). The `@aotter/mantle-spec`
586
- package ships a JSON-Schema → zod converter
587
- (`src/json-schema-zod.ts`); the runtime calls the converted zod
587
+ - **Runtime engine** = zod 4. The `@aotter/mantle-spec`
588
+ package ships a JSON-Schema → zod converter; the runtime calls the converted zod
588
589
  schema on every Procedure invocation, View parameter parse, and
589
590
  manifest boot check.
590
591
 
@@ -657,119 +658,16 @@ thing as a composition of the four; almost always it works.
657
658
  > fold into JSON Schema inside `Schema` rather than becoming new
658
659
  > kinds.
659
660
 
660
- Postgres exposes ~25 object kinds total; an application developer
661
- typically only writes 4–6 of them. The rest are engine internals that
662
- Cloudflare D1 / KV abstract away.
663
-
664
- ## Storage backend — D1 today, Postgres-via-Hyperdrive tomorrow
665
-
666
- > See the `mantle-cloudflare` README for rationale — why D1 for
667
- > v0.1 (no-CC OSS onboarding, platform-native), and the documented
668
- > PG path.
669
-
670
- ### How `Schema` data is stored on D1 today
671
-
672
- All collections share one `entries` table (defined by the
673
- Cloudflare adapter's storage migrations in
674
- `@aotter/mantle-cloudflare`):
675
-
676
- ```sql
677
- CREATE TABLE entries (
678
- id TEXT PRIMARY KEY,
679
- collection TEXT NOT NULL, -- discriminator: 'posts', 'comments', ...
680
- status TEXT NOT NULL, -- 'draft' / 'published' / ...
681
- version INTEGER NOT NULL,
682
- data TEXT NOT NULL, -- JSON blob: every Schema property (incl. locale, per ADR-0010)
683
- created_at INTEGER NOT NULL,
684
- updated_at INTEGER NOT NULL,
685
- author_id TEXT REFERENCES users(id)
686
- );
687
- ```
661
+ Postgres exposes ~25 object kinds total; an application developer typically
662
+ only writes 4–6 of them. The rest belong behind the selected storage adapter.
688
663
 
689
- Reserved metadata are native columns; **everything in your Schema's
690
- `spec.schema.properties` lives inside the `data` JSON blob**. Locale
691
- on localized Schemas lives at `data.locale` (per ADR-0010); for
692
- indexing it surfaces as a JSON-extracted virtual generated column
693
- (`json_extract(data, '$.locale')`) with a partial unique index, not as
694
- a top-level column.
695
-
696
- `uniqueIndexes` and ordered, non-unique `indexes` declarations compile
697
- into affinity-correct virtual generated columns plus partial B-tree
698
- indexes via the spec engine's DDL emitter (`@aotter/mantle-spec`).
699
- Different collections coexist on the same table without colliding
700
- because each generated-column expression is gated by `WHEN collection
701
- = '<name>'`. See [Schema indexes on D1](schema-indexes.md) for the
702
- grammar, leftmost-prefix rules, SQL helper, and query-plan examples.
703
-
704
- ### What works well on D1
705
-
706
- - D1's SQLite has the **JSON1 extension built in**: `json_extract`,
707
- `json_each`, `json_object`, `json_array`, `json_set`, `json_valid`,
708
- plus `->` / `->>` operators.
709
- - Reserved-column queries (`status = 'published'`, `ORDER BY
710
- updated_at`) use native indexes — fast.
711
- - `uniqueIndexes`- and `indexes`-declared paths use virtual columns +
712
- partial indexes. Core-compiled Views and repository lookups reference
713
- those columns instead of repeating `json_extract`.
714
-
715
- ### Where D1's JSON support has limits (vs Postgres)
716
-
717
- | Concern | D1 / SQLite | Postgres |
718
- |---|---|---|
719
- | JSON storage | TEXT — re-parsed on every `json_extract` | JSONB — stored as compact binary; faster repeated extraction |
720
- | Index any JSON path | must declare each path explicitly via virtual column + index | GIN index on JSONB indexes all paths automatically |
721
- | Array containment | `EXISTS (SELECT 1 FROM json_each(...) WHERE value = ?)` — full scan unless indexed | `data @> ARRAY[x]` with GIN — native operator |
722
- | Path operators | `->`, `->>` only | `->`, `->>`, `#>`, `#>>`, `@>`, `?`, `?|`, `?&` |
723
- | `DEFAULT now()` | not at column level — SDK-stamped via `x-mantle-bind: now` | native column default |
724
- | Foreign key on JSON path | not supported on `VIRTUAL` columns; needs `STORED` (extra space) | not natively, but B-tree on expression OK |
725
- | Row-level security | none — SDK-side filter rewriting | native RLS policies |
726
- | Multi-statement transactions | session API serializes; not true ACID across statements | full ACID transactions |
727
-
728
- ### Practical scale envelope on D1
729
-
730
- - **Blog-scale (< 10k entries / collection)**: today's design is
731
- comfortable. Anonymous public responses can hit version-local Workers
732
- Cache before the Worker; origin misses use indexed D1 reads.
733
- - **Mid-scale (10k – 100k entries)**: declare measured list/filter
734
- access paths with ordered `indexes`.
735
- - **Hard D1 limits**: 1 MB max row size; 5,000 rows per query result;
736
- single-writer per database (concurrent writes serialize).
737
- - **Cross-region read**: D1 is region-pinned; first origin hit from a
738
- far region may pay replica latency. Eligible anonymous responses are
739
- then served by version-local Workers Cache.
740
-
741
- ### Scale-up path: D1 → Postgres via Cloudflare Hyperdrive
742
-
743
- Cloudflare does not run a first-party Postgres service. The supported
744
- path when D1 limits bind is **Cloudflare Hyperdrive** (a connection
745
- pooler + query-cache that lets a Worker connect to any external
746
- Postgres at near-edge latency):
747
-
748
- - **Neon** (serverless Postgres with scale-to-zero) — closest
749
- ergonomic match; tightest CF integration.
750
- - **Supabase** (PG + auth + storage) — works out of the box.
751
- - **AWS RDS / Google Cloud SQL / Azure DB / self-hosted PG** — also
752
- supported via Hyperdrive.
753
-
754
- When this path is taken (post-v0.1; not implemented yet), the
755
- **author-facing YAML stays unchanged**. The SDK's compile target swaps:
756
-
757
- | What changes (SDK runtime) | Author impact |
758
- |---|---|
759
- | `data TEXT` → `data JSONB` | none |
760
- | Virtual generated columns → optional (PG can use GIN) | author may stop declaring `indexes` if defaults suffice; the same YAML remains valid |
761
- | `json_each` → array operators (`@>`, `&&`) | none — predicates stay declarative |
762
- | SDK-stamped `now` → `DEFAULT now()` (optional) | none |
763
- | SDK-side policy rewriting → native RLS (optional) | none |
764
-
765
- Migration shape: `entries` table dumped from D1, restored to PG. Stay
766
- single-table-with-discriminator (drop-in) or split into
767
- table-per-collection (more PG-idiomatic, longer migration). Both shapes
768
- remain valid `cms.mantle.aotter.net/v1` deployments.
769
-
770
- **v0.1 commitment**: D1 only via the Cloudflare adapter. Hyperdrive +
771
- PG is an upgrade route, not an implemented adapter. SDKs and starters target
772
- D1 exclusively.
664
+ ## Storage adapters
665
+
666
+ Manifests compile to semantic storage ports; they do not select a database.
667
+ Use the shared SQLite adapter for SQLite-compatible hosts, or implement the
668
+ same ports over PostgreSQL, MongoDB, or existing application repositories.
669
+ See the [adapter guide](adapter-guide.md); SQLite index lowering is documented
670
+ separately in [Schema indexes](schema-indexes.md).
773
671
 
774
672
  ## Detailed shipped grammar
775
673
 
package/docs/labels.md CHANGED
@@ -26,7 +26,7 @@ For package README or package-local docs changes, prefer the package area label
26
26
 
27
27
  | Label | Meaning |
28
28
  |---|---|
29
- | `area:runtime` | `packages/mantle-runtime` behavior, ports, use cases, dispatcher, render, MCP runtime. |
29
+ | `area:runtime` | `packages/mantle-runtime` behavior, ports, use cases, dispatcher, and 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
32
  | `area:starters` | External `aotter/mantle-starters` integration, release fanout, and the local moved-starter stub. |
@@ -78,7 +78,7 @@ wrangler secret put R2_SECRET_ACCESS_KEY
78
78
  ## `src/mantle/config.ts`
79
79
 
80
80
  ```ts
81
- import { R2MediaStorage, type CmsConfig } from "@aotter/mantle/cloudflare";
81
+ import { R2MediaStorage, type MantleCloudflareConfig } from "@aotter/mantle/cloudflare";
82
82
  import { AwsClient } from "aws4fetch";
83
83
 
84
84
  export interface Env {
@@ -89,7 +89,7 @@ export interface Env {
89
89
  readonly MEDIA_PUBLIC_URL_BASE?: string;
90
90
  }
91
91
 
92
- function buildMediaStorage(env: Env): CmsConfig["bindings"]["mediaStorage"] {
92
+ function buildMediaStorage(env: Env): MantleCloudflareConfig["bindings"]["mediaStorage"] {
93
93
  if (
94
94
  !env.MEDIA_BUCKET ||
95
95
  !env.R2_ACCOUNT_ID ||
@@ -0,0 +1,45 @@
1
+ # Migrating from 0.1.0-alpha.7 to 0.1.2
2
+
3
+ 0.1.2 removes the temporary full-site compatibility stack. Mantle Core is now
4
+ an embeddable parse → link → compile → prepare → bind pipeline; Web, Admin,
5
+ Admin UI, Bun, Vercel, and Cloudflare are selected separately.
6
+
7
+ | alpha.7 | 0.1.2 |
8
+ |---|---|
9
+ | `parseManifests*` | `parseManifestSources({ sources })` |
10
+ | raw `Manifest[]` validation/runtime input | `ParsedManifestSet` → `LinkedManifestSet` → `RuntimePlan` |
11
+ | `createCmsRuntime({ manifests, db })` | `bootMantleRuntime({ plan, storage })`, or explicit prepare then `createMantleRuntime({ prepared })` |
12
+ | `CmsRuntime.db` / `entryReader` | keep the application DB handle; use `runtime.entries` for Mantle reads |
13
+ | generated `manifest`, `site.ts`, `types.d.ts` | generated `plan`, `createMantle`, `bindMantle`, and types in `mantle.ts` |
14
+ | `mantle introspect` | install `@aotter/mantle-spec` directly and run `mantle-spec introspect` |
15
+ | `mantle emit-types` | use `mantle generate`; for raw declarations, run `mantle-spec emit-types` |
16
+ | generated `.agent/skills/` | generated `.agents/skills/`; legacy user files are left untouched |
17
+ | `bindMantleSite` / string-keyed Views | `bindMantle(runtime)` and generated lower-camel properties |
18
+ | `createCmsRef` / `CmsConfig` | `createMantleRuntimeRef` / `MantleCloudflareConfig` |
19
+ | `mountServerEndpoints` | explicitly compose `mountRuntimeEndpoints` and optional `mountAdmin` |
20
+
21
+ Delete stale generated `site.ts` and `types.d.ts` files once, then run
22
+ `mantle generate`. Install only the optional package used by the host;
23
+ installing the umbrella alone now pulls only Spec and Runtime.
24
+
25
+ Intentional behavior changes:
26
+
27
+ - Generated-plan fingerprint or version mismatches fail immediately and ask
28
+ the developer to regenerate.
29
+ - Runtime HTTP trigger JSON bodies must be objects. Arrays and primitives are
30
+ rejected at the request boundary.
31
+ - Malformed percent-encoded paths are routing misses (`404`), not claimed
32
+ Mantle routes.
33
+ - Better Auth and every `@better-auth/*` package move together to 1.7.
34
+ `oauthProvider.validAudiences` becomes protected `resources`; MCP uses one
35
+ canonical `${PUBLIC_ORIGIN}/mcp` resource and CIMD client discovery.
36
+ - The Cloudflare adapter no longer requires `OAUTH_KV` or
37
+ `@cloudflare/workers-oauth-provider`. Old opaque tokens and KV registrations
38
+ cannot be migrated safely and must reconnect.
39
+ - Canonical plan ordering may change stable field/export order (including
40
+ Admin CSV columns) without changing field values.
41
+
42
+ This alpha changes the Better Auth D1 schema, including required account
43
+ issuer identity and OAuth resource/client tables. Reset and re-bootstrap a
44
+ pre-1.7 alpha auth database; do not guess an issuer backfill. Content tables
45
+ remain portable through the normal application migration/export path.
@@ -10,16 +10,16 @@ make a normal content/API/page change.
10
10
  | Read or state | Owner | Notes |
11
11
  |---|---|---|
12
12
  | Entry get/list and public slug/data-field/published reads | `DatabaseEntryRepository` through `EntryRepository` / `EntryReader` | Schema-aware field resolution is shared here. |
13
- | Manifest View execution | `ExecuteViewUseCase` + `ViewSqlCompiler` | The deliberate compiled-query exception; it still resolves declared Schema indexes. |
13
+ | Manifest View execution | `ExecuteViewUseCase` + prepared `ViewQueryExecutor` | Core owns authorization and request validation; selected storage lowers queries once and resolves declared Schema indexes. |
14
14
  | Editable settings and code-owned locale/media policy | `DatabaseSiteConfigRepository` | Editable values and dynamic media tool policy are read fresh; boot-seeded locale policy may be memoized within the runtime instance. |
15
15
  | Pending media uploads | `DatabasePendingUploadRepository` | Canonical, read-after-write D1 state; never publish-cache state. |
16
16
  | Rendered HTML, Markdown, and `llms.txt` | Request-time render use cases plus the Cloudflare public-route cache policy | D1 is canonical; version-local Workers Cache stores anonymous HTTP responses. |
17
17
  | D1 transport and optional query metrics | Cloudflare bindings | Bindings stay thin. Query/cache policy does not belong in a generic provider `BaseRepository`. |
18
18
 
19
- `CmsRuntime.db` remains deprecated compatibility surface. New site code uses
20
- Manifests, runtime use cases, `entryReader`, and `siteConfig`. A site may own
19
+ The runtime exposes no raw database handle. Application and adapter code uses
20
+ the sealed plan, runtime use cases, `entries`, and `siteConfig`. An application may own
21
21
  additional tables behind its own repository at the composition root, but it
22
- must not query Mantle-owned tables through `runtime.db`.
22
+ must not query Mantle-owned tables outside the selected storage adapter.
23
23
 
24
24
  ## Cache contract
25
25
 
@@ -1,8 +1,9 @@
1
1
  # Release process
2
2
 
3
- Mantle is in `0.0.x-alpha` until the v0.1.0 gate closes. Published package
4
- versions, Git tags, GitHub releases, and Starter tags are immutable: repair a
5
- bad release with the next version, never by replacing public state.
3
+ Mantle remains prerelease software until the stable v0.1.0 gate closes.
4
+ Published package versions, Git tags, GitHub releases, and Starter tags are
5
+ immutable: repair a bad release with the next version, never by replacing
6
+ public state.
6
7
 
7
8
  ## Authority
8
9
 
@@ -15,10 +16,12 @@ The controller owns this order:
15
16
  ```text
16
17
  Core source + exact-packed Starter gates
17
18
  -> Core tag
18
- -> npmjs + GitHub Packages
19
+ -> npmjs + GitHub Packages candidate packages (`mantle-release`)
19
20
  -> Starter release worker
20
21
  -> immutable Starter tag
21
22
  -> public-registry Starter gate
23
+ -> clean-create + reviewed Landing compatibility gates
24
+ -> npmjs + GitHub Packages public channel promotion
22
25
  -> Core GitHub Release
23
26
  -> optional Landing worker
24
27
  ```
@@ -45,6 +48,34 @@ verdict expires when that SHA changes. After two patch rounds, a new
45
48
  foundational blocker returns to the state table and the user for a scope
46
49
  decision instead of starting another local redesign loop.
47
50
 
51
+ For the candidate-to-channel transition, the controller follows this finite
52
+ state table:
53
+
54
+ | Durable state | Permitted next mutation | Re-run behavior | Public channel |
55
+ |---|---|---|---|
56
+ | Source gated; version unused | Create the immutable Core tag | Existing tag must match or the run fails | Unchanged |
57
+ | Core tag exists; candidate packages incomplete | Publish and verify missing exact versions under `mantle-release` through each registry's sole publish step | Existing versions are verified and skipped | Unchanged |
58
+ | Candidate packages verified; Starter tag absent | Dispatch the pinned Starter release and wait | The Starter worker resumes or reports its matching no-op | Unchanged |
59
+ | Matching Starter tag exists | Validate its Core/base provenance; run the frozen Starter, clean-create, and Landing compatibility gates | Validation and gates repeat without mutation | Unchanged |
60
+ | All downstream consumer gates pass | Promote npmjs and GitHub Packages channel tags monotonically | Same version is a no-op; an older run preserves a newer tag | Candidate or newer version |
61
+ | Channels promoted or preserved newer | Create the Core GitHub Release | Existing matching release is a no-op | Candidate or newer version |
62
+ | Core GitHub Release exists | Dispatch Landing only when explicitly enabled | Landing remains untouched by default | Candidate or newer version |
63
+
64
+ Invariants:
65
+
66
+ - immutable package versions and Core/Starter tags must keep the requested
67
+ version, Core SHA, and pinned Starter SHA identity;
68
+ - public channel tags cannot move until the released Starter, clean-create, and
69
+ reviewed Landing compatibility gates pass;
70
+ - channel updates use the controller's monotonic promotion boundary, so an
71
+ older re-run cannot move a channel backward;
72
+ - the candidate version may be fetched explicitly or through the temporary
73
+ `mantle-release` tag before promotion, but is not the public channel default.
74
+
75
+ Non-goals: this transition does not change Starter worker ownership, add
76
+ rollback or unpublish behavior, or deploy Landing unless
77
+ `deploy_landing=true`.
78
+
48
79
  ## Branches and channels
49
80
 
50
81
  - Feature and release PRs target `develop`.
@@ -88,18 +119,27 @@ gh api --method POST repos/aotter/mantle/releases/generate-notes \
88
119
  --jq .body
89
120
  ```
90
121
 
91
- The five public packages publish in dependency order:
122
+ The nine public packages publish in dependency order:
92
123
 
93
124
  1. `@aotter/mantle-spec`
94
125
  2. `@aotter/mantle-admin-ui`
95
126
  3. `@aotter/mantle-runtime`
96
- 4. `@aotter/mantle-cloudflare`
97
- 5. `@aotter/mantle`
127
+ 4. `@aotter/mantle-web`
128
+ 5. `@aotter/mantle-admin`
129
+ 6. `@aotter/mantle-bun`
130
+ 7. `@aotter/mantle-vercel`
131
+ 8. `@aotter/mantle-cloudflare`
132
+ 9. `@aotter/mantle`
98
133
 
99
134
  The umbrella package must contain its version-matched `docs/` and `skills/`
100
135
  payload. No tarball may contain `workspace:*` dependencies, secrets, local
101
- state, or workspace-only files. Starter projects are released from the
102
- versioned `mantle-starters` repository; Core does not publish a scaffolder.
136
+ state, or workspace-only files. Starter content is still authored and released
137
+ from the versioned `mantle-starters` repository — Core owns no starter source.
138
+ Core's umbrella CLI is the canonical consumer of that release: `mantle create`
139
+ resolves the official immutable `v${packageVersion}` starter tag and renders it
140
+ through Core's shared provision module. A published Core version
141
+ therefore requires the matching starter tag to exist; see
142
+ [ADR-0018](adr/0018-core-starters-repository-boundary.md).
103
143
 
104
144
  ## Run the controller
105
145
 
@@ -114,16 +154,20 @@ Before creating the Core tag, the controller proves:
114
154
  - the requested version matches every package, plugin, and marketplace ref;
115
155
  - `pnpm check` passes;
116
156
  - packed Core passes in the exact pinned Starter source;
117
- - all five release tarballs exist;
157
+ - all nine release tarballs exist;
118
158
  - npm and cross-repository credentials are present and readable;
119
159
  - a fresh version is unused across npmjs, GitHub Packages, and Starter tags;
120
160
  - the pinned Starter commit is still the remote `develop` tip.
121
161
 
122
- After npm publication, it compares each public registry integrity value with
123
- the locally packed tarball, rejects leaked `workspace:*` dependencies, waits
124
- for the Starter tag, checks that tag's exact Core/base provenance, installs its
125
- frozen locks from the public registry, and reruns the Starter bundle gates.
126
- Only then does it create the Core GitHub Release.
162
+ After candidate publication under `mantle-release`, it compares each public
163
+ registry integrity value with the locally packed tarball, rejects leaked
164
+ `workspace:*` dependencies, waits for the Starter tag, checks that tag's exact
165
+ Core/base provenance, installs its frozen locks from the public registry, and
166
+ reruns the Starter bundle gates. It then creates clean Blank and multilingual
167
+ Transaction projects through the registry candidate, frozen-installs and checks
168
+ both, and runs the reviewed Landing consumer against the exact packed candidate.
169
+ Only then does it promote the public npmjs and GitHub Packages channel tags and
170
+ create the Core GitHub Release.
127
171
 
128
172
  ## Idempotency and recovery
129
173
 
@@ -148,7 +192,7 @@ Core repository secrets:
148
192
 
149
193
  | Secret | Minimum purpose |
150
194
  |---|---|
151
- | `NPM_TOKEN` | Publish the five `@aotter/*` packages on npmjs. |
195
+ | `NPM_TOKEN` | Publish the nine `@aotter/*` packages on npmjs. |
152
196
  | `RELEASE_FANOUT_TOKEN` | Read and dispatch `aotter/mantle-starters`; also read and dispatch `aotter/mantle-landing` only when Landing is enabled. |
153
197
 
154
198
  Core's job-scoped `GITHUB_TOKEN` creates the Core tag and release and mirrors
@@ -171,18 +215,24 @@ for p in \
171
215
  @aotter/mantle-spec \
172
216
  @aotter/mantle-admin-ui \
173
217
  @aotter/mantle-runtime \
218
+ @aotter/mantle-web \
219
+ @aotter/mantle-admin \
220
+ @aotter/mantle-bun \
221
+ @aotter/mantle-vercel \
174
222
  @aotter/mantle-cloudflare \
175
223
  @aotter/mantle; do
176
224
  npm view "$p@X.Y.Z" version dist.integrity dependencies --json
177
225
  done
178
226
  ```
179
227
 
180
- Clone the exact Starter tag into a fresh directory, install with frozen locks,
181
- run its bundle gates, and materialize at least one typed project. Confirm the
182
- generated project contains version-matched repo-local Mantle skills and the
183
- expected typed runtime surface. `blank` remains headless and contains no Kiwa;
184
- a typed Starter revision may retain its replaceable offline UI palette, but
185
- runtime code must not import it.
228
+ The controller already creates and checks clean Blank and multilingual
229
+ Transaction projects. For 0.1.2 release acceptance, give a coding agent with no
230
+ Mantle checkout or repository knowledge only the generated instructions and
231
+ confirm it reaches a running Worker. This is one manual clean-room acceptance,
232
+ not a nondeterministic CI framework. Confirm the generated project contains
233
+ version-matched repo-local Mantle skills and the expected typed runtime surface.
234
+ `blank` remains headless and contains no Kiwa; a typed Starter revision may
235
+ retain its replaceable offline UI palette, but runtime code must not import it.
186
236
 
187
237
  If `deploy_landing=false`, also verify that no Landing release dispatch or
188
238
  deployment was started.
@@ -0,0 +1,100 @@
1
+ # Sealed pipeline ownership ledger
2
+
3
+ This is the migration evidence for ADR-0019 and epic #656. It records the
4
+ `v0.1.0-alpha.7` owners before code moves, the single target owner, and the
5
+ issue that must delete or delegate the old path.
6
+
7
+ ## Rule ownership
8
+
9
+ | Behavior | Current owner(s) | Target owner | Migration |
10
+ |---|---|---|---|
11
+ | YAML document decode, empty documents, syntax errors, alias cap | `ManifestParser.parseOneStream` | Parse/decode in `mantle-spec` | #663 |
12
+ | Four-atom discrimination, envelope keys, primitive/enum shape | `ManifestParser` | Parse/normalize | #663 |
13
+ | Source identity and document location | Synthetic parser doc index plus `loadManifests`/`ManifestPathDiagnoser` reconstruction | `ManifestSourceSet` authored metadata | #663 |
14
+ | Schema atom-local shape, index/search/UI shape | Parser plus `ValidateManifestsUseCase` checkers | Parse/normalize | #663; old checks deleted in #673 |
15
+ | Defaults for localized/lifecycle/indexes/search/order | `IntrospectManifestsUseCase`, runtime lifecycle checks, and callers using `??` | Parse/normalize exactly once | #663, #667 |
16
+ | Duplicate names | `ValidateManifestsUseCase` | Link | #664 |
17
+ | Schema/View/Procedure/Trigger cross-references | `ValidateManifestsUseCase` and repeated boot checks | Link | #664 |
18
+ | Guard existence, self-reference, builtin/chain rules | Validate plus `ValidateBootUseCase` | Link | #664 |
19
+ | Locale/translates graph | `checkLocaleAndTranslates` in validate and boot | Link for manifest graph; Prepare for selected deployment locales | #664, #666 |
20
+ | Manifest-owned HTTP/MCP collisions | Validate plus boot | Link | #664 |
21
+ | Handler source/registry availability | Optional CLI source scan plus boot registry scan | Prepare | #666 |
22
+ | Optional Web/Admin/Auth path reservations | Fixed boot/Cloudflare prefix lists | Prepare selected capabilities only | #666, #669, #670 |
23
+ | Kind/name lookup maps | `ValidateManifestsUseCase`, `ValidateBootUseCase`, `createCmsRuntime`, adapters | Compile once into `RuntimePlan` | #665 |
24
+ | Authorization/guard descriptors | Raw manifests interpreted by validators, runtime use cases, and mounts | Compile plan; evaluate in Core invocation | #665, #667 |
25
+ | Trigger indices and Procedure descriptors | Runtime construction/use cases | Compile plan | #665 |
26
+ | Semantic fingerprint | Runtime boot from raw manifests | Compile plan | #665 |
27
+ | Declarative View resolution | `ViewSqlCompiler` during each `ExecuteViewUseCase` call | Compile logical plan once | #665 |
28
+ | SQLite/JSON1 View lowering and native `spec.sql` | `ViewSqlCompiler` plus `DatabaseDriver` invocation | Selected storage preparation | #666 |
29
+ | Canonical SQL migrations, indexes, schema SQL Views, readiness | `createCmsRuntime.bootInit` and runtime infrastructure | Prepare | #666 |
30
+ | Entry/media/site repositories | Runtime constructs `Database*Repository` from `DatabaseDriver` | Prepared semantic storage ports; media/config move with final owner | #666, #669, #670 |
31
+ | Content, View, Procedure, Trigger, lifecycle invocation | Runtime use cases over raw maps/driver | `MantleRuntime` over plan + semantic ports | #667 |
32
+ | Target authorization/admin bypass | Runtime plus adapter entry points | One Core invocation policy; adapter resolves caller only | #667 |
33
+ | Generated manifest module and TypeScript names | `packages/mantle/src/cli/generate.ts` + `EmitTypesUseCase` | Pure linked/plan projection and `bindMantle` | #668 |
34
+ | Admin asset copy during generation | `packages/mantle/src/cli/generate.ts` | Removed; Admin install/composition owns assets | #668, #670 |
35
+ | HTML/templates/public paths/Markdown/SEO/preview/`llms.txt`/sitemap | Runtime render services plus Cloudflare public routes | Optional `mantle-web` | #669 |
36
+ | Public request mapping and cache policy | `mountPublicRoutes` | Platform adapter using selected Web projection | #669 |
37
+ | Admin API/application/static assets | Optional `mantle-admin`; Cloudflare only binds auth/request context/assets | Optional `mantle-admin` + existing UI artifact | #670 |
38
+ | View/HTTP Trigger route transport | Combined Cloudflare mounts | Platform adapters over the same Core descriptors | #669–#672 |
39
+ | Bun SQLite/process lifecycle | `mantle-bun`; host owns server/database | `mantle-bun`; host owns server/database | #671 |
40
+ | Vercel function/durable-storage lifecycle | `mantle-vercel`; host owns handler/client | `mantle-vercel`; host owns handler/client | #672 |
41
+ | Legacy overloads, aliases, combined mounts, duplicate tests | Current public API plus temporary migration delegates | Deleted | #673 |
42
+ | Contributor/agent/release guidance | `AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md`, two release skill copies | One tool-neutral authority and one release skill | #674 |
43
+
44
+ ## Deletion evidence
45
+
46
+ All baseline production callers now consume `ParsedManifestSet`,
47
+ `LinkedManifestSet`, or `RuntimePlan` according to their pipeline stage.
48
+ `scripts/check-boundaries.mjs` fails if the removed runtime facade, generated
49
+ site API, raw parser facade, or combined Cloudflare route owner returns.
50
+ Parser-focused tests may inspect parser output, but runtime and platform tests
51
+ must compile and prepare the same sealed plan used by production.
52
+
53
+ ## Consumer manifest corpus
54
+
55
+ The Core repository does not vendor every downstream manifest. That would
56
+ create a second source of truth. The reusable golden fixture under
57
+ `packages/mantle-spec/test/fixtures/pipeline-v0.1/` covers every shipped atom,
58
+ auth/guard references, all Trigger surfaces, multi-document YAML, empty
59
+ documents, aliases, unknown keys, malformed YAML, and deterministic
60
+ diagnostics.
61
+
62
+ Exact downstream sources and their gate authorities are pinned here. The public
63
+ Starter runs in Core CI and release preflight. Private consumers run the same
64
+ exact-tarball checker in their own repositories, so public Core PRs never
65
+ receive cross-repository credentials:
66
+
67
+ | Consumer | Revision | Gate authority | Manifest paths |
68
+ |---|---|---|---|
69
+ | `aotter/mantle-starters` | `157e8f49e1e25ae1c52c0115f0dd9e8b6017ef0e` | Core CI + release | `blank/manifests/site.yaml`; `overlays/{community,intake,presence,publication,reservation,transaction}/manifests/site.yaml`; `recipes/typed-web/manifests/site.yaml` |
70
+ | `aotter/mantle-landing` | `4381354dd25d5d94f4096cf3e55a4cb9eecbf3ad` | Landing CI + Core release | `manifests/site.yaml` |
71
+ | `aotter/mantle-platform` (Remote Mantle/control plane) | `3cc0d9ad6ee47b15f06c915c12f56fc4b9143f32` | Platform `exact-packed-core` | `manifests/platform.yaml` |
72
+ | Core i18n fixture | this repository | Core CI | `packages/mantle-spec/test/fixtures/i18n-parent-child/manifests/site.yaml` |
73
+
74
+ ## Baseline gates
75
+
76
+ The alpha.7 baseline was captured from tag `v0.1.0-alpha.7` on 2026-08-16
77
+ with Node 22, pnpm 9, Wrangler local mode, and datasets of 100/10,000 rows:
78
+
79
+ | Route | p95 queries | p95 rows read | local p95 timing |
80
+ |---|---:|---:|---:|
81
+ | public View, 10k rows | 1 | 20 | 3.01 ms |
82
+ | public page miss, 10k rows | 2 | 5 | 3.92 ms |
83
+ | Admin list, 10k rows | 1 | 21–22 | 2.49–3.24 ms |
84
+ | Admin related detail, dense | 4 | 151 | 5.23 ms |
85
+ | public builtin create | 1 | 0 | 2.55 ms |
86
+
87
+ Wall-clock values are evidence, not a cross-machine budget. The normative
88
+ baseline is the existing `pnpm bench:wrangler` query/row gate: every gate must
89
+ remain true, unchanged revisions must perform no repeated preparation, and any
90
+ intentional budget change needs its own reviewed evidence.
91
+
92
+ Existing full-facade behavior is covered by Cloudflare authorization, Admin,
93
+ public-route/cache, View REST, HTTP Trigger, media, form, and facade tests.
94
+ Issues #669–#673 move those assertions to selected modules and exact packed
95
+ consumers; they do not duplicate the suite under new names.
96
+
97
+ Issue #674 leaves `CONTRIBUTING.md` plus accepted ADRs as the contributor
98
+ authority. `AGENTS.md`, `CLAUDE.md`, and the Claude release-skill entry are
99
+ small routers; `.agent/skills/mantle-release/SKILL.md` is the only maintainer
100
+ release procedure. Shipped `skills/*` remain separate consumer artifacts.