Resolve CUST-WP-0050 D1: repo-anchored model + ADR-005

Adopt the repo as the primary workplan anchor: repo_id becomes required,
market-domain is derived from each repo's classification, and the
domain/topic spine is demoted/retired (RepoGoal becomes the goal primitive).
Add task T10 for the re-anchor plus the workstream -> workplan rename across
schema/API/MCP.

Add ADR-005 (Cross-Repo Workplans Live in Dedicated Project Repos): complex
cross-repo efforts get their own project repo (category: project) as the
anchor, retired to archive on completion with results living on in the
modified product repos. Rewrite D1 as resolved and add D1a for the
project-repo naming/archival convention.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 01:36:31 +02:00
parent 0ba909263b
commit 50f4564561
2 changed files with 198 additions and 17 deletions

View File

@@ -0,0 +1,113 @@
---
id: ADR-005
type: architecture-decision-record
title: "Cross-Repo Workplans Live in Dedicated Project Repos"
status: accepted
decided_by: Bernd Worsch
date: "2026-06-22"
tags: ["architecture", "state-hub", "workplans", "cross-repo", "project-repo", "source-of-truth", "lifecycle"]
---
# ADR-005: Cross-Repo Workplans Live in Dedicated Project Repos
## Status
Accepted.
## Context
ADR-001 established that workplans and work items originate as files in the
repository that owns them, so the State Hub can rebuild its coordination state
from repo-owned files alone. The repo-classification redesign (`CUST-WP-0050`)
takes the next step: it makes the **repo the primary anchor** for a workplan
(`workstreams.repo_id` becomes required) and **derives** the market-domain from
the repo's `.repo-classification.yaml` rather than maintaining a separate
`topic`/`domain` spine. Repos are the most stable, git-managed entities in the
ecosystem; binding to them is the most durable anchor available.
This raises an unavoidable question: **what anchors a genuinely cross-repo
workplan?** Some efforts coordinate change across many repositories — ecosystem
migrations, the FOS hub bootstrap (`CUST-WP-0025`), or `CUST-WP-0050` itself,
which touches ~70 repos. If every workplan must bind to exactly one repo:
- binding it to one arbitrary product repo misrepresents the work and pollutes
that repo's history with coordination it does not own;
- leaving it unbound reintroduces the hub-only orphan that ADR-001 forbids;
- modelling it as an array of `repo_id`s breaks the "one stable anchor, clear
ownership, clean lifecycle" property and complicates the rebuild principle.
## Decision
**A complex cross-repo workplan gets its own dedicated *project repo*.**
- The project repo is a real, git-managed repository. It owns the coordination
workplan, its tasks, its decisions, and any cross-cutting artefacts. It is the
required `repo` anchor for that workplan, satisfying the repo-primary-anchor
rule without distorting any single product repo.
- The project repo is classified under the Repo Classification Standard, normally
`category: project`. Its `domain`/tags describe the effort, not any one product.
- **Implementation still happens in the product repos.** Changes land via
per-repo workplans and PRs in the repos being modified. The project repo
*coordinates and references* that work (via dependency edges / links); it does
not own product code.
- **On completion, the project repo is retired to archive — not deleted.** Its
durable results live on in the product repos it modified (the merged changes
are the outcome). The archived project repo remains as an immutable provenance
record of the coordination, consistent with the append-only-memory value.
The project repo's completion record MUST list the product repos it modified and
link to the merged PRs/commits, so the trail survives archival.
## Lifecycle
```
draft → active → completed → archived
```
- **active** — work in progress; workplan `status: active`; repo live in Gitea
and registered in the Hub.
- **completed** — all tasks done; completion record written (modified repos +
links).
- **archived** — repo archived in Gitea and `status: archived` in the Hub. The
workplan moves to `workplans/archived/` per the workplan convention. Results
persist in the product repos; the project repo is read-only history.
## Naming
Project repos SHOULD be identifiable as such (e.g. a `proj-<slug>` prefix or a
dedicated grouping). Exact convention is deferred to `CUST-WP-0050` rollout
(tracked as an open question there), but the lifecycle and ownership rules above
are fixed by this ADR.
## Consequences
- **Pro:** every workplan — including cross-repo ones — has a stable,
git-managed anchor; no hub-only orphans; the rebuild principle (ADR-001) holds.
- **Pro:** the classification standard applies uniformly; project repos are just
repos with `category: project`.
- **Pro:** clean, explicit lifecycle; results are never lost on retirement
because they live in the modified product repos.
- **Con:** proliferation of short-lived repos; requires discipline around the
naming and archival convention.
- **Con:** cross-references between the project repo and the product repos it
modified must be recorded deliberately, or the provenance trail degrades after
archival.
- **Con:** judgement is required on *when* an effort is "complex enough" to merit
a project repo versus a single-repo workplan; small cross-cutting changes
should not spawn a repo.
## Alternatives Considered
- **Bind to a "lead" product repo.** Rejected: distorts that repo's history and
creates ambiguous ownership.
- **Keep an optional hub-only topic for cross-repo coordination.** Rejected:
reintroduces the soft, non-git-managed spine that `CUST-WP-0050` removes and
ADR-001 discourages.
- **Multi-anchor workplan (array of repo_ids, no primary).** Rejected: breaks
single-anchor simplicity, ownership clarity, and lifecycle modelling.
## Related
- ADR-001 — Workplans and Work Items Are Repository Artefacts
- `CUST-WP-0050` — Repo Classification & State Hub Registration Redesign (D1)
- `canon/standards/repo-classification-standard_v1.0.md`

