--- 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 ``. 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.