39 KiB
id, type, title, domain, repo, status, owner, topic_slug, created, updated, completed, token_cost, state_hub_sync, state_hub_workstream_id
| id | type | title | domain | repo | status | owner | topic_slug | created | updated | completed | token_cost | state_hub_sync | state_hub_workstream_id | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| IHUB-WP-0009 | workplan | IHF GAAF Compliance Foundation — Type Registries, Extension Manifests, and Architectural Contracts | inter_hub | inter-hub | done | custodian | inter_hub | 2026-03-31 | 2026-03-31 | 2026-03-31 |
|
done | 24ad18c7-f2a9-4cfd-88f0-4cbc78064bb0 |
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:
-
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.
-
No
/contracts/directory orARCHITECTURE-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. -
No hub kind classification — the
hubstable 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. -
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
id: IHUB-WP-0009-T01
status: done
priority: high
state_hub_task_id: "dbeaa00d-a5c1-47bf-9621-58ece75dcd42"
Pure documentation. No schema or code changes. Establishes the governance artifacts required by GAAF §4, §9, and §12.
-
Create
/contracts/README.md— contract catalog listing all contracts by layer, with one-line descriptions and file links. Template:# 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 -
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-idmust be a valid UUID;data-hub-idmust match a registered hub slug;data-widget-typemust exist in thewidget_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_envelopeevents; they do not crash the capture pipeline.
- Required
-
Create
/contracts/core/append-only-events-v1.md:- Invariant:
interaction_eventsandoutcome_signalsrows 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
retractedwithmetadata.retracted_event_idpointing 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".
- Invariant:
-
Create
/contracts/functional/interaction-reporting-v1.md:- Summarise the
InteractionReportingContractDB 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_contractsrow; this file is the discoverable declaration.
- Summarise the
-
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.
-
Create
/contracts/extensions/hub-capability-manifest-v1.md:- Describes the
HubCapabilityManifestschema (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.
- Describes the
-
Create
ARCHITECTURE-LAYERS.mdat 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
id: IHUB-WP-0009-T02
status: done
priority: high
state_hub_task_id: "139cad01-24fb-4586-bd64-b5e1f72d657c"
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".
-
Schema addition:
-- 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'; -
Write and run migration.
-
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 inARCHITECTURE-LAYERS.md. -
Validation in
HubsController:hub_kindmust be one offramework | domain | sharedframeworkkind 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
-
Hub list view: add a kind badge column (framework=purple, domain=blue, shared=teal). Hub show page: kind badge in the header.
-
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
id: IHUB-WP-0009-T03
status: done
priority: high
state_hub_task_id: "237e56e6-ee27-4947-a16b-098722b5ed42"
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.
-
Schema — four registry tables:
-- 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); -
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,diffEvent 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_recommendationAnnotation categories (from schema defaults and IHF spec §6.6):
friction,missing_capability,policy_conflict,trust_deficit,accessibility,workflow_bottleneck,documentation_gap,product_opportunity,governance_concernPolicy scopes (from existing usage):
internal,org-wide,external,regulatory,security -
Scaffold
TypeRegistriesController(single controller for all four registries, scoped byregistryparameter):index { registry }: table of all entries for the given registry — name, label, owner hub (or "Framework"), status badge, created atshow { registry, id }: full detail including deprecated_in_favour_ofnew { registry }/create: add a new type.owner_hub_iddefaults to the current user's hub (if applicable). Framework-level entries (owner_hub_id = NULL) are restricted to admin users.DeprecateTypeAction { registry, id }: setsstatus = 'deprecated', requiresdeprecated_in_favour_ofto be set. No delete — names are permanent.- No edit of
nameafter creation (names are permanent identifiers). Label and description are editable.
-
Link "Type Registries" from global nav (admin section).
-
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
id: IHUB-WP-0009-T04
status: done
priority: high
state_hub_task_id: "85882f5c-e52f-44b3-b68e-3f35e7baa9e7"
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:
-
WidgetsController—CreateWidgetActionandUpdateWidgetAction:- Validate
widget_typeexists inwidget_type_registrywithstatus = 'active' - Validate
policy_scopeexists inpolicy_scope_registrywithstatus = 'active' - Error message format: "Widget type 'X' is not registered. Register it in the Type Registry or choose an existing type."
- Widget
newandeditforms: replacewidget_typefree-text input with a<select>populated fromwidget_type_registry(active entries first; framework-level types at top, hub-owned below a divider). Same forpolicy_scope.
- Validate
-
InteractionEventsControllerandApiInteractionEventsController—CreateInteractionEventAction:- Validate
event_typeexists inevent_type_registrywithstatus = '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.
- Validate
-
AnnotationsController—CreateAnnotationAction:- Validate
categoryexists inannotation_category_registrywithstatus = 'active' - Annotation
newform: replacecategorytext input with<select>.
- Validate
-
HubRoutingRulesController—CreateHubRoutingRuleActionandUpdateHubRoutingRuleAction:- If
match_widget_typeis set, validate it exists inwidget_type_registry - If
match_categoryis set, validate it exists inannotation_category_registry
- If
-
Add a shared validation helper in
Application/Helper/TypeRegistry.hs:-- 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:
-- 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
id: IHUB-WP-0009-T05
status: done
priority: high
state_hub_task_id: "1e5ad5db-e01f-41c5-9a81-759ab4dc88dd"
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.
-
Schema:
-- 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); -
Activation semantics (the critical business logic): On
ActivateManifestAction { hubCapabilityManifestId }:- For each name in
declared_widget_types: insert intowidget_type_registry (name, owner_hub_id)if not already present. If the name exists with a differentowner_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 newdraftrecord (the unique constraint is perhub_id, so the active record must be retired first) or the update goes via aDraftAmendmentActionthat 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.
- For each name in
-
Scaffold
HubCapabilityManifestsController:index: table of all manifests — hub name, kind badge, status badge, declared type counts (widget / event / category / policy), activated atshow { id }: full detail — hub info, all declared type arrays with links to their registry entries, activation history, contactnew { hubId }/create: creates a draft manifest for a hub. One manifest per hub (unique constraint); if a draft exists, redirect toedit.edit/update: edit draft manifest fields. Active manifests are read-only except viaDraftAmendmentAction.ActivateManifestAction { id }: runs the activation workflow above.RetireManifestAction { id }: setsstatus = 'retired'. The hub's types remain in the registry but the manifest is no longer current.
-
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.
-
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
id: IHUB-WP-0009-T06
status: done
priority: medium
state_hub_task_id: "dbe749b0-0ac2-437f-9ef7-d119b5b120dd"
Documentation tasks that complete the GAAF Functional and Extensions layer requirements. No schema changes.
-
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.
-
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:
- Create a hub row with
hub_kind = 'domain' - Create a
HubCapabilityManifestin draft — declare widget types, event types, annotation categories, policy scopes your domain introduces - Activate the manifest — your types are now registered and validated by the framework
- Create a hub row with
- 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
HubRoutingRuleentries 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
id: IHUB-WP-0009-T07
status: done
priority: medium
state_hub_task_id: "09109092-c1ca-48ba-b9b6-f4bc3d747910"
Implements the GAAF §8 requirement for automated architectural verification.
Adds a Test/Architecture/ test module that runs as part of the standard
test command.
-
Create
Test/Architecture/LayerBoundarySpec.hs:Test 1 — Core immutability contract presence: Assert that
Application/Schema.sqlcontains the four trigger names that enforce append-only semantics:interaction_events_no_updateinteraction_events_no_deleteoutcome_signals_no_updateoutcome_signals_no_deleteFailure 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.mdARCHITECTURE-LAYERS.mdFailure 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 namedwidget_type,event_type,category, orpolicy_scopeadded after a specific marker comment-- GAAF: type registries enforced from here, assert the column definition includes aREFERENCESorCHECKconstraint. 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
hubsrows withhub_kind = 'domain'. For each, check that ahub_capability_manifestsrecord exists withstatus = '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." -
Register
Test/Architecture/LayerBoundarySpec.hsin the test suite (Test/Main.hs). -
Add marker comment to
Application/Schema.sqlafter the last Phase 8 migration block:-- 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
id: IHUB-WP-0009-T08
status: done
priority: high
state_hub_task_id: "7a7dfc5b-77da-4658-b1ce-971518fb8cc8"
Closes the workplan, validates GAAF compliance targets, and prepares the repository for Phase 9.
-
Update
ARCHITECTURE-LAYERS.mdscorecard 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. -
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
HubCapabilityManifestin 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.
-
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.mdas the entry point for new domain hub developers
-
State Hub consistency sync:
check_repo_consistency(repo_slug="inter-hub", fix=True) -
Integration tests (
Test/):HubCapabilityManifestcreate 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)
-
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
HubCapabilityManifestfor the domain hub in draft; add two custom widget types; activate → verify types appear in registry with correctowner_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
- Create a hub with
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
HubCapabilityManifesttable exists and the activation workflow is operational/contracts/contains at minimumcore/widget-envelope-v1.md,core/append-only-events-v1.md,functional/interaction-reporting-v1.md, andextensions/hub-capability-manifest-v1.mdARCHITECTURE-LAYERS.mdexists and is current- Architecture fitness functions pass in CI
ARCHITECTURE-LAYERS.mdweighted 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.
HubCapabilityManifestprovides 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.