From 151ed3e97ce0284657a5db6c57077259519c8e9a Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 29 Jul 2026 22:07:39 +0200 Subject: [PATCH] Add hosted conformance suite and close WP-0006 (T08) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- README.md | 2 +- tests/test_hosted_conformance.py | 313 ++++++++++++++++++ ...EV-WP-0006-trust-service-implementation.md | 23 +- 3 files changed, 335 insertions(+), 3 deletions(-) create mode 100644 tests/test_hosted_conformance.py diff --git a/README.md b/README.md index 0cd5366..b0a9bd7 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ The concept's §13 now defines a **Global Contingency Share Determination Rule** | [TREV-WP-0003](workplans/TREV-WP-0003-normative-core-extraction.md) | Extract stable normative core docs — **finished**, reviewed and accepted 2026-07-29 | | [TREV-WP-0004](workplans/TREV-WP-0004-global-jurisdiction-research.md) | Global jurisdictional research backing the License/CUA candidates — **finished**, T10 synthesis accepted 2026-07-29 with alpha/beta working defaults (full legal review deferred until out of beta — see `SCOPE.md` §1) | | [TREV-WP-0005](workplans/TREV-WP-0005-enforcement-network-research.md) | Enforcement Network legal feasibility research — **finished**, T10 synthesis accepted 2026-07-29 on the same alpha/beta basis (Japan's Article 12 risk remains explicitly unresolved) | -| [TREV-WP-0006](workplans/TREV-WP-0006-trust-service-implementation.md) | Hosted Trust Service reference implementation (PRD Phase 4b) — active; T01 (PRD), T02 (ADR-0002, accepted), T03 (registries), T04 (Ledger append API), T05 (Metrics), T06 (Conversion Attestation), T07 (onboarding mechanism, `specs/TrustServiceOnboarding.md` + `scripts/trf_onboard.py`) done; T08 (hosted conformance suite) next | +| [TREV-WP-0006](workplans/TREV-WP-0006-trust-service-implementation.md) | Hosted Trust Service reference implementation (PRD Phase 4b) — **finished**, all 8 tasks done (Postgres-backed registries/ledger/metrics/attestation, ADR-0002 accepted, onboarding CLI, hosted conformance suite). T01's flagged gap — no task owns hosting the Breach/Compliance Record component (License V1C1 §7.4) — remains open, not yet assigned | | [TREV-WP-0007](workplans/TREV-WP-0007-degeneration-policy-and-canonical-profiles.md) | Degeneration policy + canonical monetization profile catalog — active, not yet started | | [TREV-WP-0008](workplans/TREV-WP-0008-governance-and-pilot-rollout.md) | Governance formalization + pilot rollout across `coulomb-loop`/`net-kingdom`/`helix-forge`/`railiance-*` — active, not yet started; real Phase declarations gated behind T05 | diff --git a/tests/test_hosted_conformance.py b/tests/test_hosted_conformance.py new file mode 100644 index 0000000..edba571 --- /dev/null +++ b/tests/test_hosted_conformance.py @@ -0,0 +1,313 @@ +"""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", +] +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 + ) diff --git a/workplans/TREV-WP-0006-trust-service-implementation.md b/workplans/TREV-WP-0006-trust-service-implementation.md index 25f8a43..5f21413 100644 --- a/workplans/TREV-WP-0006-trust-service-implementation.md +++ b/workplans/TREV-WP-0006-trust-service-implementation.md @@ -4,7 +4,7 @@ type: workplan title: "Trust Service reference implementation (PRD Phase 4b)" domain: infotech repo: target-revenue -status: active +status: finished owner: claude topic_slug: infotech created: "2026-07-29" @@ -317,7 +317,7 @@ offline, 71 passing with Docker; no stray containers left running. ```task id: TREV-WP-0006-T08 -status: todo +status: done priority: medium state_hub_task_id: "359c9c5e-a49a-40c1-82ea-2bf80e93e7d6" ``` @@ -327,3 +327,22 @@ 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. + +**Result:** `tests/test_hosted_conformance.py` (6 Docker-gated tests) +addresses all three named requirements directly: (1) the entire golden +Phase (`examples/phase-001/manifest.json` + full 6-entry ledger + all 4 +extensions) replayed through the hosted API reproduces exactly the +36-test-suite-established offline outcome — Development Credit 67000, +Remission Credit 33000, Outstanding Target 0, `future_license` MIT — and +the hosted Attestation matches it field-for-field; (2) two Phases under +two different Licensors (standing in for `coulomb-loop`/`net-kingdom`) +operate with interleaved, concurrent-style appends and are proven isolated +both positively (each Phase's metrics reflect only its own entries) and +negatively (a cross-Licensor write attempt is rejected); (3) a +parametrized regression test across four distinct ledger shapes +(credits-only, with remission, with a reversal, with both administrative +correction types) asserts hosted-append-then-offline-fold always +reproduces the exact expected totals, generalizing the single-shape check +already added ad hoc in T04. This closes WP-0006 — all 8 tasks done. Full +suite: 49 passing offline (plain system Python, no new dependency), 77 +passing with Docker; no stray containers left running.