Files
inter-hub/Web/Routes.hs
Bernd Worsch 3cac021213
Some checks failed
Test / test (push) Has been cancelled
feat(WP-0010): IHF Phase 9 — External API Surface and Consumer SDKs
Delivers the full Phase 9 external API layer:

- Versioned REST API (/api/v2/) with OpenAPI 3.1 spec; enum arrays for
  widget_type, event_type, annotation category drawn live from registry tables
- OAuth 2.0 client credentials flow (/api/v2/token); hub:*:write scopes
  gated on active HubCapabilityManifest FK
- API key management: SHA256-hashed tokens, key_prefix for display,
  one-time reveal on creation, revocation support
- TypeScript and Python consumer SDKs generated from registry tables
  (/api/v2/sdk/ihf-client.ts, /api/v2/sdk/ihf-client.py)
- Webhook delivery: HMAC-SHA256 signing, append-only webhook_deliveries,
  fire-and-forget dispatch via forkIO, 3-retry logic
- Admin API dashboard with 24h stats (request count, error rate, last seen)
- Rate limiting (per-minute) and daily quota enforcement via api_request_log
- Schema migration: api_consumers, api_keys, webhook_subscriptions (CHECK
  constraint on 6 framework lifecycle topics), webhook_deliveries
  (append-only trigger), api_request_log
- ARCHITECTURE-LAYERS.md scorecard: 3.34 → 3.41 (approaching Strong)
- contracts/functional/interaction-reporting-v1.md extended with Phase 9
  endpoint catalogue and 422 validation error format

GAAF: no bare TEXT discriminators; webhook event_type uses CHECK constraint
over 6 allowed framework lifecycle topic strings (not widget event types).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 19:52:20 +00:00

226 lines
8.6 KiB
Haskell

module Web.Routes where
import IHP.RouterPrelude
import Generated.Types
import Web.Types
-- Hubs
instance AutoRoute HubsController
-- Widgets
instance AutoRoute WidgetsController
-- Interaction Events (POST /widgets/:widgetId/events)
instance AutoRoute InteractionEventsController
-- Annotations (scoped to widget: /widgets/:widgetId/annotations/)
instance AutoRoute AnnotationsController
-- Annotation Threads (scoped to widget)
instance AutoRoute AnnotationThreadsController
-- Requirement Candidates
instance AutoRoute RequirementCandidatesController
-- Requirements (Phase 3)
instance AutoRoute RequirementsController
-- Decision Records (Phase 3)
instance AutoRoute DecisionRecordsController
-- Deployment Records (Phase 4)
instance AutoRoute DeploymentRecordsController
-- Agent Proposals (Phase 5)
instance AutoRoute AgentProposalsController
-- Phase 6 — Cross-Framework UI Adaptation
-- API endpoint: POST /api/v1/interaction-events
instance CanRoute ApiInteractionEventsController where
parseRoute' = do
_ <- string "/api"
_ <- string "/v1"
_ <- string "/interaction-events"
endOfInput
pure CreateApiInteractionEventAction
instance HasPath ApiInteractionEventsController where
pathTo CreateApiInteractionEventAction = "/api/v1/interaction-events"
instance AutoRoute EnvelopeEmissionContractsController
instance AutoRoute InteractionReportingContractsController
instance AutoRoute WidgetAdapterSpecsController
-- Phase 7 — Advanced Observability
instance AutoRoute CrossHubPropagationsController
-- Phase 8 — Federated Hub Maturity
instance AutoRoute WidgetOwnershipsController
instance AutoRoute HubRoutingRulesController
instance AutoRoute FederatedPolicyOverlaysController
instance AutoRoute StewardshipRolesController
instance AutoRoute ArchiveRecordsController
instance AutoRoute FederatedGovernanceController
-- GAAF Compliance Foundation (IHUB-WP-0009)
instance AutoRoute TypeRegistriesController
instance AutoRoute HubCapabilityManifestsController
-- Phase 9 — External API Surface (IHUB-WP-0010)
-- Admin: API consumers, keys, webhooks, dashboard
instance AutoRoute ApiConsumersController
instance AutoRoute ApiKeysController
instance AutoRoute WebhookSubscriptionsController
instance AutoRoute ApiDashboardController
-- /api/v2/ REST endpoints (manual routing for versioned prefix)
instance CanRoute ApiV2WidgetsController where
parseRoute' = do
_ <- string "/api/v2/widgets"
choice
[ do endOfInput; pure ApiV2IndexWidgetsAction
, do _ <- string "/"; wId <- parseUUID; endOfInput
pure ApiV2ShowWidgetAction { widgetId = Id wId }
]
instance HasPath ApiV2WidgetsController where
pathTo ApiV2IndexWidgetsAction = "/api/v2/widgets"
pathTo ApiV2ShowWidgetAction { widgetId } = "/api/v2/widgets/" <> show widgetId
instance CanRoute ApiV2InteractionEventsController where
parseRoute' = do
_ <- string "/api/v2/interaction-events"
choice
[ do endOfInput; pure ApiV2IndexInteractionEventsAction
, do _ <- string "/"; eId <- parseUUID; endOfInput
pure ApiV2ShowInteractionEventAction { interactionEventId = Id eId }
]
instance HasPath ApiV2InteractionEventsController where
pathTo ApiV2IndexInteractionEventsAction = "/api/v2/interaction-events"
pathTo ApiV2ShowInteractionEventAction { interactionEventId } = "/api/v2/interaction-events/" <> show interactionEventId
pathTo ApiV2CreateInteractionEventAction = "/api/v2/interaction-events"
instance CanRoute ApiV2AnnotationsController where
parseRoute' = do
_ <- string "/api/v2/annotations"
choice
[ do endOfInput; pure ApiV2IndexAnnotationsAction
, do _ <- string "/"; aId <- parseUUID; endOfInput
pure ApiV2ShowAnnotationAction { annotationId = Id aId }
]
instance HasPath ApiV2AnnotationsController where
pathTo ApiV2IndexAnnotationsAction = "/api/v2/annotations"
pathTo ApiV2ShowAnnotationAction { annotationId } = "/api/v2/annotations/" <> show annotationId
pathTo ApiV2CreateAnnotationAction = "/api/v2/annotations"
instance CanRoute ApiV2RequirementCandidatesController where
parseRoute' = do
_ <- string "/api/v2/requirement-candidates"
choice
[ do endOfInput; pure ApiV2IndexRequirementCandidatesAction
, do _ <- string "/"; rcId <- parseUUID; endOfInput
pure ApiV2ShowRequirementCandidateAction { requirementCandidateId = Id rcId }
]
instance HasPath ApiV2RequirementCandidatesController where
pathTo ApiV2IndexRequirementCandidatesAction = "/api/v2/requirement-candidates"
pathTo ApiV2ShowRequirementCandidateAction { requirementCandidateId } = "/api/v2/requirement-candidates/" <> show requirementCandidateId
instance CanRoute ApiV2DecisionRecordsController where
parseRoute' = do
_ <- string "/api/v2/decision-records"
choice
[ do endOfInput; pure ApiV2IndexDecisionRecordsAction
, do _ <- string "/"; drId <- parseUUID; endOfInput
pure ApiV2ShowDecisionRecordAction { decisionRecordId = Id drId }
]
instance HasPath ApiV2DecisionRecordsController where
pathTo ApiV2IndexDecisionRecordsAction = "/api/v2/decision-records"
pathTo ApiV2ShowDecisionRecordAction { decisionRecordId } = "/api/v2/decision-records/" <> show decisionRecordId
instance CanRoute ApiV2DeploymentRecordsController where
parseRoute' = do
_ <- string "/api/v2/deployment-records"
choice
[ do endOfInput; pure ApiV2IndexDeploymentRecordsAction
, do _ <- string "/"; drId <- parseUUID; endOfInput
pure ApiV2ShowDeploymentRecordAction { deploymentRecordId = Id drId }
]
instance HasPath ApiV2DeploymentRecordsController where
pathTo ApiV2IndexDeploymentRecordsAction = "/api/v2/deployment-records"
pathTo ApiV2ShowDeploymentRecordAction { deploymentRecordId } = "/api/v2/deployment-records/" <> show deploymentRecordId
instance CanRoute ApiV2OutcomeSignalsController where
parseRoute' = do
_ <- string "/api/v2/outcome-signals"
choice
[ do endOfInput; pure ApiV2IndexOutcomeSignalsAction
, do _ <- string "/"; osId <- parseUUID; endOfInput
pure ApiV2ShowOutcomeSignalAction { outcomeSignalId = Id osId }
]
instance HasPath ApiV2OutcomeSignalsController where
pathTo ApiV2IndexOutcomeSignalsAction = "/api/v2/outcome-signals"
pathTo ApiV2ShowOutcomeSignalAction { outcomeSignalId } = "/api/v2/outcome-signals/" <> show outcomeSignalId
instance CanRoute ApiV2RegistriesController where
parseRoute' = do
_ <- string "/api/v2/"
choice
[ do _ <- string "widget-types"; endOfInput; pure ApiV2ListWidgetTypesAction
, do _ <- string "event-types"; endOfInput; pure ApiV2ListEventTypesAction
, do _ <- string "annotation-categories"; endOfInput; pure ApiV2ListAnnotationCategoriesAction
]
instance HasPath ApiV2RegistriesController where
pathTo ApiV2ListWidgetTypesAction = "/api/v2/widget-types"
pathTo ApiV2ListEventTypesAction = "/api/v2/event-types"
pathTo ApiV2ListAnnotationCategoriesAction = "/api/v2/annotation-categories"
instance CanRoute ApiV2OpenApiController where
parseRoute' = do
_ <- string "/api/v2/"
choice
[ do _ <- string "openapi.json"; endOfInput; pure ApiV2OpenApiJsonAction
, do _ <- string "openapi.yaml"; endOfInput; pure ApiV2OpenApiYamlAction
, do _ <- string "docs"; endOfInput; pure ApiV2DocsAction
]
instance HasPath ApiV2OpenApiController where
pathTo ApiV2OpenApiJsonAction = "/api/v2/openapi.json"
pathTo ApiV2OpenApiYamlAction = "/api/v2/openapi.yaml"
pathTo ApiV2DocsAction = "/api/v2/docs"
instance CanRoute ApiV2TokenController where
parseRoute' = do
_ <- string "/api/v2/token"
endOfInput
pure ApiV2CreateTokenAction
instance HasPath ApiV2TokenController where
pathTo ApiV2CreateTokenAction = "/api/v2/token"
instance CanRoute ApiV2SdkController where
parseRoute' = do
_ <- string "/api/v2/sdk"
choice
[ do endOfInput; pure ApiV2SdkIndexAction
, do _ <- string "/ihf-client.ts"; endOfInput; pure ApiV2SdkTsAction
, do _ <- string "/ihf-client.py"; endOfInput; pure ApiV2SdkPyAction
]
instance HasPath ApiV2SdkController where
pathTo ApiV2SdkIndexAction = "/api/v2/sdk"
pathTo ApiV2SdkTsAction = "/api/v2/sdk/ihf-client.ts"
pathTo ApiV2SdkPyAction = "/api/v2/sdk/ihf-client.py"
-- Sessions
instance AutoRoute SessionsController