VeriFactu Software · how to connect legacy software to VeriFactu

How to connect legacy software to VeriFactu without rewriting it: Local Agent architecture

Local Agent covers systems that cannot use a native plugin or server-to-server API. It extracts data through read-only databases, watch folders or SFTP, turns it into durable jobs and sends it over HTTPS to the same MappingProfile, preflight and idempotency pipeline used by Kairoseth Fiscal. The agent receives no AEAT certificate or fiscal authority, and issuance is disabled by default.

Install Local Agent next to the legacy system and keep `issueEnabled=false` during diagnosis. Run `doctor` to check `/readyz`, SQLite and read-only source capability without calling preflight or issue. Select database, watch-folder or SFTP. Database mode requires a verified driver, one SELECT query, a stable cursor and SELECT-only credentials. Watch-folder identifies CSV/XLSX by hash and creates row jobs. SFTP is read-only, pins the host key and never modifies the remote source. Every job reaches the server as InvoiceIntent or mapped-source, runs through preflight and retains a stable Idempotency-Key. Leases, checkpoints and receipts support recovery after restarts. Certificates and tenant remain server-side and AEAT production is enabled through a separate gate.

KairosethOctober 7, 202610 min readUpdated · October 7, 2026Versión en español

The challenge with a legacy system is not merely its age: it often lives inside a local network, depends on a database nobody wants to modify, exports files on a schedule or runs on a PC without a modern integration surface. The solution should not begin by opening inbound ports or copying a fiscal certificate to every installation. Local Agent v1 creates a deliberately small local boundary. It keeps a durable SQLite queue, observes a source under read-only or non-destructive rules, applies checkpoints and stable identity, and communicates with Puente VeriFactu using outbound HTTPS. The server retains MappingProfile, tenant, certificates and the fiscal lifecycle. An ERP, POS or desktop program can therefore remain the source of commercial facts without becoming the AEAT implementation. This guide explains what each adapter does, how recovery works and which evidence exists before production is considered.

1. When to use Local Agent and when not to complicate the integration

Local Agent exists for ERP, POS, desktop applications and local-network systems that cannot reliably integrate through a native plugin or direct API. It is not automatically the first choice. If the product already provides a secure server-to-server API, that route is usually simpler. If a small volume can be handled through a controlled manual CSV/XLSX process, installing a local service may also be unnecessary. The agent adds value when extraction must be automated, survive restarts and operate close to a source that only exists inside the customer network.

The right decision comes from diagnosis. Identify which event means invoice, its stable key, the source of number/date, which tables or files contain lines and taxes and which access level is available. Choose the adapter afterwards. This order prevents designing technology before knowing whether the source can provide consistent facts. It also makes Local Agent a bridge rather than a rewrite: the legacy application keeps running while integration observes or consumes a controlled surface.

2. The security boundary: outbound-only with fiscal secrets on the server

The foundation opens no inbound HTTP port. createLocalAgentApiClient rejects non-HTTPS remote destinations; insecure HTTP is allowed only for explicit localhost development. In production, the customer does not need to expose a service from its network or configure an Internet-initiated connection. The agent initiates communication toward Kairoseth/Puente. This pattern fits small networks where changing firewall, router or corporate policies may be riskier than installing a local process with outbound HTTPS.

The manifest requires `secrets_server_side=true`, `aeat_certificate_server_side=true` and `tenant_server_authoritative=true`. No PFX, passphrase, private key or signing material is copied to the agent. Local configuration does not accept those as valid operational fields. The agent needs its bridge credential and source secrets when relevant, such as a database or SFTP password referenced through environment variables. The separation limits impact if the local machine is compromised: reading data does not grant authority to sign or switch tenant.

3. Local SQLite turns fragile extraction into a durable queue

Reading a row and sending it immediately creates a strong dependency on network availability. If Kairoseth does not respond or the PC restarts, it becomes difficult to know what was read, what was attempted and which identity belongs to each retry. Local Agent first inserts operations into SQLite. The file is protected with mode 0600 on POSIX and preserves jobs, checkpoints and receipts. The worker takes leases, processes with bounded backoff and marks non-retryable failures blocked. Source and transport are no longer coupled through a single memoryless call.

The local Idempotency-Key is derived from `sourceId + sourceKey`. That forces the adapter to define stable source identity: database cursor, row/fingerprint, event or equivalent combination. A crash after a lost response can retry using the same key. The server also maintains fiscal idempotency, so the local queue does not replace the core. This layered design recovers failures between extraction, HTTP and result persistence without inventing new identities on every process run.

4. How read-only PostgreSQL, MySQL/MariaDB and SQL Server extraction works

`pollDatabaseSource()` provides one shared path without embedding fiscal behavior in database drivers. Configuration defines sourceId, profileId, cursorField and a query. The driver must verify as read-only, the query must be exactly one SELECT statement and page size is bounded to 1,000 rows. Every result becomes a `mapped-source` job and is inserted into the queue before the checkpoint advances. If the process crashes in between, re-reading the row produces the same sourceKey and therefore the same idempotent identity.

Vendor drivers add engine-specific evidence. PostgreSQL uses a `pg` pool and performs verification/extraction inside `BEGIN READ ONLY`. MySQL/MariaDB uses `mysql2/promise` and `START TRANSACTION READ ONLY`. SQL Server uses `mssql`, probes permissions/role and preserves the SELECT-only contract. Production should create a limited user even though code rejects write-capable SQL. CI exercises PostgreSQL 16, MySQL 8.4, MariaDB 11.4 and SQL Server 2022, which is the matrix that can currently be claimed as tested.

5. Checkpoints represent extraction progress, not fiscal state

A database cursor or file receipt answers a local question: how far have we read from the source? It does not answer whether an invoice was accepted, rejected or remains pending in the fiscal engine. Local Agent explicitly documents this separation. The checkpoint advances only after the durable job has been inserted. From there, queue and server handle issuance and reconciliation. Confusing checkpoints with fiscal state would skip rows or mark an operation accepted merely because it was extracted.

The distinction also improves support. If a row never reaches the queue, investigate source and checkpoint. If it is blocked in the queue, inspect preflight or transport error. If recordId exists, reconcile on the server. Each phase preserves its own evidence instead of trying to replace the next one. In legacy systems where original observability is often weak, this segmentation creates a clearer journey without writing metadata back into the customer's database.

6. Watch-folder stabilizes, identifies and processes each file revision

Software that exports CSV or XLSX can integrate without database access. `scanWatchFolder()` detects allowed extensions and computes streaming SHA-256. Filename plus hash forms revision identity. An identical file is not queued again; changed content becomes a new revision. The pipeline moves a stable file from inbox to processing, parses it with the existing file connector and creates independent row jobs, each using server-side MappingProfile and preflight.

After all rows finish, the batch moves to processed or error. Terminal jobs automatically redact payload_json and response bodies while retaining sourceId, sourceKey, fingerprint, Idempotency-Key, state and recordId. This reduces raw-data retention without losing enough evidence to reject conflicting replays. The archived source file follows an operator-controlled retention policy; default behavior is keep and automatic deletion requires explicit day limits plus valid recognized metadata.

7. SFTP extends watch-folder to a remote source without mutating the customer's server

The SFTP adapter exists for systems able to deposit files on a managed server but unable to call the API. Host identity is pinned with a SHA-256 fingerprint. Password and private-key authentication are supported, while those secrets remain in runtime configuration rather than queue payloads or manifests. Only regular CSV/XLSX files within age and size bounds are processed. Downloaded bytes are checked against listed size and content hash before being admitted to the local inbox.

The route is deliberately non-destructive. It uses `list()` and `get()` and never uploads, renames or deletes the remote file. Each directory, filename, size and modification-time identity has a durable SQLite receipt that prevents repeated staging after restarts. CI starts a real SFTP server, pins a generated Ed25519 host key, proves uploads are rejected, downloads a CSV and confirms a second poll is idempotent. That test proves transport behavior; fiscal idempotency still belongs to jobs and the server.

8. doctor, status, once and start separate diagnosis from execution

The unified runtime can operate watch-folder, database or SFTP sources using the same process. `doctor` checks readyz, local store and source read-only capability without running preflight or issue. `status` reads local operational metadata only and does not require runtime secrets. `once` runs a single cycle while `start` keeps the continuous loop. Both execution commands use a single-instance lock to prevent two processes from working on the same data directory concurrently.

