Implement WP-0015-T01/T04/T06: Phase provenance schema + ledger UI
Schema: phase.milestone_release gains required repo_hub/repo_hub_uri/
repo_id/repo_name; phase gains optional base_phase_id. Backfilled the
golden phase-001 fixture (placeholder values, it's synthetic) and the
three real pilot-candidate manifests with actual Forgejo repo ids
confirmed live (net-kingdom=67, vergabe-teilnahme=62,
info-tech-canon=47, all coulomb/*).
Discovered along the way: the schema change alone would have broken
the existing Control Plane registration form, since nothing collected
the four new fields. Fixed inline rather than leaving it broken
between tasks -- this also completes T04's ledger UI change (drop the
hand-typed ledger input, auto-compute /phases/{id}/ledger, add a
drill-down reference link on phase_detail.html) since both changes
touch the same form/route.
Full suite: 94 passing offline (was 84), 158 passing with Docker
(was 146).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
11800c7da1
commit
2a176d9961
13 changed files with 204 additions and 20 deletions
|
|
@ -189,12 +189,15 @@ def test_operator_registers_phase_and_appends_entry(client, credentials):
|
|||
"phase_id": phase_id,
|
||||
"milestone_release_name": "CP UI smoke test release",
|
||||
"source_revision": "abc123",
|
||||
"repo_hub": "forgejo-coulomb",
|
||||
"repo_hub_uri": "https://forgejo.coulomb.social",
|
||||
"repo_id": "103",
|
||||
"repo_name": "coulomb/target-revenue",
|
||||
"initial_target_amount": "1000",
|
||||
"currency": "USD",
|
||||
"future_license": "MIT",
|
||||
"degeneration_policy": "trsl:policy:linear-longstop-v0@1.0",
|
||||
"longstop_at": "2027-01-01T00:00:00Z",
|
||||
"ledger": f"examples/{phase_id}/ledger.json",
|
||||
},
|
||||
follow_redirects=False,
|
||||
)
|
||||
|
|
@ -204,6 +207,7 @@ def test_operator_registers_phase_and_appends_entry(client, credentials):
|
|||
detail = client.get(f"/phases/{phase_id}")
|
||||
assert detail.status_code == 200
|
||||
assert phase_id in detail.text
|
||||
assert f"/phases/{phase_id}/ledger" in detail.text
|
||||
|
||||
ledger_resp = client.post(
|
||||
f"/phases/{phase_id}/ledger",
|
||||
|
|
@ -233,12 +237,15 @@ def test_contributor_proposes_operator_approves(client, credentials):
|
|||
"phase_id": phase_id,
|
||||
"milestone_release_name": "CP UI propose-flow release",
|
||||
"source_revision": "def456",
|
||||
"repo_hub": "forgejo-coulomb",
|
||||
"repo_hub_uri": "https://forgejo.coulomb.social",
|
||||
"repo_id": "103",
|
||||
"repo_name": "coulomb/target-revenue",
|
||||
"initial_target_amount": "1000",
|
||||
"currency": "USD",
|
||||
"future_license": "MIT",
|
||||
"degeneration_policy": "trsl:policy:linear-longstop-v0@1.0",
|
||||
"longstop_at": "2027-01-01T00:00:00Z",
|
||||
"ledger": f"examples/{phase_id}/ledger.json",
|
||||
},
|
||||
)
|
||||
client.post("/logout")
|
||||
|
|
@ -303,3 +310,16 @@ def test_form_bridge_script_present(client):
|
|||
assert "wn-button[type=submit]" in resp.text
|
||||
assert "requestSubmit" in resp.text
|
||||
assert "data-wn-mirror-for" in resp.text
|
||||
|
||||
|
||||
def test_phase_new_form_has_no_ledger_input(client, credentials):
|
||||
"""WP-0012-T04: the ledger reference is auto-computed, never
|
||||
hand-typed. Regression test for the registration form itself, not
|
||||
just the route's behavior."""
|
||||
_login(client, credentials["operator"].token)
|
||||
resp = client.get("/phases/new")
|
||||
assert 'name="ledger"' not in resp.text
|
||||
assert 'name="repo_hub"' in resp.text
|
||||
assert 'name="repo_hub_uri"' in resp.text
|
||||
assert 'name="repo_id"' in resp.text
|
||||
assert 'name="repo_name"' in resp.text
|
||||
|
|
|
|||
|
|
@ -59,3 +59,32 @@ def test_phase_id_immutable_across_versions():
|
|||
def test_unchanged_manifest_has_no_immutability_violations():
|
||||
manifest = golden_manifest()
|
||||
assert validation.check_manifest_immutability(manifest, manifest) == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"field", ["repo_hub", "repo_hub_uri", "repo_id", "repo_name"]
|
||||
)
|
||||
def test_manifest_missing_repo_provenance_field_is_rejected(field):
|
||||
manifest = copy.deepcopy(golden_manifest())
|
||||
del manifest["phase"]["milestone_release"][field]
|
||||
with pytest.raises(validation.ConformanceError):
|
||||
validation.validate_phase_manifest(manifest)
|
||||
|
||||
|
||||
def test_manifest_with_absent_base_phase_id_is_conformant():
|
||||
manifest = copy.deepcopy(golden_manifest())
|
||||
assert "base_phase_id" not in manifest["phase"]
|
||||
validation.validate_phase_manifest(manifest)
|
||||
|
||||
|
||||
def test_manifest_with_present_base_phase_id_is_conformant():
|
||||
manifest = copy.deepcopy(golden_manifest())
|
||||
manifest["phase"]["base_phase_id"] = "trsl:phase:example-000"
|
||||
validation.validate_phase_manifest(manifest)
|
||||
|
||||
|
||||
def test_manifest_with_malformed_base_phase_id_is_rejected():
|
||||
manifest = copy.deepcopy(golden_manifest())
|
||||
manifest["phase"]["base_phase_id"] = "not-a-phase-id"
|
||||
with pytest.raises(validation.ConformanceError):
|
||||
validation.validate_phase_manifest(manifest)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,20 @@ def test_pilot_candidate_manifest_is_conformant(name):
|
|||
assert manifest["phase"]["id"].startswith("trsl:phase:draft-")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name", PILOT_CANDIDATE_NAMES)
|
||||
def test_pilot_candidate_manifest_carries_real_repo_provenance(name):
|
||||
"""WP-0015-T06: each candidate's repo_id is the real Forgejo id
|
||||
(confirmed live against forgejo.coulomb.social), not a placeholder,
|
||||
and none carries a base_phase_id (all three are first-ever Phases)."""
|
||||
manifest = json.loads((PILOT_CANDIDATES_DIR / name / "manifest.json").read_text())
|
||||
milestone_release = manifest["phase"]["milestone_release"]
|
||||
assert milestone_release["repo_hub"] == "forgejo-coulomb"
|
||||
assert milestone_release["repo_hub_uri"] == "https://forgejo.coulomb.social"
|
||||
assert isinstance(milestone_release["repo_id"], int) and milestone_release["repo_id"] > 0
|
||||
assert milestone_release["repo_name"].startswith("coulomb/")
|
||||
assert "base_phase_id" not in manifest["phase"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name", PILOT_CANDIDATE_NAMES)
|
||||
def test_pilot_candidate_ledger_is_empty_draft_state(name):
|
||||
ledger = json.loads((PILOT_CANDIDATES_DIR / name / "ledger.json").read_text())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue