Implements CUST-WP-0007. Resolves inconsistencies I-1, I-2, I-5, I-6
identified in the GEMS audit (GenericEntityModellingSystem.md).
Pass 1 (e1f2a3b4c5d6): domain_id FK on extension_points and
technical_debt (replaces raw string column); repo_id FK on contributions.
Fixes domain-filtering bugs in EP/TD dashboard pages.
Pass 2 (f2a3b4c5d6e7): repo_id nullable FK on workstreams, aligning
the GEMS primary attachment with ADR-001 (repo > topic). Dashboard
pages updated to prefer repo->domain over topic->domain.
Pass 3 (a3b4c5d6e7f8): SBOMSnapshot container entity (GEMS Complex
between Repository and SBOMEntry). Ingest is now additive — each call
creates a new snapshot; history is retained. List/report endpoints
filter to latest snapshot per repo via _latest_snapshot_ids_subquery().
New endpoints: GET /sbom/snapshots/, GET /sbom/snapshots/{id}/.
Dashboard gains a Snapshot History section.
Also adds GEMS analysis artefacts: wiki/GEMS-StateHub-TypeRegistry.md,
wiki/GEMS-StateHub-SWOT.md, workplans/CUST-WP-0006 (analysis),
workplans/CUST-WP-0007 (migration, now completed).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5.8 KiB
GEMS State-Hub Type Registry
Domain-specific instantiation of the Generic Entity Modelling System
(wiki/GenericEntityModellingSystem.md) for the Custodian State Hub.
Status: Draft — subject to revision pending decision DEC-GEMS-001 through DEC-GEMS-006. Created: 2026-03-02
Hierarchy Overview
Ecosystem (implicit root — singleton)
└── Domain (Complex)
├── Topic (Complex) organizes focus areas
└── Repository (Complex)
├── Workstream (Complex) organizes tasks
├── SBOMSnapshot (Complex) organizes SBOM entries
├── Task (Atom) ← secondary: Workstream
├── Decision (Atom) ← secondary: Topic | Workstream
├── TechnicalDebt (Atom)
├── ExtensionPoint (Atom)
├── Contribution (Atom)
└── ProgressEvent (Atom) ← secondary: Workstream | Task | Decision
SBOMSnapshot
└── SBOMEntry (Atom)
DependsOn (Relation, primary=Domain)
from: Workstream → to: Workstream
Type Registry Table
| Type | Kind | Primary Attachment Type | Allowed Secondary Attachments | Payload / Key Fields |
|---|---|---|---|---|
| Ecosystem | Complex | — (root) | — | name, description |
| Domain | Complex | Ecosystem | — | slug, name, status |
| Topic | Complex | Domain | — | slug, title, status |
| Repository | Complex | Domain | Topic (optional scope annotation) | slug, name, local_path, remote_url |
| Workstream | Complex | Repository | Topic (organizer) | slug, title, status, owner, due_date |
| SBOMSnapshot | Complex | Repository | — | snapshot_at, source |
| Task | Atom | Workstream | — | title, status, priority, assignee, due_date |
| Decision | Atom | Repository | Topic | Workstream (context) | title, type, status, rationale, deadline |
| TechnicalDebt | Atom | Repository | Topic | Workstream (context) | td_id, debt_type, severity, status |
| ExtensionPoint | Atom | Repository | Topic | Workstream (context) | ep_id, ep_type, priority, status |
| Contribution | Atom | Repository | — | type, target_org, target_repo, status |
| ProgressEvent | Atom | Workstream | Task | Decision (context) | summary, event_type, author |
| SBOMEntry | Atom | SBOMSnapshot | — | package_name, version, ecosystem, license_spdx |
| DependsOn | Relation | Domain | — | attachments[1]=from_ws, attachments[2]=to_ws, description |
Validation Invariants
Following GEMS §5.2:
-
Primary chain must be acyclic. No entity may be its own ancestor via primary attachments.
-
Primary attachment kind/type must match the registry. A Task's primary must be a Workstream; a Workstream's primary must be a Repository, etc.
-
Context-consistency for secondary attachments. If Task has a secondary attachment to a Workstream, that Workstream's primary must be the same Repository as the Task's context (inherited via Workstream.primary).
-
Relation endpoint types must match the relation's type definition. DependsOn.from and DependsOn.to must both be Workstream entities within the same Domain.
-
Relation primary must be a Complex. DependsOn.primary = Domain (the relation-space that "owns" the inter-workstream dependency graph).
Mapping: Current Tables → GEMS
| Current Table | Target GEMS Type | Status | Change Required |
|---|---|---|---|
domains |
Domain (Complex) | Correct | None |
topics |
Topic (Complex) | Correct | None |
managed_repos |
Repository (Complex) | Mostly correct | Remove nullable topic_id; add optional secondary |
workstreams |
Workstream (Complex) | Broken | Change primary from topic_id to repo_id |
tasks |
Task (Atom) | Correct | None |
decisions |
Decision (Atom) | Ambiguous | Change primary to repo_id; topic/workstream become secondaries |
technical_debt |
TechnicalDebt (Atom) | Broken | domain string → repo_id FK |
extension_points |
ExtensionPoint (Atom) | Broken | domain string → repo_id FK |
contributions |
Contribution (Atom) | Incomplete | Add repo_id FK |
progress_events |
ProgressEvent (Atom) | Ambiguous | Clarify primary vs. secondary |
sbom_entries |
SBOMEntry (Atom) | Broken | Add SBOMSnapshot container |
workstream_dependencies |
DependsOn (Relation) | Acceptable | Consider Relation entity model |
| (missing) | SBOMSnapshot (Complex) | Missing | New table required |
| (missing) | Ecosystem (Complex) | Missing | Optional singleton |
Query Patterns Enabled
After full GEMS alignment:
# All workstreams in a domain
Workstream WHERE primary.primary.slug = "railiance"
# All open tasks for a given repo
Task WHERE primary.primary.slug = "activity-core" AND status != "done"
# Dependency graph for a domain
DependsOn WHERE primary.slug = "custodian"
# SBOM history for a repo
SBOMSnapshot WHERE primary.slug = "the-custodian" ORDER BY snapshot_at DESC
# All tech debt in a domain (currently broken — domain is a string)
TechnicalDebt WHERE primary.primary.slug = "custodian"
Notes
-
TopicandRepositoryare both children ofDomainbut are distinct organizers. Topic = "focus area / project agenda"; Repository = "git repo / code artefact boundary". A Topic may align with one or more repositories, but neither owns the other. -
Workstreammoving from Topic → Repository is the most disruptive change. It resolves the ADR-001 contradiction (workplans must live in repos, but workstreams live under topics). -
ProgressEventretains its multi-attach flexibility (topic, workstream, task, decision) but those become secondary attachments. The primary should be the Workstream (or Repository if no workstream context).