This interface improves onboarding. An installer can deploy code and configuration, run doctor and deliver a verifiable result before issuance is enabled. JSON schema v1 configuration must be private; POSIX requires mode 0600. Bridge API key, database password and SFTP credentials are referenced through environment-variable names rather than written inline. AEAT certificate and private key are not accepted options. Installation can remain fully prepared in fail-closed mode until a later decision.

9. The portable bundle and native installers make the platform matrix explicit

The Local Agent artifact is a reproducible JavaScript ZIP containing a 40-character source commit, manifest, content fingerprint and SHA-256 sidecar. The exact same download is tested on native Linux x64, Windows x64, macOS Intel x64 and macOS ARM64 runners. Each smoke installs exact dependencies and runs status plus doctor with issueEnabled=false. Claimed platform compatibility is therefore based on the same ZIP that is distributed rather than different per-platform builds.

Startup integration uses native facilities: systemd on Linux, launchd on macOS and Task Scheduler on Windows. Code, configuration and data live in separate locations. Reinstall preserves configuration unless explicitly replaced; uninstall removes startup/code while keeping secrets and local state. Windows hardens configuration/data ACLs and POSIX sensitive files use 0600. This model improves support without hiding a third-party service wrapper inside the runtime.

10. Upgrade validates state and backup; rollback is code-only by design

Before new code is activated, the guard verifies manifest SHA-256 and bytes, the global content fingerprint, supported state-schema range, compatible Node version, data-directory lock and SQLite quick_check. When agent.sqlite exists, it creates a verified backup and compares job/checkpoint/receipt counts. Only then does it switch the active release. This prevents upgrades from misreading durable state and avoids replacing code while another process is using the queue.

Rollback changes the `current` symlink/junction back to the previous release and deliberately does not restore SQLite. The backup remains evidence/recovery material for an explicit operator decision because automatic restoration could delete jobs created after upgrade. This distinction matters in a fiscal bridge: returning to older code and returning to older state are different operations. The procedure favors preserving identity and history over destructive automatic restoration.

11. Final activation remains separate from local deployment

`issueEnabled=false` is the fail-closed starting point for reference configuration. Installation, diagnosis, read-only access checks, file inspection and MappingProfile validation can therefore happen without creating fiscal records. Technical preparation remains reversible: if a SQL view contains the wrong date or a file lacks a column, it is corrected before enablement. Even bundle smoke tests keep issuance disabled so platform testing cannot be confused with fiscal issuance.

Once local integration is ready, the AEAT production gate still remains. Certificate, identity, fiscal configuration, evidence, current regulation and explicit authorization are checked separately. Local Agent receives none of those signing secrets and cannot grant itself permissions. A green CI run is not official certification either. Its value is proving that local transport, recovery and isolation satisfy the tested contract; the final fiscal decision remains a production control.

KEY TAKEAWAYS

What to remember

  • A system without an API can use read-only databases, watch-folder or SFTP without turning Local Agent into a fiscal engine.
  • SQLite queue, leases, checkpoints and sourceId/sourceKey make extraction and retries durable.
  • Production database access requires SELECT-only users; code validation is defense in depth.
  • SFTP is non-destructive and watch-folder uses content fingerprints to identify revisions.
  • Installation and doctor can be completed with issueEnabled=false; AEAT production remains behind a separate gate.

SOURCES AND TRACEABILITY

Sources used

product-evidenceLocal Agent v1

Kairoseth / Puente VeriFactu

product-evidenceUniversal Integration Matrix

Kairoseth / Puente VeriFactu

product-evidenceAPI v1 contract

Kairoseth / Puente VeriFactu

officialSpanish Tax Agency — invoicing software systems and VERI*FACTU

Agencia Estatal de Administración Tributaria

EXTERNAL RESOURCES

Official Spanish Tax Agency SIF / VERI*FACTU portal

Official technical information and regulatory references.

INTERNAL LINKS

NEXT STEP

VeriFactu Software

VeriFactu software for connecting WooCommerce, PrestaShop, ERP, CRM, Excel, custom software and local systems to Spain's VeriFactu framework without replacing the source system.