@mutmutco/opencode-mmi 2.48.0
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/dist/index.d.ts +35 -0
- package/dist/index.js +194 -0
- package/package.json +44 -0
- package/skills/_shared/doctrine.md +238 -0
- package/skills/bootstrap/SKILL.md +419 -0
- package/skills/bootstrap/seeds/Dockerfile.template +25 -0
- package/skills/bootstrap/seeds/README.template.md +36 -0
- package/skills/bootstrap/seeds/architecture.template.md +19 -0
- package/skills/bootstrap/seeds/components.json.template +31 -0
- package/skills/bootstrap/seeds/cursor-environment.template.json +3 -0
- package/skills/bootstrap/seeds/cursor-rules.template.mdc +11 -0
- package/skills/bootstrap/seeds/design-system.paths.template.json +8 -0
- package/skills/bootstrap/seeds/docker-compose.template.yml +17 -0
- package/skills/bootstrap/seeds/gate.template.yml +42 -0
- package/skills/bootstrap/seeds/google-login.template.md +35 -0
- package/skills/bootstrap/seeds/manifest.json +32 -0
- package/skills/bootstrap/seeds/mcp-playwright.template.json +13 -0
- package/skills/bootstrap/seeds/mmi-product-required-checks.template.json +23 -0
- package/skills/browser-automation/SKILL.md +137 -0
- package/skills/build/SKILL.md +237 -0
- package/skills/build/references/halt-report.md +38 -0
- package/skills/build/references/loops.md +13 -0
- package/skills/build/references/worked-example.md +18 -0
- package/skills/build/templates/campaign-northstar.md +40 -0
- package/skills/coop/SKILL.md +77 -0
- package/skills/grind/SKILL.md +469 -0
- package/skills/grind/references/auto.md +107 -0
- package/skills/grind/references/build-notes.md +56 -0
- package/skills/grind/references/routing.md +76 -0
- package/skills/grind/references/verify.md +83 -0
- package/skills/grind/templates/saga-snapshot.md +28 -0
- package/skills/grind/templates/synthesize-panel.md +104 -0
- package/skills/handoff/SKILL.md +67 -0
- package/skills/hotfix/SKILL.md +219 -0
- package/skills/mmi/SKILL.md +372 -0
- package/skills/rcand/SKILL.md +169 -0
- package/skills/release/SKILL.md +309 -0
- package/skills/secrets/SKILL.md +137 -0
- package/skills/stage/SKILL.md +150 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rcand
|
|
3
|
+
description: Promote development → rc for full-track deployable repos — merge, tag (vX.Y.0-rc.N), push, dispatch the rc deploy. Direct-track repos (releaseTrack=direct, incl. MMI-Hub) skip rc and release from development via /release. Train-authority gated. Use when an authorized user wants to cut/promote a release candidate, ship development to rc, stage work for release, or invokes /rcand.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /rcand — promote development to release-candidate
|
|
7
|
+
|
|
8
|
+
Merge `development → rc`, tag `vX.Y.0-rc.N`, push (the GitHub authority gate), then dispatch the rc deploy
|
|
9
|
+
path. **Train-authority gated (D14):** the repo's project-admin or the master. Direct-track repos
|
|
10
|
+
(product repos with `releaseTrack: direct`, plus MMI-Hub via the `isHubControlRepo` special-case) are the exception: they skip rc and `/rcand` refuses there;
|
|
11
|
+
run `/release` from `development` instead. The board needs no touch
|
|
12
|
+
here — items reach `Done` when their PR merges to `development` (native close → Done); `rc` is a deploy
|
|
13
|
+
stage, not a lane.
|
|
14
|
+
|
|
15
|
+
Authority is **structural + server-checked**: step 0 asks the Hub (`mmi-cli access role`), and step 4
|
|
16
|
+
pushes to the protected `rc` branch, whose per-repo allowlist carries the same people (master + that repo's
|
|
17
|
+
project-admins). No `.env` role marker. Gate ordering: the tag lands the rc SHA
|
|
18
|
+
for checks, and every deploy side-effect waits until the protected `rc` push accepts that checked SHA.
|
|
19
|
+
|
|
20
|
+
## Step 0 — train-authority probe (server-side, D14)
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
mmi-cli access role {owner}/{repo} --json # Hub-verified: { role, train }
|
|
24
|
+
```
|
|
25
|
+
`train: false` — or ANY error (fail closed) → stop: a product repo's train belongs to that repo's
|
|
26
|
+
**project-admin** (repo write + registry `projectAdmins`) or the master. When `project-admin` + `train`,
|
|
27
|
+
proceed — do not redirect to the master (`AGENTS.md` § Authority). Errors → fix `gh auth` first,
|
|
28
|
+
never proceed unverified.
|
|
29
|
+
|
|
30
|
+
## Step 1 — development ahead of rc?
|
|
31
|
+
|
|
32
|
+
Preconditions: on `development`, clean tree. The clean-tree check rejects UNTRACKED scratch too, not just
|
|
33
|
+
modified tracked files — if `--apply` stops with `working tree must be clean before …`, run `git status` and
|
|
34
|
+
gitignore the `??` scratch (or move it to a gitignored path like `tmp/`) before retrying (#1472).
|
|
35
|
+
```bash
|
|
36
|
+
git fetch origin
|
|
37
|
+
git rev-list --count origin/rc..origin/development
|
|
38
|
+
```
|
|
39
|
+
Dirty → stop. Count `0` → stop ("nothing to promote"). `>0` → capture the commit list for the report.
|
|
40
|
+
|
|
41
|
+
> Verifying CLI/plugin health before a train? Use `mmi-cli doctor --apply --no-repo-writes`: it runs the
|
|
42
|
+
> env/plugin repairs but never mutates the working tree, so a pending org-managed `.gitignore` repair is
|
|
43
|
+
> reported with its follow-up command instead of dirtying the product checkout right before promotion.
|
|
44
|
+
> Apply that follow-up after the train.
|
|
45
|
+
|
|
46
|
+
## Step 1b — registry + rc secret-name preflight
|
|
47
|
+
|
|
48
|
+
Resolve the project META first; its `deployModel` decides the deploy path. `tenant-container` repos use the
|
|
49
|
+
central tenant deployer and therefore need DEPLOY# coords. Serverless, `registry-publish`, and
|
|
50
|
+
`solo-container` repos deploy from their own workflow, so do **not** dispatch `tenant-deploy.yml` for them.
|
|
51
|
+
Direct-track repos (`releaseTrack: direct`, e.g. MMI-Hub) have no rc candidate path; `mmi-cli rcand --apply`
|
|
52
|
+
refuses after this preflight.
|
|
53
|
+
Verify META + required SSM secret names before touching `rc`:
|
|
54
|
+
```bash
|
|
55
|
+
mmi-cli project get {owner}/{repo}
|
|
56
|
+
mmi-cli secrets preflight --stage rc --repo {owner}/{repo}
|
|
57
|
+
```
|
|
58
|
+
Missing META or secret names → stop and repair the registry/secrets first.
|
|
59
|
+
|
|
60
|
+
## Step 2 — merge development → rc (never force)
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
git checkout rc
|
|
64
|
+
git pull --ff-only origin rc
|
|
65
|
+
git merge development --no-edit
|
|
66
|
+
```
|
|
67
|
+
Conflict → `git merge --abort`, report paths, resolve on `development`, re-run.
|
|
68
|
+
|
|
69
|
+
**Exception — version manifests only.** When every conflicted path is a version manifest
|
|
70
|
+
(`package.json` · `package-lock.json`), the incoming `development` side carries the org truth — the
|
|
71
|
+
`/release` version fold (#976) bumps manifests on `main` and the release back-merge brings them to
|
|
72
|
+
`development`, so `development` is always the newer side. Take it deterministically and continue (no
|
|
73
|
+
hand-resolution, no abort):
|
|
74
|
+
```bash
|
|
75
|
+
git checkout --theirs package.json package-lock.json && git add package.json package-lock.json
|
|
76
|
+
git commit --no-edit
|
|
77
|
+
```
|
|
78
|
+
Any other conflicted path in the same merge → abort as above. Tag (step 3) only AFTER the merge commit
|
|
79
|
+
exists — a tag minted before the conflict resolution points at the wrong SHA.
|
|
80
|
+
|
|
81
|
+
## Step 3 — tag the rc
|
|
82
|
+
|
|
83
|
+
The shared helper derives the next rc tag from existing tags (re-run-safe — `-rc.N` increments):
|
|
84
|
+
```bash
|
|
85
|
+
TAG=$(node scripts/next-version.mjs rc) # -> vX.Y.0-rc.N
|
|
86
|
+
git tag "$TAG"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Step 4 — push tag, wait for the REQUIRED checks, then push rc (the gate)
|
|
90
|
+
|
|
91
|
+
Required status checks are **per-repo branch protection, not a fixed list** — MMI-Hub requires `cli` ·
|
|
92
|
+
`infra` · `docs`, but a product repo may require different contexts or none at all (#1045). A fresh merge
|
|
93
|
+
SHA has no check-runs yet, so when checks ARE required, pushing the branch *first* is structurally rejected
|
|
94
|
+
until CI catches up (it then succeeds on a retry — avoidable noise). Push the **tag first**: it lands the
|
|
95
|
+
SHA and triggers the repo's CI without touching the protected branch ref. Then probe what `rc` actually
|
|
96
|
+
requires and wait only for those contexts:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
git push origin "vX.Y.0-rc.N" # lands the SHA + triggers the repo's CI
|
|
100
|
+
SHA=$(git rev-parse rc)
|
|
101
|
+
# discover the REQUIRED contexts on rc (classic protection + rulesets; 404 = none from that source):
|
|
102
|
+
gh api repos/{owner}/{repo}/branches/rc/protection/required_status_checks --jq '[.contexts[]]'
|
|
103
|
+
gh api repos/{owner}/{repo}/rules/branches/rc \
|
|
104
|
+
--jq '[.[]|select(.type=="required_status_checks")|.parameters.required_status_checks[].context]'
|
|
105
|
+
# ZERO required contexts -> push rc immediately (the GitHub push gate is the backstop).
|
|
106
|
+
# Otherwise poll until every required context is "success" on $SHA — never a hard-coded list, and bound
|
|
107
|
+
# the wait (~10 min): on timeout, stop with a clear failure naming the pending/failed contexts.
|
|
108
|
+
gh api repos/{owner}/{repo}/commits/$SHA/check-runs \
|
|
109
|
+
--jq '[.check_runs[]|{name:.name,conclusion:.conclusion}]'
|
|
110
|
+
git push origin rc
|
|
111
|
+
```
|
|
112
|
+
(`mmi-cli rcand --apply` performs this discovery + bounded wait itself.)
|
|
113
|
+
Rejected (protected / not a bypass actor) → stop, nothing deployed, no board writes; leave the local tag
|
|
114
|
+
for an authorized re-push, never force. Non-fast-forward → re-pull, re-run from step 1.
|
|
115
|
+
|
|
116
|
+
## Step 5 — rc deploy (model-specific, non-blocking)
|
|
117
|
+
|
|
118
|
+
For `tenant-container` repos, dispatch the Hub **tenant-deploy.yml** workflow for rc (OIDC, keyless). Don't
|
|
119
|
+
deploy by hand, and **don't block on it** — start the watch as a background task and move straight to
|
|
120
|
+
Step 6:
|
|
121
|
+
```bash
|
|
122
|
+
gh workflow run tenant-deploy.yml --repo mutmutco/MMI-Hub \
|
|
123
|
+
-f slug={slug} -f repo={owner}/{repo} -f ref=rc -f stage=rc
|
|
124
|
+
gh run watch "$(gh run list --workflow tenant-deploy.yml --limit 1 --json databaseId -q '.[0].databaseId')" \
|
|
125
|
+
--exit-status # run this in the BACKGROUND (Bash run_in_background) — capture its URL for the report
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
For serverless repos, do **not** dispatch `tenant-deploy.yml`: their own push-triggered workflow owns the
|
|
129
|
+
rc deploy.
|
|
130
|
+
|
|
131
|
+
`mmi-cli rcand --apply --json` returns the relevant run `runId` + `runUrl` (alongside `deployStatus`), so
|
|
132
|
+
you never hand-correlate Actions. For tenant-container repos, that is the dispatched `tenant-deploy.yml`
|
|
133
|
+
run. For Hub serverless, that is the auto-fired `deploy.yml` run from the protected `rc` push. Add
|
|
134
|
+
`--watch` to block on the run and have `deployStatus` resolve to `success`/`failure`; `promoted: true`
|
|
135
|
+
stays set either way — a failed **deploy** never undoes the **promotion**.
|
|
136
|
+
|
|
137
|
+
The deploy runs while you report. When the background watch returns, surface the outcome:
|
|
138
|
+
**green** → note the run + rc env URL; **red** → report plainly. The rc tag is already pushed, so the
|
|
139
|
+
correct next action is a **deploy retry of the existing rc ref** after the Hub runtime is repaired — never
|
|
140
|
+
a re-tag/re-merge:
|
|
141
|
+
```bash
|
|
142
|
+
mmi-cli tenant redeploy {owner}/{repo} rc --watch # re-dispatch tenant-deploy.yml for the promoted rc
|
|
143
|
+
```
|
|
144
|
+
Don't hold the session open waiting. Unlike `/release`, a failed rc **dispatch** stays fail-loud — rc is
|
|
145
|
+
ephemeral and re-runnable, so the same `tenant redeploy … rc` retry is the whole recovery.
|
|
146
|
+
|
|
147
|
+
## Step 6 — report
|
|
148
|
+
|
|
149
|
+
Version `vX.Y.0-rc.N` · merged commits · rc deploy run + env URL.
|
|
150
|
+
|
|
151
|
+
## Notes
|
|
152
|
+
|
|
153
|
+
- `rc` is NOT prod. Shipping `rc → main → prod` is `/release` (ships exactly what's on `rc`; never pulls
|
|
154
|
+
`development`). Never force-push `rc`; never commit to `rc` outside the step-2 merge.
|
|
155
|
+
- `rc` is **ephemeral**: `/rcand` creates the rc runtime, `/release` retires it after a confirmed prod
|
|
156
|
+
deploy. A full-track repo can skip rc entirely with `/release --dev` (`development → main`); `/hotfix`
|
|
157
|
+
always skips rc (it cherry-picks `development → main` directly).
|
|
158
|
+
- **MAJOR / exact-target cycle:** for a release the tag math can't derive (a MAJOR like `2.0.0`, or skipping
|
|
159
|
+
a version already on npm), export `MMI_RELEASE_VERSION=X.Y.Z` before `/rcand` — `next-version.mjs rc` then
|
|
160
|
+
opens that exact cycle (validated to move strictly forward). Keep it exported through `/release`.
|
|
161
|
+
|
|
162
|
+
## Retro — one check before you finish
|
|
163
|
+
Before your final report, answer one question honestly: did **this skill's own instructions** misfire
|
|
164
|
+
this run — ambiguous wording, a misleading message, or an environment failure it should have warned
|
|
165
|
+
about? (Process only — never the user's code or task; e.g. a misleading authority or gate message, or an
|
|
166
|
+
ambiguous tag or push-order step.) If yes, file **one** lesson and move on; a clean run is silent (hard
|
|
167
|
+
cap: one per run). It lands on the Hub board (deduped) and is fixed only via a reviewed PR — never edit
|
|
168
|
+
the skill live; the retro is advisory, so if the call fails, note it and continue:
|
|
169
|
+
`mmi-cli skill-lesson --skill rcand --title "<what misfired>" --body "<what; evidence; proposed amendment>"`
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: release
|
|
3
|
+
description: Ship to main + prod. Full-track repos ship rc → main; direct-track (releaseTrack=direct, incl. MMI-Hub) skip rc and ship development → main. Train-authority gated; the only prod path. Use when an authorized user wants to release to production, cut/publish a version, or invokes /release.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /release — ship to main + prod
|
|
7
|
+
|
|
8
|
+
Full-track repos ship **exactly what is on `rc`** (never pulls `development`): merge `rc → main`, tag
|
|
9
|
+
`vX.Y.0`, publish a GitHub Release, dispatch the Hub central tenant deploy workflow for prod, then roll
|
|
10
|
+
`development` forward. Direct-track repos — product repos with `releaseTrack: direct`, plus MMI-Hub via the `isHubControlRepo` special-case (its `releaseTrack` stays unset) — skip rc: release
|
|
11
|
+
merges `development → main`, tags, publishes the GitHub Release, and the release event fires the repo's own
|
|
12
|
+
deploy/publish workflow (for MMI-Hub, `deploy.yml` + `publish.yml`).
|
|
13
|
+
`rc` is **ephemeral**: `/rcand` creates the rc runtime, `/release` retires it — after a confirmed full-track
|
|
14
|
+
prod deploy the train stops the rc stage (reported as `rcRetirement` in the result; never fatal to the
|
|
15
|
+
release). Full-track repos may also pass **`--dev`** to release `development → main` directly, skipping rc —
|
|
16
|
+
the default stays `rc → main`. `--dev` **fails closed** when `origin/rc` carries content not yet in
|
|
17
|
+
`development` (a dev → main release would drop it), and is a friendly no-op on direct-track repos.
|
|
18
|
+
**Train-authority gated (D14):** the repo's project-admin or the master; the Hub repo's train is
|
|
19
|
+
master-only. This is the **only** sanctioned prod path; a prod release still needs the authorized human's
|
|
20
|
+
explicit per-turn go — an agent never self-initiates it. The board needs no
|
|
21
|
+
touch — items reached `Done` when their PRs merged to `development`; `rc`/`main` are deploy stages, not lanes.
|
|
22
|
+
|
|
23
|
+
Authority is structural + server-checked: step 0 asks the Hub (`mmi-cli access role`), and step 3 pushes
|
|
24
|
+
to the protected `main` branch, whose per-repo allowlist carries the same people (master + that repo's
|
|
25
|
+
project-admins; Hub: master + App only). Gate ordering: the tag lands the release SHA for checks, and
|
|
26
|
+
nothing deploys before the protected `main` push accepts that checked SHA.
|
|
27
|
+
|
|
28
|
+
## Step 0 — confirm + probe
|
|
29
|
+
|
|
30
|
+
Confirm the human holding train authority for THIS repo authorized a prod release this turn. Probe:
|
|
31
|
+
```bash
|
|
32
|
+
mmi-cli access role {owner}/{repo} --json # Hub-verified: { role, train }
|
|
33
|
+
```
|
|
34
|
+
`train: false` — or any error (fail closed) → stop: a product repo's train belongs to that repo's
|
|
35
|
+
project-admin or the master; the Hub train is master-only. Then preconditions: clean tree; full-track repos
|
|
36
|
+
run from `rc` (or from `development` with `--dev`), while direct-track repos run from `development`.
|
|
37
|
+
The clean-tree check rejects UNTRACKED scratch too, not just modified tracked files — if `--apply` stops
|
|
38
|
+
with `working tree must be clean before …`, run `git status` and gitignore the `??` scratch (or move it to a
|
|
39
|
+
gitignored path like `tmp/`) before retrying (#1472).
|
|
40
|
+
|
|
41
|
+
Full-track repos:
|
|
42
|
+
```bash
|
|
43
|
+
git fetch origin
|
|
44
|
+
git rev-list --count origin/main..origin/rc
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Direct-track repos (e.g. MMI-Hub):
|
|
48
|
+
```bash
|
|
49
|
+
git fetch origin
|
|
50
|
+
git rev-list --count origin/main..origin/development
|
|
51
|
+
```
|
|
52
|
+
`0` → stop ("nothing to release").
|
|
53
|
+
|
|
54
|
+
## Step 0a — stale CLI preflight (#1410)
|
|
55
|
+
|
|
56
|
+
Before local gates or `release --apply`, ensure the repo-local / PATH `mmi-cli` matches the released train
|
|
57
|
+
path — a stale checkout (e.g. 2.32.0 while 2.32.4 is released) fails release gates with opaque errors.
|
|
58
|
+
Self-heal in place without repo writes:
|
|
59
|
+
```bash
|
|
60
|
+
mmi-cli doctor --apply --no-repo-writes
|
|
61
|
+
```
|
|
62
|
+
If doctor reports a version gap it cannot auto-apply (e.g. `--no-repo-writes` blocks the npm bump), stop and
|
|
63
|
+
run `mmi-cli doctor --apply` (or `npm install -g @mutmutco/cli`) before continuing. Do **not** proceed to
|
|
64
|
+
Step 0b while the installed CLI is behind the Hub's `minClientVersion`.
|
|
65
|
+
|
|
66
|
+
## Step 0b — registry + main secret-name preflight
|
|
67
|
+
|
|
68
|
+
Resolve the project META first; its `deployModel` decides the deploy path. `tenant-container` repos use the
|
|
69
|
+
central tenant deployer and therefore need DEPLOY# coords. `hub-serverless` (MMI-Hub), `serverless`,
|
|
70
|
+
`registry-publish`, and `solo-container` repos deploy from their own branch/release-triggered or model
|
|
71
|
+
workflow, so do **not** dispatch `tenant-deploy.yml` for them.
|
|
72
|
+
Verify META + required SSM secret names before touching `main`:
|
|
73
|
+
```bash
|
|
74
|
+
mmi-cli project get {owner}/{repo}
|
|
75
|
+
mmi-cli secrets preflight --stage main --repo {owner}/{repo}
|
|
76
|
+
```
|
|
77
|
+
Missing META or secret names → stop and repair the registry/secrets first.
|
|
78
|
+
|
|
79
|
+
## Step 0c — hotfix-coverage guard (fail closed, #839, #958)
|
|
80
|
+
|
|
81
|
+
Full-track repos only. Direct-track repos skip this guard because their release candidate is `development` itself.
|
|
82
|
+
|
|
83
|
+
Hotfixes are cherry-picked from `development` to `main` with **no back-merge** (see `/hotfix`), so a
|
|
84
|
+
candidate cut *before* a fix landed on `development` would silently revert that hotfix in prod. The guard
|
|
85
|
+
proves every main-only commit is in the candidate before the `rc → main` merge.
|
|
86
|
+
|
|
87
|
+
It runs **automatically inside `mmi-cli release --apply`** (Step 1+ below) — built into the CLI so it
|
|
88
|
+
works in every product repo with no repo-local script. You do not invoke it separately.
|
|
89
|
+
|
|
90
|
+
Per main-only commit it accepts: the `(cherry picked from commit <sha>)` trailer with that dev SHA an
|
|
91
|
+
ancestor of `origin/rc` (immune to conflict-resolved ports); a matching `git patch-id` on the rc side
|
|
92
|
+
(trailer-less picks); or a distribution-manifest-only bump (exempt — rc carries its own). Anything else
|
|
93
|
+
**fails the release closed** → **stop**: the right fix is a re-cut `/rcand` from `development`. Only when
|
|
94
|
+
the authorized human has manually verified the content is in the candidate, rerun with
|
|
95
|
+
`mmi-cli release --apply --ack <sha>[,<sha>…]` — the ack is recorded in the verdict. Never ack to save time.
|
|
96
|
+
|
|
97
|
+
## Step 1 — merge to main (never force)
|
|
98
|
+
|
|
99
|
+
Full-track repos:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
git checkout main
|
|
103
|
+
git pull --ff-only origin main
|
|
104
|
+
git merge rc --no-edit
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Direct-track repos (e.g. MMI-Hub):
|
|
108
|
+
```bash
|
|
109
|
+
git checkout main
|
|
110
|
+
git pull --ff-only origin main
|
|
111
|
+
git merge development --no-edit
|
|
112
|
+
```
|
|
113
|
+
Conflict → abort + stop (the train is misaligned — investigate; don't hand-resolve on `main`).
|
|
114
|
+
Alignment PRs are the exception to the org's squash default: land them with a true merge commit
|
|
115
|
+
(`gh pr merge --merge`) — a squash discards the merge parentage, so the misalignment guard re-flags the
|
|
116
|
+
same divergence on the next run.
|
|
117
|
+
|
|
118
|
+
**Exception — spine, version-manifest, and `.gitignore` paths.** `mmi-cli release --apply` tolerates
|
|
119
|
+
conflicts confined to the org spine files, the version-fold paths (Step 1b), and `.gitignore` (#1037 — a
|
|
120
|
+
repo bootstrapped before the managed-gitignore era still carries the legacy file on `main`; the candidate
|
|
121
|
+
carries the Hub-managed copy with project-local entries preserved). A hotfix bumps the manifests +
|
|
122
|
+
committed CLI bundle on `main` only, so the next merge re-conflicts there even when the train is healthy —
|
|
123
|
+
and the fold rewrites those exact paths right after the merge. For all tolerated paths the CLI takes the
|
|
124
|
+
incoming side deterministically and continues. Any other conflicted path → abort + stop as above.
|
|
125
|
+
|
|
126
|
+
## Step 1b — version fold (automatic, inside `mmi-cli release --apply`)
|
|
127
|
+
|
|
128
|
+
Every release folds the version bump into the release itself (#976): after the merge onto local `main` and
|
|
129
|
+
before the tag, the CLI bumps the version manifests to the release version and commits — **unconditionally,
|
|
130
|
+
changed or not** — then the tag-first push (Step 3) earns that fresh commit its required checks. There is
|
|
131
|
+
no separate bump PR, no `development` prep cycle, and `development` never sits ahead of the published
|
|
132
|
+
version (the back-merge in Step 5 carries the bump back).
|
|
133
|
+
|
|
134
|
+
What the fold bumps, by repo:
|
|
135
|
+
- **Hub (`hub-serverless`):** the full locked distribution set via `scripts/release-distribution.mjs
|
|
136
|
+
prepare` — spine dogfood (`scripts/spine-dogfood.mjs`: regenerate rule packs, verify docs/mirror/surfaces),
|
|
137
|
+
Claude/Codex/Cursor plugin manifests + marketplaces, `cli/package.json` + lockfile,
|
|
138
|
+
`plugins/opencode-mmi/package.json` + lockfile, and the rebuilt CLI + OpenCode adapter dist — then verifies
|
|
139
|
+
the set (`verify --skip-npm-view`). The Claude marketplace
|
|
140
|
+
`source` is pinned to `ref: main`, so the release is *how* a plugin change reaches devs.
|
|
141
|
+
- **App-style repos with a root `package.json`** (most products): the manifest + lockfile version via
|
|
142
|
+
`npm version --no-git-tag-version`, kept in lockstep with the release tag.
|
|
143
|
+
- **Repos with neither:** nothing to fold — the tag is the version.
|
|
144
|
+
|
|
145
|
+
Nothing to do by hand; the `--apply` result reports the fold outcome (`versionFold`).
|
|
146
|
+
|
|
147
|
+
## Step 2 — tag the release
|
|
148
|
+
|
|
149
|
+
Full-track repos drop the `-rc.N` suffix from the open cycle. Direct-track repos use the next cycle directly
|
|
150
|
+
because they have no rc tag:
|
|
151
|
+
```bash
|
|
152
|
+
TAG=$(node scripts/next-version.mjs release) # full-track repos -> vX.Y.0
|
|
153
|
+
TAG=$(node scripts/next-version.mjs cycle) # direct-track repos -> vX.Y.0
|
|
154
|
+
git tag "$TAG"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Step 3 — push tag, wait for the REQUIRED checks, then push main (the gate)
|
|
158
|
+
|
|
159
|
+
Required status checks are **per-repo branch protection, not a fixed list** — MMI-Hub's `main` requires
|
|
160
|
+
`cli` · `infra` · `docs`, but a product repo may require different contexts or none at all (#1045). The
|
|
161
|
+
release SHA is always fresh (the Step 1b fold commits on local `main`), so when checks ARE required,
|
|
162
|
+
pushing the branch *first* is structurally rejected until CI catches up. Push the **tag first** (it lands
|
|
163
|
+
the SHA and triggers the repo's CI — in MMI-Hub `gate.yml` runs on `v*` tags — without touching the
|
|
164
|
+
protected ref), then probe what `main` actually requires and wait only for those contexts:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
git push origin "vX.Y.0" # lands the SHA + triggers the repo's CI
|
|
168
|
+
SHA=$(git rev-parse main)
|
|
169
|
+
# discover the REQUIRED contexts on main (classic protection + rulesets; 404 = none from that source):
|
|
170
|
+
gh api repos/{owner}/{repo}/branches/main/protection/required_status_checks --jq '[.contexts[]]'
|
|
171
|
+
gh api repos/{owner}/{repo}/rules/branches/main \
|
|
172
|
+
--jq '[.[]|select(.type=="required_status_checks")|.parameters.required_status_checks[].context]'
|
|
173
|
+
# ZERO required contexts -> push main immediately (the GitHub push gate is the backstop).
|
|
174
|
+
# Otherwise poll until every required context is "success" on $SHA — never a hard-coded list, and bound
|
|
175
|
+
# the wait (~10 min): on timeout, stop with a clear failure naming the pending/failed contexts.
|
|
176
|
+
gh api repos/{owner}/{repo}/commits/$SHA/check-runs \
|
|
177
|
+
--jq '[.check_runs[]|{name:.name,conclusion:.conclusion}]'
|
|
178
|
+
git push origin main
|
|
179
|
+
```
|
|
180
|
+
(`mmi-cli release --apply` performs this discovery + bounded wait itself.)
|
|
181
|
+
Rejected → stop (nothing released).
|
|
182
|
+
|
|
183
|
+
## Step 4 — GitHub Release + start prod deploy (non-blocking)
|
|
184
|
+
|
|
185
|
+
For `tenant-container` repos, publish the GitHub Release, dispatch the central tenant deploy, and dispatch
|
|
186
|
+
the release-scoped wiki refresh (release events never leave the releasing repo, so the keeper must be
|
|
187
|
+
dispatched centrally — #759):
|
|
188
|
+
```bash
|
|
189
|
+
gh release create "vX.Y.0" --target main --generate-notes --latest
|
|
190
|
+
gh workflow run tenant-deploy.yml --repo mutmutco/MMI-Hub \
|
|
191
|
+
-f slug={slug} -f repo={owner}/{repo} -f ref=main -f stage=main
|
|
192
|
+
gh workflow run cursor-agents.yml --repo mutmutco/MMI-Hub \
|
|
193
|
+
-f agent=wiki-keeper -f only_repo={owner}/{repo} # wiki-keeper, scoped to the released repo
|
|
194
|
+
gh run watch "$(gh run list --workflow tenant-deploy.yml --limit 1 --json databaseId -q '.[0].databaseId')" \
|
|
195
|
+
--exit-status # the central prod-deploy run — run this in the BACKGROUND (Bash run_in_background)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
For `hub-serverless` (MMI-Hub), publish the GitHub Release but do **not** dispatch `tenant-deploy.yml` or
|
|
199
|
+
`cursor-agents.yml`: the release event auto-fires `deploy.yml` for prod, `publish.yml` for the plugin/CLI
|
|
200
|
+
package, and `cursor-agents.yml` for the Hub-scoped wiki refresh. A missing `DEPLOY#main` registry row for
|
|
201
|
+
MMI-Hub is expected, not a tenant stack repair task. Watch/report the release-triggered `deploy.yml` and
|
|
202
|
+
`publish.yml` runs instead.
|
|
203
|
+
|
|
204
|
+
For other direct-track repos, the train dispatches nothing centrally: a `registry-publish` repo's release
|
|
205
|
+
event fires its own `publish.yml` (npm / plugin marketplace); a `solo-container` repo deploys via its own
|
|
206
|
+
workflow. Publish the GitHub Release, then watch/report that repo's own release-triggered run.
|
|
207
|
+
|
|
208
|
+
`mmi-cli release --apply --json` returns the relevant run id/url data with `deployStatus`; `--watch` blocks
|
|
209
|
+
on the run(s) and resolves `deployStatus` to `success`/`failure`. For tenant-container repos, that is the
|
|
210
|
+
dispatched `tenant-deploy.yml` run. For Hub serverless, that is the auto-fired release `deploy.yml` and
|
|
211
|
+
`publish.yml` workflow pair.
|
|
212
|
+
`promoted: true` stays set even on a failed deploy — promotion and deploy are separate outcomes.
|
|
213
|
+
|
|
214
|
+
**Hub releases announce to Slack (#883).** Hub scope is **only** `mutmutco/MMI-Hub`: the commits/PRs on
|
|
215
|
+
`origin/main..origin/development`, this repo's `deploy.yml` + `publish.yml`, and Hub tooling (`mmi-cli`,
|
|
216
|
+
skills, plugin, registry, central workflows). Do **not** read another repo's board (`mmi-cli board` / `/mmi`
|
|
217
|
+
on a product), watch or dispatch `ds-propagate.yml`, run `design-system` / `doctor` design-system heals for
|
|
218
|
+
consumers, or treat a product's deploy state as part of this release — those belong to that product's own
|
|
219
|
+
`/release` or `/rcand`, never a Hub train.
|
|
220
|
+
|
|
221
|
+
Before running `--apply` for MMI-Hub, resolve the real tag first:
|
|
222
|
+
`TAG=$(node scripts/next-version.mjs cycle)` — always print `$TAG` (e.g. `v2.43.0`) in summaries, Slack,
|
|
223
|
+
chat, and the final report; never a placeholder like `vX.Y.0` or `v0.x.0`.
|
|
224
|
+
|
|
225
|
+
Write a curated summary — 3-6 very short plain lines, one change per line, dev-readable, no PR-dump —
|
|
226
|
+
to a fresh temp file (`f=$(mktemp tmp/release-summary.XXXXXX)`, so a stale prior summary is never reused).
|
|
227
|
+
Source from **Hub PR titles only** (`origin/main..origin/development` on `mutmutco/MMI-Hub`), but **rewrite**
|
|
228
|
+
each line in neutral Hub-subsystem terms (CLI, skills, plugin, workflows, registry, deploy hub) — **never**
|
|
229
|
+
a product or brand name (FoFu, Katip, etc.) anywhere in the summary file, Slack post, chat, or release
|
|
230
|
+
report. Product names are allowed only when releasing **that product's repo**. Then pass the file through:
|
|
231
|
+
`mmi-cli release --apply --announce-summary-file "$f"`. After the GitHub Release publishes, the CLI posts
|
|
232
|
+
the summary to the org alerts channel as the MMI-Future Slack app (token + channel from SSM at run time).
|
|
233
|
+
Without the file it falls back to a deterministic distillation of the generated notes (product brands are
|
|
234
|
+
stripped there too). The announcement is best-effort: a Slack failure is reported in the result and never
|
|
235
|
+
fails the release. Non-Hub repos skip the announcement automatically.
|
|
236
|
+
|
|
237
|
+
**Don't block on the deploy.** Start the watch as a background task and proceed to Steps 4b–5 (docs, project
|
|
238
|
+
info, branch alignment) while prod deploys. The verdict is collected in Step 6 — verification is not
|
|
239
|
+
skipped, only un-blocked. Deploy failure → report plainly; `main` is already at the release (correct), so
|
|
240
|
+
the correct next action is a **deploy retry of the existing main ref** after the runtime is repaired — never
|
|
241
|
+
a re-tag: `mmi-cli tenant redeploy {owner}/{repo} main --watch`.
|
|
242
|
+
|
|
243
|
+
## Step 4b — refresh docs + project info
|
|
244
|
+
|
|
245
|
+
A release is the heartbeat that keeps docs true. Before advancing items:
|
|
246
|
+
- Verify the repo's `README.md` / `architecture.md` (and, in the hub, `docs/org-readme.md` / `docs/org-architecture.md`)
|
|
247
|
+
state **present truth** for what just shipped; rewrite any drift (no change-comments — see AGENTS docs rule).
|
|
248
|
+
- Re-sync the **Project** info from those docs + current member repos: set the project's short description +
|
|
249
|
+
README (the thin dashboard — it *links* the docs, never duplicates them) via the `updateProjectV2`
|
|
250
|
+
mutation (`shortDescription`, `readme`).
|
|
251
|
+
|
|
252
|
+
## Step 5 — roll development forward
|
|
253
|
+
|
|
254
|
+
- Keep branches aligned: `mmi-cli release --apply` back-merges the released `main` (incl. the version
|
|
255
|
+
fold) into `development` and reports it as `devRollForward`. When `development` has no required checks it
|
|
256
|
+
pushes directly (`status: pushed`). When `development` *requires* checks (e.g. MMI-Hub needs
|
|
257
|
+
`cli`/`infra`/`docs`), the fresh merge commit carries no passing checks, so a direct push is structurally
|
|
258
|
+
rejected — the train instead **opens an alignment PR** `main → development` (`status: pr-pending`) and the
|
|
259
|
+
release report prints the exact land command. The release itself has already shipped; **land the alignment
|
|
260
|
+
PR with a true merge** — `gh pr merge <number> --merge` (never squash — a squash drops the merge parentage
|
|
261
|
+
and the misalignment guard re-flags the divergence). Never force.
|
|
262
|
+
- Full-track repos: `mmi-cli release --apply` already aligned `rc` to the released `main` (#1036 — the
|
|
263
|
+
push runs inside the authority-gated train; the result reports it as `rcAlignment`). No manual `rc`
|
|
264
|
+
push — if the result reports a failed alignment, investigate and rerun via the train, never bare-push.
|
|
265
|
+
|
|
266
|
+
## Step 6 — collect deploy verdict + report
|
|
267
|
+
|
|
268
|
+
Collect the backgrounded prod-deploy watch from Step 4 (it has typically finished by now). Confirm prod is
|
|
269
|
+
healthy (the central deploy workflow smoke step / a health check); **red** → report the failure prominently and flag
|
|
270
|
+
that the release shipped on a failed deploy (re-run just the deploy — `main` is already correct).
|
|
271
|
+
|
|
272
|
+
Hub releases always carry a distribution bump (the Step 1b fold), so the **publish workflow**
|
|
273
|
+
(`publish.yml`) ships `@mutmutco/cli` and `@mutmutco/opencode-mmi` to npm on the GitHub Release from Step 4 — don't publish by hand.
|
|
274
|
+
Watch it, then confirm npm caught up:
|
|
275
|
+
```bash
|
|
276
|
+
gh run watch "$(gh run list --workflow publish.yml --event release --limit 1 --json databaseId -q '.[0].databaseId')" --exit-status
|
|
277
|
+
node scripts/release-distribution.mjs verify "vX.Y.0" # asserts Claude/Codex/Cursor/OpenCode surfaces and npm == the release version
|
|
278
|
+
```
|
|
279
|
+
Run that verify from a checkout **at the tag** (`main` right after the release merge, `git checkout
|
|
280
|
+
"vX.Y.0"`, or a worktree at the tag) — verify checks the working tree and refuses any other commit once
|
|
281
|
+
the tag exists, so a stale `development`/`rc` checkout can't masquerade as a broken release.
|
|
282
|
+
Release-blocking for Hub tooling changes: Claude plugin, Codex plugin, Cursor plugin, OpenCode plugin, and npm users must see the same
|
|
283
|
+
version. Manual fallback if CI can't publish: `node scripts/release-distribution.mjs publish "vX.Y.0"` from a
|
|
284
|
+
machine with npm auth.
|
|
285
|
+
|
|
286
|
+
Report: Release `$TAG` (the resolved tag from Step 2 — never a placeholder) + the GitHub Release URL · prod
|
|
287
|
+
deploy run + URL + **green/red** · branch-alignment note · npm publish run + CLI version (Hub releases).
|
|
288
|
+
|
|
289
|
+
## Notes
|
|
290
|
+
|
|
291
|
+
- PATCH-level releases are `/hotfix` only (a hotfix always skips rc — it cherry-picks `development → main`
|
|
292
|
+
directly); planned releases are MINOR or MAJOR. Never force-push `main`.
|
|
293
|
+
- **`--dev` (full-track only):** releases `development → main` skipping rc, with the same fold/tag/Release/
|
|
294
|
+
deploy machinery plus the post-release rc retirement and rc alignment. Refuses (fail closed) when
|
|
295
|
+
`origin/rc` carries commits not in `development`; on direct-track repos it's a no-op (they already
|
|
296
|
+
release from `development`).
|
|
297
|
+
- **MAJOR / exact-target release:** to ship a version the tag math can't derive (a MAJOR like `2.0.0`, or
|
|
298
|
+
skipping a version already on npm), product repos export `MMI_RELEASE_VERSION=X.Y.Z` for **both** `/rcand`
|
|
299
|
+
and `/release`; MMI-Hub exports it for `/release` only. Steps 1b/2 then fold and tag exactly that
|
|
300
|
+
version. Unset, the train ships the open cycle.
|
|
301
|
+
|
|
302
|
+
## Retro — one check before you finish
|
|
303
|
+
Before your final report, answer one question honestly: did **this skill's own instructions** misfire
|
|
304
|
+
this run — ambiguous wording, a misleading message, or an environment failure it should have warned
|
|
305
|
+
about? (Process only — never the user's code or task; e.g. a misleading authority or gate message, or an
|
|
306
|
+
ambiguous version-fold or back-merge step.) If yes, file **one** lesson and move on; a clean run is silent
|
|
307
|
+
(hard cap: one per run). It lands on the Hub board (deduped) and is fixed only via a reviewed PR — never
|
|
308
|
+
edit the skill live; the retro is advisory, so if the call fails, note it and continue:
|
|
309
|
+
`mmi-cli skill-lesson --skill release --title "<what misfired>" --body "<what; evidence; proposed amendment>"`
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: secrets
|
|
3
|
+
description: Manage this repo's secrets — list/get/set/edit/rm/use the PROJECT tier you self-serve (your /mmi-future/<slug>/dev/* keys), plus the master-only grant/revoke into an ORG-tier secret. Use when a dev wants to add, rotate, read, remove, or consume a project secret/key, asks where a secret lives or which tier it's in, the master wants org-tier grant/revoke, or invokes /secrets. Never echo a secret value.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /secrets — two-tier project secrets
|
|
7
|
+
|
|
8
|
+
**Authority:** project-tier ops are self-serve for the repo's project-admin (`mmi-cli access role` →
|
|
9
|
+
`project-admin` + `train` on that repo). Do not redirect an authorized project-admin to the master for
|
|
10
|
+
`dev/*` keys — use this skill. Org-tier grant/revoke stays master-only (`AGENTS.md` § Authority).
|
|
11
|
+
|
|
12
|
+
Secrets in the org split by **blast radius + who manages them** (not by storage — both tiers are SSM
|
|
13
|
+
SecureString + KMS, and **a value is never echoed to chat or logs**):
|
|
14
|
+
|
|
15
|
+
- **PROJECT tier** — `/mmi-future/<slug>/dev/*`. Lower-blast dev/throwaway working secrets (a scraper key,
|
|
16
|
+
a third-party API key, a throwaway SaaS account). The repo's **project-admin self-serves** these via
|
|
17
|
+
`/secrets`, on their **GitHub role alone** — no AWS, no waiting on the master.
|
|
18
|
+
- **ORG tier** — everything else under the slug (`rc/*`, `main/*` prod secrets) plus shared/infra
|
|
19
|
+
(`/mmi-future/{shared,cloudflare,docs,mmi-hub}/*`). **Crown jewels, master-gated.** A project-admin
|
|
20
|
+
reaches an org-tier secret only via a master **grant**; the master is unrestricted (and **is** a
|
|
21
|
+
project-admin of every repo — master ⊇ project-admin).
|
|
22
|
+
|
|
23
|
+
A bare `<KEY>` defaults into the **project tier** (`dev/`). To touch the org tier you name the env
|
|
24
|
+
explicitly: `main/GOOGLE_CLIENT_SECRET`, `rc/DB_URL`.
|
|
25
|
+
|
|
26
|
+
All ops run through `mmi-cli secrets …`, which calls the org backend with the caller's `gh` token; the
|
|
27
|
+
backend re-verifies **project-admin-of-this-repo** and the **tier scope** server-side and does the scoped
|
|
28
|
+
SSM op. This skill never touches AWS.
|
|
29
|
+
|
|
30
|
+
## Step 0 — orient
|
|
31
|
+
|
|
32
|
+
Read the current repo + slug; the tier of a key follows from its name (above). `secrets list` shows what
|
|
33
|
+
exists and which ones **you** can manage (a `*`), never values.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
mmi-cli secrets list # names + tier + a * on the ones you can write. NEVER values.
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`secrets: no registry META` or `Hub API unreachable` → the repo is not registered with the Hub, GitHub auth
|
|
40
|
+
is missing, or the Hub API is unavailable. Run `mmi-cli project get <owner/repo>` to distinguish those cases;
|
|
41
|
+
a master-admin backfills the registry before secrets can be resolved.
|
|
42
|
+
|
|
43
|
+
## Step 1 — the verb
|
|
44
|
+
|
|
45
|
+
Default to the **current repo**; pass `--repo owner/Name` to target another (you must be its project-admin).
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Read one value — printed ONCE, raw (so it pipes). Do not paste it into chat, a file, or a log.
|
|
49
|
+
mmi-cli secrets get SCRAPER_API_KEY
|
|
50
|
+
|
|
51
|
+
# Write / rotate — the VALUE is read from stdin, NEVER an argument (so it can't leak into shell history
|
|
52
|
+
# or process args). Pipe it in:
|
|
53
|
+
printf %s "$THE_VALUE" | mmi-cli secrets set SCRAPER_API_KEY
|
|
54
|
+
mmi-cli secrets edit SCRAPER_API_KEY # alias for set — same stdin path
|
|
55
|
+
|
|
56
|
+
# Validate a known provider key without printing its value:
|
|
57
|
+
mmi-cli secrets verify RECALL_API_KEY
|
|
58
|
+
|
|
59
|
+
# Remove:
|
|
60
|
+
mmi-cli secrets rm SCRAPER_API_KEY
|
|
61
|
+
|
|
62
|
+
# How to CONSUME a secret without committing it (no value printed):
|
|
63
|
+
mmi-cli secrets use SCRAPER_API_KEY
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Never** pass a value as an argument (`secrets set KEY thevalue` is wrong — there is no value arg). The
|
|
67
|
+
confirmation prints the **name and tier only**, never the value.
|
|
68
|
+
|
|
69
|
+
## Rotation checklist
|
|
70
|
+
|
|
71
|
+
Before rotating, enumerate every copy of the key so no tier stays stale:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
mmi-cli secrets list --repo owner/Repo
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Check bare/dev, `rc/`, `main/`, and any legacy unprefixed copy the list shows. Rotate the provider-side key
|
|
78
|
+
first, then write every canonical vault tier that still needs that key. For keys with a provider probe
|
|
79
|
+
(`RECALL_API_KEY` today), `secrets set` validates the new value before printing success; `secrets verify
|
|
80
|
+
<KEY>` repeats the same probe later without printing the value. If the verifier fails, treat the rotation as
|
|
81
|
+
incomplete even if the vault write itself succeeded.
|
|
82
|
+
|
|
83
|
+
## Step 2 — org-tier elevation (master-only)
|
|
84
|
+
|
|
85
|
+
A project-admin who needs a specific **org-tier** secret asks the master. The master grants a **scoped,
|
|
86
|
+
auditable** standing access to that one key (or revokes it). These verbs are **master-only** — the backend
|
|
87
|
+
403s anyone else.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# MASTER: let @oguz-mut manage one org-tier secret in Pulse
|
|
91
|
+
mmi-cli secrets grant mutmutco/Pulse oguz-mut main/GOOGLE_CLIENT_SECRET
|
|
92
|
+
# MASTER: withdraw it
|
|
93
|
+
mmi-cli secrets revoke mutmutco/Pulse oguz-mut main/GOOGLE_CLIENT_SECRET
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The master can also just operate the org-tier op directly while guiding (master ⊇ project-admin). Org-tier
|
|
97
|
+
access by a project-admin **always originates from the master** — never unilaterally.
|
|
98
|
+
|
|
99
|
+
## Tier — which one does a new secret belong in?
|
|
100
|
+
|
|
101
|
+
- A **dev/throwaway** working credential (rotatable, project-scoped blast radius) → **project tier**: a bare
|
|
102
|
+
`<KEY>` (lands in `dev/`). The project-admin owns it.
|
|
103
|
+
- A **real production** credential (prod OAuth client, prod DB, deploy role) → **org tier**: `main/<KEY>` or
|
|
104
|
+
`rc/<KEY>`, master-managed. If a dev secret graduates to prod, ask the master to promote it.
|
|
105
|
+
|
|
106
|
+
## Tier-to-tier copy (provider keys, #1433)
|
|
107
|
+
|
|
108
|
+
**Encryption / stage-distinct keys** (`*_ENC_KEY`, `SECRET_KEY_BASE`, etc.) must be **generated per stage** —
|
|
109
|
+
never copied. **Provider sandbox keys** (e.g. Recall/Gemini for Katip dev/rc) **may** be shared across dev/rc
|
|
110
|
+
when they point at the same sandbox project; prod stays org-tier and distinct.
|
|
111
|
+
|
|
112
|
+
Sanctioned copy (master-gated when the source tier is org):
|
|
113
|
+
```bash
|
|
114
|
+
mmi-cli secrets copy --from rc --to dev --keys RECALL_API_KEY,GEMINI_API_KEY
|
|
115
|
+
mmi-cli secrets copy --from rc --to dev --keys RECALL_API_KEY --dry-run # plan only
|
|
116
|
+
```
|
|
117
|
+
Prefer this over manual `secrets get | secrets set` piping — audit-logged, blocklist enforced.
|
|
118
|
+
|
|
119
|
+
## Notes
|
|
120
|
+
|
|
121
|
+
- **Never echo a value** — not in chat, a commit, a log, or an issue. Only `secrets get` emits a value, once,
|
|
122
|
+
to stdout for piping. Treat every secret as write-once.
|
|
123
|
+
- Authority is **GitHub**: project-admin = repo `write` plus the registry `projectAdmins` entry, master =
|
|
124
|
+
org owner. The backend checks both with the org App token, so the decision is server-side (your token
|
|
125
|
+
scope can't widen it).
|
|
126
|
+
- Runtime + CI read their own tiers **keylessly via OIDC** — they don't use `/secrets`. `secrets use <KEY>`
|
|
127
|
+
prints the consumption guidance (runtime OIDC read · CI `aws ssm get-parameter` · local gitignored `.env`).
|
|
128
|
+
- Every self-service write is **attributable** — the backend logs actor + repo + KEY name (never the value).
|
|
129
|
+
|
|
130
|
+
## Retro — one check before you finish
|
|
131
|
+
Before your final report, answer one question honestly: did **this skill's own instructions** misfire
|
|
132
|
+
this run — ambiguous wording, a misleading message, or an environment failure it should have warned
|
|
133
|
+
about? (Process only — never the user's code or task; e.g. a canonical-name or tier mix-up, or a step
|
|
134
|
+
that risked echoing a value.) If yes, file **one** lesson and move on; a clean run is silent (hard cap:
|
|
135
|
+
one per run). It lands on the Hub board (deduped) and is fixed only via a reviewed PR — never edit the
|
|
136
|
+
skill live; the retro is advisory, so if the call fails, note it and continue:
|
|
137
|
+
`mmi-cli skill-lesson --skill secrets --title "<what misfired>" --body "<what; evidence; proposed amendment>"`
|