Implement TREV-WP-0002 Stage 0 foundation: schemas, pure fold, golden Phase

Delivers the offline runnable specification foundation for the Trust Layer
(TSD §3-§6), not a hosted Trust Service:

- JSON Schemas for Phase Manifest, Ledger Entry, Extension Contract, and
  Conversion Attestation, encoding the Stage 0 working defaults (Q3 future
  license enum, Q6 single-currency Phases, Q8 required longstop_at).
- src/target_revenue: pure Outstanding Target fold, SHA-256 hash-chain
  verification, Ed25519 signing helpers, extension conformance checks
  (including a core-term-redefinition heuristic), and conversion detection
  that never requires an attestation document to determine conversion status.
- examples/phase-001: golden Phase package matching the concept doc's
  worked example, generated via scripts/generate_golden_phase.py so the
  hash chain is computed by the library itself, not hand-typed.
- 32 passing pytest tests covering manifest/ledger/extension conformance,
  tamper/reorder detection, and the full lifecycle fold to conversion.
- docs/adr/ADR-0001: proposed (not accepted) Stage 0 stack choice, per the
  WP-0002-T01 human-accept gate — implementation proceeded against the
  proposal as the workplan note permits, but the task stays open.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-07-28 18:47:29 +02:00
parent 658b1af00d
commit 57c7111cbc
28 changed files with 1751 additions and 7 deletions

28
tests/conftest.py Normal file
View file

@ -0,0 +1,28 @@
import json
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
SRC = REPO_ROOT / "src"
if str(SRC) not in sys.path:
sys.path.insert(0, str(SRC))
EXAMPLES_DIR = REPO_ROOT / "examples" / "phase-001"
FIXTURES_DIR = Path(__file__).resolve().parent / "fixtures"
def load_json(path: Path) -> dict:
with open(path, encoding="utf-8") as f:
return json.load(f)
def golden_manifest() -> dict:
return load_json(EXAMPLES_DIR / "manifest.json")
def golden_entries() -> list[dict]:
return load_json(EXAMPLES_DIR / "ledger.json")
def golden_extension(name: str) -> dict:
return load_json(EXAMPLES_DIR / "extensions" / f"{name}.json")

View file

@ -0,0 +1,24 @@
{
"id": "trsl:extension:bad-redefinition",
"version": "1.0",
"value": {
"description": "Deliberately non-conforming fixture for WP-0002-T04: attempts to redefine a core term instead of only classifying a payment."
},
"pricing": {
"method": "flat-fee"
},
"allocation": {
"rule": "Outstanding Target = 0 for any payment received under this extension, regardless of the Phase ledger.",
"default_rate": 1.0
},
"recognition": {
"event": "payment-settled"
},
"reversal": {
"rule": "No reversals."
},
"evidence": {
"requirement": "None."
},
"status": "registered"
}

52
tests/test_conversion.py Normal file
View file

@ -0,0 +1,52 @@
import pytest
from conftest import golden_entries, golden_manifest
from target_revenue import conversion, validation
def test_conversion_status_not_converted_partway_through():
manifest = golden_manifest()
status = conversion.conversion_status(manifest, golden_entries()[:4])
assert status.is_converted is False
assert status.outstanding_target == 45000
def test_conversion_status_converted_after_full_sequence():
manifest = golden_manifest()
status = conversion.conversion_status(manifest, golden_entries())
assert status.is_converted is True
assert status.outstanding_target == 0
assert status.future_license == "MIT"
def test_conversion_status_requires_no_attestation_document():
"""PRD FR-7/G6: conversion status must be computable from Manifest +
Ledger alone, with no attestation file consulted or required."""
manifest = golden_manifest()
entries = golden_entries()
status_without_attestation = conversion.conversion_status(manifest, entries)
assert status_without_attestation.is_converted is True
def test_generate_attestation_for_converted_phase():
manifest = golden_manifest()
entries = golden_entries()
attestation = conversion.generate_attestation(
manifest, entries, conversion_timestamp="2027-06-01T00:00:00Z"
)
validation.validate_conversion_attestation(attestation)
assert attestation["final_outstanding_target"] == 0
assert attestation["final_development_credit"] == 67000
assert attestation["final_remission_credit"] == 33000
assert attestation["future_license"] == "MIT"
assert attestation["phase"] == "trsl:phase:example-001"
assert attestation["ledger_checkpoint"] == entries[-1]["id"]
def test_generate_attestation_rejects_unconverted_phase():
manifest = golden_manifest()
partial_entries = golden_entries()[:4] # Outstanding Target still 45000
with pytest.raises(ValueError, match="Outstanding Target"):
conversion.generate_attestation(
manifest, partial_entries, conversion_timestamp="2026-12-01T00:00:00Z"
)

View file

@ -0,0 +1,55 @@
import copy
import pytest
from conftest import golden_extension, load_json, FIXTURES_DIR
from target_revenue import validation
@pytest.mark.parametrize(
"name",
[
"development-license",
"cost-plus-operations",
"phase-sponsorship",
"service-with-development-allocation",
],
)
def test_canonical_stage0_extensions_are_conformant(name):
validation.validate_extension_contract(golden_extension(name))
def test_non_conforming_extension_is_rejected():
extension = load_json(FIXTURES_DIR / "non_conforming_extension.json")
with pytest.raises(validation.ConformanceError) as excinfo:
validation.validate_extension_contract(extension)
assert "Outstanding Target" in str(excinfo.value)
def test_extension_missing_required_field_is_rejected():
extension = copy.deepcopy(golden_extension("development-license"))
del extension["evidence"]
with pytest.raises(validation.ConformanceError):
validation.validate_extension_contract(extension)
def test_extension_bad_status_is_rejected():
extension = copy.deepcopy(golden_extension("development-license"))
extension["status"] = "endorsed" # not in {registered, canonical, deprecated}
with pytest.raises(validation.ConformanceError):
validation.validate_extension_contract(extension)
def test_core_term_redefinition_heuristic_is_case_insensitive():
extension = copy.deepcopy(golden_extension("development-license"))
extension["allocation"]["rule"] = "Development Credit means whatever the licensor prefers."
errors = validation.check_extension_core_term_redefinition(extension)
assert errors
assert "Development Credit" in errors[0]
def test_legitimate_rule_referencing_core_term_without_redefining_is_allowed():
extension = golden_extension("development-license")
# References "Development Credit" as a target, not a redefinition.
errors = validation.check_extension_core_term_redefinition(extension)
assert errors == []

133
tests/test_ledger_fold.py Normal file
View file

@ -0,0 +1,133 @@
import copy
import pytest
from conftest import golden_entries, golden_manifest
from target_revenue import fold, hashing, validation
def test_golden_entries_are_individually_conformant():
for entry in golden_entries():
validation.validate_ledger_entry(entry)
def test_golden_entries_currency_matches_phase():
errors = validation.check_currency_consistency(golden_manifest(), golden_entries())
assert errors == []
def test_currency_mismatch_is_detected():
manifest = golden_manifest()
entries = copy.deepcopy(golden_entries())
entries[0]["currency"] = "EUR"
errors = validation.check_currency_consistency(manifest, entries)
assert len(errors) == 1
assert "EUR" in errors[0]
def test_golden_hash_chain_is_valid():
hashing.verify_chain(golden_entries())
def test_tampered_entry_breaks_hash_chain():
entries = copy.deepcopy(golden_entries())
entries[2]["amount"] = 999999 # tamper after the chain was computed
with pytest.raises(ValueError, match="hash chain broken"):
hashing.verify_chain(entries)
def test_reordered_entries_break_hash_chain():
entries = copy.deepcopy(golden_entries())
entries[1], entries[2] = entries[2], entries[1]
with pytest.raises(ValueError, match="hash chain broken"):
hashing.verify_chain(entries)
def test_fold_matches_concept_section_23_first_checkpoint():
"""spec/TargetRevenueLicenseConcept.md §23: after the first four entries,
Development Credit $37,000, Remission Credit $18,000, Outstanding $45,000."""
manifest = golden_manifest()
first_four = golden_entries()[:4]
result = fold.fold_outstanding_target(
manifest["phase"]["initial_target"]["amount"], first_four
)
assert result.development_credit == 37000
assert result.remission_credit == 18000
assert result.outstanding_target == 45000
assert not result.is_converted
def test_fold_reaches_zero_and_converts():
"""spec/TargetRevenueLicenseConcept.md §23: full six-entry sequence
reaches Outstanding Target = 0 (Development Credit $67,000 + Remission
Credit $33,000 = Initial Target $100,000)."""
manifest = golden_manifest()
result = fold.fold_phase(manifest, golden_entries())
assert result.development_credit == 67000
assert result.remission_credit == 33000
assert result.outstanding_target == 0
assert result.is_converted
def test_credit_reversal_reduces_development_credit():
entries = [
{
"id": "trsl:entry:t0001",
"phase": "trsl:phase:t",
"type": "development-credit",
"amount": 5000,
"currency": "USD",
"recognized_at": "2026-01-01T00:00:00Z",
"extension": {"id": "trsl:extension:development-license", "version": "1.0"},
"evidence_reference": "confidential:evidence:t0001",
"previous_entry_hash": "GENESIS",
},
]
entries.append(
{
"id": "trsl:entry:t0002",
"phase": "trsl:phase:t",
"type": "credit-reversal",
"amount": 5000,
"currency": "USD",
"recognized_at": "2026-01-02T00:00:00Z",
"evidence_reference": "confidential:evidence:t0002-refund",
"previous_entry_hash": hashing.entry_hash(entries[0]),
"reverses": "trsl:entry:t0001",
}
)
result = fold.fold_outstanding_target(100000, entries)
assert result.development_credit == 0
assert result.outstanding_target == 100000
def test_remission_correction_can_reduce_over_remission():
entries = [
{
"id": "trsl:entry:t0001",
"phase": "trsl:phase:t",
"type": "remission-credit",
"amount": 20000,
"currency": "USD",
"recognized_at": "2026-01-01T00:00:00Z",
"extension": {"id": "trsl:policy:linear-longstop-v0", "version": "1.0"},
"evidence_reference": "confidential:evidence:t0001",
"previous_entry_hash": "GENESIS",
},
]
entries.append(
{
"id": "trsl:entry:t0002",
"phase": "trsl:phase:t",
"type": "remission-correction",
"amount": -5000,
"currency": "USD",
"recognized_at": "2026-01-02T00:00:00Z",
"evidence_reference": "confidential:evidence:t0002-correction",
"previous_entry_hash": hashing.entry_hash(entries[0]),
"reverses": "trsl:entry:t0001",
}
)
result = fold.fold_outstanding_target(100000, entries)
assert result.remission_credit == 15000
assert result.outstanding_target == 85000

View file

@ -0,0 +1,61 @@
import copy
import pytest
from conftest import golden_manifest
from target_revenue import validation
def test_golden_manifest_is_conformant():
validation.validate_phase_manifest(golden_manifest())
def test_manifest_missing_longstop_is_rejected():
manifest = copy.deepcopy(golden_manifest())
del manifest["phase"]["longstop_at"]
with pytest.raises(validation.ConformanceError):
validation.validate_phase_manifest(manifest)
def test_manifest_bad_future_license_is_rejected():
manifest = copy.deepcopy(golden_manifest())
manifest["phase"]["future_license"] = "GPL-3.0"
with pytest.raises(validation.ConformanceError):
validation.validate_phase_manifest(manifest)
def test_manifest_missing_required_field_is_rejected():
manifest = copy.deepcopy(golden_manifest())
del manifest["phase"]["initial_target"]
with pytest.raises(validation.ConformanceError):
validation.validate_phase_manifest(manifest)
def test_manifest_rejects_unknown_top_level_field():
manifest = copy.deepcopy(golden_manifest())
manifest["unexpected_field"] = True
with pytest.raises(validation.ConformanceError):
validation.validate_phase_manifest(manifest)
def test_initial_target_amount_immutable_across_versions():
published = golden_manifest()
candidate = copy.deepcopy(published)
candidate["phase"]["initial_target"]["amount"] = 70000
errors = validation.check_manifest_immutability(published, candidate)
assert errors, "reducing initial_target.amount must be flagged"
assert "initial_target.amount" in errors[0]
def test_phase_id_immutable_across_versions():
published = golden_manifest()
candidate = copy.deepcopy(published)
candidate["phase"]["id"] = "trsl:phase:example-002"
errors = validation.check_manifest_immutability(published, candidate)
assert errors
assert "phase.id" in errors[0]
def test_unchanged_manifest_has_no_immutability_violations():
manifest = golden_manifest()
assert validation.check_manifest_immutability(manifest, manifest) == []