Files
hub/docs/decisions/ADR-006-operation-specs-as-capabilities.md
glm-5.1 2b63cda1c7 Setup repo: migrate architecture specs, code stubs, and tasks from alkhub_ts
Copy architecture docs, ADRs, storage domain specs, research, reviews,
and 56 storage architecture tasks from the alkhub_ts monorepo. Adapt for
standalone @alkdev/hub repo structure (src/ not packages/hub/).

Sanitize all sensitive information:
- Replace private IPs (10.0.0.1) with localhost defaults
- Remove internal server hostnames (dev1, ns528096)
- Replace /workspace/ private paths with npm package references
- Remove hardcoded credentials from examples
- Rewrite infrastructure.md without private network details

Add Deno project scaffolding: deno.json (pinned deps), .gitignore,
AGENTS.md, entry point. Migrate existing code stubs (crypto, config
types, logger) with updated import paths.
2026-05-25 10:56:32 +00:00

2.5 KiB

ADR-006: Operation specs as capabilities

  • Status: Superseded (see update below)
  • Date: 2026-04-19
  • Deciders: alkdev
  • Superseded by: D3 in storage-spec-phase1-resolutions.md (2026-04-22)

Context

A spoke's capabilities were previously modeled as an opaque JSONB blob. Operations are the universal abstraction; they have names, namespaces, types, and typed schemas.

Original Decision

A spoke's capabilities are its registered operation specs. The spokes table stores minimal metadata. The operation_specs table stores full definitions. The relationship: spoke registers → hub creates operation_specs rows linked to that spoke. Queries for "what can spoke X do?" go through operation_specs filtered by spoke_id, not through a capabilities blob. The spokes table has no capabilities column. Instead, operation_specs has a spoke_id FK (nullable — hub-native operations have spoke_id = null).

Revised Decision (D3, 2026-04-22)

The original unified operation_specs table conflated two concepts: "what an operation IS" (a definition) and "who provides it right now" (a registration). These are now split into two tables:

  1. operations (definitions): Stores the operation's identity — namespace, name, type, input/output schemas, access control, description, tags. Unique by (namespace, name). No spoke FK — definitions are provider-independent. These persist even when all providers disconnect.

  2. operation_registrations (provider bindings): Links a provider (spoke or client) to an operation definition. Has operationId → operations.id (CASCADE), providerType (spoke|client), providerId, status (active|inactive), and pre-remap identifiers. On spoke disconnect, registrations are set to inactive. On admin spoke-row deletion, registrations CASCADE.

This supersedes the original unified model. The core principle from the original decision — that a spoke's capabilities are its registered operations, not a capabilities blob — remains unchanged. The query pattern shifts from operation_specs filtered by spoke_id to operation_registrations filtered by providerId and status = 'active'.

Consequences

Positive: capabilities are fully typed and queryable, consistent with the operations system, no duplicated capability data. Negative: requires a join to get spoke capabilities (acceptable since operation_registrations are indexed by providerId). The split adds a second table but cleanly separates definition persistence from runtime provider state, enabling multi-instance providers and operation survival across disconnects.