specs/PhaseProvenanceSpecAddendum.md accepted 2026-08-03. Implementation filed as its own workplan (TREV-WP-0015), seven tasks mapping 1:1 to the addendum's sections rather than repeating field shapes/rationale: schema change, specs/policies/ + specs/profiles/ extraction, Control Plane reference-rendering routes, ledger UI change, forgejo_hubs migration, backfilling the three example manifests, and tests/closeout. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
264 lines
11 KiB
Markdown
264 lines
11 KiB
Markdown
# Phase Provenance, Ledger Reference, and Policy/Profile Spec-File Addendum
|
||
|
||
`workplans/TREV-WP-0012-phase-provenance-and-policy-modeling.md` T05.
|
||
Synthesizes T02–T04's accepted decisions (all accepted; see that
|
||
workplan's Result sections for the full reasoning behind each) into one
|
||
reviewable document, covering the concrete schema, spec-file, and Control
|
||
Plane UI changes they imply.
|
||
|
||
**Accepted 2026-08-03.** Implementation is tracked in
|
||
`workplans/TREV-WP-0015-phase-provenance-implementation.md`, whose seven
|
||
tasks map 1:1 to this document's §1–§6 (this document remains the
|
||
authoritative source for field shapes and rationale; that workplan does
|
||
not repeat them).
|
||
|
||
---
|
||
|
||
## 1. Phase Manifest schema changes (T02)
|
||
|
||
`schemas/phase_manifest.schema.json`, `phase.milestone_release`: four new
|
||
**required** properties, alongside the two that already exist
|
||
(`name`, `source_revision`; `artifact_sha256` stays optional):
|
||
|
||
```jsonc
|
||
"milestone_release": {
|
||
"type": "object",
|
||
"required": ["name", "source_revision", "repo_hub", "repo_hub_uri", "repo_id", "repo_name"],
|
||
"additionalProperties": false,
|
||
"properties": {
|
||
"name": { "type": "string" },
|
||
"source_revision": { "type": "string" },
|
||
"artifact_sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
||
"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 — Forgejo has no instance-level identity endpoint (confirmed live 2026-07-30, /api/v1/nodeinfo -> 404)."
|
||
},
|
||
"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 without breaking every Phase's provenance link."
|
||
},
|
||
"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, e.g. \"coulomb/target-revenue\". Human-readable; repo_id is authoritative if this ever diverges after a rename."
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
`phase`: one new **optional** property, `base_phase_id` — present only
|
||
for a successive Phase, absent for a first-ever Phase (no sentinel
|
||
value):
|
||
|
||
```jsonc
|
||
"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."
|
||
}
|
||
```
|
||
|
||
`phase.ledger` is **unchanged** by this addendum — see §4.
|
||
|
||
### Not part of this schema
|
||
|
||
The `repo_hub` → `repo_hub_uri` mapping is **not** schema-level or
|
||
file-level data in this repo. `target-revenue` is the generic framework,
|
||
not a specific deployment's list of repos it monetizes. The mapping is
|
||
hosted Trust Service data — see §5.
|
||
|
||
---
|
||
|
||
## 2. Degeneration-policy and calculation-model spec files (T03)
|
||
|
||
New `specs/policies/` subdirectory, one file per policy, replacing the
|
||
current situation where `linear-longstop-v0`'s actual definition is a
|
||
subsection (Q7) inside `specs/OpenQuestions-WorkingDefaults.md`, mixed
|
||
with many unrelated open questions:
|
||
|
||
- `specs/policies/linear-longstop-v0.md` — extracted from Q7's prose
|
||
(`R(t) = T0 × clamp((t − t0)/(tL − t0), 0, 1)`, discrete
|
||
`remission-credit` entries on a published cadence). Frontmatter:
|
||
|
||
```yaml
|
||
---
|
||
policy_id: trsl:policy:linear-longstop-v0@1.0
|
||
title: Linear Longstop v0
|
||
---
|
||
```
|
||
|
||
`OpenQuestions-WorkingDefaults.md` Q7 becomes a pointer to this file
|
||
(the *decision record* — "this is the accepted v1 norm" — stays there;
|
||
the policy's own *content* moves out).
|
||
|
||
New `specs/profiles/` subdirectory, one file per canonical monetization
|
||
profile, splitting `specs/CanonicalMonetizationProfiles.md`'s six
|
||
sections into their own files — these already have stable ids used in
|
||
real Phase Manifests today (`extensions: ["trsl:extension:development-license@1.0", ...]`),
|
||
so the id→file mapping is immediate, not something newly invented:
|
||
|
||
- `specs/profiles/development-license.md`
|
||
- `specs/profiles/cost-plus-operations.md`
|
||
- `specs/profiles/phase-sponsorship.md`
|
||
- `specs/profiles/service-with-development-allocation.md`
|
||
- `specs/profiles/product-ideation.md`
|
||
- `specs/profiles/general-consulting.md`
|
||
|
||
Each with frontmatter in the same shape:
|
||
|
||
```yaml
|
||
---
|
||
extension_id: trsl:extension:development-license@1.0
|
||
title: Development License
|
||
---
|
||
```
|
||
|
||
`specs/CanonicalMonetizationProfiles.md` §7 (cross-profile summary) and
|
||
§8 (non-goals) stay as the comparison/overview document; the six
|
||
per-profile sections (§1–§6) move out.
|
||
|
||
`specs/DevelopmentEffortCalculatorConcept.md` requires **no move** — one
|
||
file, one model, already a distinct name. Add matching frontmatter for
|
||
consistency:
|
||
|
||
```yaml
|
||
---
|
||
calculator_id: development-effort-calculator-candidate-a
|
||
revision: "1.0"
|
||
title: Development Effort Calculator — Candidate A
|
||
---
|
||
```
|
||
|
||
(No `trsl:...` schema id exists for calculators — this is a
|
||
documentation-traceability convention only, since calculators aren't
|
||
referenced by id from inside a Phase Manifest the way
|
||
`degeneration_policy` is.)
|
||
|
||
**Id and revision, built on git internals**: the id is the stable
|
||
identifier already in use (with its existing `@version` suffix where
|
||
applicable), mapped to exactly one file by naming convention; the
|
||
revision/audit trail is that file's own git history
|
||
(`git log -- specs/policies/linear-longstop-v0.md`). No new versioning
|
||
table, no pinned-commit scheme — nothing beyond what git already
|
||
provides.
|
||
|
||
### Control Plane rendering
|
||
|
||
One new read-only route family, server-side markdown → HTML at request
|
||
time (a small Python markdown library — e.g. `markdown` or `mistune` —
|
||
not a new build pipeline):
|
||
|
||
```
|
||
GET /reference/policies/{slug}
|
||
GET /reference/profiles/{slug}
|
||
```
|
||
|
||
A plain link next to wherever a policy id already appears
|
||
(`phase_detail.html`, `phase_new.html`) — never in-UI editable, matching
|
||
every governance-action pattern already established in this project
|
||
(credential revocation, extension canonicalization, proposed-entry
|
||
review — all view/act, never edit-in-place).
|
||
|
||
This deliberately does **not** adopt state-hub's Observable-Framework
|
||
static-build + "?" overlay pattern (`~/state-hub/dashboard/src/docs/*.md`)
|
||
— confirmed live 2026-07-30 that it's a real static-site generator, a
|
||
materially heavier dependency than this repo's existing lightweight
|
||
FastAPI+Jinja2 stack needs for six profile pages and one policy page.
|
||
|
||
---
|
||
|
||
## 3. Ledger UI treatment (T04)
|
||
|
||
- `phase_new.html` drops the `ledger` input entirely. The registration
|
||
route (`control_plane.register_phase`, or its caller) sets
|
||
`phase.ledger` to the canonical `/phases/{phase_id}/ledger` reference
|
||
automatically, before the manifest is validated/persisted — no human
|
||
types it in v0.
|
||
- `phase_detail.html` gains a small "ledger reference" link/disclosure
|
||
(not a new prominent field) showing the raw `ledger` string from the
|
||
manifest, plus — only when it resolves to this same Trust Service
|
||
instance — a link to `GET /phases/{id}/ledger`'s live JSON.
|
||
- The existing Ledger entries table on `phase_detail.html` is
|
||
**unaffected** — this addendum only concerns the otherwise-invisible
|
||
raw reference string, not the ledger data itself.
|
||
- The schema field `phase.ledger` is unchanged (still required, still
|
||
federation-ready for a future Phase whose ledger genuinely lives
|
||
elsewhere) — this section is a Control Plane UI change only.
|
||
|
||
---
|
||
|
||
## 4. Hosted Trust Service: `forgejo_hubs` table
|
||
|
||
A new migration (numbered after `0006_control_plane.sql`, so `0007`),
|
||
analogous to `licensor_identities`
|
||
(`migrations/0005_licensor_credentials.sql`):
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS forgejo_hubs (
|
||
hub_slug text PRIMARY KEY,
|
||
service_uri text NOT NULL,
|
||
first_seen_at timestamptz NOT NULL DEFAULT now(),
|
||
updated_at timestamptz NOT NULL DEFAULT now()
|
||
);
|
||
```
|
||
|
||
Auto-populated the first time a `repo_hub` is seen during Phase
|
||
registration (mirroring `ensure_licensor_identity`'s auto-create-on-first-
|
||
INSERT trigger pattern) — no separate onboarding step required. This
|
||
table is purely an admin/repair convenience for the hosting layer (e.g.
|
||
"this hub's URI changed, update it in one place") — a Phase Manifest
|
||
never needs it to be reachable for offline verification, since the
|
||
manifest itself always carries `repo_hub_uri`/`repo_name` directly at
|
||
registration time (use case 9, `specs/PhaseLifecycleUseCases.md`).
|
||
|
||
Whether correcting a hub's URI after the fact should be a governance
|
||
action (a `SECURITY DEFINER` function, like `revoke_credential`) or a
|
||
plain `UPDATE` is left to the implementation workplan to decide — not
|
||
load-bearing enough to need a T02-style human gate here, but flagged so
|
||
it isn't silently decided by whichever pattern is fastest to type.
|
||
|
||
---
|
||
|
||
## 5. Backfilling the three example manifests
|
||
|
||
Once the schema change above lands, `examples/pilot-candidates/*/manifest.json`
|
||
(all three still `trsl:phase:draft-*`, never registered) get:
|
||
|
||
- `repo_hub`/`repo_hub_uri`/`repo_id`/`repo_name` filled in from each
|
||
repo's real Forgejo data (the same `GET /api/v1/repos/{owner}/{repo}`
|
||
call already confirmed working for `target-revenue` itself applies
|
||
identically to `net-kingdom`, `railiance-apps`/`vergabe-teilnahme`, and
|
||
`info-tech-canon`).
|
||
- No `base_phase_id` — all three are first-ever Phases for their repos.
|
||
- `ledger` set to each candidate's own canonical reference, matching
|
||
whatever the Control Plane would auto-compute (§3), for consistency
|
||
between examples and real registered Phases.
|
||
|
||
---
|
||
|
||
## 6. Suggested implementation task breakdown
|
||
|
||
Once this addendum is accepted, file as its own workplan (working title
|
||
`TREV-WP-0015` or similar — not created yet, per this task's own charter
|
||
of producing a document, not a workplan):
|
||
|
||
1. Schema change (§1) + `validation.py`/`tests/` updates.
|
||
2. `specs/policies/`, `specs/profiles/` extraction (§2) +
|
||
`DevelopmentEffortCalculatorConcept.md` frontmatter.
|
||
3. Control Plane reference-rendering route (§2) + linking from
|
||
`phase_detail.html`/`phase_new.html`.
|
||
4. Ledger UI change (§3) — drop the form field, add the drill-down link.
|
||
5. `forgejo_hubs` migration + auto-populate-on-first-registration logic
|
||
(§4).
|
||
6. Backfill the three example manifests (§5).
|
||
7. Tests (offline schema/validation tests for §1, Docker-gated tests for
|
||
§4/§5, a rendering smoke test for §2's new routes) and a `README.md`/
|
||
workplan Result update, following this project's existing pattern for
|
||
every prior task in this session.
|