Support delta downloads on the offline malware advisory path

The offline malware advisory path only ever downloads whole snapshots. The script added in !250093 (merged) calls /v1/malware/advisories/all and writes v3/<registry_id>/full_dataset/, so every time the service publishes a new snapshot the admin pulls the whole thing again. PDS already serves /v1/malware/advisories/delta and the online connector uses it, but there's no way to consume a delta offline.

MalwareOffline gets in the way twice. It only globs inside full_dataset/ (malware_offline.rb#L58-71):

def shard_files
  Dir.glob('*.tar.zst', base: full_dataset_dir).sort
end

def full_dataset_dir
  File.join(file_prefix, FULL_DATASET_DIR)
end

and it works out the checkpoint chunk by reading the filename as base 16 (#L33), so a delta archive named for its unix timestamp comes out with a garbage chunk:

chunk = shard_file.delete_suffix('.tar.zst').to_i(16)

Proposal

On the Rails side, read v3/<registry_id>/deltas/<unix_seconds>.tar.zst next to full_dataset/, order those files by the decimal timestamp in the name, and move pm_checkpoints.sequence forward once per delta that ingests cleanly. full_dataset/ stays the first sync and the catch-up path.

In the docs, give the download script an input to pick either a whole snapshot or the deltas since the local checkpoint.

Paging

/delta returns at most DELTA_URL_LIMIT URLs per registry per call, 20 by default, and PDS hands back the oldest first so a client can page forward without gaps (handlers.go#L78-84):

// Timestamps arrive newest-first,
// so trimming from the front keeps the OLDEST n items and lets incremental
// clients paginate forward without gaps.
func OldestN(timestamps []string, n int) []string {

So the script would need a paging loop, and an instance that has been off for a month would make a lot of round trips. Add #608196 to that, where one delta can grow to the size of a whole snapshot with no shard dimension and no resume, and I think we would still want a snapshot fallback rather than deltas alone.

The script already skips the download when the service's until matches the local checkpoint.json, so the recurring cost today is one metadata call per registry. This is about the case where the snapshot has actually moved.

Relates to

Licenses

The offline licenses path has the same gap, raised in review of !252827 (thread). The download procedure documented there pulls a whole snapshot per run. Both blockers above are in the same reader, which !251674 (merged) made dataset-neutral, so the fix covers licenses and malware advisories together. The docs change applies to both scripts.

Edited by 🤖 GitLab Bot 🤖