Skip to content

M19.1: Notification coverage audit#70

Merged
n8bar merged 5 commits into
mainfrom
claude/m19.1-mail-surface-audit
May 10, 2026
Merged

M19.1: Notification coverage audit#70
n8bar merged 5 commits into
mainfrom
claude/m19.1-mail-surface-audit

Conversation

@n8bar
Copy link
Copy Markdown
Owner

@n8bar n8bar commented May 10, 2026

Summary

M19.1 — Notification Coverage Audit, Phase 1 of MS19. All four strategy doc sections complete, all three drift findings resolved in-phase, all exit criteria met.

  • ✅ §1 — Live mail surface enumerated (14 Mail classes, 0 Notifications, 2 outbound callsites — both via Mail classes, no raw-mailer bypass).
  • ✅ §2 — Coverage & Status matrix populated in NOTIFICATIONS.md (12 live, 2 stubbed).
  • ✅ §3 — Per-row drift verification: 11 rows aligned, 3 findings raised.
  • ✅ §4 — All three findings resolved; no backlog deferrals.

Strategy doc renamed 19.1 → x19.1 per the project's completed-doc convention; Phase 1 rollup checked off in docs/milestones/19_RC_HARDENING_OPS.md.

Findings & resolutions

Finding Resolution
F1 — Spec §3.1.2 says manual send "issues the invoice out of draft" but no code path transitioned Invoice::status. Drafts that got sent stayed draft, and refreshPaymentState skipped them entirely → paid invoices could remain stuck at draft. Option C (hybrid). Spec §3.1.2 reworded for an explicit, conditional transition rule with a no-regression guarantee. InvoiceDeliveryController::store updated to transition draftsent on successful queue. Two new tests in InvoiceDeliveryTest.
F2 — Spec §5.12.2 / §5.12.3 examples used (owner) but shipped labels use (issuer) after the MS17 sweep. Spec catch-up. Examples updated to (issuer) to match InvoiceDelivery::typeLabel().
F3 — Spec §4.4.2 said client underpay alert should include "outstanding USD/BTC amounts"; shipped view shows USD only. Spec narrowed. §4.4.2 reduced to "outstanding USD amount." Linked invoice page surfaces full BTC; email body stays simple/USD-focused. Code can add BTC or rate-link copy later as a non-spec enhancement.

Plus an earlier in-phase tweak (committed before §3 scan): NOTIFICATIONS spec §4.3 / §4.4 retitled (Client)(Owner + Client) to match the paired client/issuer Mailable classes that have always existed in code, and an owner-alert sub-bullet added to each, derived from the actual issuer mail bodies.

What changed

Spec (docs/specs/NOTIFICATIONS.md):

  • §3.1.2 — explicit transition rule with no-regression clause.
  • §4.3 / §4.4 — retitled (Owner + Client), owner-alert sub-bullet added.
  • §4.4.2 — narrowed to USD-only.
  • §5.12.2 / §5.12.3 — display-label examples updated to (issuer).
  • New ## Coverage & Status section: 14-row matrix replacing the MS17 placeholder.

Code:

  • app/Http/Controllers/InvoiceDeliveryController.phpstore() transitions draftsent after a successful queue.

Tests:

  • tests/Feature/InvoiceDeliveryTest.phptest_sending_draft_invoice_transitions_status_to_sent (transition path) and test_sending_does_not_regress_status_for_non_draft_invoice (no-regression for paid).

Docs:

  • docs/strategies/19.1_NOTIFICATION_COVERAGE_AUDIT.mdx19.1_... (completed-doc rename); §1–§4 results fully populated; exit criteria all checked off.
  • docs/milestones/19_RC_HARDENING_OPS.md — Phase 1 rollup checked off with date and link to the renamed strategy doc.

