- services/attest-reconciler/ as its own go.mod (admin-api internal/ is not importable; shared code gets extracted when a 3rd consumer appears). - Scheduler accepts "@every <dur>" or a bare Go duration; context-cancellation stops the loop cleanly. - Store client defines PendingRateStore + Postgres skeleton for SELECT ... WHERE rate_change_attestation_ref IS NULL. - Orchestrator + HTTPSigner + tlog.HTTPSubmitter mirror admin-api byte-for-byte so the TLog idempotency key collides on resubmits (kein Doppelsignieren). - Prometheus text-format /metrics endpoint, dependency-free: attest_reconciler_dirty_rows_total, _reconciliation_attempts_total, _reconciliation_failures_total, _idempotent_noop_total. - Tests with fake store + fake signer + fake submitter cover: happy path, already-attested rows are NOT re-signed, TLog idempotency across ticks, TLog-down failure path, pre-cancelled ctx. - Top-level README reflects the new service; CI auto-discovers services/*/go.mod so no workflow change needed. Co-Authored-By: Paperclip <noreply@paperclip.ing>
2.1 KiB
2.1 KiB
attest-reconciler
Periodic worker that re-signs per_diem_rates rows missing a
rate_change_attestation_ref. Mirrors admin-api's Signer + TLog orchestrator
(ADR-0005). Leans on the Transparency Log's idempotency guarantee so re-runs
land on the same log_index and never produce a second entry.
Layout
cmd/attest-reconciler/— entry point; wires store, orchestrator, metrics, and scheduler, handlesSIGINT/SIGTERM.internal/reconcile/—RunOnce(per-tick work) andScheduler(parses@every <dur>or a bare Go duration and drivesRunOnce).internal/attest/— Statement, Envelope, Orchestrator,HTTPSigner. Byte parity withadmin-api/internal/attest; split out until a 3rd consumer justifies a shared module (see parent issue note).internal/tlog/—HTTPSubmitterforPOST /api/v1/log/entries.internal/store/—PendingRateStoreinterface + Postgres skeleton.internal/metrics/— dependency-free Prometheus text-format exporter forattest_reconciler_dirty_rows_total,attest_reconciler_reconciliation_attempts_total,attest_reconciler_reconciliation_failures_total,attest_reconciler_idempotent_noop_total.
Running
attest-reconciler \
-spec="@every 1m" \
-signer-url="https://trust.basicstack.de" \
-tlog-url="https://tlog.basicstack.de" \
-bearer="$POCKET_ID_TOKEN" \
-metrics-addr=":9090"
Production replaces -bearer with the client_credentials TokenSource built
in DEV-602.
Tests
go test ./...
Covers:
- Pending rows get signed, logged, and attached (happy path).
- Rows that already have an attestation ref are not re-signed
(
TestReconciler_SkipsAlreadyAttestedRows). - Re-signing the same row yields the same TLog entry
(
TestReconciler_TLogIdempotencyOnReRun) — proves "kein Doppelsignieren". - TLog failures bump the failure counter and leave the row pending.
- Pre-cancelled context aborts the per-row loop.
ParseSpecaccepts@everyand bare durations, rejects crontab lines.- Metrics exposition format matches
# HELP+# TYPEheaders.