target-revenue/workplans/TREV-WP-0015-phase-provenance-implementation.md
tegwick de308f8947 Complete WP-0015: forgejo_hubs migration + closeout (T05/T07)
migrations/0007_forgejo_hubs.sql: table forgejo_hubs, auto-populated
via a BEFORE INSERT trigger on phase_manifests that reads repo_hub/
repo_hub_uri straight out of the manifest JSONB (no top-level columns
needed). ON CONFLICT DO NOTHING -- a hub already seen is left alone;
correcting a URI is a SECURITY DEFINER governance action
(correct_forgejo_hub_uri), not a plain UPDATE, matching every other
governance-action pattern in this project. Thin Python wrappers added
to registry.py.

tests/test_forgejo_hubs.py (6 Docker-gated tests) and
tests/test_reference_docs.py (13 tests, no Docker needed -- smoke-tests
every real specs/policies/specs/profiles/ file, not just the two
exercised incidentally by T03's Control Plane tests).

All seven WP-0015 tasks done; workplan marked finished. Final suite:
94 passing offline, 183 passing under the service extras venv. No
stray Docker containers left running.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 23:57:46 +02:00

13 KiB
Raw Permalink Blame History

id type title domain repo status owner topic_slug created updated state_hub_workstream_id
TREV-WP-0015 workplan Implement Phase provenance, spec-file, and ledger UI changes infotech target-revenue finished claude infotech 2026-08-03 2026-08-03 fe1bedd3-dadc-41cc-a669-92623743e620

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 T02T04. 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.

id: TREV-WP-0015-T01
status: done
priority: high
state_hub_task_id: "cdca0ef5-a332-40e2-b165-10ffa238f413"

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).

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).

id: TREV-WP-0015-T02
status: done
priority: high
state_hub_task_id: "8b5ab6ea-fa80-4e08-8733-2faad810962e"

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.

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.

id: TREV-WP-0015-T03
status: done
priority: medium
state_hub_task_id: "681002d5-582b-4ddd-8b1f-dc25ce5b0e5a"

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.

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).

id: TREV-WP-0015-T04
status: done
priority: medium
state_hub_task_id: "af660317-e732-4ad7-bd4a-86ada8602ec7"

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.

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.

id: TREV-WP-0015-T05
status: done
priority: high
state_hub_task_id: "816f1ac2-da40-4550-8d6c-64a1a335a660"

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.

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 UPDATEcorrect_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).

id: TREV-WP-0015-T06
status: done
priority: medium
state_hub_task_id: "719aa6db-93d8-4eaa-ac1b-e7ee4c00bd89"

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.

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.

id: TREV-WP-0015-T07
status: done
priority: high
state_hub_task_id: "39b34438-a1b1-49da-a88a-f3bec926aa89"

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.

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 T02T07 touches an offline-only code path), 183 passing under .venv with the service extras (the Docker-gated total moved from 146 → 170 across T01T05, plus 13 more non-Docker service-extras tests from this task). No stray Docker containers left running.