Docs Index
reconciliation center.md

Proof Card: Reconciliation Center

Status: production Scope: API surface + NetSuite↔BC cadence dispatch (real schedule claim + run-lifecycle + comparison engine; connectors mocked in tests, live fetch gated on integration config; embedded operator UI shipped (guest-context iframe surface); full schedule CRUD (create/list/update/delete) API with config-content validation at creation/update; integration_config_id is DB NOT NULL (migration 056); cross-surface embedded-CSP static-shadowing fix shipped) Last verified: 2026-05-29

Claim

ReconciliationCenterService (src/services/reconciliationCenter/ReconciliationCenterService.ts) persists tenant-scoped reconciliation exceptions in reconciliation_exceptions (migration 048) and exposes an operator API surface via GET /api/reconciliation-center/exceptions. Resolution is recorded with actor attribution via POST /api/reconciliation-center/exceptions/:id/resolve: the row's resolved_by column stores the resolving user (sourced from req.user.id under the F3 strict mount), resolved_at stamps the resolution time, and resolution_note carries the operator note. A row that does not exist (wrong id) or that belongs to another tenant causes the repository to throw ReconciliationExceptionNotFoundError, which the route maps to 404 exception_not_found so silent success cannot mask operator errors. A recurring ReconciliationScheduleJob drives runDueSchedules, which reads due reconciliation_schedules, atomically claims each (a conditional UPDATE that advances next_run_at and inserts a running reconciliation_runs row in one transaction — multi-replica safe per AGENTS.md Tier-B), looks up a Reconciler by the schedule's handler_key, invokes the production connector API (ConnectorManager.getConnector(...).list('invoice')) — the same connector-consumption path as the shipped FinanceCentral operator surface, with no demo-data fallback — persists coalesced reconciliation_exceptions, and marks the run completed/failed. v1 ships one handler — netsuite_business_central_invoice_reconciliation — reconciling invoice amounts between the two production ERP connectors (NetSuite ↔ Business Central), resolved by their registry systemType keys (netsuite / businesscentral). Amounts are compared in integer minor units (ISO-4217 exponent map, 2-decimal default); a fetched invoice missing its key/amount/currency field fails the run rather than inventing a delta. What this PR proves: the dispatch lifecycle (claim → run-row writes → coalesced exceptions → complete/fail) and the comparison engine are real and exercised end-to-end with the connectors mocked. What it does NOT prove: a credentialed live fetch — see Known Gaps (connector initialization is the tenant-integration lifecycle's job, shared with all connector consumers; an unconfigured connector makes the run fail cleanly, never fabricate data).

Source

Tests

Live vs Fixture

Known Gaps

Verification (60-second AI-reviewer recipe)

npm test -- tests/unit/database/migrations/048-create-reconciliation-center-tables.test.ts
npm test -- tests/unit/services/reconciliationCenter/
npm test -- tests/unit/routes/reconciliationCenterRoutes.test.ts
grep -n "reconciliation_exceptions\|CHECK (severity" src/database/migrations/048-create-reconciliation-center-tables.ts
grep -n "jwtOperatorIdentity\|identity_required" src/routes/reconciliationCenterRoutes.ts
# curl -H "Authorization: Bearer <jwt>" http://localhost:3003/api/reconciliation-center/exceptions