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
|
|
@ -89,7 +89,7 @@ The concept's §13 now defines a **Global Contingency Share Determination Rule**
|
|||
| [TREV-WP-0012](workplans/TREV-WP-0012-phase-provenance-and-policy-modeling.md) | Phase provenance, ledger reference, and degeneration-policy modeling — **finished**, all 5 tasks done. Decisions (T02–T04) synthesized into [`specs/PhaseProvenanceSpecAddendum.md`](specs/PhaseProvenanceSpecAddendum.md) (T05) — **not yet accepted for implementation**; that's the document to discuss before any schema/UI work is filed as its own workplan |
|
||||
| [TREV-WP-0013](workplans/TREV-WP-0013-remission-credit-automation.md) | Remission Credit automation (degeneration policy execution) — active; T01–T03 `wait` on WP-0012-T03's policy-spec-file decision. Nothing currently computes or writes `remission-credit` ledger entries |
|
||||
| [TREV-WP-0014](workplans/TREV-WP-0014-control-plane-extensions-breach-attestation-ui.md) | Control Plane UI: Extension Registry, Breach Records, Conversion Attestation — active; T01 next. Backend for all three already exists (WP-0006); UI-only work, not blocked on WP-0012 |
|
||||
| [TREV-WP-0015](workplans/TREV-WP-0015-phase-provenance-implementation.md) | Implement `specs/PhaseProvenanceSpecAddendum.md` — active; T01 (schema change) next. Seven tasks map 1:1 to the addendum's §1–§6 |
|
||||
| [TREV-WP-0015](workplans/TREV-WP-0015-phase-provenance-implementation.md) | Implement `specs/PhaseProvenanceSpecAddendum.md` — active; T01, T04, T06 done (schema change + Control Plane form/ledger UI + real-data backfill for the three pilot candidates). T02 (`specs/policies/`/`specs/profiles/` extraction) next |
|
||||
|
||||
Hub index: [`WORK-RECORDS.md`](WORK-RECORDS.md) · brief: [`.custodian-brief.md`](.custodian-brief.md)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,11 @@
|
|||
"milestone_release": {
|
||||
"name": "release-1.0",
|
||||
"source_revision": "abc123",
|
||||
"artifact_sha256": "44cd1493bd179c1207c7025c1372cdad89aac114e151db27aba2bdf3d55c688a"
|
||||
"artifact_sha256": "44cd1493bd179c1207c7025c1372cdad89aac114e151db27aba2bdf3d55c688a",
|
||||
"repo_hub": "example-hub",
|
||||
"repo_hub_uri": "https://example.invalid",
|
||||
"repo_id": 1,
|
||||
"repo_name": "example/example-repo"
|
||||
},
|
||||
"initial_target": {
|
||||
"amount": 100000,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@
|
|||
"id": "trsl:phase:draft-info-tech-canon-service-surface",
|
||||
"milestone_release": {
|
||||
"name": "info-tech-canon-service-surface-v1 (ITC-WP-0001..0012)",
|
||||
"source_revision": "f7ad73d"
|
||||
"source_revision": "f7ad73d",
|
||||
"repo_hub": "forgejo-coulomb",
|
||||
"repo_hub_uri": "https://forgejo.coulomb.social",
|
||||
"repo_id": 47,
|
||||
"repo_name": "coulomb/info-tech-canon"
|
||||
},
|
||||
"initial_target": {
|
||||
"amount": 141800,
|
||||
|
|
@ -20,7 +24,7 @@
|
|||
"future_license": "MIT",
|
||||
"degeneration_policy": "trsl:policy:linear-longstop-v0@1.0",
|
||||
"longstop_at": "2029-01-01T00:00:00Z",
|
||||
"ledger": "examples/pilot-candidates/info-tech-canon-service-surface/ledger.json"
|
||||
"ledger": "/phases/trsl:phase:draft-info-tech-canon-service-surface/ledger"
|
||||
},
|
||||
"extensions": [
|
||||
"trsl:extension:development-license@1.0"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@
|
|||
"id": "trsl:phase:draft-net-kingdom-local-identity",
|
||||
"milestone_release": {
|
||||
"name": "net-kingdom-local-identity-v1 (NK-WP-0002)",
|
||||
"source_revision": "3890dca"
|
||||
"source_revision": "3890dca",
|
||||
"repo_hub": "forgejo-coulomb",
|
||||
"repo_hub_uri": "https://forgejo.coulomb.social",
|
||||
"repo_id": 67,
|
||||
"repo_name": "coulomb/net-kingdom"
|
||||
},
|
||||
"initial_target": {
|
||||
"amount": 10000,
|
||||
|
|
@ -20,7 +24,7 @@
|
|||
"future_license": "MIT",
|
||||
"degeneration_policy": "trsl:policy:linear-longstop-v0@1.0",
|
||||
"longstop_at": "2028-01-01T00:00:00Z",
|
||||
"ledger": "examples/pilot-candidates/net-kingdom-local-identity/ledger.json"
|
||||
"ledger": "/phases/trsl:phase:draft-net-kingdom-local-identity/ledger"
|
||||
},
|
||||
"extensions": [
|
||||
"trsl:extension:development-license@1.0"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@
|
|||
"id": "trsl:phase:draft-railiance-vergabe-teilnahme",
|
||||
"milestone_release": {
|
||||
"name": "vergabe-teilnahme-v1 (RAILIANCE-WP-0002/RAILIANCE-WP-0014)",
|
||||
"source_revision": "398b0fe"
|
||||
"source_revision": "398b0fe",
|
||||
"repo_hub": "forgejo-coulomb",
|
||||
"repo_hub_uri": "https://forgejo.coulomb.social",
|
||||
"repo_id": 62,
|
||||
"repo_name": "coulomb/vergabe-teilnahme"
|
||||
},
|
||||
"initial_target": {
|
||||
"amount": 648800,
|
||||
|
|
@ -20,7 +24,7 @@
|
|||
"future_license": "Apache-2.0",
|
||||
"degeneration_policy": "trsl:policy:linear-longstop-v0@1.0",
|
||||
"longstop_at": "2029-01-01T00:00:00Z",
|
||||
"ledger": "examples/pilot-candidates/railiance-vergabe-teilnahme/ledger.json"
|
||||
"ledger": "/phases/trsl:phase:draft-railiance-vergabe-teilnahme/ledger"
|
||||
},
|
||||
"extensions": [
|
||||
"trsl:extension:development-license@1.0",
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
},
|
||||
"milestone_release": {
|
||||
"type": "object",
|
||||
"required": ["name", "source_revision"],
|
||||
"required": ["name", "source_revision", "repo_hub", "repo_hub_uri", "repo_id", "repo_name"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
|
|
@ -44,6 +44,26 @@
|
|||
"type": "string",
|
||||
"pattern": "^[a-f0-9]{64}$",
|
||||
"description": "Recommended; required if a built artifact (not just source) is governed."
|
||||
},
|
||||
"repo_hub": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9-]+$",
|
||||
"description": "Stable, human-assigned slug for the Forgejo instance hosting this repo, e.g. \"forgejo-coulomb\". Minted by this project, not read from Forgejo (no instance-level identity endpoint exists)."
|
||||
},
|
||||
"repo_hub_uri": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"description": "The Forgejo service's current base URL, e.g. \"https://forgejo.coulomb.social\". Retained alongside repo_hub (not derived from it) so a domain change stays repairable."
|
||||
},
|
||||
"repo_id": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"description": "Forgejo's own internal numeric repo id (GET /api/v1/repos/{owner}/{repo} -> id). Stable across repo renames, unlike repo_name."
|
||||
},
|
||||
"repo_name": {
|
||||
"type": "string",
|
||||
"pattern": "^[\\w.-]+/[\\w.-]+$",
|
||||
"description": "\"owner/repo\" slug at time of Phase registration. Human-readable; repo_id is authoritative if this ever diverges after a rename."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -93,6 +113,11 @@
|
|||
"ledger": {
|
||||
"type": "string",
|
||||
"description": "URL or relative path URI to the authoritative Target Ledger for this Phase."
|
||||
},
|
||||
"base_phase_id": {
|
||||
"type": "string",
|
||||
"pattern": "^trsl:phase:[a-zA-Z0-9._-]+$",
|
||||
"description": "The prior Phase this one succeeds (FR-11, Rule 7). Absent for a repo's first-ever Phase; no sentinel value is used."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -198,26 +198,41 @@ def phase_new_submit(
|
|||
phase_id: str = Form(...),
|
||||
milestone_release_name: str = Form(...),
|
||||
source_revision: str = Form(...),
|
||||
repo_hub: str = Form(...),
|
||||
repo_hub_uri: str = Form(...),
|
||||
repo_id: int = Form(...),
|
||||
repo_name: str = Form(...),
|
||||
base_phase_id: str = Form(""),
|
||||
initial_target_amount: float = Form(...),
|
||||
currency: str = Form(...),
|
||||
future_license: str = Form(...),
|
||||
degeneration_policy: str = Form(...),
|
||||
longstop_at: str = Form(...),
|
||||
ledger_uri: str = Form(..., alias="ledger"),
|
||||
):
|
||||
manifest = {
|
||||
"framework": "TRF-0.1",
|
||||
"license": "TRSL-0.1",
|
||||
"phase": {
|
||||
"id": phase_id,
|
||||
"milestone_release": {"name": milestone_release_name, "source_revision": source_revision},
|
||||
"milestone_release": {
|
||||
"name": milestone_release_name,
|
||||
"source_revision": source_revision,
|
||||
"repo_hub": repo_hub,
|
||||
"repo_hub_uri": repo_hub_uri,
|
||||
"repo_id": repo_id,
|
||||
"repo_name": repo_name,
|
||||
},
|
||||
"initial_target": {"amount": initial_target_amount, "currency": currency},
|
||||
"future_license": future_license,
|
||||
"degeneration_policy": degeneration_policy,
|
||||
"longstop_at": longstop_at,
|
||||
"ledger": ledger_uri,
|
||||
# Auto-computed, never hand-typed (WP-0012-T04) -- this Trust
|
||||
# Service instance is always the ledger's host in Stage 0.
|
||||
"ledger": f"/phases/{phase_id}/ledger",
|
||||
},
|
||||
}
|
||||
if base_phase_id:
|
||||
manifest["phase"]["base_phase_id"] = base_phase_id
|
||||
try:
|
||||
control_plane.register_phase(conn, licensor, manifest)
|
||||
except (control_plane.ControlPlaneError, registry.RegistrationError) as exc:
|
||||
|
|
|
|||
|
|
@ -6,6 +6,16 @@
|
|||
</wn-page-header>
|
||||
<p><wn-tag>{{ manifest.phase.id }}</wn-tag></p>
|
||||
|
||||
<details style="margin-bottom:1rem;color:#888;font-size:0.9rem;">
|
||||
<summary style="cursor:pointer;">Ledger reference (internal — not this Phase's ledger entries, see below)</summary>
|
||||
<p>
|
||||
Raw reference: <code>{{ manifest.phase.ledger }}</code>
|
||||
{% if manifest.phase.ledger.startswith('/phases/') %}
|
||||
· <a href="{{ manifest.phase.ledger }}">view live JSON</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<h3>Status</h3>
|
||||
<table class="wn-plain">
|
||||
<tr><td>Initial Target</td><td>{{ manifest.phase.initial_target.amount }} {{ manifest.phase.initial_target.currency }}</td></tr>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,21 @@
|
|||
<wn-field-row label="Source revision">
|
||||
<wn-input name="source_revision" required></wn-input>
|
||||
</wn-field-row>
|
||||
<wn-field-row label="Repo hub (stable Forgejo instance slug)">
|
||||
<wn-input name="repo_hub" value="forgejo-coulomb" required></wn-input>
|
||||
</wn-field-row>
|
||||
<wn-field-row label="Repo hub URI">
|
||||
<wn-input name="repo_hub_uri" value="https://forgejo.coulomb.social" required></wn-input>
|
||||
</wn-field-row>
|
||||
<wn-field-row label="Repo id (Forgejo's own numeric id)">
|
||||
<wn-input type="number" name="repo_id" required></wn-input>
|
||||
</wn-field-row>
|
||||
<wn-field-row label="Repo name (owner/repo)">
|
||||
<wn-input name="repo_name" required></wn-input>
|
||||
</wn-field-row>
|
||||
<wn-field-row label="Base Phase id (optional — only for a successive Phase)">
|
||||
<wn-input name="base_phase_id"></wn-input>
|
||||
</wn-field-row>
|
||||
<wn-field-row label="Initial Target amount">
|
||||
<wn-input type="number" step="0.01" name="initial_target_amount" required></wn-input>
|
||||
</wn-field-row>
|
||||
|
|
@ -37,9 +52,6 @@
|
|||
<wn-field-row label="Longstop date (ISO 8601)">
|
||||
<wn-input name="longstop_at" required></wn-input>
|
||||
</wn-field-row>
|
||||
<wn-field-row label="Ledger URI (relative path or URL)">
|
||||
<wn-input name="ledger" required></wn-input>
|
||||
</wn-field-row>
|
||||
<wn-button type="submit" variant="primary">Register Phase</wn-button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ what to build, rather than re-deriving field shapes or rationale here.
|
|||
|
||||
```task
|
||||
id: TREV-WP-0015-T01
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "cdca0ef5-a332-40e2-b165-10ffa238f413"
|
||||
```
|
||||
|
|
@ -39,6 +39,31 @@ 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).
|
||||
|
||||
```task
|
||||
id: TREV-WP-0015-T02
|
||||
status: todo
|
||||
|
|
@ -77,7 +102,7 @@ wherever a policy id or extension id already appears in
|
|||
|
||||
```task
|
||||
id: TREV-WP-0015-T04
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "af660317-e732-4ad7-bd4a-86ada8602ec7"
|
||||
```
|
||||
|
|
@ -90,6 +115,15 @@ 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`.
|
||||
|
||||
```task
|
||||
id: TREV-WP-0015-T05
|
||||
status: todo
|
||||
|
|
@ -109,7 +143,7 @@ action or a plain `UPDATE`, and document whichever is chosen.
|
|||
|
||||
```task
|
||||
id: TREV-WP-0015-T06
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "719aa6db-93d8-4eaa-ac1b-e7ee4c00bd89"
|
||||
```
|
||||
|
|
@ -123,6 +157,15 @@ 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`.
|
||||
|
||||
```task
|
||||
id: TREV-WP-0015-T07
|
||||
status: todo
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue