specs/TrustServiceOnboarding.md defines the mechanism: a Phase Manifest file is committed to the declaring repo (durable, independently foldable forever) and separately registered with the hosted service; once registered, the Ledger's live authoritative copy is the hosted service only, not a second competing file. Licensor token bootstrapping is explicitly out of scope here (a WP-0008-T01 governance action). scripts/trf_onboard.py: a dependency-light CLI (stdlib urllib + target_revenue.validation only, no FastAPI/psycopg needed to onboard a Phase) with validate/register-phase/append-entry/status subcommands. The Licensor token is read only from a named environment variable, never accepted as a literal argument. tests/test_trf_onboard.py (4 tests, no network/Docker) proves invalid-manifest and missing-token-env cases fail before any HTTP attempt, by monkeypatching the request function to raise if called. tests/test_onboarding_hosted.py (1 Docker-gated test) runs an actual uvicorn server on a real socket and drives the full register -> append -> status round trip through the CLI as an external repo would invoke it.
329 lines
16 KiB
Markdown
329 lines
16 KiB
Markdown
---
|
||
id: TREV-WP-0006
|
||
type: workplan
|
||
title: "Trust Service reference implementation (PRD Phase 4b)"
|
||
domain: infotech
|
||
repo: target-revenue
|
||
status: active
|
||
owner: claude
|
||
topic_slug: infotech
|
||
created: "2026-07-29"
|
||
updated: "2026-07-29"
|
||
state_hub_workstream_id: "03f63e7e-c17a-4dcc-aa34-a79824dafcf3"
|
||
---
|
||
|
||
# Trust Service reference implementation (PRD Phase 4b)
|
||
|
||
Builds the hosted Trust Service that WP-0002 deliberately deferred: Phase
|
||
Registry, Extension Registry, hosted Target Ledger, Metrics, and Conversion
|
||
Attestation publication, serving multiple repos rather than the single
|
||
offline golden-Phase package. This is the piece explicitly named in
|
||
`SCOPE.md` §3 as out of scope until now.
|
||
|
||
**Unblocked as of 2026-07-29:** `SCOPE.md` §4 rule 2 required (a) WP-0003's
|
||
core extract deliverables to exist, and (b) working defaults for Longstop,
|
||
currency, recognition, and minimum evidence to be published, before full
|
||
Trust Service implementation could start. Both conditions are now met
|
||
(WP-0003 finished 2026-07-29; `specs/OpenQuestions-WorkingDefaults.md`
|
||
published since Stage 0). This workplan is the first to actually start
|
||
that implementation.
|
||
|
||
**Why now:** the midterm goal is to govern and monetize the evolution of
|
||
repos across the `coulomb` Forgejo org's product lines (`coulomb-loop`,
|
||
`net-kingdom`, `helix-forge`, the `railiance-*` family) — that requires a
|
||
real, multi-repo-capable Trust Service, not the single-Phase offline
|
||
library WP-0002 produced.
|
||
|
||
**Does not include:** accepting real payments or tracking a real Phase for
|
||
any production repo — see `workplans/TREV-WP-0008-governance-and-pilot-rollout.md`
|
||
for the human-gated go-live step. This workplan builds the infrastructure;
|
||
it does not turn it on for anyone's real money.
|
||
|
||
## Trust Service Product Requirements Document
|
||
|
||
```task
|
||
id: TREV-WP-0006-T01
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "c98a1871-ae5c-4c91-b59c-88a6e4ccc193"
|
||
```
|
||
|
||
Produce `specs/TrustServiceProductRequirementsDocument.md`: scope,
|
||
stakeholders, and requirements for the hosted service, building directly
|
||
on `specs/TechnicalSpecificationDocument.md` §4 (component architecture)
|
||
and §3's schemas. Must state explicitly which of WP-0002's Stage 0
|
||
guarantees (determinism, offline verifiability, no Trust Service discretion
|
||
over conversion) the hosted service inherits unchanged versus which
|
||
Stage-0-only constraints (single golden Phase, no auth, no multi-tenancy)
|
||
it must now lift.
|
||
|
||
**Result:** `specs/TrustServiceProductRequirementsDocument.md` produced.
|
||
Nine stakeholders identified (including Enforcement Partner, a category
|
||
that did not exist at framework-PRD-drafting time). §3 states five
|
||
inherited-unchanged Stage 0 guarantees (determinism, offline verifiability,
|
||
no discretionary conversion authority, append-only ledger semantics, closed
|
||
core vocabulary); §4 tables ten Stage-0-only constraints that must lift,
|
||
mapped to their owning T03–T08 task, plus one flagged gap: no WP-0006 task
|
||
currently owns the Breach/Compliance Record hosting component (TSD §4.1's
|
||
2026-07-29 addition, post-dates WP-0002) — recommend either a new T09 or
|
||
folding it into T06 before implementation starts. Nine functional
|
||
requirements (TS-FR-1–9) elaborate framework PRD FR-8/9/10 for a hosted,
|
||
multi-tenant, authenticated context without redefining any schema field.
|
||
|
||
## Stack and storage ADR (hosted service — distinct scope from ADR-0001)
|
||
|
||
```task
|
||
id: TREV-WP-0006-T02
|
||
status: done
|
||
priority: high
|
||
human_accept_required: true
|
||
human_accepted_by: Bernd
|
||
human_accepted_at: "2026-07-29"
|
||
state_hub_task_id: "06eabd46-5de5-4d80-9bb7-c70ef6d80df9"
|
||
```
|
||
|
||
`docs/adr/ADR-0001-stage0-library-stack.md` explicitly scoped out hosting,
|
||
storage, and multi-tenancy decisions (its Revisit trigger names this exact
|
||
moment). Record a new ADR for: storage engine for the append-only Target
|
||
Ledger at multi-repo scale, authentication/authorization model for Phase
|
||
Registry writes, and hosting/deployment target — `railiance-infra` and
|
||
`railiance-cluster` are the existing platform-infrastructure repos in this
|
||
Forgejo org and are the natural first candidates to investigate, not
|
||
assumed by default.
|
||
|
||
**Human accept gate:** per the same policy as ADR-0001 — agents may draft,
|
||
must not treat as accepted without explicit maintainer sign-off.
|
||
|
||
**Result:** Accepted 2026-07-29 by the maintainer (Bernd), no changes
|
||
requested. `docs/adr/ADR-0002-hosted-trust-service-stack.md`: PostgreSQL
|
||
(append-only enforced at the database-grant/trigger level, not just
|
||
convention) for storage; per-Licensor API token for write/read-grant access
|
||
control, decoupled from the existing Ed25519 per-entry signature;
|
||
`railiance-cluster`'s existing k3s/Helm/GitOps baseline (on the
|
||
`railiance-infra`-provisioned HostEurope server) for hosting, rather than
|
||
new infrastructure; Python reusing `src/target_revenue/` directly behind a
|
||
thin HTTP layer. T03–T08 may now build against this decision.
|
||
|
||
## Phase Registry and Extension Registry hosting
|
||
|
||
```task
|
||
id: TREV-WP-0006-T03
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "fb9f00e5-5755-44e4-ad07-ba0dc73596b8"
|
||
```
|
||
|
||
Implement hosted versions of the Phase Registry and Extension Registry
|
||
components (`specs/TechnicalSpecificationDocument.md` §4.1), serving
|
||
multiple Phase Manifests and extension registrations across repos rather
|
||
than one golden fixture. Reject non-conformant manifests/extensions at
|
||
registration, exactly as the Stage 0 validators already do — this task
|
||
adds multi-tenancy and persistence, not new validation logic.
|
||
|
||
**Result:** Implemented per ADR-0002. `migrations/0001_registries.sql`
|
||
creates `licensors`, `phase_manifests`, `extensions` tables, a
|
||
`trf_app` role with **no UPDATE/DELETE grant** on `phase_manifests` or
|
||
`extensions` (database-enforced append-only, not just application
|
||
convention), and a `set_extension_status()` SECURITY DEFINER function as
|
||
the only sanctioned path to `canonical`/`deprecated` — canonicalization is
|
||
therefore a recorded, attributable governance action the application role
|
||
cannot perform via ordinary UPDATE. `src/target_revenue/registry.py` wraps
|
||
the existing `validation.py` checks (no new validation logic) with
|
||
persistence and per-Licensor token authentication. `src/target_revenue/service/app.py`
|
||
exposes a thin FastAPI surface (`POST/GET /phases`, `POST/GET /extensions`).
|
||
Added a `service`/`service-dev` optional-dependency group
|
||
(`pyproject.toml`) so the core offline library keeps zero new hard
|
||
dependencies. `tests/test_registry_hosting.py` (7 tests, requires Docker,
|
||
auto-skips otherwise) spins an ephemeral, disposable Postgres container —
|
||
never the shared state-hub `infra-postgres-1`/`custodian` instance —
|
||
covering registration, rejection with diff, duplicate-`phase_id` rejection,
|
||
unknown-token rejection, extension canonicalization via the governance
|
||
function, and two explicit database-privilege tests proving `trf_app`
|
||
cannot UPDATE/DELETE `phase_manifests` or UPDATE `extensions` directly.
|
||
Original 36-test offline suite verified unchanged and passing with plain
|
||
system Python (no service deps required).
|
||
|
||
## Hosted Target Ledger append API
|
||
|
||
```task
|
||
id: TREV-WP-0006-T04
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "591e87d6-3b64-4f1f-b870-edf834edc522"
|
||
```
|
||
|
||
Implement an authenticated append API for Target Ledger entries per Phase,
|
||
preserving WP-0002's append-only, hash-chained, deterministically-foldable
|
||
guarantees exactly. Any party who can compute the fold offline from an
|
||
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
|
||
id: TREV-WP-0006-T05
|
||
status: done
|
||
priority: medium
|
||
state_hub_task_id: "6bab5e64-0091-4a25-906d-758310cbe723"
|
||
```
|
||
|
||
Implement the public metrics set from `specs/OpenQuestions-WorkingDefaults.md`
|
||
Q9 (Initial Target, cumulative credits, Outstanding Target, conversion
|
||
status, last checkpoint, Longstop timestamp as mandatory; velocity/forecast
|
||
as recommended, clearly labeled as forecasts per concept §14.6).
|
||
|
||
**Result:** `src/target_revenue/metrics.py`'s `compute_metrics(manifest,
|
||
entries, as_of)` — pure and deterministic like `fold.py` (the only input
|
||
that varies with wall-clock time, `as_of`, is an explicit parameter, never
|
||
read internally), reusing `fold.py`/`conversion.py` unchanged. Returns
|
||
three explicitly separated blocks per TrustServicePRD TS-FR-5: `facts`
|
||
(Q9's mandatory set — Initial Target, cumulative Development/Remission
|
||
Credit, Outstanding Target, conversion status, last ledger entry id,
|
||
Longstop timestamp), `calculations` (target satisfaction percentage,
|
||
Development/Remission Credit velocity per day, days since last material
|
||
progress), and `forecasts` (projected conversion date, `null` whenever
|
||
already converted or velocity is zero/negative — never populated as a
|
||
disguised fact). Added `GET /phases/{id}/metrics` to `service/app.py`,
|
||
unauthenticated per FR-9/FR-10. `tests/test_metrics.py` (6 tests, no
|
||
Docker/Postgres required, runs under plain system Python — no new hard
|
||
dependency) covers the mandatory-facts set, fact/calculation/forecast
|
||
separation, percentage correctness, the zero-material-entries case, and
|
||
`as_of` timezone-awareness. Added one Docker-gated test to
|
||
`tests/test_ledger_hosting.py` proving the hosted `/metrics` response
|
||
(fetched with no Authorization header) exactly equals
|
||
`compute_metrics()` run offline against the same exported Manifest +
|
||
Ledger. Full suite (offline 36 + metrics 6 + Docker-gated 22) verified
|
||
passing; no stray containers left running.
|
||
|
||
## Conversion Attestation publication
|
||
|
||
```task
|
||
id: TREV-WP-0006-T06
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "f08d3d73-4c75-4049-97d0-c27ae8513336"
|
||
```
|
||
|
||
Implement Conversion Attestation generation and publication as a hosted
|
||
service, preserving the non-discretionary rule from
|
||
`src/target_revenue/conversion.py`: the service publishes evidence of a
|
||
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
|
||
id: TREV-WP-0006-T07
|
||
status: done
|
||
priority: medium
|
||
state_hub_task_id: "44c5846e-1f37-41fe-8ac0-fc7d88628e9f"
|
||
```
|
||
|
||
Define and implement how a repo (e.g. `coulomb-loop`, `net-kingdom`,
|
||
`helix-forge`, a `railiance-*` repo) registers a Phase against the hosted
|
||
Trust Service: what it commits to its own repo (a Phase Manifest file,
|
||
per `specs/PhaseManifestSpecification.md`) versus what it submits to the
|
||
Trust Service (the registration call). Coordinate with
|
||
`workplans/TREV-WP-0008-governance-and-pilot-rollout.md`, which selects
|
||
the actual first repos — this task defines the mechanism, not the pilot
|
||
selection.
|
||
|
||
**Result:** `specs/TrustServiceOnboarding.md` defines the mechanism: a
|
||
Phase Manifest file is committed to the declaring repo (durable,
|
||
git-history-backed, independently foldable forever) *and* registered with
|
||
the hosted service (the multi-tenant, publicly-servable copy); once
|
||
registered, the Ledger's live authoritative copy is the hosted service
|
||
only, not a second competing file — a repo may keep periodic exports for
|
||
its own offline re-verification, but those are copies, not a source of
|
||
truth. Licensor token bootstrapping is explicitly out of this mechanism's
|
||
scope (a WP-0008-T01 governance action). `scripts/trf_onboard.py`
|
||
implements it as a dependency-light CLI (stdlib `urllib` + the existing
|
||
`target_revenue.validation`, no FastAPI/psycopg required to onboard a
|
||
Phase) with four subcommands: `validate` (offline only, no network),
|
||
`register-phase` (validates offline before ever attempting the network
|
||
call), `append-entry`, and `status` (public, no token). The Licensor API
|
||
token is read only from an environment variable named on the command
|
||
line, never accepted as a literal argument or committed. `tests/test_trf_onboard.py`
|
||
(4 tests, no network, no Docker) proves both invalid-manifest and
|
||
missing-token-env cases fail before any HTTP attempt is made — asserted by
|
||
monkeypatching the request function to raise if called at all.
|
||
`tests/test_onboarding_hosted.py` (1 Docker-gated test) is the one test in
|
||
the suite that runs an actual `uvicorn` server on a real socket (rather
|
||
than FastAPI's in-process TestClient, since the CLI genuinely speaks HTTP)
|
||
and drives the full register → append → status round trip through the CLI
|
||
exactly as an external repo would invoke it. Full suite: 49 passing
|
||
offline, 71 passing with Docker; no stray containers left running.
|
||
|
||
## Conformance test suite at hosted scale
|
||
|
||
```task
|
||
id: TREV-WP-0006-T08
|
||
status: todo
|
||
priority: medium
|
||
state_hub_task_id: "359c9c5e-a49a-40c1-82ea-2bf80e93e7d6"
|
||
```
|
||
|
||
Extend WP-0002's conformance suite to run against the hosted service:
|
||
multiple concurrent Phases across different repos, the golden Phase
|
||
example replayed through the hosted API rather than only the offline
|
||
library, and a specific regression test that hosted and offline folds of
|
||
the same exported ledger always agree.
|