@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
@@ -1,554 +1,187 @@
1
1
  # Release process
2
2
 
3
- mantle is in `0.0.x-alpha` until the v0.1.0 release gate closes. The process below documents the branch, tag, GitHub release, and npm publish discipline for prereleases and stable releases.
4
-
5
- ## Branch model
6
-
7
- - `develop` is the integration branch for all PRs.
8
- - `main` is release-only.
9
- - Release PRs merge `develop -> main`.
10
- - Hotfixes branch from `main`, merge back to `main`, tag, then merge or cherry-pick back to `develop`.
11
-
12
- ## Versioning
13
-
14
- - Use semver after v0.1.0.
15
- - Tag format is `vMAJOR.MINOR.PATCH`, for example `v0.1.0`.
16
- - Alpha tags may use prerelease suffixes, for example `v0.0.6-alpha`.
17
- - Package versions and agent plugin manifest versions must stay aligned unless
18
- a future ADR explicitly changes release policy.
19
-
20
- ## Release channels
21
-
22
- ### Alpha
23
-
24
- Alpha releases validate dogfood and integration flows. They may include
25
- new capability, starter changes, and compatibility-breaking pre-v0.1
26
- behavior. Use alpha for official-site dogfood, provision-path testing,
27
- and early consumer projects that can tolerate churn.
28
-
29
- - Version suffix: `-alpha`, e.g. `0.0.7-alpha`.
30
- - Git tag: `v0.0.7-alpha`.
31
- - GitHub Release: mark as prerelease.
32
- - npm dist-tag: `alpha`.
33
- - npm `latest` (pre-v0.1.0 policy): `latest` tracks the most
34
- user-useful pre-release for default `npm install` (no tag) calls.
35
- Concretely: when a beta exists, `latest` follows the most recent
36
- beta; otherwise it follows the most recent alpha. Once v0.1.0
37
- stable ships, `latest` switches to stable-only and never points
38
- at a prerelease again.
39
- - Required before publish: `pnpm run check`, changelog entry (written
40
- at release time, see playbook step 2 below), release PR merged to
41
- `main`, tag pushed.
42
-
43
- ### Beta
44
-
45
- Beta means the v0.1 feature shape is close to complete. New feature work
46
- should be rare and explicitly called out in the release PR.
47
-
48
- - Version suffix: `-beta.N` once needed, e.g. `0.1.0-beta.1`.
49
- - GitHub Release: prerelease.
50
- - npm dist-tag: `beta`.
51
- - Focus: bug fixes, docs, provision UX, migration/upgrade path.
52
-
53
- ### Release candidate
54
-
55
- RC means "could become stable if no blocker appears." Only blocker fixes
56
- should land between RCs.
57
-
58
- - Version suffix: `-rc.N`, e.g. `0.1.0-rc.1`.
59
- - GitHub Release: prerelease.
60
- - npm dist-tag: `rc`.
61
- - Focus: release blockers only.
62
-
63
- ### Stable
64
-
65
- Stable releases use no prerelease suffix.
66
-
67
- - Version: `0.1.0`, `0.1.1`, ...
68
- - Git tag: `v0.1.0`.
69
- - GitHub Release: not prerelease.
70
- - npm dist-tag: `latest`.
71
- - Focus: public install path and supported upgrade story.
72
-
73
- ## Normal release playbook
74
-
75
- The release pipeline is automated end-to-end (#191). Pushing a `v*` tag
76
- fans out: npm publish → starters bump → starters tag → landing bump →
77
- landing deploy. The human steps are:
78
-
79
- 1. Confirm the release scope and blocking issues.
80
- 2. **Write the `CHANGELOG.md` entry for this release.** Per-PR `[Unreleased]` entries are not used (see `CONTRIBUTING.md § Changelog`). Aggregate the merged-since-last-tag commit log into Keep-a-Changelog buckets (`Added` / `Changed` / `Deprecated` / `Removed` / `Fixed` / `Security`), prefix package scope when relevant (`**`@aotter/mantle-runtime`**: ...`), cross-link the closing PR + issue. The entry lives under a new `## [vX.Y.Z] - YYYY-MM-DD` heading directly; no `[Unreleased]` placeholder. Useful command for the aggregation pass:
81
-
82
- ```bash
83
- git log --oneline --no-merges vPREV..HEAD -- ':!CHANGELOG.md' ':!pnpm-lock.yaml'
84
- ```
85
-
86
- 3. **If this release widens any SDK type** (new required field, new closed-enum entry, removed export, broader runtime contract), run the [cross-repo type-shape audit](#cross-repo-type-shape-changes) below BEFORE bumping. CI inside `mantle/` won't catch downstream literal breaks — only the fanout's validate gate will, after publish is irreversible.
87
- 4. Run the full local gate from `develop`:
88
-
89
- ```bash
90
- pnpm run check
91
- ```
92
-
93
- 5. **Pre-v0.1 alpha shortcut**: cut the SDK release directly from `develop` — open a release PR with base=`develop` titled `release: publish alpha.N as pre-v1 latest`, merge with a merge commit, tag the develop merge commit, push the tag. Skip steps 6–8. `mantle/main` updates less frequently than alphas; promotion happens when an alpha graduates to beta/stable. Downstream fanout still promotes `mantle-starters/develop` into `mantle-starters/main` for each alpha so the starter tarball and landing deploy roll forward. (Per practice since alpha.7; see [§ Pre-v0.1 alpha cadence](#pre-v01-alpha-cadence) below.)
94
- 6. **Stable / beta / RC**: open a release PR base=`main`, head=`develop`. The PR title MUST contain the literal substring `release: bump @aotter/mantle* to vX.Y.Z` — that exact phrase is the trigger contract for `mantle-starters/.github/workflows/tag-and-dispatch-landing.yml`'s tag job. Anything else and the landing chain skips tagging.
95
- 7. Review the diff for accidental unreleased work.
96
- 8. Merge with a merge commit.
97
- 9. Tag the merge commit:
98
-
99
- ```bash
100
- git tag v0.1.0
101
- git push origin v0.1.0
102
- ```
103
-
104
- 10. The release fanout takes over (see § Release fanout below). Watch
105
- the Actions tab for the chain; intervene only if a gate fails. See [§ Fix-forward when bump fanout fails](#fix-forward-when-bump-fanout-fails) if validate fails downstream.
106
-
107
- ### Pre-v0.1 alpha cadence
108
-
109
- Through 2026-05-19 (alpha.7, alpha.8, alpha.9), every SDK alpha bump tagged directly from `develop` — no `mantle/develop → mantle/main` promotion. Codex hand-tagged the develop merge commit; `release.yml` doesn't care which branch the tag points at. `mantle/main` updates intentionally lag the alpha cadence so the canonical SDK "released" pointer doesn't churn daily.
110
-
111
- This drops steps 6–9 from the SDK playbook above for pre-v0.1 alphas — the PR base stays `develop`, merge with a merge commit, tag the develop merge commit. The release fanout still fires on the tag push because `release.yml` is keyed on `v*` tags, not branch. The downstream starters fanout opens its release PR against `mantle-starters/main`, using the default-branch `develop` checkout as the head, so starter content and landing can keep auto-publishing per alpha.
112
-
113
- Once v0.1.0 ships, switch to the full `develop → main → tag` flow per the steps above.
114
-
115
- ### Cross-repo type-shape changes
116
-
117
- When a release widens an SDK type, downstream literal constructors in `mantle-starters/` and `mantle-landing/` may break the fanout's validate / typecheck gate AFTER npm publish lands. The npm publish itself succeeds (the SDK code compiles fine in isolation); the breakage surfaces in `bump-from-sdk.yml` and `bump-from-starters.yml`'s gate, by which point a hotfix has to chase the broken alpha.
118
-
119
- Audit checklist for any release that widens a type — run from the workroot containing all three repos:
120
-
121
- ```bash
122
- # Find literal constructors of every SDK-owned type in downstream repos.
123
- # Extend the type list when adding more SDK-owned exported types.
124
- for repo in mantle-starters mantle-landing; do
125
- echo "=== $repo"
126
- git -C "$repo" grep -nE ': (SiteConfig|SiteDefaults|MediaAsset|Entry|Revision)\s*[=:]' -- '*.ts' || true
127
- done
128
- ```
129
-
130
- Concrete examples:
131
-
132
- - Adding a required field to `SiteConfig` (e.g. `media: { purposes }` in v0.0.11-alpha.9): every `SiteConfig` literal in starter `test/fixture/data.ts` + `scripts/seed-initial-content.ts` + landing equivalents needs the new field BEFORE the SDK ships, or downstream bump fails. Fix-forward path described below works but it's bumpy.
133
- - Adding a closed-enum entry: same hazard if downstream `switch` statements are exhaustive.
134
- - Removing an export: starter `import` statements need migration in the same release PR.
135
-
136
- The audit takes ~30 seconds and rules out the most common fanout failure. Do it as part of step 3 above, not after the tag is pushed.
137
-
138
- ### Fix-forward when bump fanout fails
139
-
140
- `bump-from-sdk.yml` / `bump-from-starters.yml` failed at the validate / typecheck gate because the SDK release introduced a code-shape break? Re-firing the workflow won't help — it'll fail the same gate on the same source. The fix-forward path:
141
-
142
- 1. Branch off `develop` (starters) or `main` (landing): `release/vX.Y.Z`.
143
- 2. Replicate what the bump workflow would have done — bump every `@aotter/mantle*` dep + own `version` in package.json files, refresh lockfiles via `pnpm install --no-frozen-lockfile`, and rebuild starter provision bundles when starters source changed.
144
- 3. Add whatever source-code fixes satisfy the new SDK shape.
145
- 4. Commit subject MUST be `release: bump @aotter/mantle* to vX.Y.Z` (starters) or `release: bump @aotter/mantle to vX.Y.Z` (landing) — `tag-and-dispatch-landing.yml` filters on this in starters; landing has no equivalent filter but the convention keeps history consistent.
146
- 5. Open PR base=`main` (starters and landing), CI passes now that lockfile + source are in sync, rebase-merge.
147
- 6. **Immediately open a `chore: backport main→develop` PR** (same repo, base=`develop`, head=`main`). The fast-path commit lives on `main` only — leaving it there silently shifts `main` ahead of `develop` in the touched files. The next `bump-from-sdk.yml` (which opens its release PR from develop→main) then collides on those exact files. alpha.15 hit this in mantle-starters: 13 file conflicts because alpha.14-era #272 follow-ups had been fix-forwarded to main without back-merging. Auto-merge the backport PR; no review gate needed since main is authoritative.
148
- 7. **Don't** re-fire `bump-from-sdk.yml` afterwards — it'll error with `No changes after bump — was the SDK version the same as current?` because your fast-path PR already did the bump. The workflow's only purpose was to produce the same end state your PR did.
149
-
150
- ### Re-spin release for a downstream-content-only fix
151
-
152
- Sometimes the SDK npm artifact is fine but the generated starter provision bundle is broken (e.g. starter content didn't include a freshly-required field at release time). Per `§ Rollback / yanking policy`, the right path is to publish the next alpha as a no-op SDK bump that re-spins the fanout:
153
-
154
- 1. Cut alpha.N+1 in `mantle/` with empty SDK diff (versions + CHANGELOG only).
155
- 2. CHANGELOG entry MUST say explicitly: `No SDK code changes. alpha.N+1 re-spins the release fanout to ship starter content that should have been part of alpha.N (see #XXX).`
156
- 3. Tag + push -> full fanout produces fresh `mantle-starters` provision bundles with the corrected content.
157
-
158
- Don't force-retag the broken alpha. Don't introduce a starter-only sub-tag like `vX.Y.Z-starter.N`. Either breaks the convention that starter version === SDK version.
159
-
160
- ## Release fanout
161
-
162
- `mantle/.github/workflows/release.yml` triggers on `v*` tag push.
163
- The full chain:
164
-
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.
6
+
7
+ ## Authority
8
+
9
+ `.github/workflows/release.yml` is the single release controller. Humans merge
10
+ a reviewed release PR and dispatch that workflow from the merge commit. Humans
11
+ do not push release tags or start downstream release workers directly.
12
+
13
+ The controller owns this order:
14
+
15
+ ```text
16
+ Core source + exact-packed Starter gates
17
+ -> Core tag
18
+ -> npmjs + GitHub Packages
19
+ -> Starter release worker
20
+ -> immutable Starter tag
21
+ -> public-registry Starter gate
22
+ -> Core GitHub Release
23
+ -> optional Landing worker
165
24
  ```
166
- mantle: git push tag v0.0.11-alpha.4
167
- │
168
- ▼
169
- release.yml: pnpm install → build → test (gate) → verify package.json
170
- versions match the tag → pack tarballs → publish/verify
171
- npmjs → mirror GitHub Packages → GitHub release →
172
- repository_dispatch
173
- to mantle-starters
174
- │
175
- ▼
176
- mantle-starters/bump-from-sdk.yml: bump @aotter/mantle* deps
177
- + own version → pnpm install + rebuild bundles →
178
- validate × 5 starters (gate) → typecheck × 5 (gate) →
179
- PR onto main → auto-approve + auto-merge
180
- │
181
- ▼ (after PR merges to main)
182
- mantle-starters/tag-and-dispatch-landing.yml: tag the merge commit
183
- vX.Y.Z → repository_dispatch to mantle-landing
184
- │
185
- ▼
186
- mantle-landing/bump-from-starters.yml: bump @aotter/mantle* dep
187
- + own version (so STARTER_VERSION const updates) →
188
- pnpm install → typecheck (gate) → wrangler dry build (gate) →
189
- PR onto main → auto-approve + auto-merge
190
- │
191
- ▼ (after PR merges to main)
192
- mantle-landing/deploy.yml (existing): wrangler deploy → production
193
- ```
194
-
195
- Every workflow is gated. A failed gate stops the chain at the failing
196
- PR (left open for human triage). Nothing downstream fires until the
197
- upstream PR is fixed + merged.
198
-
199
- ### Operator setup (one-time)
200
-
201
- Repo secrets:
202
-
203
- | Repo | Secret | Purpose |
204
- |---|---|---|
205
- | `aotter/mantle` | `NPM_TOKEN` | npm publish access to `@aotter/*` |
206
- | `aotter/mantle` | `RELEASE_FANOUT_TOKEN` | cross-repo dispatch to `mantle-starters` |
207
- | `aotter/mantle-starters` | `RELEASE_FANOUT_TOKEN` | open release PR + cross-repo dispatch to `mantle-landing` |
208
- | `aotter/mantle-landing` | `RELEASE_FANOUT_TOKEN` | open release PR (deploy is `deploy.yml`'s job) |
209
-
210
- `RELEASE_FANOUT_TOKEN` is the same fine-grained PAT across all three
211
- repos — easier to manage than three separate tokens. Required
212
- permissions: `contents: write`, `pull-requests: write`, `actions: write`
213
- on the three repos. Token expiration policy is whatever you want;
214
- rotate when expired.
215
-
216
- Long-term recommendation: replace the PAT with a GitHub App
217
- (`mantle-release-bot`) installed on the three repos and use
218
- `actions/create-github-app-token` to mint short-lived install
219
- tokens per workflow run. PAT path works for now.
220
-
221
- ### Manual fallback
222
-
223
- If `RELEASE_FANOUT_TOKEN` is missing or a workflow fails partway:
224
-
225
- - npm publish still happens (it doesn't need the fanout token; only
226
- `NPM_TOKEN`)
227
- - Each downstream workflow has a `workflow_dispatch` trigger so the
228
- operator can re-fire it manually from the Actions UI with the
229
- version as input.
230
-
231
- Order of manual re-fires: `mantle-starters/bump-from-sdk.yml` →
232
- (wait for merge) → `mantle-starters/tag-and-dispatch-landing.yml`
233
- (fires automatically on merge) → `mantle-landing/bump-from-starters.yml`
234
- fires automatically via the cross-repo dispatch.
235
-
236
- ### Channel-specific behavior
237
-
238
- `release.yml` infers the npm dist-tag from the version suffix:
239
-
240
- | Tag pushed | npm dist-tag | GitHub release marked |
241
- |---|---|---|
242
- | `v1.2.3` | `latest` | normal release |
243
- | `v0.0.11-alpha.4` | `alpha` | prerelease |
244
- | `v0.1.0-beta.1` | `beta` | prerelease |
245
- | `v0.1.0-rc.1` | `rc` | prerelease |
246
25
 
247
- Without the inference, every prerelease would land on `latest` and
248
- break adopters running `npm install @aotter/mantle` without an
249
- explicit tag. The mapping is in the "Extract version + infer npm tag"
250
- step of `release.yml`.
251
-
252
- ## npm publish
253
-
254
- Published npm packages are the runtime dependency source for
255
- agent-provisioned consumer projects (ADR-0013). Starter files may be
256
- copied from GitHub, but `setup:site` rewrites runtime dependencies to
257
- the selected npm version.
258
-
259
- ### Packages published in alpha
260
-
261
- For `0.0.x-alpha`, publish SDK packages in dependency order:
26
+ The Starter worker owns only its repository transition: it prepares a release
27
+ PR from the exact gated `develop` commit, waits for the named checks, merges
28
+ that checked head atomically into `develop`, and tags the recorded merge
29
+ commit. It does not promote `main`, backport, infer releases from commit text,
30
+ or dispatch Landing.
31
+
32
+ Landing is an explicit controller input and defaults off. A release that keeps
33
+ `deploy_landing=false` does not mutate or deploy Landing.
34
+
35
+ ## Changing release automation
36
+
37
+ Before editing a release workflow, put a finite state table plus its
38
+ invariants and non-goals in a Draft PR. Name the single mutation boundary for
39
+ each external resource; recovery must return through that boundary rather than
40
+ introduce a second writer.
41
+
42
+ Freeze one commit SHA for review. Every finding must name the affected state
43
+ row, a concrete event interleaving, and the wrong mutation it permits. A clean
44
+ verdict expires when that SHA changes. After two patch rounds, a new
45
+ foundational blocker returns to the state table and the user for a scope
46
+ decision instead of starting another local redesign loop.
47
+
48
+ ## Branches and channels
49
+
50
+ - Feature and release PRs target `develop`.
51
+ - Pre-v0.1 alphas release directly from the merged `develop` release commit.
52
+ - Beta, RC, and stable promotion to `main` remains a deliberate human decision;
53
+ it is not part of the alpha controller.
54
+ - Alpha, beta, and RC GitHub releases are prereleases.
55
+ - npm dist-tags follow the suffix: `alpha`, `beta`, `rc`, or `latest` for
56
+ stable versions.
57
+ - During the current `0.0.x-alpha` cadence, `latest` follows the current alpha
58
+ while the `alpha` tag remains available.
59
+
60
+ ## Release PR
61
+
62
+ 1. Fetch Core and Starter remotes and choose the next unused version.
63
+ 2. Read `CHANGELOG.md` completely. Add a dated Keep-a-Changelog entry from the
64
+ merged commits since the previous tag; do not add an `[Unreleased]` bucket.
65
+ 3. Set that exact version in every workspace package and in all four agent
66
+ plugin manifests. Set `.agents/plugins/marketplace.json` to the immutable
67
+ `v<version>` ref.
68
+ 4. Pin both the controller and Core CI to the exact reviewed
69
+ `mantle-starters/develop` commit intended for this release. Do not use a
70
+ branch, latest tag, or inferred fallback.
71
+ 5. If an SDK type changed, audit downstream literal constructors and exhaustive
72
+ switches before publication. CI in Core cannot prove downstream source
73
+ compatibility by itself.
74
+ 6. Run `pnpm check`, inspect the packed umbrella package, and run the exact
75
+ packed-consumer gate. Review and merge a same-repository PR into `develop`;
76
+ the controller rejects a direct-push release commit.
77
+
78
+ The five public packages publish in dependency order:
262
79
 
263
80
  1. `@aotter/mantle-spec`
264
81
  2. `@aotter/mantle-admin-ui`
265
82
  3. `@aotter/mantle-runtime`
266
83
  4. `@aotter/mantle-cloudflare`
267
- 5. `@aotter/mantle` (umbrella — depends on all four above; publish last so its exact-pinned `dependencies` resolve)
268
-
269
- The umbrella is the adopter-facing entry: a single dep, subpath imports
270
- `@aotter/mantle/{spec,runtime,cloudflare,admin-ui}`. Sub-packages
271
- stay individually installable for tooling / alt-adapter authors.
272
-
273
- Do **not** publish starter packages during alpha unless a separate PR
274
- explicitly prepares their package allowlists and verifies the tarballs.
275
- Current starter launch flow is landing-driven: landing fetches generated
276
- `provision-bundles/<type>.json` artifacts from `aotter/mantle-starters`,
277
- commits the user's GitHub repo, and uses npm as the runtime dependency
278
- source.
279
-
280
- Do **not** publish `@aotter/mantle-netlify` while it is a stub.
281
-
282
- Releases on this SDK repo must not attach or publish a separate starter
283
- scaffolder package. Local cold start is owned by the versioned provision
284
- bundles and materializer in `aotter/mantle-starters`.
285
-
286
- `skills/install/SKILL.md` creates or continues a local / landing-generated
287
- project. Human-facing starter bundle details belong in the `mantle-starters`
288
- README, not this SDK repo.
289
-
290
- ### Pre-publish checks
291
-
292
- Run the full gate before publishing:
293
-
294
- ```bash
295
- pnpm run check
296
- ```
297
-
298
- Run `pnpm build` from the workspace root first so `dist/` exists for
299
- every package — `pnpm publish` does NOT run the build lifecycle scripts
300
- by default, and a tarball published without `dist/` is a wasted version
301
- slot (npm forbids republishing the same version, and `npm unpublish`
302
- needs an OTP not always to hand). Then pack + inspect:
303
-
304
- ```bash
305
- pnpm run check # boundary + build + typecheck + test
306
- mkdir -p /tmp/mantle-pack
307
- pnpm -C packages/mantle-spec pack --pack-destination /tmp/mantle-pack
308
- pnpm -C packages/mantle-admin-ui pack --pack-destination /tmp/mantle-pack
309
- pnpm -C packages/mantle-runtime pack --pack-destination /tmp/mantle-pack
310
- pnpm -C packages/adapters/cloudflare pack --pack-destination /tmp/mantle-pack
311
- pnpm -C packages/mantle pack --pack-destination /tmp/mantle-pack
312
- tar tzf /tmp/mantle-pack/aotter-mantle-<ver>.tgz | head # spot-check
313
- ```
314
-
315
- Confirm each tarball contains only intended `dist`, `README.md`,
316
- `LICENSE`, and `package.json` payloads for that package. Do not publish
317
- tarballs containing local state, `.wrangler/`, secrets, fixtures, or
318
- workspace-only artifacts.
319
-
320
- ### Alpha publish command
84
+ 5. `@aotter/mantle`
321
85
 
322
- Publish prerelease packages with the `alpha` dist-tag, in dep order:
86
+ The umbrella package must contain its version-matched `docs/` and `skills/`
87
+ payload. No tarball may contain `workspace:*` dependencies, secrets, local
88
+ state, or workspace-only files. Starter projects are released from the
89
+ versioned `mantle-starters` repository; Core does not publish a scaffolder.
323
90
 
324
- ```bash
325
- pnpm publish --filter @aotter/mantle-spec --no-git-checks --access public
326
- pnpm publish --filter @aotter/mantle-admin-ui --no-git-checks --access public
327
- pnpm publish --filter @aotter/mantle-runtime --no-git-checks --access public
328
- pnpm publish --filter @aotter/mantle-cloudflare --no-git-checks --access public
329
- pnpm publish --filter @aotter/mantle --no-git-checks --access public
330
- ```
91
+ ## Run the controller
331
92
 
332
- Or one-shot:
93
+ Dispatch `.github/workflows/release.yml` from the merged release commit with:
333
94
 
334
- ```bash
335
- pnpm -r publish --no-git-checks --access public --tag alpha
336
- ```
95
+ - `version`: the version without the leading `v`.
96
+ - `deploy_landing`: `false` unless Landing was separately reviewed and is
97
+ intentionally part of this release.
337
98
 
338
- `pnpm publish` resolves `workspace:*` deps to the actual published
339
- version at pack time, so the umbrella's `dependencies` lock to the
340
- exact `0.0.X-alpha` of each sub-package once the publish completes.
99
+ Before creating the Core tag, the controller proves:
341
100
 
342
- **DO NOT use `npm publish` directly** inside a workspace package.
343
- `npm publish` (unlike `pnpm publish`) does **not** rewrite `workspace:*`
344
- specifiers — they ship to the registry verbatim as the literal string
345
- `"workspace:*"`, which no consumer can resolve. Recovery requires
346
- bumping past the broken version (see "Rollback / yanking policy") since
347
- republishing the same version is forbidden. Saw this concretely on the
348
- 0.0.11-alpha rename round: mantle-runtime / cloudflare / umbrella all
349
- shipped broken via `npm publish` and had to be republished at
350
- 0.0.11-alpha.3 via `pnpm publish`.
101
+ - the requested version matches every package, plugin, and marketplace ref;
102
+ - `pnpm check` passes;
103
+ - packed Core passes in the exact pinned Starter source;
104
+ - all five release tarballs exist;
105
+ - npm and cross-repository credentials are present and readable;
106
+ - a fresh version is unused across npmjs, GitHub Packages, and Starter tags;
107
+ - the pinned Starter commit is still the remote `develop` tip.
351
108
 
352
- Confirm zero `workspace:*` leaked into published deps:
109
+ After npm publication, it compares each public registry integrity value with
110
+ the locally packed tarball, rejects leaked `workspace:*` dependencies, waits
111
+ for the Starter tag, checks that tag's exact Core/base provenance, installs its
112
+ frozen locks from the public registry, and reruns the Starter bundle gates.
113
+ Only then does it create the Core GitHub Release.
353
114
 
354
- ```bash
355
- for p in @aotter/mantle{-spec,-admin-ui,-runtime,-cloudflare,}; do
356
- echo "=== $p"
357
- npm view "$p@alpha" dependencies --json | grep -i workspace && echo " ⚠ LEAK"
358
- done
359
- ```
115
+ ## Idempotency and recovery
360
116
 
361
- `pnpm publish` uses `publishConfig.tag` (set to `alpha` on every
362
- package) but `npm` also assigns the `latest` dist-tag by default. Per
363
- the pre-v0.1 `latest` policy above, that's the correct behavior; no
364
- action needed. Add the `alpha` tag explicitly only if it's missing:
117
+ The global controller lock serializes releases. Re-running the same release is
118
+ supported:
365
119
 
366
- ```bash
367
- npm dist-tag add @aotter/mantle@<ver> alpha
368
- ```
120
+ - an existing Core tag must resolve to the same controller commit;
121
+ - existing npm and GitHub Packages versions are verified and skipped;
122
+ - channel dist-tags are never moved backward by an older rerun;
123
+ - a duplicate Starter dispatch resumes its open/merged state or reports a
124
+ tagged no-op;
125
+ - an existing GitHub Release is a no-op.
369
126
 
370
- After publishing, verify:
127
+ If source or immutable state disagrees, the workflow fails instead of guessing.
128
+ Fix source and publish the next version when public state is wrong. Re-run the
129
+ same controller only for a transient failure or a verified partial transition.
130
+ Never force-retag or republish an existing version.
371
131
 
372
- ```bash
373
- for p in @aotter/mantle{-spec,-admin-ui,-runtime,-cloudflare,}; do
374
- npm view "$p" version dist-tags --json
375
- done
376
- ```
132
+ ## Credentials
377
133
 
378
- Note: `npm view` against a freshly-published name can 404 for 5–15 min
379
- while the Fastly read-side cache propagates. The write side (and
380
- `pnpm publish`'s "cannot publish over the previously published
381
- versions" sanity check) is the authoritative confirmation that the
382
- publish landed.
383
-
384
- **For consumer projects depending on the just-published packages**,
385
- also smoke-test installability after publish:
386
-
387
- ```bash
388
- mkdir -p /tmp/install-smoke && cd /tmp/install-smoke
389
- echo '{"name":"smoke","private":true}' > package.json
390
- npm install @aotter/mantle@alpha --no-package-lock --no-save
391
- ls node_modules/@aotter/mantle # expect: dist/ package.json README.md
392
- ```
393
-
394
- If `npm install` 404s for >15 min despite `pnpm publish` succeeding,
395
- the metadata doc is likely tombstoned (see Rollback policy below for
396
- the 24h unpublish cooldown).
397
-
398
- ### Rollback / yanking policy
399
-
400
- Do not use npm unpublish as the normal rollback mechanism. If an alpha
401
- is broken:
402
-
403
- 1. Publish the next alpha with a higher version.
404
- 2. Deprecate the broken version with a clear message.
405
-
406
- Example:
407
-
408
- ```bash
409
- npm deprecate @aotter/mantle-runtime@0.0.7-alpha "Broken alpha; use 0.0.8-alpha"
410
- ```
411
-
412
- Only unpublish when the tarball contains secrets, private files, or a
413
- severely wrong package. Remember:
414
-
415
- - npm package versions cannot be reused after unpublish (forever).
416
- - After unpublishing, the **package's metadata document is tombstoned
417
- for 24 hours**. Republishing the SAME version is forbidden, AND new
418
- versions you publish during the cooldown can land but the `npm view`
419
- / `npm install` read path returns 404 because the metadata doc is in
420
- a frozen state. Saw this concretely on 0.0.11-alpha rename:
421
- unpublished a workspace-leaked umbrella, republished as
422
- 0.0.11-alpha.1 and .alpha.2 — both were technically published (the
423
- registry refused republish with "previously published versions"
424
- errors) but invisible to consumers. Only `0.0.11-alpha.3` (after a
425
- version-number "jump") cleared the tombstone.
426
-
427
- Safer rollback discipline: skip unpublish entirely. Just bump + deprecate.
428
-
429
- ## Cross-cutting rename playbook
430
-
431
- A "rename" here means a substring-level identifier shift that crosses
432
- multiple repos / packages (e.g. `mantle` → `mantle` on 2026-05-16).
433
- These are once-per-product-life events. The rules below cost ~30 minutes
434
- of pre-flight; skipping them cost a half-day of outage when ignored.
435
-
436
- ### Step 1 — pre-flight grep for substring false-positives
437
-
438
- A naive `sed s/OLD/NEW/g` matches OLD as a **substring** of unrelated
439
- identifiers. Concrete trap: renaming `mantle` → `mantle` hit
440
- `aotter-mantle` (the CF worker name) → `aottermantle`. The
441
- auto-deploy after merge created an orphan worker without secrets;
442
- `the Mantle landing page` returned 503 for 30 minutes.
443
-
444
- Before the sed, search for shapes where OLD could appear as a substring
445
- of a meaningful different identifier:
446
-
447
- ```bash
448
- # Substring of an unrelated infra identifier?
449
- git grep -E "[a-zA-Z]+-?${OLD_NAME}" -- '*.toml' '*.yaml' '*.yml' '*.json'
450
-
451
- # Specifically check Cloudflare worker / D1 / KV / DO names
452
- git grep -nE "^name|database_name|class_name|queue.*name" -- wrangler.toml '**/wrangler.toml'
453
-
454
- # CI workflow names / step IDs
455
- git grep -nE "name:|id:" -- '.github/workflows/**'
456
- ```
457
-
458
- Hand-edit (or use word-boundary regex) any match that should *not* shift.
459
-
460
- ### Step 2 — post-sed infra-config diff
461
-
462
- After the bulk replace, **explicitly diff every infrastructure config
463
- file** even if the change looks mechanical:
464
-
465
- ```bash
466
- git diff --name-only HEAD~1 | grep -E "wrangler|workflow|toml|terraform|kubernetes"
467
- git diff HEAD~1 -- wrangler.toml '**/wrangler.toml'
468
- ```
134
+ Core repository secrets:
469
135
 
470
- Eyeball each line. Worker / DB / queue names that shift mean wrangler
471
- will deploy to a NEW resource without secrets / bindings — silent
472
- disaster.
136
+ | Secret | Minimum purpose |
137
+ |---|---|
138
+ | `NPM_TOKEN` | Publish the five `@aotter/*` packages on npmjs. |
139
+ | `RELEASE_FANOUT_TOKEN` | Read and dispatch `aotter/mantle-starters`; also read and dispatch `aotter/mantle-landing` only when Landing is enabled. |
473
140
 
474
- ### Step 3 — CI green ≠ deploy OK
141
+ Core's job-scoped `GITHUB_TOKEN` creates the Core tag and release and mirrors
142
+ packages to GitHub Packages. Starter's job-scoped token pushes its generated
143
+ branch, checked merge, and tag; its `RELEASE_FANOUT_TOKEN` is used only to
144
+ create the canonical same-repository PR. Prefer separate fine-grained tokens
145
+ or a GitHub App when practical; do not grant organization-wide repository
146
+ access for this flow.
475
147
 
476
- The deploy workflow can report success while the live URL is broken.
477
- Reasons: secrets don't carry to a renamed worker, route bindings
478
- re-attach to the new (empty) worker, etc.
148
+ ## Post-release verification
479
149
 
480
- **Always smoke-test the live URL after a config-touching deploy:**
150
+ Completion requires evidence for both repositories, not only a green publish
151
+ step:
481
152
 
482
153
  ```bash
483
- sleep 60 # Let the deploy + DNS propagate
484
- for path in / /zh-TW /skill/after-launch /admin; do
485
- code=$(curl -sIo /dev/null -w "%{http_code}" "https://<your-site>$path")
486
- echo " $path → $code"
154
+ gh -R aotter/mantle release view vX.Y.Z
155
+ gh api repos/aotter/mantle-starters/git/ref/tags/vX.Y.Z
156
+
157
+ for p in \
158
+ @aotter/mantle-spec \
159
+ @aotter/mantle-admin-ui \
160
+ @aotter/mantle-runtime \
161
+ @aotter/mantle-cloudflare \
162
+ @aotter/mantle; do
163
+ npm view "$p@X.Y.Z" version dist.integrity dependencies --json
487
164
  done
488
165
  ```
489
166
 
490
- 5xx without an obvious source-level cause usually means
491
- infrastructure-config drift, not application bug.
492
-
493
- ### Step 4 — Cross-repo rename order
494
-
495
- For renames spanning SDK + starters + landing (or any
496
- SDK-depends-on-published-SDK chain):
497
-
498
- 1. Source-level rename in all repos. Consumer repos may temporarily use
499
- npm aliases (for example the new package name pointing at a
500
- pre-rename package version) so CI can pass before the
501
- first `@aotter/*` SDK release exists.
502
- 2. Merge the starters / landing workflow changes before pushing the SDK
503
- release tag, so the fanout understands the new package names.
504
- 3. Merge the SDK rename PR, then push the next `v*` tag. The SDK
505
- `release.yml` publishes `@aotter/*` through GitHub Actions using
506
- `NPM_TOKEN`; do not publish locally.
507
- 4. Let `bump-from-sdk.yml` replace the temporary consumer aliases with
508
- the freshly published real `@aotter/*` version, regenerate lockfiles,
509
- and promote through starters → landing.
510
- 5. Smoke-test live URLs.
511
- 6. Deprecate old packages with `npm deprecate` and a message pointing
512
- to `@aotter/mantle` (repeat for the subpackages).
513
-
514
- GitHub repo renames (`gh repo rename`) and local-dir renames can happen
515
- anytime — GitHub auto-redirects old URLs to new ones; no consumer
516
- breakage from this step alone.
517
-
518
- ## Hotfix process
519
-
520
- Use hotfixes only for released `main` defects.
521
-
522
- 1. Branch from `main`:
523
-
524
- ```bash
525
- git checkout -b fix/issue-NN-hotfix origin/main
526
- ```
527
-
528
- 2. Keep the patch narrow.
529
- 3. Run the relevant tests and the full gate when feasible.
530
- 4. Open the PR against `main`.
531
- 5. Merge, tag a patch release, and update GitHub release notes.
532
- 6. Merge or cherry-pick the hotfix back to `develop`.
533
-
534
- ## Pre-flight checklist
535
-
536
- - [ ] PR base is correct for the release type — `develop` for pre-v0.1 alphas, `main` for beta/RC/stable (see [§ Pre-v0.1 alpha cadence](#pre-v01-alpha-cadence)).
537
- - [ ] `CHANGELOG.md` has the release entry. If it's a no-op SDK bump to re-spin starter content, the entry MUST say so explicitly (see [§ Re-spin release for a downstream-content-only fix](#re-spin-release-for-a-downstream-content-only-fix)).
538
- - [ ] **Cross-repo type-shape audit ran** if this release widens any SDK type — see [§ Cross-repo type-shape changes](#cross-repo-type-shape-changes). Skipping this is how alpha.9 shipped with broken starter content.
539
- - [ ] `pnpm run check` passed or failures are documented and accepted.
540
- - [ ] Package versions and tag name match.
541
- - [ ] GitHub release notes link the relevant issues and ADRs.
542
- - [ ] Automated `release.yml` published packed tarballs to npmjs and
543
- mirrored them to GitHub Packages before dispatching downstream
544
- fanout. If publishing manually, verify no `workspace:*` leaked
545
- into published `dependencies` via the check script in the
546
- "Alpha publish command" section.
547
- - [ ] Cross-repo rename? Pre-flight grep for substring false-positives
548
- ran (see "Cross-cutting rename playbook"); infra-config diff
549
- explicitly reviewed; consumer-repo lockfiles refreshed after the
550
- SDK publish lands.
551
- - [ ] Smoke-tested live downstream URL (e.g. `the Mantle landing page`)
552
- after consumer-repo deploys — CI green is not enough when infra
553
- config (wrangler.toml `name`, D1 / KV / DO bindings) shifted.
554
- - [ ] npm publish steps are either completed or explicitly not applicable.
167
+ Clone the exact Starter tag into a fresh directory, install with frozen locks,
168
+ run its bundle gates, and materialize at least one typed project. Confirm the
169
+ generated project contains version-matched repo-local Mantle skills and the
170
+ expected typed runtime surface. `blank` remains headless and contains no Kiwa;
171
+ a typed Starter revision may retain its replaceable offline UI palette, but
172
+ runtime code must not import it.
173
+
174
+ If `deploy_landing=false`, also verify that no Landing release dispatch or
175
+ deployment was started.
176
+
177
+ ## Fix-forward policy
178
+
179
+ - Broken public package or Starter bundle: publish the next alpha and explain
180
+ the re-spin in `CHANGELOG.md`.
181
+ - Use `npm deprecate` to steer consumers away from a broken version.
182
+ - Unpublish only for secrets, private files, or similarly severe exposure;
183
+ npm versions cannot be reused and registry metadata may remain unavailable
184
+ during the unpublish cooldown.
185
+ - A cross-cutting rename must include an explicit infrastructure-config diff
186
+ and live smoke test. CI success does not prove renamed Worker, D1, KV, route,
187
+ or secret bindings are correct.