Architect storage around SQLite+Honker: remove PG, add multi-tenant identity, scoping

Reorient @alkdev/storage around a single SQLite database host with Honker
for pub/sub, event streams, and task queues. PostgreSQL is removed as a
target (ADR-038), eliminating dual schema maintenance and infrastructure
complexity. Honker provides DB + pubsub + queues in one .db file (ADR-039).

Add system/tenant DB model (ADR-040): identity tables in system.db, all
graph data in tenant-{orgId}.db files. Identity tables move from the hub
into storage (ADR-041). Scoping columns (ownerId, projectId) added to
graphs table (ADR-042). Graph types get scope (system/tenant/user) to
protect infrastructure schemas (ADR-043).

Define Drizzle-Honker session adapter (ADR-044): ~100-line adapter enabling
Drizzle typed queries and Honker pubsub/queue on a single connection with
transactional consistency.

Resolve OQ-03, OQ-04, OQ-19, OQ-21, OQ-22, OQ-23, OQ-24. Add new
open questions OQ-26 through OQ-29 for Honker integration specifics.

New docs: honker-integration.md (adapter, event patterns, migration).
Scrub all PG/jsonb/libsql references from existing spec docs.
This commit is contained in:
2026-05-31 15:41:41 +00:00
parent 6b5f32bad4
commit 6aa2fcc6ff
19 changed files with 1446 additions and 515 deletions

View File

@@ -1,6 +1,6 @@
---
status: draft
last_updated: 2026-05-30
last_updated: 2026-05-31
---
# Access Control Graph
@@ -500,18 +500,14 @@ itself, with `actions: ["admin"]` for owners, `["manage"]` for admins.
## Scoping Model
### Two-Level Scoping
With the system/tenant DB model (ADR-040), ACL scoping is simplified:
The ACL system operates at two levels:
1. **Operation-level** (setup-time): `AccessControl` on `OperationSpec` defines
*what scopes and resource actions are required* to invoke an operation. This
is registered once when the operation is defined and changes infrequently.
This data lives in `@alkdev/operations` and the hub's `operations` table.
2. **Graph-level** (runtime): The ACL graph instance stores *who has what* and
*who delegates to whom*. This is queried at call time to resolve whether a
specific identity satisfies an operation's access control requirements.
- **One ACL graph instance per tenant DB** — The tenant DB is inherently
org-scoped. OQ-22 is resolved: each org gets its own ACL graph instance.
- **No cross-org scoping within a tenant** — The entire tenant DB is one org.
The ACL graph does not need `orgId` columns or cross-org filtering.
- **Cross-org delegation** requires the hub to mediate between tenant DBs
(OQ-28, open).
### How They Connect
@@ -538,9 +534,11 @@ The **evaluator** bridges the two:
### Org-Scoped Access
When a `BelongsToEdge` connects an account to an org, and the org has
`ScopesEdge` connections to resources, the account inherits org-level access
through its membership:
Within a tenant DB, ACL evaluation is straightforward — the entire DB is one
org. The PrincipalNode's `identityId` logically references `accounts.id` in the
system DB (ADR-041). When evaluating ACL, the hub reads the account's org
membership from the system DB's `organization_members` table (authoritative per
ADR-045) and the ACL graph's `BelongsToEdge` (derived) in the tenant DB.
```
Account PrincipalNode ──belongs_to──→ Org PrincipalNode
@@ -565,29 +563,20 @@ own base scopes limit what they can exercise from org membership.
### The Disconnected `actors` Table
The `actors` table in `src/sqlite/tables/actors.ts` is replaced by
`PrincipalNode` in the ACL graph. The `ACTOR_TYPE` enum (`Human`, `Llm`,
`Agent`) maps to `identityType` values (`account`, `service`, `account` — LLMs
are accounts in the hub model per ADR-012). The standalone table has no
foreign key relationships and was explicitly deferred pending ACL design (OQ-03).
`PrincipalNode` in the ACL graph. The standalone table has been removed
(ADR-035, ADR-038).
This does **not** mean the hub's `accounts` table is replaced. The hub's
`accounts` table remains the authoritative identity store with email, access
level, and Gitea linking. `PrincipalNode` in the ACL graph **references** the
account by `identityId` but does not duplicate its columns. The ACL graph
stores *authorization* data; the hub's identity tables store *authentication*
data.
### Hub's `organization_members` as Authoritative Source
### Hub's `organization_members` as a Source
The hub's `organization_members` table is the authoritative source for who
belongs to which org. When org membership changes, the hub updates both:
The hub's (now storage's) `organization_members` table is the authoritative
source for who belongs to which org (ADR-045). When org membership changes,
the consumer writes both:
1. The `organization_members` row (fast lookup, FK constraints)
2. The `BelongsToEdge` in the ACL graph instance (graph traversal, evaluation)
This dual-write is necessary because the hub needs fast SQL lookups for
membership checks (e.g., "list all members of this org"), while the ACL graph
needs the edge for traversal-based evaluation (e.g., "compute effective scopes
for this account across all orgs").
This dual-write is necessary because the SQL table provides O(1) membership
lookups and cascade behavior, while the ACL graph needs the edge for
traversal-based evaluation.
### Hub's Permission Resolution
@@ -608,15 +597,17 @@ the effective scope is `dev:read`.
- **OQ-20**: Should `DelegatesEdge` support temporary delegation with
expiration? (Referenced in [open-questions.md](open-questions.md))
- **OQ-21**: Should the ACL evaluator live in `@alkdev/storage` or in the hub?
- **OQ-25**: What are the scope string semantics for subset validation?
(Referenced in [open-questions.md](open-questions.md))
- **OQ-22**: How are ACL graph instances created and managed? (Referenced in
[open-questions.md](open-questions.md))
- **OQ-23**: Should `BelongsToEdge` be derived (materialized from
`organization_members`) or primary (ACL graph is the source of truth)?
- **OQ-28**: How does cross-tenant delegation work with separate DBs?
(Referenced in [open-questions.md](open-questions.md))
- **OQ-24**: How does `identityId` reference hub entities without creating a
package dependency? (Referenced in [open-questions.md](open-questions.md))
## Resolved Questions
- **OQ-21** (ACL evaluator location): Storage provides traversal primitives; hub composes with operations. Simplified by single-host model — no cross-DB joins needed within a tenant DB.
- **OQ-22** (ACL graph instance lifecycle): One per tenant DB. ADR-040.
- **OQ-23** (BelongsToEdge derivation): Derived from `organization_members`. ADR-045.
- **OQ-24** (identityId reference): Logical reference to `accounts.id` in system DB. ADR-041.
## References