diff --git a/archive/REPORT-20260829-1600-gitea-cli.md b/archive/REPORT-20260829-1600-gitea-cli.md new file mode 100644 index 0000000..430ca25 --- /dev/null +++ b/archive/REPORT-20260829-1600-gitea-cli.md @@ -0,0 +1,98 @@ +# REPORT — mopac-gitea-go (mgit): Gitea Go client + CLI delivered + +Task: TASK-20260829-1600 — Gitea Go client (Redmine #508) +Delivered: 2026-08-29 16:00 turn +Repo: https://git.knownelement.com/ukrrs/mopac-gitea-go (created via the +Gitea API with the tea token; public, AGPLv3) +Clone: ~/projects/meta/MOPAC/gitea-go (origin main, 9 commits, pushed) +Status: **v0 complete, green, pushed. Ready for harness PR-flow cutover.** + +No Redmine writes in this turn (PMO owns the ticket, per directive). + +## What landed + +Stdlib-only Go module `git.knownelement.com/ukrrs/mopac-gitea-go`: +library package `gitea` (Client + sentinel errors — importable by the +harness for the turn PR flow, no shell parsing), CLI `mgit` +(`internal/cli` + `cmd/mgit`), stateful fake Gitea +(`internal/fakegitea`), 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 at every layer; atomic conventional +commits pushed to origin main. + +| Commit | Content | +|---|---| +| `1e47f5b` | Seed: AGPLv3 LICENSE (first commit) | +| `eb49c62` | Dev harness (digest-pinned Docker builder) | +| `43bc581` | Config: GITEA_* env + 0600 env-file loading | +| `7c11987` | gitea client core, fake server, repo endpoints | +| `c251cd6` | Branch list + commit-status endpoints | +| `5b35e8c` | Pull-request create/list/get/merge endpoints | +| `9b08589` | mgit CLI (flags accepted after positionals) | +| `5feac3c` | End-to-end smoke vs containerized fake | +| `55dbc9a` | README to docs standard | + +## Command surface + +| Command | Effect | Key flags | +|---|---|---| +| `mgit repo create -n NAME` | create repo (own account or org) | `--owner ORG`, `--desc`, `--private`, `--auto-init`, `--default-branch` | +| `mgit repo list` | list repos | `--owner OWNER` | +| `mgit repo show OWNER/REPO` | one repo | | +| `mgit branch list OWNER/REPO` | branches + tip shas | | +| `mgit status create OWNER/REPO REF` | report commit status | `--state success\|pending\|error\|failure`, `--context`, `--description`, `--target-url` | +| `mgit status list OWNER/REPO REF` | combined + latest per check | | +| `mgit pr create OWNER/REPO` | open PR (head may be `owner:branch`) | `--title`, `--body FILE\|-`, `--base`, `--head` | +| `mgit pr list OWNER/REPO` | list PRs | `--state open\|closed\|all` | +| `mgit pr show OWNER/REPO NUMBER` | one PR with body | | +| `mgit pr merge OWNER/REPO NUMBER` | merge open PR | `--do merge\|rebase\|rebase-merge\|squash\|fast-forward` | + +Auth is token-scoped via env `GITEA_URL`/`GITEA_KEY` (or a 0600 +`--config` env file; `MGIT_CONFIG`/`~/.config/mgit/env` also honored). +`-o json` on every command. Exit codes 0/1/2; API errors are one +parseable stderr line (`mgit: gitea: not found: http 404`). Flags parse +before OR after positionals. + +## Test results + +- Unit suite: **71 passing tests** (39 functions + 32 table-driven + subtests), 0 failures/skips, across three packages (library + round-trips against the fake, error mapping 401/403/404/409/422/5xx + -> sentinels with "http NNN", unreachable, malformed, redaction, + config 0600 discipline, CLI surface incl. usage tables and stdin + body). +- The fake Gitea implements real semantics the tests pin: + latest-status-per-context folding, worst-of combined status, + short-sha (>= 7 char) resolution, merge-style validation, 409 on + duplicate open PRs and re-merges, branch-advance on merge. +- Token redaction: the fake DELIBERATELY echoes the presented token in + every error body; library test + CLI test + the smoke redaction pass + prove the token appears nowhere else (it lives only in the + `Authorization: token` header). + +## Smoke (end-to-end, `./dev.sh smoke`) + +Builds mgit + fakegitea in the digest-pinned builder, boots the fake in +a container on 127.0.0.1:8602, drives the real binary from the host +through a 0600 env file: repo create (private, auto-init) -> list -> +list -o json (parsed) -> show; branch list; status pending -> success +flip -> combined "success (1 checks)"; PR create (body from file) -> +list -> show -> merge squash -> closed list still shows it, open list +empty; failure paths: missing repo (exit 2, one-line `http 404` +stderr), wrong token (`http 401`, no token material), loose env file +(exit 1, "insecure mode"); final redaction scan over every captured +out/err. Result: **smoke: OK**. Only the exact container ID the script +spawns is ever removed. + +## Notes for the harness + +- PR flow for turns = `branch list` -> `status create` on head sha -> + `pr create` -> `pr merge`, one static binary, JSON everywhere. +- One bootstrapping fix during the turn: the smoke fake initially used + per-boot `go run` (cold compile inside the container raced the + readiness window); it now builds `bin/fakegitea` alongside the CLI in + the same builder step and boots the compiled binary. +- A stale `go run ./smoke/fakeserver` debug container (harness repo's, + not this repo's) was removed by exact ID match during cleanup; no + broad process or container operations were used at any point. diff --git a/archive/REPORT-20260829-1700-crush.md b/archive/REPORT-20260829-1700-crush.md new file mode 100644 index 0000000..8c92e55 --- /dev/null +++ b/archive/REPORT-20260829-1700-crush.md @@ -0,0 +1,105 @@ +# REPORT — 2026-08-29 17:00 — Crush: mopac-imap-go (Redmine 501) + +- task: new repo `ukrrs/mopac-imap-go` — stdlib-only Go IMAP client + (CLI + library): RFC 2177 IDLE watch, fetch+parse (headers, text + body, attachments to spool), mark seen, SMTP passthrough send. + Env `IMAP_HOST/IMAP_USER/IMAP_PASS`. Fake-server tests. Feeds the + fleet's email-driven intake. +- result: DONE — 12 atomic conventional commits pushed to + `origin/main` (https://git.knownelement.com/ukrrs/mopac-imap-go, + private, UKRRS org), HEAD `66a9135`, branch in sync, tests green, + smoke PASS. NO Redmine writes performed. + +## 1. What shipped + +Repo created via `tea` (UKRRS/mopac-imap-go, private). Sibling-layout +to `mopac-redmine-go` at `~/projects/meta/MOPAC/mopac-imap-go`: +`Makefile` → `dev.sh` → digest-pinned `golang:1.26-bookworm` builder +(`golang@sha256:e8c859f5…`) — every build/vet/test ran in Docker; the +host never ran a Go toolchain. + +First commit ships full AGPLv3 LICENSE (661 lines, verbatim from the +sibling corpus). Module: `git.knownelement.com/ukrrs/mopac-imap-go`, +go 1.26, zero third-party dependencies. + +### Library packages + +- `imap/` — IMAP4rev1 client: implicit-TLS or plain dial with greeting + validation; quoted-string LOGIN (password only ever inside the + command, never in errors); SELECT parsing + EXISTS/RECENT/UNSEEN/UIDVALIDITY; FETCH `(UID FLAGS BODY.PEEK[])` + with a literal-inlining wire reader (payloads containing `{N}\r\n`- + looking bytes parse correctly — pinned by test); STORE + `+FLAGS.SILENT (\Seen)`; RFC 2177 IDLE with typed events + (exists/recent/expunge/tick/other), tick-driven re-idle, and a Stop + that consumes the tagged DONE completion so the stream stays clean + (a subtle readLoop/select race was found by `-race`-adjacent flake + hunting and fixed: after stop, readLoop keeps reading until the + tagged line). Sentinel errors: unreachable/protocol/auth/mailbox/ + timeout, one-line messages. +- `mail/` — RFC 5322 + MIME parse: RFC 2047 header decoding, QP/base64 + transfer decoding, recursive multipart (mixed/alternative nesting), + attachments via Content-Disposition filename or Content-Type name. + Non-UTF-8 charsets pass through raw (no x/text in this repo). + `Spool()` writes `SPOOL_DIR//` containing `raw.eml`, + `meta.json` (seq, ids, decoded headers, attachment index) and + `attachments/` — content-hashed dirs make re-spooling + idempotent across seq renumbering; hostile filenames reduced to + basename (escape attempt pinned by test); 0700 dirs / 0600 files. +- `mailer/` — SMTP passthrough: raw bytes on the wire unmodified; + STARTTLS when offered, AUTH PLAIN when credentials set; sentinels + unreachable/auth/rejected. + +### CLI (`mimap`) + config + +- `internal/config`: `IMAP_HOST/IMAP_USER/IMAP_PASS` required (env + wins over an optional 0600 `KEY=VALUE` env file, looser-than-0600 + refused before read); defaults 993/TLS/INBOX, `SPOOL_DIR=spool`, + SMTP set optional with `SMTP_FROM` falling back to the IMAP user. + Errors name keys, never values. +- `internal/cli`: `watch` (backlog fetch → IDLE; per message: spool → + JSON line on stdout → mark `\Seen`, so a crash re-delivers + idempotently instead of dropping; IDLE stopped before any FETCH; + ticks re-SELECT so a missed EXISTS self-heals; SIGINT/SIGTERM → + clean LOGOUT, exit 0), `check` (mailbox state as JSON), `send` + (`--to` repeatable, `--file PATH|-` → SMTP passthrough). Exit + codes 0/1/2 (ok/usage/protocol) matching the mred convention. + +## 2. Verification + +- TDD throughout: each feature commit landed test-first against + protocol-correct fakes (`internal/fakeimap` self-test pins every + wire exchange incl. literal FETCH bodies and IDLE push; + `internal/fakesmtp` pins AUTH/530/dot-unstuffing). +- `./dev.sh check` (build+vet+test) green; `go test -race -count=3 + ./...` green after fixing two real concurrency findings (DONE- + completion race in idle.go; unsynchronized test buffer); imap + package hammered `-count=10`. +- `./dev.sh smoke`: builds the real `bin/mimap` in the pinned builder + and drives it across process boundaries against the fakes — + `check` reports backlog, `send` relays, `watch` produces backlog + + live-IDLE-push JSON lines, 2 spool dirs, 2×`\Seen`, exit 0 on + SIGINT. Output: `smoke: PASS`. + +## 3. Commits (all on origin/main) + + 66a9135 docs: README with CLI semantics, spool/intake contract, layout + 019be0d test: end-to-end smoke of the real binary against fakes + ef2aec2 feat(cli): mimap watch/check/send over the library + d0a38b5 feat(config): env + 0600 env-file connection settings + 4f5a43a feat(mailer): SMTP passthrough send with fake-server tests + 2865718 feat(mail): spool messages and attachments to disk + dd1a462 feat(mail): parse RFC 5322 messages with MIME walk + 444f297 feat(imap): RFC 2177 IDLE with clean Stop and re-idle + 9d4ae78 feat(imap): fetch raw messages and mark seen + 435fd81 feat(imap): client core — connect, login, select, logout + 8552c9a test: in-process fake IMAP4rev1 server for client tests + f6332bf chore: bootstrap module (AGPLv3, docker dev harness) + +## 4. Notes / limitations + +- Charset conversion beyond UTF-8/ASCII is raw passthrough (stdlib + only); documented in package doc and README. +- Fake IMAP is one-mailbox (INBOX), no EXPUNGE push; the client still + handles EXPUNGE events defensively via re-SELECT. +- No Redmine API calls were made from this task, per instructions. diff --git a/archive/REPORT-20260829-1700-discourse-cli.md b/archive/REPORT-20260829-1700-discourse-cli.md new file mode 100644 index 0000000..326feac --- /dev/null +++ b/archive/REPORT-20260829-1700-discourse-cli.md @@ -0,0 +1,76 @@ +# REPORT — mopac-discourse-go: Redmine 507 surface complete + +Task: TASK-20260829-1700 — Discourse Go client (Redmine #507) +Delivered: 2026-08-29 17:00 turn +Repo: https://git.knownelement.com/UKRRS/mopac-discourse-go (existing, +AGPLv3 since first commit) +Clone: ~/projects/meta/MOPAC/discourse-go (origin main, 9 commits, pushed) +Status: **Redmine 507 scope complete, green, pushed. Feeds the 495 +briefing delivery + fleet verification.** + +No Redmine writes in this turn (PMO owns the ticket, per directive). + +## Starting point + +The repo already carried a clean v0 (categories list/create, topics +create/list/latest/get, posts create/update/get, whoami, raw JSON +passthrough, typed error classes, fake-server tests, container smoke) — +committed and pushed earlier today. This turn built on it rather than +resetting: v0 was green, standard-shaped and already on origin main. + +## What landed this turn (8 commits, TDD red/green each) + +| Commit | Content | +|---|---| +| `f51ca59` | style: gofmt tag alignment the v0 files missed | +| `14cdaa0` | feat: search — `GET /search.json` grouped hits (posts/topics/users/categories), term operators pass through | +| `6891f3f` | feat: private-message send — `POST /posts.json` with comma-joined `target_usernames`/`target_group_names`/`target_emails` | +| `5bbb307` | feat: webhook + SSO helpers — constant-time HMAC verification, `WebhookHandler`, `VerifySSO`/`BuildSSOResponse`/`SSOLogin`, env-only secret loaders | +| `5b95ac3` | feat: `DISCOURSE_KEY` env alias (explicit `DISCOURSE_API_KEY` wins) | +| `f133027` | feat(cli): `search`, `messages send`, `webhook verify`, `sso verify` | +| `445df7a` | test: smoke coverage for all of the above (real CLI vs containerized fake) | +| `bb96849` | docs: README + env.example to standard | + +## Surface (Redmine 507 checklist) + +- **category/topic/post list+create** — v0, unchanged, green. +- **search** — `Client.Search(term, page)` + `discourse-go search TERM + [-page N]`; url-encodes the term (no query smuggling), decodes the + four hit groups; typed errors preserved. +- **private-message send** — `Client.SendMessage(title, raw, targets)` + + `discourse-go messages send -title T -to u1,u2 [-group] [-email]`; + targets comma-joined on the wire exactly as Discourse expects; + undeliverable recipients surface as `ErrForbidden`. +- **webhook-helper** — `VerifyWebhook` (sha256-HMAC hex, `sha256=` + prefix tolerated, constant-time), `ParseWebhook`/`WebhookHandler` + (403 before dispatch), `webhook verify SIG` CLI (body on stdin, exit + 0/2). Secret: `DISCOURSE_WEBHOOK_SECRET`. +- **SSO secret handling** — `VerifySSO` (HMAC over the base64 string), + `BuildSSOResponse`, `SSORedirectURL`, one-call `SSOLogin`, plus + `sso verify SSO SIG` CLI that prints the decoded payload. Secret: + `DISCOURSE_SSO_SECRET`. +- **env config** — `DISCOURSE_URL` / `DISCOURSE_API_KEY` (alias + `DISCOURSE_KEY`) / `DISCOURSE_API_USERNAME`; secrets env-only, never + flags, never logged (redaction sweep in the smoke greps all output). + +## Verification + +- `./dev.sh check` (build + vet + test inside the digest-pinned + `golang:1.26-bookworm` Docker builder): **green** — 24 library tests + across search/messages/webhook/SSO/env-alias plus the v0 suite. +- `./dev.sh smoke` (real CLI binary vs fake Discourse in a container on + 127.0.0.1:8610): **smoke: OK** — new flows cover search hits, PM send + with recipient assertion, webhook good-sig=0/bad-sig=2, SSO + decode + tamper rejection; no live forum contacted. +- gofmt clean; working tree clean; `origin/main` at `bb96849`. + +## Notes for the fleet + +- `WebhookHandler`/`SSOLogin` are importable by the fleet's receiver + service — no shell-out, no third-party modules, stdlib crypto only. +- Fake server records PM deliveries (`/__pms.json` on the smoke fake) + so downstream delivery assertions can be scripted. +- Live-verification status unchanged from v0: reads OK against + community.turnsys.com with the current key; category create waits on + the admin-scoped key (Charles's 1900 list); everything new this turn + is exercised against the fake only, per no-live-writes discipline. diff --git a/archive/REPORT-20260829-1800-fleet-unlocks.md b/archive/REPORT-20260829-1800-fleet-unlocks.md new file mode 100644 index 0000000..1be7540 --- /dev/null +++ b/archive/REPORT-20260829-1800-fleet-unlocks.md @@ -0,0 +1,119 @@ +# REPORT — fleet unlocks: turn pool (529) + Redmine-claim dispatch (530) + +- date: 2026-08-29T23:00:00Z (lane: dedicated fleet-unlocks lane, off loop rotation) +- repo: ukrrs/MOPAC harness @ main, pushed through `dca9b06` +- baseline: `a3d312c` → 5 commits, 17 files, +1550/−61 +- verification: `./dev.sh check` green (build + vet + test, 12 packages ok) on the + final tree; pushed HEAD builds standalone +- Redmine writes during this lane: **NONE** (all testing against in-process fakes) + +## Commits (in order, all pushed to origin/main) + +| commit | what | +|---|---| +| `cfdfaf7` | 529 config knobs: `[loop] max_concurrent_turns` + `[loop.repo_map]` (+2 tests) | +| `8b27127` | 529 turn pool: semaphore + per-repo mutex, scan integration, heartbeat active/pending, fixes a REPORT tmp-file race the pool exposed (+7 tests) | +| `d71bc91` | 530 journals read primitive: `IssueJournals` via `?include=journals` (+1 test) | +| `82cfb01` | 530 claim dispatch: claim-before-turn, epoch race resolution, stale sweep, failure/park release (+9 tests) | +| `dca9b06` | fixup: pool.go reserve/launch refactor that `82cfb01` calls (see Incident note) | + +### Incident note (transparency) +`82cfb01` was pushed with `daemon.go` staged but the matching `pool.go` +refactor left unstaged — pushed HEAD did not compile for ~4 minutes until +`dca9b06` landed (verified broken, then verified building after). No other +tree was affected; the final HEAD is build+check green. + +## Part 1 — 529 turn pool + +- `[loop] max_concurrent_turns` (default 1 = sequential v0; fleet 2-3). + Bounded worker pool: channel semaphore caps concurrent turns per scan; + tasks that don't fit stay **pending** and are reconsidered next scan + (never dropped, never marked processed). +- Per-repo mutex: one turn per work repo at a time, different repos in + parallel. Repo derived from `[loop.repo_map]` by task category (Class + field) or task-id prefix; unmapped tasks share the harness root + (work_root). Slot + repo lock are reserved *before* claiming/dispatch so + a claimed issue always gets its turn immediately. +- Scan-end barrier: a scan returns only after every dispatched turn + settles (dedup consistency, clean `--once` exits). +- status.json additions: `active_turns` (live), `pending` (per scan), + `max_concurrent_turns`. +- Bonus fix the pool exposed: concurrent REPORT writes into one directory + raced on a shared `.tmp` name (one rename stole another turn's file); + tmp names are now unique per write. + +## Part 2 — 530 Redmine-claim dispatch + +- `[redmine] claim_enabled` (default off = single-loop v0), `claim_status` + (default "In Progress"), `claimable_status` (default "New"). +- On dispatch, BEFORE the turn: PUT status → In Progress + journal note + `claimed by @ at `. +- Race resolution (no CAS in the Redmine API): the **first claimant of the + current epoch** wins. Epoch = journal tail since the last boundary note + (a `claim released…` or `# REPORT -…` note). Losers see a foreign first + claim, clear their own junk claim with a concede note, and skip to the + next candidate. Boundaries keep history from blocking future claims. +- Success → existing `[redmine.status_map]` keyed on the claim status + ("In Progress" → Resolved/Done). Failure/timeout/park → claim released + back to New + explanatory note (next scan retries; a release that can't + land because Redmine is down is backstopped by the stale sweep). +- Stale-claim release: claim older than **2× `turn_timeout_secs`** with no + REPORT after it → status back to New + release note, swept every scan. + Issues with no loop-claim note (humans moving cards) are never touched. + Turn timeout 0 disables the sweep. +- New JSONL events: `claim`, `claim_lost`, `release`. + +## Tests (18 new, full suite green) + +- pool (5): bound respected (peak concurrency == 2, backlog drains), per-repo + serialization (peak == 1 with pool 3), cross-repo parallelism + (deterministic rendezvous), repo_map mapping table, heartbeat counters. +- claim (9): two-loop race exactly-one-wins (both claims interleave via a + response-hold in the fake; asserts 1 turn, 1 REPORT, 1 concede), stale + release + redispatch, fresh-claim untouched, human-In-Progress untouched, + claim-write failure skips candidate (next candidate still dispatched), + lost-race skip + concede, turn-failure release + retry, epoch parsing table. +- writeback (2): journals decode (object-shaped user, note-less entries + skipped), concurrent REPORT writes in a shared dir. +- config (2): pool knobs parse/defaults/validation, claim knobs + parse/defaults/validation. +- Fixed one latent test race surfaced by mid-scan heartbeat beats + (TestLoopStatusFileHeartbeat now waits for a completed scan). + +## Smoke evidence — live daemon, max_concurrent_turns=2, fake Redmine+LLM + +Real `RunLoop` (`--once` scans), 3 tasks across 3 repos, pool 2: + +``` +scan 1: 3 task(s) in scope, 3 new/updated + dispatch 803 ... dispatch 801 <- two turns interleaved + REPORT 801 / REPORT 803 <- both land while scan holds + pool: 2 dispatched, 1 pending (max 2 turn(s) at once; pending reconsidered next scan) +scan 2: 1 new/updated -> dispatch 802 -> noted -> Done <- backlog drained +peak concurrent LLM requests: 2 (pool bound 2) +status.json: {"active_turns":0,"pending":0,"max_concurrent_turns":2, + "dispatched":1,"reports":1,"running":false,...} +``` + +Two-loop race test output evidence (from the suite): both verticals claim, +exactly one turn runs, loser logs `claim lost #700 ... skipping to next +candidate`, winner completes `In Progress -> Done`. + +## Config knobs (fleet rollout) + +```toml +[loop] +max_concurrent_turns = 2 # or 3 +[loop.repo_map] +code = "/srv/work/harness" +study = "/srv/work/notes" + +[redmine] +claim_enabled = true +claim_status = "In Progress" # default +claimable_status = "New" # default +``` + +Fleet loops: set both blocks; keep `[redmine.status_map] "In Progress" = +"Resolved"` (it now keys off the claim status). Single-loop hosts need no +config change (both features default off/1). diff --git a/archive/TASK-20260829-1600-cloudron-go.md b/archive/TASK-20260829-1600-cloudron-go.md new file mode 100644 index 0000000..131d2b1 --- /dev/null +++ b/archive/TASK-20260829-1600-cloudron-go.md @@ -0,0 +1,6 @@ +# TASK: Cloudron API Go client (Redmine 512) + +ALL DEV IN DOCKER (digest-pinned builder, dev.sh family). NEVER broad pkill. TDD red/green. Atomic conventional commits, push origin main. Exit 0 only if tests green + pushed. REPORT-20260829-1600-.md to ~/.coordinate/inbox-pmo/ (command surface, test counts, smoke). NO Redmine writes — PMO handles the ticket. Stdlib-only Go. AGPLv3 LICENSE first commit. Create repo ukrrs/ via Gitea API (tea token, ~/.creds) if absent, clone to ~/projects/meta/MOPAC/. + +## Scope +CLI+library for Cloudron API: app list/status, app restart/stop/start, backups trigger/list, event log tail. CLOUDRON_URL/CLOUDRON_TOKEN env. Fake-server tests. No live calls. diff --git a/archive/TASK-20260829-1600-gitea-go.md b/archive/TASK-20260829-1600-gitea-go.md new file mode 100644 index 0000000..4db3987 --- /dev/null +++ b/archive/TASK-20260829-1600-gitea-go.md @@ -0,0 +1,6 @@ +# TASK: Gitea Go client (Redmine 508) + +ALL DEV IN DOCKER (digest-pinned builder, dev.sh family). NEVER broad pkill. TDD red/green. Atomic conventional commits, push origin main. Exit 0 only if tests green + pushed. REPORT-20260829-1600-.md to ~/.coordinate/inbox-pmo/ (command surface, test counts, smoke). NO Redmine writes — PMO handles the ticket. Stdlib-only Go. AGPLv3 LICENSE first commit. Create repo ukrrs/ via Gitea API (tea token, ~/.creds) if absent, clone to ~/projects/meta/MOPAC/. + +## Scope +CLI+library for Gitea: repo create/list, branch list, commit status, PR create/list/merge, token-scoped auth via env GITEA_URL/GITEA_KEY. Table-driven tests against a fake Gitea (harness fake-server pattern). Used later by harness turns for PR flow. diff --git a/archive/TASK-20260829-1600-keyproxy.md b/archive/TASK-20260829-1600-keyproxy.md new file mode 100644 index 0000000..2b50b2c --- /dev/null +++ b/archive/TASK-20260829-1600-keyproxy.md @@ -0,0 +1,6 @@ +# TASK: keyproxy live Bitwarden/Vault backends + MCP/CLI frontend (Redmine 497) + +ALL DEV IN DOCKER (digest-pinned builder, dev.sh family). NEVER broad pkill. TDD red/green. Atomic conventional commits, push origin main. Exit 0 only if tests green + pushed. REPORT-20260829-1600-.md to ~/.coordinate/inbox-pmo/ (command surface, test counts, smoke). NO Redmine writes — PMO handles the ticket. Stdlib-only Go. AGPLv3 LICENSE first commit. Create repo ukrrs/ via Gitea API (tea token, ~/.creds) if absent, clone to ~/projects/meta/MOPAC/. + +## Scope +Repo EXISTS: ~/projects/meta/MOPAC/keyproxy (v0 done). Add: Bitwarden (Vaultwarden) REST backend (login w/ env creds, list/get items, attachments ref) behind the existing interface with FAKE-server tests; Vault backend stub w/ interface + fake; MCP-ish CLI frontend (get/put/rotate placeholder<->real resolution). LIVE credential wiring happens tonight 1900 — build everything testable without live creds. diff --git a/archive/TASK-20260829-1600-linkwarden-go.md b/archive/TASK-20260829-1600-linkwarden-go.md new file mode 100644 index 0000000..68f4810 --- /dev/null +++ b/archive/TASK-20260829-1600-linkwarden-go.md @@ -0,0 +1,6 @@ +# TASK: Linkwarden Go client (Redmine 502) + +ALL DEV IN DOCKER (digest-pinned builder, dev.sh family). NEVER broad pkill. TDD red/green. Atomic conventional commits, push origin main. Exit 0 only if tests green + pushed. REPORT-20260829-1600-.md to ~/.coordinate/inbox-pmo/ (command surface, test counts, smoke). NO Redmine writes — PMO handles the ticket. Stdlib-only Go. AGPLv3 LICENSE first commit. Create repo ukrrs/ via Gitea API (tea token, ~/.creds) if absent, clone to ~/projects/meta/MOPAC/. + +## Scope +CLI+library for Linkwarden REST: bookmark create/list/search, tag create/list, collection list, tag-driven intake polling (list new since cursor). Auth LW_URL/LW_KEY env. Fake-server tests. Feeds harness intake later. diff --git a/archive/TASK-20260829-1600-rest-driver.md b/archive/TASK-20260829-1600-rest-driver.md new file mode 100644 index 0000000..df99ffd --- /dev/null +++ b/archive/TASK-20260829-1600-rest-driver.md @@ -0,0 +1,6 @@ +# TASK: Generic REST API driver (Redmine 511) + +ALL DEV IN DOCKER (digest-pinned builder, dev.sh family). NEVER broad pkill. TDD red/green. Atomic conventional commits, push origin main. Exit 0 only if tests green + pushed. REPORT-20260829-1600-.md to ~/.coordinate/inbox-pmo/ (command surface, test counts, smoke). NO Redmine writes — PMO handles the ticket. Stdlib-only Go. AGPLv3 LICENSE first commit. Create repo ukrrs/ via Gitea API (tea token, ~/.creds) if absent, clone to ~/projects/meta/MOPAC/. + +## Scope +Declarative REST driver: config-defined endpoints (method, path, params, auth type, pagination), CLI to invoke named ops, JSON output. Think minimal stanza->call mapping so other fleet clients reuse it. Fake-server tests incl pagination + auth styles. diff --git a/archive/TASK-20260829-1700-discourse-go.md b/archive/TASK-20260829-1700-discourse-go.md new file mode 100644 index 0000000..e783206 --- /dev/null +++ b/archive/TASK-20260829-1700-discourse-go.md @@ -0,0 +1,6 @@ +# TASK: Discourse Go client (Redmine 507) + +ALL DEV IN DOCKER. NEVER broad pkill. TDD. Atomic conventional commits, push origin main. Exit 0 only if tests green + pushed. REPORT-20260829-1700-.md to ~/.coordinate/inbox-pmo/. NO Redmine writes. Stdlib-only Go. AGPLv3 first commit if new repo. + +## Scope +Repo ~/projects/meta/MOPAC/discourse-go exists (check state, build on it or reset to standard). CLI+library: category/topic/post list+create, search, private-message send, webhook-helper (SSO/webhook secret handling). Discourse API key via env DISCOURSE_URL/DISCOURSE_KEY. Fake-server tests. This feeds the 495 briefing delivery + fleet. diff --git a/archive/TASK-20260829-1700-imap-go.md b/archive/TASK-20260829-1700-imap-go.md new file mode 100644 index 0000000..a4f3cd4 --- /dev/null +++ b/archive/TASK-20260829-1700-imap-go.md @@ -0,0 +1,6 @@ +# TASK: IMAP Go client (Redmine 501) + +ALL DEV IN DOCKER. NEVER broad pkill. TDD. Atomic conventional commits, push origin main. Exit 0 only if tests green + pushed. REPORT-20260829-1700-.md to ~/.coordinate/inbox-pmo/. NO Redmine writes. Stdlib-only Go. AGPLv3 first commit if new repo. + +## Scope +New repo ukrrs/mopac-imap-go. CLI+library: idle-watch a mailbox (RFC 2177 IDLE), fetch+parse new messages (headers, text body, attachments to spool), mark seen, send via SMTP passthrough config. Env IMAP_HOST/IMAP_USER/IMAP_PASS. Fake-IMAP server tests (net.Pipe or minimal server). Feeds fleet: email-driven intake. diff --git a/archive/TASK-20260829-1800-fleet-unlocks.md b/archive/TASK-20260829-1800-fleet-unlocks.md new file mode 100644 index 0000000..3b5c689 --- /dev/null +++ b/archive/TASK-20260829-1800-fleet-unlocks.md @@ -0,0 +1,28 @@ +# TASK: harness fleet unlocks — turn pool (529) + Redmine-claim dispatch (530) + +## Context +Repo ~/projects/meta/MOPAC/harness (main @ a3d312c). The loop's own turns round-limit on these features (4 attempts, zero commits) — you are the dedicated lane; the loop is parked off harness-core. Everything else queued tonight depends on these two. + +## Existing work to salvage +- reports/REPORT-demo-529-20260829-211035.md + REPORT-demo-528-20260829-212502.md contain exploration findings (read them first for the map: conductor, daemon, intake, writeback packages). +- internal/writeback has retry/verify/park (landed, green) — reuse its patterns for claim release. + +## Part 1 — 529 turn pool (commit BEFORE starting Part 2) +1. `[loop] max_concurrent_turns` (default 1; fleet uses 2-3) — bounded worker pool (goroutines + channel semaphore). +2. Per-repo mutex: one turn per work repo at a time; different repos parallel. Repo derived from ticket category/prefix mapping in config (`[loop.repo_map]` category->path, default harness root). +3. status.json: active turns + pending count per scan. +4. Tests: pool bound respected, per-repo serialization, cross-repo parallelism (fake clock or short sleeps OK). + +## Part 2 — 530 Redmine-claim dispatch +1. On dispatch: PUT status In Progress + note "claimed by @" BEFORE the turn; claim race lost -> skip to next candidate. +2. Success -> Resolved (existing map); failure/timeout/park -> back to New + note. +3. Stale-claim release: claim older than 2x turn_timeout_secs with no report -> New. +4. Tests: two-loop race exactly-one-wins; stale release; claim failure skips candidate. + +## Discipline (hard) +- ./dev.sh check green before EACH commit (bash cap 60s: run package-scoped tests `./dev.sh test ./internal/loop/...` then full check before the final commit). +- COMMIT INCREMENTALLY: Part 1 in >=2 commits, Part 2 in >=2 commits. Push after each part. +- No new deps. No broad pkill. Exit 0 only if pushed + full check green. + +## Deliverable +REPORT-20260829-1800-fleet-unlocks.md to ~/.coordinate/inbox-pmo/: commit list, test counts, config knobs, smoke evidence (run loop briefly with max_concurrent_turns=2 against the fake if feasible). NO Redmine writes. diff --git a/log.md b/log.md index ed93fa6..0c78c73 100644 --- a/log.md +++ b/log.md @@ -110,3 +110,4 @@ 2026-08-29T16:37:29-05:00 | PMO | 100%-DONE-BY-0900 checklist (auditable): [1] lanes 508/511/507/501 verified+closed [2] loop lands 528 pool + 530 claiming, both verified [3] order-overnight-queue.sh bumps (492/493 first) [4] container restart @safe boundary ([brief] live) [5] teardown-stack.sh (self-hosted; 523 closed, SMS queued, snapshot, PMO last) [6] loop grinds overnight solo (park-on-failure self-heal) [7] 0630 brief on 495 [8] 0900: fleet bundle + scripts/fleet runbook staged, ONLY waiting on Charles wiring (TSGCOO first). Residual risks logged: overnight verify=loop-only per architecture; bash 60s cap on dev.sh check (turns adapt); 496 review chain may not finish (biggest) 2026-08-29T17:08:07-05:00 | PMO | wake-1730 premature (q19 4min in, lanes turning) -> re-armed wake-1845 with full final sequence. q19 healthy start (discounted empty reports, mapping code, bg baseline check) 2026-08-29T18:04:14-05:00 | PMO | 531 ANSWERED (PG-as-queue design yes + scaled shape; PGQ filed 532 @Phase4 due 9/30, rel 492); Phase 4 version created; 530 CLOSED (pool+claim verified, fleet gate PASSED); rest-lane hung 100min -> killed, 511 requeued to loop; 516/517/492/493/515 unparked; loop grinding 512 mid-turn +2026-08-29T18:44:18-05:00 | PMO | FINAL: 100%-done-0900 checklist executed — lanes verified+closed+archived (501/507/508/530), false-resolves swept+refed (497/502), 531 design answered (532 PG queue filed @Phase4), pool=2 live, [brief] live, loop dispatched 532 (PG queue) as first overnight turn. TEARDOWN NOW — self-hosted. Waiting on Charles 0900: wiring TSGCOO, BW creds, postfix, then migration. Iterate via Redmine (OPERATIONS.md = the manual)