Skip to content

Design the trusted-list refresh: what a pass is, when it runs, and what a failure leaves behind #345

Description

@LKSNDRTMLKV

Decision 4 of #324. The storage landed in #343 and nothing fills it, which is what this decides.

The direction was agreed — per-list, driven by each list's own NextUpdate, never a global sweep, and a failed refresh leaves the previous good copy in place. put in #343 is already shaped for that. What is not settled is the part below.

What a pass is

A refresh is not one operation. It is:

  1. fetch the LOTL and verify it against the pinned anchor;
  2. filter to national pointers — national_pointers already drops the self-pointer and the PDF variants several Member States publish beside their XML;
  3. for each pointer, fetch and verify, and write verified or unavailable.

Step 1 failing is categorically different from step 3 failing for one territory: without a verified LOTL there is no authorised certificate list for any national list, so nothing in step 3 can be checked at all. A failed LOTL fetch must not mark 27 territories unavailable — that would turn one outage into a cache that says the Union cannot be checked, and put cannot prevent it because the caller decides.

Open questions

Cadence. Lists carry NextUpdate and the LOTL does too. Per-list scheduling is more faithful and needs per-list timers or a wakeup that scans due rows. A single periodic tick that refreshes whatever is due is far simpler and is what spawn_scan_prune / spawn_idempotency_purge / spawn_job_cleanup already do. The tick is probably right; the due-check needs NextUpdate persisted, which #343 does not store — it was left out deliberately rather than guessed, and this is where it gets decided and added.

Concurrency and politeness. 27+ documents, several MB each, from 27 Member State servers. Sequential is slow and kind; parallel is fast and looks like a small crawl. The guarded outbound path (dpp_common::outbound) is mandatory either way — the LOTL names the URLs, and until it is verified those URLs are attacker-influenceable, which is what fetch_lotl_pointers parsing before verifying already means.

First fill on a fresh node. A node booting with an empty cache has 27 fetches to make and cannot block boot on them. This is the same question as #344 from the other side.

Removal. A territory the LOTL stops naming should eventually leave the cache. #343 grants no DELETE on odal.trusted_list_cache deliberately — adding it means adding the row to ops/pg/README.md's DELETE set, which just grants-check gates. Decide whether stale rows are deleted or simply ignored; ignoring them is safer and leaks rows slowly.

Failure taxonomy. unavailable_reason is free text, and #343 argues that is right because what stops a list being read is not enumerable. But the retry behaviour differs sharply: a fetch timeout should be retried soon, a signature that does not verify should not be hammered, and a document over a parser ceiling (#320, Germany) will never succeed until the parser changes. If retry policy needs to distinguish them, the reason needs structure beside the prose.

The anchor interaction

A refresher is the only thing positioned to act on #321's anchor-freshness signal, and it is also the thing that breaks first when the pin goes stale: the LOTL stops verifying, step 1 fails, and every national list goes unrefreshed. The current signing certificate expires 2027-11-17. See #346.

Not blocking

Nothing here can produce a qualified seal, so an unfilled cache costs nothing today. It becomes the blocker the moment a QTSP credential exists.

Related: #324 · #343 (storage) · #344 (what is reported meanwhile) · #345 (anchor) · #320 (a territory that will not succeed)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    surface/sealMoves sealing, trusted lists or trust-service procurementtype/decisionNeeds a call; analysis alone will not close iturgency/backlogOwed, unscheduled

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions