Implement hosted Conversion Attestation publication (WP-0006-T06)

migrations/0003_attestations.sql: one row per Phase, no UPDATE/DELETE
grant for trf_app (a published attestation is permanent).

src/target_revenue/attestation.py: publish_attestation() is idempotent
(existing row returned unchanged, never regenerated) and derives
conversion_timestamp from the ledger itself via _find_conversion_prefix(),
which finds the earliest prefix where the fold reaches Outstanding
Target = 0 rather than trusting entries[-1] or wall-clock publish time -
so a later unrelated entry can never change an already-converged Phase's
recorded conversion moment. Raises NotConvertedError rather than
fabricating a conversion. Reuses conversion.generate_attestation()
unchanged. Guarded the psycopg import behind TYPE_CHECKING so the pure
helper stays testable under plain system Python.

service/app.py adds GET /phases/{id}/attestation (unauthenticated,
publish-on-first-observation).

tests/test_attestation.py (3 tests, no Docker/Postgres) proves the
earliest-crossing behavior. 6 new Docker-gated tests in
test_ledger_hosting.py cover pre-conversion 404, the core
legal-technical property that /metrics facts/calculations/forecasts
are identical whether or not /attestation was ever called, one-time
publication, signature verification, and DB-level UPDATE/DELETE
rejection.
This commit is contained in:
tegwick 2026-07-29 21:50:02 +02:00
parent caa98ead3b
commit 28f0f429b2
7 changed files with 341 additions and 3 deletions

View file

@ -229,7 +229,7 @@ passing; no stray containers left running.
```task
id: TREV-WP-0006-T06
status: todo
status: done
priority: high
state_hub_task_id: "f08d3d73-4c75-4049-97d0-c27ae8513336"
```
@ -241,6 +241,34 @@ conversion already true from the Manifest + Ledger, never a precondition
for it. Any repo's own tooling must be able to recompute conversion status
without querying this service at all.
**Result:** `migrations/0003_attestations.sql` adds `attestations`
(one row per Phase, `trf_app` again with no UPDATE/DELETE grant — a
published attestation is a permanent record, never revised).
`src/target_revenue/attestation.py`'s `publish_attestation()` is idempotent
(an existing row is returned unchanged, never regenerated) and derives
`conversion_timestamp` from the ledger itself — `_find_conversion_prefix()`
walks increasing ledger prefixes to find the *earliest* point the fold
reaches `Outstanding Target = 0`, rather than using the publish call's
wall-clock time or blindly trusting `entries[-1]`, so a later, unrelated
ledger entry can never retroactively change an already-converged Phase's
recorded conversion moment. Raises `NotConvertedError` (never fabricates a
conversion) if the ledger never reaches zero. Reuses
`conversion.generate_attestation()` unchanged. `service/app.py` adds
`GET /phases/{id}/attestation`, unauthenticated, publishing on first
observation and simply returning the stored record thereafter.
`tests/test_attestation.py` (3 tests, no Docker/Postgres, plain system
Python — required a `TYPE_CHECKING`-guarded psycopg import to keep it
dependency-free) proves the earliest-crossing behavior explicitly,
including the case where a later entry exists past the conversion point.
6 new Docker-gated tests in `tests/test_ledger_hosting.py` cover
404-before-conversion, the core legal-technical property (`/metrics`
facts/calculations/forecasts are bit-for-bit identical whether or not
`/attestation` has ever been called), one-time publication with identical
output on reread, public-key signature verification, and DB-level
UPDATE/DELETE rejection on `attestations`. Full suite verified: 45 passing
offline (36 + 6 metrics + 3 attestation) under plain system Python, 30
passing under the Docker-gated suite; no stray containers left running.
## Multi-repo onboarding flow
```task