Maintainer (Bernd) accepted 2026-07-29 with no changes requested. T03-T08 may now build against PostgreSQL + per-Licensor API tokens + railiance-cluster hosting + Python/src/target_revenue reuse.
12 KiB
| id | title | status | date | decided_by | accepted_at | workstream | alternatives_considered | |||
|---|---|---|---|---|---|---|---|---|---|---|
| ADR-0002 | Hosted Trust Service stack: storage, auth, and deployment target | accepted | 2026-07-29 | Bernd | 2026-07-29 | TREV-WP-0006 (trust-service-implementation) |
|
ADR-0002 — Hosted Trust Service Stack (Storage, Auth, Deployment)
Status
Accepted 2026-07-29 by the maintainer (Bernd). Per
docs/adr/ADR-0001-stage0-library-stack.md's own Revisit trigger, this was
that separate, later decision — hosting, multi-tenant storage, and
authentication were explicitly out of ADR-0001's scope and are not
inherited from it. T03–T08 (workplans/TREV-WP-0006-trust-service-implementation.md)
may now build against this decision.
Scope
Three decisions, per T02's task description:
- Storage engine for the append-only Target Ledger at multi-repo scale.
- Authentication/authorization model for Phase Registry and Ledger writes.
- Hosting/deployment target.
This ADR does not decide API framework internals beyond what's needed
to justify the storage/auth/hosting choices, does not draft the actual API
routes (specs/TrustServiceProductRequirementsDocument.md §6 previews
those, non-binding), and does not resolve the Breach/Compliance Record
component's ownership gap flagged in that same PRD §4 — that remains a
separate open item.
Context
specs/TrustServiceProductRequirementsDocument.md (WP-0006-T01) specifies
what the hosted service must do: serve multiple Phases/Licensors
concurrently (TS-FR-8), enforce phase-scoped authenticated writes (TS-FR-3),
preserve WP-0002's append-only/hash-chained/deterministic-fold guarantees
under hosting (§3 of that PRD), and publish public facts alongside
gated confidential evidence (TS-FR-4). The immediate driver is
workplans/TREV-WP-0008-governance-and-pilot-rollout.md's four target
product lines (coulomb-loop, net-kingdom, helix-forge, railiance-*),
all within the same Forgejo org — this is an internal-scale multi-tenant
problem (single-digit to low-double-digit Licensors at pilot stage), not an
internet-scale one.
Existing infrastructure in this org, per the state hub's repo-goal records:
railiance-infraprovisions and hardens a HostEurope server.railiance-clusterinstalls k3s, Helm, GitOps tooling, monitoring, and "essential platform services" on that server, explicitly as reusable baseline infrastructure forcoulomb-org projects — not a repo-specific deployment target onlyrailiance-clusteritself may use.
This means a hosting decision does not start from zero: a Kubernetes baseline with GitOps deployment already exists and is the natural fit target-revenue's own PRD (T02's task description) named as the first candidate to investigate, not assume.
Decision
Storage: PostgreSQL, one target_ledger_entries table partitioned
logically by phase_id, with database-level enforcement — not just
application-level convention — that settled entries cannot be updated or
deleted:
- the application's database role has
INSERT/SELECTonly on this table, noUPDATE/DELETEgrant at all; - a unique constraint on
(phase_id, entry_id)and a foreign-key-shaped check thatprevious_entry_hashmatches the prior entry for thatphase_id, enforced by a trigger, not solely by application code; phase_manifestsandextensionsas separate tables,JSONBcolumns for the less rigidly-typed fields (target_basis, extension contract metadata) so schema evolution doesn't require a migration for every new optional field.
Authentication: per-Licensor API token for write access, independent from per-entry Ed25519 signing. Two distinct guarantees, not one mechanism doing both jobs:
- each registered Licensor (one per product line at pilot stage, per
WP-0008-T01's Licensor-identity question) is issued a bearer token scoped
to write only within its own
phase_idnamespace — this is service-level access control, revocable and rotatable without touching the ledger's cryptographic history; - every accepted Ledger entry is still signed with the Trust Service instance's Ed25519 key over the canonical bytes (TSD §3.2, ADR-0001's established crypto), independent of which token authenticated the write — this is the integrity guarantee an external verifier checks, and it does not depend on trusting the token layer;
- confidential-evidence read access (TS-FR-4) uses the same token
infrastructure with a separate, narrower read-grant scope, so a Licensor
can delegate temporary read access (e.g., to an Enforcement Partner
investigating an Alleged Violation,
specs/EnforcementNetworkConcept.md) without issuing a write-capable credential.
Hosting: railiance-cluster's existing k3s deployment on the
railiance-infra-provisioned HostEurope server, packaged as a
containerized service with a Helm chart following that cluster's existing
GitOps pattern, rather than provisioning separate infrastructure. PostgreSQL
runs as a dedicated StatefulSet in the same cluster (or against a shared
cluster Postgres instance, if railiance-cluster already operates one for
another service — an open item for T03 to confirm at implementation time,
not assumed here).
Application language: Python, wrapping src/target_revenue/ directly
rather than reimplementing the fold/hashing/validation logic in a second
language. A thin HTTP layer (a lightweight ASGI framework — the specific
package is an implementation detail for T03/T04, not fixed by this ADR) adds
the network boundary, auth, and persistence TS-FR-1–9 require; the pure fold
and canonical-serialization code that WP-0002 already tested (36 passing
tests) is called, not rewritten.
Rationale
| Requirement | Choice | Why |
|---|---|---|
| Append-only enforcement must survive a hosting layer that could accidentally add an update path (TrustServicePRD §3 point 4) | Postgres with revoked UPDATE/DELETE grants + a trigger |
Makes "no mutation of settled entries" a database-enforced fact, not just an API design intention that a future endpoint could quietly violate |
| Deterministic fold must match between hosted and offline computation (TrustServicePRD TS-NFR-1, WP-0006-T04's stated highest-risk property) | Reuse src/target_revenue/fold.py unchanged inside the hosted service |
Eliminates the entire class of bug where a second, hand-written fold implementation drifts from the tested one |
| Multi-tenant isolation (TS-FR-8) must be a security property, not a convention | Per-Licensor token scoped at the database-query level (row-level filtering by phase_id ownership), not just at the API-route level |
An API-layer-only check can be bypassed by a bug in one route; a query-level scope is harder to forget |
| Existing infra should be reused, not duplicated, for an internal-scale service | railiance-cluster's k3s + GitOps baseline |
That repo's own stated goal is to be reusable "coulomb infrastructure," and this avoids a second hosting decision this org would have to maintain |
| Ledger integrity must not depend on trusting the hosting/access-control layer | Ed25519 signature over canonical bytes, independent of the API token | Matches TrustServicePRD §3 point 2 (offline verifiability) — a party with an exported ledger and the public key can verify without trusting the token system at all |
Why not a Go service
docs/adr/ADR-0001-stage0-library-stack.md names Go as fitting "a
long-lived, standalone, performance-sensitive service." This is not (yet)
that: pilot-stage throughput is a handful of Phases across four internal
product lines, not a high-throughput public API. Reusing the
already-tested Python fold avoids a second implementation of the exact
logic WP-0002 spent its test suite validating. Revisit if throughput,
latency, or team composition changes materially post-pilot.
Why not a file/git-based ledger store
workplans/TREV-WP-0008-governance-and-pilot-rollout.md T05 already
countenances "an explicit interim manual/git-based ledger process" as an
acceptable fallback if the hosted service isn't ready in time for the
first pilot Phase. That is a reasonable stopgap precisely because it avoids
concurrent-writer problems by having only one thing (a human, or a very
small number of them) append at a time. It is a poor default for the actual
hosted service once multiple product lines write concurrently: a flat file
or git-commit-based ledger has no native transactional guarantee against a
lost-update race between two near-simultaneous append requests, which a
relational database's row-level locking handles as a solved problem. Keep
the git-based path available as WP-0008-T05's named fallback; do not build
the hosted service's primary storage on it.
Why not per-repo-hosted SQLite
Would avoid running a shared database service, but reintroduces exactly the
isolation and concurrent-access problems a single shared Postgres instance
with per-tenant row scoping already solves, while adding an operational
question (where does each repo's SQLite file actually live, who backs it
up) that railiance-cluster's existing platform-services remit is better
positioned to answer once, centrally.
Consequences
Positive
- No new infrastructure to provision — reuses
railiance-infra/railiance-cluster's already-stated purpose. - Ledger integrity (signature + hash chain) is decoupled from access control (tokens), so revoking or rotating a Licensor's credentials never touches the cryptographic history a third party would verify.
- Reusing
src/target_revenue/means WP-0002's 36 tests continue to cover the core fold/hash logic inside the hosted service, not just a now-unused offline library.
Negative / risks
- Single HostEurope server is a single point of failure. Acceptable for
alpha/beta pilot scale (small, internal,
coulomb-org-only usage perSCOPE.md§1's alpha/beta risk-acceptance framing) but must be revisited before any external or higher-availability commitment. - Database-level append-only enforcement (revoked grants + trigger) must actually be tested as part of T04's conformance work, not assumed correct because it sounds right on paper — a misconfigured role grant would silently reintroduce the exact risk this ADR is designed to close.
- Whether
railiance-clusteralready runs a shared Postgres instance suitable for reuse, or whether this service needs its own, is not resolved here and is explicitly left to T03 to confirm before implementation, not assumed by this ADR.
Compensating guardrails
src/target_revenue/fold.pyandhashing.pyare imported, not reimplemented, inside the hosted service's request handlers.- Database migrations must never grant the application role
UPDATEorDELETEontarget_ledger_entries; any migration that does is a defect, not a convenience fix, and should be caught in review. - T04's conformance testing must include an explicit test that attempts to update or delete a settled entry through every available access path (API, direct DB role) and asserts each is rejected.
- T08's hosted conformance suite must assert hosted-computed and offline-computed folds agree on the same exported ledger, per TrustServicePRD TS-NFR-1.
Revisit trigger
Reconsider this decision if: pilot usage moves beyond the four named
coulomb-org product lines to external, higher-throughput, or
higher-availability requirements; railiance-cluster's k3s baseline is
retired or materially changed; or T03's implementation discovers the
single-Postgres-instance assumption doesn't hold operationally.