265 lines
11 KiB
Markdown
265 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.
|
|||
|
|
|
|||
|
|
**This document proposes changes. It does not make them.** Per the
|
|||
|
|
maintainer's own established sequencing for this workplan, nothing here
|
|||
|
|
is implemented until this addendum itself is reviewed and accepted — at
|
|||
|
|
which point the implementation work is filed as its own workplan(s),
|
|||
|
|
the same way WP-0009/WP-0010 were split out of an earlier combined plan.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 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.
|