Unified authentication (ADR-023): SSH and WebTransport auth share the same
Ed25519 key material. Token auth uses signed timestamps verified against the
same authorized_keys set. IdentityProvider trait decouples core from identity
storage.
Bidirectional call protocol (ADR-024): Generalizes control channel (ADR-018)
to support hub→spoke and spoke→hub calls. Operation paths use /{spoke}/{service}/{op}
format for three-level routing. EventEnvelope wire format, five call events,
PendingRequestMap for correlation.
Handler/spec separation (ADR-025): Downstream consumers register operations
without modifying core. OperationRegistry maps paths to specs + handlers.
Service discovery via /services/list and /services/schema.
Resolves OQ-17 (transport-aware auth), OQ-21 (spoke routing), OQ-CFG-04 and
OQ-CFG-06 (WebTransport auth and transport-aware auth layer). Adds OQ-18
through OQ-22 for remaining open questions.
73 lines
3.1 KiB
Markdown
73 lines
3.1 KiB
Markdown
# ADR-025: Handler/Spec Separation for Downstream Service Registration
|
|
|
|
## Status
|
|
Accepted
|
|
|
|
## Context
|
|
|
|
The current control channel (ADR-018) is hardcoded: `wraith-control:0` bridges
|
|
to the local pubsub event bus. If NAPI wants to expose `fs.readFile` or
|
|
`bash.exec` as callable operations, it has no way to register these with core's
|
|
channel routing. The NAPI handler would need to intercept channel data outside
|
|
of core.
|
|
|
|
For the hub/spoke model, spokes register their operations with the hub when
|
|
they connect. The hub's registry must include both hub-local operations and
|
|
remote operations exposed by spokes.
|
|
|
|
## Decision
|
|
|
|
Operation specs and handlers are separated from core. Core provides:
|
|
|
|
1. `OperationSpec` — describes what an operation does (name, type, input/output
|
|
schemas, access control)
|
|
2. `OperationHandler` — implements the operation logic
|
|
3. `OperationRegistry` — maps paths to specs + handlers
|
|
4. Built-in operations: `/services/list`, `/services/schema`
|
|
|
|
Downstream consumers register their own operations:
|
|
|
|
```rust
|
|
// NAPI layer registers dev env tools
|
|
registry.register(OperationSpec { name: "/fs/readFile", ... }, fs_read_handler);
|
|
registry.register(OperationSpec { name: "/bash/exec", ... }, bash_exec_handler);
|
|
|
|
// Browser client registers a custom UDF
|
|
registry.register(OperationSpec { name: "/notify/alert", ... }, notify_handler);
|
|
```
|
|
|
|
Operation names use slash-based paths: `/{spoke}/{service}/{op}`. The first
|
|
segment routes to the node. The `namespace` field on `OperationSpec` is
|
|
derived from the second path segment (`service`).
|
|
|
|
When spoke operations are registered with the hub, the hub adds the spoke
|
|
prefix: a spoke that registers `/fs/readFile` as "dev1" becomes addressable as
|
|
`/dev1/fs/readFile` in the hub's routing table.
|
|
|
|
The `/services/list` operation returns all registered specs. The
|
|
`/services/schema` operation returns the spec for a specific operation. These
|
|
are read-only — no admin operations.
|
|
|
|
## Consequences
|
|
|
|
- **Positive**: NAPI, Python, and any downstream consumer can register
|
|
operations without modifying core.
|
|
- **Positive**: Service discovery is built in. Clients query `/services/list`
|
|
to learn what operations a hub offers.
|
|
- **Positive**: Spoke prefix naturally differentiates multiple spokes exposing
|
|
the same service (dev1 vs dev2).
|
|
- **Positive**: `AccessControl` on each `OperationSpec` enables per-operation
|
|
authorization. Higher-risk operations (shell, filesystem write) can require
|
|
tighter scopes.
|
|
- **Positive**: Schema exposure enables MCP adapter generation. OperationSpec
|
|
maps directly to MCP tool definitions.
|
|
- **Negative**: The registry adds complexity. Core now owns `OperationSpec`,
|
|
`OperationRegistry`, and `PendingRequestMap`.
|
|
- **Negative**: Namespace collisions between downstream consumers are possible.
|
|
The spoke prefix mitigates this: `/dev1/fs/readFile` vs `/dev2/fs/readFile`.
|
|
|
|
## References
|
|
|
|
- [call-protocol.md](../call-protocol.md) — Full call protocol spec
|
|
- [ADR-018](018-control-channel-for-pubsub.md) — Control channel (generalized)
|
|
- `@alkdev/operations` — TypeScript `OperationSpec`, `CallHandler`, registry |