[
  {
    "id": "accountant-export-001",
    "title": "Expense movements debit gross amount and double-credit A/P — ledger unbalanced",
    "severity": "P0",
    "area": "integration",
    "what_spec_says": "Expense (deductible): Dr Expense category + VAT-input, Cr Vendor (A/P)/Bank; expenses-module net expense = amount − vat_amount (gross incl VAT in amount).",
    "what_code_does": "deriveMovements debits expenseAccount with full exp.amount (gross), credits apAccount, then debits vatInputAccount and credits apAccount again for vatAmount — two AP credits and overstated expense debits.",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-api/src/reports/movements.ts:232-255; docs/specs/2026-05-30-expenses-module.md:91",
    "notes": "Example amount=118 vat=18 → ΣDr 136 vs ΣCr 236."
  },
  {
    "id": "accountant-export-002",
    "title": "Invoice movements use face subtotal/total without ILS normalization",
    "severity": "P0",
    "area": "integration",
    "what_spec_says": "Plan Task 3: all amounts in ILS using invoices.total_ils for FX invoices (multi-currency snapshot at TAX_ISSUED).",
    "what_code_does": "deriveMovements uses toIls(inv.subtotal) and toIls(inv.total) with no total_ils lookup; invoices schema has total/subtotal only (no total_ils column or migration).",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-api/src/reports/movements.ts:138-140; packages/db/src/schema/invoices.ts:86-88",
    "notes": "Foreign-currency invoice face values are posted as if ILS."
  },
  {
    "id": "accountant-export-003",
    "title": "Contractor payout movements use net amount and credit A/P instead of Bank",
    "severity": "P0",
    "area": "integration",
    "what_spec_says": "Contractor payout: Dr Subcontractor expense, Cr Bank, Withholding-payable (856); payout_bills.amount is gross.",
    "what_code_does": "Uses netAmount for expense debit, credits apAccount (not bank); withholding reclassified via Dr AP Cr withholding instead of Cr bank + Cr withholding from gross expense.",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-api/src/reports/movements.ts:263-291; packages/db/src/schema/contractors.ts:153-161",
    "notes": "Misstates payout cash flow and expense gross."
  },
  {
    "id": "accountant-export-004",
    "title": "Input-VAT movements ignore vat_deductible and deduction_pct",
    "severity": "P0",
    "area": "integration",
    "what_spec_says": "expenses-module: input VAT sums vat_amount only where vat_deductible; partial deductibility scales vat_amount × deduction_pct/100.",
    "what_code_does": "Posts VAT-input leg whenever exp.vatAmount > 0 with no vatDeductible filter and no deductionPct scaling.",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-api/src/reports/movements.ts:244-255; packages/db/src/schema/expenses.ts:75,86",
    "notes": "Non-deductible and partial-deductible expenses over-post VAT input."
  },
  {
    "id": "accountant-export-005",
    "title": "Form 6111 Excel uses non-statutory 600/710 codes instead of ITA 1000–3999 fields",
    "severity": "P0",
    "area": "api",
    "what_spec_says": "Form 6111 P&L section uses ITA-numbered fields 1000–3999 (e.g. 1010 revenue, 2010 cost of sales), grouped via coa_accounts.form6111_code.",
    "what_code_does": "generateForm6111 hardcodes codes 600/601/710/799/800/900 in workbook rows; coa-defaults seeds form6111Code values like 600/710 not 1010/2010.",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-api/src/reports/form6111.ts:102-196; apps/zync-api/src/reports/coa-defaults.ts:43-58",
    "notes": "Output is a P&L summary, not ITA 6111 field-coded structure."
  },
  {
    "id": "accountant-export-006",
    "title": "Form 6111 calls getProfitLoss SQL referencing missing invoices.total_ils column",
    "severity": "P0",
    "area": "api",
    "what_spec_says": "Form 6111 sources P&L from financial-statements aggregations (getProfitLoss over invoices/expenses/payout_bills).",
    "what_code_does": "getProfitLoss raw SQL uses COALESCE(total_ils, total) but no migration or Drizzle column defines total_ils on invoices — PostgreSQL will error at query time.",
    "fix_direction": "code_to_spec",
    "evidence": "packages/db/src/reports/profit-loss.ts:56; packages/db/src/schema/invoices.ts:86-88; packages/db/migrations (grep total_ils: no matches)",
    "notes": "POST /api/reports/accountant/form6111 likely fails when job runs generateForm6111."
  },
  {
    "id": "accountant-export-007",
    "title": "generateMovementFile updates export jobs by id only — no tenant_id predicate",
    "severity": "P0",
    "area": "api",
    "what_spec_says": "Plan Task 8: tenant-scope every query; job mutations must be tenant-isolated.",
    "what_code_does": "movement-file.ts UPDATE accountant_export_jobs SET … WHERE id=jobId only (running/done/error paths); bypasses tenant-scoped setAccountantExportJobDone helper in @zync/db/queries.",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-api/src/reports/movement-file.ts:107-153; packages/db/src/queries/accountant-export.ts:153-183",
    "notes": "Cross-tenant job UUID could mutate another tenant's job row."
  },
  {
    "id": "accountant-export-008",
    "title": "generateForm6111 updates export jobs by id only — no tenant_id predicate",
    "severity": "P0",
    "area": "api",
    "what_spec_says": "Plan Task 8: tenant-scope every query on accountant_export_jobs updates.",
    "what_code_does": "form6111.ts UPDATE accountant_export_jobs WHERE eq(id, jobId) with no tenantId guard on running/done/error updates.",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-api/src/reports/form6111.ts:68-221",
    "notes": "Same cross-tenant write vector as movement-file generator."
  },
  {
    "id": "accountant-export-009",
    "title": "is_accountant column exists in migration but not in Drizzle schema or invite flow",
    "severity": "P1",
    "area": "db",
    "what_spec_says": "ALTER tenant_memberships ADD is_accountant; set true when business invites Accountant.",
    "what_code_does": "Migration 0041 adds is_accountant; tenantMemberships Drizzle table has no isAccountant field; acceptInvitationExistingUser inserts userId/tenantId/roleId only.",
    "fix_direction": "code_to_spec",
    "evidence": "packages/db/migrations/0041_accountant_export.sql:43; packages/db/src/schema/rbac.ts:58-85; packages/db/src/queries/auth-writes.ts:374-386",
    "notes": "Zero application references to is_accountant outside migration SQL."
  },
  {
    "id": "accountant-export-010",
    "title": "Accountant invite Task 9 incomplete — no is_accountant grant or PII scoping",
    "severity": "P1",
    "area": "auth",
    "what_spec_says": "Invite Accountant via /settings/users; scoped read-only financial access; no customer PII beyond billing identity.",
    "what_code_does": "Generic invite flow accepts any roleId with permission-subset check; no Accountant-specific is_accountant flag; no middleware stripping non-billing customer PII for accountant sessions.",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-api/src/routes/auth/invite.ts:79-162; apps/zync-app/src/pages/settings/integrations/AccountingPage.tsx:231-236",
    "notes": "Accountant role appears via dynamic roles API if seeded, but scoped grant flag and data scoping absent."
  },
  {
    "id": "accountant-export-011",
    "title": "B110 chart-of-accounts records never emitted when ledger enabled",
    "severity": "P1",
    "area": "integration",
    "what_spec_says": "uniform-format-export: B110 account records from tenant chart of accounts when accountant-export ledger enabled (coa_mappings present); B100/B110 counts in INI.txt.",
    "what_code_does": "uniform-format generate.ts step 6 emits B100 from deriveMovements when hasCoa; counts['B110'] initialized to 0 and never incremented; no buildB110 anywhere in repo.",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-api/src/reports/uniform-format/generate.ts:108-115,253-277",
    "notes": "Journal-enabled uniform export missing CoA account records."
  },
  {
    "id": "accountant-export-012",
    "title": "No automated tests for deriveMovements, generators, or accountant routes",
    "severity": "P1",
    "area": "api",
    "what_spec_says": "Plan acceptance: balanced movements, CP1255 movement file, Form 6111 rows, permission gates on export routes.",
    "what_code_does": "grep across **/*test* finds zero matches for deriveMovements, movement-file, form6111, or accountant export routes.",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-api/src/reports/movements.ts; apps/zync-api/src/routes/reports/accountant-exports.ts",
    "notes": "P0 ledger bugs would not be caught by CI."
  },
  {
    "id": "accountant-export-013",
    "title": "Chart-of-accounts UI edits mappings only — no account or form6111_code editor",
    "severity": "P1",
    "area": "client",
    "what_spec_says": "UI Chart of accounts: editable code/name/type/form6111_code and category→account mapping; 6111 mapped-count warning.",
    "what_code_does": "ChartOfAccountsEditor loads/saves coa_mappings only via useCoaMappings/useSaveCoaMappings; no PUT /api/coa/accounts UI; AccountingPage shows account count without 6111 mapped ratio.",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-app/src/pages/settings/integrations/ChartOfAccountsEditor.tsx:43-85; apps/zync-app/src/pages/settings/integrations/AccountingPage.tsx:215-217",
    "notes": "useSaveCoaAccounts hook exists but is unused in UI."
  },
  {
    "id": "accountant-export-014",
    "title": "Recent-exports list never fetches per-job downloadUrl — download link absent",
    "severity": "P1",
    "area": "client",
    "what_spec_says": "Recent exports list with download affordance; GET /api/reports/accountant/exports/:id returns signed url when done.",
    "what_code_does": "useAccountantExports calls GET /exports list only; ExportJobRow renders download link only when job.downloadUrl set, which list response omits; useAccountantExportJob poll hook unused in AccountingPage.",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-app/src/hooks/useAccountantExports.ts:24-28,72-77; apps/zync-app/src/pages/settings/integrations/AccountingPage.tsx:289-297",
    "notes": "Done jobs show status but no working download without manual per-id fetch."
  },
  {
    "id": "accountant-export-015",
    "title": "POST movement omits required format parameter and UI has no format selector",
    "severity": "P1",
    "area": "api",
    "what_spec_says": "POST /api/reports/accountant/movement body { periodFrom, periodTo, format: 'hashavshevet' }; UI Format [Hashavshevet ▾].",
    "what_code_does": "movementJobSchema validates periodFrom/periodTo only; AccountingPage has quarter presets but no format Select or format field in POST body.",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-api/src/routes/reports/accountant-exports.ts:63-66; apps/zync-app/src/pages/settings/integrations/AccountingPage.tsx:139-151",
    "notes": "Server cannot validate or support alternate movement formats."
  },
  {
    "id": "accountant-export-016",
    "title": "Expense movements include all COMPLETED rows — no approval_status filter",
    "severity": "P2",
    "area": "integration",
    "what_spec_says": "Financial-statements / Form 6111 expense sources use COMPLETED expenses with approval workflow (approved/not_required).",
    "what_code_does": "deriveMovements filters eq(expenses.status,'COMPLETED') only; ignores approvalStatus pending/rejected.",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-api/src/reports/movements.ts:109; packages/db/src/schema/expenses.ts:115",
    "notes": "Unapproved expenses may appear in movement file."
  },
  {
    "id": "accountant-export-017",
    "title": "Form 6111 ignores tenant coa_accounts.form6111_code mapping",
    "severity": "P2",
    "area": "api",
    "what_spec_says": "Group P&L into 6111 fields via coa_accounts.form6111_code with supporting category breakdown for reconciliation.",
    "what_code_does": "generateForm6111 uses hardcoded categoryForm6111 map; never calls listCoaAccounts; tenant CoA edits do not affect output.",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-api/src/reports/form6111.ts:149-167",
    "notes": "Duplicates finding 005 at mapping layer; tenant chart is bypassed."
  },
  {
    "id": "accountant-export-018",
    "title": "Form 6111 missing tax-adjustment section stub",
    "severity": "P2",
    "area": "api",
    "what_spec_says": "v1 emits P&L section (1000–3999) plus tax-adjustment section stub for accountant to complete.",
    "what_code_does": "Workbook ends after net_profit summary row; no empty adjustment rows appended.",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-api/src/reports/form6111.ts:186-196",
    "notes": "Accountant must add adjustments outside Zync export."
  },
  {
    "id": "accountant-export-019",
    "title": "Expired export download refreshes presigned URL instead of HTTP 410",
    "severity": "P2",
    "area": "api",
    "what_spec_says": "GET /api/reports/accountant/exports/:id returns 410 when download_expires_at is past.",
    "what_code_does": "When expired, route issues new presign URL and updateAccountantExportJobDownloadExpiry instead of 410 response.",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-api/src/routes/reports/accountant-exports.ts:201-210",
    "notes": "TTL semantics differ from plan Task 8 acceptance."
  },
  {
    "id": "accountant-export-020",
    "title": "Export job creation and audit are not wrapped in one transaction",
    "severity": "P2",
    "area": "api",
    "what_spec_says": "Plan Task 8: wrap job creation + generation kickoff in transaction recording audit entry (require-audit-in-transaction).",
    "what_code_does": "createAccountantExportJob then void logAuditEvent fire-and-forget; no db.transaction linking insert and audit.",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-api/src/routes/reports/accountant-exports.ts:99-122",
    "notes": "Audit row may be missing if logAuditEvent fails after job insert."
  },
  {
    "id": "accountant-export-021",
    "title": "Large-period movement export runs inline — export.generate queue not wired",
    "severity": "P2",
    "area": "api",
    "what_spec_says": "Plan Task 5: route large ranges through export.generate queue to stay within Worker memory/CPU limits.",
    "what_code_does": "POST /movement fires void generateMovementFile inline with comment only; no queue consumer for accountant exports.",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-api/src/routes/reports/accountant-exports.ts:121-122",
    "notes": "Multi-year movement generation may hit Worker limits."
  },
  {
    "id": "accountant-export-022",
    "title": "Accounting UI missing 6111 mapped warning, custom period, and invited-accountant display",
    "severity": "P2",
    "area": "client",
    "what_spec_says": "Wireframe: custom period, '6111 codes mapped: 21/23', accountant@cpa.co.il invited date in Your accountant block.",
    "what_code_does": "AccountingPage shows preset QUARTER_OPTIONS only, account count without mapped ratio, generic invite link to /settings/users without listing accountant member.",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-app/src/pages/settings/integrations/AccountingPage.tsx:38-44,215-237",
    "notes": "Core page shell exists but wireframe details missing."
  },
  {
    "id": "accountant-export-023",
    "title": "Accounting page does not enforce reports:read guard",
    "severity": "P2",
    "area": "client",
    "what_spec_says": "Page visible to sessions with reports:read; generate actions require accountant:export.",
    "what_code_does": "AccountingPage checks sessionLoading and accountant:export for buttons only; no deny/redirect when reports:read absent (unlike ExpenseReportsPage pattern).",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-app/src/pages/settings/integrations/AccountingPage.tsx:79-116; apps/zync-app/src/modules/expense-reports/ExpenseReportsPage.tsx:86",
    "notes": "Route registered in settings module without permission wrapper."
  },
  {
    "id": "accountant-export-024",
    "title": "ACCOUNTANT role grants reports:export_external beyond spec permission triple",
    "severity": "P2",
    "area": "auth",
    "what_spec_says": "Accountant role permissions: reports:read, reports:export, accountant:export only.",
    "what_code_does": "SYSTEM_ROLE_PERMISSIONS.ACCOUNTANT includes reports:export_external as fourth permission.",
    "fix_direction": "code_to_spec",
    "evidence": "packages/db/src/seed/permission-keys.ts:83",
    "notes": "May allow external email exfiltration paths gated by that permission."
  },
  {
    "id": "accountant-export-025",
    "title": "Generate flow does not poll job status until done",
    "severity": "P2",
    "area": "client",
    "what_spec_says": "Plan Task 13: poll GET /exports/:id until done then surface download from signed URL.",
    "what_code_does": "onSuccess uses setTimeout(refetchExports, 2000); useAccountantExportJob with refetchInterval exists but is not used after generate.",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-app/src/pages/settings/integrations/AccountingPage.tsx:96-107; apps/zync-app/src/hooks/useAccountantExports.ts:80-91",
    "notes": "User must manually refresh exports list."
  },
  {
    "id": "accountant-export-026",
    "title": "Integration hub catalog omits Accountant export entry",
    "severity": "P2",
    "area": "client",
    "what_spec_says": "integration-hub lists Accountant export (Hashavshevet / Form 6111) at /settings/integrations/accounting.",
    "what_code_does": "INTEGRATION_CATALOG accounting category has invoice adapters only (morning, icount, rivhit, etc.); no Hashavshevet/6111 accountant-export card.",
    "fix_direction": "code_to_spec",
    "evidence": "apps/zync-app/src/features/settings/integrations/catalog.ts:93-148; docs/specs/2026-05-31-integration-hub.md:120",
    "notes": "Route exists but not discoverable from integration hub manifest."
  },
  {
    "id": "accountant-export-027",
    "title": "Movement type shape differs from spec/plan (paired accounts vs side/counter_account/vat_code)",
    "severity": "P2",
    "area": "integration",
    "what_spec_says": "Spec overview: each movement carries account code, debit/credit side, counter-account, VAT code; Plan Task 3 Movement interface lists side, counter_account, vat_code.",
    "what_code_does": "packages/types Movement uses debitAccount/creditAccount/amountIls pair per row; no vatCode, side, or counterAccount fields.",
    "fix_direction": "spec_review",
    "evidence": "packages/types/src/movement.ts:8-25; docs/specs/2026-06-01-accountant-export.md:34",
    "notes": "B100 uniform-format records also use paired accounts on one line — orchestrator should decide if paired model satisfies spec intent."
  }
]
