Split administrative-correction into typed development/remission corrections

Resolves the WP-0003-T06 review flag: the single administrative-correction
ledger entry type had an unstated fold-side effect (concept §17 names the
type but never says which side of the target it corrects). Maintainer chose
option (a) — split into administrative-correction-development and
administrative-correction-remission so the corrected side is explicit in
the type name rather than an implicit library default.

- schemas/ledger_entry.schema.json: enum split, no other behavior change.
- src/target_revenue/fold.py: each new type maps to its named side only.
- specs/TargetLedgerSpecification.md, TechnicalSpecificationDocument.md
  §3.2, ProductRequirementsDocument.md FR-5: updated to the split types.
- tests/test_ledger_fold.py: dedicated coverage for both new types plus a
  regression test that the old unsplit type name is now rejected.

spec/TargetRevenueLicenseConcept.md §17 is left unedited — its entry-type
list is explicitly non-exhaustive ("may include"), so this specializes
rather than contradicts it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-07-29 09:16:22 +02:00
parent 754d104b7a
commit 2b675c11ad
7 changed files with 129 additions and 29 deletions

View file

@ -32,10 +32,11 @@
"remission-credit", "remission-credit",
"credit-reversal", "credit-reversal",
"remission-correction", "remission-correction",
"administrative-correction", "administrative-correction-development",
"administrative-correction-remission",
"conversion-checkpoint" "conversion-checkpoint"
], ],
"description": "Closed set per TSD §3.2; not extensible per-project." "description": "Closed set per TSD §3.2; not extensible per-project. administrative-correction-development/-remission split 2026-07-29 to make the corrected target side explicit in the type name (specs/TargetLedgerSpecification.md §2)."
}, },
"amount": { "amount": {
"type": "number", "type": "number",

View file

@ -202,7 +202,7 @@ The Trust Service's five core responsibilities — Phase Registry, Extension Reg
### FR-5 — Target Ledger ### FR-5 — Target Ledger
**Requirement:** The framework shall define an append-only ledger entry schema supporting `development-credit`, `remission-credit`, `credit-reversal`, `remission-correction`, `administrative-correction`, and `conversion-checkpoint` entry types (§17). **Requirement:** The framework shall define an append-only ledger entry schema supporting `development-credit`, `remission-credit`, `credit-reversal`, `remission-correction`, `administrative-correction-development`, `administrative-correction-remission`, and `conversion-checkpoint` entry types (§17).
**Acceptance criteria:** **Acceptance criteria:**
- Every ledger entry carries a phase reference, amount, currency, recognition timestamp, applicable extension reference, evidence reference, and cryptographic linkage to the previous entry (§17 example). - Every ledger entry carries a phase reference, amount, currency, recognition timestamp, applicable extension reference, evidence reference, and cryptographic linkage to the previous entry (§17 example).

View file

@ -19,9 +19,12 @@ The Target Ledger is the append-only record of everything that moves a Phase's O
| `remission-credit` | `+= amount` to Remission Credit | Rule 5: never represented as revenue. | | `remission-credit` | `+= amount` to Remission Credit | Rule 5: never represented as revenue. |
| `credit-reversal` | `-= amount` from Development Credit | Rule 8: compensating entry; requires `reverses` pointing to the original entry id. Never edits or deletes the original. | | `credit-reversal` | `-= amount` from Development Credit | Rule 8: compensating entry; requires `reverses` pointing to the original entry id. Never edits or deletes the original. |
| `remission-correction` | `+= amount` to Remission Credit (amount may be negative) | Requires `reverses`. | | `remission-correction` | `+= amount` to Remission Credit (amount may be negative) | Requires `reverses`. |
| `administrative-correction` | `+= amount` to Development Credit | **Stage 0 simplification:** a generic correction bucket, not yet specialized by which side of the target it corrects. Revisit if this proves ambiguous in practice. | | `administrative-correction-development` | `+= amount` to Development Credit | Generic correction on the development side, not tied to a specific prior credit entry. |
| `administrative-correction-remission` | `+= amount` to Remission Credit | Generic correction on the remission side, not tied to a specific prior remission entry. |
| `conversion-checkpoint` | No numeric effect | Marker only. | | `conversion-checkpoint` | No numeric effect | Marker only. |
**Resolved 2026-07-29:** the concept document (§17) lists a single, non-exhaustive `administrative-correction` entry type ("permitted entry types **may include**...") without specifying which side of the target it adjusts. This extract specializes it into the two typed entries above, closing that ambiguity in the type name itself rather than leaving it as an unstated fold-side default. This does not contradict the concept document — its list was illustrative, not a closed enum — but it is a Stage 0 interpretation this extract adds beyond direct citation, so it remains listed for `workplans/TREV-WP-0003-normative-core-extraction.md` T06 review alongside the rest of this file.
This set is closed for Stage 0: a project **must not** invent new entry types; new monetization behavior belongs in an extension (`specs/MonetizationExtensionSpecification.md`), not a new ledger entry type. This set is closed for Stage 0: a project **must not** invent new entry types; new monetization behavior belongs in an extension (`specs/MonetizationExtensionSpecification.md`), not a new ledger entry type.
## 3. Required entry fields ## 3. Required entry fields
@ -53,8 +56,8 @@ Per Rule 8, the ledger is append-only. A correction or reversal is always a **ne
## 6. Outstanding Target fold (normative definition) ## 6. Outstanding Target fold (normative definition)
```text ```text
Outstanding Target = max(0, Initial Target Σ(development-credit, administrative-correction) Outstanding Target = max(0, Initial Target Σ(development-credit, administrative-correction-development)
Σ(remission-credit, remission-correction) Σ(remission-credit, remission-correction, administrative-correction-remission)
+ Σ(credit-reversal)) + Σ(credit-reversal))
``` ```

View file

@ -117,7 +117,7 @@ The following schemas formalize the YAML sketches already present in `spec/Targe
|---|---|---|---| |---|---|---|---|
| `entry.id` | string (URN, `trsl:entry:<ulid>`) | Required | Globally unique, monotonically orderable (ULID or equivalent). | | `entry.id` | string (URN, `trsl:entry:<ulid>`) | Required | Globally unique, monotonically orderable (ULID or equivalent). |
| `entry.phase` | string (Phase URN) | Required | Must reference an existing, published Phase Manifest. | | `entry.phase` | string (Phase URN) | Required | Must reference an existing, published Phase Manifest. |
| `entry.type` | enum: `development-credit`, `remission-credit`, `credit-reversal`, `remission-correction`, `administrative-correction`, `conversion-checkpoint` | Required | Closed set; not extensible per-project (PRD FR-5). | | `entry.type` | enum: `development-credit`, `remission-credit`, `credit-reversal`, `remission-correction`, `administrative-correction-development`, `administrative-correction-remission`, `conversion-checkpoint` | Required | Closed set; not extensible per-project (PRD FR-5). The two `administrative-correction-*` types were split 2026-07-29 so the corrected target side is explicit in the type name (`specs/TargetLedgerSpecification.md` §2). |
| `entry.amount` | decimal | Required | Signed only for reversal/correction types. | | `entry.amount` | decimal | Required | Signed only for reversal/correction types. |
| `entry.currency` | ISO 4217 code | Required | Must match `phase.initial_target.currency` or declare an explicit FX rule (PRD open question §14.6). | | `entry.currency` | ISO 4217 code | Required | Must match `phase.initial_target.currency` or declare an explicit FX rule (PRD open question §14.6). |
| `entry.recognized_at` | ISO 8601 timestamp | Required | Settlement time, not invoice time (Rule 4). | | `entry.recognized_at` | ISO 8601 timestamp | Required | Settlement time, not invoice time (Rule 4). |

View file

@ -24,7 +24,8 @@ _KNOWN_TYPES = frozenset(
"remission-credit", "remission-credit",
"credit-reversal", "credit-reversal",
"remission-correction", "remission-correction",
"administrative-correction", "administrative-correction-development",
"administrative-correction-remission",
"conversion-checkpoint", "conversion-checkpoint",
} }
) )
@ -50,17 +51,22 @@ def fold_outstanding_target(
"""Fold an ordered list of ledger entries into cumulative totals. """Fold an ordered list of ledger entries into cumulative totals.
Entry-type effects (Stage 0): Entry-type effects (Stage 0):
development-credit += amount to Development Credit development-credit += amount to Development Credit
remission-credit += amount to Remission Credit remission-credit += amount to Remission Credit
credit-reversal -= amount from Development Credit (Rule 8: credit-reversal -= amount from Development Credit
compensating entry, never edits the (Rule 8: compensating entry,
original) never edits the original)
remission-correction += amount to Remission Credit (amount may be remission-correction += amount to Remission Credit
negative to correct an over-remission) (amount may be negative to
administrative-correction += amount to Development Credit (Stage 0 correct an over-remission)
simplification: a generic correction administrative-correction-development += amount to Development Credit
bucket; not yet specialized by target side) administrative-correction-remission += amount to Remission Credit
conversion-checkpoint no numeric effect (marker only) (split 2026-07-29 so the
corrected target side is
explicit in the type name,
replacing the single ambiguous
administrative-correction type)
conversion-checkpoint no numeric effect (marker only)
""" """
development_credit = 0.0 development_credit = 0.0
remission_credit = 0.0 remission_credit = 0.0
@ -81,8 +87,10 @@ def fold_outstanding_target(
development_credit -= amount development_credit -= amount
elif entry_type == "remission-correction": elif entry_type == "remission-correction":
remission_credit += amount remission_credit += amount
elif entry_type == "administrative-correction": elif entry_type == "administrative-correction-development":
development_credit += amount development_credit += amount
elif entry_type == "administrative-correction-remission":
remission_credit += amount
outstanding_target = max( outstanding_target = max(
0.0, initial_target_amount - development_credit - remission_credit 0.0, initial_target_amount - development_credit - remission_credit

View file

@ -131,3 +131,81 @@ def test_remission_correction_can_reduce_over_remission():
result = fold.fold_outstanding_target(100000, entries) result = fold.fold_outstanding_target(100000, entries)
assert result.remission_credit == 15000 assert result.remission_credit == 15000
assert result.outstanding_target == 85000 assert result.outstanding_target == 85000
def test_administrative_correction_development_adjusts_development_side():
entries = [
{
"id": "trsl:entry:t0001",
"phase": "trsl:phase:t",
"type": "administrative-correction-development",
"amount": 3000,
"currency": "USD",
"recognized_at": "2026-01-01T00:00:00Z",
"evidence_reference": "confidential:evidence:t0001-admin",
"previous_entry_hash": "GENESIS",
},
]
result = fold.fold_outstanding_target(100000, entries)
assert result.development_credit == 3000
assert result.remission_credit == 0
assert result.outstanding_target == 97000
def test_administrative_correction_remission_adjusts_remission_side():
entries = [
{
"id": "trsl:entry:t0001",
"phase": "trsl:phase:t",
"type": "administrative-correction-remission",
"amount": 3000,
"currency": "USD",
"recognized_at": "2026-01-01T00:00:00Z",
"evidence_reference": "confidential:evidence:t0001-admin",
"previous_entry_hash": "GENESIS",
},
]
result = fold.fold_outstanding_target(100000, entries)
assert result.development_credit == 0
assert result.remission_credit == 3000
assert result.outstanding_target == 97000
def test_administrative_correction_types_are_schema_valid():
from target_revenue import validation
for entry_type in (
"administrative-correction-development",
"administrative-correction-remission",
):
entry = {
"id": "trsl:entry:t0001",
"phase": "trsl:phase:t",
"type": entry_type,
"amount": 100,
"currency": "USD",
"recognized_at": "2026-01-01T00:00:00Z",
"evidence_reference": "confidential:evidence:t0001",
"previous_entry_hash": "GENESIS",
}
validation.validate_ledger_entry(entry)
def test_old_unsplit_administrative_correction_type_is_no_longer_valid():
"""The single ambiguous administrative-correction type was split
2026-07-29 (specs/TargetLedgerSpecification.md §2); it must no longer
validate as a known type."""
from target_revenue import validation
entry = {
"id": "trsl:entry:t0001",
"phase": "trsl:phase:t",
"type": "administrative-correction",
"amount": 100,
"currency": "USD",
"recognized_at": "2026-01-01T00:00:00Z",
"evidence_reference": "confidential:evidence:t0001",
"previous_entry_hash": "GENESIS",
}
with pytest.raises(validation.ConformanceError):
validation.validate_ledger_entry(entry)

View file

@ -94,11 +94,21 @@ state_hub_task_id: "ab11ec85-9c4c-4ca1-bc58-a0a044241c01"
Result 2026-07-29: `specs/TargetLedgerSpecification.md` (TRF-Ledger-0.1) Result 2026-07-29: `specs/TargetLedgerSpecification.md` (TRF-Ledger-0.1)
produced, aligned with `schemas/ledger_entry.schema.json` and produced, aligned with `schemas/ledger_entry.schema.json` and
`src/target_revenue/fold.py`/`hashing.py`. Flags the `src/target_revenue/fold.py`/`hashing.py`. Flagged the
`administrative-correction` fold-side effect as a Stage 0 simplification `administrative-correction` fold-side effect as a Stage 0 simplification
(concept §17 names the entry type but does not specify which side of the (concept §17 names the entry type but does not specify which side of the
target it corrects) — called out explicitly for T06 review rather than target it corrects) for T06 review.
presented as settled.
Result 2026-07-29 (same day, human decision): **Resolved** — maintainer
chose to split the ambiguous type into `administrative-correction-development`
and `administrative-correction-remission` (option (a) of three offered).
Updated `schemas/ledger_entry.schema.json`, `src/target_revenue/fold.py`,
`specs/TargetLedgerSpecification.md` §2/§6, `specs/TechnicalSpecificationDocument.md`
§3.2, and `specs/ProductRequirementsDocument.md` FR-5. Added dedicated test
coverage (`tests/test_ledger_fold.py`) for both new types plus a regression
test confirming the old unsplit type name is now rejected. `spec/TargetRevenueLicenseConcept.md`
§17 was left unedited — its entry-type list is explicitly non-exhaustive
("may include"), so the split specializes rather than contradicts it.
Produce `specs/TargetLedgerSpecification.md` from concept §17 and TSD §3.2: Produce `specs/TargetLedgerSpecification.md` from concept §17 and TSD §3.2:
@ -182,12 +192,12 @@ accept):
`schemas/phase_manifest.schema.json` (already implemented, WP-0002-T02); `schemas/phase_manifest.schema.json` (already implemented, WP-0002-T02);
`phase.longstop_at` is explicitly labeled a Stage 0 working default `phase.longstop_at` is explicitly labeled a Stage 0 working default
(Q8), not silently presented as permanent Rule 1 scope. (Q8), not silently presented as permanent Rule 1 scope.
- [ ] `specs/TargetLedgerSpecification.md` — entry-type table matches - [x] `specs/TargetLedgerSpecification.md` — entry-type table matches
`schemas/ledger_entry.schema.json`; the `administrative-correction` `schemas/ledger_entry.schema.json`. **Resolved 2026-07-29:** the
"Stage 0 simplification" note in §2 is flagged for maintainer judgment `administrative-correction` ambiguity was reviewed and split into
— this is the one place this extraction added interpretation beyond a `administrative-correction-development` / `-remission` (maintainer's
direct concept citation (concept §17 lists the entry type but does not choice of option (a)); this was the one place the extraction added
specify its fold-side effect). interpretation beyond a direct concept citation, and it is now closed.
- [ ] `specs/MonetizationExtensionSpecification.md` — six-field contract - [ ] `specs/MonetizationExtensionSpecification.md` — six-field contract
matches concept §12; registered/canonical distinction matches concept matches concept §12; registered/canonical distinction matches concept
§12.1§12.2; Stage 0 catalog table (Q11) correctly shows §12.1§12.2; Stage 0 catalog table (Q11) correctly shows