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.
28 lines
2.5 KiB
Markdown
28 lines
2.5 KiB
Markdown
# 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. |