docs: correct ecosystem dependency direction and add integration context

Architecture docs previously referenced the hub as the authoritative source
for call/identity specs. In reality, call protocol, identity, and access control
come from @alkdev/operations; call graph schemas from @alkdev/flowgraph; task
graph schemas from @alkdev/taskgraph; event transport from @alkdev/pubsub. The
hub is a consumer of @alkdev/storage, not the other way around.

Key changes:
- overview.md: add Ecosystem Integration section with dependency direction
  diagram, What Comes From Where table, repo layer bridging pattern, and
  circular dependency avoidance guidance
- overview.md: promote repo-layer vs operations-bridging from open question
  to explicit decision (CRUD in storage, bridging in consumer)
- overview.md: add zero-ecosystem-dependency statement; fix taskgraph type
  names (TaskGraphNodeAttributes, DependencyEdge)
- overview.md: fix terminology (hub is consumer, not authority)
- metagraph.md: add Ecosystem Context section; replace hub references with
  correct ecosystem sources; fix GraphStatus/GraphBaseType enum
  mischaracterization (C1); unify empty-array semantics with sqlite-host (C2);
  clarify repo layer does NOT import operations (C3); add flowgraph canonical
  schema note; add versioning cross-reference to graph_types table
- encrypted-data.md: reframe hub as provenance not authority; update What
  Lives Where table; fix standalone table advice; update references
- sqlite-host.md: fix actors table description; unify empty-array semantics;
  contextualize hub as reference consumer; add operations identity reference
This commit is contained in:
2026-05-28 14:25:16 +00:00
parent bb544469fd
commit 33a5b0816d
4 changed files with 229 additions and 58 deletions

View File

@@ -5,22 +5,26 @@ last_updated: 2026-05-28
# Encrypted Data # Encrypted Data
Design for storing encrypted data at rest within the metagraph model. Adapts the Design for storing encrypted data at rest within the metagraph model. Uses
hub's AES-256-GCM + PBKDF2 encryption pattern as a reusable node type and crypto AES-256-GCM + PBKDF2 key derivation, providing a reusable node type, TypeBox
utility. schema, and crypto utility for any consumer that needs to store secrets.
## Overview ## Overview
Sensitive data — API keys, passwords, OAuth tokens, SSH keys — must be encrypted Sensitive data — API keys, passwords, OAuth tokens, SSH keys — must be encrypted
at rest. The hub's `client_secrets` table stores these as encrypted JSON blobs. at rest. In `@alkdev/storage`, the encryption pattern becomes a reusable utility
In `@alkdev/storage`, the same encryption pattern becomes a reusable utility and and an encrypted node type, so any graph can store secrets without special table
an encrypted node type, so any graph can store secrets without special table
definitions. definitions.
**Key principle**: The storage package provides the **encryption primitives and **Key principle**: The storage package provides the **encryption primitives and
the schema shape**, not key management. Consumers provide the encryption key. the schema shape**, not key management. Consumers provide the encryption key.
This keeps the package agnostic to deployment-specific secret management. This keeps the package agnostic to deployment-specific secret management.
**Provenance**: The encryption pattern (AES-256-GCM + PBKDF2) was originally
implemented in the hub's `client_secrets` table and `src/crypto/mod.ts`.
`@alkdev/storage` extracts this pattern as a general-purpose utility, independent
of the hub's domain model.
## The Problem ## The Problem
The hub has `client_secrets` as a standalone table with columns like: The hub has `client_secrets` as a standalone table with columns like:
@@ -113,13 +117,13 @@ graph edge rather than a foreign key.
| `@alkdev/storage` graphs | `EncryptedDataSchema` (TypeBox shape) | `@alkdev/storage` | | `@alkdev/storage` graphs | `EncryptedDataSchema` (TypeBox shape) | `@alkdev/storage` |
| `@alkdev/storage` crypto | `encrypt()`, `decrypt()`, `generateEncryptionKey()` | `@alkdev/storage` | | `@alkdev/storage` crypto | `encrypt()`, `decrypt()`, `generateEncryptionKey()` | `@alkdev/storage` |
| `@alkdev/storage` sqlite | Node storage (attributes contain encrypted JSON) | `@alkdev/storage/sqlite` | | `@alkdev/storage` sqlite | Node storage (attributes contain encrypted JSON) | `@alkdev/storage/sqlite` |
| `@alkdev/storage` repo | Validate schema, encrypt before insert (⚠️ not yet impl) | `@alkdev/storage` |
| Application | Key management (key ring, key rotation) | Consumer | | Application | Key management (key ring, key rotation) | Consumer |
| Application | Repository layer (validate schema, encrypt before insert) | Consumer |
## EncryptedData Schema ## EncryptedData Schema
Ported from the hub's `src/crypto/mod.ts` interface, expressed as a TypeBox Ported from the hub's `src/crypto/mod.ts` interface, now expressed as a TypeBox
schema: schema in `@alkdev/storage`:
```ts ```ts
import { Type } from "@alkdev/typebox"; import { Type } from "@alkdev/typebox";
@@ -232,14 +236,14 @@ because:
- **Uniform query patterns** — All graph queries work on secret nodes without - **Uniform query patterns** — All graph queries work on secret nodes without
special code special code
**When a standalone table might be better**: If the hub needs to query "all **When a standalone table might be better**: If a consumer (like the hub) needs
active API keys" across all clients with a single indexed `WHERE` clause, a to query "all active API keys" across all clients with a single indexed `WHERE`
dedicated `api_keys` table with proper indexes is faster. The graph model clause, a dedicated `api_keys` table with proper indexes is faster. The graph
requires traversing edges to find related secrets. For the hub's specific use model requires traversing edges to find related secrets. For a hub's specific use
case (key lookup on every authenticated request), this matters. The metagraph case (key lookup on every authenticated request), this matters. The metagraph
pattern is optimized for flexibility, not raw key-lookup performance. The hub pattern is optimized for flexibility, not raw key-lookup performance. Consumers
should use a standalone `api_keys` table for authentication and the metagraph should use standalone tables for authentication hot paths and the metagraph for
for everything else. everything else.
### ED3: Password-based encryption, not raw-key encryption ### ED3: Password-based encryption, not raw-key encryption
@@ -366,9 +370,11 @@ Crypto API and `@alkdev/typebox` for the schema).
## References ## References
- Hub crypto utility: `/workspace/@alkdev/hub/src/crypto/mod.ts`
- Hub `client_secrets` table:
`/workspace/@alkdev/hub/docs/architecture/storage/services.md`
- Hub ADR-008:
`/workspace/@alkdev/hub/docs/decisions/ADR-008-secrets-encrypted-at-rest-with-key-versioning.md`
- Web Crypto API: https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto - Web Crypto API: https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto
- Hub crypto utility (provenance): `/workspace/@alkdev/hub/src/crypto/mod.ts`
- Hub `client_secrets` table (provenance):
`/workspace/@alkdev/hub/docs/architecture/storage/services.md`
- Hub ADR-008 (provenance):
`/workspace/@alkdev/hub/docs/decisions/ADR-008-secrets-encrypted-at-rest-with-key-versioning.md`
- `@alkdev/operations` AccessControl:
`/workspace/@alkdev/operations/docs/architecture/api-surface.md`

View File

@@ -141,8 +141,9 @@ Enum-backed types for graph lifecycle and structural type:
- `GraphStatus`: `active`, `archived`, `draft` - `GraphStatus`: `active`, `archived`, `draft`
- `GraphBaseType`: `directed`, `undirected`, `mixed` - `GraphBaseType`: `directed`, `undirected`, `mixed`
These are provided both as TypeScript enums and TypeBox schemas, derived from These are provided as `as const` object constants and TypeBox `Type.Union` of
the same enum definition. `Type.Literal` schemas, following the project convention (see overview.md D6).
The TypeBox schemas derive their literal values from the same const object.
## SchemaBuilder ## SchemaBuilder
@@ -182,9 +183,13 @@ node type in its schema. Callers should not reuse a builder after a failure.
Create a new `SchemaBuilder` instead. Create a new `SchemaBuilder` instead.
**Edge type enforcement**: When `allowedSourceTypes` or `allowedTargetTypes` is **Edge type enforcement**: When `allowedSourceTypes` or `allowedTargetTypes` is
undefined (or an empty array at the application layer), any node type is a valid `undefined` in the schema layer, any node type is a valid endpoint. When a
endpoint. When a non-empty array is provided, only the listed node types are non-empty array is provided, only the listed node types are valid endpoints.
valid endpoints. The repository layer should enforce this at write time. In the database layer, `[]` (the column default) represents "no restriction" —
any node type is valid — matching the behavior of `undefined` in the schema.
There is no "no types allowed" state; if edge types need to be disabled, use a
status or soft-delete pattern on the edge type definition. The repository layer
must enforce this convention consistently.
The SchemaBuilder enforces structural integrity at definition time. The database The SchemaBuilder enforces structural integrity at definition time. The database
stores graph/node/edge type schemas as JSON blobs (`text` mode in SQLite, will stores graph/node/edge type schemas as JSON blobs (`text` mode in SQLite, will
@@ -235,6 +240,11 @@ Graph types have a `version` integer (default 1). This tracks **breaking**
schema changes — field removals, type changes that break backward compatibility. schema changes — field removals, type changes that break backward compatibility.
Non-breaking changes (adding optional fields) do not require a version bump. Non-breaking changes (adding optional fields) do not require a version bump.
The `version` field is stored as a column on the `graph_types` table (see
[sqlite-host.md](./sqlite-host.md)). It is not part of the `GraphSchema` — it
lives at the database level because it affects how the repository layer
processes data for that graph type.
The repository layer should check `version` before processing to ensure The repository layer should check `version` before processing to ensure
compatibility. A version mismatch indicates the data format has changed compatibility. A version mismatch indicates the data format has changed
incompatibly and the consumer should handle it explicitly. incompatibly and the consumer should handle it explicitly.
@@ -243,6 +253,12 @@ incompatibly and the consumer should handle it explicitly.
### Defining a Call Graph Type ### Defining a Call Graph Type
> **Note**: `@alkdev/flowgraph` exports a canonical `CallNodeAttrs` schema that
> defines these same fields (plus `parentRequestId`, `identity`, timestamps).
> The example below illustrates the structure; production code should import
> `CallNodeAttrs` from `@alkdev/flowgraph/schema` when defining the graph type
> for persisted call graphs.
```ts ```ts
import { import {
BaseEdgeAttributes, BaseEdgeAttributes,
@@ -334,12 +350,49 @@ const schema = new SchemaBuilder()
See [encrypted-data.md](./encrypted-data.md) for the full encrypted data design. See [encrypted-data.md](./encrypted-data.md) for the full encrypted data design.
## Ecosystem Context
The metagraph model is a **storage layer** consumed by other packages in the
@alkdev ecosystem. It does not depend on the hub — the dependency flows the other
way. The call protocol, identity model, and graph type definitions that inform
the usage patterns below originate from sibling packages:
- **Call protocol** — Defined in `@alkdev/operations`. The `CallEventMap`,
`PendingRequestMap`, and `CallHandler` define call/subscribe semantics, event
types, and the request correlation model. Storage persists what the call
protocol records.
- **Identity & access control** — Defined in `@alkdev/operations`. The
`Identity` interface and `AccessControl` schema provide the security context
carried through calls. The "ACL graph type" usage pattern maps to these
constructs.
- **Call graph schema** — Defined in `@alkdev/flowgraph`. `CallNodeAttrs`,
`CallEdgeAttrs`, `CallStatus`, and `EdgeType` specify the attribute shapes for
call graph instances. Storage persists these shapes; flowgraph operates on
them in memory.
- **Task graph schema** — Defined in `@alkdev/taskgraph`. `TaskGraphNodeAttributes`
and `DependencyEdge` specify task dependency shapes. Another graph type
that storage persists.
- **Event transport** — Provided by `@alkdev/pubsub`. The `TypedEventTarget`
contract and `EventEnvelope` wrapping carry call protocol events between
processes. Storage is not involved in event routing but stores the events'
outcomes.
The repository layer (not yet implemented) will provide typed CRUD for metagraph
data — insert, find, update, delete with schema validation. A consumer can then
wire these CRUD functions into `@alkdev/operations`'s registry pattern
(analogous to how `drizzle-graphql` auto-generates a GraphQL schema from Drizzle
tables, but using operations instead of GraphQL resolvers). This avoids circular
dependencies: storage defines the schema types and tables, operations provides
the call protocol and registry, and the consumer bridges them.
## References ## References
- Hub call graph spec: - Call protocol: `/workspace/@alkdev/operations/docs/architecture/call-protocol.md`
`/workspace/@alkdev/hub/docs/architecture/storage/call-graph.md` - Identity & access control: `/workspace/@alkdev/operations/docs/architecture/api-surface.md`
- Hub identity spec: - Call graph schema: `/workspace/@alkdev/flowgraph/docs/architecture/schema.md`
`/workspace/@alkdev/hub/docs/architecture/storage/identity.md` - Call graph (dynamic): `/workspace/@alkdev/flowgraph/docs/architecture/call-graph.md`
- Task graph schema: `/workspace/@alkdev/taskgraph_ts/docs/architecture/schemas.md`
- Pubsub architecture: `/workspace/@alkdev/pubsub/docs/architecture/README.md`
- TypeBox: https://github.com/sinclairzx/typebox - TypeBox: https://github.com/sinclairzx/typebox
- SchemaBuilder source: `src/graphs/schemaBuilder.ts` - SchemaBuilder source: `src/graphs/schemaBuilder.ts`
- Schema types source: `src/graphs/types.ts` - Schema types source: `src/graphs/types.ts`

View File

@@ -57,14 +57,14 @@ the main `mod.ts` re-exports it. Importing from either `@alkdev/storage` or
| Term | Definition | | Term | Definition |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Metagraph** | A type system where graph types define schemas, node types define data shapes within those graphs, and edge types define typed relationships. Graph instances are concrete data conforming to these type definitions. | | **Metagraph** | A type system where graph types define schemas, node types define data shapes within those graphs, and edge types define typed relationships. Graph instances are concrete data conforming to these type definitions. |
| **Hub** | The central service in the hub-spoke architecture. Runs PostgreSQL, hosts API endpoints, coordinates spokes, and is the authoritative data store. `@alkdev/storage`'s PostgreSQL host (not yet implemented) targets the hub. | | **Hub** | The central service in the hub-spoke architecture. A consumer of `@alkdev/storage` — uses the PostgreSQL host for persistent graph storage. The hub also depends on `@alkdev/operations`, `@alkdev/pubsub`, `@alkdev/flowgraph`. |
| **Spoke** | A local/embedded instance that runs per-project or per-session. Uses SQLite for local storage. `@alkdev/storage`'s SQLite host targets spokes. | | **Spoke** | A local/embedded instance that runs per-project or per-session. A consumer of `@alkdev/storage` — uses the SQLite host for local graph storage. |
| **Graph type** | A class of graphs (e.g., "call-graph", "acl"). Defines structural constraints (directed/undirected/mixed, multi-edges, self-loops) and the valid node/edge type vocabularies. Stored in the `graph_types` table. | | **Graph type** | A class of graphs (e.g., "call-graph", "acl"). Defines structural constraints (directed/undirected/mixed, multi-edges, self-loops) and the valid node/edge type vocabularies. Stored in the `graph_types` table. |
| **Node type** | A category of node within a graph type. Defines the attribute schema for nodes of that type. Stored in the `node_types` table. | | **Node type** | A category of node within a graph type. Defines the attribute schema for nodes of that type. Stored in the `node_types` table. |
| **Edge type** | A category of edge within a graph type. Defines the attribute schema and optionally restricts which node types can be source/target. Stored in the `edge_types` table. | | **Edge type** | A category of edge within a graph type. Defines the attribute schema and optionally restricts which node types can be source/target. Stored in the `edge_types` table. |
| **Graph instance** | A concrete graph belonging to a graph type. Contains nodes and edges conforming to its type definitions. Stored in the `graphs` table. | | **Graph instance** | A concrete graph belonging to a graph type. Contains nodes and edges conforming to its type definitions. Stored in the `graphs` table. |
| **Consumer** | Code that imports `@alkdev/storage` (or a subpath) to define graph types and persist graph data. The hub and spokes are consumers. | | **Consumer** | Code that imports `@alkdev/storage` (or a subpath) to define graph types and persist graph data. The hub, spokes, and other @alkdev packages are consumers. |
| **Repository layer** | ⚠️ Not yet implemented. The typed CRUD functions (insert, find, update, delete) that sit between consumer code and raw Drizzle queries. Performs schema validation before writes. | | **Repository layer** | ⚠️ Not yet implemented. The typed CRUD functions (insert, find, update, delete) that sit between consumer code and raw Drizzle queries. Performs schema validation before writes. No dependency on `@alkdev/operations` — the consumer wires CRUD into the registry. |
| **Validation boundary** | The line where schema validation is enforced. In this package, validation happens in the SchemaBuilder (at type definition time) and the repository layer (at mutation time), NOT in the database. | | **Validation boundary** | The line where schema validation is enforced. In this package, validation happens in the SchemaBuilder (at type definition time) and the repository layer (at mutation time), NOT in the database. |
## Design Decisions ## Design Decisions
@@ -137,6 +137,12 @@ in PG). This ensures every row has auditability and extensibility.
`@alkdev/typebox` and `@alkdev/drizzlebox` are npm packages (not yet on JSR). `@alkdev/typebox` and `@alkdev/drizzlebox` are npm packages (not yet on JSR).
JSR handles npm dependencies natively. JSR handles npm dependencies natively.
**Ecosystem packages are not runtime dependencies of `@alkdev/storage`.** All
ecosystem references in this document describe consumer-side data shapes and
integration patterns, not import dependencies. The `@alkdev/operations`,
`@alkdev/pubsub`, `@alkdev/flowgraph`, and `@alkdev/taskgraph` packages are
consumed by the hub and spokes, not by storage itself.
## What Exists vs. What's Needed ## What Exists vs. What's Needed
### Implemented ### Implemented
@@ -151,21 +157,108 @@ JSR handles npm dependencies natively.
| Gap | Priority | Notes | | Gap | Priority | Notes |
| ----------------------------------------- | ------------ | --------------------------------------------------------------------------------------------------- | | ----------------------------------------- | ------------ | --------------------------------------------------------------------------------------------------- |
| Encrypted data node type + crypto utility | **Critical** | ⚠️ Not yet implemented. API keys and secrets at rest. See [encrypted-data.md](./encrypted-data.md). | | Encrypted data node type + crypto utility | **Critical** | ⚠️ Not yet implemented. API keys and secrets at rest. See [encrypted-data.md](./encrypted-data.md). |
| Repository/CRUD layer | High | ⚠️ Not yet implemented. Typed insert, find, update, delete functions for graphs, nodes, edges | | Repository/CRUD layer | High | ⚠️ Not yet implemented. Typed insert, find, update, delete functions for graphs, nodes, edges. No dependency on `@alkdev/operations` — consumer wires CRUD into registry. |
| Tests | High | Zero tests exist. Needed before any real use. | | Tests | High | Zero tests exist. Needed before any real use. |
| PostgreSQL host | Medium | Same table shapes, `pgTable` + `jsonb` + `timestamp` + `pgEnum`. Stub only. | | PostgreSQL host | Medium | Same table shapes, `pgTable` + `jsonb` + `timestamp` + `pgEnum`. Stub only. |
| ACL graph type | Medium | Access control as a graph. Depends on encrypted data and CRUD layer. | | Call graph type | Medium | Informed by `@alkdev/flowgraph`'s `CallNodeAttrs`/`CallEdgeAttrs` schemas and `@alkdev/operations`' call protocol events. Not hub-specific — any consumer that tracks call invocations needs this. |
| Call graph type | Low | Hub-specific, uses metagraph. Deferred until hub consumes this package. | | ACL graph type | Medium | Access control as a graph. Informed by `@alkdev/operations`' `Identity` and `AccessControl`. Depends on encrypted data and CRUD layer. |
| Session/message models | Low | Hub-specific, may remain domain tables. | | Task graph type | Low | Informed by `@alkdev/taskgraph`'s `TaskGraphNodeAttributes` and `DependencyEdge` schemas. |
## Ecosystem Integration
`@alkdev/storage` is a **data layer package** consumed by other packages in the
@alkdev ecosystem. It does not depend on the hub — the dependency flows the
other way. The hub consumes storage (along with operations, pubsub, flowgraph,
and taskgraph) as part of its architecture.
### Dependency Direction
```
@alkdev/pubsub ← transport only (no storage dependency)
@alkdev/operations ← call protocol, registry, identity, access control
↑ (depends on: @alkdev/pubsub, @alkdev/typebox)
@alkdev/flowgraph ← call graph schema, operation graph, workflow templates
↑ (depends on: @alkdev/operations [peer], @alkdev/typebox)
@alkdev/taskgraph ← task dependency graph schema, cost-benefit analysis
(depends on: @alkdev/typebox)
@alkdev/storage ← YOU ARE HERE — typed graph persistence
(depends on: @alkdev/typebox, @alkdev/drizzlebox)
↑ ↑
| |
Hub / Spoke Any consumer that needs
(consumes all) persistent graph storage
```
The key insight: `@alkdev/storage` provides the **persistence primitives**
(schemas, tables, repository layer). The **domain semantics** (what a call graph
means, what identity looks like, how access control works) are defined by the
packages above. Storage stores the shapes those packages define; it does not
define the semantics itself.
### What Comes from Where
| Concept | Source package | Storage's role |
|---------|---------------|----------------|
| Call protocol events (`call.requested`, `call.responded`, etc.) | `@alkdev/operations` | Storage persists the outcomes — graphs with `CallNodeAttrs` nodes |
| Identity (`id`, `scopes`, `resources`) | `@alkdev/operations` | Storage stores identity as node attributes; `Identity` is a data shape, not a storage concept |
| Access control (`AccessControl`, `requiredScopes`) | `@alkdev/operations` | Storage's ACL graph type mirrors the operations `AccessControl` schema as graph structure |
| Call graph schema (`CallNodeAttrs`, `CallEdgeAttrs`, `CallStatus`) | `@alkdev/flowgraph` | Storage persists these in-memory shapes to the database |
| Task graph schema (`TaskGraphNodeAttributes`, `DependencyEdge`) | `@alkdev/taskgraph` | Storage persists task dependency shapes |
| Event transport (`TypedEventTarget`, `EventEnvelope`) | `@alkdev/pubsub` | Storage is not involved in event routing; it stores the events' outcomes |
### Repository Layer Bridging Pattern (Consumer-Side Concern)
The repository layer in `@alkdev/storage` provides typed CRUD — no `@alkdev/operations`
dependency. A **consumer-side** bridging module can then wire these CRUD functions
into the `@alkdev/operations` registry, analogous to how `drizzle-graphql`
auto-generates a GraphQL schema from Drizzle tables — but using operations
(queries, mutations, subscriptions) instead of GraphQL resolvers. This works
because:
1. `@alkdev/operations` already maps closely to GraphQL's
queries/mutations/subscriptions (it was modeled after that pattern)
2. `@alkdev/pubsub` provides the subscription transport (forked from
graphql-yoga's pubsub with additions)
3. `@alkdev/storage`'s metagraph tables are the data source, analogous to
Drizzle tables for drizzle-graphql
The bridging module would live in a consumer package (e.g., the hub or a
dedicated `@alkdev/storage-operations` adapter), not in `@alkdev/storage` itself,
to avoid circular dependencies:
```
@alkdev/storage → defines types + tables (no operations dependency)
@alkdev/operations → defines call protocol + registry (no storage dependency)
Consumer (hub / adapter) → imports both, generates operations from schemas
```
### Avoiding Circular Dependencies
Neither `@alkdev/storage` nor `@alkdev/operations` should depend on each
other directly. Storage defines the schema types and database tables; operations
defines the call protocol and execution model. The consumer (hub, spoke, or
adapter package) imports both and bridges them. This preserves the
single-responsibility principle and allows each package to evolve independently.
If shared type definitions are needed (e.g., `Identity` referenced in both
storage node attributes and operations call events), they should either:
1. Be duplicated in each package with a documented correspondence (acceptable
for small, stable types)
2. Be extracted to a minimal shared types package if the duplication becomes
burdensome
## Open Questions ## Open Questions
1. **Should `actors` be a node type or a standalone table?** Currently `actors` 1. **Should `actors` be a node type or a standalone table?** Currently `actors`
is a standalone table in the SQLite host that isn't referenced by any is a standalone table in the SQLite host that isn't referenced by any
relation. If identity/authentication is a graph (ACL nodes), actors become relation. If identity/authentication is a graph (ACL nodes based on
node types. If identity is a domain concept that needs special query patterns `@alkdev/operations`'s `Identity` interface), actors become node types. If
(auth lookups, session joins), standalone tables may be better. Decision: identity is a domain concept that needs special query patterns (auth lookups,
defer until ACL design. session joins), standalone tables may be better. Decision: defer until ACL
design, informed by `@alkdev/operations`'s `AccessControl` model.
2. **Should the repository layer be host-specific or host-agnostic?** A 2. **Should the repository layer be host-specific or host-agnostic?** A
host-agnostic repository (insert graph, find nodes by type) requires an host-agnostic repository (insert graph, find nodes by type) requires an
@@ -190,9 +283,24 @@ JSR handles npm dependencies natively.
application-level. See [metagraph.md](./metagraph.md) for the versioning application-level. See [metagraph.md](./metagraph.md) for the versioning
approach. approach.
6. **~~Should the repository layer live in `@alkdev/storage` or in a consumer
package?~~** Decision: the repository CRUD layer (host-specific typed
queries, schema validation before writes) belongs in `@alkdev/storage`. The
operations bridging layer (generating `OperationSpec`s from metagraph schemas)
belongs in a consumer or adapter package. These are separate concerns — CRUD
is a storage concern; call protocol integration is an application concern.
The repository layer in `@alkdev/storage` has **no dependency on
`@alkdev/operations`**. It performs typed inserts, finds, updates, and
deletes with schema validation. The consumer then wires these CRUD functions
into the operations registry if desired.
## References ## References
- Hub storage spec: `/workspace/@alkdev/hub/docs/architecture/storage/` - Operations architecture: `/workspace/@alkdev/operations/docs/architecture/README.md`
- Pubsub architecture: `/workspace/@alkdev/pubsub/docs/architecture/README.md`
- Flowgraph architecture: `/workspace/@alkdev/flowgraph/docs/architecture/README.md`
- Taskgraph architecture: `/workspace/@alkdev/taskgraph_ts/docs/architecture/README.md`
- drizzle-graphql (reference for repo bridging pattern): `/workspace/drizzle-graphql/`
- Source heritage: `@ade/ade-v0/packages/core/graphs` and - Source heritage: `@ade/ade-v0/packages/core/graphs` and
`@ade/ade-v0/packages/storage_sqlite` `@ade/ade-v0/packages/storage_sqlite`
- Drizzle ORM: https://orm.drizzle.team/ - Drizzle ORM: https://orm.drizzle.team/

View File

@@ -62,9 +62,9 @@ All tables share these columns:
} }
``` ```
**Notable differences from hub's PostgreSQL common columns**: **Notable differences from a typical PostgreSQL common columns pattern**:
| Column | SQLite | PostgreSQL (hub) | | Column | SQLite | PostgreSQL (typical) |
| ----------- | ------------------------------------- | ------------------------------------------------------------- | | ----------- | ------------------------------------- | ------------------------------------------------------------- |
| `id` | text PK (consumer-generated) | text PK with `$defaultFn(() => crypto.randomUUID())` | | `id` | text PK (consumer-generated) | text PK with `$defaultFn(() => crypto.randomUUID())` |
| `metadata` | `text` with JSON mode | `jsonb` with `$type<Record<string, unknown>>()` | | `metadata` | `text` with JSON mode | `jsonb` with `$type<Record<string, unknown>>()` |
@@ -130,12 +130,13 @@ Stores edge type definitions within a graph type.
a graph type. a graph type.
**Empty array semantics**: `allowedSourceTypes` and `allowedTargetTypes` default **Empty array semantics**: `allowedSourceTypes` and `allowedTargetTypes` default
to `[]` (empty JSON array) in the database. The repository layer must treat `[]` to `[]` (empty JSON array) in the database. `[]` means "no restriction" — any
(empty array) as "no restriction" — any node type is a valid endpoint — matching node type is a valid endpoint — matching the behavior of `undefined` in the
the behavior of `undefined` in the `EdgeType` schema. A non-empty array `EdgeType` schema layer. A non-empty array restricts endpoints to only the
restricts endpoints to only the listed node types. There is no "no types listed node types. There is no "no types allowed" state; if edge types need to
allowed" state; if edge types need to be disabled, use a status or soft-delete be disabled, use a status or soft-delete pattern on the edge type definition.
pattern on the edge type definition. The repository layer must enforce this convention consistently. See
[metagraph.md](./metagraph.md) for the schema-layer definition.
### `graphs` ### `graphs`
@@ -209,9 +210,11 @@ its edges.
### `actors` ### `actors`
Standalone identity table. Currently not referenced by any relation. This is a Standalone identity table. Currently not referenced by any relation — the
placeholder for the hub's account/identity model and may become a node type in `actors` table has no FK references to or from any metagraph table and is not
an ACL graph or remain a standalone table. See [overview.md](./overview.md) Open included in `relations.ts`. This is a placeholder for identity data and may
become a node type in an ACL graph (based on `@alkdev/operations`'s `Identity`
interface) or remain a standalone table. See [overview.md](./overview.md) Open
Question 1. Question 1.
| Column | Type | Constraints | Notes | | Column | Type | Constraints | Notes |
@@ -355,14 +358,15 @@ changes:
| `integer` (boolean mode) | `boolean` | | `integer` (boolean mode) | `boolean` |
| `text` (enum) | `pgEnum` or `text` with check constraint | | `text` (enum) | `pgEnum` or `text` with check constraint |
See hub's `commonCols` reference in See a consumer's `commonCols` pattern (e.g., the hub's
[../../hub/docs/architecture/storage/table-reference.md] for the PostgreSQL `/workspace/@alkdev/hub/docs/architecture/storage/table-reference.md`) for
patterns. PostgreSQL reference patterns.
## References ## References
- Drizzle ORM SQLite core: https://orm.drizzle.team/docs/sqlite-core - Drizzle ORM SQLite core: https://orm.drizzle.team/docs/sqlite-core
- libsql client: https://github.com/tursodatabase/libsql - libsql client: https://github.com/tursodatabase/libsql
- Hub common columns pattern: - Hub common columns (reference consumer):
`/workspace/@alkdev/hub/docs/architecture/storage/table-reference.md` `/workspace/@alkdev/hub/docs/architecture/storage/table-reference.md`
- Operations AccessControl and Identity: `/workspace/@alkdev/operations/docs/architecture/api-surface.md`
- Source: `src/sqlite/` - Source: `src/sqlite/`