@mutmutco/claude-plugin 4.5.161 → 4.5.162

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.
@@ -0,0 +1,421 @@
1
+ ## Seed sources + create-vs-upgrade
2
+
3
+ The org-standard scaffolding is a **machine-readable manifest** — `skills/bootstrap/seeds/manifest.json`
4
+ (loaded by the CLI; `mmi-cli devops bootstrap apply <repo> [--execute]` consumes it). Every seed carries an **ownership**:
5
+
6
+ - **`org`** — org-delivered, **overwritten on upgrade** (the org owns it): the issue templates, the gate
7
+ workflow, and the org-managed `.gitignore` block. `source: self` = copied verbatim from MMI-Hub's own
8
+ current file. Personal **agent guides** (`AGENTS.md`/`CLAUDE.md`/`.claude/settings.json`) are **never**
9
+ seeded — they are developer-owned and gitignored, carried per machine by each developer's own plugins
10
+ (Jervaise's ride Jerv PowerTools), not delivered, overwritten, or fanned out by MMI; the org push ruleset
11
+ `mmi-no-agent-files-org` even blocks committing them. Board moves are central (the Hub webhook), so **no
12
+ per-repo board workflow is stamped**.
13
+ - **`repo`** — created **once on a fresh bootstrap**, **never clobbered on upgrade** (the repo owns its
14
+ content): `README.md`, `architecture.md`.
15
+ These render `seeds/*.template.*` with `{{PLACEHOLDERS}}`.
16
+
17
+ So **create** stamps every seed; **upgrade** refreshes the `org` seeds and adds any *missing* `repo` seeds
18
+ without overwriting existing repo-owned files (D35: legacy docs are archived + written fresh, never carried
19
+ over verbatim). The manifest's `labels` list is the canonical label set — three: `bug`, `feature`, `task`.
20
+ Priority is a **board field**, never a label (#1454 retired the four `priority:*` labels; `bootstrap verify`
21
+ checks they are gone). The per-step instructions below
22
+ are the manual path; `bootstrap apply <repo> [--execute]` automates them from this manifest.
23
+ For a registry-configured gate, use `mmi-cli devops bootstrap apply <repo> --only gate.yml --from-config`
24
+ (dry-run) then add `--execute` to propose the file on a review branch + PR. This uses the same
25
+ registry `gate` render as `ci reconcile`. An unstamped legacy gate must first match the registry
26
+ render exactly; the first PR then adopts it by adding a hashed render stamp. After adoption, changing
27
+ the registry config produces an update PR if the committed stamp still verifies. A stamped gate edited
28
+ by hand, or a divergent unstamped gate, is refused with a diff: make the registry config reproduce an
29
+ unstamped file first, then re-run to adopt. Edit the registry config, never the stamped file.
30
+
31
+ **`bootstrap apply` is a single-repo tool** — fresh bootstrap/onboarding, or refreshing one repo's own
32
+ drifted seed. It is **never** the fleet-wide fan-out for an `org`-owned seed edit: that write path is
33
+ propagation from a Hub merge (#4233), and the seeded `agent-pr.yml` itself now fails the merge gate on
34
+ any repo that receives a hand-edit to an org-owned target path outside a propagation/bootstrap-delivery
35
+ branch (#4241). Running `apply --execute` against every registry repo by hand to fan out a Hub edit is
36
+ exactly the copy-per-repo pattern this rule exists to end — see Hub#4234.
37
+
38
+ **Rollback is per-repo, never a second fleet overwrite (#4240).** When a propagated `org`-owned seed
39
+ breaks a repo, the recovery is `mmi-cli devops bootstrap rollback <repo> --target <t> [--execute]` — it resolves
40
+ the ONE merge commit that repo's `bootstrap propagate` (#4238) run actually landed (live from the repo's
41
+ `seed-propagate-<slug>` merged-PR history, or replayed from a persisted propagate `--json` report via
42
+ `--record`) and opens an ordinary revert PR of it, through that repo's own gate, on a `seed-rollback-<slug>`
43
+ branch. It refuses — never guesses a commit — when no clean, single-file propagation record resolves for
44
+ that repo+target. **Explicitly forbidden as a rollback path: a fleet-wide `bootstrap apply --execute` of
45
+ "the old bytes"** — that is the second blind fleet overwrite #4233 rules out, and it races the propagation
46
+ lane. **MMI-Hub stays the source of truth during a rollback**: a per-repo revert is the emergency stop, not
47
+ the fix — closure is the Hub reverting (or fixing forward) the bad seed commit on `development`, after
48
+ which reverted repos match the Hub again and the drift alarm closes itself. A per-repo revert with no
49
+ Hub-side follow-up re-alarms as drift within a week, deliberately, so an emergency divergence can never
50
+ silently become permanent.
51
+
52
+ **Release gap — a new topology value must reach TWO surfaces, and a local dev build only fixes one (#2928).**
53
+ A new `--project-type`, `--deploy-model`, or `--release-track` lands on `development` but is not live until a
54
+ release train ships it — in **both** of these places:
55
+
56
+ 1. **The published/installed `mmi-cli`**, which rejects the unknown enum with no hint that it merely lags
57
+ `development`. A local dev build routes around this:
58
+ `node <MMI-Hub checkout>/cli/dist/index.cjs bootstrap apply ...` from a `development` checkout (rebuild
59
+ `dist` first: `npm --prefix cli ci && npm --prefix cli run build`).
60
+ 2. **The deployed registry Lambda**, which carries its *own* copies of the enums
61
+ (`infra/src/registry-route.ts` validates `projectType` **and** `deployModel`; `releaseTrack` is CLI-side
62
+ only) and **deploys from `main`**. A local dev build does nothing for this one.
63
+
64
+ So the local dev build is **not** a workaround for a just-merged enum value — it gets you further and then
65
+ fails at the step that matters. The CLI cheerfully accepts the new value, sends it, and the API rejects it:
66
+
67
+ ```
68
+ ddb register <slug> (failed: HTTP 400 — projectType must be one of:
69
+ web-app, hub-service, content, desktop-game, non-deployable, cli-tool, worker)
70
+ ```
71
+
72
+ That failure lands **after** seeding, labels, and the ruleset, so the repo is left half-provisioned with an
73
+ almost-empty registry META row. **Bootstrapping against a topology value merged since the last train requires
74
+ a `/release` to `main` first.** The local dev build alone suffices only for values already live in the API.
75
+
76
+ ## Step 0b — create the repo (when it does not exist yet)
77
+
78
+ If `$OWNER/$REPO` is not on GitHub yet, create it and seed an initial commit **before** Step 1 — a brand-new
79
+ repo has no commits, so there is no branch to push and no default branch to set. **Create the first commit
80
+ through the contents API, on every track** (#3655) — an API write is not a push, so neither the #1660
81
+ protected-branch guard nor `mmi-train-floor` objects, and it is the same server-side mechanism Step 1 uses
82
+ for `rc`/`main` (#3433). `$FIRST` is the track's first branch: `development` for **full**/**direct**,
83
+ `main` for **trunk**:
84
+
85
+ ```bash
86
+ FIRST=development # full / direct
87
+ FIRST=main # trunk (content)
88
+
89
+ # One-time namespace + first-ref creation uses the authenticated master-admin GitHub session. The current
90
+ # mmi-cli has no App-backed command for a repository that does not exist; this is the named exception.
91
+ gh repo create "$OWNER/$REPO" --private --disable-wiki
92
+ gh api -X PUT "repos/$OWNER/$REPO/contents/.gitkeep" \
93
+ -f message="chore: initial commit" -f content="Cg==" -f branch="$FIRST"
94
+ ```
95
+
96
+ `--disable-wiki` seeds `has_wiki=false` at creation: wikis are retired org-wide (compute-at-read, #4206),
97
+ GitHub defaults new repos to `has_wiki=true`, and `bootstrap apply --execute` / `bootstrap reconcile --apply`
98
+ enforce the same rule as drift checks afterwards.
99
+
100
+ On an empty repo that PUT creates `$FIRST` **and leaves it the default branch**, so no `gh repo edit
101
+ --default-branch` is needed here. Skip this step when the repo already exists with the track's default branch.
102
+
103
+ The two `gh` writes above are authorized by the authenticated master-admin login and are the one-time
104
+ bootstrap exception while no App-backed `mmi-cli` command owns a repository that does not yet exist. They
105
+ are not a general write lane: after the namespace and first ref exist, use the App-backed
106
+ `mmi-cli devops bootstrap apply --execute` path for managed seeds, labels, rulesets, and registry state.
107
+
108
+ For a **content** repo the API form is not merely tidier, it is the only thing that works: `main` is that
109
+ track's first and only branch, so the initial commit IS a push to `main` and the #1660 guard fences it — and
110
+ the deny's own advice ("land through a CI-gated PR to development") is meaningless on a repo with no commits,
111
+ no `development`, and no CI (#3541).
112
+
113
+ The old recipe here was a local `git init` + empty commit + `git push` for deployable repos, opening with
114
+ `gh repo create --private --confirm`. **That flag no longer exists** — `gh` removed it, so the first command
115
+ of the first step errored out. The local-git ritual it opened is also unnecessary: the API form above does
116
+ the same job for `development` in one call.
117
+
118
+ ## Step 1 — branches
119
+
120
+ Determine the repo's **release track** first — the branch set follows the track, not just the class (#1097),
121
+ so a direct-track repo never gets a stray `rc` the `mmi-train-floor` ruleset can't clean up:
122
+
123
+ - **full** (deployable default) — three permanent branches, default `development`: `development`, `rc`, `main`.
124
+ - **direct** (deployable, skips rc — e.g. cli-tool/worker/Hub) — two permanent branches, default
125
+ `development`: `development`, `main`.
126
+ - **trunk** (content) — one permanent branch, default `main`: `main` only.
127
+
128
+ Ensure exactly the track's permanent branches exist and set the default branch — create only what the track
129
+ uses; never create an `rc` for a direct-track repo.
130
+
131
+ **`bootstrap apply --execute` creates `rc`/`main` server-side from the track's first branch (#6070).** The
132
+ local **#1660 protected-branch push guard** fences every push to `main`/`master`/`rc`, including the
133
+ legitimate *creation* of those refs on a brand-new repo (#3433); a ref created through the API is not a
134
+ force-push, so both the guard and the `mmi-train-floor` ruleset allow it. The apply's train-readiness plan
135
+ prints one `lane-branch` row per track branch — `CREATE` (missing), `SKIP` (already exists), or `GAP`
136
+ (`bootstrap-gap`: the first branch from Step 0b is missing, so there is no source ref) — and the live apply
137
+ creates every `CREATE` row; a `422 Reference already exists` reply is the idempotent no-op, not a failure.
138
+ `development` (or `main`, for a content repo) already exists from Step 0b. Only the default branch stays a
139
+ `gh` write:
140
+ ```bash
141
+ gh repo edit "$OWNER/$REPO" --default-branch development # full / direct
142
+ gh repo edit "$OWNER/$REPO" --default-branch main # trunk (content)
143
+ ```
144
+ Verify with `gh api "repos/$OWNER/$REPO/branches" --jq '.[].name'`, or read the `lane-branch` rows of the
145
+ next dry-run `bootstrap apply`.
146
+
147
+ ## Step 2 — authority (org Ruleset)
148
+
149
+ Confirm the org-level rulesets already target this repo (they apply org-wide): **`mmi-branch-protection`**
150
+ PR-gates `development` and blocks force-push/deletion there (bypass = org admins + the GitHub App 3026732),
151
+ and **`mmi-train-floor`** blocks force-push/deletion on `rc`/`main` (no PR rule — the train pushes merges and
152
+ tags directly; no bypass). (Definitions mirror live in `.github/rulesets/mmi-branch-protection.json` and
153
+ `.github/rulesets/mmi-train-floor.json`.) MMI-Hub additionally has its own repository ruleset,
154
+ `.github/rulesets/mmi-hub-required-checks.json`, for the Hub-only `cli`, `infra`, and `docs` jobs; do not
155
+ apply those contexts org-wide unless every target repo exposes them.
156
+
157
+ ## Step 2b — lock the train branches (who can push)
158
+
159
+ The ruleset says *a PR is required*; this says *who may merge it*. `bootstrap apply --execute` applies the
160
+ classic branch protection on `development`/`rc`/`main` for deployable repos, or just `main` for content
161
+ repos, with **"Restrict who can push"** = the org owners + the App (#6070): the owners are resolved live
162
+ from the GitHub org admins (`orgs/$OWNER/members?role=admin`), never a hard-coded user list — an empty
163
+ resolution refuses to write an empty allowlist — and the App is `mmi-github-app`. The repo's full-write
164
+ people get added at Step 4b. Everyone else is `write`-locked on protected branches: they push feature
165
+ branches and open PRs but cannot merge there.
166
+
167
+ The plan prints one `push-allowlist` row per track branch — `UPDATE` (preserve every existing
168
+ review/status/admin/force-push bit and replace only the push restriction), `SKIP` (exact owners + App
169
+ already present), or `GAP` (`bootstrap-gap`: the protection read failed; zero writes are performed while any
170
+ control-plane read is unverified). Only the master (sole repo `admin`) can change this afterward. Full
171
+ grant/lock mechanics + inspect commands: `docs/Guides/repo-access.md`.
172
+
173
+ ## Step 3 — attach to the repo's Project (one board per repo; confirm)
174
+
175
+ **One board per repo, named after it** — that is the convention the org actually runs, and what the rest of
176
+ the system assumes: the registry META carries a single `projectId` per `PROJECT#<slug>`, and the Hub webhook
177
+ adds each new issue to that one board. There are no division boards; a `<CATEGORY>`-prefixed default would
178
+ resolve to nothing (#3542). Still **confirm with the master** — repos and projects are genuinely **not 1:1**,
179
+ and joining an existing board remains a legitimate answer for a repo that belongs to an existing effort.
180
+ ```bash
181
+ gh project list --owner "$PROJECT_OWNER" --format json # existing boards to choose from
182
+ ```
183
+ - **Attach** to the chosen project:
184
+ ```bash
185
+ gh project link "$PROJECT_NUMBER" --owner "$PROJECT_OWNER" --repo "$OWNER/$REPO"
186
+ ```
187
+ - **Create** the chosen board if it doesn't exist yet — clone the **named template board**, never an
188
+ arbitrary existing one, so the 4-lane `Status` field (`Todo · In Progress · In Review · Done`), the
189
+ built-in workflows, **and** the org's view/card-field shape (#4093) all carry over instead of whatever
190
+ the previous copy-of-a-copy happened to drift to:
191
+ ```bash
192
+ gh project copy 4 --source-owner "$PROJECT_OWNER" \
193
+ --target-owner "$PROJECT_OWNER" --title "<Project>"
194
+ ```
195
+ **Project 4 (MMI-Hub's own board) is the template** (#4093) — it already carries the org-standard view
196
+ triple (`List`/`Board`/`Roadmap`), Board grouping (columns=Status, swimlanes=Repository), and the
197
+ canonical Board card-field set (`Title, Assignees, Status, Labels, Linked pull requests, Parent issue,
198
+ Sub-issues progress, Priority`). `bootstrap verify` asserts all three against every board (next note); a
199
+ freshly copied project starts green on them instead of inheriting a stale source's drift. Copying from
200
+ anything else re-introduces the "5 card-field variants across 16 boards" problem #4093 was filed to end.
201
+ Then **interview the master** for the seed short description + README (what it tracks, member repos, links
202
+ to the repos' `README.md`/`architecture.md`); set them via the `updateProjectV2` mutation
203
+ (`shortDescription`, `readme`). If cloning, verify the built-in workflows survived (next note).
204
+ - **Built-in workflows:** `mmi-cli devops bootstrap verify` checks these Project workflows are enabled:
205
+ `Auto-add sub-issues to project`, `Auto-archive items`, `Item added to project`, and `Item closed`. The
206
+ Todo/In Progress/In Review moves are **central** (the Hub webhook); the built-in `Item closed` sets `Done`
207
+ on merge. GitHub's public GraphQL schema exposes delete/read surfaces for Project workflows but no
208
+ create/update/enable mutation; if any required workflow is missing or disabled, repair it in the project's
209
+ **Workflows** settings before calling the repo ready for developers.
210
+ - **Board shape (#4093):** `mmi-cli devops bootstrap verify` also checks the view triple (`List`/`Board`/`Roadmap`
211
+ by name **and** layout), the Board view's grouping, and its card fields against the standard the template
212
+ board (project 4) carries. Grouping (columns=`Status`, swimlanes=`Repository`) has **no** GraphQL create/
213
+ update mutation (`ProjectV2ViewConfigurationInput` carries only `visibleFieldIds`) — a drifted swimlane or
214
+ column can only be fixed in the UI: Board view → ⚙ (top-right) → Group by / Swimlanes → Save view. The
215
+ view triple and the card fields (`Title, Assignees, Status, Labels, Linked pull requests, Parent issue,
216
+ Sub-issues progress, Priority`) ARE API-writable (`createProjectV2View`/`updateProjectV2View`), so a
217
+ drifted one can be fixed live instead of only reported.
218
+
219
+ Record the chosen `projectNumber`/`projectId` plus Status/Priority field ids in the Hub registry META
220
+ (`PROJECT#<slug>`) by re-running `mmi-cli devops bootstrap apply --execute` **after** `gh project link`. Apply reads
221
+ the repo's linked board and derives all of them itself (#3543) — pass `--var PROJECT_ID=<node id>` only to
222
+ override, which is also what you need when the repo is linked to more than one board and apply therefore
223
+ refuses to guess. **Verify the row afterwards** (`mmi-cli oracle org project get <owner/repo>`): a registered repo
224
+ whose META carries no `projectId`/`statusFieldId` looks finished everywhere else while the Hub webhook has
225
+ nothing to move issues with, and `registry project board META exists` is the single check that says so. Going forward the thin Lambda adds each new issue to that project on `issues.opened` and sets
226
+ `Status: Todo`.
227
+
228
+ **Register the project in the Hub registry.** The same registry META row carries `{name, slug, projectId,
229
+ repos[]}`; do not append to a committed `projects.json`. Repo wikis and doc freshness are owned by
230
+ the repo itself. If attaching this repo to an existing project, merge this repo into that project's `repos[]`
231
+ instead of creating a new project.
232
+
233
+ ## Step 4 — vault tiers + deploy substrate
234
+
235
+ Provision the repo's vault namespace and deploy substrate from the Hub, not from repo-local Actions
236
+ secrets. Runtime config names live in the two-tier vault (`/mmi-future/<slug>/dev|rc|main/*`) and are
237
+ managed through `/secrets`; never use `gh secret set` for product runtime config.
238
+
239
+ The default `tenant-container` substrate is a **Hetzner box** (`hetzner-ssh`): the deploy streams the stage's
240
+ vault values into a tmpfs runtime env (no `.env` file) and runs the container via docker-compose, deployed over the Hub's bounded
241
+ SSH lane. Do **not** create an AWS OIDC deploy role or a repo deploy Action for it. **Ask the master for the
242
+ box assignment** — the `sshHost` (and the loopback port) per stage — then write the `DEPLOY#<stage>` rows with
243
+ `mmi-cli oracle org project set-deploy <owner/repo> --stage <dev|rc|main> --ssh-host <host> [--port <p>]` (defaults:
244
+ `substrate: hetzner-ssh`, deploy path `/opt/mmi/<slug>/<stage>`, service = slug, ssh-user `root`). Without those
245
+ rows the tenant cannot deploy (`tenant-deploy.yml` errors on missing `DEPLOY#` coords), so do not skip this.
246
+ `bootstrap apply` prints one `deploy-row` row per track stage for container models (#6070): `SKIP` when the
247
+ registry row exists, `GAP` (`bootstrap-gap`) with the exact `set-deploy` command otherwise (for
248
+ `tenant-container` it carries `--no-env-file true`, matching the fileless compose seed; run it from the tenant
249
+ checkout once that compose is on the stage branch) — a box
250
+ assignment cannot be invented, so this row is the one train-readiness fact the apply never writes itself.
251
+ Keep every runtime config value in the vault; never paste secret values into logs.
252
+
253
+ Only an AWS `tenant-container` (the exception) provisions the reusable tenant stack: release bucket,
254
+ per-stage OIDC deploy role, and constrained service-control role/document. Either way the deploy path
255
+ trusts the Hub's central `tenant-deploy.yml` / `tenant-control.yml` environments, not a product repo deploy
256
+ workflow.
257
+
258
+ ## Step 4b — developer access
259
+
260
+ **Repository Environments (#4458, plan-gating #5032)** — the track's deployment environments are
261
+ control-plane state this skill's own `bootstrap verify` asserts, so live apply provisions them itself:
262
+ `mmi-cli devops bootstrap apply --execute` creates `dev`/`rc`/`main` (per the release track: direct has no
263
+ `rc`, trunk has `main` only) with a **deployment-branch policy = protected branches only** — the one
264
+ protection the check exists to carry. On an org plan whose billing entitlement rejects protection rules
265
+ (an explicit `wait_timer` or required `reviewers` comes back 422 naming the plan), apply provisions the
266
+ settable policy and prints `plan-gated protections` for those rules; `bootstrap verify` then reports the
267
+ environment rows as green with an `advisory:` detail naming what the plan cannot set — never a FAIL. The
268
+ policy is set either way, so a repo on the lower plan is still protected and ready for developers.
269
+
270
+ Grant each developer their **org membership + repo access** through the App (`gh api` with the App token):
271
+ `write` for a developer; for a **full-write** member ("project-admin") also add them to the train-branch
272
+ push allowlist from Step 2b (so they can merge protected development PRs). GitHub's
273
+ collaborator list + the per-branch allowlist are the record — no separate roster. Exact commands:
274
+ `docs/Guides/repo-access.md`.
275
+
276
+ ## Step 4c — CI trigger, labels, templates
277
+
278
+ - **Enable workflow triggers** — a freshly-provisioned repo can have GitHub Actions auto-trigger stuck off
279
+ (push/PR never run workflows though dispatch does). Toggle it off→on:
280
+ ```bash
281
+ gh api -X PUT repos/$OWNER/$REPO/actions/permissions -F enabled=false
282
+ gh api -X PUT repos/$OWNER/$REPO/actions/permissions -F enabled=true -f allowed_actions=selected
283
+ ```
284
+ - **Self-hosted CI is the org default** — the `mmi-automation` runner group is org-wide (all repos), so a
285
+ new repo needs no manual group join. Any CI workflow it adds uses
286
+ `runs-on: [self-hosted, linux, x64, mmi-live]` (never `ubuntu-*`/`windows-*`/`macos-*`, which bill
287
+ GitHub-hosted minutes); jobs needing system packages run in a `container:`. The runner has **six
288
+ concurrent job lanes** — independent checks belong in separate parallel jobs (each with its own
289
+ `runs-on`), never chained into one serial job or throttled with unneeded `concurrency` groups. See
290
+ `docs/Guides/gh-runner-runbook.md`.
291
+ - **Product gate + required checks (#1333, stack-aware #1550)** — deployable repos bootstrap-seed
292
+ `.github/workflows/gate.yml` (single `gate` job — the simple default for one check command; a repo
293
+ that grows several independent suites splits them into parallel jobs across the runner's twelve lanes,
294
+ as MMI-Hub's `cli`/`infra`/`docs` gate does) and a ruleset reference at
295
+ `.github/rulesets/mmi-product-required-checks.json`. The gate is stack-aware: capture the repo's
296
+ **runtime** (`node` | `python`), its **check command**, and its **working directory** at interview time
297
+ and pass them as `--var GATE_RUNTIME=node|python`, `--var GATE_CMD=...`, `--var GATE_WORKDIR=...` (and
298
+ `--var GATE_CACHE_DEP_PATH=<path/to/package-lock.json>` when the app's Node lockfile is not at the repo
299
+ root). Defaults: `node` runtime, `npm run check` / `npm ci` at the repo root; a Python repo defaults to
300
+ `pytest` / `pip install -e ".[dev]"` with `--var GATE_PY_VERSION=` (3.11 default). The runtime selects
301
+ which setup step (`setup-node` vs `setup-python`) the rendered `if:` fires. The check command runs
302
+ under the org **wall-clock budget** (#3178): the render pins `run-with-budget` to the CLI's blessed SHA
303
+ with `--var GATE_MAX_SECONDS=` (300s onboarding default — tighten once the gate is measured, via
304
+ `--var` or `org project set <repo> --var gate={"maxSeconds":N}`); `ci audit` and the release train
305
+ both enforce the step, so do not remove it. The tenant-container product image build (#7542)
306
+ shares that ceiling — no separate knob. Every trigger runs the full `GATE_CMD`. A repo whose checks
307
+ need a service container or an extra budgeted step declares them in its registry gate config, never by
308
+ hand-editing the rendered gate (#7407): `gate.services` is keyed by service name with `image`, `env`,
309
+ `ports`, one-line `options` and `tmpfs` paths; `gate.steps` is a list of `{name, command, env,
310
+ maxSeconds}` run after the main check under the same budget wrapper. Invalid declarations fail the
311
+ render by field name; a repo that declares neither renders exactly as before. `bootstrap apply --execute` installs that JSON as the
312
+ repository ruleset itself (#6070) and reports one `product-ruleset` row: `PARK` (the gate file is not on
313
+ the base branch yet — installed disabled), `WAIT` (the gate exists but has not emitted a proven-green
314
+ `gate` context — kept disabled; re-run apply after the gate passes), `ACTIVATE` (gate green — enforcement
315
+ active), or `SKIP` (already exact). The activation boundary is fail-closed on purpose: a required context
316
+ that cannot pass yet would lock the seed PR behind itself. `mmi-cli devops ci reconcile --apply --repo
317
+ $OWNER/$REPO` remains the supported fallback; confirm `product required-check ruleset enforcement active`
318
+ with `bootstrap verify` before reporting bootstrap complete. MMI-Hub keeps its own three-job gate
319
+ (`cli`/`infra`/`docs`) — never apply the product ruleset there.
320
+ - **The committed ruleset owns its up-to-date policy.** New repositories default to
321
+ `strict_required_status_checks_policy: false`. Bootstrap and CI reconciliation preserve that policy
322
+ when updating contexts, and converge the live ruleset to the committed setting. Repositories may
323
+ explicitly require an up-to-date head. `pr merge` / `pr land` still update a BEHIND head from the base
324
+ with a merge commit and re-wait once — see `docs/Guides/train-troubleshooting.md#head-behind-base`.
325
+ - **A brand-new repo cannot pass the gate you just installed — its first commit must carry a real project
326
+ (#2928).** The seeded `gate.yml` runs `GATE_INSTALL_CMD` + `GATE_CMD` (`npm ci` + `npm run check` by
327
+ default) **unconditionally**. An empty repo has no `package.json`, so the gate **fails on the seed PR
328
+ itself** — and since the seed PR is what *installs* the gate, this is the normal first-bootstrap path, not
329
+ an edge case. The repo cannot go green and `bootstrap verify` cannot pass until the first commit contains a
330
+ real project satisfying `GATE_CMD`: a `package.json` with a `check` script (or the Python equivalent), plus
331
+ its lockfile. Landing that minimal project is **part of finishing the bootstrap**, not follow-up work —
332
+ otherwise the gate is unpassable by construction.
333
+ **The `check` must be able to fail.** It has to actually compile or lint the code being committed — a
334
+ script that exits 0 without looking at anything does **not** satisfy `GATE_CMD` and must never be committed
335
+ to force a green gate. The goal is a gate that catches a broken change, not a green tick wired to nothing.
336
+ A typecheck plus a lint is enough to start. **Do not seed a test suite to satisfy `GATE_CMD`** — tests are
337
+ opt-in org-wide (#3562); a new repo seeds tests only for paths its own `test-policy.json` marks mandatory,
338
+ and an empty `mandatory` array is the normal answer.
339
+ - **Standard labels** — type labels only (the issue templates reference them). **Priority is a Project
340
+ field, not a label** (#416): never seed `priority:*` labels; `--priority` writes the board field.
341
+ ```bash
342
+ gh label create bug --color d73a4a --description "Something is broken or behaving wrong" -R $OWNER/$REPO
343
+ gh label create feature --color a2eeef --description "New capability or enhancement" -R $OWNER/$REPO
344
+ gh label create task --color 0052cc --description "Task, chore, or improvement" -R $OWNER/$REPO
345
+ ```
346
+ - **Board view (card fields, #4093)** — the Project's Board view must show the org-standard card set:
347
+ `Title, Assignees, Status, Labels, Linked pull requests, Parent issue, Sub-issues progress, Priority`.
348
+ Cloning from the template board (project 4, Step 3) already produces this; `bootstrap verify` asserts it
349
+ (`Board view card fields match the org standard`) on every subsequent run, so drift shows up there rather
350
+ than only at bootstrap time. Fix via Board view → **Fields**, or `updateProjectV2View(configuration:
351
+ {visibleFieldIds:[...]})`. Strip any legacy `priority:*` / taxonomy labels with `mmi-cli oracle board doctor --fix`.
352
+ - **Org App credentials** — nothing to register per repo (#494). Board moves are central (the Hub webhook
353
+ moves Todo/In Progress/In Review for every repo) and the org App token is minted inside the Hub's own
354
+ central workflows, so `MMI_APP_ID` / `MMI_APP_PRIVATE_KEY` live only on the Hub — a product repo seeds no
355
+ App var/secret.
356
+ - **Issue templates** — seed `.github/ISSUE_TEMPLATE/` (Bug · Feature · Task + `config.yml`).
357
+ - **Merge settings (org canon)** — every repo: auto-merge on, squash on, automatic merged-branch deletion
358
+ off. A merged remote branch survives until delayed cleanup proves no open PR still uses it as a base
359
+ (#5789); deleting it at merge can irreversibly close stacked PRs:
360
+ `gh api -X PATCH repos/$OWNER/$REPO -f allow_auto_merge=true -f allow_squash_merge=true -F delete_branch_on_merge=false`
361
+ - **Cursor environment** — `.cursor/environment.json` is retired org-wide (#2501): it caused Cursor to
362
+ auto-spawn cloud agents nobody asked for. Bootstrap no longer seeds it and no
363
+ repo should carry a tracked copy.
364
+
365
+ ## Step 5 — install the plugin + seed docs
366
+
367
+ - Bootstrap does **not** seed `.claude/settings.json` or any agent guide (hub-v3 WS4). The developer installs
368
+ the org plugins per machine: `mmi@mutmutco` (the plugin carries the org tooling + skills) and
369
+ `superpowers@claude-plugins-official` (Anthropic's skills framework — TDD, debugging, subagent dev).
370
+ Personal agent guides (`AGENTS.md`/`CLAUDE.md`) are developer-owned and gitignored — MMI never delivers,
371
+ overwrites, or deletes them; Jervaise's guide rides Jerv PowerTools.
372
+ - Seed `README.md` + `architecture.md` from the templates, then **fill them before finishing** — `bootstrap
373
+ verify` now fails on any leftover `(placeholder)` or `{{TOKEN}}` (#1520). Fill the code-local fields from
374
+ what this bootstrap already knows: **Stack / Run locally / Verify** from the Step-4c gate + install commands
375
+ and the repo's actual code; **Gotchas** and the architecture **Overview / Build & deploy** from the confirmed
376
+ Step-0 axes (class, project-type, deploy-model). Do **not** write the release track, board number, or deploy
377
+ coords as a value — the templates point at `mmi-cli oracle org project get` (registry SSOT, never copied, so it cannot drift).
378
+ Do not write `AGENTS.md` / `CLAUDE.md` — these are developer-owned, gitignored agent guides, never a bootstrapped repo file (the `mmi-no-agent-files-org` ruleset blocks committing them).
379
+ - **`docs/index.md` is an optional generated routing index (#3545).** Apply may create `docs/index.md` once for link routing; it never rewrites it. Once the repo has docs of its own,
380
+ `mmi-cli oracle docs index --write` owns the routing artifact and `--check` gates drift — it is not product
381
+ current-state SSOT. Decision records under `docs/decisions/` remain append-only *why*.
382
+ - `.claude/settings.local.json` is local-only and gitignored; bootstrap seeds no committed `.claude/settings.json`.
383
+ - **No agent guide is committed — none, anywhere (#2921).** `mmi-no-agent-files-org` is active with no bypass
384
+ and restricts `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `.claude/**`, `.codex/**`, `.agents/**` **and
385
+ `.cursor/rules/**`**. Bootstrap used to seed `.cursor/rules/<repo-slug>.mdc`, which the wall refuses with a
386
+ 409 — it wrote a file its own ruleset bans, then verified the file was there. That seed, its template, and
387
+ the verify check are gone. Repo-specific agent guidance belongs in **`README.md` § Agent context** (stack,
388
+ verify commands, gotchas) with the deep build truth in `architecture.md`; agents read those. Personal agent
389
+ guides stay developer-owned and gitignored, carried per machine by each developer's own plugins.
390
+
391
+ Choose the v2 shape explicitly. Use `--project-type web-app --deploy-model tenant-container` for ordinary
392
+ web tenants, `--project-type desktop-game --deploy-model none --clear-web-profile` for a desktop game,
393
+ `--project-type desktop-app --deploy-model none --clear-web-profile` for a packaged desktop application
394
+ (swap in `--deploy-model registry-publish` when it also publishes a package alongside its installer),
395
+ `--project-type mobile-app --deploy-model none --clear-web-profile` for a phone app distributed through the
396
+ app stores, and
397
+ `--class content --project-type content --deploy-model content --clear-web-profile` for a content/KB repo.
398
+ An **MM-Work module** adds `--var SUITE=mm-work` (#7944): it registers `suite: mm-work` and derives its
399
+ own `dev.<sub>.mutmut.work` + `<sub>.mutmut.work` hosts, with no rc host (the suite serves its rc from the
400
+ live host). Without it, apply derives the `*.mutatismutandis.co` web-app hosts, which a module never uses.
401
+ For a `suite: mm-work` tenant-container module, bootstrap apply selects the MM-Work module seed instead: a live `/health` server and `/module.js`, the example module scaffold, secret-backed image build, and `gate` plus `screens / screens`. It sets `requiredBuildSecrets: ["NODE_AUTH_TOKEN=@github-packages-token"]` and requires only `gate`. Before the first CI/deploy, an org admin must grant the repo Read on both `@mutmutco/mmwork-ds` and `@mutmutco/mmwork-screens` under Package settings → Manage Actions access (no GitHub REST API; verify names the manual step). Add `screens / screens` to `requiredChecks` only after the screens job is green, or every PR blocks.
402
+
403
+ **Never provision a Google OAuth client for an MM-Work suite module, even if old META declares `oauth`.** Modules sign in through the MM-Work pass and reach Google via `ctx.act`; read the package's GUIDE. The suite seed omits the Google-login guide.
404
+
405
+ For a non-suite `web-app` that declares `oauth` META, print the canonical OAuth surface and provision the client once:
406
+
407
+ ```bash
408
+ mmi-cli vault org oauth plan --repo "$OWNER/$REPO" # the exact JS origins + redirect URIs + canonical SSM keys
409
+ ```
410
+
411
+ Register those JS origins + `/api/auth/callback` redirect URIs on the Console client (master, per
412
+ `docs/Guides/oauth-provision.md`), then store the creds in the canonical keys in one step:
413
+
414
+ ```bash
415
+ mmi-cli vault org oauth set-creds --repo "$OWNER/$REPO" < client.json # the Console "Download JSON" file
416
+ ```
417
+
418
+ The keys are the one stageless pair `GOOGLE_CLIENT_ID` + `GOOGLE_CLIENT_SECRET` at the slug root — every
419
+ stage reads it (#2244/#2528). Never a staged `{dev,rc,main}/GOOGLE_*`, `GOOGLE_OAUTH_CLIENT_*`, or `prod/`
420
+ variant; the runtime reads only the canonical names and declare-first rejects the rest.
421
+
@@ -13,7 +13,7 @@ A hotfix is promotion, not authoring. The fix lands on `development` first throu
13
13
 
14
14
  ## Lane
15
15
 
16
- In the suite lead (MM-Work) a plain rcand, release or hotfix refuses: run `mmi-cli devops suite rcand --owner-go` or `mmi-cli devops suite release --owner-go` instead (a lead-only cut needs `--suite-solo --owner-go`). A suite member cannot run standalone rcand, release or hotfix: cut from the suite lead; only a backward-compatible solo backend release/hotfix with explicit owner approval may pass `--suite-solo --owner-go`, and its MM-Work development pin PR must land before the suite is in sync.
16
+ For an MM-Work suite lead or member, read [suite.md](suite.md) before choosing a lane.
17
17
 
18
18
  Read META and name the lane before describing or executing anything:
19
19
 
@@ -84,9 +84,7 @@ Branch facts close the verdict: the tag commit is on `origin/main`; `development
84
84
 
85
85
  ## Recovery
86
86
 
87
- Every refusal and every doctor blocker names its `code` and `anchor`: open `docs/Guides/train-troubleshooting.md#<code>` and follow that entry, nothing else. That guide is test-parsed, never free text — the infra drift and plugin-set suites validate every anchor cited here and assert policy from its wording — so an edit to it re-runs the infra suite (`npm --prefix infra test`) in the same PR: a heading or slug change is a code change. The hotfix subcommands are idempotent, so after an interruption rerun the identical `mmi-cli devops hotfix release vX.Y.Z --carries …` — each effect is checked before it is re-attempted, and the ledger names that rerun as this lane's continuation; `--resume --watch --json` is the release and rcand ledgers' only continuation form and never a hotfix one. A refused pick, a port conflict, a red authority probe, a failed check, a missing carry, or `development fold port FAILED` each ends the run (Merge floor): fix the cause on `development` through a normal PR, then rerun from the port merge SHA — never improvise a merge from `main`.
88
-
89
- A prior release's unresolved ledger also blocks this lane — and when its only unresolved leg is a content-caused failed deploy on the already-published tag, this lane is the fix's carrier: close that leg first with the master-approved `mmi-cli devops release --supersede-deploy --apply` (the cause fix must already be merged on `development`), then run the hotfix; the gate archives the closed ledger aside on its own.
87
+ On any failure or interruption, read [recovery.md](recovery.md).
90
88
 
91
89
  ## Merge floor
92
90
 
@@ -0,0 +1,4 @@
1
+ Every refusal and every doctor blocker names its `code` and `anchor`: open `docs/Guides/train-troubleshooting.md#<code>` and follow that entry, nothing else. That guide is test-parsed, never free text — the infra drift and plugin-set suites validate every anchor cited here and assert policy from its wording — so an edit to it re-runs the infra suite (`npm --prefix infra test`) in the same PR: a heading or slug change is a code change. The hotfix subcommands are idempotent, so after an interruption rerun the identical `mmi-cli devops hotfix release vX.Y.Z --carries …` — each effect is checked before it is re-attempted, and the ledger names that rerun as this lane's continuation; `--resume --watch --json` is the release and rcand ledgers' only continuation form and never a hotfix one. A refused pick, a port conflict, a red authority probe, a failed check, a missing carry, or `development fold port FAILED` each ends the run (Merge floor): fix the cause on `development` through a normal PR, then rerun from the port merge SHA — never improvise a merge from `main`.
2
+
3
+ A prior release's unresolved ledger also blocks this lane — and when its only unresolved leg is a content-caused failed deploy on the already-published tag, this lane is the fix's carrier: close that leg first with the master-approved `mmi-cli devops release --supersede-deploy --apply` (the cause fix must already be merged on `development`), then run the hotfix; the gate archives the closed ledger aside on its own.
4
+
@@ -0,0 +1,2 @@
1
+ In the suite lead (MM-Work) a plain rcand, release or hotfix refuses: run `mmi-cli devops suite rcand --owner-go` or `mmi-cli devops suite release --owner-go` instead (a lead-only cut needs `--suite-solo --owner-go`). A suite member cannot run standalone rcand, release or hotfix: cut from the suite lead; only a backward-compatible solo backend release/hotfix with explicit owner approval may pass `--suite-solo --owner-go`, and its MM-Work development pin PR must land before the suite is in sync.
2
+
@@ -16,7 +16,7 @@ the [MMI help skill](../mmi-help/SKILL.md).
16
16
  command arguments, or repository environment files.
17
17
  - Personal agent instructions and host settings stay local and gitignored in product repositories.
18
18
  Do not commit AGENTS.md, CLAUDE.md, or copied plugin instructions.
19
- - Operate from the repo's `docs/Guides/operator.md`; update it with the code it covers.
19
+ - Update affected `docs/Guides/operator.md` instructions with code. Commit, then run existing fast preflight before pushes/PRs.
20
20
  - Claim approved work through the board, use the repository's checks, and land through MMI.
21
21
  A request for help or a plan does not authorize a change or a production release.
22
22
  - When an MMI tooling bug affects a developer other than `jervaise`, ask before filing a Hub report.
@@ -21,7 +21,8 @@ Feedback belongs on the MMI Hub board and never requires another developer's per
21
21
  Read the issue, implementation, callers, and repository policy before editing.
22
22
  Run `mmi-cli tests policy --base <comparison-base>` to determine required checks against your target
23
23
  branch (normally origin/development for train repositories). The repository's policy decides, not
24
- a universal test count. Use its build and check commands.
24
+ a universal test count. Run only the focused checks the repository policy permits and requires; do not
25
+ substitute a broad local build or full suite. Complete the documentation preflight below before pushing.
25
26
  Open a PR through `mmi-cli devops pr create` with a closing issue link, and land through
26
27
  `mmi-cli devops pr land`. Verify required checks, merge, issue closure, and cleanup before calling
27
28
  the implementation complete. A merged implementation is not a production release.
@@ -32,6 +33,23 @@ deterministic checks. Screen, geometry, motion, synthetic and Chrome suites run
32
33
  workflow armed nightly through the Hub schedule plane and on demand; it is never required, and a red
33
34
  run updates one `ci-heavy-red` issue. Never add such a suite to a gate or a local test policy.
34
35
 
36
+ ## Documentation preflight
37
+
38
+ Before each push or PR, follow this order:
39
+
40
+ 1. Review which docs and runbook sections the changed behavior affects, including declared coverage.
41
+ 2. Update those instructions with the code. When instructions genuinely remain unchanged, record a
42
+ precise `Runbook-Unaffected: <reason>` commit trailer explaining why they still apply.
43
+ 3. Commit the code, documentation, and any justified explanation. Coverage compares the committed
44
+ base...HEAD range; an uncommitted documentation edit is not part of that check.
45
+ 4. Run the repository's existing fast local preflight, normally `npm run check:local`, using the
46
+ documented equivalent where it has another name. Fix findings, commit corrections, and rerun.
47
+ 5. Push the branch, then open the PR.
48
+
49
+ Fast local and PR documentation checks remain backstops. Review guidance before them rather than
50
+ waiting for a rejected push or PR to discover documentation work. Keep heavy local aggregates out
51
+ of this sequence and retain the existing documentation/process exclusions from merge-queue checks.
52
+
35
53
  ## Report MMI tooling bugs
36
54
 
37
55
  This workflow covers bugs in MMI tooling, including its CLI, plugin, and shared workflows.
@@ -13,7 +13,7 @@ Merge `development → rc`, tag `vX.Y.0-rc.N`, push the tag first, wait for the
13
13
 
14
14
  ## Lane
15
15
 
16
- In the suite lead (MM-Work) a plain rcand, release or hotfix refuses: run `mmi-cli devops suite rcand --owner-go` or `mmi-cli devops suite release --owner-go` instead (a lead-only cut needs `--suite-solo --owner-go`). A suite member cannot run standalone rcand, release or hotfix: cut from the suite lead; only a backward-compatible solo backend release/hotfix with explicit owner approval may pass `--suite-solo --owner-go`, and its MM-Work development pin PR must land before the suite is in sync.
16
+ For an MM-Work suite lead or member, read [suite.md](suite.md) before choosing a lane.
17
17
 
18
18
  Read META and name the lane before describing or executing anything:
19
19
 
@@ -59,7 +59,7 @@ Then confirm origin: `git rev-list --count origin/rc..origin/development` is `0`
59
59
 
60
60
  ## Recovery
61
61
 
62
- Every refusal and every doctor blocker names its `code` and `anchor`: open `docs/Guides/train-troubleshooting.md#<code>` and follow that entry, nothing else. That guide is test-parsed, never free text — the infra drift and plugin-set suites validate every anchor cited here and assert policy from its wording — so an edit to it re-runs the infra suite (`npm --prefix infra test`) in the same PR: a heading or slug change is a code change. A candidate whose immutable rc tag passed policy but whose `origin/rc` was not pushed continues with `mmi-cli devops rcand --resume --watch --json --out <fresh-receipt>` — the only continuation form: `--resume` never re-tags, and `--apply` mints the next `-rc.N` for a new cut only. A promoted candidate whose rc deploy failed retries it, once the cause is fixed, with `mmi-cli devops rcand --retry-deploy --watch --json --out <fresh-receipt>` — same tag and SHA, never re-cut or re-pushed; `--resume` only re-reads that failed leg. Supersede is the last resort, with the authorized human's fresh approval. A refused train is a stop (Merge floor): fix the cause on `development` through a normal PR, then rerun; a rejected protected push leaves the local tag for an authorized rerun, never a force.
62
+ On any failure or interruption, read [recovery.md](recovery.md).
63
63
 
64
64
  ## Merge floor
65
65
 
@@ -0,0 +1,2 @@
1
+ Every refusal and every doctor blocker names its `code` and `anchor`: open `docs/Guides/train-troubleshooting.md#<code>` and follow that entry, nothing else. That guide is test-parsed, never free text — the infra drift and plugin-set suites validate every anchor cited here and assert policy from its wording — so an edit to it re-runs the infra suite (`npm --prefix infra test`) in the same PR: a heading or slug change is a code change. A candidate whose immutable rc tag passed policy but whose `origin/rc` was not pushed continues with `mmi-cli devops rcand --resume --watch --json --out <fresh-receipt>` — the only continuation form: `--resume` never re-tags, and `--apply` mints the next `-rc.N` for a new cut only. A promoted candidate whose rc deploy failed retries it, once the cause is fixed, with `mmi-cli devops rcand --retry-deploy --watch --json --out <fresh-receipt>` — same tag and SHA, never re-cut or re-pushed; `--resume` only re-reads that failed leg. Supersede is the last resort, with the authorized human's fresh approval. A refused train is a stop (Merge floor): fix the cause on `development` through a normal PR, then rerun; a rejected protected push leaves the local tag for an authorized rerun, never a force.
2
+
@@ -0,0 +1,2 @@
1
+ In the suite lead (MM-Work) a plain rcand, release or hotfix refuses: run `mmi-cli devops suite rcand --owner-go` or `mmi-cli devops suite release --owner-go` instead (a lead-only cut needs `--suite-solo --owner-go`). A suite member cannot run standalone rcand, release or hotfix: cut from the suite lead; only a backward-compatible solo backend release/hotfix with explicit owner approval may pass `--suite-solo --owner-go`, and its MM-Work development pin PR must land before the suite is in sync.
2
+
@@ -13,10 +13,7 @@ Full-track repos ship exactly what is on `rc` (never `development`): merge `rc
13
13
 
14
14
  ## Lane
15
15
 
16
- In the suite lead (MM-Work) a plain rcand, release or hotfix refuses: run `mmi-cli devops suite rcand --owner-go` or `mmi-cli devops suite release --owner-go` instead (a lead-only cut needs `--suite-solo --owner-go`). A suite member cannot run standalone rcand, release or hotfix: cut from the suite lead; only a backward-compatible solo backend release/hotfix with explicit owner approval may pass `--suite-solo --owner-go`, and its MM-Work development pin PR must land before the suite is in sync.
17
-
18
- Every new mm-work suite Release (including solo and hotfix releases) requires both `--announce-summary-file <path>` (English) and `--announce-summary-file-tr <path>` (Turkish, "sen" form) in the update-notes template. The canonical wording lives in the [MM-Work GUIDE, "Update notes"](https://github.com/mutmutco/MM-Work/blob/development/packages/mmwork-ds/GUIDE.md#update-notes); this repo links it and never copies it. Each file has the section word `Fixed` / `Düzeldi` (what works now, never the problem: "Drive: Scans of large drives now finish without timing out."; a line without now / again / no longer, or artık / yeniden / tekrar / yine, is refused) and/or `New` / `Yeni` (what you can now do) on its own line, Fixed first, each only when it has lines. Under them go 1–8 lines of `<Area>: <sentence>.`, each at most 120 characters, with no issue numbers, versions or commit prefixes. Off-template or missing files refuse before tagging. Do not write the head (app · date · time) or the thanks line: MM-Work renders both from the Release date and credits. Suite release also requires a valid `--member-summary-file <owner/repo=path>` and `--member-summary-file-tr <owner/repo=path>` for every member being released, before promoting any member. Carried, held and already-released members need none, and existing Releases can resume without new files.
19
- The `/user-releases` feed serves template notes as `updateNotes: {en?, tr?: {fixed[], new[]}}`. Older title-and-notes bodies keep `plainWords` and get `updateNotes: null`.
16
+ For an MM-Work suite lead or member, read [suite.md](suite.md) before choosing a lane or preparing update notes.
20
17
 
21
18
  Read META and name the lane before describing or executing anything:
22
19
 
@@ -26,7 +23,7 @@ mmi-cli oracle org project get {owner}/{repo} --json
26
23
 
27
24
  `releaseTrack: direct` → `development → main`, no `/rcand`. Absent → full (`rc → main`), except MMI-Hub, which is direct by the Hub-control special case. Full-track `--dev` ships `development → main` skipping rc and fails closed when `origin/rc` carries content not in `development`. Unreadable META → stop; never infer the lane from branch names, tags or release history.
28
25
 
29
- `deployModel: vercel` (on either track): the push to `main` is the go-live — Vercel builds `main` itself. The train dispatches nothing; with `--watch` it waits for the **Production** GitHub Deployment the Vercel GitHub app posts for the release SHA, probes the served URL once, and announces once it is green. Unwatched or timed out, the deploy leg stays `pending` and settles on `release --resume` or the next doctor `--heal`, which re-read the Deployment. No version-file bump on a vercel repo — the tag is the version. The receipt's `workflowRuns` carries a `vercel` row with the Vercel log URL; `pending` with "is the Vercel GitHub app connected?" means no Deployment was ever posted — connect the app, never re-tag.
26
+ For `deployModel: vercel`, read [vercel.md](vercel.md) before applying or interpreting deployment status.
30
27
 
31
28
  ## Preflight
32
29
 
@@ -80,19 +77,18 @@ Read the saved result, never the exit code (exit `1` = a follow-up failed after
80
77
 
81
78
  - `releaseVerdict.releaseStatus` + `followUpStatus` — `succeeded` + `pending` is shipped with the follow-up unresolved: neither failed nor done. A `--resume` receipt carries the same `releaseVerdict` (`command: release-resume`), plus its own `mode` and `state`.
82
79
  - `deployStatus` — `pending` is never terminal; `promoted: true` holds even when a deploy failed.
83
- - `served-channel(<product>)` row — the release's proof that new installs get it: the public `/release/version` pointer of the releasing repo's OWN channel (the Hub's `mmi`, or the product its registry `installer` declaration names; no declaration → no row), read after deploy and publish are green. `success` names the version read. `failure` is either a channel still on the previous version or a stale served process (the route answers 401/404): the note names what moves that channel and the exact remedy; run it, then re-read the pointer. `pending` means not probed (legs not green) or unreadable, never proof. The row is receipt evidence only, never a ledger leg, so it never blocks the next train.
80
+ - For a `served-channel(<product>)` row, read [verification.md](verification.md) and verify the installer channel before declaring completion.
81
+
84
82
  - `workflowRuns` — every run on the release SHA (deploy-model runs, any `push: main` deploy, the repo's own `gate`). On this host, discover them with the bounded run-list read below; filter locally by the exact receipt SHA. If the list hits its 100-row cap or returns no matching SHA, treat the run set as unverified and stop rather than infer completeness. Watch each matching non-success entry to conclusion: `gh run watch <run-id> --repo {owner}/{repo} --exit-status --interval 30`. Name which run is red: `deploy.yml` / `publish.yml` / `tenant-deploy.yml` / `jerv-gateway` is a deploy or publish verdict; a `gate` push run is ordinary CI sharing the SHA.
85
83
  - With `--watch`, require every follow-up complete before declaring the cut done. For any pending ledger leg, read [recovery.md](recovery.md) before continuing or starting a new train.
86
84
  - `announceNote` — `announced` (to the alerts channel for the Hub, to the project release channel for a product repo), `skipped`, `not announced` (deploy or publish not green; the note says whether a `--resume` carrying the same `--announce-summary-file` will post it), or the failure note. The channel id itself is never printed.
87
85
  - `devRollForward` / `rcAlignment` — `pushed`, or `pr-pending` with the alignment PR to land by true merge (Merge floor). An enqueued auto-merge is not evidence of the method; before reporting, read the enqueued method back — `gh pr view <n> --json autoMergeRequest --jq .autoMergeRequest.mergeMethod` — and prove it is `MERGE`. An enqueued `SQUASH` is a stop under the Merge floor, not something to wait out.
88
86
  - `versionFold`, `rcRetirement`, `checkout` — `returned`, or the named reason you are still on `main`.
89
- - A `private` npm surface (`surfaces.json`): the doctor's `npm-token-rejected` finding comes first. A 401 from `npm whoami` through the `_org` `npm/NPM_TOKEN` vault hop means the token is rejected, and every authenticated `npm view` then 404s exactly like an absent package — rotate the token; only after a login is a restricted 404 "unverifiable from here", never "not published" (`publish-private-package`). On a repo whose workflows publish through Trusted Publishing and name no registry token, that row is `info`, not a warning: no lane consumes the org token, so it constrains your manual read only.
87
+ For a private npm surface, read [verification.md](verification.md) before interpreting authentication or package visibility.
90
88
 
91
89
  Then verify Latest with this bounded one-row read; require `isLatest: true` and `tagName` equal to the resolved tag: `gh release list --repo {owner}/{repo} --limit 1 --json tagName,isLatest,publishedAt`.
92
90
 
93
- For the independent run recheck, scope the read to the release window, so a busy repo's unrelated runs cannot fill the cap: note the UTC instant you started `--apply` (`date -u +%Y-%m-%dT%H:%M:%SZ`), then run `gh run list --repo {owner}/{repo} --created ">=<apply-start-utc>" --limit 100 --json databaseId,headSha,workflowName,status,conclusion,url > .jerv/tmp/release-runs.json`, then filter locally by the exact SHA from the saved receipt with `node -e 'const rows = require("./.jerv/tmp/release-runs.json"); const sha = process.argv[1]; const matches = rows.filter((run) => run.headSha === sha); if (rows.length >= 100 || !matches.length) throw new Error("release run set is incomplete or absent"); console.log(JSON.stringify(matches, null, 2));' "<release-sha>"`.
94
-
95
- Do not use the raw `releases/latest` API route, a `--jq` formatter, or `gh run list --commit`; if the bounded list reaches 100 rows or has no SHA match, the run set is unverified and must not be called green.
91
+ For the independent run recheck, read [verification.md](verification.md) and use its bounded release-window recipe.
96
92
 
97
93
  Four facts close the verdict: the `origin/main..origin/development` count, the tag on origin, Latest, and green runs on the release SHA.
98
94
 
@@ -0,0 +1,5 @@
1
+ In the suite lead (MM-Work) a plain rcand, release or hotfix refuses: run `mmi-cli devops suite rcand --owner-go` or `mmi-cli devops suite release --owner-go` instead (a lead-only cut needs `--suite-solo --owner-go`). A suite member cannot run standalone rcand, release or hotfix: cut from the suite lead; only a backward-compatible solo backend release/hotfix with explicit owner approval may pass `--suite-solo --owner-go`, and its MM-Work development pin PR must land before the suite is in sync.
2
+
3
+ Every new mm-work suite Release (including solo and hotfix releases) requires both `--announce-summary-file <path>` (English) and `--announce-summary-file-tr <path>` (Turkish, "sen" form) in the update-notes template. The canonical wording lives in the [MM-Work GUIDE, "Update notes"](https://github.com/mutmutco/MM-Work/blob/development/packages/mmwork-ds/GUIDE.md#update-notes); this repo links it and never copies it. Each file has the section word `Fixed` / `Düzeldi` (what works now, never the problem: "Drive: Scans of large drives now finish without timing out."; a line without now / again / no longer, or artık / yeniden / tekrar / yine, is refused) and/or `New` / `Yeni` (what you can now do) on its own line, Fixed first, each only when it has lines. Under them go 1–8 lines of `<Area>: <sentence>.`, each at most 120 characters, with no issue numbers, versions or commit prefixes. Off-template or missing files refuse before tagging. Do not write the head (app · date · time) or the thanks line: MM-Work renders both from the Release date and credits. Suite release also requires a valid `--member-summary-file <owner/repo=path>` and `--member-summary-file-tr <owner/repo=path>` for every member being released, before promoting any member. Carried, held and already-released members need none, and existing Releases can resume without new files.
4
+ The `/user-releases` feed serves template notes as `updateNotes: {en?, tr?: {fixed[], new[]}}`. Older title-and-notes bodies keep `plainWords` and get `updateNotes: null`.
5
+
@@ -0,0 +1,2 @@
1
+ `deployModel: vercel` (on either track): the push to `main` is the go-live — Vercel builds `main` itself. The train dispatches nothing; with `--watch` it waits for the **Production** GitHub Deployment the Vercel GitHub app posts for the release SHA, probes the served URL once, and announces once it is green. Unwatched or timed out, the deploy leg stays `pending` and settles on `release --resume` or the next doctor `--heal`, which re-read the Deployment. No version-file bump on a vercel repo — the tag is the version. The receipt's `workflowRuns` carries a `vercel` row with the Vercel log URL; `pending` with "is the Vercel GitHub app connected?" means no Deployment was ever posted — connect the app, never re-tag.
2
+