Skip to content

Commit 9b082b3

Browse files
chore(release): 0.2.0 (#60)
* test: a 0.1.0 database upgrades with no data loss Migrates and writes a database with the published 0.1.0 package, then runs this version's migrations and checks every row survives and the 0.1.0 documents stay searchable. * test: move the real-Postgres suites to e2e/ The end-to-end suites and their harness move from tests/ to the root e2e/ directory, with the database harness and HTTP stub merged into e2e/helpers.ts, to match the repo test layout. bun run test now runs src/ only and the new test:e2e script runs e2e/; CI runs both. * chore(release): 0.2.0
1 parent 778847b commit 9b082b3

19 files changed

Lines changed: 236 additions & 282 deletions

‎.github/workflows/ci.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ jobs:
2828
- run: bun install --frozen-lockfile
2929
- run: bun run typecheck
3030
- run: bun run test
31+
- run: bun run test:e2e
3132
node:
3233
runs-on: ubuntu-latest
3334
strategy:

‎CHANGELOG.md‎

Lines changed: 34 additions & 203 deletions
Original file line numberDiff line numberDiff line change
@@ -5,217 +5,48 @@ All notable changes to `@corbits/memory` are documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [Unreleased]
8+
## [0.2.0] — 2026-09-25
99

1010
### Changed
1111

12-
- `@intx/*`, `drizzle-orm`, `hono`, `hono-openapi` and `postgres` are peer
13-
dependencies; the host supplies them. `engines` is removed.
12+
- **Breaking:** `@intx/*`, `drizzle-orm`, `hono`, `hono-openapi` and
13+
`postgres` are peer dependencies; the host supplies them. `engines` is
14+
removed.
15+
- **Breaking:** `createMemoryRoutes({ memory, requireGrant, callerResolver? })`
16+
returns the memory routes as a `Hono<TenantEnv>` sub-app with paths relative
17+
to its mount point; hosts mount it at `/api/tenants/:tenantId/memory`. It
18+
replaces `createMemory({ app, callerResolver })` and `registerMemoryRoutes`,
19+
and `RouteDeps` no longer carries `grants`. `createMemory` only builds the
20+
plane.
21+
- **Breaking:** the package root exports only the public API. Internal
22+
services and helpers (transform, retention, feed, share materialization,
23+
corroboration, embed model registry, degrade metrics, FTS helpers), the test
24+
fakes, and `resolveGrantConfig` are no longer exported. The distiller stays
25+
at `@corbits/memory/distiller` and migrations at
26+
`@corbits/memory/migrations`.
27+
- **Breaking:** `runMemoryMigrations(config, { schema, ftsLanguage })` takes
28+
the same `DBConfig` as Interchange `runMigrations` instead of a database
29+
URL. `schema` names the host schema holding Interchange's `tenant` and
30+
`principal` tables (the value passed to `runMigrations`, e.g. `"public"`);
31+
memory's tables stay in the `memory` schema. `ftsLanguage` is required, and
32+
the runner no longer reads `FTS_LANGUAGE` from the environment or accepts a
33+
`log` option.
34+
- Every migration file is idempotent and replayed on each run, with a 5 s
35+
`lock_timeout`. The `memory._migrations` ledger is dropped.
1436
- `MEMORY_GRANT_REQUIREMENTS` is read from `package.json`
1537
`interchange.grantRequirements`, now the only declaration.
1638
`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`.
27-
- `runMemoryMigrations(config, { schema, ftsLanguage })` takes the same
28-
`DBConfig` as Interchange `runMigrations` instead of a database URL.
29-
`schema` names the host schema holding Interchange's `tenant` and
30-
`principal` tables (the value passed to `runMigrations`, e.g. `"public"`);
31-
memory's tables stay in the `memory` schema. `ftsLanguage` is required, and
32-
the runner no longer reads `FTS_LANGUAGE` from the environment or accepts a `log` option.
33-
Every migration file is idempotent and replayed on each run. The
34-
`memory._migrations` ledger is dropped.
35-
36-
### Added
37-
38-
- Retention HTTP routes (CL-6288): `POST …/memory/documents/:documentId/forget`
39-
(tombstone, grant `memory:forget`), `POST …/memory/documents/:documentId/purge`
40-
(hard delete, grant `memory:purge`), and
41-
`POST …/memory/versions/:versionId/retention-class` (grant `memory:forget`).
42-
Forget and purge are separate routes with separate grant actions — never one
43-
route with a boolean flag — and both are refused with 403 unless the caller
44-
is the document/version's creator, independent of any share grant that lets
45-
them merely see it. `sweepEphemeral` stays off the HTTP surface (maintenance
46-
sweep, not a user action); a host schedules it on its own cron against the
47-
in-process `Memory`. New `memory:forget` / `memory:purge` grant requirements
48-
(`source: "creator"`) and `capabilityIdsForSurface()` so distiller/tools
49-
installs no longer pick up routes-only capabilities by accident.
50-
- `RouteDeps.callerResolver` — an
51-
optional host-supplied resolver from a request to a `{ tenantId,
52-
principalId }` scope, for a caller that never goes through the host's
53-
tenant-session middleware (e.g. a workflow-run child authenticating with
54-
its own sidecar bearer token). Unset by default: every route still reads
55-
identity from `c.get("principal")` exactly as before. When set, the
56-
resolved identity is seated as the request's principal/tenant ahead of
57-
`grantGuard`, so the same `requireGrant` authorization path applies to a
58-
machine caller — never a separate, weaker one. Identity from the resolver
59-
always wins over anything a request body claims. The resolver's return
60-
value is parsed with arktype (non-empty `tenantId`/`principalId`); a
61-
resolver returning a malformed identity is rejected with `500` (a host
62-
bug), never seated as a garbage scope.
63-
64-
### Fixed
65-
66-
- `loadMemoryConfig` / `EngineConfig.embed` no longer requires an embed
67-
endpoint: a host with a pgvector Postgres and no embed endpoint now
68-
constructs and serves `add` + lexical `search`. Dense retrieval is skipped
69-
(not attempted-and-failed) and `search` reports
70-
`degraded: ["dense_unavailable", "lexical_only"]` so the state stays
71-
observable (CL-6287). **Migration note:** `dense_unavailable` is now also
72-
emitted on every search for a deliberately-unconfigured engine, not only on
73-
a transient dense-retrieval failure — a host with an existing alert rule
74-
keyed on `dense_unavailable` alone should also check for `lexical_only` in
75-
the same `degraded` array to distinguish "opted into lexical-only" from an
76-
actual regression.
77-
- `add`'s `degraded` is now a reason array (`["embed_unavailable"]` and/or
78-
`["embed_unavailable", "lexical_only"]`), matching `search`'s shape —
79-
previously a bare boolean, which made it impossible to write one
80-
"is this response degraded" check across both verbs (CL-6287). **Breaking
81-
if a host coded against the boolean:** `degraded: true` is now
82-
`degraded: [...]`; check array presence/length instead of truthiness (both
83-
are still falsy/omitted when the document captured cleanly).
84-
- `Memory.capabilities.embeddingsConfigured` (and the underlying
85-
`DocumentStore.capabilities`) let a host learn recall is lexical-only at
86-
construction time, without issuing a search first (CL-6287).
87-
- Feed `nextCursor` advances past the examined raw page after grant-tag
88-
post-filter (a fully denied page no longer stalls the consumer forever).
89-
- Distiller default system prompt uses the configured `agentId` for
90-
`exclude_generator` / `generator_agent_id` (not only the package default id).
91-
92-
### Changed
93-
94-
- **Breaking:** `createResidentDistiller` runs on a `mail` trigger (CL-8798).
95-
`CreateResidentDistillerOpts.cron` is replaced by a required `mailTo`, the
96-
address the workflow's run is triggered at, and
97-
`RESIDENT_DISTILLER_CRON_DEFAULT` is removed. The `schedule` trigger was
98-
reserved with nothing behind it in `@intx/workflow`, so the distiller never
99-
ticked, and 0.4.0 rejects it at `defineWorkflow`. Ticking is the host's job:
100-
mail `mailTo` on a schedule, e.g. from `@corbits/cron`.
101-
- `@intx/*` dependencies move to 0.4.0 (CL-8797).
102-
- **Product narrative:** default path is **add → ingest elements → process**
103-
(one host pipeline). Pull feed + `createResidentDistiller` are optional
104-
multi-writer / backfill process helpers, not the primary ingest story.
105-
See `PRODUCT.md`, `docs/DISTILLER.md`, `docs/FEED.md`.
106-
- **Breaking:** Postgres schema renamed from `knowledge` to **`memory`**. Fresh
107-
installs only — drop/recreate the old schema (or rename) on existing DBs.
108-
Citation `open.type` is now `"memory"`.
109-
- **Breaking:** env var is `DATABASE_URL` (was `KNOWLEDGE_DATABASE_URL`); pass
110-
`memory.databaseUrl` on config as an alternative. No deprecated alias.
111-
- `add` (plane + HTTP) returns `{ documentId, versionId }` so share-grant audit
112-
and provenance can name the version that carried the write.
113-
- Interchange `defineTool` factories at `@corbits/memory/tools` (`memory_add`,
114-
`memory_search`, `memory_list`) — HTTP clients for mounted hub routes with
115-
install env `memoryBaseUrl` / `memoryTenantId` / `memoryAuthToken`. Declared
116-
via `package.json` `interchange.tools` and `exports["./tools"]`.
117-
118-
### Previously
119-
120-
- **Breaking:** package and public surface renamed from `@corbits/knowledge-engine`
121-
to `@corbits/memory`. Public APIs: `createMemory` (optional `app` registers HTTP),
122-
`registerMemoryRoutes`.
123-
124-
`createMemory`, `loadMemoryConfig`, `runMemoryMigrations`, `Memory`,
125-
`MemoryConfig`, `MemoryError`. HTTP paths are under
126-
`/api/tenants/:tenantId/memory/`; grants are
127-
`memory:add` / `memory:search`; access tags use `memory.owner:` / `memory.tenant:`
128-
/ `memory.space:`. Postgres schema is `memory`.
129-
- **Breaking:** config reads `DATABASE_URL` (was `KNOWLEDGE_DATABASE_URL`) for
130-
the engine's own pgvector Postgres connection.
131-
- **Breaking:** memory plane surface is `add` / `search` / `list` with
132-
`principalId` + `tenantId` only. Inference is host-owned (no answer endpoint
133-
or personal-memory side-channel on the plane).
134-
135-
- **Breaking:** HTTP routes are `POST /api/tenants/:tenantId/memory/add`,
136-
`POST /api/tenants/:tenantId/memory/search`,
137-
`GET /api/tenants/:tenantId/memory/list` (inherits hub `resolveTenant`).
138-
Old unscoped `/api/memory/*` paths are not mounted.
139-
- **Breaking:** grant actions are `add` and `search` (was `capture` / `find` /
140-
knowledge `search`). `list` uses the `search` grant. Capability resource is
141-
`memory`. Document-tag checks use action `search`.
142-
- **Breaking:** `add` returns `{ documentId }`; search body uses `limit` (not `k`);
143-
search wire uses `items` (not `hits`).
144-
- **Breaking:** document access is Interchange **grant tags** (`accessTags` +
145-
creator-always + host `GrantStore`), not the visibility-mode / block-list
146-
mini-ACL. Share sugar only mints tags. See `docs/AUTHZ-DOCUMENT-ACCESS.md`.
147-
- **Breaking:** Postgres baseline is two files (`0001_extensions` +
148-
`0002_memory_baseline`) with `access_tags` and no `visibility_*` columns.
149-
Fresh installs only — drop/recreate the `memory` schema on existing DBs.
150-
- **Breaking:** `grantStore` + `conditionRegistry` are top-level `createMemory`
151-
options (no nested `grants: { … }`).
152-
153-
### Added
154-
155-
- Optional `TextExtractor` + `file` XOR `content` on `add`
156-
- `share` sugar on `add` (maps to access tags; principals also materialize
157-
grants when the host grant store is writable); `grantsMaterialized` on the
158-
add result when peers were requested
159-
- `access_tags` on `memory.document` (baseline schema)
160-
- Claim-bearing schema, temporal model, transform/replay, share grants
161-
(resident memory distillation foundation — CL-5865/5866/5872/5873)
162-
- Living relevancy: corroboration factor from supports/contradicts edges;
163-
strong evidence gate (CL-5867). See `docs/RELEVANCY.md`
164-
- Capture feed: `memory.feed` + `GET .../memory/feed` with `feed_seq` cursor
165-
(CL-5868). See `docs/FEED.md`
166-
- **Wire attribution (CL-5870):** search hits carry additive `attribution`
167-
(versionId, provenance, source/temporal class, createdByKind,
168-
generatorAgentId, occurredAt/validUntil, corroboration counts, derivedFrom)
169-
- **Retention (CL-5871):** `retention_class` on versions; plane APIs
170-
`deprecateVersion` / `tombstoneDocument` / `hardDeleteDocument` /
171-
`sweepEphemeral` / `setRetentionClass`; `includeDeprecated` on search.
172-
See `docs/RETENTION.md`
173-
- **Resident distiller (CL-5869):** `@corbits/memory/distiller` —
174-
`createResidentDistiller({ inference })` schedule workflow + `runDistillTick`
175-
+ `buildDistilledClaim`. Claim-aware `add` (generator_agent_id, provenance,
176-
derived_from, …). `memory_feed` tool. Feed entries include `accessTags`.
177-
See `docs/DISTILLER.md`
178-
179-
### Removed
180-
181-
- Host-injected generate path on the plane (use host inference + `add` / `search`)
182-
183-
## [0.1.2] — 2026-07-31
184-
185-
### Added
186-
- Public `createMemory` export for out-of-band capture and search (CLI seeders, batch ingesters, tests) without mounting HTTP routes (`#8`)
187-
188-
### Fixed
189-
190-
- Fail-closed document ACL: withhold hits whose `acl_block` is unreadable or non-string (`#9`)
191-
- Timeline titles use the same document ACL post-filter as search (`#12`)
192-
- Return 401 (not 500) when the host has not resolved a principal (`#7`)
193-
- ACL wiring tests mock `sql.unsafe` for FTS verification so the suite stays green under the search path
194-
195-
## [0.1.1] — 2026-07-31
196-
197-
### Added
198-
199-
- Halfvec expression indexes for embedding models above 2000 dimensions (up to 4000); dense search ORDER BY uses the same expression as the index (`#1`)
200-
- Configurable lexical FTS language (`FTS_LANGUAGE` / `EngineConfig.ftsLanguage`) with parse, catalog verify, and rebuild recipe; public `DEFAULT_FTS_LANGUAGE` export (`#4`)
201-
- Named chunk FK with `ON DELETE CASCADE` and composite tenant index on per-model embedding tables created at activation (`#3`)
202-
203-
### Changed
204-
205-
- Dense search scales `hnsw.ef_search` to the overfetch limit (floor 40) and probes `hnsw.iterative_scan = relaxed_order` once per process when available (`#2`)
206-
- Package install docs point at `@corbits/memory` (`#6`)
207-
208-
### Fixed
20939

210-
- Rerank document truncation to fit cross-encoder token limits (`#10`)
211-
- Degrade metrics: surface full rerank failure detail and count DegradeFlags so silent degradation is visible (`#11`)
40+
### Upgrading from 0.1.0
21241

213-
## [0.1.0] — 2026-07
42+
- Call the new `runMemoryMigrations` once; it upgrades a 0.1.0 database in
43+
place with no data loss.
44+
- After the upgrade, do not run 0.1.0 against the database: its ledger is
45+
gone, so there is no downgrade.
21446

215-
### Added
47+
## [0.1.0] — 2026-09-25
21648

217-
- Initial cut: mountable memory capture + search SDK for Interchange hubs
49+
First release on npm.
21850

219-
[0.1.2]: https://github.com/corbitsdev/corbits-memory/compare/v0.1.1...v0.1.2
220-
[0.1.1]: https://github.com/corbitsdev/corbits-memory/compare/v0.1.0...v0.1.1
221-
[0.1.0]: https://github.com/corbitsdev/corbits-memory/releases/tag/v0.1.0
51+
[0.2.0]: https://github.com/corbitsdev/corbits-memory/releases/tag/v0.2.0
52+
[0.1.0]: https://www.npmjs.com/package/@corbits/memory/v/0.1.0

‎CONTRIBUTING.md‎

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ Postgres. See `IMPLEMENTATION.md` for how the pieces fit together.
2222
## Running the tests
2323

2424
```bash
25-
bun run typecheck && bun run test
25+
bun run typecheck && bun run test && bun run test:e2e
2626
```
2727

28-
- `bun run test` runs the unit suite in `src/` and the end-to-end suite in
29-
`tests/`. The end-to-end tests drive the mounted routes and migrations
28+
- `bun run test` runs the unit suite in `src/`; `bun run test:e2e` runs the
29+
end-to-end suite in `e2e/`. The end-to-end tests drive the mounted routes and migrations
3030
against a real pgvector Postgres: set `TEST_DATABASE_URL` to a server the
3131
tests can create and drop databases on (for `docker compose up -d`,
3232
`postgres://memory:memory-dev-password@localhost:5434/memory`). Each suite
@@ -53,18 +53,16 @@ numbered file, because a guarded `ADD CONSTRAINT` keeps the old definition.
5353
changed; link any relevant issue.
5454
- Make sure `bun run typecheck && bun run test` pass before requesting review.
5555

56-
## Commit messages
57-
58-
- Present tense, imperative mood: "Add X", "Fix Y", "Harden Z" — not "Added"
59-
or "Fixes".
60-
- No issue-tracker ticket references (e.g. `CL-1234`) in commit messages,
61-
code, or comments — commit messages should be self-explanatory without an
62-
external ticket.
63-
- One logical change per commit where practical; a commit message describes
64-
the change, not the task that produced it.
65-
6656
## Contributor License Agreement
6757

6858
Contributions require agreeing to the project's CLA — see `CLA.md`. The CLA
6959
bot will comment on your first PR with instructions if you haven't signed
7060
yet.
61+
62+
## Commit messages
63+
64+
Commit subjects and PR titles follow [Conventional Commits](https://www.conventionalcommits.org): `feat`, `fix`, `refactor`, `test`, `docs`, `build`, `ci`, `perf`, and `chore(release): x.y.z` for releases.
65+
Add `!` only for public API breaks: removed or renamed exports, changed signatures, newly required params. Peer and dependency range changes are `build(deps):` with no `!`.
66+
Keep subjects imperative, lowercase after the colon, 72 characters or less, and free of ticket IDs.
67+
No ticket IDs in code or comments either. One logical change per commit where practical; describe the change, not the task that produced it.
68+
Every PR links its issue with a `Closes <issue id>` line in the PR body.

‎bun.lock‎

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎docs/RETENTION.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ creator (`created_by_principal_id` — the document's first version for
6464
independent of any share grant. A peer who can search a shared document gets
6565
403 on `forget`/`purge`/`retention-class` for it. See
6666
`src/services/retention-ownership.ts` and the ownership tests in
67-
`tests/grants.test.ts` / `tests/caller-resolver.test.ts`.
67+
`e2e/grants.test.ts` / `e2e/caller-resolver.test.ts`.
6868

6969
**`sweepEphemeral` stays off the HTTP surface.** It is a maintenance sweep —
7070
"deprecate every ephemeral version past its TTL for this tenant" — not
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
seedPrincipal,
1313
testDatabaseUrl,
1414
type TestDb,
15-
} from "./lib/db-harness.ts";
15+
} from "./helpers.ts";
1616

1717
describe.skipIf(testDatabaseUrl() === undefined)("add and search", () => {
1818
let db: TestDb;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
seedPrincipal,
1616
testDatabaseUrl,
1717
type TestDb,
18-
} from "./lib/db-harness.ts";
18+
} from "./helpers.ts";
1919

2020
// A machine caller (e.g. a workflow run) never passes the host's session
2121
// middleware; the host's callerResolver names its tenant and principal.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
seedPrincipal,
1313
testDatabaseUrl,
1414
type TestDb,
15-
} from "./lib/db-harness.ts";
15+
} from "./helpers.ts";
1616

1717
describe.skipIf(testDatabaseUrl() === undefined)("synthesized context rows", () => {
1818
let db: TestDb;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
seedPrincipal,
1313
testDatabaseUrl,
1414
type TestDb,
15-
} from "./lib/db-harness.ts";
15+
} from "./helpers.ts";
1616

1717
describe.skipIf(testDatabaseUrl() === undefined)("distill tick", () => {
1818
let db: TestDb;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
embedTexts,
77
probeEmbedDims,
88
} from "../src/core/embed-client.ts";
9-
import { startHttpStub } from "./lib/http-stub.ts";
9+
import { startHttpStub } from "./helpers.ts";
1010

1111
const stub = startHttpStub();
1212
afterAll(() => stub.stop());

0 commit comments

Comments
 (0)