@aotter/mantle 0.0.11-alpha.63 → 0.0.11-alpha.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -12
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +52 -0
- package/dist/cli.js.map +1 -0
- package/dist/generate.d.ts +2 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +181 -0
- package/dist/generate.js.map +1 -0
- package/dist/skills.d.ts +2 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +80 -0
- package/dist/skills.js.map +1 -0
- package/dist/update.d.ts +2 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +387 -0
- package/dist/update.js.map +1 -0
- package/docs/adr/0001-four-atom-manifest-model.md +6 -7
- package/docs/adr/0007-ai-as-primary-author.md +100 -138
- package/docs/adr/0008-structured-diagnostic-shape.md +79 -99
- package/docs/adr/0009-consumer-supplied-manifests.md +101 -228
- package/docs/adr/0012-views-as-public-rest.md +43 -15
- package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +43 -17
- package/docs/adr/0018-core-starters-repository-boundary.md +155 -0
- package/docs/adr/README.md +8 -6
- package/docs/cloudflare-low-level-composition.md +94 -0
- package/docs/design-atoms.md +59 -57
- package/docs/design-references/editorial-blog-2026-05-05.md +7 -7
- package/docs/labels.md +1 -1
- package/docs/media-uploads.md +1 -1
- package/docs/release-process.md +156 -523
- package/package.json +9 -6
- package/skills/README.md +20 -16
- package/skills/develop/SKILL.md +4 -4
- package/skills/install/SKILL.md +16 -2
- package/skills/plugin/SKILL.md +1 -1
- package/skills/provision/SKILL.md +1 -1
- package/skills/theme/SKILL.md +12 -10
- package/skills/update/SKILL.md +31 -19
- package/skills/customize-design/SKILL.md +0 -215
- package/skills/extend/SKILL.md +0 -257
package/docs/release-process.md
CHANGED
|
@@ -1,554 +1,187 @@
|
|
|
1
1
|
# Release process
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
93
|
+
Dispatch `.github/workflows/release.yml` from the merged release commit with:
|
|
333
94
|
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
362
|
-
|
|
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
|
-
|
|
367
|
-
npm
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
150
|
+
Completion requires evidence for both repositories, not only a green publish
|
|
151
|
+
step:
|
|
481
152
|
|
|
482
153
|
```bash
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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.
|