Files
inter-hub/workplans/IHUB-WP-0009-gaaf-compliance-foundation.md
Bernd Worsch b5d73aa18b
Some checks failed
Test / test (push) Has been cancelled
feat(WP-0009): IHF GAAF Compliance Foundation — type registries, extension manifests, architectural contracts
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>
2026-03-31 21:17:39 +00:00

858 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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: "T01T03 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: "T03T08 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 18 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.53.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 13 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 T01T07 ──→ T08 (gate)
```
Parallelisable: T01 (documentation only) can proceed in parallel with T02T07.
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.