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.
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:
-
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. -
operation_registrations(provider bindings): Links a provider (spoke or client) to an operation definition. HasoperationId → operations.id(CASCADE),providerType(spoke|client),providerId,status(active|inactive), and pre-remap identifiers. On spoke disconnect, registrations are set toinactive. 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.