{
  "slug": "recurring-invoices",
  "spec_file": "docs/specs/2026-05-31-recurring-invoices.md",
  "findings": [
    {
      "id": "recurring-invoices-001",
      "severity": "P1",
      "type": "bug",
      "summary": "computeNextDate monthly from Jan-31 day-31 yields Mar-31 not Feb-28",
      "spec_ref": "Next date calculation / Day clamping",
      "code_ref": "packages/db/src/queries/recurring-invoices.ts:85",
      "evidence": "Node repro of computeNextDate('2026-01-31','monthly',31) returns 2026-03-31; spec requires 2026-02-28.",
      "repro": "Create monthly template frequency_day=31 starting 2026-01-31; preview/advance shows March date instead of clamped February."
    },
    {
      "id": "recurring-invoices-002",
      "severity": "P2",
      "type": "missing",
      "summary": "No OWNER notification when template auto-completes",
      "spec_ref": "Auto-complete",
      "code_ref": "apps/zync-api/src/routes/cron/recurring-invoice-generator.ts:150",
      "evidence": "advanceRecurringTemplate sets status completed but cron never calls createNotification for OWNER.",
      "repro": ""
    },
    {
      "id": "recurring-invoices-003",
      "severity": "P2",
      "type": "divergent",
      "summary": "Generated invoices use source retainer not manual",
      "spec_ref": "Generation Mechanism step 1",
      "code_ref": "apps/zync-api/src/routes/cron/recurring-invoice-generator.ts:111",
      "evidence": "createInvoice called with source:'retainer'; spec buildInvoice uses manual source semantics.",
      "repro": ""
    },
    {
      "id": "recurring-invoices-004",
      "severity": "P2",
      "type": "divergent",
      "summary": "GET /api/recurring-invoices lacks cursor pagination",
      "spec_ref": "API Endpoints GET /api/recurring-invoices",
      "code_ref": "apps/zync-api/src/routes/invoices/recurring.ts:75",
      "evidence": "List returns full templates array; no cursor/limit/buildPaginated despite spec default limit 50.",
      "repro": ""
    },
    {
      "id": "recurring-invoices-005",
      "severity": "P2",
      "type": "missing",
      "summary": "Generated invoices filtered view /invoices?recurring_template_id missing",
      "spec_ref": "Generated Invoices View",
      "code_ref": "NONE",
      "evidence": "Grep recurring_template_id in apps/zync-app returns no matches; InvoicePage has no breadcrumb/filter hook.",
      "repro": "Navigate /invoices?recurring_template_id=<id> — full list shown, no breadcrumb."
    },
    {
      "id": "recurring-invoices-006",
      "severity": "P2",
      "type": "divergent",
      "summary": "auto_charge warning not returned on template create",
      "spec_ref": "auto_charge v2 placeholder",
      "code_ref": "apps/zync-api/src/routes/invoices/recurring.ts:129",
      "evidence": "POST handler forces autoCharge:false but never returns warning field when client sends auto_charge=true.",
      "repro": ""
    },
    {
      "id": "recurring-invoices-007",
      "severity": "P3",
      "type": "divergent",
      "summary": "generate-now on inactive template returns 422 not 409",
      "spec_ref": "POST /api/recurring-invoices/:id/generate-now",
      "code_ref": "apps/zync-api/src/routes/invoices/recurring.ts:235",
      "evidence": "Non-active template returns 422; spec specifies 409.",
      "repro": ""
    },
    {
      "id": "recurring-invoices-008",
      "severity": "P3",
      "type": "divergent",
      "summary": "DELETE cancel returns 204 not 200 {status:cancelled}",
      "spec_ref": "DELETE /api/recurring-invoices/:id",
      "code_ref": "apps/zync-api/src/routes/invoices/recurring.ts:216",
      "evidence": "Handler returns new Response(null,{status:204}); spec returns 200 with {status:'cancelled'}.",
      "repro": ""
    },
    {
      "id": "recurring-invoices-009",
      "severity": "P3",
      "type": "divergent",
      "summary": "Below-Business tier gets 402 not 403 on create",
      "spec_ref": "Tier Availability / Active template limit",
      "code_ref": "apps/zync-api/src/routes/invoices/recurring.ts:107",
      "evidence": "requireTier failure returns 402 Upgrade required; spec 403 with upgrade prompt body.",
      "repro": ""
    }
  ],
  "summary": { "P0": 0, "P1": 1, "P2": 5, "P3": 3, "total": 9 }
}
