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