152 lines
8.1 KiB
Markdown
152 lines
8.1 KiB
Markdown
# REPORT — Beta finish: 525 / 522 / 495 (2026-08-29 15:30 CST)
|
|
|
|
Repo: `~/projects/meta/MOPAC/harness` (main). All work in the digest-pinned
|
|
Docker builder via `./dev.sh`. `./dev.sh check` (build + vet + test ./...)
|
|
ran GREEN immediately before EVERY commit below. Branch pushed:
|
|
`19d61a0..f9fb729` on `origin/main`. Working tree clean at handoff.
|
|
|
|
Note: no Redmine tickets were written from this session (PMO owns tickets),
|
|
per instructions.
|
|
|
|
## Commit map
|
|
|
|
| Commit | Part | Subject |
|
|
|---|---|---|
|
|
| `e8f50d3` | env chore | deploy: add host docker group to the loop container (uncommitted WIP found in tree; prerequisite for the Docker builders) |
|
|
| `3927b38` | **525** | loop: retry Redmine writebacks with backoff, verify, and park on failure |
|
|
| `5784698` | **522** | notify: native SMS notifier for loop events via stdlib SMTP |
|
|
| `f9fb729` | **495** | brief: wire the 0630 pipeline - journal note, sections, daemon schedule |
|
|
|
|
Net: 24 files changed, +2732 / -112.
|
|
|
|
## 525 — retry/backoff on status/note writes
|
|
|
|
**Compile fix (first):** the loop turn's WIP did not build —
|
|
`retry.go:74` field/method collision (`Backoff`), `redmine.go:186-210`
|
|
`Attempts` used as a value, and the `[redmine] writeback_*` config fields
|
|
did not exist yet. All fixed (method renamed `BackoffFor`; config knobs
|
|
added to `RedmineConfig` + TOML parsing).
|
|
|
|
**Finished behavior**
|
|
- Bounded retries with exponential backoff + ±25% jitter on the note and
|
|
status writes: `[redmine] writeback_retries = 3`, `writeback_backoff_secs
|
|
= 2`, `writeback_max_backoff_secs = 30`. Retryable: 5xx, 429, transport
|
|
(dial/reset/EOF). Never retried: 4xx, caller context cancel. Retried
|
|
PUTs replay an identical body; every retry logs one audit line to the
|
|
loop stdout.
|
|
- **Verification re-read** after the writes: one GET confirms the status
|
|
transition actually landed and advances the dedup marker in the same
|
|
round trip (`IssueState`; supersedes the old refresh-only read).
|
|
- **Park on final failure:** when the outage outlives the bound the task
|
|
PARKS — issue status left exactly as-is (New stays New), one `park`
|
|
event in loop.jsonl pins the dedup marker (no hot-loop), heartbeat
|
|
`status.json` gains `last_writeback_error` + a `parked` counter
|
|
(cleared by the next verified writeback), and the next PMO update of
|
|
the issue re-releases the task. A paid-for REPORT is never silently
|
|
stranded.
|
|
|
|
**Tests (all green, fake-server based)**
|
|
- `internal/writeback` — 14 tests total incl. new: 502-then-success
|
|
(replayed body + audit line asserted), bounded give-up after exactly N
|
|
attempts, 401 one-shot, verify re-read shape, RetryPolicyFrom
|
|
defaults/overrides, BackoffFor jitter band, Retryable classification.
|
|
- `internal/loop` — 502-flap recovery at the daemon level, park path
|
|
(no note, no status move, no hot-loop, PMO update re-dispatches),
|
|
`last_writeback_error`/`parked` surfaced in status.json.
|
|
|
|
## 522 — native SMS notifier
|
|
|
|
**`internal/notify` (rewritten from the WIP sketch) + `[notify]` config**
|
|
- Stdlib-only send (`net/smtp` via `NewClient` + `DialTimeout`, deadline-
|
|
bounded so a black-holed relay cannot stall the sequential loop;
|
|
optional PLAIN AUTH from refs). Recipients = carrier email-to-SMS
|
|
gateway addresses, resolved from one ref.
|
|
- Events wired in the daemon: turn complete, turn failed (incl. parked
|
|
writebacks), quota/resource deferral, scan error, daemon start/stop.
|
|
- Per-kind rate limit with **collapsed repeats**: repeats inside the
|
|
window are counted and ride along on the next send
|
|
("+N suppressed repeat(s)"); no phone spam during a flapping scan.
|
|
- **Offline queue on disk** (one JSON file per event, FIFO by name,
|
|
bounded with oldest-dropped, corrupt-entry tolerant) drained after
|
|
every scan and at daemon stop.
|
|
- **Redaction rules** applied BEFORE queueing (so secrets are never at
|
|
rest either): credential-shaped assignments (`token=`, `password:`,
|
|
bearer/api-key/secret/auth) and key refs (`env:`/`file:`/`literal:`/
|
|
`mpk:`/`bw:` values).
|
|
- Disabled `[notify]` yields a nil notifier; all methods are no-ops, so
|
|
call sites never gate. `once` never notifies (daemon-only surface).
|
|
|
|
**Tests** — 7 in `internal/notify` against a local fake SMTP server
|
|
(real SMTP dialogue: EHLO/AUTH/MAIL/RCPT/DATA/QUIT): send + headers +
|
|
recipients, rate-limit collapse + follow-up count, offline queue drain,
|
|
bounded queue oldest-drop, redaction (no secret bytes on the wire), nil
|
|
no-op, AUTH path. Plus a loop-level wiring test (turn_complete +
|
|
daemon_start fire and queue while SMTP is dead).
|
|
|
|
## 495 — 0630 briefing pipeline (surface existed at 064aed0; now wired)
|
|
|
|
- **Render** grows the 0630 sections: one-line **executive summary**
|
|
(shipped/moved/incoming/open · review chain · decisions needed ·
|
|
quota pulse), per-vertical **open-now backlog table** (window-
|
|
independent, capped at 15 rows) alongside the existing shipped/moved/
|
|
incoming window tables, **decisions-needed** (open issues in Feedback/
|
|
Needs-Decision statuses), **review-chain line** (open PR count + oldest
|
|
age), **quota line from quota state** (unchanged section + exec pulse),
|
|
and **system health** (loop heartbeat status.json: running/scans/
|
|
dispatched/reports/parked/last_scan_error + host load/mem/disk).
|
|
- **Delivery:** journal note on `[brief] redmine_issue` (the SoR copy,
|
|
e.g. 495 — benefits from 525's retry on the note write) + Discourse
|
|
topic when creds are present; missing creds = graceful logged skip
|
|
(never a failed briefing when the SoR note landed). Legacy
|
|
discourse-with-fallback path preserved when no redmine_issue is set.
|
|
Marker records `redmine` / `discourse` / `redmine+discourse`.
|
|
- **Daemon schedule hook:** the loop fires the brief daily at
|
|
`[brief] schedule` (default **06:00 America/Chicago** — 06:00 render
|
|
for the 0630 read; configurable HH:MM + timezone; "" = off; catch-up
|
|
on late start). Injected via `loop.DailyHook` from the CLI to avoid an
|
|
import cycle (brief already reads loop state). Idempotent per calendar
|
|
day via the existing brief.jsonl marker; `harness brief` unchanged as
|
|
the one-shot/cron surface.
|
|
|
|
**Tests** — 16 in `internal/brief` (9 new): render section coverage
|
|
(exec line / open-now / decisions / review chain / quota / health),
|
|
classify open-backlog + decisions, health-file reading, Redmine-primary
|
|
delivery (note content + graceful discourse skip), redmine+discourse
|
|
both-land marker, redmine-fails-discourse-saves, full render against the
|
|
fake Redmine; plus loop-side daily-hook tests (timezone math table +
|
|
fires-exactly-once-per-day under a live daemon loop).
|
|
|
|
## Smoke evidence
|
|
|
|
- `./dev.sh check` green before each commit (build + go vet + go test
|
|
./... — 12 packages ok, no failures; final run at commit `f9fb729`).
|
|
- Live read-only smoke of the 495 render (dry-run, **no writes to
|
|
Redmine**, no marker): authenticated GET of the real scope at
|
|
projects.knownelement.com (fixed_version_id=35, status New — genuinely
|
|
empty; the live loop heartbeat's "29 scans / 0 dispatched" corroborates),
|
|
live system-health section reading this host's running daemon
|
|
status.json. Transcript: `SMOKE-20260829-brief-dryrun.txt` beside this
|
|
report.
|
|
- 525/522 behaviors are exercised in-test against fake Redmine/SMTP
|
|
servers (see test lists above); no live 502-injection or SMS send was
|
|
performed against production systems.
|
|
|
|
## Deferred / open
|
|
|
|
- **Live Discourse send** (495): vendored client is wired and fake-tested,
|
|
but no live topic has been created — awaiting the 1900 CST creds
|
|
session Charles offered (discourse key ref + category). Config slot is
|
|
ready (`[brief] discourse_url` / `discourse_key_ref` / `category_slug`).
|
|
- **Live SMS send** (522): fake-SMTP-tested; a real relay + carrier
|
|
gateway addresses are needed to enable `[notify]` in production.
|
|
- **Quota pulse depth** (495): quota line currently reads "gate off"
|
|
until `[quota]` is enabled per instance (existing 490 surface, opt-in).
|
|
- gofmt drift exists across the repo (pre-existing, incl. at 19d61a0);
|
|
the check gate is build+vet+test, unchanged.
|
|
|
|
## Handoff
|
|
|
|
- `git status` clean; `origin/main` at `f9fb729`.
|
|
- New config surfaces documented in `harness.toml.example` ([redmine]
|
|
writeback knobs, [notify], [brief]) and README tables/usage.
|