Add hosted conformance suite and close WP-0006 (T08)
tests/test_hosted_conformance.py (6 Docker-gated tests) addresses all
three requirements from the task description: the golden Phase example
(manifest + full ledger + all 4 extensions) replayed through the
hosted API reproduces exactly the offline-established outcome
(Development Credit 67000, Remission Credit 33000, Outstanding Target
0, MIT) including the hosted Attestation; two Phases under different
Licensors operate with interleaved appends and are proven isolated
both positively and negatively (cross-Licensor writes rejected); a
parametrized regression test across four ledger shapes (credits-only,
remission, reversal, admin corrections) confirms hosted-append-then-
offline-fold always matches expected totals.
WP-0006 is now finished - all 8 tasks (PRD, ADR-0002, registries,
ledger API, metrics, attestation, onboarding, conformance) done. T01's
flagged gap (no task owns hosting the Breach/Compliance Record
component from License V1C1 §7.4) remains open and unassigned.
2026-07-29 22:07:39 +02:00
|
|
|
"""Hosted-scale conformance suite (WP-0006-T08).
|
|
|
|
|
|
|
|
|
|
Extends WP-0002's offline conformance suite (`tests/test_*.py`, 36 tests)
|
|
|
|
|
to run against the actual hosted service, per the task's own three
|
|
|
|
|
requirements:
|
|
|
|
|
|
|
|
|
|
1. the golden Phase example (`examples/phase-001/`) replayed through the
|
|
|
|
|
hosted API end-to-end, not only the offline library;
|
|
|
|
|
2. multiple concurrent Phases across different Licensors, isolated from
|
|
|
|
|
one another;
|
|
|
|
|
3. a standing regression check that hosted and offline folds of the same
|
|
|
|
|
exported ledger always agree — generalized here across several ledger
|
|
|
|
|
shapes (development-credit, remission-credit, credit-reversal,
|
|
|
|
|
administrative corrections), not just the golden fixture's own shape.
|
|
|
|
|
|
|
|
|
|
Same ephemeral, disposable Postgres-via-Docker pattern as the other hosted
|
|
|
|
|
test modules (never the shared state-hub instance).
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import shutil
|
|
|
|
|
import subprocess
|
|
|
|
|
import time
|
|
|
|
|
import uuid
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
psycopg = pytest.importorskip("psycopg")
|
|
|
|
|
pytest.importorskip("fastapi")
|
|
|
|
|
from fastapi.testclient import TestClient # noqa: E402
|
|
|
|
|
|
|
|
|
|
from conftest import golden_entries, golden_extension, golden_manifest # noqa: E402
|
|
|
|
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
MIGRATIONS = [
|
|
|
|
|
REPO_ROOT / "migrations" / "0001_registries.sql",
|
|
|
|
|
REPO_ROOT / "migrations" / "0002_ledger.sql",
|
|
|
|
|
REPO_ROOT / "migrations" / "0003_attestations.sql",
|
2026-07-29 22:23:41 +02:00
|
|
|
REPO_ROOT / "migrations" / "0004_breach_records.sql",
|
Extend WP-0006 auth for per-human sub-credentials (WP-0009-T02)
migrations/0005_licensor_credentials.sql: licensors can now hold
multiple rows per licensor_id (credential_label, rights tier,
issued_by, revoked_at). Real structural finding: licensor_id couldn't
simply become non-unique, since phase_manifests, extensions, and
breach_records all FK to licensors(licensor_id), which only worked
because that column used to be unique. Introduced licensor_identities
(one row per tenant) as the new FK target for all four tables, with an
ensure_licensor_identity trigger auto-creating the identity on first
credential insert - so existing code (including every earlier test
fixture) needed no changes.
registry.py: Licensor gains credential_label/rights; RIGHTS_TIERS +
has_right() ordinal helper (enforcement is Control Plane's job, T03/
T04, not this task's); issue_sub_credential/revoke_sub_credential
(revocation via a SECURITY DEFINER function, matching
set_extension_status's existing pattern - trf_app has no UPDATE grant
on licensors); authenticate() rejects revoked credentials identically
to unrecognized ones.
Attribution scoped honestly: ledger_entry.schema.json stays unmodified
(frozen Stage 0 surface, additionalProperties:false) - per-entry human
attribution is a hosting-layer-only column
(ledger_entries.submitted_by_token, ledger.get_ledger_attribution()),
recorded alongside but never inside the signed entry payload. Narrower
than "the signature names the human," but exactly the "(or an
accompanying attributable field)" alternative this task's own
description anticipated.
All four Docker-gated test files that append Ledger entries needed
migration 0005 added (append_entry's INSERT now references the new
column). New tests/test_licensor_credentials.py (8 tests): multi-
credential resolution, duplicate-label rejection, revocation and its
idempotence, invalid-rights rejection, the has_right helper, per-entry
attribution recorded and not leaking into exported ledger JSON, and
DB-level UPDATE rejection. Full suite: 84 offline, 41 with Docker (up
from 30); no stray containers left running.
2026-07-30 14:26:33 +02:00
|
|
|
REPO_ROOT / "migrations" / "0005_licensor_credentials.sql",
|
Add hosted conformance suite and close WP-0006 (T08)
tests/test_hosted_conformance.py (6 Docker-gated tests) addresses all
three requirements from the task description: the golden Phase example
(manifest + full ledger + all 4 extensions) replayed through the
hosted API reproduces exactly the offline-established outcome
(Development Credit 67000, Remission Credit 33000, Outstanding Target
0, MIT) including the hosted Attestation; two Phases under different
Licensors operate with interleaved appends and are proven isolated
both positively and negatively (cross-Licensor writes rejected); a
parametrized regression test across four ledger shapes (credits-only,
remission, reversal, admin corrections) confirms hosted-append-then-
offline-fold always matches expected totals.
WP-0006 is now finished - all 8 tasks (PRD, ADR-0002, registries,
ledger API, metrics, attestation, onboarding, conformance) done. T01's
flagged gap (no task owns hosting the Breach/Compliance Record
component from License V1C1 §7.4) remains open and unassigned.
2026-07-29 22:07:39 +02:00
|
|
|
]
|
|
|
|
|
EXTENSION_NAMES = [
|
|
|
|
|
"development-license",
|
|
|
|
|
"cost-plus-operations",
|
|
|
|
|
"phase-sponsorship",
|
|
|
|
|
"service-with-development-allocation",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
pytestmark = pytest.mark.skipif(
|
|
|
|
|
shutil.which("docker") is None, reason="docker not available"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
|
|
|
def pg_container():
|
|
|
|
|
name = f"trf-test-pg-conformance-{uuid.uuid4().hex[:8]}"
|
|
|
|
|
subprocess.run(
|
|
|
|
|
[
|
|
|
|
|
"docker", "run", "--rm", "-d",
|
|
|
|
|
"--name", name,
|
|
|
|
|
"-e", "POSTGRES_PASSWORD=postgres",
|
|
|
|
|
"-e", "POSTGRES_DB=target_revenue_test",
|
|
|
|
|
"-p", "127.0.0.1::5432",
|
|
|
|
|
"postgres:16-alpine",
|
|
|
|
|
],
|
|
|
|
|
check=True, capture_output=True,
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
port_out = subprocess.run(
|
|
|
|
|
["docker", "port", name, "5432/tcp"], check=True, capture_output=True, text=True
|
|
|
|
|
).stdout.strip()
|
|
|
|
|
host_port = port_out.split(":")[-1]
|
|
|
|
|
dsn = f"host=127.0.0.1 port={host_port} dbname=target_revenue_test user=postgres password=postgres"
|
|
|
|
|
|
|
|
|
|
for _ in range(60):
|
|
|
|
|
try:
|
|
|
|
|
with psycopg.connect(dsn, connect_timeout=1):
|
|
|
|
|
break
|
|
|
|
|
except psycopg.OperationalError:
|
|
|
|
|
time.sleep(0.5)
|
|
|
|
|
else:
|
|
|
|
|
raise RuntimeError("postgres container did not become ready in time")
|
|
|
|
|
|
|
|
|
|
with psycopg.connect(dsn) as conn:
|
|
|
|
|
for migration in MIGRATIONS:
|
|
|
|
|
conn.execute(migration.read_text(encoding="utf-8"))
|
|
|
|
|
conn.commit()
|
|
|
|
|
for token, licensor_id in [
|
|
|
|
|
("test-token-repo-a", "coulomb-loop"),
|
|
|
|
|
("test-token-repo-b", "net-kingdom"),
|
|
|
|
|
]:
|
|
|
|
|
conn.execute(
|
|
|
|
|
"INSERT INTO licensors (token, licensor_id) VALUES (%s, %s)",
|
|
|
|
|
(token, licensor_id),
|
|
|
|
|
)
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
app_dsn = (
|
|
|
|
|
f"host=127.0.0.1 port={host_port} dbname=target_revenue_test "
|
|
|
|
|
f"user=trf_app password=changeme-in-deployment"
|
|
|
|
|
)
|
|
|
|
|
yield {"app_dsn": app_dsn, "token_a": "test-token-repo-a", "token_b": "test-token-repo-b"}
|
|
|
|
|
finally:
|
|
|
|
|
subprocess.run(["docker", "stop", name], capture_output=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture()
|
|
|
|
|
def client(pg_container, monkeypatch):
|
|
|
|
|
monkeypatch.setenv("TRF_DATABASE_URL", pg_container["app_dsn"])
|
|
|
|
|
monkeypatch.setenv("TRF_SIGNING_KEY_HEX", "33" * 32)
|
|
|
|
|
from target_revenue.service import app as app_module
|
|
|
|
|
|
|
|
|
|
if hasattr(app_module.app.state, "pool"):
|
|
|
|
|
app_module.app.state.pool.close()
|
|
|
|
|
del app_module.app.state.pool
|
|
|
|
|
if hasattr(app_module.app.state, "signing_key"):
|
|
|
|
|
del app_module.app.state.signing_key
|
|
|
|
|
with TestClient(app_module.app) as c:
|
|
|
|
|
yield c
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def auth(token: str) -> dict[str, str]:
|
|
|
|
|
return {"Authorization": f"Bearer {token}"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_golden_phase_replayed_through_hosted_api_matches_offline_library(client, pg_container):
|
|
|
|
|
"""Requirement 1: the golden Phase example, replayed through the
|
|
|
|
|
hosted API rather than the offline library, must reproduce exactly
|
|
|
|
|
the outcomes tests/test_conversion.py and tests/test_ledger_fold.py
|
|
|
|
|
already established offline (Development Credit 67000, Remission
|
|
|
|
|
Credit 33000, Outstanding Target 0, future_license MIT)."""
|
|
|
|
|
from target_revenue import conversion, fold
|
|
|
|
|
|
|
|
|
|
manifest = golden_manifest()
|
|
|
|
|
manifest["phase"]["id"] = manifest["phase"]["id"] + "-conformance-golden-" + uuid.uuid4().hex[:6]
|
|
|
|
|
phase_id = manifest["phase"]["id"]
|
|
|
|
|
|
|
|
|
|
resp = client.post("/phases", json=manifest, headers=auth(pg_container["token_a"]))
|
|
|
|
|
assert resp.status_code == 201, resp.text
|
|
|
|
|
|
|
|
|
|
for name in EXTENSION_NAMES:
|
|
|
|
|
ext = golden_extension(name)
|
|
|
|
|
ext_resp = client.post("/extensions", json=ext, headers=auth(pg_container["token_a"]))
|
|
|
|
|
# Extensions are shared/global, not per-Phase — a second replay in
|
|
|
|
|
# the same test session would collide; accept both outcomes here.
|
|
|
|
|
assert ext_resp.status_code in (201, 422)
|
|
|
|
|
|
|
|
|
|
for entry in golden_entries():
|
|
|
|
|
submitted = {k: v for k, v in entry.items() if k not in ("previous_entry_hash", "signature")}
|
|
|
|
|
submitted["phase"] = phase_id
|
|
|
|
|
entry_resp = client.post(f"/phases/{phase_id}/ledger", json=submitted, headers=auth(pg_container["token_a"]))
|
|
|
|
|
assert entry_resp.status_code == 201, entry_resp.text
|
|
|
|
|
|
|
|
|
|
exported_entries = client.get(f"/phases/{phase_id}/ledger").json()
|
|
|
|
|
hosted_result = fold.fold_outstanding_target(
|
|
|
|
|
manifest["phase"]["initial_target"]["amount"], exported_entries
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
offline_manifest = golden_manifest()
|
|
|
|
|
offline_status = conversion.conversion_status(offline_manifest, golden_entries())
|
|
|
|
|
|
|
|
|
|
assert hosted_result.development_credit == offline_status.development_credit == 67000
|
|
|
|
|
assert hosted_result.remission_credit == offline_status.remission_credit == 33000
|
|
|
|
|
assert hosted_result.outstanding_target == offline_status.outstanding_target == 0
|
|
|
|
|
assert hosted_result.is_converted is offline_status.is_converted is True
|
|
|
|
|
|
|
|
|
|
attestation_resp = client.get(f"/phases/{phase_id}/attestation")
|
|
|
|
|
assert attestation_resp.status_code == 200
|
|
|
|
|
hosted_attestation = attestation_resp.json()
|
|
|
|
|
assert hosted_attestation["final_development_credit"] == 67000
|
|
|
|
|
assert hosted_attestation["final_remission_credit"] == 33000
|
|
|
|
|
assert hosted_attestation["final_outstanding_target"] == 0
|
|
|
|
|
assert hosted_attestation["future_license"] == "MIT"
|
|
|
|
|
assert hosted_attestation["ledger_checkpoint"] == golden_entries()[-1]["id"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_multiple_concurrent_phases_across_different_licensors_are_isolated(client, pg_container):
|
|
|
|
|
"""Requirement 2: two Phases, belonging to two different Licensors
|
|
|
|
|
(standing in for two different product-line repos, e.g. coulomb-loop
|
|
|
|
|
and net-kingdom), operate concurrently without cross-contamination —
|
|
|
|
|
each Licensor's writes and reads only ever affect its own Phase."""
|
|
|
|
|
manifest_a = golden_manifest()
|
|
|
|
|
manifest_a["phase"]["id"] = "trsl:phase:conformance-repo-a-" + uuid.uuid4().hex[:6]
|
|
|
|
|
manifest_a["phase"]["initial_target"]["amount"] = 10000
|
|
|
|
|
|
|
|
|
|
manifest_b = golden_manifest()
|
|
|
|
|
manifest_b["phase"]["id"] = "trsl:phase:conformance-repo-b-" + uuid.uuid4().hex[:6]
|
|
|
|
|
manifest_b["phase"]["initial_target"]["amount"] = 20000
|
|
|
|
|
|
|
|
|
|
assert client.post("/phases", json=manifest_a, headers=auth(pg_container["token_a"])).status_code == 201
|
|
|
|
|
assert client.post("/phases", json=manifest_b, headers=auth(pg_container["token_b"])).status_code == 201
|
|
|
|
|
|
|
|
|
|
def entry(phase_id: str, entry_id: str, amount: float) -> dict:
|
|
|
|
|
return {
|
|
|
|
|
"id": entry_id,
|
|
|
|
|
"phase": phase_id,
|
|
|
|
|
"type": "development-credit",
|
|
|
|
|
"amount": amount,
|
|
|
|
|
"currency": "USD",
|
|
|
|
|
"recognized_at": "2026-08-01T00:00:00Z",
|
|
|
|
|
"evidence_reference": f"confidential:evidence:{entry_id}",
|
|
|
|
|
"extension": {"id": "trsl:extension:development-license", "version": "1.0"},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Interleaved appends across both Phases, simulating concurrent
|
|
|
|
|
# multi-repo activity rather than strictly sequential per-Phase use.
|
|
|
|
|
a1 = client.post(
|
|
|
|
|
f"/phases/{manifest_a['phase']['id']}/ledger",
|
|
|
|
|
json=entry(manifest_a["phase"]["id"], "trsl:entry:conformrepoa0001", 3000),
|
|
|
|
|
headers=auth(pg_container["token_a"]),
|
|
|
|
|
)
|
|
|
|
|
b1 = client.post(
|
|
|
|
|
f"/phases/{manifest_b['phase']['id']}/ledger",
|
|
|
|
|
json=entry(manifest_b["phase"]["id"], "trsl:entry:conformrepob0001", 5000),
|
|
|
|
|
headers=auth(pg_container["token_b"]),
|
|
|
|
|
)
|
|
|
|
|
a2 = client.post(
|
|
|
|
|
f"/phases/{manifest_a['phase']['id']}/ledger",
|
|
|
|
|
json=entry(manifest_a["phase"]["id"], "trsl:entry:conformrepoa0002", 2000),
|
|
|
|
|
headers=auth(pg_container["token_a"]),
|
|
|
|
|
)
|
|
|
|
|
assert a1.status_code == b1.status_code == a2.status_code == 201
|
|
|
|
|
|
|
|
|
|
# Cross-Licensor writes are rejected (isolation, not just non-interference).
|
|
|
|
|
cross = client.post(
|
|
|
|
|
f"/phases/{manifest_a['phase']['id']}/ledger",
|
|
|
|
|
json=entry(manifest_a["phase"]["id"], "trsl:entry:conformrepoacross", 1),
|
|
|
|
|
headers=auth(pg_container["token_b"]),
|
|
|
|
|
)
|
|
|
|
|
assert cross.status_code == 422
|
|
|
|
|
|
|
|
|
|
metrics_a = client.get(f"/phases/{manifest_a['phase']['id']}/metrics").json()
|
|
|
|
|
metrics_b = client.get(f"/phases/{manifest_b['phase']['id']}/metrics").json()
|
|
|
|
|
|
|
|
|
|
assert metrics_a["facts"]["cumulative_development_credit"] == 5000
|
|
|
|
|
assert metrics_b["facts"]["cumulative_development_credit"] == 5000 # only b1 applied to B
|
|
|
|
|
assert metrics_a["facts"]["outstanding_target"] == 5000
|
|
|
|
|
assert metrics_b["facts"]["outstanding_target"] == 15000
|
|
|
|
|
|
|
|
|
|
ledger_a = client.get(f"/phases/{manifest_a['phase']['id']}/ledger").json()
|
|
|
|
|
ledger_b = client.get(f"/phases/{manifest_b['phase']['id']}/ledger").json()
|
|
|
|
|
assert {e["id"] for e in ledger_a} == {"trsl:entry:conformrepoa0001", "trsl:entry:conformrepoa0002"}
|
|
|
|
|
assert {e["id"] for e in ledger_b} == {"trsl:entry:conformrepob0001"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"plan",
|
|
|
|
|
[
|
|
|
|
|
[("development-credit", 1000), ("development-credit", 500)],
|
|
|
|
|
[("development-credit", 2000), ("remission-credit", 300)],
|
|
|
|
|
[("development-credit", 5000), ("credit-reversal", 1000)],
|
|
|
|
|
[
|
|
|
|
|
("development-credit", 4000),
|
|
|
|
|
("administrative-correction-development", 250),
|
|
|
|
|
("administrative-correction-remission", 100),
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
ids=["credits-only", "with-remission", "with-reversal", "with-admin-corrections"],
|
|
|
|
|
)
|
|
|
|
|
def test_hosted_and_offline_fold_always_agree(client, pg_container, plan):
|
|
|
|
|
"""Requirement 3, generalized: for several distinct ledger shapes (not
|
|
|
|
|
only the golden fixture's own), appending through the hosted API and
|
|
|
|
|
folding the export offline must reproduce exactly the same
|
|
|
|
|
Development Credit, Remission Credit, and Outstanding Target the plan
|
|
|
|
|
implies by construction."""
|
|
|
|
|
from target_revenue import fold, hashing
|
|
|
|
|
|
|
|
|
|
manifest = golden_manifest()
|
|
|
|
|
manifest["phase"]["id"] = "trsl:phase:conformance-plan-" + uuid.uuid4().hex[:8]
|
|
|
|
|
manifest["phase"]["initial_target"]["amount"] = 100000
|
|
|
|
|
phase_id = manifest["phase"]["id"]
|
|
|
|
|
|
|
|
|
|
assert client.post("/phases", json=manifest, headers=auth(pg_container["token_a"])).status_code == 201
|
|
|
|
|
|
|
|
|
|
expected_dev = 0.0
|
|
|
|
|
expected_rem = 0.0
|
|
|
|
|
for i, (entry_type, amount) in enumerate(plan, start=1):
|
|
|
|
|
e = {
|
|
|
|
|
"id": f"trsl:entry:conformplan{uuid.uuid4().hex[:6]}{i:02d}",
|
|
|
|
|
"phase": phase_id,
|
|
|
|
|
"type": entry_type,
|
|
|
|
|
"amount": amount,
|
|
|
|
|
"currency": "USD",
|
|
|
|
|
"recognized_at": f"2026-08-{i:02d}T00:00:00Z",
|
|
|
|
|
"evidence_reference": f"confidential:evidence:plan-{i}",
|
|
|
|
|
}
|
|
|
|
|
if entry_type in ("development-credit", "remission-credit"):
|
|
|
|
|
e["extension"] = {"id": "trsl:extension:development-license", "version": "1.0"}
|
|
|
|
|
if entry_type == "credit-reversal":
|
|
|
|
|
e["reverses"] = "trsl:entry:conformplanseed"
|
|
|
|
|
resp = client.post(f"/phases/{phase_id}/ledger", json=e, headers=auth(pg_container["token_a"]))
|
|
|
|
|
assert resp.status_code == 201, resp.text
|
|
|
|
|
|
|
|
|
|
if entry_type == "development-credit":
|
|
|
|
|
expected_dev += amount
|
|
|
|
|
elif entry_type == "remission-credit":
|
|
|
|
|
expected_rem += amount
|
|
|
|
|
elif entry_type == "credit-reversal":
|
|
|
|
|
expected_dev -= amount
|
|
|
|
|
elif entry_type == "administrative-correction-development":
|
|
|
|
|
expected_dev += amount
|
|
|
|
|
elif entry_type == "administrative-correction-remission":
|
|
|
|
|
expected_rem += amount
|
|
|
|
|
|
|
|
|
|
exported = client.get(f"/phases/{phase_id}/ledger").json()
|
|
|
|
|
hashing.verify_chain(exported)
|
|
|
|
|
result = fold.fold_outstanding_target(manifest["phase"]["initial_target"]["amount"], exported)
|
|
|
|
|
|
|
|
|
|
assert result.development_credit == expected_dev
|
|
|
|
|
assert result.remission_credit == expected_rem
|
|
|
|
|
assert result.outstanding_target == max(
|
|
|
|
|
0.0, manifest["phase"]["initial_target"]["amount"] - expected_dev - expected_rem
|
|
|
|
|
)
|