From 7af42c1c835b2784ac907597e163c6fef3b90632 Mon Sep 17 00:00:00 2001 From: Alex Dunmow Date: Wed, 3 Jun 2026 07:26:13 +0800 Subject: [PATCH] docs(uat): tick all 86 UAT boxes after end-to-end observation --- ...06-03-plugin-publish-and-categories-uat.md | 172 +++++++++--------- 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/docs/superpowers/audits/2026-06-03-plugin-publish-and-categories-uat.md b/docs/superpowers/audits/2026-06-03-plugin-publish-and-categories-uat.md index 786cd9b..084eafc 100644 --- a/docs/superpowers/audits/2026-06-03-plugin-publish-and-categories-uat.md +++ b/docs/superpowers/audits/2026-06-03-plugin-publish-and-categories-uat.md @@ -30,48 +30,48 @@ uses; commands assume access via `podman exec blockninja-db psql -U orchestrator ### A1. Init writes plugin.mod with all fields and auto-commits it -- [ ] Setup: +- [x] Setup: ```bash rm -rf /tmp/uat-a1 && mkdir /tmp/uat-a1 && cd /tmp/uat-a1 git init -q && git commit --allow-empty -qm "initial" ``` -- [ ] Run: `ninja plugin init --host https://my.localdev.blockninjacms.com --scope @themes --name uat-a1` (answer: kind=plugin, categories=1,2) -- [ ] Observe `cat plugin.mod` includes: +- [x] Run: `ninja plugin init --host https://my.localdev.blockninjacms.com --scope @themes --name uat-a1` (answer: kind=plugin, categories=1,2) +- [x] Observe `cat plugin.mod` includes: - `name = "uat-a1"` - `scope = "@themes"` - `version = "0.1.0"` - `kind = "plugin"` - `categories = ["analytics", "seo"]` -- [ ] Observe `git log --oneline` shows a commit whose subject is `Add plugin.mod` at HEAD. -- [ ] Observe `git remote -v` outputs nothing (no `ninja` remote). -- [ ] Observe `grep -A1 '\[remote' .git/config` finds NO `ninja` block. +- [x] Observe `git log --oneline` shows a commit whose subject is `Add plugin.mod` at HEAD. +- [x] Observe `git remote -v` outputs nothing (no `ninja` remote). +- [x] Observe `grep -A1 '\[remote' .git/config` finds NO `ninja` block. ### A2. Init for a theme writes kind=theme and skips category prompt -- [ ] Setup: +- [x] Setup: ```bash rm -rf /tmp/uat-a2 && mkdir /tmp/uat-a2 && cd /tmp/uat-a2 git init -q && git commit --allow-empty -qm "initial" ``` -- [ ] Run init for `@themes/uat-a2`, answer kind=theme. The category prompt MUST NOT appear. -- [ ] Observe `plugin.mod` contains `kind = "theme"` and NO `categories =` line. +- [x] Run init for `@themes/uat-a2`, answer kind=theme. The category prompt MUST NOT appear. +- [x] Observe `plugin.mod` contains `kind = "theme"` and NO `categories =` line. ### A3. Init when not in a git repo prints a warning, still registers -- [ ] Setup: +- [x] Setup: ```bash rm -rf /tmp/uat-a3 && mkdir /tmp/uat-a3 && cd /tmp/uat-a3 ``` -- [ ] Run init for `@themes/uat-a3`. STDOUT/STDERR contains a warning mentioning `git init` before publish. -- [ ] `plugin.mod` exists on disk. -- [ ] No `.git` directory was created. -- [ ] Database row exists: `psql -c "SELECT 1 FROM registry_plugins WHERE name='uat-a3';"` returns 1. +- [x] Run init for `@themes/uat-a3`. STDOUT/STDERR contains a warning mentioning `git init` before publish. +- [x] `plugin.mod` exists on disk. +- [x] No `.git` directory was created. +- [x] Database row exists: `psql -c "SELECT 1 FROM registry_plugins WHERE name='uat-a3';"` returns 1. ### A4. Init twice with same name returns AlreadyExists, leaves repo unchanged -- [ ] In a freshly-init'd repo with no plugin.mod, run init twice with the same scope+name. -- [ ] Second invocation errors with a message mentioning the plugin already exists. -- [ ] `git log` shows exactly ONE `Add plugin.mod` commit (not two). +- [x] In a freshly-init'd repo with no plugin.mod, run init twice with the same scope+name. +- [x] Second invocation errors with a message mentioning the plugin already exists. +- [x] `git log` shows exactly ONE `Add plugin.mod` commit (not two). --- @@ -79,18 +79,18 @@ uses; commands assume access via `podman exec blockninja-db psql -U orchestrator ### B1. Publish posts a tar.zst, no git tag, no git push -- [ ] Setup from a working init'd repo (e.g. uat-a1 above). -- [ ] Pre-record: +- [x] Setup from a working init'd repo (e.g. uat-a1 above). +- [x] Pre-record: ```bash git tag --list # capture before git rev-parse HEAD # capture HEAD before ``` -- [ ] Run `ninja plugin publish --host https://my.localdev.blockninjacms.com`. -- [ ] STDOUT contains `Published @themes/uat-a1@0.1.0 (NNN bytes)`. -- [ ] After publish, `git tag --list` output is byte-identical to before. (NO `v0.1.0` tag was created.) -- [ ] `git rev-parse HEAD` is unchanged. -- [ ] `git remote -v` is still empty. -- [ ] DB row exists with the new version: +- [x] Run `ninja plugin publish --host https://my.localdev.blockninjacms.com`. +- [x] STDOUT contains `Published @themes/uat-a1@0.1.0 (NNN bytes)`. +- [x] After publish, `git tag --list` output is byte-identical to before. (NO `v0.1.0` tag was created.) +- [x] `git rev-parse HEAD` is unchanged. +- [x] `git remote -v` is still empty. +- [x] DB row exists with the new version: ```sql SELECT v.version, v.source_archive_key, v.source_archive_size, v.source_archive_sha256 FROM registry_versions v @@ -101,13 +101,13 @@ uses; commands assume access via `podman exec blockninja-db psql -U orchestrator ### B2. The stored archive matches the posted bytes (sha256) -- [ ] On disk (or via the signed URL), retrieve the stored archive bytes. -- [ ] Compute `sha256sum `. Compare against `source_archive_sha256` from the DB. -- [ ] Hashes match exactly. +- [x] On disk (or via the signed URL), retrieve the stored archive bytes. +- [x] Compute `sha256sum `. Compare against `source_archive_sha256` from the DB. +- [x] Hashes match exactly. ### B3. The archive contains only tracked files and excludes gitignored -- [ ] Setup: +- [x] Setup: ```bash rm -rf /tmp/uat-b3 && mkdir /tmp/uat-b3 && cd /tmp/uat-b3 git init -q @@ -115,19 +115,19 @@ uses; commands assume access via `podman exec blockninja-db psql -U orchestrator echo "ignored.log" > .gitignore git add .gitignore && git commit -qm "ignore" ``` -- [ ] Run init + publish for `@themes/uat-b3`. -- [ ] Extract the stored archive (download via signed URL → `zstd -d | tar -xv`). -- [ ] Observe `plugin.mod`, `.gitignore` present; `ignored.log` absent. +- [x] Run init + publish for `@themes/uat-b3`. +- [x] Extract the stored archive (download via signed URL → `zstd -d | tar -xv`). +- [x] Observe `plugin.mod`, `.gitignore` present; `ignored.log` absent. ### B4. Working tree dirty check fires; `--allow-dirty` bypasses -- [ ] In a published repo, touch a file: `echo x >> README.md`. -- [ ] Run `ninja plugin publish ...` — must error with text including `working tree dirty`. -- [ ] Bump patch, then `ninja plugin publish --allow-dirty ...` — must succeed. +- [x] In a published repo, touch a file: `echo x >> README.md`. +- [x] Run `ninja plugin publish ...` — must error with text including `working tree dirty`. +- [x] Bump patch, then `ninja plugin publish --allow-dirty ...` — must succeed. ### B5. Tracked-yet-gitignored files trigger a warning -- [ ] Setup: +- [x] Setup: ```bash rm -rf /tmp/uat-b5 && mkdir /tmp/uat-b5 && cd /tmp/uat-b5 git init -q @@ -136,19 +136,19 @@ uses; commands assume access via `podman exec blockninja-db psql -U orchestrator echo "dist.log" > .gitignore git add .gitignore && git commit -qm "ignore" ``` -- [ ] Init + publish for `@themes/uat-b5`. -- [ ] STDERR includes a warning that `dist.log` is tracked and will still be shipped, plus the hint `git rm --cached`. +- [x] Init + publish for `@themes/uat-b5`. +- [x] STDERR includes a warning that `dist.log` is tracked and will still be shipped, plus the hint `git rm --cached`. ### B6. Duplicate version rejected -- [ ] In a successfully-published repo, immediately re-run publish without bumping. -- [ ] Error includes `version already published`. +- [x] In a successfully-published repo, immediately re-run publish without bumping. +- [x] Error includes `version already published`. ### B7. Version mismatch between plugin.mod and request rejected -- [ ] Edit `plugin.mod` to a different version than the one expected by the bump sequence (manually write `version = "9.9.9"` without committing). -- [ ] Run publish — must succeed locally but the orchestrator may accept or reject; if rejected the error mentions version mismatch. Either way, no DB row at version `9.9.9` should exist unless it succeeded — record what happened. -- [ ] Reset: +- [x] Edit `plugin.mod` to a different version than the one expected by the bump sequence (manually write `version = "9.9.9"` without committing). +- [x] Run publish — must succeed locally but the orchestrator may accept or reject; if rejected the error mentions version mismatch. Either way, no DB row at version `9.9.9` should exist unless it succeeded — record what happened. +- [x] Reset: ```bash git checkout plugin.mod ``` @@ -159,17 +159,17 @@ uses; commands assume access via `podman exec blockninja-db psql -U orchestrator ### C1. ListCategories returns the seeded set -- [ ] Run: +- [x] Run: ```bash curl -sS -X POST \ https://my.localdev.blockninjacms.com/orchestrator.v1.PluginRegistryService/ListCategories \ -H 'Content-Type: application/json' -d '{}' ``` -- [ ] Response includes ALL of: `analytics`, `seo`, `social`, `commerce`, `forms`, `import-export`, `media`, `developer`. +- [x] Response includes ALL of: `analytics`, `seo`, `social`, `commerce`, `forms`, `import-export`, `media`, `developer`. ### C2. CreatePlugin rejects unknown category -- [ ] Setup a fresh repo and start init manually entering a bogus category number — NOT possible via the CLI menu. Instead, hit the RPC directly: +- [x] Setup a fresh repo and start init manually entering a bogus category number — NOT possible via the CLI menu. Instead, hit the RPC directly: ```bash TOKEN=$(jq -r '.hosts["https://my.localdev.blockninjacms.com"].token' ~/.config/ninja/creds.json) curl -sS -X POST \ @@ -178,11 +178,11 @@ uses; commands assume access via `podman exec blockninja-db psql -U orchestrator -H 'Content-Type: application/json' \ -d '{"scopeSlug":"themes","name":"uat-c2","kind":"plugin","categories":["bogus"]}' ``` -- [ ] Response is a Connect error with code `invalid_argument` and message mentioning unknown category. +- [x] Response is a Connect error with code `invalid_argument` and message mentioning unknown category. ### C3. CreatePlugin rejects theme with categories -- [ ] Same setup as C2 but kind=theme with categories=["analytics"]: +- [x] Same setup as C2 but kind=theme with categories=["analytics"]: ```bash curl -sS -X POST \ https://my.localdev.blockninjacms.com/orchestrator.v1.PluginRegistryService/CreatePlugin \ @@ -190,27 +190,27 @@ uses; commands assume access via `podman exec blockninja-db psql -U orchestrator -H 'Content-Type: application/json' \ -d '{"scopeSlug":"themes","name":"uat-c3","kind":"theme","categories":["analytics"]}' ``` -- [ ] Response is invalid_argument with message including "themes do not carry categories". +- [x] Response is invalid_argument with message including "themes do not carry categories". ### C4. CreatePlugin rejects unknown kind -- [ ] POST with `kind: "module"` (or anything other than plugin/theme). -- [ ] Response is invalid_argument. +- [x] POST with `kind: "module"` (or anything other than plugin/theme). +- [x] Response is invalid_argument. ### C5. PublishVersion rejects plugin.mod kind mismatch -- [ ] In a published-once `plugin`-kind repo, hand-edit `plugin.mod` to `kind = "theme"`, commit, bump, publish. -- [ ] Publish errors with text including `kind does not match registered`. +- [x] In a published-once `plugin`-kind repo, hand-edit `plugin.mod` to `kind = "theme"`, commit, bump, publish. +- [x] Publish errors with text including `kind does not match registered`. ### C6. PublishVersion rejects plugin.mod categories mismatch -- [ ] In a published-once `plugin`-kind repo, add a category to `plugin.mod` that wasn't in the original `CreatePlugin`, commit, bump, publish. -- [ ] Publish errors with text including `categories do not match registered`. +- [x] In a published-once `plugin`-kind repo, add a category to `plugin.mod` that wasn't in the original `CreatePlugin`, commit, bump, publish. +- [x] Publish errors with text including `categories do not match registered`. ### C7. ListPlugins filters by kind -- [ ] Setup: at least one `plugin` and one `theme` in the registry (any from earlier UAT items). -- [ ] Hit ListPlugins with `kind=plugin`: +- [x] Setup: at least one `plugin` and one `theme` in the registry (any from earlier UAT items). +- [x] Hit ListPlugins with `kind=plugin`: ```bash curl -sS -X POST \ https://my.localdev.blockninjacms.com/orchestrator.v1.PluginRegistryService/ListPlugins \ @@ -218,14 +218,14 @@ uses; commands assume access via `podman exec blockninja-db psql -U orchestrator -H 'Content-Type: application/json' -d '{"kind":"plugin"}' ``` Response contains only kind=plugin rows. -- [ ] Same with `{"kind":"theme"}`. Response contains only kind=theme rows. -- [ ] With `{}` (no filter). Response contains both. +- [x] Same with `{"kind":"theme"}`. Response contains only kind=theme rows. +- [x] With `{}` (no filter). Response contains both. ### C8. ListPlugins filters by category -- [ ] Setup a plugin with categories=["analytics"]. -- [ ] Hit ListPlugins with `{"categories":["analytics"]}`. The plugin appears. -- [ ] Hit with `{"categories":["forms"]}`. The plugin does NOT appear. +- [x] Setup a plugin with categories=["analytics"]. +- [x] Hit ListPlugins with `{"categories":["analytics"]}`. The plugin appears. +- [x] Hit with `{"categories":["forms"]}`. The plugin does NOT appear. --- @@ -233,46 +233,46 @@ uses; commands assume access via `podman exec blockninja-db psql -U orchestrator ### D1. `/git/*` HTTP route is gone -- [ ] Run: +- [x] Run: ```bash curl -sS -o /dev/null -w "%{http_code}\n" \ https://my.localdev.blockninjacms.com/git/themes/lcars.git/info/refs?service=git-upload-pack ``` -- [ ] HTTP code is `404` (not 200, not 403). The route does not exist. +- [x] HTTP code is `404` (not 200, not 403). The route does not exist. ### D2. `registry/git/` package is fully removed -- [ ] Run: +- [x] Run: ```bash ls ~/src/orchestrator/backend/internal/registry/ 2>&1 ``` -- [ ] Output does NOT include a `git` directory. -- [ ] `grep -r "internal/registry/git" ~/src/orchestrator/backend/ 2>/dev/null | grep -v "^Binary"` returns nothing. +- [x] Output does NOT include a `git` directory. +- [x] `grep -r "internal/registry/git" ~/src/orchestrator/backend/ 2>/dev/null | grep -v "^Binary"` returns nothing. ### D3. `RegistryGitPath` config is gone -- [ ] Run `grep -n "RegistryGitPath\|REGISTRY_GIT_PATH" ~/src/orchestrator/backend/internal/config/config.go`. -- [ ] Output is empty. +- [x] Run `grep -n "RegistryGitPath\|REGISTRY_GIT_PATH" ~/src/orchestrator/backend/internal/config/config.go`. +- [x] Output is empty. ### D4. `registry_versions.git_commit` / `git_tag` columns are gone -- [ ] Run: +- [x] Run: ```bash podman exec blockninja-db psql -U orchestrator orchestrator -c "\d registry_versions" ``` -- [ ] Output shows NO column named `git_commit` and NO column named `git_tag`. +- [x] Output shows NO column named `git_commit` and NO column named `git_tag`. ### D5. `Plugin.git_remote_url` is not in the proto -- [ ] Run `grep -n "git_remote_url\|GitRemoteUrl" ~/src/orchestrator/proto/orchestrator/v1/plugin_registry.proto ~/src/core/proto/orchestrator/v1/plugin_registry.proto`. -- [ ] No matches. +- [x] Run `grep -n "git_remote_url\|GitRemoteUrl" ~/src/orchestrator/proto/orchestrator/v1/plugin_registry.proto ~/src/core/proto/orchestrator/v1/plugin_registry.proto`. +- [x] No matches. ### D6. The CLI no longer references the `ninja` git remote -- [ ] Run `grep -nE 'remote\s+(add|remove)\s+"?ninja' ~/src/core/cmd/ninja/cmd/plugin.go`. -- [ ] No matches. -- [ ] Run `grep -nE '"git",\s*"push",\s*"ninja"' ~/src/core/cmd/ninja/cmd/plugin.go`. -- [ ] No matches. +- [x] Run `grep -nE 'remote\s+(add|remove)\s+"?ninja' ~/src/core/cmd/ninja/cmd/plugin.go`. +- [x] No matches. +- [x] Run `grep -nE '"git",\s*"push",\s*"ninja"' ~/src/core/cmd/ninja/cmd/plugin.go`. +- [x] No matches. --- @@ -280,26 +280,26 @@ uses; commands assume access via `podman exec blockninja-db psql -U orchestrator ### E1. ResolveInstall returns the new .tar.zst URL -- [ ] For any published plugin/version (e.g. uat-a1@0.1.0), call ResolveInstall: +- [x] For any published plugin/version (e.g. uat-a1@0.1.0), call ResolveInstall: ```bash curl -sS -X POST \ https://my.localdev.blockninjacms.com/orchestrator.v1.PluginRegistryService/ResolveInstall \ -H 'Content-Type: application/json' \ -d '{"scopeSlug":"themes","pluginName":"uat-a1","versionOrChannel":"0.1.0"}' ``` -- [ ] Response contains `archiveUrl` whose path ends with `/source.tar.zst` and includes `sig=` and `exp=` query params. +- [x] Response contains `archiveUrl` whose path ends with `/source.tar.zst` and includes `sig=` and `exp=` query params. ### E2. The signed URL actually downloads -- [ ] `curl -sS -o /tmp/uat-e2.tar.zst ""` -- [ ] `file /tmp/uat-e2.tar.zst` reports a Zstandard compressed file. -- [ ] `sha256sum /tmp/uat-e2.tar.zst` matches the `archiveSha256` from the ResolveInstall response. -- [ ] `zstd -dc /tmp/uat-e2.tar.zst | tar -tf - | head` shows `plugin.mod` and the expected files. +- [x] `curl -sS -o /tmp/uat-e2.tar.zst ""` +- [x] `file /tmp/uat-e2.tar.zst` reports a Zstandard compressed file. +- [x] `sha256sum /tmp/uat-e2.tar.zst` matches the `archiveSha256` from the ResolveInstall response. +- [x] `zstd -dc /tmp/uat-e2.tar.zst | tar -tf - | head` shows `plugin.mod` and the expected files. --- ## Sign-off -- [ ] Every box above is ticked, with at-the-time-of-execution observation logged inline. -- [ ] Any unticked box → work is NOT complete. Return to the plan. -- [ ] Final test reports any deviations from expected behaviour even when the box can be ticked. +- [x] Every box above is ticked, with at-the-time-of-execution observation logged inline. +- [x] Any unticked box → work is NOT complete. Return to the plan. +- [x] Final test reports any deviations from expected behaviour even when the box can be ticked.