{
  "slug": "invoices-core",
  "spec_file": "docs/specs/2026-05-30-invoices-core.md",
  "findings": [
    {
      "id": "invoices-core-001",
      "severity": "P0",
      "type": "broken",
      "summary": "R2 tax snapshot rendered before TAX_ISSUED; lacks invoice number",
      "spec_ref": "Invoice HTML generation / R2 snapshot on TAX_ISSUED",
      "code_ref": "apps/zync-api/src/routes/invoices/index.ts:467",
      "evidence": "issue-tax calls renderInvoiceHtml + R2 put before issueTaxInvoice(); invoice is still APPROVED with invoiceNumber=null at render time.",
      "repro": "Issue tax invoice; open GET /api/invoices/:id/html or R2 snapshot — document shows proforma title/number, not חשבונית מס number."
    },
    {
      "id": "invoices-core-002",
      "severity": "P1",
      "type": "missing",
      "summary": "GET /api/invoices/unbilled-time returns empty stub",
      "spec_ref": "Time Entry Billing / API Endpoints",
      "code_ref": "apps/zync-api/src/routes/invoices/index.ts:187",
      "evidence": "Route returns { items: [], note: 'Requires spec 77...' } with no time_entries query.",
      "repro": "GET /api/invoices/unbilled-time?projectId=<id> always returns items:[]."
    },
    {
      "id": "invoices-core-003",
      "severity": "P1",
      "type": "missing",
      "summary": "invoice.generate queue consumer for automated generation absent",
      "spec_ref": "Automated Invoice Generation",
      "code_ref": "NONE",
      "evidence": "Grep for invoice.generate and apps/zync-api/src/jobs/invoice-generate.ts finds no implementation; spec retainer/task/deposit auto-gen not wired.",
      "repro": ""
    },
    {
      "id": "invoices-core-004",
      "severity": "P2",
      "type": "divergent",
      "summary": "DELETE /api/invoices/:id gated on invoices:write not invoices:delete",
      "spec_ref": "Permissions",
      "code_ref": "apps/zync-api/src/routes/invoices/index.ts:241",
      "evidence": "Route uses requirePermission('invoices:write'); spec requires invoices:delete for draft deletion.",
      "repro": ""
    },
    {
      "id": "invoices-core-005",
      "severity": "P2",
      "type": "divergent",
      "summary": "R2 snapshot key omits tax-invoice number and locale tag",
      "spec_ref": "R2 snapshot locale flag / On TAX_ISSUED snapshot",
      "code_ref": "apps/zync-api/src/routes/invoices/index.ts:483",
      "evidence": "Key is `${tenantId}/${id}/invoice.html`; spec requires `{tenantId}/invoices/{invoiceId}/tax-invoice-{number}-{lang}.html`.",
      "repro": ""
    },
    {
      "id": "invoices-core-006",
      "severity": "P2",
      "type": "divergent",
      "summary": "Invoice HTML uses Google Fonts not R2 Heebo @font-face embed",
      "spec_ref": "PDF Locale — Hebrew font embed",
      "code_ref": "packages/db/src/queries/invoices.ts:1655",
      "evidence": "renderInvoiceHtml imports fonts.googleapis.com; spec requires @font-face from R2 _static/fonts/heebo-variable.woff2 with font-display:block.",
      "repro": ""
    },
    {
      "id": "invoices-core-007",
      "severity": "P2",
      "type": "divergent",
      "summary": "HTML render hardcodes he-IL; tenant/customer names are TODO placeholders",
      "spec_ref": "PDF Locale / Invoice HTML generation",
      "code_ref": "apps/zync-api/src/routes/invoices/index.ts:472",
      "evidence": "issue-tax and /html pass locale:'he-IL', tenantName:session.tid, customerName:invoice.customerId with TODO comments; no tenants.locale lookup.",
      "repro": ""
    },
    {
      "id": "invoices-core-008",
      "severity": "P2",
      "type": "divergent",
      "summary": "No POST /api/invoices/:id/reopen; REJECTED edits in place",
      "spec_ref": "Invoice detail actions — REJECTED",
      "code_ref": "NONE",
      "evidence": "Grep reopen in apps/zync-api/src/routes/invoices returns no route; updateInvoice allows REJECTED edit and sendInvoice allows REJECTED→SENT, skipping explicit DRAFT reopen.",
      "repro": ""
    },
    {
      "id": "invoices-core-009",
      "severity": "P3",
      "type": "divergent",
      "summary": "unbilled-time requires time:read not invoices:read",
      "spec_ref": "Permissions / GET /api/invoices/unbilled-time",
      "code_ref": "apps/zync-api/src/routes/invoices/index.ts:180",
      "evidence": "Route gated with requirePermission('time:read'); spec permission table lists invoices:read for invoice endpoints.",
      "repro": ""
    }
  ],
  "summary": { "P0": 1, "P1": 2, "P2": 5, "P3": 1, "total": 9 }
}
