--- id: CUST-WP-0002 type: workplan title: "State Hub v0.3 — Contribution Tracking & SBOM" domain: custodian status: completed owner: custodian topic_slug: custodian state_hub_workstream_id: 2446400d-6d01-4679-a314-92af0601c608 created: "2026-02-26" updated: "2026-02-28" --- # State Hub v0.3 — Contribution Tracking & SBOM ## Summary Establish the custodian as the central authority for cross-repo upstream contribution management (bug reports, feature requests, extension points, upstream PRs) and software supply-chain transparency (SBOM aggregation, licence governance). ## Context Three interconnected layers: - **Layer 1 — Convention & Canon**: define contrib/ directory structure and artifact types (BR, FR, EP, UPR) in markdown with typed YAML frontmatter. - **Layer 2 — Schema & API**: DB tables for contributions, repos (deferred to v0.5), and SBOM entries; FastAPI routers. - **Layer 3 — MCP Tools & Dashboard**: tracking tools and Observable pages. ## Dependencies - Depends on: `dynamic-domains-multi-repo` (v0.5, CUST-WP-0005). Four tasks in this workplan (P2.2, P2.4, P3.2, P4.1) are reduced in scope because v0.5 supersedes their original designs: - `managed_repos` table → done by v0.5 P2.1 - `/repos/` API router → done by v0.5 P2.2 - `register_repo` MCP tool → done by v0.5 P3.1 - Base registration workflow → done by v0.5 P2.3 - state_hub_dep_id: 2033172d-2462-4253-acb7-cb64c7432480 ## Phase 1 — Convention & Canon ### P1.1 — Write canon/standards/contribution-convention_v0.1.md ```task id: CUST-WP-0002-T01 state_hub_task_id: c2d7df02-5f37-4c02-a418-fe7ab0050edc status: done priority: high ``` Define the master contrib/ convention: directory layout, artifact types (BR/FR/EP/UPR), frontmatter schema per type, ID schemes (EP--NNN for EPs; date-prefixed slug for others), status lifecycle, and relationship to state-hub. Authoritative reference for other repos. ### P1.2 — Create contrib/ templates for BR, FR, EP, UPR ```task id: CUST-WP-0002-T02 state_hub_task_id: 5ef89639-950c-4d57-9578-6d0211edc2df status: done priority: high ``` Four Markdown template files under `canon/standards/contrib-templates/`: `br-template.md`, `fr-template.md`, `ep-template.md`, `upr-template.md`. Each has typed YAML frontmatter matching the schema in P1.1 plus a guided prose skeleton. ### P1.3 — Store Observable Framework TOC sidebar UPR as first artifact ```task id: CUST-WP-0002-T03 state_hub_task_id: e8251873-647c-4a4b-b283-298260b19c22 status: done priority: medium ``` Create `contrib/upstream-prs/2026-02-26--observablehq--framework--toc-sidebar-inject.md` using the UPR template. Captures: summary of the `injectTocTop` utility, motivation, link to local component, target upstream file, draft PR body. Status: draft. ### P1.4 — Align Railiance EP convention docs with canon master spec ```task id: CUST-WP-0002-T04 state_hub_task_id: 1c982457-de52-4fc1-a439-3bf3120bb5b6 status: done priority: low ``` The staged-promotion-lifecycle workstream defines EP-RAIL-NNN IDs and inline doc markers. Review and update so the Railiance convention is a proper instance of the custodian master spec, not a parallel one. ## Phase 2 — Schema & API ### P2.1 — Design and implement contributions DB table + migration ```task id: CUST-WP-0002-T05 state_hub_task_id: c41d71bd-dc88-4201-bd9a-3f8a4eae4910 status: done priority: high ``` Add `contributions` table: id (UUID PK), type (enum: br|fr|ep|upr), target_org, target_repo, slug, title, status (enum: draft|submitted|acknowledged|accepted|rejected|merged|withdrawn), body_path (relative path to .md file), related_topic_id (nullable FK), related_workstream_id (nullable FK), submitted_at, resolved_at, notes, created_at, updated_at. Alembic migration. ### P2.2 — sbom_entries table + migration (managed_repos deferred to v0.5) ```task id: CUST-WP-0002-T06 state_hub_task_id: 28c9bd38-05d8-4466-bff3-3ba4e3957635 status: done priority: high ``` SCOPE REDUCED — managed_repos superseded by v0.5 P2.1. Do not create `managed_repos` here. After v0.5 P2.1 lands, add `sbom_source` (text) and `last_sbom_at` (timestamp) columns to the existing `managed_repos` table via an additive migration. Then create `sbom_entries` table: id (UUID PK), repo_id (FK managed_repos), package_name, package_version, ecosystem, license_spdx (nullable), is_direct (bool), is_dev (bool), snapshot_at (timestamp — new ingest replaces old entries for that repo), created_at. Prerequisite: v0.5 P2.1 (managed_repos table) must be complete. ### P2.3 — Implement /contributions/ router (CRUD + status patch) ```task id: CUST-WP-0002-T07 state_hub_task_id: ee1cd17c-6dbd-4321-bb72-4499147c4837 status: done priority: high ``` FastAPI router: GET list (filter by type, status, target_repo), POST create, GET by id, PATCH status (validates lifecycle transitions), soft delete (sets status=withdrawn). Schemas: `ContributionCreate`, `ContributionRead`, `ContributionStatusPatch`. Add `contribution_counts` to `/state/summary`. ### P2.4 — Implement /sbom/ router (basic /repos/ endpoints implemented by v0.5) ```task id: CUST-WP-0002-T08 state_hub_task_id: 25f7ab5c-69d8-41d7-a108-38380eb1f3a9 status: done priority: high ``` SCOPE REDUCED — /repos/ superseded by v0.5 P2.2. This task covers the `/sbom/` router only: - `POST /sbom/ingest` — accept repo_slug + list of package entries; replace existing snapshot for that repo. - `GET /sbom/` — aggregated view across all repos, grouped by license_spdx. - `GET /sbom/{repo_slug}/` — single-repo SBOM. - `GET /sbom/report/licences/` — counts and repo lists per SPDX identifier, flagging copyleft (GPL/AGPL/LGPL). - Add `licence_risk_count` to `/state/summary`. Prerequisite: v0.5 P2.1 (managed_repos) and P2.2 (sbom_entries) must be complete. ### P2.5 — Write make ingest-sbom tooling (pyproject + package.json parsers) ```task id: CUST-WP-0002-T09 state_hub_task_id: edcf9b02-8177-4abc-b133-d303cc7ea19d status: done priority: medium ``` `scripts/ingest_sbom.py`: parses `uv.lock` (Python deps with licence metadata), `package-lock.json` / `yarn.lock` (Node deps); outputs normalised list; POSTs to `/sbom/ingest/`. Makefile target: `make ingest-sbom REPO=`. Composable into `make seed`. ## Phase 3 — MCP Tools & Dashboard ### P3.1 — New MCP tools: register_contribution, update_contribution_status, get_contributions ```task id: CUST-WP-0002-T10 state_hub_task_id: 51b2999b-a9c7-4871-8ef8-f5c927ac2454 status: done priority: high ``` - `register_contribution(type, target_org, target_repo, title, body_path, related_workstream_id?, notes?)` → ContributionRead - `update_contribution_status(contribution_id, status, notes?)` → ContributionRead - `get_contributions(type?, status?, target_repo?)` → list - Add `state://contributions` resource. ### P3.2 — New MCP tools: ingest_sbom, get_licence_report (register_repo implemented by v0.5) ```task id: CUST-WP-0002-T11 state_hub_task_id: 20c5c2ae-7758-42cc-885c-a886957e44c2 status: done priority: high ``` SCOPE REDUCED — register_repo superseded by v0.5 P3.1. - `ingest_sbom(repo_slug: str, lockfile_path: str)` — calls `scripts/ingest_sbom.py`, POSTs result to `/sbom/ingest/`. - `get_licence_report()` — returns licence groups with copyleft flag from `/sbom/report/licences/`. - Add resources: `state://sbom/aggregated`, `state://sbom/{repo_slug}`. Prerequisite: v0.5 P3.1 (register_repo tool) and P2.4 (/sbom/ router) must be complete. ### P3.3 — Dashboard: contributions.md page ```task id: CUST-WP-0002-T12 state_hub_task_id: 1eb13411-b2da-4d0d-8fe2-e926d029c50f status: done priority: medium ``` New Observable Framework page. Layout: filter bar (type, status, target_repo), status Kanban columns (draft → submitted → acknowledged → accepted/rejected/merged), count KPIs per type. Chart: contribution velocity over time (cumulative, same period selector as decisions page). Apply `injectTocTop` for page-level KPI. ### P3.4 — Dashboard: sbom.md page ```task id: CUST-WP-0002-T13 state_hub_task_id: 1267f313-1bf3-4059-8276-bfefcd9f6aed status: done priority: medium ``` New Observable Framework page. Aggregated dependency table: package, version, licence, repos using it, direct/dev flags. Licence summary donut chart (MIT vs Apache-2.0 vs GPL family vs other). Copyleft risk section (red highlight for GPL/AGPL/LGPL in direct prod deps). Per-repo drill-down accordion. Register in `observablehq.config.js`. ### P3.5 — Update index.md overview to surface contribution and SBOM health ```task id: CUST-WP-0002-T14 state_hub_task_id: abab0022-d3a4-45ef-a7e1-e0e7c05d295f status: done priority: low ``` Add contribution counts KPI card (total, by type, any needing follow-up). Add licence risk indicator (green if no copyleft in direct prod deps, red otherwise). Uses `/state/summary` fields added in P2.3 and P2.4. ## Phase 4 — Repo Integration Tooling ### P4.1 — Add sbom_source prompt + ingest-sbom step to registration workflow (base workflow by v0.5) ```task id: CUST-WP-0002-T15 state_hub_task_id: 47987720-23db-4465-861b-1f93bb1bb391 status: done priority: low ``` SCOPE REDUCED — base registration workflow superseded by v0.5 P2.3. SBOM-specific additions to the workflow: 1. Extend the `add-repo` command to prompt for `ecosystem` and `sbom_source` (path or URL to lockfile). 2. Optionally run `make ingest-sbom REPO=` automatically at end of registration. 3. Update `project_claude_md.template` to document the contrib/ convention, SBOM MCP tools, and `make ingest-sbom` target. Prerequisite: v0.5 P2.3 (updated registration workflow) must be complete. --- ## Closure Review — 2026-03-02 **Outcome:** All 15 tasks completed. No carry-forwards. No dropped tasks. **Context:** This workplan was created DB-first on 2026-02-28, before ADR-001 was formalised. The workplan file correctly recorded all tasks as `status: done`, but the DB rows were never synced from the file — they remained in their initial `todo` state in the database. The daily stale-task cleanup script (`scripts/cleanup_stale_tasks.py`) detected these 15 stale DB rows and cancelled them on 2026-03-02. No actual work was lost: all deliverables in Phase 1–4 were shipped as part of State Hub v0.3. ### Completed (DB updated at delivery time; file status = done) - CUST-WP-0002-T01 — Write canon/standards/contribution-convention_v0.1.md - CUST-WP-0002-T02 — Create contrib/ templates for BR, FR, EP, UPR - CUST-WP-0002-T03 — Store Observable Framework TOC sidebar UPR as first artifact - CUST-WP-0002-T04 — Align Railiance EP convention docs with canon master spec - CUST-WP-0002-T05 — Design and implement contributions DB table + migration - CUST-WP-0002-T06 — sbom_entries table + migration - CUST-WP-0002-T07 — Implement /contributions/ router (CRUD + status patch) - CUST-WP-0002-T08 — Implement /sbom/ router - CUST-WP-0002-T09 — Write make ingest-sbom tooling - CUST-WP-0002-T10 — New MCP tools: register_contribution, update_contribution_status, get_contributions - CUST-WP-0002-T11 — New MCP tools: ingest_sbom, get_licence_report - CUST-WP-0002-T12 — Dashboard: contributions.md page - CUST-WP-0002-T13 — Dashboard: sbom.md page - CUST-WP-0002-T14 — Update index.md overview to surface contribution and SBOM health - CUST-WP-0002-T15 — Add sbom_source prompt + ingest-sbom step to registration workflow ### Cancelled (DB records only — legacy stale rows, not real cancellations) All 15 DB task rows were cancelled by the cleanup script. The workplan file was authoritative; the DB rows were artefacts of the pre-ADR-001 DB-first creation pattern. This does not reflect a change in work outcome.