A change only counts once it is secured
RegMon checks the regulatory sources of German health IT for changes every day. In September we asked what such a system can promise a customer who is subject to audits — and we began by examining our own capture path the way an auditor would. The result is a promise that can be verified, and a product version that honours it. This post describes the way there, including what the examination found.
The examination first
Before making a promise, you check whether you can keep it. So in September we went through RegMon's entire capture path the way an auditor would: source by source, from retrieval through change detection to the record in the ledger, with the audit trail of our reference instance since June as evidence.
For all the other sources, this path ran through end to end — around 2,100 events in the reference instance's ledger attest to that. For one source, a step was missing: the plugin for the twelve KBV web sections (press releases, billing, agenda, statements, announcements, the Federal Framework Agreement (Bundesmantelvertrag), resolutions, accident insurance and other payers, further legal sources, publications, videos, studies and reports). It detected changes reliably and reported them to the scheduler. In automated operation, however, it did not write the file from which the aggregator fills the ledger, and the stored state was advanced anyway. For this one source, the ledger remained without entries from 25 March onwards, while the audit trail correctly recorded that the plugin had run and had found changes.
What occupied us was less the error than its shape. Every entry in the audit trail was true, and that is exactly why nothing stood out: a record proves what it logs — and it logged the retrieval, not the arrival. It is the same gap we described in August at Xommodo, seen from the other side.
The review also found four places where a failure would have stayed silent. For none of them did it find a case that had actually occurred; all four are closed in the same version:
- A retrieval error would have looked like a quiet day. Several plugins returned an empty list on an HTTP error — indistinguishable in the audit trail from “no change”.
- The state was saved before the delta. A crash precisely in between would have marked a change as seen for good.
- The aggregator archived before it saved. An error in the middle of a run would have moved files whose events were not yet in the ledger.
- A missed run would have left no trace. Nothing checked when a source had last been read successfully.
The test suite had checked each of these steps on its own, and each one passed. What was missing was a check of the whole path from source to ledger — the perspective an auditor takes, and the one a system has to take on itself. Precisely that perspective has been built into RegMon since September.
What a monitoring system can promise
The literal promise — “we capture every relevant change” — cannot be kept by anyone who does not control the sources. An authority rebuilds its website, and the best parser reads into a void.
An auditor does not demand it either. ISO 13485 and the MDR oblige the manufacturer to identify regulatory requirements and keep them up to date; ISO 27001 has the same obligation for legal and contractual requirements; GxP data integrity, with ALCOA+, requires the record to be complete. What they share is the yardstick: a controlled process. Defined scope, defined procedure, defined frequency, a record of every execution, deviations detected and handled.
From this follows the promise RegMon is built on:
For every source in the agreed source register and for every agreed capture time, exactly one record exists. Either the published inventory was collected and every deviation from the previous inventory was durably secured as an event — or the deviation is recorded with its cause, visible to the customer, and closed by a documented catch-up.
Two separations make it hold. Capturing is not classifying: what is guaranteed is that every change to a registered source arrives. Whether it is relevant is still decided by a person, and that decision is audited — a language model prepares, it does not decide. Completeness is proven, not claimed: every statement in the record derives from records the auditor can recalculate for themselves.
What has changed
The first stage of this promise has been implemented since 15 September in version 2.2.0 and runs on the reference instance. It secures the path from detection to the ledger:
- Delta before state, in every plugin. The change is written durably first, then the state is advanced — atomically, so that even a crash in between loses nothing. A change never counts as seen before it is secured.
-
An error is an error. If one of twelve pages fails, the plugin keeps
the eleven that succeeded, carries the old state of the twelfth forward and ends the
run as a partial failure —
ok: falsein the audit trail, with the list of pages. A page that answers with HTTP 200 and zero entries although the state knows the category counts as selector drift, not as “everything removed”. - A safety net in the scheduler. If a plugin delivers events without a delta file appearing, the scheduler writes it itself and notes that in the audit entry. No plugin, including any future one, can lose events silently any more.
- Ledger before archive. The aggregator saves after every delta file and archives only afterwards. Invalid files neither block the run nor get lost: they move into quarantine, and the run ends with a visible error.
- Missed runs leave traces and are caught up. A run register keeps the last attempt and the last success per source; a missed slot is caught up within a grace period, and after a restart every source without a success in the last 26 hours runs once more.
- The self-test checks freshness. Every registered source needs a successful run within 26 hours; otherwise the system reports itself as degraded and names the source with its last status and error.
- The audit chain runs across day boundaries. Previously every daily file started afresh, and a deleted daily file was invisible. Now the first entry of a day carries the hash of the last entry of the previous day, and verification reports a missing or retroactively modified previous-day file as a break.
For the twelve KBV web sections, the first run after the update backfills the complete current inventory once, marked as a baseline — so everything the KBV publishes there today is in the ledger. What appeared and was withdrawn again between March and September is beyond the reach of any backfill; the release notes say so. The inventory is complete, the limit is named.
What follows for verification systems
- Check the whole path into the ledger. An audit trail attests to exactly what it logs; whoever logs the arrival has proven it. A test that places a change at the source and looks it up in the ledger finds a gap like this on day one.
- Every failure gets a word of its own. “No change” and “could not read” look different in the log — so a failure stays distinguishable from a quiet day and can be reported.
- Freshness is a metric to test for. “Last success per source” is the one number that makes a silently failed run visible. It belongs in every self-test.
- Secure first, then advance. Whoever writes the result durably before advancing the state has exactly two cases after any abort: the change is secured, or it is detected again on the next run: The order decides!
Limits
This stage secures the path from detection to the ledger. Detection itself currently works on title, date and file size; the content comparison that also recognises a PDF replaced under the same address as a change is the next stage. Raw responses are not yet kept as snapshots, and without an external anchor the hash chain remains blind to a rewrite of all files. Those are the next stages: a contractually fixed source register, inventory capture with content hashes, a coverage log that also attests to a run that did not take place, and a monthly signed capture report.
The order is deliberate: first, what is detected has to arrive. Only then is it worth detecting more.
The full release notes from version 2.2.0 onwards are public on GitHub.