Proposed. It answers the open questions of
0367
(OQ1 and the multi-sheet question) and adds the payroll export of STA-714. Linear STA-711,
STA-714.
Context
ADR 0367 shipped one-sheet imports, CSV and XLSX, and only the memory ledger. Products import workbooks whose sheets feed several models that depend on each other (products and variants; grocery items, PLU, barcodes and scale layouts, R3-SO-19, GRO-207). OQ1 asked whether JSON and JSON Lines are formats here or only the full-tenant export’s. The OS data-exchange service needs a ledger contract it can implement durably, with the before-images gone when the undo window closes. Figentra runs no payroll (RV-50), so pay inputs leave as files for every provider without a write API.Decision
- One run, several sheets, several targets.
ImportRunner.runSheetsimports the sheets a workbook import names, in the order their targets depend on each other; a sheet not named is read past. A batch never spans two sheets and records its sheet, so each sheet resumes after its own last batch and each batch reverts through its own target, newest first (children before parents). A file with the named sheets out of order is refused before any write. A mapping profile (IMPORT_MAPPING_PROFILE_SCHEMA: sheet, target name, column mapping) is seed data per product, andsheetImportsOfturns it into the run’s sheets. - JSON Lines yes, plain JSON no. A JSON Lines reader and writer are
TabularReaderandTabularWriterdrivers: the first object’s keys are the header, each line is its own row number, and a later key the header lacks is refused. Plain JSON (one array) cannot be streamed and stays on the full-tenant export path. - The durable ledger contract. Every
ImportLedgerdriver returns copies;appendis atomic and refuses an out-of-order batch; batches come back in append order; before-images are stored under the data class of the records they copy and are never logged;purge(settledBefore, at)empties the before-images of every run settled before the cutoff, keeps each batch’s index, sheet, cursor and errors, and stampspurgedAt. A purged run cannot be reverted. The caller runspurgeon a schedule withsettledBefore = now − undo window(a setting). - Payroll exports from profiles. A
PayrollExportProfilenames the provider, the format (csv,xlsx,datev-ascii), the columns and what fills each, the pay-item-to-wage-code map, the number and date formats and, for DATEV, the[Allgemein]entries and the record description.payrollSheetturns pay inputs into the sheet; an input whose pay item has no code is refused, never dropped.DatevAsciiWriterDriverwrites the LODAS movement-data file in ISO 8859-1 and refuses a character it cannot hold. The launch profiles (generic CSV, DATEV LODAS, ZenHR XLSX) ship as settings records inassets/data/payroll-export-profiles/; the tenant’s DATEV numbers and wage codes are tenant settings. - XLSX behind
./node. The exceljs drivers move to the./nodeentry, loaded throughcreateRequirebecause exceljs is CommonJS with named-export typings only, so.stays browser-safe.
Consequences
- A grocery or catalogue workbook imports in one run and reverts as one.
- The OS data-exchange service implements one written contract; the memory driver is its reference.
- Payroll providers are data: a new provider is a profile, not a release.
Enforcement
import-runner.service.test.ts (sheet order, per-sheet resume, revert targets),
memory-import-ledger.driver.test.ts (purge), json-lines-*.driver.test.ts, payroll-sheet.function.test.ts,
datev-ascii-writer.driver.test.ts and payroll-export-profiles.test.ts in packages/data/data-transfer. Planned —
the OS data-exchange durable driver runs the same contract tests.
Supersedes and sources
Supersedes: none. Answers 0367 OQ1. Sources: owner questionnaire item 22; R3-SO-05, R3-SO-19, GRO-207; RV-50;notes/handover/payroll-providers-2026-10-26.md.