Verification

  • Local: ./vendor/bin/sail artisan test336 passing (1640 assertions). Was 334 before this PR; +2 from the F1 transition tests.
  • CI: PR Tests workflow is the next gate.
  • Spec table links resolve to tests/Feature/** paths (relative).

Test plan

  • PR Tests workflow runs green on this branch.
  • InvoiceDeliveryTest + InvoiceNotificationTest both pass in CI.
  • Coverage & Status matrix in NOTIFICATIONS.md renders cleanly in the GitHub web view.

Process notes

This phase was the second pass of the M19 GitHub Issues trial. Audit-style phase work — driven by a strategy doc rather than a single bug — didn't have a parent Issue. The PR description is doing the work of a milestone-progress note. Worth a look at the M20 retro: should phases like this still have an umbrella Issue for visibility, or is the strategy doc + PR sufficient?

n8bar added 3 commits May 9, 2026 20:48
Confirms 14 Mail classes (no app/Notifications/, no raw-mailer bypass)
and lists the two outbound callsites — DeliverInvoiceMail (canonical
delivery-log-driven path) and NotificationSettingsController (owner
branding preview, not delivery-log driven).

Two drift flags for §3 to address:
- Spec §4.3 and §4.4 title overpay/underpay alerts as "(Client)" only,
  but code has paired client/issuer classes and §5.4 / §5.12.2 imply
  the pair. Small inline spec tweak indicated.
- Partial-warning Mail classes are wired into the type map but no
  service queues them; matches spec §4.4.3 / §5.12.3 (deprecated alert
  family, legacy display only). Will surface as `stubbed` in the §2
  matrix.
…Client)

Code has had paired client/issuer Mailables for both alerts since they
were introduced, and §5.4 / §5.12.2 of NOTIFICATIONS.md already imply
the pair. §4.3 and §4.4 still titled them as "(Client)" only, leaving
the spec narrative internally inconsistent.

Retitled both as "(Owner + Client)" — matching §4.2 Past Due — and
added one owner-alert sub-bullet to each, derived from the actual
issuer mail bodies:

- §4.3 owner: overpayment percentage + disposition prompt (tip /
  credit / manual adjustment / refund) with link back into app.
- §4.4 owner: outstanding balance + link back for follow-up or manual
  adjustment.

Existing client-alert language and the fragmented-partial-payment
guidance are unchanged.

Pulled this work forward from §3 of the M19.1 strategy doc; the §1
drift flag is annotated as resolved with a breadcrumb to here.
14-row coverage matrix replaces the MS17 placeholder. One row per
outbound Mailable in `app/Mail/`. Status: 12 `live`, 2 `stubbed`
(InvoicePartialWarning client/issuer — Mail classes wired into
DeliverInvoiceMail's type map but no service queues new rows; matches
spec §4.4.3 deprecation guidance and §5.12.3 legacy-display rule).

Each row links to its feature test(s) by relative path; delivery log
types reference the canonical map in DeliverInvoiceMail::handle plus
`send` (manual default) and `—` for NotificationBrandingPreviewMail
which bypasses the delivery log per spec §5.14.4.1.

`(MS17 deliverable)` suffix dropped from the heading.

Strategy doc §2 checkoffs all marked, with a brief §2 results note.
@n8bar n8bar added documentation Improvements or additions to documentation area:notifications Outbound mail, alerts, delivery surface labels May 10, 2026
n8bar added 2 commits May 9, 2026 21:47
Walked each of the 14 matrix rows. Read each Mailable's Blade view and
its trigger callsite (InvoiceAlertService for alerts, DeliverInvoiceMail
for sends, the two controllers for manual send/receipt and the branding
preview, and the past-due console command).

Aligned (11 rows): InvoiceReady (modulo F1 question), payment-ack pair,
InvoicePaidReceipt, InvoiceIssuerPaidNotice, past-due pair, overpay pair
(post-tweak), underpay-issuer, partial-warning pair (stubbed by design,
matches §4.4.3 / §5.12.3), NotificationBrandingPreview.

Three findings raised for §4:

- F1: Spec §3.1.2 says manual send "issues the invoice out of `draft`"
  but no code path transitions Invoice::status on send. Either spec
  over-specifies or code is missing the transition. Decision needed.

- F2: Spec §5.12.2 / §5.12.3 examples use "(owner)" but shipped labels
  use "(issuer)" after the MS17 sweep. Small spec tweak.

- F3: Client underpay alert view shows USD only; spec §4.4.2 calls for
  USD/BTC. Either add BTC line to the view or drop "/BTC" from spec.
  Decision needed (small either way).

Strategy doc §3 checkoffs marked; findings recorded inline.
Three findings from §3 resolved in-phase. No backlog deferrals. All
Phase 1 exit criteria satisfied. Strategy doc renamed with `x` prefix
per the project's completed-doc convention; Phase 1 rollup checked off
in the M19 milestone doc.

F1 — Manual send draft transition (Option C):
  - Spec §3.1.2 reworded: explicit transition rule with no-regression
    guarantee for already-sent/partial/pending/paid/void invoices.
  - InvoiceDeliveryController::store: after a successful queue, draft
    invoices transition to `sent`; non-draft invoices keep their
    current status.
  - Two new feature tests in InvoiceDeliveryTest covering both the
    transition and the no-regression cases.

F2 — Owner → Issuer in spec display labels:
  - Spec §5.12.2 example: `Underpayment alert (owner)` → `(issuer)`.
  - Spec §5.12.3 example: `Partial payment warning (client|owner)` →
    `(client|issuer)`.
  - Catches the spec up to the MS17 owner→issuer terminology sweep
    that already shipped in InvoiceDelivery::typeLabel().

F3 — Client underpay alert USD/BTC:
  - Spec §4.4.2 narrowed to "outstanding USD amount". Linked invoice
    page surfaces full BTC; email body stays simple/USD-focused.
  - No code change — current `invoice-underpayment-client.blade.php`
    is already aligned with the narrower spec.

Test suite: 336 passing (was 334; +2 new tests for F1).
@n8bar n8bar marked this pull request as ready for review May 10, 2026 04:33
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3d075c620c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +72 to +74
if ($invoice->status === 'draft') {
$updates['status'] = 'sent';
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recompute payment state when issuing paid drafts

When a draft already has detected payment activity, such as a public draft link paid before the owner manually sends it or a partially paid invoice reset to draft and resent, this always changes the invoice to sent without running the existing ledger/status logic. Invoice::refreshPaymentState() deliberately keeps underpaid draft invoices as draft, but once this action issues the invoice out of draft it should immediately become pending/partial based on those existing payments; otherwise it can remain incorrectly sent until some later watcher or correction event happens to refresh it.

Useful? React with 👍 / 👎.

@n8bar n8bar merged commit 2268ac3 into main May 10, 2026
1 check passed
@n8bar n8bar deleted the claude/m19.1-mail-surface-audit branch May 10, 2026 04:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:notifications Outbound mail, alerts, delivery surface documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant