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:
parent
754d104b7a
commit
2b675c11ad
7 changed files with 129 additions and 29 deletions
|
|
@ -32,10 +32,11 @@
|
|||
"remission-credit",
|
||||
"credit-reversal",
|
||||
"remission-correction",
|
||||
"administrative-correction",
|
||||
"administrative-correction-development",
|
||||
"administrative-correction-remission",
|
||||
"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": {
|
||||
"type": "number",
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ The Trust Service's five core responsibilities — Phase Registry, Extension Reg
|
|||
|
||||
### 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:**
|
||||
- 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).
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
| `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`. |
|
||||
| `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. |
|
||||
|
||||
**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.
|
||||
|
||||
## 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)
|
||||
|
||||
```text
|
||||
Outstanding Target = max(0, Initial Target − Σ(development-credit, administrative-correction)
|
||||
− Σ(remission-credit, remission-correction)
|
||||
Outstanding Target = max(0, Initial Target − Σ(development-credit, administrative-correction-development)
|
||||
− Σ(remission-credit, remission-correction, administrative-correction-remission)
|
||||
+ Σ(credit-reversal))
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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.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.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). |
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ _KNOWN_TYPES = frozenset(
|
|||
"remission-credit",
|
||||
"credit-reversal",
|
||||
"remission-correction",
|
||||
"administrative-correction",
|
||||
"administrative-correction-development",
|
||||
"administrative-correction-remission",
|
||||
"conversion-checkpoint",
|
||||
}
|
||||
)
|
||||
|
|
@ -50,17 +51,22 @@ def fold_outstanding_target(
|
|||
"""Fold an ordered list of ledger entries into cumulative totals.
|
||||
|
||||
Entry-type effects (Stage 0):
|
||||
development-credit += amount to Development Credit
|
||||
remission-credit += amount to Remission Credit
|
||||
credit-reversal -= amount from Development Credit (Rule 8:
|
||||
compensating entry, never edits the
|
||||
original)
|
||||
remission-correction += amount to Remission Credit (amount may be
|
||||
negative to correct an over-remission)
|
||||
administrative-correction += amount to Development Credit (Stage 0
|
||||
simplification: a generic correction
|
||||
bucket; not yet specialized by target side)
|
||||
conversion-checkpoint no numeric effect (marker only)
|
||||
development-credit += amount to Development Credit
|
||||
remission-credit += amount to Remission Credit
|
||||
credit-reversal -= amount from Development Credit
|
||||
(Rule 8: compensating entry,
|
||||
never edits the original)
|
||||
remission-correction += amount to Remission Credit
|
||||
(amount may be negative to
|
||||
correct an over-remission)
|
||||
administrative-correction-development += amount to Development Credit
|
||||
administrative-correction-remission += amount to Remission Credit
|
||||
(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
|
||||
remission_credit = 0.0
|
||||
|
|
@ -81,8 +87,10 @@ def fold_outstanding_target(
|
|||
development_credit -= amount
|
||||
elif entry_type == "remission-correction":
|
||||
remission_credit += amount
|
||||
elif entry_type == "administrative-correction":
|
||||
elif entry_type == "administrative-correction-development":
|
||||
development_credit += amount
|
||||
elif entry_type == "administrative-correction-remission":
|
||||
remission_credit += amount
|
||||
|
||||
outstanding_target = max(
|
||||
0.0, initial_target_amount - development_credit - remission_credit
|
||||
|
|
|
|||
|
|
@ -131,3 +131,81 @@ def test_remission_correction_can_reduce_over_remission():
|
|||
result = fold.fold_outstanding_target(100000, entries)
|
||||
assert result.remission_credit == 15000
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -94,11 +94,21 @@ state_hub_task_id: "ab11ec85-9c4c-4ca1-bc58-a0a044241c01"
|
|||
|
||||
Result 2026-07-29: `specs/TargetLedgerSpecification.md` (TRF-Ledger-0.1)
|
||||
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
|
||||
(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
|
||||
presented as settled.
|
||||
target it corrects) for T06 review.
|
||||
|
||||
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:
|
||||
|
||||
|
|
@ -182,12 +192,12 @@ accept):
|
|||
`schemas/phase_manifest.schema.json` (already implemented, WP-0002-T02);
|
||||
`phase.longstop_at` is explicitly labeled a Stage 0 working default
|
||||
(Q8), not silently presented as permanent Rule 1 scope.
|
||||
- [ ] `specs/TargetLedgerSpecification.md` — entry-type table matches
|
||||
`schemas/ledger_entry.schema.json`; the `administrative-correction`
|
||||
"Stage 0 simplification" note in §2 is flagged for maintainer judgment
|
||||
— this is the one place this extraction added interpretation beyond a
|
||||
direct concept citation (concept §17 lists the entry type but does not
|
||||
specify its fold-side effect).
|
||||
- [x] `specs/TargetLedgerSpecification.md` — entry-type table matches
|
||||
`schemas/ledger_entry.schema.json`. **Resolved 2026-07-29:** the
|
||||
`administrative-correction` ambiguity was reviewed and split into
|
||||
`administrative-correction-development` / `-remission` (maintainer's
|
||||
choice of option (a)); this was the one place the extraction added
|
||||
interpretation beyond a direct concept citation, and it is now closed.
|
||||
- [ ] `specs/MonetizationExtensionSpecification.md` — six-field contract
|
||||
matches concept §12; registered/canonical distinction matches concept
|
||||
§12.1–§12.2; Stage 0 catalog table (Q11) correctly shows
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue