Skip to content

Commit 3ec7fcb

Browse files
committed
Replace createMemory({ app }) with a createMemoryRoutes sub-app
createMemoryRoutes(deps) builds the routes on a fresh Hono<TenantEnv> with paths relative to the mount point; the host mounts it at /api/tenants/:tenantId/memory with its own requireGrant. The barrel's createMemory wrapper that mutated a passed app is gone, so the one createMemory is the plane factory in memory.ts, and it assembles its options as explicit literals rather than conditional spreads. The package root now exports only the public API: internal services, helpers and test fakes are gone from it, the distiller and migrations stay on their own subpaths, and the internal MergeLocalLiveV1 name no longer appears in shipped declarations.
1 parent c7eca84 commit 3ec7fcb

22 files changed

Lines changed: 151 additions & 364 deletions

‎AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ CI runs `typecheck` + `test` — both must pass before any push.
1919

2020
## Layout
2121

22-
- `src/index.ts` — public surface: `createMemory` (optional `app` registers HTTP), `registerMemoryRoutes`
22+
- `src/index.ts` — public surface: `createMemory`, `createMemoryRoutes`
2323

2424
- `src/mount-config.ts` / `src/config.ts` — mount config + engine config
2525
- `src/routes/` — Hono routes (`add`, `search`, `list`, `feed`, retention

‎ARCHITECTURE.md‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ The store was detachable from a larger backend, then mountable:
1515
- Document access is Interchange grant tags on the row (`accessTags` + creator),
1616
not a private ACL engine inside this package.
1717

18-
It ships as `createMemory({ app, … })`: the host passes its Hono app and grant
19-
store; the library registers routes, reads identity from request context, and
20-
talks to its DocumentStore. No second server.
18+
It ships as `createMemory(…)` plus `createMemoryRoutes(deps)`: the host builds
19+
the plane, mounts the returned Hono sub-app, and passes its `requireGrant`; the
20+
routes read identity from request context and talk to the DocumentStore. No
21+
second server.
2122

2223
## Product path
2324

@@ -53,8 +54,8 @@ helpers are optional multi-writer / backfill — not the primary path.
5354
puts `principal` + `tenant` on context; routes read identity from there
5455
(`tenantId = principal.tenantId`, `principalId = principal.id`). A host
5556
with a non-browser caller (e.g. a workflow-run child with its own sidecar
56-
bearer token) may instead pass `callerResolver` (`RouteDeps` /
57-
`createMemory`) — the host still does 100% of the authenticating, it just
57+
bearer token) may instead pass `callerResolver` to `createMemoryRoutes`
58+
— the host still does 100% of the authenticating, it just
5859
hands the resolved `{ tenantId, principalId }` in through the seam instead
5960
of setting context itself. Either way the resolved identity, never
6061
anything from the request body, is what `grantGuard` authorizes.
@@ -96,7 +97,7 @@ exposes the same three verbs.
9697

9798
## Mounted surface
9899

99-
`createMemory({ app })` registers:
100+
`createMemoryRoutes(deps)`, mounted at `/api/tenants/:tenantId/memory`, serves:
100101

101102
- `POST /api/tenants/:tenantId/memory/add` — ingest (raw + derive on the default store).
102103
- `POST /api/tenants/:tenantId/memory/search` — hybrid retrieval (FTS + dense → RRF → rerank →

‎CHANGELOG.md‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- `MEMORY_GRANT_REQUIREMENTS` is read from `package.json`
1515
`interchange.grantRequirements`, now the only declaration.
1616
`MEMORY_CAPABILITY_IDS` is typed `string[]`.
17+
- `createMemoryRoutes({ memory, requireGrant, callerResolver? })` returns the
18+
memory routes as a `Hono<TenantEnv>` sub-app with paths relative to its
19+
mount point; hosts mount it at `/api/tenants/:tenantId/memory`. It replaces
20+
`createMemory({ app })` and `registerMemoryRoutes`, and `RouteDeps` no
21+
longer carries `grants`. `createMemory` only builds the plane.
22+
- The package root exports only the public API. Internal services and
23+
helpers (transform, retention, feed, share materialization, corroboration,
24+
embed model registry, degrade metrics, FTS helpers), the test fakes, and
25+
`resolveGrantConfig` are no longer exported. The distiller stays at
26+
`@corbits/memory/distiller` and migrations at `@corbits/memory/migrations`.
1727

1828
### Added
1929

@@ -29,7 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2939
in-process `Memory`. New `memory:forget` / `memory:purge` grant requirements
3040
(`source: "creator"`) and `capabilityIdsForSurface()` so distiller/tools
3141
installs no longer pick up routes-only capabilities by accident.
32-
- `RouteDeps.callerResolver` / `createMemory({ callerResolver })` — an
42+
- `RouteDeps.callerResolver` — an
3343
optional host-supplied resolver from a request to a `{ tenantId,
3444
principalId }` scope, for a caller that never goes through the host's
3545
tenant-session middleware (e.g. a workflow-run child authenticating with

‎IMPLEMENTATION.md‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ and wire shapes. For the "why standalone" / boundaries story, read
88

99
```
1010
src/
11-
index.ts # createMemory / registerMemoryRoutes / mountWorkflowMemory + distiller re-exports
11+
index.ts # createMemory / createMemoryRoutes / mountWorkflowMemory
1212
1313
mount-config.ts # MemoryConfig + loadMemoryConfig() — the mount config
1414
config.ts # EngineConfig — the core vector-plane config (db + embed + rerank)
@@ -23,7 +23,7 @@ src/
2323
migrations.ts # runMemoryMigrations(url)
2424
ports/ # DocumentStore / SourceProvider + fakes
2525
routes/ # the mounted tenant routes
26-
mount.ts # registerMemoryRoutes (HTTP)
26+
mount.ts # createMemoryRoutes (HTTP sub-app)
2727
2828
deps.ts # RouteDeps, caller(c) (context identity), grantGuard
2929
add.ts, search.ts, list.ts, feed.ts
@@ -605,16 +605,17 @@ end-user agents.
605605

606606
## Mounted routes
607607

608-
`createMemory({ app })` registers these onto the host app. Identity is the request
608+
`createMemoryRoutes(deps)` serves these once the host mounts it at
609+
`/api/tenants/:tenantId/memory`. Identity is the request
609610

610611
principal read off the Interchange context (`caller(c)` →
611612
`{ scopeId: principal.tenantId, subjectId: principal.id }`); clients never send
612613
`tenant_id`/`principal_id` — the handlers only read title/text/query/limit/access_tags/share.
613614
Each route is guarded with `grantGuard(deps, action)`, which applies the host's
614615
`requireGrant("memory", action)` when provided (else a pass-through).
615616

616-
**Machine callers (CL-6286):** `RouteDeps.callerResolver` /
617-
`createMemory({ callerResolver })` lets a host resolve identity for a caller
617+
**Machine callers (CL-6286):** `RouteDeps.callerResolver` (passed to
618+
`createMemoryRoutes`) lets a host resolve identity for a caller
618619
that never goes through its tenant-session middleware — e.g. a workflow-run
619620
child authenticating with its own sidecar bearer token. Unset by default
620621
(every existing host is unaffected). When set, `resolveCaller` (`deps.ts`)
@@ -640,7 +641,7 @@ surface, or a migrating host silently loses them.
640641
| `POST /api/tenants/:tenantId/memory/documents/:documentId/purge` | `purge` | none | `200 { documentId, deleted, reason? }`; `403` unless caller is the document's creator; `404` unknown document. Hard-deletes the row — irreversible; refused while a `durable` version is untombstoned. |
641642
| `POST /api/tenants/:tenantId/memory/versions/:versionId/retention-class` | `forget` | `{ retention_class }` | `200 { versionId, documentId, status }`; `400` invalid class; `403` unless caller is the version's creator; `404` unknown version. |
642643

643-
`registerMemoryRoutes` and `createMemory({ app })` register these seven HTTP
644+
`createMemoryRoutes` serves these seven HTTP
644645
routes (add, search, list, feed, forget, purge, retention-class).
645646
**Capture** (`services/capture.ts`) is the write path inside `add`.
646647
**Search** (`services/search.ts`) is hybrid retrieval. Deployed agents do
@@ -658,7 +659,7 @@ have HTTP to the tenant tree can use `createMemoryHttpClient`
658659
### Run-scoped sidecar (`mountWorkflowMemory`)
659660

660661
`src/workflow-mount.ts`, re-exported from the barrel. Parallel to the
661-
tenant tree — do not fold agent-bearer auth into `registerMemoryRoutes`.
662+
tenant tree — do not fold agent-bearer auth into `createMemoryRoutes`.
662663
`package.json` exports `@corbits/memory/sidecar-bundle` →
663664
`src/sidecar-bundle.ts`.
664665

@@ -713,7 +714,7 @@ store implements `WritableGrantStore.putGrant`:
713714
1. Appends `memory.doc:<documentId>` to the document's `access_tags`.
714715
2. Writes one allow/`search` grant per peer on that resource, origin
715716
`system`, with `conditions.memoryShare` audit payload.
716-
3. `resolveGrantConfig` merges `MEMORY_SHARE_CONDITION_REGISTRY` so those
717+
3. `createMemory` merges `MEMORY_SHARE_CONDITION_REGISTRY` so those
717718
condition keys are not fail-closed-skipped by `@intx/authz`.
718719

719720
Without a writable store: tags only + warn log (peers need host grants).

‎PRODUCT.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ never creates one; it mounts onto yours.
3636

3737
| Surface | Role |
3838
| --- | --- |
39-
| `createMemory({ app, … })` | Register `/api/tenants/:tenantId/memory/*` + return the plane |
39+
| `createMemory({ … })` | Build the plane |
40+
| `createMemoryRoutes({ memory, requireGrant })` | Hono sub-app the host mounts at `/api/tenants/:tenantId/memory` |
4041
| `mountWorkflowMemory(app, { memory, agentToken })` | Parallel run-scoped `/api/workflow-memory/*` for deployed agents |
4142
| `loadMemoryConfig()` | Config from env |
4243
| `runMemoryMigrations(url)` | Apply pgvector schema |
43-
| `registerMemoryRoutes` | Low-level HTTP only (optional) |
4444
| `@corbits/memory/sidecar-bundle` | Deployed-agent factory — no client code, no base URL, no token |
4545
| `@corbits/memory/distiller` | Optional process helpers: `runDistillTick`, `createResidentDistiller` |
4646

@@ -80,7 +80,8 @@ Deployed agent (sidecar-bundle)
8080
┌──────────────────────────────────────────────┐
8181
│ Host Interchange createApp │
8282
│ principal + tenant on context │
83-
│ + createMemory({ app, grantStore, … }) │
83+
│ + createMemory({ grantStore, … }) │
84+
│ + app.route(…, createMemoryRoutes(deps)) │
8485
│ grants: memory:add | memory:search │
8586
│ documentStore: pgvector | host | fake │
8687
│ + mountWorkflowMemory(app, { memory, … }) │

‎README.md‎

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,29 +24,38 @@ yarn add @corbits/memory
2424
bun add @corbits/memory
2525
```
2626

27-
Write the mount as a function that takes your hub's `app`, `grantStore`, and
28-
`conditionRegistry` — the same trio you already pass to
27+
Build the plane, then mount its routes on your hub's `app` with the
28+
`grantStore` and `conditionRegistry` you already pass to
2929
`createRequireGrant`/`createApp`:
3030

3131
```ts
3232
import { Hono } from "hono";
33-
import type { TenantEnv } from "@intx/hub-api";
33+
import { createRequireGrant, type TenantEnv } from "@intx/hub-api";
3434
import type { ConditionRegistry, GrantStore } from "@intx/authz";
35-
import { createMemory, loadMemoryConfig, type Memory } from "@corbits/memory";
35+
import {
36+
createMemory,
37+
createMemoryRoutes,
38+
loadMemoryConfig,
39+
type Memory,
40+
} from "@corbits/memory";
3641

3742
export function installMemory(
3843
app: Hono<TenantEnv>,
3944
grantStore: GrantStore,
4045
conditionRegistry: ConditionRegistry,
4146
): Memory {
42-
const memoryApp = new Hono<TenantEnv>();
4347
const memory = createMemory({
44-
app: memoryApp,
4548
config: loadMemoryConfig(), // DATABASE_URL + embed env — see below
4649
grantStore,
4750
conditionRegistry,
4851
});
49-
app.route("/", memoryApp);
52+
app.route(
53+
"/api/tenants/:tenantId/memory",
54+
createMemoryRoutes({
55+
memory,
56+
requireGrant: createRequireGrant({ grantStore, conditionRegistry }),
57+
}),
58+
);
5059
return memory;
5160
}
5261
```
@@ -125,10 +134,9 @@ at `@corbits/memory/sidecar-bundle`.
125134

126135
## How it works
127136

128-
`createMemory` builds the plane. Pass `app` to register
129-
`/api/tenants/:tenantId/memory/*` behind `requireGrant("memory", …)` —
130-
`grantStore` is required for that mount. `loadMemoryConfig` lives on the
131-
barrel and at `@corbits/memory/config`.
137+
`createMemory` builds the plane. `createMemoryRoutes` returns its HTTP routes
138+
as a Hono sub-app, each guarded by `requireGrant("memory", …)`.
139+
`loadMemoryConfig` lives on the barrel and at `@corbits/memory/config`.
132140

133141
Capability grants (`memory:add` / `memory:search` / `memory:forget` /
134142
`memory:purge`) gate the routes. Per-document visibility is Interchange
@@ -167,9 +175,8 @@ mail-triggered workflow is ticked by addressing it directly. No import of
167175

168176
### Lower-level: in-process calls, no HTTP
169177

170-
`app` is optional. Passing only `config` builds the plane without
171-
registering routes, for a host worker that calls `add`/`search` directly
172-
(the resident distiller does this):
178+
A host worker can skip `createMemoryRoutes` and call `add`/`search` on the
179+
plane directly (the resident distiller does this):
173180

174181
```ts
175182
import { createMemory, loadMemoryConfig } from "@corbits/memory";
@@ -203,8 +210,8 @@ bun run typecheck # tsc --noEmit
203210
bun run test # bun test ./src
204211
```
205212

206-
Tests use `createFakeDocumentStore`/`createFakeSourceProvider` (exported for
207-
this purpose) so the suite runs without Postgres. `bun run build` compiles
213+
Unit tests use the in-repo `createFakeDocumentStore`/`createFakeSourceProvider`
214+
so the suite runs without Postgres. `bun run build` compiles
208215
`src/` to `dist/`, which is what the package publishes.
209216

210217
## License

‎src/core/merge-local-live.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* MergeLocalLiveV1 — combine local DocumentStore hits with live SourceProvider
2+
* Local/live merge — combine local DocumentStore hits with live SourceProvider
33
* hits into one ranked list.
44
*
55
* Spec (frozen for M3/M4):

0 commit comments

Comments
 (0)