2026-07-30 19:35:18 +02:00
|
|
|
|
---
|
|
|
|
|
|
id: TREV-WP-0013
|
|
|
|
|
|
type: workplan
|
|
|
|
|
|
title: "Remission Credit automation (degeneration policy execution)"
|
|
|
|
|
|
domain: infotech
|
|
|
|
|
|
repo: target-revenue
|
2026-08-05 16:00:06 +02:00
|
|
|
|
status: finished
|
2026-07-30 19:35:18 +02:00
|
|
|
|
owner: claude
|
|
|
|
|
|
topic_slug: infotech
|
|
|
|
|
|
created: "2026-07-30"
|
2026-08-05 16:00:06 +02:00
|
|
|
|
updated: "2026-08-05"
|
2026-07-30 19:36:30 +02:00
|
|
|
|
state_hub_workstream_id: "4a423219-2a3e-413c-b0dc-f8542c2afba1"
|
2026-07-30 19:35:18 +02:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
# Remission Credit automation (degeneration policy execution)
|
|
|
|
|
|
|
|
|
|
|
|
Spun out of `workplans/TREV-WP-0012-phase-provenance-and-policy-modeling.md`
|
|
|
|
|
|
(use case 4). Every Phase declares a `degeneration_policy`
|
|
|
|
|
|
(`trsl:policy:linear-longstop-v0` is the accepted v1 norm,
|
|
|
|
|
|
`specs/OpenQuestions-WorkingDefaults.md` Q7), which is supposed to
|
|
|
|
|
|
generate `remission-credit` Target Ledger entries over time when
|
|
|
|
|
|
Development Credit progress is insufficient (FR-6,
|
|
|
|
|
|
`specs/ProductRequirementsDocument.md`). **Nothing in this codebase
|
2026-08-05 16:00:06 +02:00
|
|
|
|
previously computed or wrote these entries.** `fold.py` can consume them
|
|
|
|
|
|
if they exist; this workplan produces them. This gap predated the Control
|
|
|
|
|
|
Plane UI work — it was never in scope for WP-0006 (Trust Service) or
|
|
|
|
|
|
WP-0009 (Control Plane), and surfaced when reviewing the UI's
|
2026-07-30 19:35:18 +02:00
|
|
|
|
Phase-registration flow prompted a fuller look at what a Phase's lifecycle
|
|
|
|
|
|
actually requires end to end.
|
|
|
|
|
|
|
|
|
|
|
|
Blocked on `workplans/TREV-WP-0012-phase-provenance-and-policy-modeling.md`
|
2026-08-05 16:00:06 +02:00
|
|
|
|
T03's decision (how a policy id maps to its spec file) — **unblocked**
|
|
|
|
|
|
when WP-0012 finished and WP-0015 landed `specs/policies/linear-longstop-v0.md`.
|
2026-07-30 19:35:18 +02:00
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: TREV-WP-0013-T01
|
2026-08-05 16:00:06 +02:00
|
|
|
|
status: done
|
2026-07-30 19:35:18 +02:00
|
|
|
|
priority: high
|
2026-07-30 19:36:30 +02:00
|
|
|
|
state_hub_task_id: "d6ab7448-c009-4398-b616-560e8085cf1a"
|
2026-07-30 19:35:18 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Design the remission calculation and scheduling model.** `R(t) = T0 ×
|
|
|
|
|
|
clamp((t - t0)/(tL - t0), 0, 1)` (Q7) needs: a chosen recognition cadence
|
|
|
|
|
|
(daily/monthly UTC per Q7's own text), an idempotent
|
|
|
|
|
|
"has this period's remission already been recorded" check (ledger entries
|
|
|
|
|
|
are append-only — a re-run must not double-remit), and a decision on
|
|
|
|
|
|
where `t0` (Phase activation) comes from if it isn't already a manifest
|
|
|
|
|
|
field (registration time? an explicit `activated_at`?). Cross-check
|
|
|
|
|
|
against whatever `trsl:policy:linear-longstop-v0`'s spec file (once
|
|
|
|
|
|
WP-0012-T03 lands) says, rather than re-deriving the formula from Q7
|
|
|
|
|
|
prose alone.
|
|
|
|
|
|
|
2026-08-05 16:00:06 +02:00
|
|
|
|
**Result (2026-08-05):** Design recorded in
|
|
|
|
|
|
`src/target_revenue/remission.py` module docstring and
|
|
|
|
|
|
`specs/policies/linear-longstop-v0.md` Status section:
|
|
|
|
|
|
|
|
|
|
|
|
1. **t0** = Trust Service `phase_manifests.registered_at`. No new
|
|
|
|
|
|
manifest `activated_at` field for Stage 0 — a Phase is not active
|
|
|
|
|
|
until registered. Pure callers pass `t0` explicitly.
|
|
|
|
|
|
2. **Cadence / idempotency**: cumulative delta model, not period-keyed
|
|
|
|
|
|
rows. Each run remits `max(0, R(as_of) − Σ policy remission-credit)`.
|
|
|
|
|
|
Re-run at the same `as_of` is a no-op; missed schedules catch up
|
|
|
|
|
|
without double-counting. Dust floor `MIN_REMISSION_AMOUNT = 0.01`.
|
|
|
|
|
|
Scheduled convention: monthly UTC (1st 00:00) or longstop if sooner.
|
|
|
|
|
|
3. **Actor**: dedicated Licensor credential labeled
|
|
|
|
|
|
`system:policy-engine` (rights operator), auto-issued on first use.
|
|
|
|
|
|
Never a null `submitted_by_token`; never a human credential for
|
|
|
|
|
|
policy-driven rows. Control Plane audit still records which human
|
|
|
|
|
|
*triggered* an on-demand run.
|
|
|
|
|
|
4. Formula checked against `specs/policies/linear-longstop-v0.md`, not
|
|
|
|
|
|
re-derived from Q7 prose alone.
|
|
|
|
|
|
|
2026-07-30 19:35:18 +02:00
|
|
|
|
```task
|
|
|
|
|
|
id: TREV-WP-0013-T02
|
2026-08-05 16:00:06 +02:00
|
|
|
|
status: done
|
2026-07-30 19:35:18 +02:00
|
|
|
|
priority: high
|
2026-07-30 19:36:30 +02:00
|
|
|
|
state_hub_task_id: "078da2e7-594b-4bd3-85b6-881a808f1ca2"
|
2026-07-30 19:35:18 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Implement and test** the calculation as a pure function (mirroring
|
|
|
|
|
|
`fold.py`'s determinism discipline) plus whatever writes the resulting
|
|
|
|
|
|
`remission-credit` entries into the hosted Target Ledger — a scheduled
|
|
|
|
|
|
job, an on-demand Control Plane action, or both. Decide which actor
|
|
|
|
|
|
"submits" these entries for attribution purposes (no human credential
|
|
|
|
|
|
naturally owns a policy-driven entry) and record that decision explicitly
|
|
|
|
|
|
rather than leaving `submitted_by_token` implicitly null.
|
|
|
|
|
|
|
2026-08-05 16:00:06 +02:00
|
|
|
|
**Result:** `src/target_revenue/remission.py` — pure
|
|
|
|
|
|
`cumulative_remission` / `plan_remission` / `build_remission_entry_input`
|
|
|
|
|
|
plus hosted `apply_remission_for_phase` /
|
|
|
|
|
|
`apply_remission_for_all_phases`. Trust Service routes:
|
|
|
|
|
|
`POST /phases/{id}/remission` (on-demand, Operator+),
|
|
|
|
|
|
`POST /remission/run` (batch for cron). Control Plane:
|
|
|
|
|
|
`control_plane.apply_policy_remission` + form on phase detail.
|
|
|
|
|
|
Tests: `tests/test_remission.py` (pure), `tests/test_remission_hosting.py`
|
|
|
|
|
|
(Docker Postgres — append, idempotency, `system:policy-engine`
|
|
|
|
|
|
attribution, metrics forecasts).
|
|
|
|
|
|
|
2026-07-30 19:35:18 +02:00
|
|
|
|
```task
|
|
|
|
|
|
id: TREV-WP-0013-T03
|
2026-08-05 16:00:06 +02:00
|
|
|
|
status: done
|
2026-07-30 19:35:18 +02:00
|
|
|
|
priority: medium
|
2026-07-30 19:36:30 +02:00
|
|
|
|
state_hub_task_id: "fe12ee80-2868-4857-83ca-04bd7223e270"
|
2026-07-30 19:35:18 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Surface it in the Control Plane UI**: `phase_detail.html`'s Ledger
|
|
|
|
|
|
table already renders `remission-credit` rows generically once they
|
|
|
|
|
|
exist; verify that holds, and add a metrics-level explanation (e.g. next
|
|
|
|
|
|
scheduled remission date/amount) if `metrics.py` doesn't already forecast
|
|
|
|
|
|
one.
|
2026-08-05 16:00:06 +02:00
|
|
|
|
|
|
|
|
|
|
**Result:** `metrics.compute_metrics` gains optional `activated_at` and
|
|
|
|
|
|
forecasts `remission_if_applied_now`, `next_scheduled_remission_at`,
|
|
|
|
|
|
`next_scheduled_remission_amount` (labeled **forecast**, never facts).
|
|
|
|
|
|
Hosted metrics and Control Plane phase detail pass
|
|
|
|
|
|
`registered_at` as t0. `phase_detail.html` shows the forecast table,
|
|
|
|
|
|
activation/longstop facts, and an Operator+ "Apply policy remission now"
|
|
|
|
|
|
button. Existing ledger table continues to render `remission-credit`
|
|
|
|
|
|
rows generically.
|