- 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>
55 lines
2.1 KiB
Markdown
55 lines
2.1 KiB
Markdown
# 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, handles `SIGINT`/`SIGTERM`.
|
|
- `internal/reconcile/` — `RunOnce` (per-tick work) and `Scheduler` (parses
|
|
`@every <dur>` or a bare Go duration and drives `RunOnce`).
|
|
- `internal/attest/` — Statement, Envelope, Orchestrator, `HTTPSigner`. Byte
|
|
parity with `admin-api/internal/attest`; split out until a 3rd consumer
|
|
justifies a shared module (see parent issue note).
|
|
- `internal/tlog/` — `HTTPSubmitter` for `POST /api/v1/log/entries`.
|
|
- `internal/store/` — `PendingRateStore` interface + Postgres skeleton.
|
|
- `internal/metrics/` — dependency-free Prometheus text-format exporter for
|
|
`attest_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.
|
|
- `ParseSpec` accepts `@every` and bare durations, rejects crontab lines.
|
|
- Metrics exposition format matches `# HELP` + `# TYPE` headers.
|