View File

@@ -11,6 +11,7 @@ planning_priority: high
planning_order: 50
created: "2026-06-22"
updated: "2026-06-22"
state_hub_workstream_id: "9f031f48-8de8-48b6-8e69-d2d83ad70a7a"
---
# CUST-WP-0050 - Repo Classification & State Hub Registration Redesign
@@ -63,7 +64,7 @@ The new standard fixes the root cause: it separates *category* (work mode),
*domain* (intended market/user), *capability tags* (what it does), and *business
stake* (who cares) — concerns the current 14 "domains" conflate.
### Architecture decision: replace the domain model
### Architecture decision: repo-anchored model, domain derived from classification
The standard's `domain` is a **fixed 14-value market vocabulary** (infotech,
financials, communication, consumer, health, industrials, energy, utilities,
@@ -72,12 +73,31 @@ the Hub's current 14 coordination domains. Per the steering decision on
2026-06-22, the new market-domain vocabulary **replaces** the Hub's domain model
(rather than augmenting it or running a parallel two-axis model).
This is a **breaking migration**: the current `domains` table is 1:1 with
`topics`, and topics own workstreams, goals, decisions, and progress events. The
new market domains are coarse (most repos are `infotech`), so the old 1:1
domain↔topic assumption cannot survive unchanged. **Decoupling coordination
topics from the market-domain attribute is the central design problem of T04/T05**
(see Open Questions D1).
The current spine is `Domain → Topic → Workstream`, where `topics.domain_id` and
`workstreams.topic_id` are both **NOT NULL** and the 14 domains are seeded **1:1
with 14 topics** (a data convention — the schema actually allows many topics per
domain, but that has never been used). `workstreams.repo_id` and
`repo_goals.repo_id` already exist, but the *required* anchor is the soft,
hub-only `topic`, while the stable git-managed `repo` link is optional.
Per the 2026-06-22 steering decision, this redesign **flips the polarity**: the
**repo becomes the primary anchor** for workplans, and market-domain is
**derived** from the repo's `.repo-classification.yaml`, not stored as a separate
`topic`/`domain` parent. Concretely:
- `workstreams.repo_id` becomes the **required** anchor; `topic_id` is demoted to
optional (or `topic` is retired) — see T10.
- Market-domain is computed from `repo → classification.domain`; the standalone
`topics.domain_id` / `managed_repos.domain_id` spine is removed.
- `RepoGoal` (already repo-anchored) becomes the goal primitive; `DomainGoal`
becomes a thin strategic rollup keyed by the 14 market domains.
- **Cross-repo workplans** anchor to a dedicated **project repo** that retires to
archive on completion, with results living on in the modified product repos —
see **ADR-005** and Open Question D1.
This is consistent with ADR-001: the spine becomes the git-managed repo plus its
committed classification file, so the Hub stays fully rebuildable from repo-owned
files. It is a **breaking migration** of the coordination spine (T04/T05).
## Scope
@@ -98,12 +118,19 @@ In scope:
- Updates to dashboard, consistency checker, MCP/REST surface, and orientation
docs to the new taxonomy.
In scope (added 2026-06-22):
- Re-anchor workplans to repos (`repo_id` required, `topic` optional/retired) and
derive market-domain from classification (T04/T10).
- Rename `workstream → workplan` across schema, API, and MCP so the Hub
vocabulary matches the repo files and current usage (T10).
Out of scope:
- Re-architecting workstream/task semantics beyond what the domain replacement
forces.
- Re-architecting task-level semantics beyond what the re-anchor and rename force.
- Changing the Gitea hosting model or repo contents beyond adding the
classification file.
classification file (and, for cross-repo efforts, creating project repos per
ADR-005).
- Classifying throwaway/forked/non-ecosystem repos (explicit exclusion list).
## Repo boundary
@@ -125,6 +152,7 @@ hub remains a read/index model fed by repo-owned files (ADR-001).
id: CUST-WP-0050-T01
status: todo
priority: high
state_hub_task_id: "d978b1f3-4eca-4a17-835b-2c25d13cae22"
```
Extract the standard's controlled vocabularies (5 categories, 14 domains, the
@@ -144,6 +172,7 @@ a small validator can check a `.repo-classification.yaml` against it.
id: CUST-WP-0050-T02
status: todo
priority: high
state_hub_task_id: "b7edfbb5-483f-4600-9356-8f885c78ce58"
```
Author and human-review `.repo-classification.yaml` for the custodian-domain
@@ -160,6 +189,7 @@ has been reviewed by a human.
id: CUST-WP-0050-T03
status: todo
priority: high
state_hub_task_id: "81489716-61ef-4207-ab8a-5877843281de"
```
Produce proposed `.repo-classification.yaml` for every active repo in the Gitea
@@ -179,6 +209,7 @@ file (or is on the recorded exclusion list).
id: CUST-WP-0050-T04
status: todo
priority: high
state_hub_task_id: "b61f6267-c2b2-4325-95fa-30ee899ce7d1"
```
Replace the `domains` table contents with the 14 fixed market domains and add
@@ -186,8 +217,10 @@ classification storage to `managed_repos`: `category`, primary `domain_id`,
`secondary_domains[]`, `capability_tags[]`, `business_stake[]`,
`business_mechanics[]`, plus provenance (`classified_at`, `classified_by`,
`standard_version`). Enforce the allowed-values from T01 at the API boundary.
Decouple `topic` from market-domain (see D1). Provide an Alembic migration and
updated SQLAlchemy models + Pydantic schemas.
Make `repo_id` the **required** workplan anchor, derive market-domain from the
repo's classification, and demote/retire the `topic`/`domain` spine (see D1 and
T10). Provide an Alembic migration and updated SQLAlchemy models + Pydantic
schemas.
Done when the schema/model/API accept and validate the full classification and
reject invalid values, with a forward migration and a tested downgrade path.
@@ -198,6 +231,7 @@ reject invalid values, with a forward migration and a tested downgrade path.
id: CUST-WP-0050-T05
status: todo
priority: high
state_hub_task_id: "171fa385-4d78-41ea-b749-ac3f9082fe47"
```
Define and apply the mapping from the old 14 domains/topics to the new model
@@ -219,6 +253,7 @@ deferred with reasons.
id: CUST-WP-0050-T06
status: todo
priority: high
state_hub_task_id: "6ae14007-d6d2-4395-814e-ace91486a953"
```
Build an idempotent `register-from-classification` capability (Make target +
@@ -238,6 +273,7 @@ emits a report of registered / updated / skipped / invalid.
id: CUST-WP-0050-T07
status: todo
priority: medium
state_hub_task_id: "6411bf3f-9de2-4bcd-9ffe-6209cda6ba93"
```
Run T06 against the classification files for the 57 previously-registered repos,
@@ -254,6 +290,7 @@ the managed-repo set matches the (non-excluded) Gitea inventory.
id: CUST-WP-0050-T08
status: todo
priority: medium
state_hub_task_id: "09951aec-2960-4c50-b73d-4e2e7bd285c9"
```
Update the dashboard to navigate by category/domain/capability/business-stake;
@@ -271,6 +308,7 @@ classification rule, and docs no longer assume the old domain model.
id: CUST-WP-0050-T09
status: todo
priority: medium
state_hub_task_id: "babbb80a-c52d-4ec2-b217-2f6196a2e5f3"
```
Switch orientation/registration tooling to the new model end-to-end, archive the
@@ -279,13 +317,43 @@ old domain semantics, and run `make fix-consistency REPO=the-custodian`.
Done when an end-to-end pass (classify → auto-register → dashboard view) is
verified and the old ad-hoc domain model is retired.
### T10 - Re-anchor to repos + rename workstream → workplan
```task
id: CUST-WP-0050-T10
status: todo
priority: high
state_hub_task_id: "bee16416-a67f-4155-93d7-09f278daa04f"
```
Two coupled changes to the coordination spine, executed in `/home/worsch/state-hub`:
1. **Re-anchor:** make `repo_id` the required anchor for a workplan, derive
market-domain from the repo's classification, and remove the
`topic`/`domain` parent spine (or demote `topic` to an optional cross-repo
tag). Promote `RepoGoal` to the goal primitive; reduce `DomainGoal` to a thin
rollup. Cross-repo workplans anchor to a **project repo** per ADR-005.
2. **Rename:** rename `workstream → workplan` across the DB table, SQLAlchemy
models, Pydantic schemas, REST routes, and MCP tools/resources, so the Hub
vocabulary matches the repo `workplans/` files and current usage. Provide
migration + compatibility/redirect notes for existing tool callers.
Sequence with T04/T05 (same migration window where practical). Done when a
workplan is anchored to a repo with no required topic, market-domain resolves
from classification, and the API/MCP surface uses "workplan" with green tests.
## Open Questions / Decisions
- **D1 (blocking T04/T05): topic ↔ market-domain after replacement.** Market
domains are coarse; coordination still needs finer grouping. Proposed: keep
`topic` as the coordination unit, made independent of market domain (market
domain becomes a `managed_repo` attribute; a topic may span repos of different
market domains). Needs confirmation before schema work starts.
- **D1 (RESOLVED 2026-06-22): the repo is the primary anchor.** Workplans bind to
repos (`repo_id` required); market-domain is *derived* from the repo's
classification; `topic`/`domain` stop being the spine (`topic` retires or
becomes an optional cross-repo tag). This supersedes the earlier "keep topic as
an independent coordination unit" proposal. Implemented by T04/T10.
- **D1a (open, follows from D1): anchor for cross-repo workplans.** Per **ADR-005**,
a complex cross-repo effort gets its own **project repo** (`category: project`)
as the anchor, retired to archive on completion with results living in the
modified product repos. Open sub-point: the project-repo **naming convention**
(e.g. `proj-<slug>` vs a dedicated grouping) and the archival trigger details.
- **D2: classification ownership/approval.** Who approves each repo's
`.repo-classification.yaml` — per-repo owner, or central custodian review?
- **D3: exclusion list.** Confirm exclusions (fork `tegwick/the-custodian`,