Implement hosted Target Ledger append API (WP-0006-T04)
migrations/0002_ledger.sql adds ledger_entries with an identity-column
sequence for exact append order and no UPDATE/DELETE grant for trf_app.
src/target_revenue/ledger.py: append_entry() rejects caller-supplied
previous_entry_hash/signature, enforces per-Licensor phase ownership,
serializes concurrent appends via pg_advisory_xact_lock, computes the
chain tip and signs with the Trust Service instance's own Ed25519 key
(service/keys.py), reusing validation.py's checks unchanged. Adds
POST/GET /phases/{id}/ledger and an unauthenticated GET /public-key.
Also fixes a route-ordering bug found while wiring this in: phase IDs
never needed the {phase_id:path} converter (they contain colons, not
slashes), and its greedy matching was swallowing /ledger-suffixed
paths into the plain GET /phases/{id} route.
tests/test_ledger_hosting.py (8 Docker-gated tests) exercises hash-chain
linkage, forged-field rejection, cross-Licensor isolation, currency and
duplicate-id rejection, DB-privilege enforcement, signature
verification via the public-key endpoint, and the task's own
highest-priority property: append -> export -> offline fold reproduces
the exact expected Development/Remission Credit and Outstanding Target.
This commit is contained in:
parent
a419178c08
commit
5064815e77
7 changed files with 567 additions and 12 deletions
|
|
@ -147,7 +147,7 @@ system Python (no service deps required).
|
|||
|
||||
```task
|
||||
id: TREV-WP-0006-T04
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "591e87d6-3b64-4f1f-b870-edf834edc522"
|
||||
```
|
||||
|
|
@ -159,6 +159,35 @@ export must get the same Outstanding Target as the hosted service — this
|
|||
is the property to test most aggressively, since it's the one a hosted
|
||||
service could most easily and least visibly break.
|
||||
|
||||
**Result:** `migrations/0002_ledger.sql` adds `ledger_entries`
|
||||
(`GENERATED ALWAYS AS IDENTITY` sequence for exact append order; `trf_app`
|
||||
again has no UPDATE/DELETE grant). `src/target_revenue/ledger.py`'s
|
||||
`append_entry()`: rejects any caller-supplied `previous_entry_hash`/
|
||||
`signature` outright (server-computed only), enforces phase ownership
|
||||
(a Licensor may only append to its own registered Phase — TS-FR-8
|
||||
isolation), serializes concurrent appends per Phase via
|
||||
`pg_advisory_xact_lock`, computes the chain tip from the last stored entry
|
||||
and signs with the Trust Service instance's own Ed25519 key
|
||||
(`service/keys.py`, env-configured or ephemeral-with-warning for dev),
|
||||
reusing `validation.py`'s existing schema/currency checks unchanged.
|
||||
`service/app.py` adds `POST/GET /phases/{id}/ledger` and an unauthenticated
|
||||
`GET /public-key` (so an external party can verify signatures without
|
||||
trusting this API's own access control at all). Fixed an unrelated route
|
||||
ordering bug found while wiring this in: `{phase_id:path}` on the plain
|
||||
`GET /phases/{id}` route was greedily matching `/ledger`-suffixed paths
|
||||
too, since phase IDs contain colons but no slashes and never needed the
|
||||
`:path` converter — switched all phase routes to plain `{phase_id}`.
|
||||
`tests/test_ledger_hosting.py` (8 tests, Docker-gated): hash-chain linkage
|
||||
across appends, forged-hash/signature rejection, cross-Licensor isolation,
|
||||
currency-mismatch rejection, duplicate-entry-id rejection, DB-level
|
||||
UPDATE/DELETE privilege checks, signature verification via the public-key
|
||||
endpoint, and — the task's own highest-priority property — appending a
|
||||
mixed development-credit/remission-credit/credit-reversal sequence through
|
||||
the API, exporting it, and confirming `fold.fold_outstanding_target` over
|
||||
the export reproduces the exact expected Development Credit, Remission
|
||||
Credit, and Outstanding Target. Offline 36-test suite re-verified unchanged
|
||||
with plain system Python; no stray Docker containers left running.
|
||||
|
||||
## Metrics service
|
||||
|
||||
```task
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue