feat(ownership): idempotent, packaged snapshot publisher

The self-hosted publish timer has not produced an upload since it was
enabled (manifest last updated 2026-07-14, a manual run), and failures
were invisible. Make the job safe to run daily and easy to deploy:

- publish-ownership-snapshot.sh: skip the upload when the published
  manifest already has the latest as-of date (pre-check via discover
  for XLSX sources, post-build check for legacy PDF); --force to
  override. Always ends with one line: RESULT: published|up-to-date
  <as-of> or RESULT: FAILED stage=<stage> (exit N). gh release create
  no longer requires a git checkout.
- check-ownership-snapshot-freshness.sh: exit 1 when the published
  latest_as_of_date is older than --max-age-days (default 40).
- flake: packages.ownership-publisher bundles the scripts with a
  pinned idx (curl-impersonate + mupdf) and jq/gh/sqlite/curl as
  bin/idx-ownership-publish and bin/idx-ownership-freshness, so hosts
  need no checkout, nix develop or cargo build at run time. Built in CI.
- contrib/systemd + docs: daily 06:00 UTC timer, run semantics table,
  freshness alarm, packaged usage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017a5Cv5Z1NzdYKgKnTiAF4E
This commit is contained in:
hermes 2026-09-24 21:40:02 +00:00
commit 1f6f0ee02e
7 changed files with 237 additions and 44 deletions

View file

@ -5,7 +5,8 @@ After=network-online.target
[Service]
Type=oneshot
WorkingDirectory=/srv/idx-cli
Environment=HOME=/var/lib/idx-ownership-snapshot
EnvironmentFile=/etc/idx-ownership-snapshot.env
ExecStart=/bin/sh -lc 'nix develop --command scripts/publish-ownership-snapshot.sh --build --idx-bin ./target/debug/idx --output-dir /var/lib/idx-ownership-snapshot/current --repo 0xrsydn/idx-cli --release-tag ownership-snapshot-current'
# Build once with `nix build github:0xrsydn/idx-cli#ownership-publisher -o /opt/idx-ownership-publisher`.
# Idempotent: exits 0 with "RESULT: up-to-date" when nothing newer exists.
ExecStart=/opt/idx-ownership-publisher/bin/idx-ownership-publish --output-dir /var/lib/idx-ownership-snapshot/current --repo 0xrsydn/idx-cli --release-tag ownership-snapshot-current --history 5

View file

@ -1,8 +1,10 @@
[Unit]
Description=Run monthly idx ownership snapshot publish job
Description=Daily idx ownership snapshot publish check
[Timer]
OnCalendar=*-*-02 09:00:00
# The publisher is idempotent, so a daily run retries automatically when IDX
# posts late or a run is blocked; most days end with "RESULT: up-to-date".
OnCalendar=*-*-* 06:00:00 UTC
Persistent=true
RandomizedDelaySec=30m
Unit=idx-ownership-snapshot-publish.service