2026-08-03 20:31:52 +02:00
|
|
|
|
---
|
|
|
|
|
|
id: TREV-WP-0015
|
|
|
|
|
|
type: workplan
|
|
|
|
|
|
title: "Implement Phase provenance, spec-file, and ledger UI changes"
|
|
|
|
|
|
domain: infotech
|
|
|
|
|
|
repo: target-revenue
|
2026-08-03 23:57:46 +02:00
|
|
|
|
status: finished
|
2026-08-03 20:31:52 +02:00
|
|
|
|
owner: claude
|
|
|
|
|
|
topic_slug: infotech
|
|
|
|
|
|
created: "2026-08-03"
|
|
|
|
|
|
updated: "2026-08-03"
|
2026-08-03 20:33:01 +02:00
|
|
|
|
state_hub_workstream_id: "fe1bedd3-dadc-41cc-a669-92623743e620"
|
2026-08-03 20:31:52 +02:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
# Implement Phase provenance, spec-file, and ledger UI changes
|
|
|
|
|
|
|
|
|
|
|
|
Implements `specs/PhaseProvenanceSpecAddendum.md`, accepted 2026-08-03,
|
|
|
|
|
|
which itself synthesizes the decisions from
|
|
|
|
|
|
`workplans/TREV-WP-0012-phase-provenance-and-policy-modeling.md` T02–T04.
|
|
|
|
|
|
Task numbering below matches the addendum's §6 suggested breakdown
|
|
|
|
|
|
1:1 — each task cites its addendum section as the authoritative source of
|
|
|
|
|
|
what to build, rather than re-deriving field shapes or rationale here.
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: TREV-WP-0015-T01
|
2026-08-03 20:43:30 +02:00
|
|
|
|
status: done
|
2026-08-03 20:31:52 +02:00
|
|
|
|
priority: high
|
2026-08-03 20:33:01 +02:00
|
|
|
|
state_hub_task_id: "cdca0ef5-a332-40e2-b165-10ffa238f413"
|
2026-08-03 20:31:52 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Schema change** (addendum §1): add `repo_hub`, `repo_hub_uri`,
|
|
|
|
|
|
`repo_id`, `repo_name` as required properties of
|
|
|
|
|
|
`phase.milestone_release`, and `phase.base_phase_id` as an optional
|
|
|
|
|
|
property of `phase`, to `schemas/phase_manifest.schema.json`. Update
|
|
|
|
|
|
`validation.py` if the new pattern/format constraints need anything
|
|
|
|
|
|
beyond what JSON Schema's own `pattern`/`format` keywords already
|
|
|
|
|
|
enforce (they shouldn't). Add offline tests: a manifest missing any of
|
|
|
|
|
|
the four new required fields is rejected; one with `base_phase_id`
|
|
|
|
|
|
absent validates (first Phase); one with it present and correctly
|
|
|
|
|
|
patterned validates (successive Phase).
|
|
|
|
|
|
|
2026-08-03 20:43:30 +02:00
|
|
|
|
**Result:** Schema updated exactly as specified; `validation.py` needed
|
|
|
|
|
|
no changes (`pattern`/`format`/`minimum` already sufficient). Added six
|
|
|
|
|
|
tests to `tests/test_manifest_validation.py` covering all four new
|
|
|
|
|
|
required fields and both `base_phase_id` states plus a malformed one.
|
|
|
|
|
|
|
|
|
|
|
|
Updating `examples/phase-001/manifest.json` (the fixture 13 other test
|
|
|
|
|
|
files depend on) turned out to be necessary just to keep the offline
|
|
|
|
|
|
suite green, not optional — done inline with placeholder-but-plausible
|
|
|
|
|
|
values (`repo_hub: "example-hub"`, synthetic id/name), since it's a
|
|
|
|
|
|
fictional fixture, not a real repo.
|
|
|
|
|
|
|
|
|
|
|
|
**Discovered scope gap, fixed inline rather than left broken**: the
|
|
|
|
|
|
addendum's task breakdown didn't call out that the Control Plane's
|
|
|
|
|
|
existing Phase registration form (`phase_new.html`,
|
|
|
|
|
|
`control_plane_app.py`) would stop working the moment this schema
|
|
|
|
|
|
change landed, since nothing collected the four new required fields.
|
|
|
|
|
|
Fixed as part of this task rather than leaving registration broken
|
|
|
|
|
|
between tasks — this also completes T04's registration-form half (drop
|
|
|
|
|
|
`ledger` input, auto-compute it) and adds the `base_phase_id` field,
|
|
|
|
|
|
since all three changes touch the same form/route. `phase_detail.html`'s
|
|
|
|
|
|
ledger-reference drill-down (T04's other half) is done too. Full suite:
|
|
|
|
|
|
94 passing offline (up from 84 — six new schema tests, three new
|
|
|
|
|
|
provenance-backfill tests, two new Control Plane form tests), 158
|
|
|
|
|
|
passing with Docker (up from 146).
|
|
|
|
|
|
|
2026-08-03 20:31:52 +02:00
|
|
|
|
```task
|
|
|
|
|
|
id: TREV-WP-0015-T02
|
2026-08-03 20:49:22 +02:00
|
|
|
|
status: done
|
2026-08-03 20:31:52 +02:00
|
|
|
|
priority: high
|
2026-08-03 20:33:01 +02:00
|
|
|
|
state_hub_task_id: "8b5ab6ea-fa80-4e08-8733-2faad810962e"
|
2026-08-03 20:31:52 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Spec-file extraction** (addendum §2): create `specs/policies/
|
|
|
|
|
|
linear-longstop-v0.md` (extracted from `specs/
|
|
|
|
|
|
OpenQuestions-WorkingDefaults.md` Q7's prose, with `policy_id`/`title`
|
|
|
|
|
|
frontmatter); create the six `specs/profiles/*.md` files (extracted from
|
|
|
|
|
|
`specs/CanonicalMonetizationProfiles.md` §1–§6, with `extension_id`/
|
|
|
|
|
|
`title` frontmatter), leaving §7 (cross-profile summary) and §8
|
|
|
|
|
|
(non-goals) in place as the overview document. Add `calculator_id`/
|
|
|
|
|
|
`revision`/`title` frontmatter to `specs/
|
|
|
|
|
|
DevelopmentEffortCalculatorConcept.md` (no move). Update
|
|
|
|
|
|
`OpenQuestions-WorkingDefaults.md` Q7 to point at the new file rather
|
|
|
|
|
|
than contain the policy's content directly.
|
|
|
|
|
|
|
2026-08-03 20:49:22 +02:00
|
|
|
|
**Result:** `specs/policies/linear-longstop-v0.md` and all six
|
|
|
|
|
|
`specs/profiles/*.md` files created exactly as scoped, each with the
|
|
|
|
|
|
decided frontmatter shape. `CanonicalMonetizationProfiles.md` §1–§6
|
|
|
|
|
|
replaced with a linked list pointing at the new files; §7 (cross-profile
|
|
|
|
|
|
summary) and §8 (non-goals) untouched. `OpenQuestions-WorkingDefaults.md`
|
|
|
|
|
|
Q7 now records only the adoption decision, pointing at the policy file
|
|
|
|
|
|
for content. `DevelopmentEffortCalculatorConcept.md` got its
|
|
|
|
|
|
`calculator_id`/`revision`/`title` frontmatter, no move (already
|
|
|
|
|
|
conformed). Checked for stale cross-references before editing — nothing
|
|
|
|
|
|
in the repo links to the removed `CanonicalMonetizationProfiles.md`
|
|
|
|
|
|
section anchors (`grep` confirmed), so no other file needed updating.
|
|
|
|
|
|
Offline suite unaffected (no code reads these doc files' content) —
|
|
|
|
|
|
94 passing, unchanged.
|
|
|
|
|
|
|
2026-08-03 20:31:52 +02:00
|
|
|
|
```task
|
|
|
|
|
|
id: TREV-WP-0015-T03
|
2026-08-03 23:45:59 +02:00
|
|
|
|
status: done
|
2026-08-03 20:31:52 +02:00
|
|
|
|
priority: medium
|
2026-08-03 20:33:01 +02:00
|
|
|
|
state_hub_task_id: "681002d5-582b-4ddd-8b1f-dc25ce5b0e5a"
|
2026-08-03 20:31:52 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Control Plane reference-rendering routes** (addendum §2): add
|
|
|
|
|
|
`GET /reference/policies/{slug}` and `GET /reference/profiles/{slug}` to
|
|
|
|
|
|
`service/control_plane_app.py`, rendering the corresponding
|
|
|
|
|
|
`specs/policies/`/`specs/profiles/` markdown file to HTML server-side at
|
|
|
|
|
|
request time (a small Python markdown library — confirm one is already
|
|
|
|
|
|
an acceptable addition to the `service` extras in `pyproject.toml`, or
|
|
|
|
|
|
add it). Read-only, no edit capability. Link to these routes from
|
|
|
|
|
|
wherever a policy id or extension id already appears in
|
|
|
|
|
|
`phase_detail.html`/`phase_new.html`.
|
|
|
|
|
|
|
2026-08-03 23:45:59 +02:00
|
|
|
|
**Result:** New module `service/reference_docs.py` (`load_reference_doc`,
|
|
|
|
|
|
`policy_slug_from_id`, `extension_slug_from_id`), kept separate from
|
|
|
|
|
|
`control_plane_app.py` per this project's pattern of separating HTTP
|
|
|
|
|
|
framing from logic. One parameterized route,
|
|
|
|
|
|
`GET /reference/{kind}/{slug}` (`kind` restricted to `policies`/
|
|
|
|
|
|
`profiles`), satisfies both addendum URL shapes without two near-identical
|
|
|
|
|
|
route functions. Added `markdown>=3.6` and `PyYAML>=6.0` to the `service`
|
|
|
|
|
|
extras (frontmatter parsing).
|
|
|
|
|
|
|
|
|
|
|
|
**Discovered along the way**: `phase_detail.html`'s Status table never
|
|
|
|
|
|
displayed `manifest.phase.degeneration_policy` at all — there was no
|
|
|
|
|
|
existing spot to attach a link to. Added the row (with the reference
|
|
|
|
|
|
link) as part of wiring this in, rather than leaving the policy id
|
|
|
|
|
|
invisible on the one page where a human would want to check it.
|
|
|
|
|
|
`phase_new.html` gets a plain link next to the field (only one policy
|
|
|
|
|
|
exists today, so a static link is sufficient; revisit if a second policy
|
|
|
|
|
|
is ever offered as a choice there).
|
|
|
|
|
|
|
|
|
|
|
|
**Deliberately not done in this task**: extension ids don't currently
|
|
|
|
|
|
appear anywhere in the Control Plane UI at all (Phase registration
|
|
|
|
|
|
doesn't collect `extensions`, and the ledger-entry form's `extension_id`
|
|
|
|
|
|
is free-typed, not selected from a registered list) — that's WP-0014's
|
|
|
|
|
|
gap (Extension Registry has no UI yet), not something to silently expand
|
|
|
|
|
|
here. `extension_slug_from_id`/the `profiles` reference route exist and
|
|
|
|
|
|
are tested directly; wiring an actual extension-id link into the UI
|
|
|
|
|
|
waits for WP-0014.
|
|
|
|
|
|
|
|
|
|
|
|
6 new Docker-gated tests (`test_reference_policy_doc_renders`,
|
|
|
|
|
|
`_profile_doc_renders`, `_unknown_slug_is_404`, `_unknown_kind_is_404`,
|
|
|
|
|
|
`_requires_login`, `test_phase_detail_links_to_policy_reference`). Full
|
|
|
|
|
|
suite: 94 passing offline (unchanged, this task touches no offline
|
|
|
|
|
|
code path), 164 passing with Docker (up from 158).
|
|
|
|
|
|
|
2026-08-03 20:31:52 +02:00
|
|
|
|
```task
|
|
|
|
|
|
id: TREV-WP-0015-T04
|
2026-08-03 20:43:30 +02:00
|
|
|
|
status: done
|
2026-08-03 20:31:52 +02:00
|
|
|
|
priority: medium
|
2026-08-03 20:33:01 +02:00
|
|
|
|
state_hub_task_id: "af660317-e732-4ad7-bd4a-86ada8602ec7"
|
2026-08-03 20:31:52 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Ledger UI change** (addendum §3): drop the `ledger` input from
|
|
|
|
|
|
`phase_new.html`; have the registration route auto-compute
|
|
|
|
|
|
`phase.ledger` as the canonical `/phases/{phase_id}/ledger` reference
|
|
|
|
|
|
before validation/persistence. Add a small "ledger reference"
|
|
|
|
|
|
link/disclosure to `phase_detail.html` showing the raw reference plus a
|
|
|
|
|
|
live-data link when it resolves to this same instance. The existing
|
|
|
|
|
|
Ledger entries table is untouched.
|
|
|
|
|
|
|
2026-08-03 20:43:30 +02:00
|
|
|
|
**Result:** Done as part of T01's fix (same form/route touched by both
|
|
|
|
|
|
tasks — see T01's Result). `control_plane_app.py`'s registration route
|
|
|
|
|
|
sets `phase["ledger"] = f"/phases/{phase_id}/ledger"` before validation;
|
|
|
|
|
|
`phase_new.html` no longer has a `ledger` input at all.
|
|
|
|
|
|
`phase_detail.html` gained a `<details>` disclosure showing the raw
|
|
|
|
|
|
reference plus a live-JSON link when it resolves to this instance
|
|
|
|
|
|
(`manifest.phase.ledger.startswith('/phases/')`). Regression test:
|
|
|
|
|
|
`test_phase_new_form_has_no_ledger_input`.
|
|
|
|
|
|
|
2026-08-03 20:31:52 +02:00
|
|
|
|
```task
|
|
|
|
|
|
id: TREV-WP-0015-T05
|
2026-08-03 23:57:46 +02:00
|
|
|
|
status: done
|
2026-08-03 20:31:52 +02:00
|
|
|
|
priority: high
|
2026-08-03 20:33:01 +02:00
|
|
|
|
state_hub_task_id: "816f1ac2-da40-4550-8d6c-64a1a335a660"
|
2026-08-03 20:31:52 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**`forgejo_hubs` migration** (addendum §4): new migration
|
|
|
|
|
|
`migrations/0007_forgejo_hubs.sql`, table `forgejo_hubs (hub_slug
|
|
|
|
|
|
PRIMARY KEY, service_uri, first_seen_at, updated_at)`, auto-populated on
|
|
|
|
|
|
first sight of a `repo_hub` during Phase registration (mirroring
|
|
|
|
|
|
`ensure_licensor_identity`'s auto-create-on-first-INSERT trigger
|
|
|
|
|
|
pattern in `migrations/0005_licensor_credentials.sql`). Decide during
|
|
|
|
|
|
implementation (not a T02-style human gate, per the addendum) whether
|
|
|
|
|
|
correcting a hub's URI after the fact is a `SECURITY DEFINER` governance
|
|
|
|
|
|
action or a plain `UPDATE`, and document whichever is chosen.
|
|
|
|
|
|
|
2026-08-03 23:57:46 +02:00
|
|
|
|
**Result:** `migrations/0007_forgejo_hubs.sql` — the `BEFORE INSERT`
|
|
|
|
|
|
trigger on `phase_manifests` reads `repo_hub`/`repo_hub_uri` straight out
|
|
|
|
|
|
of the manifest JSONB (`NEW.manifest #>> '{phase,milestone_release,
|
|
|
|
|
|
repo_hub}'`) rather than needing those as top-level table columns, since
|
|
|
|
|
|
`phase_manifests` stores the whole manifest as one `jsonb` blob.
|
|
|
|
|
|
`ON CONFLICT (hub_slug) DO NOTHING` — a hub already seen is left alone by
|
|
|
|
|
|
the trigger; only a second registration under the same slug with a
|
|
|
|
|
|
different URI would otherwise silently overwrite it, which is exactly
|
|
|
|
|
|
the failure mode this table exists to prevent.
|
|
|
|
|
|
|
|
|
|
|
|
**Decided: `SECURITY DEFINER` governance action**, not a plain `UPDATE`
|
|
|
|
|
|
— `correct_forgejo_hub_uri(hub_slug, new_uri)`, matching
|
|
|
|
|
|
`set_extension_status()`/`revoke_credential()`. `trf_app` has no UPDATE
|
|
|
|
|
|
grant on `forgejo_hubs` at all. Thin Python wrappers added to
|
|
|
|
|
|
`registry.py`: `get_forgejo_hub()`, `correct_forgejo_hub_uri()` (raises
|
|
|
|
|
|
`RegistrationError` for an unknown slug, via `psycopg.errors.
|
|
|
|
|
|
RaiseException`).
|
|
|
|
|
|
|
|
|
|
|
|
New `tests/test_forgejo_hubs.py`, 6 Docker-gated tests: auto-creation on
|
|
|
|
|
|
first registration, unknown-hub lookup, second-registration-same-hub
|
|
|
|
|
|
does *not* overwrite, explicit correction does, correction of an unknown
|
|
|
|
|
|
hub is rejected, and DB-level UPDATE rejection for the application role.
|
|
|
|
|
|
Full suite: 94 passing offline (unchanged), 170 passing with Docker (up
|
|
|
|
|
|
from 164).
|
|
|
|
|
|
|
2026-08-03 20:31:52 +02:00
|
|
|
|
```task
|
|
|
|
|
|
id: TREV-WP-0015-T06
|
2026-08-03 20:43:30 +02:00
|
|
|
|
status: done
|
2026-08-03 20:31:52 +02:00
|
|
|
|
priority: medium
|
2026-08-03 20:33:01 +02:00
|
|
|
|
state_hub_task_id: "719aa6db-93d8-4eaa-ac1b-e7ee4c00bd89"
|
2026-08-03 20:31:52 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Backfill the three example manifests** (addendum §5):
|
|
|
|
|
|
`examples/pilot-candidates/{net-kingdom-local-identity,
|
|
|
|
|
|
railiance-vergabe-teilnahme, info-tech-canon-service-surface}/manifest.json`
|
|
|
|
|
|
each get `repo_hub`/`repo_hub_uri`/`repo_id`/`repo_name` from their real
|
|
|
|
|
|
Forgejo data (`GET /api/v1/repos/{owner}/{repo}`, same call already
|
|
|
|
|
|
confirmed working for `target-revenue` itself during WP-0012-T02), no
|
|
|
|
|
|
`base_phase_id` (all three are first Phases), and `ledger` set to match
|
|
|
|
|
|
what the Control Plane would now auto-compute.
|
|
|
|
|
|
|
2026-08-03 20:43:30 +02:00
|
|
|
|
**Result:** Confirmed live against `forgejo.coulomb.social` (all three
|
|
|
|
|
|
repos are `coulomb/*`, same org as `target-revenue`): `net-kingdom` id
|
|
|
|
|
|
`67`, `vergabe-teilnahme` id `62` (the actual application repo per
|
|
|
|
|
|
`history/260730-EffortCalculator-CandidateApplication.md` §3, not the
|
|
|
|
|
|
`railiance-apps` deployment repo), `info-tech-canon` id `47`. All three
|
|
|
|
|
|
manifests backfilled with real, not placeholder, ids; `ledger` set to
|
|
|
|
|
|
`/phases/{phase_id}/ledger` matching §3's auto-compute convention. New
|
|
|
|
|
|
regression test: `test_pilot_candidate_manifest_carries_real_repo_provenance`.
|
|
|
|
|
|
|
2026-08-03 20:31:52 +02:00
|
|
|
|
```task
|
|
|
|
|
|
id: TREV-WP-0015-T07
|
2026-08-03 23:57:46 +02:00
|
|
|
|
status: done
|
2026-08-03 20:31:52 +02:00
|
|
|
|
priority: high
|
2026-08-03 20:33:01 +02:00
|
|
|
|
state_hub_task_id: "39b34438-a1b1-49da-a88a-f3bec926aa89"
|
2026-08-03 20:31:52 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Tests, docs, and closeout**: offline schema/validation tests (T01),
|
|
|
|
|
|
Docker-gated `TestClient` tests for T03's new routes and T04/T05's
|
|
|
|
|
|
changes (extending `tests/test_control_plane_app.py`), a rendering smoke
|
|
|
|
|
|
test that each `specs/policies/`/`specs/profiles/` file actually renders
|
|
|
|
|
|
without error. Update `README.md`'s WP-0015 row and this workplan's
|
|
|
|
|
|
Result sections; run the full offline + Docker-gated suite; fence-count
|
|
|
|
|
|
check before committing any workplan edit, per this project's standing
|
|
|
|
|
|
practice.
|
2026-08-03 23:57:46 +02:00
|
|
|
|
|
|
|
|
|
|
**Result:** New `tests/test_reference_docs.py` — 13 tests, no Docker
|
|
|
|
|
|
required (only needs the `service` extras venv for `markdown`/`PyYAML`),
|
|
|
|
|
|
covering every real policy/profile file on disk, not just the two
|
|
|
|
|
|
exercised incidentally by T03's Control Plane tests. Skips cleanly under
|
|
|
|
|
|
plain `python3` (offline suite) when those extras aren't installed,
|
|
|
|
|
|
matching this project's existing `pytest.importorskip` convention.
|
|
|
|
|
|
|
|
|
|
|
|
All seven WP-0015 tasks are now done. Final suite counts: **94 passing
|
|
|
|
|
|
offline** (up from 84 at the start of this workplan — T01's schema tests
|
|
|
|
|
|
and T06's backfill-regression test, unchanged since; nothing in T02–T07
|
|
|
|
|
|
touches an offline-only code path), **183 passing under `.venv` with the
|
|
|
|
|
|
`service` extras** (the Docker-gated total moved from 146 → 170 across
|
|
|
|
|
|
T01–T05, plus 13 more non-Docker service-extras tests from this task). No
|
|
|
|
|
|
stray Docker containers left running.
|