generated from coulomb/repo-seed
Some checks failed
Test / test (push) Has been cancelled
Implements IHUB-WP-0009: closes four GAAF-2026 gaps before domain hub work begins. - TypeRegistry helper + controllers/views (hub_kind, hub_capability_manifest) - HubCapabilityManifest entity with validation and registry linkage - ARCHITECTURE-LAYERS.md + CI-enforced boundary contracts - Alembic migration 1743724800, fitness tests (Test/Architecture/) - GAAF spec, Operational Architecture spec, domain hub extension guide - Updates to CLAUDE.md, SCOPE.md, Schema.sql, Routes, FrontController, Types state_hub_sync: pending (tunnel was STALE at completion time; run fix-consistency) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
858 lines
38 KiB
Markdown
858 lines
38 KiB
Markdown
---
|
||
id: IHUB-WP-0009
|
||
type: workplan
|
||
title: "IHF GAAF Compliance Foundation — Type Registries, Extension Manifests, and Architectural Contracts"
|
||
domain: inter_hub
|
||
repo: inter-hub
|
||
status: done
|
||
owner: custodian
|
||
topic_slug: inter_hub
|
||
created: "2026-03-31"
|
||
updated: "2026-03-31"
|
||
completed: "2026-03-31"
|
||
token_cost:
|
||
session_1_context_exhausted: true
|
||
session_1_model: claude-sonnet-4-6
|
||
session_1_input_tokens_est: ~180000
|
||
session_1_output_tokens_est: ~22000
|
||
session_1_notes: "T01–T03 partial; contracts, ARCHITECTURE-LAYERS.md, migration, TypeRegistry helper, 4 registry controllers/views, hub_kind schema+views+controller"
|
||
session_2_model: claude-sonnet-4-6
|
||
session_2_input_tokens_est: ~95000
|
||
session_2_output_tokens_est: ~18000
|
||
session_2_notes: "T03–T08 complete; registry validation wired, HubCapabilityManifests controller+views, fitness tests, docs, CLAUDE.md/SCOPE.md updates"
|
||
total_input_tokens_est: ~275000
|
||
total_output_tokens_est: ~40000
|
||
total_cost_usd_est: "~$1.10 (Sonnet 4.6 at $3/MTok input, $15/MTok output)"
|
||
state_hub_sync: pending
|
||
state_hub_note: "No state_hub_workstream_id set; mcp__custodian tools not connected at completion time"
|
||
---
|
||
|
||
# IHF GAAF Compliance Foundation
|
||
|
||
## Goal
|
||
|
||
Establish inter-hub as a GAAF-2026 compliant framework foundation before any
|
||
domain hub (dev-hub, ops-hub, fin-hub, sec-hub) begins implementation. Close
|
||
the architectural gaps identified in the GAAF review of 2026-03-31 so that
|
||
Phase 9 and beyond are built on a framework with a formal extension layer,
|
||
typed vocabularies, explicit contracts, and CI-enforced architectural boundaries.
|
||
|
||
## Background
|
||
|
||
Phases 1–8 are complete. The core traceability chain, federated governance
|
||
machinery, and cross-framework adapter protocol are all production-grade. The
|
||
GAAF-2026 review confirmed the Core layer is strong (3.4/5.0) but revealed
|
||
four critical gaps that must be closed before domain hubs can safely extend
|
||
the framework:
|
||
|
||
1. **No Extension Layer** — no mechanism for domain hubs to register their
|
||
widget types, event types, annotation categories, or policy vocabulary
|
||
with the framework. All type discriminators are unvalidated TEXT, creating
|
||
vocabulary divergence risk the moment two hubs independently name things.
|
||
|
||
2. **No `/contracts/` directory or `ARCHITECTURE-LAYERS.md`** — GAAF requires
|
||
these as living governance artifacts. Their absence means architectural
|
||
intent is implicit in spec documents and tribal knowledge rather than
|
||
machine-readable contracts in the repository.
|
||
|
||
3. **No hub kind classification** — the `hubs` table treats the framework host
|
||
(inter-hub itself) and domain consumer hubs (dev, ops, fin, sec) as
|
||
structurally identical. The Phase 10 Hub Registry cannot distinguish
|
||
providers from consumers without this.
|
||
|
||
4. **No architectural fitness functions** — architectural constraints exist
|
||
in the spec but are not automatically verified in CI. Layer boundary
|
||
violations can accumulate silently.
|
||
|
||
Reference: `specs/GoodSoftwareArchitectureFramework_2026.md`,
|
||
`specs/InteractionHubFrameworkSpecification_v0.2.md`,
|
||
GAAF review document (2026-03-31 analysis).
|
||
|
||
## Why This Workplan Precedes Phase 9
|
||
|
||
Phase 9 exposes the IHF as a versioned external API and generates an OpenAPI
|
||
3.1 specification. That specification must enumerate type discriminators
|
||
(widget_type, event_type, annotation category, policy_scope) as finite enum
|
||
arrays — not arbitrary strings. If the type registries do not exist before
|
||
Phase 9 begins, the OpenAPI spec will document TEXT fields and the API will
|
||
be incorrect by design. The type registries must be stable before the API
|
||
contract is written.
|
||
|
||
Phase 10 (Hub Registry and Marketplace) is the extension layer in IHF terms.
|
||
Its hub registry IS the `HubCapabilityManifest` table introduced here, scaled
|
||
to a public-facing UI. Building Phase 10 without first establishing the
|
||
manifest schema would require a breaking re-architecture mid-phase.
|
||
|
||
## GAAF Compliance Targets
|
||
|
||
After this workplan, inter-hub should score:
|
||
|
||
| Layer | Before | Target |
|
||
|---|---|---|
|
||
| Core | 3.4 | 3.8 |
|
||
| Functional | 2.2 | 3.2 |
|
||
| Customization | 1.5 | 2.5 |
|
||
| Configuration | 1.6 | 3.0 |
|
||
| Extensions | 0.3 | 3.5 |
|
||
| Cross-layer | 2.3 | 3.5 |
|
||
| **Weighted total** | **2.23** | **~3.3** |
|
||
|
||
The target moves the framework from "Needs restructuring" (≤2.4) to "Usable
|
||
but vulnerable" (2.5–3.4), clearing the floor for Phase 9+ work. A score of
|
||
≥3.5 (Strong) is the Phase 10 exit target.
|
||
|
||
## Data Artifacts Introduced
|
||
|
||
`WidgetTypeRegistry`, `EventTypeRegistry`, `AnnotationCategoryRegistry`,
|
||
`PolicyScopeRegistry`, `HubCapabilityManifest`
|
||
|
||
Schema additions: `hubs.hub_kind`, maturity columns on existing contract
|
||
tables.
|
||
|
||
---
|
||
|
||
## Tasks
|
||
|
||
### T01 — Governance scaffolding: `/contracts/` and `ARCHITECTURE-LAYERS.md`
|
||
|
||
```task
|
||
id: IHUB-WP-0009-T01
|
||
status: done
|
||
priority: high
|
||
```
|
||
|
||
Pure documentation. No schema or code changes. Establishes the governance
|
||
artifacts required by GAAF §4, §9, and §12.
|
||
|
||
1. Create `/contracts/README.md` — contract catalog listing all contracts by
|
||
layer, with one-line descriptions and file links. Template:
|
||
|
||
```markdown
|
||
# IHF Contract Catalog
|
||
**Framework:** GAAF-2026 | **Last reviewed:** YYYY-MM-DD
|
||
|
||
## Core Contracts
|
||
- [widget-envelope-v1](core/widget-envelope-v1.md) — Required widget envelope
|
||
attributes and format rules
|
||
- [append-only-events-v1](core/append-only-events-v1.md) — Immutability
|
||
invariant for interaction_events and outcome_signals
|
||
|
||
## Functional Contracts
|
||
- [interaction-reporting-v1](functional/interaction-reporting-v1.md) — REST
|
||
API contract for external event and annotation submission
|
||
- [module-maturity-labels](functional/module-maturity-labels.md) — Definition
|
||
of Experimental / Beta / Stable / Deprecated for IHF modules
|
||
|
||
## Extensions Contracts
|
||
- [hub-capability-manifest-v1](extensions/hub-capability-manifest-v1.md) —
|
||
Domain hub extension registration protocol
|
||
```
|
||
|
||
2. Create `/contracts/core/widget-envelope-v1.md`:
|
||
- Required `data-*` attributes: `data-widget-id`, `data-hub-id`,
|
||
`data-view-context`, `data-widget-type`
|
||
- Optional: `data-capability-ref`, `data-policy-scope`,
|
||
`data-widget-version`, `data-experiment-variant`
|
||
- Format rules: `data-widget-id` must be a valid UUID; `data-hub-id` must
|
||
match a registered hub slug; `data-widget-type` must exist in the
|
||
`widget_type_registry`
|
||
- Version: 1.0. Immutable after activation. New requirements → v1.1 with
|
||
backwards-compatible additions only.
|
||
- Failure mode: widgets missing required attributes are logged as
|
||
`malformed_envelope` events; they do not crash the capture pipeline.
|
||
|
||
3. Create `/contracts/core/append-only-events-v1.md`:
|
||
- Invariant: `interaction_events` and `outcome_signals` rows are never
|
||
updated or deleted after insertion.
|
||
- Enforcement: PostgreSQL triggers `interaction_events_no_update`,
|
||
`interaction_events_no_delete`, `outcome_signals_no_update`,
|
||
`outcome_signals_no_delete`.
|
||
- Correction policy: erroneous events are retracted by inserting a new
|
||
event of type `retracted` with `metadata.retracted_event_id` pointing
|
||
to the original. The original row is never modified.
|
||
- Failure mode: any attempt to UPDATE or DELETE raises a PostgreSQL
|
||
exception with message "is append-only".
|
||
|
||
4. Create `/contracts/functional/interaction-reporting-v1.md`:
|
||
- Summarise the `InteractionReportingContract` DB record as a human and
|
||
machine-readable contract file. Include endpoint path, accepted event
|
||
types, required payload fields, auth scheme.
|
||
- Note: the canonical source of truth is the active
|
||
`interaction_reporting_contracts` row; this file is the discoverable
|
||
declaration.
|
||
|
||
5. Create `/contracts/functional/module-maturity-labels.md`:
|
||
- **Stable**: public interface will not change within a major version.
|
||
Removing a Stable field is a breaking change requiring a major version bump.
|
||
- **Beta**: interface is finalised for typical use but edge-case fields may
|
||
change with a minor-version notice. Suitable for production with awareness.
|
||
- **Experimental**: interface may change without notice between patches.
|
||
Use only for internal prototyping or explicit opt-in.
|
||
- **Deprecated**: will be removed in the next major version. A replacement
|
||
is documented in the contract.
|
||
|
||
6. Create `/contracts/extensions/hub-capability-manifest-v1.md`:
|
||
- Describes the `HubCapabilityManifest` schema (introduced in T05).
|
||
- Registration workflow: create manifest in draft → declare types → activate
|
||
(auto-registers declared types into their registries).
|
||
- Invariant: once a type name is added to a registry, it cannot be deleted
|
||
(only deprecated). Names are permanent — hubs may depend on them.
|
||
- Versioning: manifest_version tracks the protocol version, not the content
|
||
version. Content changes in draft do not require a version bump; a new
|
||
active manifest supersedes its predecessor.
|
||
|
||
7. Create `ARCHITECTURE-LAYERS.md` at the repository root using the GAAF §12.1
|
||
template. Include:
|
||
- Layer map: Core (Hub, Widget, WidgetVersion, InteractionEvent, Annotation,
|
||
traceability chain, append-only invariants, widget envelope) | Functional
|
||
(RequirementCandidate lifecycle through to FederatedGovernance) |
|
||
Customization (HubRoutingRule, FederatedPolicyOverlay — hub-specific
|
||
routing and policy) | Configuration (hub_kind, policy_scope, hub api_key,
|
||
HubCapabilityManifest) | Extensions (HubCapabilityManifest + type
|
||
registries — domain hub vocabulary registration)
|
||
- Dependency rule diagram
|
||
- GAAF weighted scorecard (initial values from 2026-03-31 review)
|
||
- Next review date: 2026-09-30
|
||
|
||
**Exit criteria:** `/contracts/` exists with six contract files; `README.md`
|
||
catalogs all of them; `ARCHITECTURE-LAYERS.md` exists at root with layer map
|
||
and scorecard filled in.
|
||
|
||
---
|
||
|
||
### T02 — Hub kind: classify hubs as `framework | domain | shared`
|
||
|
||
```task
|
||
id: IHUB-WP-0009-T02
|
||
status: done
|
||
priority: high
|
||
```
|
||
|
||
Adds the structural distinction between inter-hub (the framework host) and
|
||
domain consumer hubs. Required by Phase 10 Hub Registry and by any fitness
|
||
function that checks "all domain hubs have an active capability manifest".
|
||
|
||
1. Schema addition:
|
||
|
||
```sql
|
||
-- hub_kind: structural role of the hub within the framework
|
||
-- 'framework' = inter-hub itself; there is exactly one framework hub
|
||
-- 'domain' = a bounded domain consumer (dev-hub, ops-hub, fin-hub, sec-hub)
|
||
-- 'shared' = a cross-domain service hub (state-hub, governance-hub)
|
||
ALTER TABLE hubs
|
||
ADD COLUMN hub_kind TEXT NOT NULL DEFAULT 'domain';
|
||
|
||
CREATE INDEX hubs_hub_kind_idx ON hubs (hub_kind);
|
||
|
||
-- Constraint: only one framework hub at a time
|
||
CREATE UNIQUE INDEX hubs_one_framework_idx ON hubs (hub_kind)
|
||
WHERE hub_kind = 'framework';
|
||
```
|
||
|
||
2. Write and run migration.
|
||
|
||
3. If a hub row representing inter-hub itself exists (slug = 'inter-hub' or
|
||
equivalent), update it: `UPDATE hubs SET hub_kind = 'framework' WHERE
|
||
slug = 'inter-hub'`. If no such row exists, this is a documentation-only
|
||
concern — add a note in `ARCHITECTURE-LAYERS.md`.
|
||
|
||
4. Validation in `HubsController`:
|
||
- `hub_kind` must be one of `framework | domain | shared`
|
||
- `framework` kind cannot be set via the web UI (read-only in forms;
|
||
only settable via migration or seeding)
|
||
- Default for new hubs created through the UI: `domain`
|
||
|
||
5. Hub list view: add a kind badge column (framework=purple, domain=blue,
|
||
shared=teal). Hub show page: kind badge in the header.
|
||
|
||
6. Hub index: add a tab filter for kind (All / Framework / Domain / Shared).
|
||
|
||
**Exit criteria:** migration runs; hub kind badge renders correctly;
|
||
constraint prevents a second `framework` hub; new hubs default to `domain`.
|
||
|
||
---
|
||
|
||
### T03 — Type registries: registered vocabularies for widget type, event type, annotation category, and policy scope
|
||
|
||
```task
|
||
id: IHUB-WP-0009-T03
|
||
status: done
|
||
priority: high
|
||
```
|
||
|
||
This is the most consequential task in the workplan. These four registries
|
||
replace the implicit, unvalidated TEXT vocabularies that all type
|
||
discriminators currently rely on. They are the foundation for both the
|
||
Phase 9 OpenAPI enumerations and the Phase 10 marketplace's type-safe
|
||
widget patterns.
|
||
|
||
1. Schema — four registry tables:
|
||
|
||
```sql
|
||
-- widget_type_registry: registered widget types
|
||
CREATE TABLE widget_type_registry (
|
||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||
name TEXT NOT NULL UNIQUE,
|
||
-- canonical identifier: lowercase-hyphenated, e.g. 'pipeline-status'
|
||
label TEXT NOT NULL,
|
||
description TEXT,
|
||
owner_hub_id UUID REFERENCES hubs(id),
|
||
-- null = framework-level type (cross-domain); non-null = domain-owned
|
||
status TEXT NOT NULL DEFAULT 'active',
|
||
-- 'active' | 'deprecated'
|
||
deprecated_in_favour_of TEXT,
|
||
-- name of the replacement type, if deprecated
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL
|
||
);
|
||
|
||
CREATE INDEX widget_type_registry_status_idx
|
||
ON widget_type_registry (status);
|
||
CREATE INDEX widget_type_registry_owner_hub_idx
|
||
ON widget_type_registry (owner_hub_id);
|
||
|
||
-- event_type_registry: registered interaction event types
|
||
CREATE TABLE event_type_registry (
|
||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||
name TEXT NOT NULL UNIQUE,
|
||
label TEXT NOT NULL,
|
||
description TEXT,
|
||
owner_hub_id UUID REFERENCES hubs(id),
|
||
status TEXT NOT NULL DEFAULT 'active',
|
||
deprecated_in_favour_of TEXT,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL
|
||
);
|
||
|
||
CREATE INDEX event_type_registry_status_idx
|
||
ON event_type_registry (status);
|
||
|
||
-- annotation_category_registry: registered annotation categories
|
||
CREATE TABLE annotation_category_registry (
|
||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||
name TEXT NOT NULL UNIQUE,
|
||
label TEXT NOT NULL,
|
||
description TEXT,
|
||
owner_hub_id UUID REFERENCES hubs(id),
|
||
status TEXT NOT NULL DEFAULT 'active',
|
||
deprecated_in_favour_of TEXT,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL
|
||
);
|
||
|
||
CREATE INDEX annotation_category_registry_status_idx
|
||
ON annotation_category_registry (status);
|
||
|
||
-- policy_scope_registry: registered policy scope names
|
||
CREATE TABLE policy_scope_registry (
|
||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||
name TEXT NOT NULL UNIQUE,
|
||
label TEXT NOT NULL,
|
||
description TEXT,
|
||
owner_hub_id UUID REFERENCES hubs(id),
|
||
status TEXT NOT NULL DEFAULT 'active',
|
||
deprecated_in_favour_of TEXT,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL
|
||
);
|
||
|
||
CREATE INDEX policy_scope_registry_status_idx
|
||
ON policy_scope_registry (status);
|
||
```
|
||
|
||
2. Seed the framework-level vocabulary (owner_hub_id = NULL):
|
||
|
||
**Widget types** (from IHF spec §6.2 and existing usage):
|
||
`chart`, `form`, `table`, `action`, `panel`, `workflow-step`,
|
||
`recommendation`, `chat`, `diff`
|
||
|
||
**Event types** (from IHF spec §6.4):
|
||
`viewed`, `focused`, `clicked`, `submitted`, `abandoned`, `retried`,
|
||
`failed`, `commented`, `flagged_confusing`, `flagged_helpful`,
|
||
`blocked_by_policy`, `escalated`, `accepted_recommendation`,
|
||
`rejected_recommendation`
|
||
|
||
**Annotation categories** (from schema defaults and IHF spec §6.6):
|
||
`friction`, `missing_capability`, `policy_conflict`, `trust_deficit`,
|
||
`accessibility`, `workflow_bottleneck`, `documentation_gap`,
|
||
`product_opportunity`, `governance_concern`
|
||
|
||
**Policy scopes** (from existing usage):
|
||
`internal`, `org-wide`, `external`, `regulatory`, `security`
|
||
|
||
3. Scaffold `TypeRegistriesController` (single controller for all four
|
||
registries, scoped by `registry` parameter):
|
||
- `index { registry }`: table of all entries for the given registry —
|
||
name, label, owner hub (or "Framework"), status badge, created at
|
||
- `show { registry, id }`: full detail including deprecated_in_favour_of
|
||
- `new { registry }` / `create`: add a new type. `owner_hub_id` defaults
|
||
to the current user's hub (if applicable). Framework-level entries
|
||
(owner_hub_id = NULL) are restricted to admin users.
|
||
- `DeprecateTypeAction { registry, id }`: sets `status = 'deprecated'`,
|
||
requires `deprecated_in_favour_of` to be set. No delete — names are
|
||
permanent.
|
||
- No edit of `name` after creation (names are permanent identifiers).
|
||
Label and description are editable.
|
||
|
||
4. Link "Type Registries" from global nav (admin section).
|
||
|
||
5. Framework-level type entries are pre-seeded by the migration and not
|
||
creatable via the UI without admin privileges. Hub-owned types are
|
||
created by any authenticated user.
|
||
|
||
**Exit criteria:** all four registry tables exist; framework vocabulary is
|
||
seeded; CRUD UI works; deprecation works; name edits are blocked; registry
|
||
index is accessible from nav.
|
||
|
||
---
|
||
|
||
### T04 — Registry-backed validation: type discriminators checked against registries in controllers
|
||
|
||
```task
|
||
id: IHUB-WP-0009-T04
|
||
status: done
|
||
priority: high
|
||
```
|
||
|
||
Wire the four type registries (T03) into the controllers that create records
|
||
using type discriminator fields. This transforms the registries from
|
||
documentation artifacts into enforced contracts.
|
||
|
||
Also adds `maturity` to the three existing contract tables so that
|
||
functional module stability is machine-readable.
|
||
|
||
**Controller validations to add:**
|
||
|
||
1. `WidgetsController` — `CreateWidgetAction` and `UpdateWidgetAction`:
|
||
- Validate `widget_type` exists in `widget_type_registry` with
|
||
`status = 'active'`
|
||
- Validate `policy_scope` exists in `policy_scope_registry` with
|
||
`status = 'active'`
|
||
- Error message format: "Widget type 'X' is not registered. Register it
|
||
in the Type Registry or choose an existing type."
|
||
- Widget `new` and `edit` forms: replace `widget_type` free-text input
|
||
with a `<select>` populated from `widget_type_registry` (active entries
|
||
first; framework-level types at top, hub-owned below a divider).
|
||
Same for `policy_scope`.
|
||
|
||
2. `InteractionEventsController` and `ApiInteractionEventsController` —
|
||
`CreateInteractionEventAction`:
|
||
- Validate `event_type` exists in `event_type_registry` with
|
||
`status = 'active'`
|
||
- The API controller returns HTTP 422 with
|
||
`{"error": "event_type 'X' not registered"}` if validation fails.
|
||
- New/create form and API docs: enumerate active event types.
|
||
|
||
3. `AnnotationsController` — `CreateAnnotationAction`:
|
||
- Validate `category` exists in `annotation_category_registry` with
|
||
`status = 'active'`
|
||
- Annotation `new` form: replace `category` text input with `<select>`.
|
||
|
||
4. `HubRoutingRulesController` — `CreateHubRoutingRuleAction` and
|
||
`UpdateHubRoutingRuleAction`:
|
||
- If `match_widget_type` is set, validate it exists in
|
||
`widget_type_registry`
|
||
- If `match_category` is set, validate it exists in
|
||
`annotation_category_registry`
|
||
|
||
5. Add a shared validation helper in `Application/Helper/TypeRegistry.hs`:
|
||
|
||
```haskell
|
||
-- Returns Right () if the name is registered and active; Left error otherwise.
|
||
validateRegisteredType ::
|
||
(?modelContext :: ModelContext) =>
|
||
Text -> Text -> IO (Either Text ())
|
||
validateRegisteredType registryTable name = ...
|
||
```
|
||
|
||
Use this helper from all four controllers above.
|
||
|
||
**Maturity on existing contract tables:**
|
||
|
||
```sql
|
||
-- Add maturity tracking to existing contract tables (T04)
|
||
ALTER TABLE envelope_emission_contracts
|
||
ADD COLUMN maturity TEXT NOT NULL DEFAULT 'stable';
|
||
|
||
ALTER TABLE interaction_reporting_contracts
|
||
ADD COLUMN maturity TEXT NOT NULL DEFAULT 'stable';
|
||
|
||
ALTER TABLE widget_adapter_specs
|
||
ADD COLUMN maturity TEXT NOT NULL DEFAULT 'beta';
|
||
-- existing adapter specs are beta until explicitly promoted
|
||
```
|
||
|
||
Valid values: `experimental | beta | stable | deprecated`
|
||
(consistent with `/contracts/functional/module-maturity-labels.md`).
|
||
|
||
Show maturity badge on the show views for each contract table.
|
||
|
||
**Exit criteria:** creating a widget with an unregistered `widget_type`
|
||
returns a validation error; creating an interaction event with an
|
||
unregistered `event_type` returns a validation error (web and API);
|
||
creating an annotation with an unregistered `category` returns a validation
|
||
error; widget and annotation forms render as select dropdowns; maturity
|
||
columns exist and render badges on contract show pages.
|
||
|
||
---
|
||
|
||
### T05 — HubCapabilityManifest: domain hub extension registration
|
||
|
||
```task
|
||
id: IHUB-WP-0009-T05
|
||
status: done
|
||
priority: high
|
||
```
|
||
|
||
Introduces the GAAF Extensions layer: a formal mechanism for domain hubs to
|
||
declare the vocabulary they contribute to the framework. This is the central
|
||
artifact for the extension registration goal. Phase 10's Hub Registry will
|
||
build its public-facing UI directly on top of this table.
|
||
|
||
1. Schema:
|
||
|
||
```sql
|
||
-- HubCapabilityManifest: a domain hub's declaration of the types it
|
||
-- introduces and the capabilities it governs.
|
||
CREATE TABLE hub_capability_manifests (
|
||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||
hub_id UUID NOT NULL UNIQUE REFERENCES hubs(id),
|
||
manifest_version TEXT NOT NULL DEFAULT '1.0',
|
||
-- protocol version of the manifest format, not the hub's content version
|
||
declared_widget_types JSONB NOT NULL DEFAULT '[]',
|
||
-- array of type names referencing widget_type_registry
|
||
declared_event_types JSONB NOT NULL DEFAULT '[]',
|
||
declared_annotation_categories JSONB NOT NULL DEFAULT '[]',
|
||
declared_policy_scopes JSONB NOT NULL DEFAULT '[]',
|
||
capability_description TEXT,
|
||
-- human-readable summary of what this hub governs
|
||
contact TEXT,
|
||
-- team / person responsible for this hub's vocabulary
|
||
status TEXT NOT NULL DEFAULT 'draft',
|
||
-- 'draft' | 'active' | 'retired'
|
||
activated_at TIMESTAMP WITH TIME ZONE,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
|
||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL
|
||
);
|
||
|
||
CREATE INDEX hub_capability_manifests_hub_id_idx
|
||
ON hub_capability_manifests (hub_id);
|
||
CREATE INDEX hub_capability_manifests_status_idx
|
||
ON hub_capability_manifests (status);
|
||
```
|
||
|
||
2. Activation semantics (the critical business logic):
|
||
On `ActivateManifestAction { hubCapabilityManifestId }`:
|
||
- For each name in `declared_widget_types`: insert into
|
||
`widget_type_registry (name, owner_hub_id)` if not already present.
|
||
If the name exists with a different `owner_hub_id`, return a validation
|
||
error: "Type 'X' is already owned by hub Y. Coordinate with that hub
|
||
to share or rename."
|
||
- Same for `declared_event_types`, `declared_annotation_categories`,
|
||
`declared_policy_scopes`.
|
||
- Set `status = 'active'`, `activated_at = now()`.
|
||
- Once active, `declared_*` arrays are read-only. To add new types, the
|
||
hub operator amends the manifest: creates a new `draft` record
|
||
(the unique constraint is per `hub_id`, so the active record must be
|
||
retired first) or the update goes via a `DraftAmendmentAction` that
|
||
merges new types into the active manifest with a review step.
|
||
- A hub with `hub_kind = 'domain'` should have an active manifest before
|
||
it creates any hub-owned type registry entries. This is enforced as a
|
||
warning (not a hard block) in the activation flow.
|
||
|
||
3. Scaffold `HubCapabilityManifestsController`:
|
||
- `index`: table of all manifests — hub name, kind badge, status badge,
|
||
declared type counts (widget / event / category / policy), activated at
|
||
- `show { id }`: full detail — hub info, all declared type arrays with
|
||
links to their registry entries, activation history, contact
|
||
- `new { hubId }` / `create`: creates a draft manifest for a hub.
|
||
One manifest per hub (unique constraint); if a draft exists, redirect
|
||
to `edit`.
|
||
- `edit` / `update`: edit draft manifest fields. Active manifests are
|
||
read-only except via `DraftAmendmentAction`.
|
||
- `ActivateManifestAction { id }`: runs the activation workflow above.
|
||
- `RetireManifestAction { id }`: sets `status = 'retired'`. The hub's
|
||
types remain in the registry but the manifest is no longer current.
|
||
|
||
4. Hub show page: add "Capability Manifest" section — status badge (No
|
||
manifest / Draft / Active / Retired), declared type summary, link to
|
||
manifest show page, "Register Capabilities" button if no active manifest.
|
||
|
||
5. Link "Extensions" from global nav (lists all active manifests).
|
||
|
||
**Exit criteria:** manifest can be created in draft; activation registers all
|
||
declared types into registries; name conflict during activation returns an
|
||
error; activated manifest is read-only on declared types; hub show page
|
||
displays manifest status; extensions nav entry renders all active manifests.
|
||
|
||
---
|
||
|
||
### T06 — Functional module maturity documentation and domain hub extension guide
|
||
|
||
```task
|
||
id: IHUB-WP-0009-T06
|
||
status: done
|
||
priority: medium
|
||
```
|
||
|
||
Documentation tasks that complete the GAAF Functional and Extensions layer
|
||
requirements. No schema changes.
|
||
|
||
1. Create `docs/functional-modules.md` — the authoritative maturity register
|
||
for all IHF functional modules:
|
||
|
||
| Module | Phase introduced | Maturity | Stability guarantee | Deprecation policy |
|
||
|--------|-----------------|----------|--------------------|--------------------|
|
||
| RequirementCandidate lifecycle | Phase 2 | Stable | Schema and controller API frozen | Major version only |
|
||
| DecisionRecord + governance ledger | Phase 3 | Stable | Schema and controller API frozen | Major version only |
|
||
| DeploymentRecord + OutcomeSignal | Phase 4 | Stable | Append-only invariant permanent | Major version only |
|
||
| AgentProposal + review workflow | Phase 5 | Beta | Core fields stable; confidence model may extend | Minor version notice |
|
||
| Cross-framework adapter contracts | Phase 6 | Stable | EnvelopeEmissionContract v1.0 immutable | Superseding contract only |
|
||
| FrictionScore + BottleneckRecord | Phase 7 | Beta | Computation algorithm may improve | Minor version notice |
|
||
| HubHealthSnapshot | Phase 7 | Beta | Snapshot schema stable; score formula may change | Minor version notice |
|
||
| CrossHubPropagation | Phase 7 | Experimental | Pattern detection logic evolving | No notice |
|
||
| WidgetOwnership + routing | Phase 8 | Stable | Ownership audit pattern permanent | Major version only |
|
||
| FederatedPolicyOverlay | Phase 8 | Beta | Activation immutability permanent; scope model may extend | Minor version notice |
|
||
| StewardshipRole | Phase 8 | Stable | Point-in-time audit pattern permanent | Major version only |
|
||
| ArchiveRecord + lineage inspector | Phase 8 | Beta | Soft-delete pattern stable; lineage query may deepen | Minor version notice |
|
||
| Type registries (this workplan) | GAAF | Beta | Schema stable; seed vocabulary may expand | Additive only |
|
||
| HubCapabilityManifest (this workplan) | GAAF | Beta | Activation semantics stable; manifest protocol may version | Minor version notice |
|
||
|
||
For each module: also document known limitations and what is NOT
|
||
guaranteed.
|
||
|
||
2. Create `docs/domain-hub-extension-guide.md` — the integration guide for
|
||
developers building a new domain hub (dev-hub, ops-hub, etc.):
|
||
|
||
Sections:
|
||
- **What inter-hub provides** — the framework services a domain hub
|
||
inherits without any setup (event capture, annotation, requirements,
|
||
governance, AI assistance, observability, federation)
|
||
- **Extension registration in three steps**:
|
||
1. Create a hub row with `hub_kind = 'domain'`
|
||
2. Create a `HubCapabilityManifest` in draft — declare widget types,
|
||
event types, annotation categories, policy scopes your domain
|
||
introduces
|
||
3. Activate the manifest — your types are now registered and validated
|
||
by the framework
|
||
- **Naming your types** — conventions: lowercase-hyphenated, prefixed
|
||
with domain shortcode where collision-prone (e.g. `dev-pipeline-run`,
|
||
`fin-budget-alert`, `sec-vuln-widget`). Framework-level types have no
|
||
prefix.
|
||
- **Using framework-level types** — no registration needed; any active
|
||
framework type is available to all hubs
|
||
- **Cross-hub routing** — how to configure `HubRoutingRule` entries so
|
||
cross-domain candidates route correctly to your hub
|
||
- **What changes between upgrades** — how to interpret the maturity
|
||
labels; Stable = safe to depend on; Beta = upgrade-aware; Experimental
|
||
= internal use only
|
||
- **FAQ**: Can two hubs declare the same type name? (No — activate order
|
||
determines ownership; coordinate with the owning hub to share.) Can a
|
||
type be renamed? (No — deprecated and replaced by a new name.) Can a
|
||
hub retire its manifest? (Yes — its types remain in registries but are
|
||
orphaned; reassign ownership or deprecate them.)
|
||
|
||
**Exit criteria:** `docs/functional-modules.md` exists with all modules
|
||
listed; `docs/domain-hub-extension-guide.md` exists with all three
|
||
registration steps and naming conventions covered.
|
||
|
||
---
|
||
|
||
### T07 — Architectural fitness functions in CI
|
||
|
||
```task
|
||
id: IHUB-WP-0009-T07
|
||
status: done
|
||
priority: medium
|
||
```
|
||
|
||
Implements the GAAF §8 requirement for automated architectural verification.
|
||
Adds a `Test/Architecture/` test module that runs as part of the standard
|
||
`test` command.
|
||
|
||
1. Create `Test/Architecture/LayerBoundarySpec.hs`:
|
||
|
||
**Test 1 — Core immutability contract presence**:
|
||
Assert that `Application/Schema.sql` contains the four trigger names that
|
||
enforce append-only semantics:
|
||
- `interaction_events_no_update`
|
||
- `interaction_events_no_delete`
|
||
- `outcome_signals_no_update`
|
||
- `outcome_signals_no_delete`
|
||
Failure message: "Core append-only invariant trigger missing from schema."
|
||
|
||
**Test 2 — Contract artifact presence**:
|
||
Assert that the following files exist (filesystem check):
|
||
- `/contracts/README.md`
|
||
- `/contracts/core/widget-envelope-v1.md`
|
||
- `/contracts/core/append-only-events-v1.md`
|
||
- `ARCHITECTURE-LAYERS.md`
|
||
Failure message: "GAAF contract artifact missing: {path}. See
|
||
ARCHITECTURE-LAYERS.md §Compliance."
|
||
|
||
**Test 3 — Type registry non-empty**:
|
||
Query `widget_type_registry`, `event_type_registry`,
|
||
`annotation_category_registry`, `policy_scope_registry`. Assert each
|
||
table has ≥1 active entry.
|
||
Failure message: "Type registry {table} has no active entries. Seed the
|
||
framework vocabulary before running tests."
|
||
|
||
**Test 4 — No bare TEXT type discriminators in new migrations**:
|
||
Read `Application/Schema.sql`. For any column named `widget_type`,
|
||
`event_type`, `category`, or `policy_scope` added after a specific marker
|
||
comment `-- GAAF: type registries enforced from here`, assert the column
|
||
definition includes a `REFERENCES` or `CHECK` constraint.
|
||
Failure message: "Column {col} in {table} uses bare TEXT for a type
|
||
discriminator. Reference a registry table or add a CHECK constraint."
|
||
|
||
**Test 5 — Domain hub manifest coverage** (warning, not failure):
|
||
Query all `hubs` rows with `hub_kind = 'domain'`. For each, check that a
|
||
`hub_capability_manifests` record exists with `status = 'active'`.
|
||
Log a warning (not a test failure): "Domain hub '{slug}' has no active
|
||
capability manifest. Register its types via the Extension Registry before
|
||
adding hub-owned type discriminators."
|
||
|
||
2. Register `Test/Architecture/LayerBoundarySpec.hs` in the test suite
|
||
(`Test/Main.hs`).
|
||
|
||
3. Add marker comment to `Application/Schema.sql` after the last Phase 8
|
||
migration block:
|
||
|
||
```sql
|
||
-- GAAF: type registries enforced from here (IHUB-WP-0009)
|
||
-- All new type discriminator columns (widget_type, event_type, category,
|
||
-- policy_scope) must reference a registry table or carry a CHECK constraint.
|
||
```
|
||
|
||
**Exit criteria:** `test` includes the architecture spec; Tests 1–3 pass
|
||
against a fresh database with seed data; Test 4 passes against the current
|
||
schema; Test 5 produces no warnings for a DB with a seeded framework hub and
|
||
no domain hubs yet.
|
||
|
||
---
|
||
|
||
### T08 — GAAF gate: scorecard, consistency, documentation updates
|
||
|
||
```task
|
||
id: IHUB-WP-0009-T08
|
||
status: done
|
||
priority: high
|
||
```
|
||
|
||
Closes the workplan, validates GAAF compliance targets, and prepares the
|
||
repository for Phase 9.
|
||
|
||
1. **Update `ARCHITECTURE-LAYERS.md` scorecard** with post-workplan scores.
|
||
Fill in the actual weighted total and compare against targets from this
|
||
workplan's Background section. Document any criteria that remain below
|
||
target and why.
|
||
|
||
2. **Update `SCOPE.md`**:
|
||
- Current state: add "GAAF compliance foundation complete (IHUB-WP-0009);
|
||
type registries, extension manifests, architectural contracts, and
|
||
fitness functions in place"
|
||
- Upstream dependencies: update hub-core reference to note that
|
||
`HubCapabilityManifest` in inter-hub now provides the DB-side of
|
||
capability registration; hub-core (when implemented) will provide the
|
||
shared Haskell library that domain hubs compile against.
|
||
|
||
3. **Update `CLAUDE.md`**:
|
||
- Active workplan: change from IHUB-WP-0005 to IHUB-WP-0010 (Phase 9)
|
||
- Add "GAAF compliance" context block noting that type registries must be
|
||
kept seeded and all new type discriminator columns must reference a
|
||
registry
|
||
- Add reference to `docs/domain-hub-extension-guide.md` as the entry
|
||
point for new domain hub developers
|
||
|
||
4. **State Hub consistency sync**:
|
||
`check_repo_consistency(repo_slug="inter-hub", fix=True)`
|
||
|
||
5. **Integration tests** (`Test/`):
|
||
- `HubCapabilityManifest` create in draft + activate + verify type
|
||
auto-registration in registries
|
||
- Activation conflict: two hubs declaring the same type name → second
|
||
activation returns a conflict error
|
||
- Widget create with unregistered type → validation error
|
||
- Widget create with registered type → success
|
||
- InteractionEvent create via API with unregistered event_type → 422
|
||
- Annotation create with unregistered category → validation error
|
||
- Architecture spec tests pass (T07)
|
||
|
||
6. **Smoke test checklist**:
|
||
- Create a hub with `hub_kind = 'domain'`; verify kind badge
|
||
- Open Type Registries nav; verify all four registries have seeded entries
|
||
- Create a widget with an unregistered `widget_type`; verify error
|
||
- Register a new widget type via the Type Registry admin UI; retry widget
|
||
creation → success
|
||
- Create a `HubCapabilityManifest` for the domain hub in draft; add two
|
||
custom widget types; activate → verify types appear in registry with
|
||
correct `owner_hub_id`
|
||
- Attempt to activate a second hub's manifest using the same type name →
|
||
verify conflict error
|
||
- Open Extensions nav; verify active manifest is listed
|
||
- Hub show page: verify "Capability Manifest" section shows active status
|
||
- Run `test`; verify architecture spec tests pass with no warnings
|
||
|
||
**Exit criteria:** all integration tests pass; smoke test completed without
|
||
errors; `ARCHITECTURE-LAYERS.md` scorecard shows weighted total ≥3.1;
|
||
`SCOPE.md` and `CLAUDE.md` updated; State Hub consistency sync reports no
|
||
errors.
|
||
|
||
---
|
||
|
||
## Task Dependencies
|
||
|
||
```
|
||
T01 (contracts scaffold) ──→ T08 (gate reads scorecard)
|
||
T02 (hub_kind) ──→ T05 (manifest references hub_kind)
|
||
──→ T07 (fitness test 5 queries hub_kind)
|
||
T03 (type registries) ──→ T04 (validation reads registries)
|
||
──→ T05 (manifest declares types into registries)
|
||
──→ T07 (fitness tests 3 and 4 check registries)
|
||
T04 (validation) ──→ T08 (gate integration tests validate creation paths)
|
||
T05 (manifest) ──→ T06 (extension guide documents manifest workflow)
|
||
──→ T08 (gate integration tests activate manifests)
|
||
T06 (maturity docs) ──→ T08 (gate checks docs exist)
|
||
T07 (fitness functions) ──→ T08 (gate runs architecture spec)
|
||
All T01–T07 ──→ T08 (gate)
|
||
```
|
||
|
||
Parallelisable: T01 (documentation only) can proceed in parallel with T02–T07.
|
||
T02 and T03 are independent of each other and can proceed in parallel.
|
||
T04 requires T03 complete. T05 requires T02 and T03 complete. T06 requires
|
||
T05 complete. T07 requires T03 complete.
|
||
|
||
## Phase 9 Readiness Checklist
|
||
|
||
The following must be true before IHUB-WP-0010 (Phase 9) begins:
|
||
|
||
- [ ] All four type registries are seeded with framework vocabulary
|
||
- [ ] All type discriminator TEXT columns are validated against registries in controllers
|
||
- [ ] `HubCapabilityManifest` table exists and the activation workflow is operational
|
||
- [ ] `/contracts/` contains at minimum `core/widget-envelope-v1.md`,
|
||
`core/append-only-events-v1.md`, `functional/interaction-reporting-v1.md`,
|
||
and `extensions/hub-capability-manifest-v1.md`
|
||
- [ ] `ARCHITECTURE-LAYERS.md` exists and is current
|
||
- [ ] Architecture fitness functions pass in CI
|
||
- [ ] `ARCHITECTURE-LAYERS.md` weighted scorecard is ≥3.1
|
||
|
||
## Notes
|
||
|
||
- **Type names are permanent.** Once a name enters a registry (whether via
|
||
seed or manifest activation), it cannot be deleted — only deprecated. Build
|
||
the seeded vocabulary carefully. Domain hub names should use a
|
||
domain-shortcode prefix where collision is likely (see extension guide).
|
||
|
||
- **Manifests are the domain hub's identity contract.** A domain hub that has
|
||
not activated a manifest is an unregistered participant. It can still create
|
||
hubs and widgets (the framework permits it) but its types are not validated,
|
||
not namespaced, and not discoverable in Phase 10's marketplace.
|
||
|
||
- **This workplan does not split the shared PostgreSQL schema.** Domain
|
||
isolation at the database level (separate schemas or RLS) is a Phase 9+
|
||
concern. This workplan establishes the vocabulary-level isolation needed
|
||
before physical isolation decisions are made.
|
||
|
||
- **hub-core remains a future concern.** The Haskell shared library for domain
|
||
hub bootstrapping is planned but not blocked on this workplan.
|
||
`HubCapabilityManifest` provides the DB-side registration contract.
|
||
hub-core, when implemented, will provide the compile-time Haskell types
|
||
that correspond to a hub's declared vocabulary. The two are complementary,
|
||
not redundant.
|