feat(shipreport): daily scripted fleet ship-report (safety valve for #405) #406

Merged
joshtronic merged 1 commit from feat/ship-report into master 2026-07-20 22:29:00 +00:00
Collaborator

Step ④ of the review-gate rollout, and the safety valve for #405. Spec merged (#404).

What it does

A daily email to PRIMARY_RECIPIENTS — the once-a-day window that keeps you in control by exception once you're out of the per-PR merge gate:

  • Needs you — open PRs on the human-gated carve-outs (joshing.you, igor.bot, porksicle).
  • Shipped, last 24h — what auto-merged + deployed, each tagged 🤖 shadow / 👤 you.
  • In flight — open PRs on default (shadow-gated) repos.

Mechanics

  • Fully scripted, no model call — sits above the health gate and sends even during a Claude cooldown (mirrors do_seo_tick: runs in both the health-blocked branch and the normal cascade).
  • Daily, first tick after 07:00 local, one .shipreport stamp (clear to resend).
  • New lib/ship-report.sh (build/render/stamp, unit-tested); gathering in do_shipreport_tick.
  • Reads the require_human flag directly, so this PR is independent of #405.

Tests

bin/test-shipreport.sh: bucketing, the shadow-vs-human tag, empty-window, the daily-stamp round-trip, and a guard that the module makes no model call. make test green (incl. check-sync).

Merge together with #405

Merge this with #405 so shadow-gating and its daily visibility go live as a pair — this report is what makes flipping the gate safe.

Step ④ of the review-gate rollout, and the safety valve for #405. Spec merged (#404). ## What it does A daily email to `PRIMARY_RECIPIENTS` — the once-a-day window that keeps you in control by exception once you're out of the per-PR merge gate: - **Needs you** — open PRs on the human-gated carve-outs (joshing.you, igor.bot, porksicle). - **Shipped, last 24h** — what auto-merged + deployed, each tagged 🤖 shadow / 👤 you. - **In flight** — open PRs on default (shadow-gated) repos. ## Mechanics - **Fully scripted, no model call** — sits above the health gate and sends even during a Claude cooldown (mirrors `do_seo_tick`: runs in both the health-blocked branch and the normal cascade). - Daily, first tick after 07:00 local, one `.shipreport` stamp (clear to resend). - New `lib/ship-report.sh` (build/render/stamp, unit-tested); gathering in `do_shipreport_tick`. - Reads the `require_human` flag directly, so this PR is **independent of #405**. ## Tests `bin/test-shipreport.sh`: bucketing, the shadow-vs-human tag, empty-window, the daily-stamp round-trip, and a guard that the module makes no model call. `make test` green (incl. `check-sync`). ## Merge together with #405 Merge this **with** #405 so shadow-gating and its daily visibility go live as a pair — this report is what makes flipping the gate safe.
feat(shipreport): daily scripted fleet ship-report (needs-you / shipped / in-flight)
All checks were successful
Lint / check-sync (pull_request) Successful in 4s
Lint / check-sync (push) Successful in 4s
6bee5bf12d
The safety valve for shadow-review auto-merge (spec: docs/superpowers/specs/
2026-07-20-automerge-review-gate-and-ship-report-design.md). do_shipreport_tick
emails PRIMARY_RECIPIENTS once a day (first tick after 07:00, one .shipreport
stamp): what auto-merged + deployed in the last 24h tagged shadow-vs-human, what
needs the human (open PRs on require_human carve-outs), and what is in flight.
Fully scripted -- no model call -- so it runs above the health gate and sends
even during a Claude cooldown, mirroring do_seo_tick (blocked branch + normal
cascade). Assembly/render/stamp in lib/ship-report.sh (unit-tested via
bin/test-shipreport.sh); the Forgejo gathering lives in the tick. Reads the
require_human flag directly, so this is independent of the gate-logic PR.
Author
Collaborator

🤖 Review — APPROVE (automated)

CI for 6bee5bf1: success

Summary: Adds a fully-scripted daily "fleet ship report" email (new lib/ship-report.sh + do_shipreport_tick in tick.sh), with unit tests. In scope, honestly described, CI green. APPROVE with minor non-blocking notes.

What I checked and liked:

  • Scope: ~320 lines, single feature, no unrelated churn. Under the 400 line bar.
  • Checklist honesty: Every claim maps to real code — pure build/render/stamp module (shipreport_build, shipreport_is_empty, shipreport_render_text/html, shipreport_sent_today, shipreport_mark_sent), gathering in do_shipreport_tick, and the "no model call" claim is both true in the module and guarded by a grep test in bin/test-shipreport.sh.
  • Dual-branch placement: correctly wired into both the health-blocked branch (do_shipreport_tick || true) and the normal cascade (if do_shipreport_tick; then exit 0; fi), mirroring do_seo_tick. A single tick only executes one branch (health-blocked path exit 0s), so no double-send; the daily stamp guards across ticks.
  • State stamp: shipreport_mark_sent uses jq '.shipreport = {...}' which preserves sibling keys (e.g. sports' .sports) in the shared discretionary-state.json. Empty-window path stamps + returns 0 so it won't retry all day.
  • Independence from #405: reads .automerge.require_human directly via forgejo_repo_get_file rather than the automerge module — matches the description's claim.
  • Escaping: titles rendered through |@html. URLs come from Forgejo html_url, low injection risk.

Non-blocking notes for the human:

  • "Shipped = deployed" is an overclaim. The description (and subject line semantics) say "auto-merged + deployed," but the gather only filters on merged_at >= since; there's no deploy verification. The footer ("Deploy failures are alerted separately") covers the gap, but the "shipped" bucket is really "merged in last 24h."
  • Unverifiable integration points. email_send, recipients_with_primary, _fj, BOT_USER, ANALYSIS_REPOS_JSON are external to this diff; I can't confirm their signatures/behavior from the diff alone. They mirror the existing sports/SEO digests and CI (incl. check-sync) is green, which is my basis for accepting them.
  • HTML escape test is weak. has "...@html" "$HTML" "shadow merge" only asserts the substring appears, not that a </& in a title is actually escaped. Consider a fixture title containing <b> to prove the @html filter. Not blocking.

No correctness, security, or scope defects found; CI is success.


Independent review by the harness on claude-opus-4-8 (effort: high). The human reviewer is requested once Igor has reviewed; a human still merges.

### 🤖 Review — `APPROVE` _(automated)_ CI for `6bee5bf1`: **success** **Summary:** Adds a fully-scripted daily "fleet ship report" email (new `lib/ship-report.sh` + `do_shipreport_tick` in `tick.sh`), with unit tests. In scope, honestly described, CI green. APPROVE with minor non-blocking notes. **What I checked and liked:** - **Scope:** ~320 lines, single feature, no unrelated churn. Under the 400 line bar. - **Checklist honesty:** Every claim maps to real code — pure build/render/stamp module (`shipreport_build`, `shipreport_is_empty`, `shipreport_render_text/html`, `shipreport_sent_today`, `shipreport_mark_sent`), gathering in `do_shipreport_tick`, and the "no model call" claim is both true in the module and guarded by a grep test in `bin/test-shipreport.sh`. - **Dual-branch placement:** correctly wired into both the health-blocked branch (`do_shipreport_tick || true`) and the normal cascade (`if do_shipreport_tick; then exit 0; fi`), mirroring `do_seo_tick`. A single tick only executes one branch (health-blocked path `exit 0`s), so no double-send; the daily stamp guards across ticks. - **State stamp:** `shipreport_mark_sent` uses `jq '.shipreport = {...}'` which preserves sibling keys (e.g. sports' `.sports`) in the shared `discretionary-state.json`. Empty-window path stamps + returns 0 so it won't retry all day. - **Independence from #405:** reads `.automerge.require_human` directly via `forgejo_repo_get_file` rather than the automerge module — matches the description's claim. - **Escaping:** titles rendered through `|@html`. URLs come from Forgejo `html_url`, low injection risk. **Non-blocking notes for the human:** - **"Shipped = deployed" is an overclaim.** The description (and subject line semantics) say "auto-merged + deployed," but the gather only filters on `merged_at >= since`; there's no deploy verification. The footer ("Deploy failures are alerted separately") covers the gap, but the "shipped" bucket is really "merged in last 24h." - **Unverifiable integration points.** `email_send`, `recipients_with_primary`, `_fj`, `BOT_USER`, `ANALYSIS_REPOS_JSON` are external to this diff; I can't confirm their signatures/behavior from the diff alone. They mirror the existing sports/SEO digests and CI (incl. `check-sync`) is green, which is my basis for accepting them. - **HTML escape test is weak.** `has "...@html" "$HTML" "shadow merge"` only asserts the substring appears, not that a `<`/`&` in a title is actually escaped. Consider a fixture title containing `<b>` to prove the `@html` filter. Not blocking. No correctness, security, or scope defects found; CI is `success`. --- <sub>Independent review by the harness on `claude-opus-4-8` (effort: high). The human reviewer is requested once Igor has reviewed; a human still merges.</sub> <!-- review sha=6bee5bf12d320ad1f742baa9938ce0e5861e7315 verdict=APPROVE ci=success -->
igor added spent time 2026-07-20 22:28:22 +00:00
1 minute
joshtronic deleted branch feat/ship-report 2026-07-20 22:29:00 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No assignees
1 participant
Notifications
Total time spent: 1 minute
igor
1 minute
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
joshtronic/igor!406
No description provided.