{
  "spec_file": "docs/specs/2026-05-30-invoices-adapters.md",
  "audited_at_commit": "e307875",
  "summary": "Adapter layer is partially scaffolded (5 provider stubs, credential CRUD, sync-log table) but core push lifecycle, settings contract, reconciliation, and provider API mappings diverge heavily from spec.",
  "findings": [
    {
      "id": "ia-001",
      "severity": "P0",
      "title": "invoice.push queue messages are never consumed",
      "detail": "Push Lifecycle requires a queue consumer for invoice.push / invoice.payment_sync. credit-notes.ts:105 enqueues { type: 'invoice.push' }, but apps/zync-api/src/index.ts:141-145 default branch logs zync-jobs:unhandled-type and acks — no invoice-sync consumer exists anywhere in the repo.",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-002",
      "severity": "P1",
      "title": "issue-tax does not enqueue invoice.push",
      "detail": "Push Lifecycle step 1–2: on TAX_ISSUED transition, enqueue invoice.push when tenant has adapter + autoSyncOnTaxIssue. apps/zync-api/src/routes/invoices/index.ts:583-655 calls issueTaxInvoice and returns with no QUEUE.send (contrast credit-notes.ts:105).",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-003",
      "severity": "P1",
      "title": "Settings API /api/settings/integrations/invoicing missing",
      "detail": "API Endpoints require GET/PUT/POST test/GET logs under /api/settings/integrations/invoicing. Implementation mounts invoiceAdapterRouter at /api/invoice-adapters (apps/zync-api/src/routes/index.ts:335); grep finds no settings/integrations/invoicing routes in zync-api.",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-004",
      "severity": "P1",
      "title": "InvoiceAutomationSettings not persisted",
      "detail": "Invoice Automation Settings defines { adapter, autoSyncOnTaxIssue, autoDraftFromRetainer, autoSendRetainerInvoice, taskStatusTrigger }. No InvoiceAutomationSettings type or tenant_settings JSONB read/write exists; only unrelated invoice defaults live in packages/db/src/queries/settings-invoices.ts (task_status_trigger, auto_send_retainer_invoice without adapter/autoSync fields).",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-005",
      "severity": "P1",
      "title": "Push success never writes invoices.external_id/external_provider",
      "detail": "Push Lifecycle step 3d: on success store external_id + external_provider on invoice record. apps/zync-api/src/routes/invoice-adapters/router.ts:248-261 logs externalId to integration_sync_logs only; grep shows no UPDATE invoices SET external_id anywhere in adapter/cron code.",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-006",
      "severity": "P1",
      "title": "Reconcile cron re-pushes instead of getStatus paid sync",
      "detail": "Reconciliation Cron: for TAX_ISSUED invoices older than 24h with external_id, call adapter.getStatus(); if paid flip to PAID and fire invoice.paid webhook. apps/zync-api/src/routes/invoice-adapters/cron.ts:91-115 calls getPendingInvoices + pushInvoice (push retry), never getStatus, never status flip, no webhook.deliver.",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-007",
      "severity": "P1",
      "title": "Adapter interface missing createCreditNote/recordPayment/getStatus",
      "detail": "Adapter Interface requires testConnection, createInvoice, createCreditNote, optional recordPayment/getStatus. apps/zync-api/src/integrations/invoice-adapters/index.ts:24-27 defines only testConnection + pushInvoice(invoiceId, tenantId, db).",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-008",
      "severity": "P1",
      "title": "Credit notes pushed as tax invoices",
      "detail": "Credit note flow must call adapter.createCreditNote (Morning type 305). Credit-note issue enqueues invoice.push (credit-notes.ts:105) but all adapters use pushInvoice with tax-invoice payloads — Morning hardcodes doctype 320 (integrations/invoice-adapters/index.ts:144), never 305 or credit-note endpoints.",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-009",
      "severity": "P1",
      "title": "record-payment does not enqueue invoice.payment_sync",
      "detail": "Push Lifecycle: when invoice transitions to PAID and recordPayment supported, enqueue invoice.payment_sync. apps/zync-api/src/routes/invoices/index.ts:669-715 records payment and returns invoice with no QUEUE.send; no invoice.payment_sync handler exists.",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-010",
      "severity": "P1",
      "title": "No retry/backoff or admin notification on push failure",
      "detail": "Push Lifecycle step 3e: retry up to 3× with exponential backoff; on final failure createNotification for tenant admin. Manual push (router.ts:264-282) and cron (cron.ts:117-136) log failure once and return/continue — no message.retry, no createNotification call anywhere in invoice-adapter code.",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-011",
      "severity": "P1",
      "title": "@zync/invoice-adapters package and registry absent",
      "detail": "Task plan architecture: packages/invoice-adapters exporting InvoiceAdapter types, getInvoiceAdapter, listInvoiceAdapters, five concrete adapters. No packages/invoice-adapters directory; adapter code lives inline under apps/zync-api/src/integrations/invoice-adapters with local factory switch in router.ts:311-321.",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-012",
      "severity": "P2",
      "title": "Manual push route/path diverges from spec",
      "detail": "API Endpoints: POST /api/invoices/:id/push (staff retry using tenant adapter). Implemented as POST /api/invoice-adapters/:provider/push/:invoiceId (router.ts:220) requiring explicit provider param; returns 502/404 not spec 409 for non-pushable/no-adapter cases.",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-013",
      "severity": "P2",
      "title": "integration_sync_logs schema diverges from spec",
      "detail": "Sync Logs DDL: adapter_id, entity_type invoice|credit_note|payment, status success|error, request_payload/response_payload/error_message. packages/db/src/schema/invoice-adapters.ts:57-100 uses provider column, entity_type invoice|customer only, status success|failure|skipped, errorPayload jsonb — no request/response payload columns.",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-014",
      "severity": "P2",
      "title": "Morning adapter uses wrong API and auth",
      "detail": "Supported Adapters — Morning: https://api.greeninvoice.co.il/api/v1, credentials { apiKey, secret }, POST /documents types 320/305. integrations/invoice-adapters/index.ts:126 uses https://api.morning.co.il/v2, Bearer apiKey only (apiSecret ignored), POST /doc with doctype 320.",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-015",
      "severity": "P2",
      "title": "iCount/Rivhit/Invoice4u/Easycount endpoints diverge",
      "detail": "Spec endpoints: iCount ?action=doc_create form POST; Rivhit secure.rivhit.co.il CreateInvoice; Invoice4u CreateDoc token; Easycount app.easycount.co.il POST /invoices. Code uses iCount cmd=create_invrec (index.ts:203), Rivhit api.rivhit.co.il CreateTaxInvoice (257), Invoice4u AddDocument with apiKey (316), Easycount api.easycount.co.il POST /documents (375).",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-016",
      "severity": "P2",
      "title": "Permissions use system roles not RBAC settings/invoices perms",
      "detail": "Permissions table: configure adapter settings:write, view logs settings:read, manual push invoices:write. invoice-adapters/router.ts:77-94,293 uses isOwnerOrAdminRole / isOwnerAdminOrMemberRole checks instead of requirePermission('settings:read'|'settings:write'|'invoices:write').",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-017",
      "severity": "P2",
      "title": "Reconcile cron path and schedule diverge",
      "detail": "Reconciliation Cron: POST /api/cron/invoice-reconcile daily (CRON_SECRET). Mounted at /api/cron/invoice-adapter-reconcile (routes/index.ts:336); cron/runner.ts:17 schedules it hourly (0 * * * *), not daily.",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-018",
      "severity": "P2",
      "title": "Push payload omits InvoicePayload line/VAT/customer fields",
      "detail": "InvoicePayload requires lines with quantity/unitPrice/discount/total/taxable plus vatRate/subtotal/vatAmount and customer vatId/address. fetchInvoiceForPush (integrations/invoice-adapters/index.ts:57-119) loads header + customer name/email only; each adapter collapses to a single income row from invoice.total — invoice_lines, tax_id, VAT breakdown never mapped.",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-019",
      "severity": "P2",
      "title": "Multi-adapter per tenant; no single active adapter",
      "detail": "Architecture decision: per-tenant single adapter via InvoiceAutomationSettings.adapter. listAdapterConfigs (invoice-adapters.ts:209-235) returns all five providers independently; tenant may configure multiple invoice:* credentials; push/cron iterate every configured provider with no active-adapter selection.",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-020",
      "severity": "P2",
      "title": "Sync-log read API path and helper diverge",
      "detail": "API Endpoints: GET /api/settings/integrations/invoicing/logs; task 9 also requires getLatestSyncStatus helper. Implemented as GET /api/invoice-adapters/logs (router.ts:288); listSyncLogs exists but getLatestSyncStatus is not exported from packages/db/src/queries/invoice-adapters.ts.",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-021",
      "severity": "P2",
      "title": "Admin Services Log and per-adapter status card not wired",
      "detail": "Sync Logs: visible in admin tenant detail Services Log tab and tenant settings per-adapter status card. No admin route/UI reads integration_sync_logs; invoicing settings page (apps/zync-app/src/routes/settings/integrations/invoicing.tsx) shows configure/test only — no sync-log fetch or status badge from getLatestSyncStatus.",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-022",
      "severity": "P2",
      "title": "Spec-mandated adapter contract tests missing",
      "detail": "Task 10 acceptance: packages/invoice-adapters/test/adapters.contract.test.ts and apps/zync-api/test/invoice-sync.test.ts covering registry, Morning 320/305, iCount doc_create, queue consumer external_id write, reconcile PAID flip. Glob finds zero invoice-adapter test files under apps/zync-api/test or packages/.",
      "fix_direction": "code_to_spec"
    },
    {
      "id": "ia-023",
      "severity": "P2",
      "title": "adapter_credentials storage shape differs from spec DDL",
      "detail": "Adapter Credentials Storage spec shows adapter_id='morning' and credentials BYTEA column. Implementation namespaces adapter_id as invoice:{provider} (invoice-adapters.ts:31-33) and stores ciphertext/iv/authTag text columns on existing communications adapter_credentials table — diverges from spec table shape though matches upstream comms pattern.",
      "fix_direction": "spec_review"
    }
  ]
}
