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:
parent
caa98ead3b
commit
28f0f429b2
7 changed files with 341 additions and 3 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue