@mutmutco/codex-plugin 4.5.161 → 4.5.163
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/.codex-plugin/plugin.json +1 -1
- package/cli/dist/main.cjs +1183 -956
- package/cli/package.json +1 -1
- package/package.json +1 -1
- package/skills/board/SKILL.md +3 -20
- package/skills/board/examples.md +12 -0
- package/skills/board/reference.md +11 -0
- package/skills/bootstrap/SKILL.md +5 -418
- package/skills/bootstrap/reference.md +421 -0
- package/skills/hotfix/SKILL.md +2 -4
- package/skills/hotfix/recovery.md +4 -0
- package/skills/hotfix/suite.md +2 -0
- package/skills/org-guide/SKILL.md +1 -1
- package/skills/org-guide/references/work-and-releases.md +19 -1
- package/skills/rcand/SKILL.md +2 -2
- package/skills/rcand/recovery.md +2 -0
- package/skills/rcand/suite.md +2 -0
- package/skills/release/SKILL.md +6 -10
- package/skills/release/suite.md +5 -0
- package/skills/release/vercel.md +2 -0
- package/skills/release/verification.md +7 -0
- package/skills/secrets/SKILL.md +3 -64
- package/skills/secrets/reference.md +67 -0
- package/skills/stage/SKILL.md +4 -96
- package/skills/stage/live.md +34 -0
- package/skills/stage/reference.md +49 -0
- package/skills/stage/smoke.md +17 -0
package/cli/package.json
CHANGED
package/package.json
CHANGED
package/skills/board/SKILL.md
CHANGED
|
@@ -44,17 +44,7 @@ Refs are `[RepoName#N](url) · short title` — no `owner/` prefix, no `[type]`
|
|
|
44
44
|
its essence. **Taken** renders id · status · owner only, never the title. Skip empty sections silently.
|
|
45
45
|
One screen.
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
>
|
|
49
|
-
> **On your plate** — two train fixes in flight:
|
|
50
|
-
> - [MMI-Hub#834](https://github.com/mutmutco/MMI-Hub/issues/834) · automated hotfix apply path
|
|
51
|
-
> - [MMI-Hub#841](https://github.com/mutmutco/MMI-Hub/issues/841) · rcand stuck on required checks
|
|
52
|
-
>
|
|
53
|
-
> **Up for grabs** — nine open, I can take them in three PRs:
|
|
54
|
-
> - [MMI-Hub#821](https://github.com/mutmutco/MMI-Hub/issues/821) · redesign tenant env-writer
|
|
55
|
-
>
|
|
56
|
-
> **Taken**
|
|
57
|
-
> - MMI-Hub#827 · In Progress · @otherdev
|
|
47
|
+
For a rendering example, read [examples.md](examples.md).
|
|
58
48
|
|
|
59
49
|
Status moves happen automatically as work flows — claim, PR open, merge. Never offer a status move.
|
|
60
50
|
|
|
@@ -114,9 +104,7 @@ the per-item results rather than assuming the batch took. **Route is `oracle boa
|
|
|
114
104
|
no `oracle issue claim` and no flat `board claim` — both refuse. Guessing either burns a call.
|
|
115
105
|
|
|
116
106
|
Then one worktree per batch, all batches concurrently. The host owns worktree mechanics; MMI owns board
|
|
117
|
-
and PR facts.
|
|
118
|
-
`git worktree add` (#5499). Linked worktrees share one `.git`, so serialize `git branch -m` and other ref
|
|
119
|
-
renames across them, or retry serially when the error names `.tmp-renamed-log` (#5485).
|
|
107
|
+
and PR facts. For worktree setup on Windows or concurrent ref renames, read [reference.md](reference.md).
|
|
120
108
|
|
|
121
109
|
## Step 5 — land one PR per batch
|
|
122
110
|
|
|
@@ -141,12 +129,7 @@ Materialize the body as a real UTF-8 file and pass its path; do not pipe it to `
|
|
|
141
129
|
When an issue must stay open, write `Part of #N` or `Refs #N`. **Never** write `Does not close #N` —
|
|
142
130
|
GitHub closes it anyway (JC#495).
|
|
143
131
|
|
|
144
|
-
|
|
145
|
-
after a red check: when the batch's touch-set intersects that repo's guidance files (Jerv-JervCode:
|
|
146
|
-
`product/soul.md`, `product/runbooks/**`, `packages/*/skills/**/SKILL.md`), the body must carry a
|
|
147
|
-
standalone `learning: none — <reason>` line unless a closing learning issue's `promote.globs` names a
|
|
148
|
-
changed file. The line must stand alone on its own newline — a literal `\n` inside an inline edit fails
|
|
149
|
-
the gate again.
|
|
132
|
+
When the repo runs a guidance-learning gate, read [reference.md](reference.md) before preparing the PR body.
|
|
150
133
|
|
|
151
134
|
Landing requires a merged receipt before any cleanup: `state` `MERGED`, the head ref and OID matching the
|
|
152
135
|
local task branch, and a clean worktree. Anything else means retain and report, never clean.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
> 👋 Welcome back, @dev — here's your board on **MMI-Hub**.
|
|
2
|
+
>
|
|
3
|
+
> **On your plate** — two train fixes in flight:
|
|
4
|
+
> - [MMI-Hub#834](https://github.com/mutmutco/MMI-Hub/issues/834) · automated hotfix apply path
|
|
5
|
+
> - [MMI-Hub#841](https://github.com/mutmutco/MMI-Hub/issues/841) · rcand stuck on required checks
|
|
6
|
+
>
|
|
7
|
+
> **Up for grabs** — nine open, I can take them in three PRs:
|
|
8
|
+
> - [MMI-Hub#821](https://github.com/mutmutco/MMI-Hub/issues/821) · redesign tenant env-writer
|
|
9
|
+
>
|
|
10
|
+
> **Taken**
|
|
11
|
+
> - MMI-Hub#827 · In Progress · @otherdev
|
|
12
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
On Windows set `git config core.longpaths true` on the main checkout before the first
|
|
2
|
+
`git worktree add` (#5499). Linked worktrees share one `.git`, so serialize `git branch -m` and other ref
|
|
3
|
+
renames across them, or retry serially when the error names `.tmp-renamed-log` (#5485).
|
|
4
|
+
|
|
5
|
+
A repo running a guidance-learning gate also needs the body's learning line PLANNED here, not patched in
|
|
6
|
+
after a red check: when the batch's touch-set intersects that repo's guidance files (Jerv-JervCode:
|
|
7
|
+
`product/soul.md`, `product/runbooks/**`, `packages/*/skills/**/SKILL.md`), the body must carry a
|
|
8
|
+
standalone `learning: none — <reason>` line unless a closing learning issue's `promote.globs` names a
|
|
9
|
+
changed file. The line must stand alone on its own newline — a literal `\n` inside an inline edit fails
|
|
10
|
+
the gate again.
|
|
11
|
+
|
|
@@ -15,80 +15,7 @@ invoke the skill (the master holds the App key), not the caller's GitHub role.
|
|
|
15
15
|
Bootstrap operates on the target repo entirely through the App — **no per-repo checkout**. The repo must
|
|
16
16
|
already be named on the taxonomy `<CATEGORY>-<PascalName>`.
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
The org-standard scaffolding is a **machine-readable manifest** — `skills/bootstrap/seeds/manifest.json`
|
|
21
|
-
(loaded by the CLI; `mmi-cli devops bootstrap apply <repo> [--execute]` consumes it). Every seed carries an **ownership**:
|
|
22
|
-
|
|
23
|
-
- **`org`** — org-delivered, **overwritten on upgrade** (the org owns it): the issue templates, the gate
|
|
24
|
-
workflow, and the org-managed `.gitignore` block. `source: self` = copied verbatim from MMI-Hub's own
|
|
25
|
-
current file. Personal **agent guides** (`AGENTS.md`/`CLAUDE.md`/`.claude/settings.json`) are **never**
|
|
26
|
-
seeded — they are developer-owned and gitignored, carried per machine by each developer's own plugins
|
|
27
|
-
(Jervaise's ride Jerv PowerTools), not delivered, overwritten, or fanned out by MMI; the org push ruleset
|
|
28
|
-
`mmi-no-agent-files-org` even blocks committing them. Board moves are central (the Hub webhook), so **no
|
|
29
|
-
per-repo board workflow is stamped**.
|
|
30
|
-
- **`repo`** — created **once on a fresh bootstrap**, **never clobbered on upgrade** (the repo owns its
|
|
31
|
-
content): `README.md`, `architecture.md`.
|
|
32
|
-
These render `seeds/*.template.*` with `{{PLACEHOLDERS}}`.
|
|
33
|
-
|
|
34
|
-
So **create** stamps every seed; **upgrade** refreshes the `org` seeds and adds any *missing* `repo` seeds
|
|
35
|
-
without overwriting existing repo-owned files (D35: legacy docs are archived + written fresh, never carried
|
|
36
|
-
over verbatim). The manifest's `labels` list is the canonical label set — three: `bug`, `feature`, `task`.
|
|
37
|
-
Priority is a **board field**, never a label (#1454 retired the four `priority:*` labels; `bootstrap verify`
|
|
38
|
-
checks they are gone). The per-step instructions below
|
|
39
|
-
are the manual path; `bootstrap apply <repo> [--execute]` automates them from this manifest.
|
|
40
|
-
For a registry-configured gate, use `mmi-cli devops bootstrap apply <repo> --only gate.yml --from-config`
|
|
41
|
-
(dry-run) then add `--execute` to propose the file on a review branch + PR. This uses the same
|
|
42
|
-
registry `gate` render as `ci reconcile`. An unstamped legacy gate must first match the registry
|
|
43
|
-
render exactly; the first PR then adopts it by adding a hashed render stamp. After adoption, changing
|
|
44
|
-
the registry config produces an update PR if the committed stamp still verifies. A stamped gate edited
|
|
45
|
-
by hand, or a divergent unstamped gate, is refused with a diff: make the registry config reproduce an
|
|
46
|
-
unstamped file first, then re-run to adopt. Edit the registry config, never the stamped file.
|
|
47
|
-
|
|
48
|
-
**`bootstrap apply` is a single-repo tool** — fresh bootstrap/onboarding, or refreshing one repo's own
|
|
49
|
-
drifted seed. It is **never** the fleet-wide fan-out for an `org`-owned seed edit: that write path is
|
|
50
|
-
propagation from a Hub merge (#4233), and the seeded `agent-pr.yml` itself now fails the merge gate on
|
|
51
|
-
any repo that receives a hand-edit to an org-owned target path outside a propagation/bootstrap-delivery
|
|
52
|
-
branch (#4241). Running `apply --execute` against every registry repo by hand to fan out a Hub edit is
|
|
53
|
-
exactly the copy-per-repo pattern this rule exists to end — see Hub#4234.
|
|
54
|
-
|
|
55
|
-
**Rollback is per-repo, never a second fleet overwrite (#4240).** When a propagated `org`-owned seed
|
|
56
|
-
breaks a repo, the recovery is `mmi-cli devops bootstrap rollback <repo> --target <t> [--execute]` — it resolves
|
|
57
|
-
the ONE merge commit that repo's `bootstrap propagate` (#4238) run actually landed (live from the repo's
|
|
58
|
-
`seed-propagate-<slug>` merged-PR history, or replayed from a persisted propagate `--json` report via
|
|
59
|
-
`--record`) and opens an ordinary revert PR of it, through that repo's own gate, on a `seed-rollback-<slug>`
|
|
60
|
-
branch. It refuses — never guesses a commit — when no clean, single-file propagation record resolves for
|
|
61
|
-
that repo+target. **Explicitly forbidden as a rollback path: a fleet-wide `bootstrap apply --execute` of
|
|
62
|
-
"the old bytes"** — that is the second blind fleet overwrite #4233 rules out, and it races the propagation
|
|
63
|
-
lane. **MMI-Hub stays the source of truth during a rollback**: a per-repo revert is the emergency stop, not
|
|
64
|
-
the fix — closure is the Hub reverting (or fixing forward) the bad seed commit on `development`, after
|
|
65
|
-
which reverted repos match the Hub again and the drift alarm closes itself. A per-repo revert with no
|
|
66
|
-
Hub-side follow-up re-alarms as drift within a week, deliberately, so an emergency divergence can never
|
|
67
|
-
silently become permanent.
|
|
68
|
-
|
|
69
|
-
**Release gap — a new topology value must reach TWO surfaces, and a local dev build only fixes one (#2928).**
|
|
70
|
-
A new `--project-type`, `--deploy-model`, or `--release-track` lands on `development` but is not live until a
|
|
71
|
-
release train ships it — in **both** of these places:
|
|
72
|
-
|
|
73
|
-
1. **The published/installed `mmi-cli`**, which rejects the unknown enum with no hint that it merely lags
|
|
74
|
-
`development`. A local dev build routes around this:
|
|
75
|
-
`node <MMI-Hub checkout>/cli/dist/index.cjs bootstrap apply ...` from a `development` checkout (rebuild
|
|
76
|
-
`dist` first: `npm --prefix cli ci && npm --prefix cli run build`).
|
|
77
|
-
2. **The deployed registry Lambda**, which carries its *own* copies of the enums
|
|
78
|
-
(`infra/src/registry-route.ts` validates `projectType` **and** `deployModel`; `releaseTrack` is CLI-side
|
|
79
|
-
only) and **deploys from `main`**. A local dev build does nothing for this one.
|
|
80
|
-
|
|
81
|
-
So the local dev build is **not** a workaround for a just-merged enum value — it gets you further and then
|
|
82
|
-
fails at the step that matters. The CLI cheerfully accepts the new value, sends it, and the API rejects it:
|
|
83
|
-
|
|
84
|
-
```
|
|
85
|
-
ddb register <slug> (failed: HTTP 400 — projectType must be one of:
|
|
86
|
-
web-app, hub-service, content, desktop-game, non-deployable, cli-tool, worker)
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
That failure lands **after** seeding, labels, and the ruleset, so the repo is left half-provisioned with an
|
|
90
|
-
almost-empty registry META row. **Bootstrapping against a topology value merged since the last train requires
|
|
91
|
-
a `/release` to `main` first.** The local dev build alone suffices only for values already live in the API.
|
|
18
|
+
Read [reference.md](reference.md) when choosing create vs upgrade, refreshing seeds, rolling back propagation, or using newly released topology values.
|
|
92
19
|
|
|
93
20
|
## Step 0 — capability shape (confirm the four axes before any mutation)
|
|
94
21
|
|
|
@@ -154,336 +81,15 @@ acceptable end states (#2928):**
|
|
|
154
81
|
section — GitHub's contents API lags for a minute or so right after a merge. **Re-run before believing a
|
|
155
82
|
content-read failure**; it clears on its own.
|
|
156
83
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
If `$OWNER/$REPO` is not on GitHub yet, create it and seed an initial commit **before** Step 1 — a brand-new
|
|
160
|
-
repo has no commits, so there is no branch to push and no default branch to set. **Create the first commit
|
|
161
|
-
through the contents API, on every track** (#3655) — an API write is not a push, so neither the #1660
|
|
162
|
-
protected-branch guard nor `mmi-train-floor` objects, and it is the same server-side mechanism Step 1 uses
|
|
163
|
-
for `rc`/`main` (#3433). `$FIRST` is the track's first branch: `development` for **full**/**direct**,
|
|
164
|
-
`main` for **trunk**:
|
|
165
|
-
|
|
166
|
-
```bash
|
|
167
|
-
FIRST=development # full / direct
|
|
168
|
-
FIRST=main # trunk (content)
|
|
169
|
-
|
|
170
|
-
# One-time namespace + first-ref creation uses the authenticated master-admin GitHub session. The current
|
|
171
|
-
# mmi-cli has no App-backed command for a repository that does not exist; this is the named exception.
|
|
172
|
-
gh repo create "$OWNER/$REPO" --private --disable-wiki
|
|
173
|
-
gh api -X PUT "repos/$OWNER/$REPO/contents/.gitkeep" \
|
|
174
|
-
-f message="chore: initial commit" -f content="Cg==" -f branch="$FIRST"
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
`--disable-wiki` seeds `has_wiki=false` at creation: wikis are retired org-wide (compute-at-read, #4206),
|
|
178
|
-
GitHub defaults new repos to `has_wiki=true`, and `bootstrap apply --execute` / `bootstrap reconcile --apply`
|
|
179
|
-
enforce the same rule as drift checks afterwards.
|
|
180
|
-
|
|
181
|
-
On an empty repo that PUT creates `$FIRST` **and leaves it the default branch**, so no `gh repo edit
|
|
182
|
-
--default-branch` is needed here. Skip this step when the repo already exists with the track's default branch.
|
|
183
|
-
|
|
184
|
-
The two `gh` writes above are authorized by the authenticated master-admin login and are the one-time
|
|
185
|
-
bootstrap exception while no App-backed `mmi-cli` command owns a repository that does not yet exist. They
|
|
186
|
-
are not a general write lane: after the namespace and first ref exist, use the App-backed
|
|
187
|
-
`mmi-cli devops bootstrap apply --execute` path for managed seeds, labels, rulesets, and registry state.
|
|
188
|
-
|
|
189
|
-
For a **content** repo the API form is not merely tidier, it is the only thing that works: `main` is that
|
|
190
|
-
track's first and only branch, so the initial commit IS a push to `main` and the #1660 guard fences it — and
|
|
191
|
-
the deny's own advice ("land through a CI-gated PR to development") is meaningless on a repo with no commits,
|
|
192
|
-
no `development`, and no CI (#3541).
|
|
193
|
-
|
|
194
|
-
The old recipe here was a local `git init` + empty commit + `git push` for deployable repos, opening with
|
|
195
|
-
`gh repo create --private --confirm`. **That flag no longer exists** — `gh` removed it, so the first command
|
|
196
|
-
of the first step errored out. The local-git ritual it opened is also unnecessary: the API form above does
|
|
197
|
-
the same job for `development` in one call.
|
|
198
|
-
|
|
199
|
-
## Step 1 — branches
|
|
200
|
-
|
|
201
|
-
Determine the repo's **release track** first — the branch set follows the track, not just the class (#1097),
|
|
202
|
-
so a direct-track repo never gets a stray `rc` the `mmi-train-floor` ruleset can't clean up:
|
|
203
|
-
|
|
204
|
-
- **full** (deployable default) — three permanent branches, default `development`: `development`, `rc`, `main`.
|
|
205
|
-
- **direct** (deployable, skips rc — e.g. cli-tool/worker/Hub) — two permanent branches, default
|
|
206
|
-
`development`: `development`, `main`.
|
|
207
|
-
- **trunk** (content) — one permanent branch, default `main`: `main` only.
|
|
208
|
-
|
|
209
|
-
Ensure exactly the track's permanent branches exist and set the default branch — create only what the track
|
|
210
|
-
uses; never create an `rc` for a direct-track repo.
|
|
211
|
-
|
|
212
|
-
**`bootstrap apply --execute` creates `rc`/`main` server-side from the track's first branch (#6070).** The
|
|
213
|
-
local **#1660 protected-branch push guard** fences every push to `main`/`master`/`rc`, including the
|
|
214
|
-
legitimate *creation* of those refs on a brand-new repo (#3433); a ref created through the API is not a
|
|
215
|
-
force-push, so both the guard and the `mmi-train-floor` ruleset allow it. The apply's train-readiness plan
|
|
216
|
-
prints one `lane-branch` row per track branch — `CREATE` (missing), `SKIP` (already exists), or `GAP`
|
|
217
|
-
(`bootstrap-gap`: the first branch from Step 0b is missing, so there is no source ref) — and the live apply
|
|
218
|
-
creates every `CREATE` row; a `422 Reference already exists` reply is the idempotent no-op, not a failure.
|
|
219
|
-
`development` (or `main`, for a content repo) already exists from Step 0b. Only the default branch stays a
|
|
220
|
-
`gh` write:
|
|
221
|
-
```bash
|
|
222
|
-
gh repo edit "$OWNER/$REPO" --default-branch development # full / direct
|
|
223
|
-
gh repo edit "$OWNER/$REPO" --default-branch main # trunk (content)
|
|
224
|
-
```
|
|
225
|
-
Verify with `gh api "repos/$OWNER/$REPO/branches" --jq '.[].name'`, or read the `lane-branch` rows of the
|
|
226
|
-
next dry-run `bootstrap apply`.
|
|
227
|
-
|
|
228
|
-
## Step 2 — authority (org Ruleset)
|
|
229
|
-
|
|
230
|
-
Confirm the org-level rulesets already target this repo (they apply org-wide): **`mmi-branch-protection`**
|
|
231
|
-
PR-gates `development` and blocks force-push/deletion there (bypass = org admins + the GitHub App 3026732),
|
|
232
|
-
and **`mmi-train-floor`** blocks force-push/deletion on `rc`/`main` (no PR rule — the train pushes merges and
|
|
233
|
-
tags directly; no bypass). (Definitions mirror live in `.github/rulesets/mmi-branch-protection.json` and
|
|
234
|
-
`.github/rulesets/mmi-train-floor.json`.) MMI-Hub additionally has its own repository ruleset,
|
|
235
|
-
`.github/rulesets/mmi-hub-required-checks.json`, for the Hub-only `cli`, `infra`, and `docs` jobs; do not
|
|
236
|
-
apply those contexts org-wide unless every target repo exposes them.
|
|
237
|
-
|
|
238
|
-
## Step 2b — lock the train branches (who can push)
|
|
239
|
-
|
|
240
|
-
The ruleset says *a PR is required*; this says *who may merge it*. `bootstrap apply --execute` applies the
|
|
241
|
-
classic branch protection on `development`/`rc`/`main` for deployable repos, or just `main` for content
|
|
242
|
-
repos, with **"Restrict who can push"** = the org owners + the App (#6070): the owners are resolved live
|
|
243
|
-
from the GitHub org admins (`orgs/$OWNER/members?role=admin`), never a hard-coded user list — an empty
|
|
244
|
-
resolution refuses to write an empty allowlist — and the App is `mmi-github-app`. The repo's full-write
|
|
245
|
-
people get added at Step 4b. Everyone else is `write`-locked on protected branches: they push feature
|
|
246
|
-
branches and open PRs but cannot merge there.
|
|
247
|
-
|
|
248
|
-
The plan prints one `push-allowlist` row per track branch — `UPDATE` (preserve every existing
|
|
249
|
-
review/status/admin/force-push bit and replace only the push restriction), `SKIP` (exact owners + App
|
|
250
|
-
already present), or `GAP` (`bootstrap-gap`: the protection read failed; zero writes are performed while any
|
|
251
|
-
control-plane read is unverified). Only the master (sole repo `admin`) can change this afterward. Full
|
|
252
|
-
grant/lock mechanics + inspect commands: `docs/Guides/repo-access.md`.
|
|
253
|
-
|
|
254
|
-
## Step 3 — attach to the repo's Project (one board per repo; confirm)
|
|
255
|
-
|
|
256
|
-
**One board per repo, named after it** — that is the convention the org actually runs, and what the rest of
|
|
257
|
-
the system assumes: the registry META carries a single `projectId` per `PROJECT#<slug>`, and the Hub webhook
|
|
258
|
-
adds each new issue to that one board. There are no division boards; a `<CATEGORY>`-prefixed default would
|
|
259
|
-
resolve to nothing (#3542). Still **confirm with the master** — repos and projects are genuinely **not 1:1**,
|
|
260
|
-
and joining an existing board remains a legitimate answer for a repo that belongs to an existing effort.
|
|
261
|
-
```bash
|
|
262
|
-
gh project list --owner "$PROJECT_OWNER" --format json # existing boards to choose from
|
|
263
|
-
```
|
|
264
|
-
- **Attach** to the chosen project:
|
|
265
|
-
```bash
|
|
266
|
-
gh project link "$PROJECT_NUMBER" --owner "$PROJECT_OWNER" --repo "$OWNER/$REPO"
|
|
267
|
-
```
|
|
268
|
-
- **Create** the chosen board if it doesn't exist yet — clone the **named template board**, never an
|
|
269
|
-
arbitrary existing one, so the 4-lane `Status` field (`Todo · In Progress · In Review · Done`), the
|
|
270
|
-
built-in workflows, **and** the org's view/card-field shape (#4093) all carry over instead of whatever
|
|
271
|
-
the previous copy-of-a-copy happened to drift to:
|
|
272
|
-
```bash
|
|
273
|
-
gh project copy 4 --source-owner "$PROJECT_OWNER" \
|
|
274
|
-
--target-owner "$PROJECT_OWNER" --title "<Project>"
|
|
275
|
-
```
|
|
276
|
-
**Project 4 (MMI-Hub's own board) is the template** (#4093) — it already carries the org-standard view
|
|
277
|
-
triple (`List`/`Board`/`Roadmap`), Board grouping (columns=Status, swimlanes=Repository), and the
|
|
278
|
-
canonical Board card-field set (`Title, Assignees, Status, Labels, Linked pull requests, Parent issue,
|
|
279
|
-
Sub-issues progress, Priority`). `bootstrap verify` asserts all three against every board (next note); a
|
|
280
|
-
freshly copied project starts green on them instead of inheriting a stale source's drift. Copying from
|
|
281
|
-
anything else re-introduces the "5 card-field variants across 16 boards" problem #4093 was filed to end.
|
|
282
|
-
Then **interview the master** for the seed short description + README (what it tracks, member repos, links
|
|
283
|
-
to the repos' `README.md`/`architecture.md`); set them via the `updateProjectV2` mutation
|
|
284
|
-
(`shortDescription`, `readme`). If cloning, verify the built-in workflows survived (next note).
|
|
285
|
-
- **Built-in workflows:** `mmi-cli devops bootstrap verify` checks these Project workflows are enabled:
|
|
286
|
-
`Auto-add sub-issues to project`, `Auto-archive items`, `Item added to project`, and `Item closed`. The
|
|
287
|
-
Todo/In Progress/In Review moves are **central** (the Hub webhook); the built-in `Item closed` sets `Done`
|
|
288
|
-
on merge. GitHub's public GraphQL schema exposes delete/read surfaces for Project workflows but no
|
|
289
|
-
create/update/enable mutation; if any required workflow is missing or disabled, repair it in the project's
|
|
290
|
-
**Workflows** settings before calling the repo ready for developers.
|
|
291
|
-
- **Board shape (#4093):** `mmi-cli devops bootstrap verify` also checks the view triple (`List`/`Board`/`Roadmap`
|
|
292
|
-
by name **and** layout), the Board view's grouping, and its card fields against the standard the template
|
|
293
|
-
board (project 4) carries. Grouping (columns=`Status`, swimlanes=`Repository`) has **no** GraphQL create/
|
|
294
|
-
update mutation (`ProjectV2ViewConfigurationInput` carries only `visibleFieldIds`) — a drifted swimlane or
|
|
295
|
-
column can only be fixed in the UI: Board view → ⚙ (top-right) → Group by / Swimlanes → Save view. The
|
|
296
|
-
view triple and the card fields (`Title, Assignees, Status, Labels, Linked pull requests, Parent issue,
|
|
297
|
-
Sub-issues progress, Priority`) ARE API-writable (`createProjectV2View`/`updateProjectV2View`), so a
|
|
298
|
-
drifted one can be fixed live instead of only reported.
|
|
299
|
-
|
|
300
|
-
Record the chosen `projectNumber`/`projectId` plus Status/Priority field ids in the Hub registry META
|
|
301
|
-
(`PROJECT#<slug>`) by re-running `mmi-cli devops bootstrap apply --execute` **after** `gh project link`. Apply reads
|
|
302
|
-
the repo's linked board and derives all of them itself (#3543) — pass `--var PROJECT_ID=<node id>` only to
|
|
303
|
-
override, which is also what you need when the repo is linked to more than one board and apply therefore
|
|
304
|
-
refuses to guess. **Verify the row afterwards** (`mmi-cli oracle org project get <owner/repo>`): a registered repo
|
|
305
|
-
whose META carries no `projectId`/`statusFieldId` looks finished everywhere else while the Hub webhook has
|
|
306
|
-
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
|
|
307
|
-
`Status: Todo`.
|
|
308
|
-
|
|
309
|
-
**Register the project in the Hub registry.** The same registry META row carries `{name, slug, projectId,
|
|
310
|
-
repos[]}`; do not append to a committed `projects.json`. Repo wikis and doc freshness are owned by
|
|
311
|
-
the repo itself. If attaching this repo to an existing project, merge this repo into that project's `repos[]`
|
|
312
|
-
instead of creating a new project.
|
|
313
|
-
|
|
314
|
-
## Step 4 — vault tiers + deploy substrate
|
|
315
|
-
|
|
316
|
-
Provision the repo's vault namespace and deploy substrate from the Hub, not from repo-local Actions
|
|
317
|
-
secrets. Runtime config names live in the two-tier vault (`/mmi-future/<slug>/dev|rc|main/*`) and are
|
|
318
|
-
managed through `/secrets`; never use `gh secret set` for product runtime config.
|
|
319
|
-
|
|
320
|
-
The default `tenant-container` substrate is a **Hetzner box** (`hetzner-ssh`): the deploy streams the stage's
|
|
321
|
-
vault values into a tmpfs runtime env (no `.env` file) and runs the container via docker-compose, deployed over the Hub's bounded
|
|
322
|
-
SSH lane. Do **not** create an AWS OIDC deploy role or a repo deploy Action for it. **Ask the master for the
|
|
323
|
-
box assignment** — the `sshHost` (and the loopback port) per stage — then write the `DEPLOY#<stage>` rows with
|
|
324
|
-
`mmi-cli oracle org project set-deploy <owner/repo> --stage <dev|rc|main> --ssh-host <host> [--port <p>]` (defaults:
|
|
325
|
-
`substrate: hetzner-ssh`, deploy path `/opt/mmi/<slug>/<stage>`, service = slug, ssh-user `root`). Without those
|
|
326
|
-
rows the tenant cannot deploy (`tenant-deploy.yml` errors on missing `DEPLOY#` coords), so do not skip this.
|
|
327
|
-
`bootstrap apply` prints one `deploy-row` row per track stage for container models (#6070): `SKIP` when the
|
|
328
|
-
registry row exists, `GAP` (`bootstrap-gap`) with the exact `set-deploy` command otherwise (for
|
|
329
|
-
`tenant-container` it carries `--no-env-file true`, matching the fileless compose seed; run it from the tenant
|
|
330
|
-
checkout once that compose is on the stage branch) — a box
|
|
331
|
-
assignment cannot be invented, so this row is the one train-readiness fact the apply never writes itself.
|
|
332
|
-
Keep every runtime config value in the vault; never paste secret values into logs.
|
|
333
|
-
|
|
334
|
-
Only an AWS `tenant-container` (the exception) provisions the reusable tenant stack: release bucket,
|
|
335
|
-
per-stage OIDC deploy role, and constrained service-control role/document. Either way the deploy path
|
|
336
|
-
trusts the Hub's central `tenant-deploy.yml` / `tenant-control.yml` environments, not a product repo deploy
|
|
337
|
-
workflow.
|
|
338
|
-
|
|
339
|
-
## Step 4b — developer access
|
|
340
|
-
|
|
341
|
-
**Repository Environments (#4458, plan-gating #5032)** — the track's deployment environments are
|
|
342
|
-
control-plane state this skill's own `bootstrap verify` asserts, so live apply provisions them itself:
|
|
343
|
-
`mmi-cli devops bootstrap apply --execute` creates `dev`/`rc`/`main` (per the release track: direct has no
|
|
344
|
-
`rc`, trunk has `main` only) with a **deployment-branch policy = protected branches only** — the one
|
|
345
|
-
protection the check exists to carry. On an org plan whose billing entitlement rejects protection rules
|
|
346
|
-
(an explicit `wait_timer` or required `reviewers` comes back 422 naming the plan), apply provisions the
|
|
347
|
-
settable policy and prints `plan-gated protections` for those rules; `bootstrap verify` then reports the
|
|
348
|
-
environment rows as green with an `advisory:` detail naming what the plan cannot set — never a FAIL. The
|
|
349
|
-
policy is set either way, so a repo on the lower plan is still protected and ready for developers.
|
|
350
|
-
|
|
351
|
-
Grant each developer their **org membership + repo access** through the App (`gh api` with the App token):
|
|
352
|
-
`write` for a developer; for a **full-write** member ("project-admin") also add them to the train-branch
|
|
353
|
-
push allowlist from Step 2b (so they can merge protected development PRs). GitHub's
|
|
354
|
-
collaborator list + the per-branch allowlist are the record — no separate roster. Exact commands:
|
|
355
|
-
`docs/Guides/repo-access.md`.
|
|
356
|
-
|
|
357
|
-
## Step 4c — CI trigger, labels, templates
|
|
358
|
-
|
|
359
|
-
- **Enable workflow triggers** — a freshly-provisioned repo can have GitHub Actions auto-trigger stuck off
|
|
360
|
-
(push/PR never run workflows though dispatch does). Toggle it off→on:
|
|
361
|
-
```bash
|
|
362
|
-
gh api -X PUT repos/$OWNER/$REPO/actions/permissions -F enabled=false
|
|
363
|
-
gh api -X PUT repos/$OWNER/$REPO/actions/permissions -F enabled=true -f allowed_actions=selected
|
|
364
|
-
```
|
|
365
|
-
- **Self-hosted CI is the org default** — the `mmi-automation` runner group is org-wide (all repos), so a
|
|
366
|
-
new repo needs no manual group join. Any CI workflow it adds uses
|
|
367
|
-
`runs-on: [self-hosted, linux, x64, mmi-live]` (never `ubuntu-*`/`windows-*`/`macos-*`, which bill
|
|
368
|
-
GitHub-hosted minutes); jobs needing system packages run in a `container:`. The runner has **six
|
|
369
|
-
concurrent job lanes** — independent checks belong in separate parallel jobs (each with its own
|
|
370
|
-
`runs-on`), never chained into one serial job or throttled with unneeded `concurrency` groups. See
|
|
371
|
-
`docs/Guides/gh-runner-runbook.md`.
|
|
372
|
-
- **Product gate + required checks (#1333, stack-aware #1550)** — deployable repos bootstrap-seed
|
|
373
|
-
`.github/workflows/gate.yml` (single `gate` job — the simple default for one check command; a repo
|
|
374
|
-
that grows several independent suites splits them into parallel jobs across the runner's twelve lanes,
|
|
375
|
-
as MMI-Hub's `cli`/`infra`/`docs` gate does) and a ruleset reference at
|
|
376
|
-
`.github/rulesets/mmi-product-required-checks.json`. The gate is stack-aware: capture the repo's
|
|
377
|
-
**runtime** (`node` | `python`), its **check command**, and its **working directory** at interview time
|
|
378
|
-
and pass them as `--var GATE_RUNTIME=node|python`, `--var GATE_CMD=...`, `--var GATE_WORKDIR=...` (and
|
|
379
|
-
`--var GATE_CACHE_DEP_PATH=<path/to/package-lock.json>` when the app's Node lockfile is not at the repo
|
|
380
|
-
root). Defaults: `node` runtime, `npm run check` / `npm ci` at the repo root; a Python repo defaults to
|
|
381
|
-
`pytest` / `pip install -e ".[dev]"` with `--var GATE_PY_VERSION=` (3.11 default). The runtime selects
|
|
382
|
-
which setup step (`setup-node` vs `setup-python`) the rendered `if:` fires. The check command runs
|
|
383
|
-
under the org **wall-clock budget** (#3178): the render pins `run-with-budget` to the CLI's blessed SHA
|
|
384
|
-
with `--var GATE_MAX_SECONDS=` (300s onboarding default — tighten once the gate is measured, via
|
|
385
|
-
`--var` or `org project set <repo> --var gate={"maxSeconds":N}`); `ci audit` and the release train
|
|
386
|
-
both enforce the step, so do not remove it. The tenant-container product image build (#7542)
|
|
387
|
-
shares that ceiling — no separate knob. Every trigger runs the full `GATE_CMD`. A repo whose checks
|
|
388
|
-
need a service container or an extra budgeted step declares them in its registry gate config, never by
|
|
389
|
-
hand-editing the rendered gate (#7407): `gate.services` is keyed by service name with `image`, `env`,
|
|
390
|
-
`ports`, one-line `options` and `tmpfs` paths; `gate.steps` is a list of `{name, command, env,
|
|
391
|
-
maxSeconds}` run after the main check under the same budget wrapper. Invalid declarations fail the
|
|
392
|
-
render by field name; a repo that declares neither renders exactly as before. `bootstrap apply --execute` installs that JSON as the
|
|
393
|
-
repository ruleset itself (#6070) and reports one `product-ruleset` row: `PARK` (the gate file is not on
|
|
394
|
-
the base branch yet — installed disabled), `WAIT` (the gate exists but has not emitted a proven-green
|
|
395
|
-
`gate` context — kept disabled; re-run apply after the gate passes), `ACTIVATE` (gate green — enforcement
|
|
396
|
-
active), or `SKIP` (already exact). The activation boundary is fail-closed on purpose: a required context
|
|
397
|
-
that cannot pass yet would lock the seed PR behind itself. `mmi-cli devops ci reconcile --apply --repo
|
|
398
|
-
$OWNER/$REPO` remains the supported fallback; confirm `product required-check ruleset enforcement active`
|
|
399
|
-
with `bootstrap verify` before reporting bootstrap complete. MMI-Hub keeps its own three-job gate
|
|
400
|
-
(`cli`/`infra`/`docs`) — never apply the product ruleset there.
|
|
401
|
-
- **The committed ruleset owns its up-to-date policy.** New repositories default to
|
|
402
|
-
`strict_required_status_checks_policy: false`. Bootstrap and CI reconciliation preserve that policy
|
|
403
|
-
when updating contexts, and converge the live ruleset to the committed setting. Repositories may
|
|
404
|
-
explicitly require an up-to-date head. `pr merge` / `pr land` still update a BEHIND head from the base
|
|
405
|
-
with a merge commit and re-wait once — see `docs/Guides/train-troubleshooting.md#head-behind-base`.
|
|
406
|
-
- **A brand-new repo cannot pass the gate you just installed — its first commit must carry a real project
|
|
407
|
-
(#2928).** The seeded `gate.yml` runs `GATE_INSTALL_CMD` + `GATE_CMD` (`npm ci` + `npm run check` by
|
|
408
|
-
default) **unconditionally**. An empty repo has no `package.json`, so the gate **fails on the seed PR
|
|
409
|
-
itself** — and since the seed PR is what *installs* the gate, this is the normal first-bootstrap path, not
|
|
410
|
-
an edge case. The repo cannot go green and `bootstrap verify` cannot pass until the first commit contains a
|
|
411
|
-
real project satisfying `GATE_CMD`: a `package.json` with a `check` script (or the Python equivalent), plus
|
|
412
|
-
its lockfile. Landing that minimal project is **part of finishing the bootstrap**, not follow-up work —
|
|
413
|
-
otherwise the gate is unpassable by construction.
|
|
414
|
-
**The `check` must be able to fail.** It has to actually compile or lint the code being committed — a
|
|
415
|
-
script that exits 0 without looking at anything does **not** satisfy `GATE_CMD` and must never be committed
|
|
416
|
-
to force a green gate. The goal is a gate that catches a broken change, not a green tick wired to nothing.
|
|
417
|
-
A typecheck plus a lint is enough to start. **Do not seed a test suite to satisfy `GATE_CMD`** — tests are
|
|
418
|
-
opt-in org-wide (#3562); a new repo seeds tests only for paths its own `test-policy.json` marks mandatory,
|
|
419
|
-
and an empty `mandatory` array is the normal answer.
|
|
420
|
-
- **Standard labels** — type labels only (the issue templates reference them). **Priority is a Project
|
|
421
|
-
field, not a label** (#416): never seed `priority:*` labels; `--priority` writes the board field.
|
|
422
|
-
```bash
|
|
423
|
-
gh label create bug --color d73a4a --description "Something is broken or behaving wrong" -R $OWNER/$REPO
|
|
424
|
-
gh label create feature --color a2eeef --description "New capability or enhancement" -R $OWNER/$REPO
|
|
425
|
-
gh label create task --color 0052cc --description "Task, chore, or improvement" -R $OWNER/$REPO
|
|
426
|
-
```
|
|
427
|
-
- **Board view (card fields, #4093)** — the Project's Board view must show the org-standard card set:
|
|
428
|
-
`Title, Assignees, Status, Labels, Linked pull requests, Parent issue, Sub-issues progress, Priority`.
|
|
429
|
-
Cloning from the template board (project 4, Step 3) already produces this; `bootstrap verify` asserts it
|
|
430
|
-
(`Board view card fields match the org standard`) on every subsequent run, so drift shows up there rather
|
|
431
|
-
than only at bootstrap time. Fix via Board view → **Fields**, or `updateProjectV2View(configuration:
|
|
432
|
-
{visibleFieldIds:[...]})`. Strip any legacy `priority:*` / taxonomy labels with `mmi-cli oracle board doctor --fix`.
|
|
433
|
-
- **Org App credentials** — nothing to register per repo (#494). Board moves are central (the Hub webhook
|
|
434
|
-
moves Todo/In Progress/In Review for every repo) and the org App token is minted inside the Hub's own
|
|
435
|
-
central workflows, so `MMI_APP_ID` / `MMI_APP_PRIVATE_KEY` live only on the Hub — a product repo seeds no
|
|
436
|
-
App var/secret.
|
|
437
|
-
- **Issue templates** — seed `.github/ISSUE_TEMPLATE/` (Bug · Feature · Task + `config.yml`).
|
|
438
|
-
- **Merge settings (org canon)** — every repo: auto-merge on, squash on, automatic merged-branch deletion
|
|
439
|
-
off. A merged remote branch survives until delayed cleanup proves no open PR still uses it as a base
|
|
440
|
-
(#5789); deleting it at merge can irreversibly close stacked PRs:
|
|
441
|
-
`gh api -X PATCH repos/$OWNER/$REPO -f allow_auto_merge=true -f allow_squash_merge=true -F delete_branch_on_merge=false`
|
|
442
|
-
- **Cursor environment** — `.cursor/environment.json` is retired org-wide (#2501): it caused Cursor to
|
|
443
|
-
auto-spawn cloud agents nobody asked for. Bootstrap no longer seeds it and no
|
|
444
|
-
repo should carry a tracked copy.
|
|
445
|
-
|
|
446
|
-
## Step 5 — install the plugin + seed docs
|
|
447
|
-
|
|
448
|
-
- Bootstrap does **not** seed `.claude/settings.json` or any agent guide (hub-v3 WS4). The developer installs
|
|
449
|
-
the org plugins per machine: `mmi@mutmutco` (the plugin carries the org tooling + skills) and
|
|
450
|
-
`superpowers@claude-plugins-official` (Anthropic's skills framework — TDD, debugging, subagent dev).
|
|
451
|
-
Personal agent guides (`AGENTS.md`/`CLAUDE.md`) are developer-owned and gitignored — MMI never delivers,
|
|
452
|
-
overwrites, or deletes them; Jervaise's guide rides Jerv PowerTools.
|
|
453
|
-
- Seed `README.md` + `architecture.md` from the templates, then **fill them before finishing** — `bootstrap
|
|
454
|
-
verify` now fails on any leftover `(placeholder)` or `{{TOKEN}}` (#1520). Fill the code-local fields from
|
|
455
|
-
what this bootstrap already knows: **Stack / Run locally / Verify** from the Step-4c gate + install commands
|
|
456
|
-
and the repo's actual code; **Gotchas** and the architecture **Overview / Build & deploy** from the confirmed
|
|
457
|
-
Step-0 axes (class, project-type, deploy-model). Do **not** write the release track, board number, or deploy
|
|
458
|
-
coords as a value — the templates point at `mmi-cli oracle org project get` (registry SSOT, never copied, so it cannot drift).
|
|
459
|
-
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).
|
|
460
|
-
- **`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,
|
|
461
|
-
`mmi-cli oracle docs index --write` owns the routing artifact and `--check` gates drift — it is not product
|
|
462
|
-
current-state SSOT. Decision records under `docs/decisions/` remain append-only *why*.
|
|
463
|
-
- `.claude/settings.local.json` is local-only and gitignored; bootstrap seeds no committed `.claude/settings.json`.
|
|
464
|
-
- **No agent guide is committed — none, anywhere (#2921).** `mmi-no-agent-files-org` is active with no bypass
|
|
465
|
-
and restricts `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `.claude/**`, `.codex/**`, `.agents/**` **and
|
|
466
|
-
`.cursor/rules/**`**. Bootstrap used to seed `.cursor/rules/<repo-slug>.mdc`, which the wall refuses with a
|
|
467
|
-
409 — it wrote a file its own ruleset bans, then verified the file was there. That seed, its template, and
|
|
468
|
-
the verify check are gone. Repo-specific agent guidance belongs in **`README.md` § Agent context** (stack,
|
|
469
|
-
verify commands, gotchas) with the deep build truth in `architecture.md`; agents read those. Personal agent
|
|
470
|
-
guides stay developer-owned and gitignored, carried per machine by each developer's own plugins.
|
|
84
|
+
For a new repo, board selection/creation, access, gate setup, or manual provisioning, read [reference.md](reference.md) Steps 0b–5 before applying; upgrades can use the apply path below.
|
|
471
85
|
|
|
472
86
|
## Step 6 — register Hub META
|
|
473
87
|
|
|
474
88
|
Do not seed a product repo control-plane config file. `mmi-cli` carries the Hub API endpoint and resolves board,
|
|
475
89
|
deploy, secret, and project state from the Hub registry at runtime.
|
|
476
90
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
`--project-type desktop-app --deploy-model none --clear-web-profile` for a packaged desktop application
|
|
480
|
-
(swap in `--deploy-model registry-publish` when it also publishes a package alongside its installer),
|
|
481
|
-
`--project-type mobile-app --deploy-model none --clear-web-profile` for a phone app distributed through the
|
|
482
|
-
app stores, and
|
|
483
|
-
`--class content --project-type content --deploy-model content --clear-web-profile` for a content/KB repo.
|
|
484
|
-
An **MM-Work module** adds `--var SUITE=mm-work` (#7944): it registers `suite: mm-work` and derives its
|
|
485
|
-
own `dev.<sub>.mutmut.work` + `<sub>.mutmut.work` hosts, with no rc host (the suite serves its rc from the
|
|
486
|
-
live host). Without it, apply derives the `*.mutatismutandis.co` web-app hosts, which a module never uses.
|
|
91
|
+
For project-type/deploy-model flag combinations or an MM-Work module, read [reference.md](reference.md) before choosing the apply flags.
|
|
92
|
+
|
|
487
93
|
Run the apply path with the board variables discovered above, or register the same values with
|
|
488
94
|
`mmi-cli oracle org project set` from the Hub or from the target project checkout:
|
|
489
95
|
|
|
@@ -507,26 +113,7 @@ the bootstrap seed Dockerfile and compose templates). The seeded compose is file
|
|
|
507
113
|
`labels: { mmi.runtime-env: "true" }`, no `env_file`, and a loopback-only port; the deploy injects the stage's
|
|
508
114
|
vault values at runtime (`DEPLOY#<stage>.noEnvFile: true`) and the app reads plain env vars — it must **not** self-load SSM and must **not** ship a committed `.env`.
|
|
509
115
|
|
|
510
|
-
For
|
|
511
|
-
|
|
512
|
-
**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.
|
|
513
|
-
|
|
514
|
-
For a non-suite `web-app` that declares `oauth` META, print the canonical OAuth surface and provision the client once:
|
|
515
|
-
|
|
516
|
-
```bash
|
|
517
|
-
mmi-cli vault org oauth plan --repo "$OWNER/$REPO" # the exact JS origins + redirect URIs + canonical SSM keys
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
Register those JS origins + `/api/auth/callback` redirect URIs on the Console client (master, per
|
|
521
|
-
`docs/Guides/oauth-provision.md`), then store the creds in the canonical keys in one step:
|
|
522
|
-
|
|
523
|
-
```bash
|
|
524
|
-
mmi-cli vault org oauth set-creds --repo "$OWNER/$REPO" < client.json # the Console "Download JSON" file
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
The keys are the one stageless pair `GOOGLE_CLIENT_ID` + `GOOGLE_CLIENT_SECRET` at the slug root — every
|
|
528
|
-
stage reads it (#2244/#2528). Never a staged `{dev,rc,main}/GOOGLE_*`, `GOOGLE_OAUTH_CLIENT_*`, or `prod/`
|
|
529
|
-
variant; the runtime reads only the canonical names and declare-first rejects the rest.
|
|
116
|
+
For an MM-Work module or non-suite web-app with OAuth META, read [reference.md](reference.md) before the first CI/deploy or OAuth provisioning.
|
|
530
117
|
|
|
531
118
|
Local `/stage` is optional product-owned configuration. A repo that wants `/stage` may carry a local
|
|
532
119
|
`stage` block, but that file must not contain board, deploy, or secret registry facts.
|