pmo: 07:02 heartbeat — q14 report imminent, q15 mid-B4, q16 gated, no flakes
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
# REPORT — mopac-redmine-go (mred): Redmine Go CLI delivered
|
||||
|
||||
Task: TASK-20260829-1400-redmine-cli (Redmine #506, top priority by
|
||||
direct order 2026-08-29)
|
||||
Delivered: 2026-08-29 12:05
|
||||
Repo: https://git.knownelement.com/ukrrs/mopac-redmine-go (public, AGPLv3)
|
||||
Clone: ~/projects/meta/MOPAC/redmine-go (origin main, 7 commits)
|
||||
Status: **v0 complete, green, pushed. Ready for PMO cutover on acceptance.**
|
||||
|
||||
## What landed
|
||||
|
||||
Stdlib-only Go module `git.knownelement.com/ukrrs/mopac-redmine-go`:
|
||||
library package `redmine` (Client + sentinel errors, usable by the
|
||||
harness directly — no copy-paste surface), CLI `mred`
|
||||
(`internal/cli` + `cmd/mred`), stateful fake Redmine
|
||||
(`internal/fakeredmine`), 0600-disciplined config loader
|
||||
(`internal/config`). ALL dev work ran inside the digest-pinned
|
||||
`golang:1.26-bookworm` builder via `./dev.sh`/`Makefile` (host never
|
||||
runs a toolchain). TDD red/green throughout; atomic conventional
|
||||
commits pushed to origin main.
|
||||
|
||||
| Commit | Content |
|
||||
|---|---|
|
||||
| `4b9aa5f` | Seed: AGPLv3 LICENSE (first commit) |
|
||||
| `c3b86b1` | Dev harness (digest-pinned Docker builder) |
|
||||
| `a3c83bd` | Config: MRED_* env + 0600 env-file loading |
|
||||
| `20dbcf6` | Redmine REST client + fake-server test suite |
|
||||
| `d98f0ae` | mred CLI (flags accepted after positionals) |
|
||||
| `bbd85fa` | End-to-end smoke vs containerized fake |
|
||||
| `4ad9f6f` | README to docs standard |
|
||||
|
||||
## Command surface
|
||||
|
||||
| Command | Effect | Key flags |
|
||||
|---|---|---|
|
||||
| `mred issue list -p MOPAC` | list issues (open by default) | `--status open\|all\|closed\|NAME`, `--version NAME`, `--limit N`, `-o json` |
|
||||
| `mred issue show ID` | full issue + description | `--with journals` |
|
||||
| `mred issue create -p PROJ -s SUBJECT` | create; returns new issue | `--desc FILE\|-`, `--tracker`, `--priority`, `--category`, `--version`, `--due DATE`, `--parent ID`, `--est HOURS`, `--note TEXT` |
|
||||
| `mred issue update ID` | partial update (only set fields sent) | `--status`, `--priority`, `--category`, `--version`, `--due`, `--done-ratio`, `--desc FILE\|-`, `--note TEXT` (= journal) |
|
||||
| `mred version list -p PROJ` / `version create -p PROJ -n NAME` | milestones | `--due DATE`, `--status open\|closed` (sharing=descendants) |
|
||||
| `mred category list -p PROJ` / `category create -p PROJ -n NAME` | categories | |
|
||||
| `mred relation create FROM TO --type blocks\|relates` | issue relations | |
|
||||
|
||||
Names resolve case-insensitively against the server's own enumerations
|
||||
(`--tracker feature` -> id 2, `--priority immediate` -> id 5,
|
||||
`--status done`, `--category`/`--version` by name; no id lookups in
|
||||
PMO scripts ever again). Flags parse before OR after positionals.
|
||||
Exit codes: 0 ok, 1 usage/config, 2 API error — stderr is one
|
||||
parseable line: `mred: redmine: not found: http 404`.
|
||||
|
||||
## Test results
|
||||
|
||||
- Unit suite: **62 passing tests** (34 functions + 28 table/subtests)
|
||||
across library round-trips, list filters, notes-as-journals,
|
||||
relations, versions, categories, name resolution, error mapping
|
||||
(401/403/404/422/5xx -> sentinels; unreachable; malformed),
|
||||
config 0600 discipline.
|
||||
- Key redaction: the fake DELIBERATELY echoes the presented API key in
|
||||
every error body; `TestKeyNeverLeaks` (library) + the smoke redaction
|
||||
pass (CLI) prove the key appears nowhere except the
|
||||
`X-Redmine-API-Key` header.
|
||||
- End-to-end smoke: **smoke: OK** — builds in the builder, boots the
|
||||
fake in a container on 127.0.0.1:8601, drives the real binary through
|
||||
a 0600 env file: version/category create+list, issue create with full
|
||||
flags + desc from stdin (`-o json`), list + filters, update with
|
||||
status/done-ratio/note then show-with-journals, relation create,
|
||||
404/401/loose-file failure paths with exact exit codes, redaction
|
||||
sweep. Only the exact container ID is ever removed; no broad pkill.
|
||||
- `./dev.sh check` (build+vet+test) green at HEAD.
|
||||
- **Live tracker verified (read-only)** 2026-08-29: with
|
||||
`~/.creds/redmine.env` key-renamed to a 0600 `--config` file,
|
||||
`mred issue list -p MOPAC --limit 3` returned real issues (522/521/519,
|
||||
text and `-o json`, exit 0) from projects.knownelement.com — zero code
|
||||
change from the fake-tested path. Live WRITES await PMO acceptance
|
||||
(step 3 below).
|
||||
|
||||
## PMO replacement mappings (python ops today -> mred one-liners)
|
||||
|
||||
Setup once (then every row below is one command):
|
||||
|
||||
```sh
|
||||
sudo install -m 755 ~/projects/meta/MOPAC/redmine-go/bin/mred /usr/local/bin/mred
|
||||
install -d -m 700 ~/.config/mred
|
||||
sed -e 's/^REDMINE_URL=/MRED_URL=/' -e 's/^REDMINE_API_KEY=/MRED_KEY=/' \
|
||||
~/.creds/redmine.env > ~/.config/mred/env && chmod 600 ~/.config/mred/env
|
||||
mred issue list -p MOPAC --limit 5 # live verify vs projects.knownelement.com
|
||||
```
|
||||
|
||||
| Python op today (file) | mred one-liner |
|
||||
|---|---|
|
||||
| Create roadmap version: `POST /projects/MOPAC/versions.json {"name","due_date","status","sharing":"descendants"}` (roadmap-buildout.py §1) | `mred version create -p MOPAC -n "Beta" --due 2026-08-31 --status open` |
|
||||
| List versions to map name->id (roadmap-buildout.py §1 fallback) | `mred version list -p MOPAC -o json` (name->id lookup no longer needed; `--version` takes the NAME) |
|
||||
| Create category (roadmap-buildout.py §2) | `mred category create -p MOPAC -n "Quota & Backpressure"` |
|
||||
| List categories (roadmap-buildout.py §2 fallback) | `mred category list -p MOPAC -o json` |
|
||||
| Update issue category+version+estimate: `PUT /issues/490.json {"category_id","fixed_version_id","estimated_hours"}` (roadmap-buildout.py §3) | `mred issue update 490 --category "Quota & Backpressure" --version Beta --est 8` |
|
||||
| Relation: `POST /issues/490/relations.json {"issue_to_id":492,"relation_type":"blocks"}` (roadmap-buildout.py §4) | `mred relation create 490 492 --type blocks` |
|
||||
| Create child issue with tracker/priority/category/version/est/desc (roadmap-buildout.py §5) | `mred issue create -p MOPAC -s "Google suite MCP/CLI" --tracker task --priority normal --category Integrations --version "Phase 3 - Integrations" --est 8 --parent 498 --desc -` (desc via heredoc/file) |
|
||||
| Fetch issue 517, rewrite description at marker, PUT desc + note (amend-517.py) | `mred issue show 517 -o json \| jq -r '.issue.description' \| { editor pipeline } > /tmp/d.md && mred issue update 517 --desc /tmp/d.md --note "Hard requirement added: ..."` |
|
||||
| Append-desc + note to 497 (finalize-497.py §1) | `mred issue show 497 -o json \| jq -r '.issue.description' \| head ... > /tmp/d.md && mred issue update 497 --desc /tmp/d.md --note "FINAL model: ..."` |
|
||||
| Cross-issue note (finalize-497.py §2, note on 517) | `mred issue update 517 --note "Clarification per #497 final: ..."` |
|
||||
| Read category/version maps then create identity ticket (file-identity-ticket.py) | `mred issue create -p MOPAC -s "Two-level identity model: Linux account (runtime) + Cloudron accounts (acting)" --tracker feature --priority urgent --category Infrastructure --version Production --est 5 --desc identity-desc.md` |
|
||||
| Generic list/read the PMO does inline (status sweeps) | `mred issue list -p MOPAC --status all -o json` / `mred issue show ID --with journals -o json` |
|
||||
|
||||
Notes for the cutover:
|
||||
- `--note` is idempotent-safe to skip: omit it and no journal entry is
|
||||
written. Notes are one-per-call (Redmine journals are append-only).
|
||||
- Text output is stable/grep-able (`ID STATUS SUBJECT @Version`);
|
||||
`-o json` is the machine contract (`{"issues":[...]}` etc.).
|
||||
- No python process, no urllib timeout handling, no key material in
|
||||
scripts: the key sits in a 0600 env file mred refuses to read looser.
|
||||
|
||||
## Acceptance checklist (PMO)
|
||||
|
||||
1. Run the setup block above; `mred issue list -p MOPAC --limit 5`
|
||||
returns live issues (exit 0). (Pre-verified 2026-08-29: this exact
|
||||
command returned 522/521/519 from the live tracker.)
|
||||
2. Spot-map one recurring op (e.g. the finalize-497 note pattern) with
|
||||
`mred issue update <id> --note "cutover test"` and verify the journal
|
||||
in the web UI.
|
||||
3. On pass: python glue in ~/.coordinate/scripts is retired for Redmine
|
||||
writes; comment/close #506 via `mred issue update 506 --status done
|
||||
--done-ratio 100 --note "Delivered: ukrrs/mopac-redmine-go v0"`.
|
||||
|
||||
Zero downtime: python glue keeps working until step 3 — mred adds no
|
||||
server-side change; both clients can run side by side indefinitely.
|
||||
Reference in New Issue
Block a user