@arnilo/prism 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -1
- package/README.md +19 -16
- package/dist/agent-approval.d.ts +7 -1
- package/dist/agent-approval.js +15 -6
- package/dist/agent-run-lifecycle.d.ts +2 -1
- package/dist/agent-run-lifecycle.js +20 -6
- package/dist/agent-run-state.d.ts +26 -5
- package/dist/agent-run-state.js +97 -1
- package/dist/agent-session/event-subscriber.d.ts +2 -0
- package/dist/agent-session/event-subscriber.js +3 -0
- package/dist/agent-session/session/assemble.js +165 -16
- package/dist/agent-session/session/persist.js +11 -5
- package/dist/agent-session/session/provider-round.js +54 -13
- package/dist/agent-session/session/tool-round.d.ts +2 -2
- package/dist/agent-session/session/tool-round.js +86 -23
- package/dist/agent-session/session/types.d.ts +21 -2
- package/dist/agent-session/session.d.ts +66 -4
- package/dist/agent-session/session.js +159 -18
- package/dist/checkpoint-restore.d.ts +50 -14
- package/dist/checkpoint-restore.js +104 -28
- package/dist/context-budget.d.ts +11 -0
- package/dist/context-budget.js +33 -2
- package/dist/contracts-core/agent.d.ts +26 -5
- package/dist/contracts-core/extensions.d.ts +3 -0
- package/dist/contracts-core/guardrail-packs.d.ts +8 -3
- package/dist/contracts-core/loop.d.ts +36 -0
- package/dist/contracts-core/provider.d.ts +6 -1
- package/dist/contracts-core/run-limits.d.ts +10 -1
- package/dist/contracts-core/session.d.ts +2 -1
- package/dist/contracts-protocol.d.ts +6 -4
- package/dist/contracts-run-state.d.ts +48 -6
- package/dist/contributions.d.ts +2 -1
- package/dist/contributions.js +1 -0
- package/dist/extensions.d.ts +15 -1
- package/dist/extensions.js +68 -0
- package/dist/guardrail-packs/types.d.ts +10 -0
- package/dist/guardrail-packs/validation-respect.js +16 -0
- package/dist/guardrails.d.ts +42 -1
- package/dist/guardrails.js +124 -15
- package/dist/index.d.ts +7 -7
- package/dist/index.js +4 -4
- package/dist/leases.js +32 -6
- package/dist/middleware.d.ts +1 -1
- package/dist/node/contribution-discovery.d.ts +16 -1
- package/dist/node/contribution-discovery.js +47 -0
- package/dist/node/session-store-jsonl.js +67 -17
- package/dist/run-bundle.d.ts +6 -1
- package/dist/run-bundle.js +4 -1
- package/dist/run-limits.d.ts +11 -5
- package/dist/run-limits.js +13 -0
- package/dist/session-stores.js +61 -12
- package/dist/testing/prefix-stability-conformance.d.ts +73 -1
- package/dist/testing/prefix-stability-conformance.js +158 -27
- package/dist/tools.js +10 -3
- package/dist/usage-estimation.d.ts +7 -1
- package/dist/usage-estimation.js +16 -10
- package/docs/acp.md +2 -2
- package/docs/agent-events.md +15 -10
- package/docs/agent-session-runtime.md +10 -7
- package/docs/coding-agent-tools.md +1 -1
- package/docs/coding-tools.md +7 -11
- package/docs/compaction-llm.md +2 -0
- package/docs/compaction-observational-memory.md +21 -1
- package/docs/context-and-skills.md +6 -7
- package/docs/contribution-discovery.md +13 -0
- package/docs/durable-runs.md +14 -6
- package/docs/embeddings.md +7 -1
- package/docs/execution-timeline.md +9 -2
- package/docs/extensions.md +21 -5
- package/docs/guardrails.md +16 -6
- package/docs/hooks.md +282 -0
- package/docs/impeccable.md +1 -2
- package/docs/index.md +28 -21
- package/docs/input-and-prompt-assembly.md +1 -1
- package/docs/instruction-injection.md +1 -0
- package/docs/live-testing.md +3 -2
- package/docs/memory-fabric.md +29 -0
- package/docs/middleware-hooks.md +54 -4
- package/docs/migrate-to-0.11.md +65 -0
- package/docs/migration.md +24 -0
- package/docs/node-jsonl-session-store.md +4 -3
- package/docs/operations.md +1 -1
- package/docs/options-index.md +3 -1
- package/docs/peer-dependencies.md +3 -5
- package/docs/policy-and-audit.md +15 -2
- package/docs/prefix-stability-conformance.md +82 -9
- package/docs/provider-packages.md +20 -20
- package/docs/public-contracts.md +2 -1
- package/docs/rag.md +94 -7
- package/docs/release-and-install.md +62 -59
- package/docs/runs-and-usage.md +21 -10
- package/docs/scoped-agent-memory.md +17 -9
- package/docs/scoped-memory.md +138 -0
- package/docs/session-stores.md +2 -2
- package/docs/supervisors.md +14 -6
- package/docs/testing.md +17 -9
- package/docs/tools.md +1 -1
- package/docs/wiki.md +4 -2
- package/docs/workflows.md +2 -2
- package/package.json +8 -5
- package/docs/caveman.md +0 -130
- package/docs/graft.md +0 -149
- package/docs/ponytail.md +0 -129
package/docs/rag.md
CHANGED
|
@@ -109,11 +109,48 @@ const result = await propagator.propagate("doc:erp-lead");
|
|
|
109
109
|
// { sourceId, ids, tombstoned, layers: { rag: 4, wiki: 1 }, batched: true }
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
- `propagate(sourceId)` expands the source through `_lineage.sourceIds` (`collectInvalidationIds`, depth 8) into a closed id set, tombstones **all** of it with reason `forgotten` inside one store transaction, then runs every registered handler with `{ sourceId, ids, scope, signal }`. Handlers return how many artifacts they removed (reported per `kind` in `layers`).
|
|
112
|
+
- `propagate(sourceId)` expands the source through `_lineage.sourceIds` (`collectInvalidationIds`, depth 8) into a closed id set, tombstones **all** of it with the propagator's resolved reason (`forgotten` by default; `legal_hold` stamps `hold: true`) inside one store transaction, then runs every registered handler with `{ sourceId, ids, scope, reason, signal }`. Handlers return how many artifacts they removed (reported per `kind` in `layers`). The context `reason` is the single source of truth: a handler's own `reason` option is only the fallback for a hand-built context, so a `legal_hold` propagation cannot land in a handler's tombstones as `forgotten`.
|
|
113
113
|
- Tombstones, not deletions, for derived rows: rows stay for explainability (`recall({ explain: true })` reports the invalidation), and lineage links never dangle. Handlers own physical removal (chunk rows, files, ledger entries).
|
|
114
|
-
- Retrieval is belt-and-suspenders: `retrieveContext()` reads per-scope invalidations before assembly and drops any candidate whose record id, `_lineage.sourceIds`, or `_rag.sourceId` is tombstoned — so a delete that lands after the query legs read rows still returns zero hits.
|
|
115
|
-
- `HARD_PROPAGATION_EDGES` (4,096) is the one-pass privileged ceiling; over it the whole delete rejects (fail-closed), never a half-tombstoned document. Each store `invalidate` call carries at most `HARD_INVALIDATION_BATCH` (64) entries.
|
|
114
|
+
- Retrieval is belt-and-suspenders: `retrieveContext()` reads per-scope invalidations before assembly and drops any candidate whose record id, `_lineage.sourceIds`, or `_rag.sourceId` is tombstoned — so a delete that lands after the query legs read rows still returns zero hits. The split matters for direct store users: the store's own SQL predicate filters by record id and `_lineage` edge, while a source's *own* chunk rows are covered by the `_rag.sourceId` rule at the retrieval boundary (or removed physically by the `rag` handler) — a raw `store.query()` is not a recall path.
|
|
115
|
+
- `HARD_PROPAGATION_EDGES` (4,096) is the one-pass privileged ceiling; over it the whole delete rejects (fail-closed), never a half-tombstoned document. Each store `invalidate` call carries at most `HARD_INVALIDATION_BATCH` (64) entries. On a durable store that shape holds: PostgreSQL/pgvector tombstones 1,001 rows (1,000 derived chunk rows + the source root) in **one transaction and 22 statements** (16 of them `HARD_INVALIDATION_BATCH`-sized `INSERT`s), measured at **29–155 ms** across runs on an AMD Ryzen 9 PRO 7940HS against `pgvector/pgvector:pg16` (more under parallel load) — the durable counterpart of the in-memory suite's 1k-under-2s check, and evidence rather than a gate. Re-run it with `PRISM_TEST_POSTGRES_URL=… npm run test:postgres` (`packages/memory/src/__tests__/postgres-propagation.integration.test.ts`); the leg also proves the store's own SQL predicate hides the tombstoned rows, not only the in-app guard, and that a denied propagation opens no transaction at all.
|
|
116
116
|
- Deletion is privileged: `authorization` is required, tenant-checked, and enforced through the store's existing `checkSourceAccess` ACL when the store declares `authorization: "acl"` (missing grant → `MemoryScopeError` before anything is written). Retrieval paths never construct a propagator.
|
|
117
|
+
- Observational memory registers its own leg: `createObservationalMemoryDropHandler({ session, appendEntry })` (from `@arnilo/prism-memory/compaction/observational-memory`) folds the session ledger once per propagation and writes one `om.observations.dropped` entry for the observations that rest on a tombstoned record id; see [observational memory](compaction-observational-memory.md).
|
|
118
|
+
|
|
119
|
+
### One wiring, every layer (host recipe)
|
|
120
|
+
|
|
121
|
+
A host composes the legs itself — no facade ships until a host asks for one, because the propagator already owns handler registration, privilege, and the lineage-closed id set:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { createDeletionPropagator, createMemoryVectorStore, listInvalidatedIds } from "@arnilo/prism-memory";
|
|
125
|
+
import { createRagDeletionHandler } from "@arnilo/prism-memory/rag";
|
|
126
|
+
import { createWikiDeletionHandler } from "@arnilo/prism-memory/wiki";
|
|
127
|
+
import { buildObservationalMemoryContextBlocks, createObservationalMemoryDropHandler } from "@arnilo/prism-memory/compaction/observational-memory";
|
|
128
|
+
import { createFabricRepointHandler } from "@arnilo/prism-memory/fabric";
|
|
129
|
+
|
|
130
|
+
const store = createMemoryVectorStore();
|
|
131
|
+
const propagator = createDeletionPropagator({
|
|
132
|
+
scope,
|
|
133
|
+
vectorStore: store,
|
|
134
|
+
authorization: hostVerifiedPrincipal, // required by the type; must match the scope's tenant
|
|
135
|
+
handlers: [
|
|
136
|
+
createRagDeletionHandler({ store, scope: ragScope }),
|
|
137
|
+
createWikiDeletionHandler({ workspaceRoot }),
|
|
138
|
+
createObservationalMemoryDropHandler({ session, appendEntry }),
|
|
139
|
+
createFabricRepointHandler({ scope, vectorStore: store }),
|
|
140
|
+
],
|
|
141
|
+
});
|
|
142
|
+
const result = await propagator.propagate("docs/policy.md");
|
|
143
|
+
// { sourceId, ids: ["docs/policy.md", "summary:docs/policy.md"], tombstoned: 2, layers: { rag: 1, wiki: 1, observational: 1, fabric: 1 }, batched: true }
|
|
144
|
+
|
|
145
|
+
// Write path: the drop entry is the ledger's record of what the revocation retired (one append, ids only).
|
|
146
|
+
// Read path: a projection whose ledger was never written passes the same tombstones instead.
|
|
147
|
+
const blocked = await listInvalidatedIds(store, scope);
|
|
148
|
+
const blocks = buildObservationalMemoryContextBlocks(entries, { invalidatedIds: blocked });
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
- Both paths keep a revoked observation out of memory, in the same rendered order: the drop entry retires every active observation whose id or `sourceEntryIds` intersect the tombstone set (`om.observations.dropped`, ids only — never observation text), and `invalidatedIds` withholds them at build time. So an id is withheld whether or not the physical drop ran, and a projection built from an older snapshot matches the post-drop one.
|
|
152
|
+
- One `listInvalidatedIds` read per projection build (one scope read, `corrected` entries stay), and the recipe adds no work beyond the propagator: the same `layers` result already answers per-leg counts, so nothing is re-read to report it.
|
|
153
|
+
- The fabric leg is the one that cannot be left out: a note names its document by `metadata.path`, so no `_lineage` edge exists to walk and a deleted path would otherwise keep being served. `createFabricRepointHandler()` tombstones the notes recorded against the deleted id in the same pass (plan 102 Task 11), and the same handler follows a `repointSource()` move — see the re-point section below.
|
|
117
154
|
|
|
118
155
|
## Grant recheck and re-pointing
|
|
119
156
|
|
|
@@ -136,6 +173,7 @@ await store.setSourceAccess(thread, [{ sourceId: "doc:payroll", principalIds: []
|
|
|
136
173
|
- Fail closed, never silently: an absent/revoked/version-mismatched grant and a **thrown** store error both withhold the hits, and every withheld source is reported once through `onAccessDenied` as `{ sourceId, scope, reason: "no_grant" | "check_failed", hits, error? }` (`error` is redacted and capped at 256 chars). The query still completes with the remaining hits. Abort still aborts — it is not reclassified as a denial.
|
|
137
174
|
- There is no per-request off switch: passing `authorization` is what turns the gate on, and the only knob is the audit sink. A store that declares `authorization: "acl"` without `checkSourceAccess` fails closed before ranking.
|
|
138
175
|
- The store's own query/lexical predicate remains the first line of defense (unauthorized text never leaves the store); the boundary recheck also covers stores whose query leg ignores grants, and revokes that land after the query legs have read.
|
|
176
|
+
- Sources the store filtered *inside* its own predicate no longer go unaudited (plan 102 Task 6). An `authorization: "acl"` store reports what it withheld per query when the caller opts in — `onDeniedSources: (denials) => …` on `query`/`lexicalQuery`, carrying `{ sourceId, reason: "no_grant" | "version_mismatch" | "unknown" }` (ids and reasons only) — and `retrieveContext()` passes that report into the same `onAccessDenied` path, so a store-filtered source produces one event per query with `hits: 0`. The report never widens the predicate, and without the callback the store's SQL is unchanged: PostgreSQL/pgvector then issues no extra statement, or exactly one grouped anti-join with it (1.2–1.4ms on the 23-row protected fixture).
|
|
139
177
|
|
|
140
178
|
When a source's grant identity moves (`doc:a` → `doc:b`, a document re-filed under a new source id), `repointSource()` makes the derived artifacts follow **without re-embedding**:
|
|
141
179
|
|
|
@@ -151,15 +189,63 @@ const moved = await repointSource({
|
|
|
151
189
|
authorization: hostVerifiedPrincipal, // must admit BOTH ids on an ACL store
|
|
152
190
|
handlers: [createWikiRepointHandler({ workspaceRoot })],
|
|
153
191
|
});
|
|
154
|
-
// { from, to, movedChunks, rewrittenEdges, layers: { wiki: 1 }, batched: true }
|
|
192
|
+
// { from, to, movedChunks, rewrittenEdges, layers: { wiki: 1 }, batched: true } (add `cursor` for the next page)
|
|
155
193
|
```
|
|
156
194
|
|
|
157
|
-
- Chunk rows keep their text, embeddings, offsets, and generation: the row id (`doc:a#0001` → `doc:b#0001`), `_rag.sourceId`, and `_rag.citationId` are rewritten, old ids are deleted, and the
|
|
195
|
+
- Chunk rows keep their text, embeddings, offsets, and generation: the row id (`doc:a#0001` → `doc:b#0001`), `_rag.sourceId`, and `_rag.citationId` are rewritten, old ids are deleted, and the page lands in one store transaction (`batched: true`) or not at all — never a half-written page, and never a re-embed. A durable store keeps that promise: PostgreSQL/pgvector re-keys the chunk rows and rewrites the lineage edges in **one transaction per page**, and the grant check for both ids runs before the first transaction opens. The protected leg measures the small fixture (1 chunk row + 3 lineage edges, 3–8 ms); the 1k-row durable cost above is the propagation number.
|
|
158
196
|
- Lineage edges (`_lineage.sourceIds`) on derived rows move from `from` to `to` in the same pass, so `createDeletionPropagator()` stays correct afterwards: deleting `doc:b` still tombstones the derived rows, deleting `doc:a` no longer touches them.
|
|
159
|
-
- Privileged like deletion propagation: on a store that declares `authorization: "acl"` the caller must pass an `authorization` that admits **both** the source and the destination, and re-point never creates or copies grants — grant the destination first or the move fails closed.
|
|
197
|
+
- Privileged like deletion propagation: on a store that declares `authorization: "acl"` the caller must pass an `authorization` that admits **both** the source and the destination, and re-point never creates or copies grants — grant the destination first or the move fails closed.
|
|
198
|
+
- **One call moves one page.** `HARD_REPOINT_RECORDS` (4,096) is the default `pageSize`, not a ceiling on the scope: a bigger scope returns a `cursor` and the host continues from it. Each page re-keys and rewrites inside its own transaction, so a page is atomic; the ACL check runs once per call (a resumed call re-validates rather than trusting a cached decision), and the whole scope is read per page because no store exposes a ranged read — the bound is on the write, which is where the cost was.
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
let cursor: string | undefined;
|
|
202
|
+
let movedChunks = 0;
|
|
203
|
+
do {
|
|
204
|
+
const page = await repointSource({
|
|
205
|
+
scope,
|
|
206
|
+
vectorStore: store,
|
|
207
|
+
from: "doc:a",
|
|
208
|
+
to: "doc:b",
|
|
209
|
+
authorization: hostVerifiedPrincipal,
|
|
210
|
+
handlers: [createWikiRepointHandler({ workspaceRoot })],
|
|
211
|
+
pageSize: 1_000, // ≤ HARD_REPOINT_RECORDS if you want the documented ceiling
|
|
212
|
+
...(cursor === undefined ? {} : { cursor }),
|
|
213
|
+
});
|
|
214
|
+
movedChunks += page.movedChunks;
|
|
215
|
+
cursor = page.cursor; // present while records past the page still need moving
|
|
216
|
+
} while (cursor !== undefined);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
- The cursor is an opaque token that names the last record **id** the page considered, ascending. Records an earlier page already moved no longer touch `from`, so a **stale cursor is a no-op** (no transaction opens, no handler runs, `movedChunks: 0`), and a cursor from another scope or source pair is rejected with a validation error before the store is read — a partial move is never silently completed as a different one. Re-running a completed move with the final page's cursor reports `movedChunks: 0`.
|
|
220
|
+
- Pages are id-ordered, not store-ordered, so a resume is deterministic even for a store that returns rows in an unstable order. A destination collision fails closed on whichever page it appears in (`MemoryValidationError`, before that page's write).
|
|
221
|
+
- Passing `maxRecords` keeps the plan 089 all-or-nothing posture: over that many records in the scope the call rejects instead of paging. Use it when a partial move is worse than no move; `pageSize` alone is the paged mode.
|
|
160
222
|
- Row ids that already exist at the destination (other than rows of the moved source) abort the move instead of overwriting (`MemoryValidationError`).
|
|
161
223
|
- `createWikiRepointHandler()` moves the wiki projection: manifest `rawSources`/`anchors`, the `sourceFileHashes` entry, every page that names the old path, the index pages, and a `Repointed` log line — no recompilation. `pathsFor(sourceId → paths)` maps ids to paths when they differ.
|
|
162
224
|
- Observational memory: `listInvalidatedIds(vectorStore, scope)` returns the ids a scope currently withholds (`corrected` sources stay) — pass them as `invalidatedIds` to `buildObservationalMemoryProjection()` / recall so already-emitted blocks that rest on a revoked source go stale on the next build instead of being re-injected.
|
|
225
|
+
- Fabric notes (`@arnilo/prism-memory/fabric`): `createFabricRepointHandler({ scope, vectorStore })` is the same handler on **both** seams — registered for a move it rewrites `metadata.fabric.path` on `kind: "file"` notes recorded against `from` (id, text, embedding, `sourceEntryIds`, and every other field reused verbatim, so nothing is re-embedded and no `_lineage` field is invented), and registered on `createDeletionPropagator()` it tombstones the notes of a deleted path through the store's own invalidation path. Notes are store-backed metadata, not derived chunk rows, so this handler is the only path that reaches them; it reads the scope once per leg, selects on `metadata.fabric.path` (never on content), only ever touches its own scope, and reports its count as the `fabric` layer.
|
|
226
|
+
|
|
227
|
+
### Renaming in batches
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
import { applySourceRenames } from "@arnilo/prism-memory";
|
|
231
|
+
|
|
232
|
+
const { results, failures } = await applySourceRenames({
|
|
233
|
+
scope,
|
|
234
|
+
vectorStore: store,
|
|
235
|
+
authorization: hostVerifiedPrincipal,
|
|
236
|
+
renames: [
|
|
237
|
+
{ from: "doc:a", to: "doc:b" },
|
|
238
|
+
{ from: "doc:c", to: "doc:d" },
|
|
239
|
+
],
|
|
240
|
+
handlers: [createWikiRepointHandler({ workspaceRoot })],
|
|
241
|
+
onRenamed: (event) => audit.write({ kind: "rag.repointed", ...event }),
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
- A thin, audited loop over `repointSource()`: no second re-key path and no new store method. Every pair is handed over as-is, so `repointSource` re-checks its own ACLs (both ids) — nothing is cached or pre-authorized across renames — and each rename walks its own page loop, so a pair above one page still moves completely while every page keeps its one ACL check, one transaction, and one handler pass. The counts folded into the audit event are the rename's totals across pages, and a rename's result carries no `cursor`: a batch is all pages or an error.
|
|
246
|
+
- The batch is validated as a **set before the first store read**: duplicate ids, a chained move (`a→b` then `b→c`), an overlapping move, `from === to`, or an empty id rejects the whole call with `MemoryValidationError` and writes nothing. An empty list is a no-op, not an error.
|
|
247
|
+
- Fail fast by default: the first failing pair writes nothing, later pairs never start, and its original error propagates. `continueOnError: true` records it in `failures` — `{ from, to, error }` — and keeps going.
|
|
248
|
+
- `onRenamed` is the audit sink, called once per rename when it settles: `{ from, to, outcome: "moved", movedChunks, rewrittenEdges, layers }`, or `{ from, to, outcome: "failed", error }` — ids and counts only, never rows or text, and `error` passes through the optional `redact` before it is capped at 256 chars. A pair a fail-fast run never started is not audited; an abort stops the batch and is never recorded as a rename failure.
|
|
163
249
|
|
|
164
250
|
## Local reranker
|
|
165
251
|
|
|
@@ -174,7 +260,8 @@ const result = await retrieveContext("How do approvals work?", { embedder, store
|
|
|
174
260
|
```
|
|
175
261
|
|
|
176
262
|
- `resolveReranker({ kind: "local" })` is the zero-config path. The model runtime is a host seam exactly like `Embedder`: `createLocalReranker({ model?, runtime?, onLoad?, cacheDir?, dtype?, device?, allowRemoteModels? })`. Pass `runtime: { load(model) → { id, score({ query, documents, signal }) } }` to inject a runtime the host already owns (transformers.js, onnxruntime-node, llama.cpp). With no `runtime`, the built-in loader resolves `@huggingface/transformers` at first use — the package declares no inference dependency (no new dependency name in any manifest) and nothing resolves it at build/install time.
|
|
177
|
-
- Sizing trade-off:
|
|
263
|
+
- Sizing trade-off: the download is one-time and host-cached, and per-query latency is CPU-bound and grows with candidates × tokens, so keep `topK`/`queryCandidates` near what recall actually needs — the reranker reorders what retrieval returned, it cannot recover a chunk the candidate pool never returned. Measured on one corpus (24 queries / 96 chunks: one answering chunk + three mention-only chunks per query, k=5, `Xenova/bge-reranker-base` q8 on x86 CPU, vector-only) with two embedders: the deterministic lexical `createHashEmbedder` baseline gives recall@5 **0.21 → 0.79** and recall@20 **0.63** at the package default 20-candidate pool, while the semantic `Xenova/all-MiniLM-L6-v2` q8/cpu (384 dims) gives **0.79 → 0.79** and recall@20 **1.00** — the semantic baseline starts at the lexical reranked number, the reranker's lift is 0.000 on this corpus, and the pool is not the binding constraint. Read the row for your own embedder: with a lexical/deterministic embedder the pool bound is what to raise first; with a semantic one the reranker is ordering quality only and the residual misses need better retrieval. Top-50 median was 95–289 ms across runs. Corpus, misses, pool-bound numbers, latency, cache state, and the semantic side-by-side live in [`docs/_evidence/phase111-reranker-semantic-recall.md`](_evidence/phase111-reranker-semantic-recall.md), with the lexical control in [`docs/_evidence/phase102-local-rerank-latency.md`](_evidence/phase102-local-rerank-latency.md); both are regenerated by `PRISM_TEST_LOCAL_RERANK=1 npm run test:live`. Treat the numbers as one data point on one machine, not a ceiling: dtype, device, and the embedder move them. The non-CPU (fp16/GPU) leg is host-provisioned — re-measure before expecting the CPU numbers to hold. The package guarantees the plumbing (one lazy load, one batched score call per rerank), not the model's speed. The hosted/TEI adapters stay for scale (higher throughput, no local RAM, no download).
|
|
264
|
+
- Host defaults: `dtype: "q8"` with `device: "cpu"` on x86 — fp32 weights are roughly 4× the download for no measurable ranking gain in this size class, and fp16/GPU is worth opting into only when the host already provisions it. Weights are cached per host: pass one `cacheDir` (e.g. `~/.cache/prism/models`) and the runtime lays out one subdirectory per model id, so a second model or a second process reuses the same files — point local embedders running through the same runtime at that directory too. With `cacheDir` omitted the runtime's own default cache applies (inside the installed package). On a cache miss the model is downloaded once into that directory and later runs stay on disk: add `allowRemoteModels: false` on an offline host to fail instead of reaching the model registry, which is exactly what the live leg's second pass proves.
|
|
178
265
|
- Cheap by construction: the model loads lazily once per reranker instance, `score` is called once per rerank with every candidate (never one call per document), and `onLoad({ model, loadMs })` is the only opt-in observability — no document text is ever logged. Zero network after load; the built-in loader only touches the model registry at load time, and `allowRemoteModels: false` pins it to local files.
|
|
179
266
|
- Failure is loud: a missing runtime, an unreachable model, or a runtime that returns no per-document scores throws a redacted `RagValidationError` naming the model and the install path (`npm i @huggingface/transformers` or pass `{ runtime }`). There is deliberately **no** silent lexical fallback.
|
|
180
267
|
- `rerankHits` is unchanged and still owns the caps and the trust boundary: local scores reorder the same `RagHit` references (provenance/trust untouched), byte/ms/concurrency limits apply, and abort/timeout/malformed-score cases fail closed.
|
|
@@ -5,28 +5,29 @@
|
|
|
5
5
|
## What it does
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
Prism's current **0.
|
|
8
|
+
Prism's current **0.11.0** line has **12 publishable manifests**: the root `@arnilo/prism` core package plus **11 workspace packages** — **20 provider adapters** (20 provider adapter subpaths inside the `@arnilo/prism-providers` family), 4 `prism-*` family packages, and 7 capability packages. (Generated by `node scripts/package-truth.mjs` → `scripts/package-truth.json` — the manifest-derived single source for counts, provider membership, umbrella closures, and profile closures.) The last lockstep cut was 0.3.0; Decision B now publishes changed packages independently inside `^0.3.0` — the plan 039 changed-package cut moved root `@arnilo/prism` and every plan-035+ changed package to **0.3.1**, and the plan 050 changed-package cut moved root plus four changed packages to **0.3.2**; the plan 041-044 changed-package cut moves root to **0.3.3** with `@arnilo/prism-memory@0.3.2` (composite recall scoring), `@arnilo/prism-evals@0.3.1` (trace-to-dataset curation), the three session-store packages at **0.3.1** (run-ledger `promptVersion` provenance), and the initial `@arnilo/prism-prompts@0.0.1` (independent opt-in, outside `prism-all`); plan 054 consolidation then folded `@arnilo/prism-browser` and `@arnilo/prism-obscura` into the `@arnilo/prism-web-tools` family as `/browser` and `/obscura` subpaths, folded `@arnilo/prism-rag`, both compaction strategies, `@arnilo/prism-graft`, and `@arnilo/prism-wiki` into the `@arnilo/prism-memory` family as `/rag`, `/compaction/llm`, `/compaction/observational-memory`, `/graft`, and `/wiki` subpaths (deleting the `@arnilo/prism-compaction` profile), and folded all 17 `@arnilo/prism-provider-*` packages into the `@arnilo/prism-providers` family as `/<adapter>` subpaths (Azure/Bedrock/Vertex stop being special all-only manifests); independent publication continues inside `^0.3.0` ranges (which satisfy 0.3.1, 0.3.2, and 0.3.3). This page describes how they are packed, what each tarball contains, how to install them, the required non-optional **caret** `@arnilo/prism@^0.11.0` peer range, the release workflow, and the offline test budget. The measurable 1.0 readiness gates (command-per-gate) live in [`0.1.0-readiness.md`](history/./0.1.0-readiness.md).
|
|
9
9
|
|
|
10
10
|
Core `@arnilo/prism` ships runtime, CLI, templates, and docs. Every code package has a required `@arnilo/prism` peer inside the Decision B window — the caret current spec is `@arnilo/prism@^0.3.3` and every declared window peer satisfies it: packages republishing in the plan 050 cut carry `^0.3.2`; the plan 039 set keeps `^0.3.1`; unchanged packages keep their `^0.3.0` peer; profiles are pure manifests. The plan 050 republished set declares the required `@arnilo/prism@^0.3.2` peer; the plan 041-044 republished set keeps its existing `^0.3.0` window peer; unchanged packages keep their prior window. Installation activates no provider, listener, database, browser, credential, or tool capability.
|
|
11
11
|
|
|
12
|
-
The **0.
|
|
12
|
+
The **0.11.0 lockstep cut** moved all **twelve** manifests together. The current declared peer is `@arnilo/prism@^0.11.0` on every package, and `release.mjs` lockstep mode fails closed on any internal range that merely satisfies the cut version instead of matching it. The **0.6.0–0.10.0 lockstep cuts** each moved their then-current manifest set together (`@arnilo/prism-hooks` arrived in the 0.10.0 cut). The independent-publication history above (0.3.x, 0.4.x, 0.5.x) describes how the line grew when packages moved separately.
|
|
13
13
|
|
|
14
14
|
<!-- generated:package-truth:inventory begin -->
|
|
15
|
-
**
|
|
15
|
+
**12 publishable manifests** — root `@arnilo/prism` plus 11 workspace packages (4 `prism-*` family packages, 7 capability packages). Generated by `node scripts/package-truth.mjs --emit-docs` — do not hand-edit.
|
|
16
16
|
|
|
17
17
|
| package | version | notes |
|
|
18
18
|
| --- | --- | --- |
|
|
19
|
-
| `@arnilo/prism` | 0.
|
|
20
|
-
| `@arnilo/prism-channels` | 0.
|
|
21
|
-
| `@arnilo/prism-coding-tools` | 0.
|
|
22
|
-
| `@arnilo/prism-core` | 0.
|
|
23
|
-
| `@arnilo/prism-providers` | 0.
|
|
24
|
-
| `@arnilo/prism-acp-agent` | 0.
|
|
25
|
-
| `@arnilo/prism-ag-ui` | 0.
|
|
26
|
-
| `@arnilo/prism-
|
|
27
|
-
| `@arnilo/prism-
|
|
28
|
-
| `@arnilo/prism-
|
|
29
|
-
| `@arnilo/prism-
|
|
19
|
+
| `@arnilo/prism` | 0.11.0 | core — runtime, CLI/RPC, templates, docs |
|
|
20
|
+
| `@arnilo/prism-channels` | 0.11.0 | family — transport-neutral messaging runtime, durable journal, pairing and one-use approvals; official /telegram (private DMs, opt-in granted groups/topics) and experimental pinned signal-cli /signal |
|
|
21
|
+
| `@arnilo/prism-coding-tools` | 0.11.0 | family — /agent, /security, /openapi, /computer-use-linux, /dev, /impeccable subpaths |
|
|
22
|
+
| `@arnilo/prism-core` | 0.11.0 | family — /runtime, /sessions, /governance, /credentials, /enterprise, /validation subpaths |
|
|
23
|
+
| `@arnilo/prism-providers` | 0.11.0 | family — all provider adapters as `/<adapter>` subpaths |
|
|
24
|
+
| `@arnilo/prism-acp-agent` | 0.11.0 | capability — ACP adapter |
|
|
25
|
+
| `@arnilo/prism-ag-ui` | 0.11.0 | capability — AG-UI/A2A/A2UI adapter |
|
|
26
|
+
| `@arnilo/prism-hooks` | 0.11.0 | capability — Claude/Codex-compatible hooks.json adapter compiled onto middleware, guardrail, injector, and stop-hook seams |
|
|
27
|
+
| `@arnilo/prism-mcp` | 0.11.0 | capability — MCP client/server/OAuth interop |
|
|
28
|
+
| `@arnilo/prism-memory` | 0.11.0 | capability — memory plus /rag, /compaction/*, /fabric, /wiki subpaths |
|
|
29
|
+
| `@arnilo/prism-web-tools` | 0.11.0 | capability — Brave/Exa/Firecrawl plus peer-gated /browser and /obscura subpaths |
|
|
30
|
+
| `@arnilo/prism-work` | 0.11.0 | capability — /connectors, /documents, /sheets, /diagrams, /document-reader, /sandbox, /skills, /tools subpaths |
|
|
30
31
|
<!-- generated:package-truth:inventory end -->
|
|
31
32
|
|
|
32
33
|
|
|
@@ -35,30 +36,30 @@ The **0.9.0 lockstep cut** moved all **eleven** manifests together: the current
|
|
|
35
36
|
|
|
36
37
|
| adapter package | version |
|
|
37
38
|
| --- | --- |
|
|
38
|
-
| `@arnilo/prism-providers/ai-sdk` | 0.
|
|
39
|
-
| `@arnilo/prism-providers/alibaba` | 0.
|
|
40
|
-
| `@arnilo/prism-providers/anthropic` | 0.
|
|
41
|
-
| `@arnilo/prism-providers/azure` | 0.
|
|
42
|
-
| `@arnilo/prism-providers/bedrock` | 0.
|
|
43
|
-
| `@arnilo/prism-providers/clinepass` | 0.
|
|
44
|
-
| `@arnilo/prism-providers/commandcode` | 0.
|
|
45
|
-
| `@arnilo/prism-providers/deepseek` | 0.
|
|
46
|
-
| `@arnilo/prism-providers/google` | 0.
|
|
47
|
-
| `@arnilo/prism-providers/hyper` | 0.
|
|
48
|
-
| `@arnilo/prism-providers/kimi` | 0.
|
|
49
|
-
| `@arnilo/prism-providers/model-discovery` | 0.
|
|
50
|
-
| `@arnilo/prism-providers/neuralwatt` | 0.
|
|
51
|
-
| `@arnilo/prism-providers/ollama` | 0.
|
|
52
|
-
| `@arnilo/prism-providers/openai` | 0.
|
|
53
|
-
| `@arnilo/prism-providers/opencode-go` | 0.
|
|
54
|
-
| `@arnilo/prism-providers/openrouter` | 0.
|
|
55
|
-
| `@arnilo/prism-providers/vertex` | 0.
|
|
56
|
-
| `@arnilo/prism-providers/xai` | 0.
|
|
57
|
-
| `@arnilo/prism-providers/zai` | 0.
|
|
39
|
+
| `@arnilo/prism-providers/ai-sdk` | 0.11.0 |
|
|
40
|
+
| `@arnilo/prism-providers/alibaba` | 0.11.0 |
|
|
41
|
+
| `@arnilo/prism-providers/anthropic` | 0.11.0 |
|
|
42
|
+
| `@arnilo/prism-providers/azure` | 0.11.0 |
|
|
43
|
+
| `@arnilo/prism-providers/bedrock` | 0.11.0 |
|
|
44
|
+
| `@arnilo/prism-providers/clinepass` | 0.11.0 |
|
|
45
|
+
| `@arnilo/prism-providers/commandcode` | 0.11.0 |
|
|
46
|
+
| `@arnilo/prism-providers/deepseek` | 0.11.0 |
|
|
47
|
+
| `@arnilo/prism-providers/google` | 0.11.0 |
|
|
48
|
+
| `@arnilo/prism-providers/hyper` | 0.11.0 |
|
|
49
|
+
| `@arnilo/prism-providers/kimi` | 0.11.0 |
|
|
50
|
+
| `@arnilo/prism-providers/model-discovery` | 0.11.0 |
|
|
51
|
+
| `@arnilo/prism-providers/neuralwatt` | 0.11.0 |
|
|
52
|
+
| `@arnilo/prism-providers/ollama` | 0.11.0 |
|
|
53
|
+
| `@arnilo/prism-providers/openai` | 0.11.0 |
|
|
54
|
+
| `@arnilo/prism-providers/opencode-go` | 0.11.0 |
|
|
55
|
+
| `@arnilo/prism-providers/openrouter` | 0.11.0 |
|
|
56
|
+
| `@arnilo/prism-providers/vertex` | 0.11.0 |
|
|
57
|
+
| `@arnilo/prism-providers/xai` | 0.11.0 |
|
|
58
|
+
| `@arnilo/prism-providers/zai` | 0.11.0 |
|
|
58
59
|
<!-- generated:package-truth:providers end -->
|
|
59
60
|
|
|
60
61
|
|
|
61
|
-
Core ships `dist`, docs, templates, and `CHANGELOG.md`; code packages ship compiled output, README, license, and changelog. Family/profile packages ship manifest, README, and changelog. `@arnilo/prism-providers` is the unified provider family: all provider adapters ship as `dist/<adapter>` subpaths in one tarball (Azure/Bedrock/Vertex included), with the required `@arnilo/prism` peer as the only dependency and `@ai-sdk/provider` an optional peer of `/ai-sdk`. `@arnilo/prism-core` provides the unified runtime, sessions, governance, credentials, and enterprise persistence family package. `@arnilo/prism-channels` provides the transport-neutral messaging runtime, durable journal and pairing surface; it has only the required `@arnilo/prism` peer, while its SQLite/PostgreSQL conformance drivers are development-only. `@arnilo/prism-web-tools` provides the unified web tools family: root Brave/Exa/Firecrawl research tools plus `/browser` (Playwright-peer gated) and `/obscura` (host-binary + MCP gated) subpaths. `@arnilo/prism-memory` provides the unified memory and context family: root working/vector memory plus `/rag` (with `/rag/loaders` and `/rag/parsers`), `/compaction/llm`, `/compaction/observational-memory`,
|
|
62
|
+
Core ships `dist`, docs, templates, and `CHANGELOG.md`; code packages ship compiled output, README, license, and changelog. Family/profile packages ship manifest, README, and changelog. `@arnilo/prism-providers` is the unified provider family: all provider adapters ship as `dist/<adapter>` subpaths in one tarball (Azure/Bedrock/Vertex included), with the required `@arnilo/prism` peer as the only dependency and `@ai-sdk/provider` an optional peer of `/ai-sdk`. `@arnilo/prism-core` provides the unified runtime, sessions, governance, credentials, and enterprise persistence family package. `@arnilo/prism-channels` provides the transport-neutral messaging runtime, durable journal and pairing surface; it has only the required `@arnilo/prism` peer, while its SQLite/PostgreSQL conformance drivers are development-only. `@arnilo/prism-web-tools` provides the unified web tools family: root Brave/Exa/Firecrawl research tools plus `/browser` (Playwright-peer gated) and `/obscura` (host-binary + MCP gated) subpaths. `@arnilo/prism-memory` provides the unified memory and context family: root working/vector memory plus `/rag` (with `/rag/loaders` and `/rag/parsers`), `/compaction/llm`, `/compaction/observational-memory`, and `/wiki` subpaths, including the `prism-wiki` bin and bundled skills. `@arnilo/prism-coding-tools/dev` ships the loopback dev inspector — the `prism-dev` bin, the `prism dev` CLI composition, and the `/dev/cli` export the core CLI delegates to for `prism dev` (plan 040 Tasks 4–5); dev tooling is developer-time only and must never be the production API boundary. `@arnilo/prism-core/governance/prompts` (plan 042) is the versioned prompt registry: an explicit host opt-in with no first-party package depending on it — unlike `@arnilo/prism-memory` (a family member) and `@arnilo/prism-core/governance/evals` (used by the promotion helper as an optional peer). `@arnilo/prism-work` is the unified work family: `/connectors`, `/documents`, `/sheets`, `/diagrams`, `/document-reader`, `/sandbox`, `/skills`, and `/tools` subpaths in one tarball. Office dependencies are exact-pinned; `/document-reader` keeps `mammoth` and `pdf-parse` optional and fail-closed, while `playwright-core` remains a devDependency for gated draw.io testing. Importing `/connectors` never evaluates document dependencies.
|
|
62
63
|
|
|
63
64
|
## When to use it
|
|
64
65
|
|
|
@@ -90,7 +91,6 @@ Consumers install the core package for the runtime and add first-party packages
|
|
|
90
91
|
| Install Obscura browser-engine tools (host supplies the binary; `/obscura`) | `npm install @arnilo/prism @arnilo/prism-web-tools @arnilo/prism-mcp` |
|
|
91
92
|
| Install RAG retrieval (memory family `/rag`) | `npm install @arnilo/prism @arnilo/prism-memory` |
|
|
92
93
|
| Install the Wiki CLI and skills (memory family `/wiki`) | `npm install @arnilo/prism @arnilo/prism-memory` (`npx prism-wiki --help`) |
|
|
93
|
-
| Install the Graft context-graph bridge (`/graft`, host supplies the CLI) | `npm install @arnilo/prism @arnilo/prism-memory` (+ host-installed `@nanonets/graft`) |
|
|
94
94
|
| Install work connectors | `npm install @arnilo/prism @arnilo/prism-work` (import `@arnilo/prism-work/connectors`) |
|
|
95
95
|
| Install document/spreadsheet/presentation engine | `npm install @arnilo/prism @arnilo/prism-work` (import `@arnilo/prism-work/documents`) |
|
|
96
96
|
| Install spreadsheet and CSV data engine | `npm install @arnilo/prism @arnilo/prism-work` (import `@arnilo/prism-work/sheets`) |
|
|
@@ -103,6 +103,8 @@ Consumers install the core package for the runtime and add first-party packages
|
|
|
103
103
|
| Local mirror of the release verify gate | `npm run release:dry-run` |
|
|
104
104
|
| Validate independent versions/ranges and reject registry collisions | `npm run release:check -- --allow-dirty --allow-untagged` |
|
|
105
105
|
| Preview deterministic changed-package publication | `npm run release:publish -- --dry-run --allow-dirty --allow-untagged` |
|
|
106
|
+
| Verify published artifacts from the registry (post-publish smoke) | `npm run post-publish:smoke` |
|
|
107
|
+
| Run the same smoke against local `npm pack` tarballs (pre-publish parity) | `npm run post-publish:smoke -- --local` |
|
|
106
108
|
| Resume interrupted package-tag publication | `npm run release:publish -- --resume --report release-artifacts/publish-report.json` |
|
|
107
109
|
| Protected PostgreSQL enterprise suite | `PRISM_TEST_POSTGRES_URL="$DATABASE_URL" npm run test:postgres` |
|
|
108
110
|
| Full SDK readiness gate (typecheck + offline tests + pack) | `npm run sdk:ready` |
|
|
@@ -150,13 +152,13 @@ A packed tarball contains only public compiled output and release files:
|
|
|
150
152
|
- Code packages ship `README.md`, `LICENSE`, and `CHANGELOG.md`; family/profile packages ship `README.md` and `CHANGELOG.md`.
|
|
151
153
|
- The core tarball additionally ships the full `docs/` directory (the docs hub), `templates/init/`, and the `templates/` gallery (e.g. `deep-research`) used by `prism init`.
|
|
152
154
|
- `dist/cli.js` and the `bin` link in core.
|
|
153
|
-
- **Tarball filenames.** npm strips the `@scope/` prefix, so the core package `@arnilo/prism` produces a tarball named `arnilo-prism-0.
|
|
155
|
+
- **Tarball filenames.** npm strips the `@scope/` prefix, so the core package `@arnilo/prism` produces a tarball named `arnilo-prism-0.11.0.tgz`; family packages produce `arnilo-prism-core-0.11.0.tgz`, `arnilo-prism-coding-tools-0.11.0.tgz`, `arnilo-prism-providers-0.11.0.tgz` (all 20 adapters inside), `arnilo-prism-channels-0.11.0.tgz`, `arnilo-prism-memory-0.11.0.tgz`, and `arnilo-prism-web-tools-0.11.0.tgz`; capability packages like `arnilo-prism-hooks-0.11.0.tgz`, `arnilo-prism-mcp-0.11.0.tgz`, and `arnilo-prism-work-0.11.0.tgz` carry their own package version. Independent-package tags carry their own version. The CLI bin name `prism` is unaffected by the package name (`npx prism` still works; npm allows the bin field to differ from the package name).
|
|
154
156
|
|
|
155
157
|
Excluded from every tarball by `files` negation:
|
|
156
158
|
|
|
157
159
|
- `dist/__tests__/` — compiled tests and the meta-tests (`packaging.test.js`, `install-smoke.test.js`, `docs.test.js`, `network-free-guard.test.js`, and the phase boundary tests).
|
|
158
160
|
- `dist/**/*.map` — source maps. Source maps are still emitted locally (`tsconfig` `sourceMap: true`) for debugging; the `!dist/**/*.map` line is the **map-retention knob**: remove that negation to ship source maps in releases.
|
|
159
|
-
- `src/`, `plans/`, `.agents/`, `.github/`, `tsconfig*.json`, `roadmap.md`, and `
|
|
161
|
+
- `src/`, `plans/`, `.agents/`, `.github/`, `tsconfig*.json`, `roadmap.md`, and `bun.lock` are never packed (outside the `files` whitelist and/or explicitly ignored).
|
|
160
162
|
|
|
161
163
|
`sideEffects` is `false` for every first-party package (their entrypoints export only types and declarations). Core sets `sideEffects: ["dist/cli.js"]` because `src/cli.ts` runs the CLI and sets `process.exitCode` at import time; every other core entrypoint is side-effect-free.
|
|
162
164
|
|
|
@@ -251,13 +253,13 @@ Frozen by Phase 12 Task 0 in `scripts/phase12-freeze-manifest.json` (schema gate
|
|
|
251
253
|
|
|
252
254
|
| Runtime | Supported | Measured in CI |
|
|
253
255
|
| --- | --- | --- |
|
|
254
|
-
| Node | 22, 24 (`engines.node >=22`) | `verify` runs the full `sdk:ready` gate on Node 24; `node22-compat` builds and imports every public root `exports` target on Node 22. Node 20 support was dropped in 0.6.0 (`dev-006`; Node 20 reached upstream end-of-life 2026-04-30); 0.
|
|
256
|
+
| Node | 22, 24 (`engines.node >=22`) | `verify` runs the full `sdk:ready` gate on Node 24; `node22-compat` builds and imports every public root `exports` target on Node 22. Node 20 support was dropped in 0.6.0 (`dev-006`; Node 20 reached upstream end-of-life 2026-04-30); 0.11.0 keeps the same floor. |
|
|
255
257
|
| PostgreSQL | 16 (`pgvector/pgvector:pg16`) | `postgres-integration` service container |
|
|
256
258
|
|
|
257
259
|
## Extension and configuration notes
|
|
258
260
|
|
|
259
261
|
|
|
260
|
-
- **Required `@arnilo/prism` peer.** Every first-party code package declares a non-optional **caret** `@arnilo/prism@^0.
|
|
262
|
+
- **Required `@arnilo/prism` peer.** Every first-party code package declares a non-optional **caret** `@arnilo/prism@^0.11.0` peer (the lockstep 0.11.0 cut rewrote every internal range; the version-literal gate rejects a declared range that only satisfies the cut version) (`peerDependenciesMeta` must not mark `@arnilo/prism` optional; other peers such as `playwright-core` may be optional). **Peer-version policy (plan 030, Decision B — independent packages):** internal ranges stay inside the caret window of the cut they shipped in, so a package may patch independently while consumers remain on a compatible 0.x line. A package outside that window is refused by the release gate until the next coordinated peer bump. Inside the workspace each package also declares `"@arnilo/prism": "file:../.."` in `devDependencies` so `npm install` resolves the peer locally; that devDependency is stripped from consumer installs and is not a runtime dependency.
|
|
261
263
|
- **Public access.** All 56 manifests (root + 55 workspace packages: 49 code packages + 6 pure-manifest family/profile packages — the 10 `prism-*` family/profile set is the 6 pure-manifest profiles plus the 4 code packages `prism-caveman`, `prism-impeccable`, `prism-openapi-tools`, `prism-ponytail`) declare `"publishConfig": { "access": "public" }`; the publisher also passes `--access public` explicitly because scoped packages otherwise default to restricted on first publish.
|
|
262
264
|
- **Shipped vs repository docs.** The npm tarball ships `docs/` pages linked from `docs/index.md` (public API, security, migration, providers, install). It excludes `docs/_evidence/` (per-phase evidence freezes, including `release-0.2.7-evidence.md`), `docs/release-*-evidence.md`, and `docs/api-page-template.md`. Those files remain in git for audit. `dist/__tests__` and `*.map` stay excluded.
|
|
263
265
|
- **Map retention knob.** Source maps are emitted locally but stripped from tarballs by `!dist/**/*.map`. Removing that `files` negation ships maps in releases (larger tarballs, better consumer stack traces).
|
|
@@ -282,8 +284,8 @@ Frozen by Phase 12 Task 0 in `scripts/phase12-freeze-manifest.json` (schema gate
|
|
|
282
284
|
## Security and performance notes
|
|
283
285
|
|
|
284
286
|
|
|
285
|
-
- **Export-count budget.** `scripts/budget-gate.test.mjs` counts each publishable package's public exports (same name classes as `scripts/dead-exports.mjs`) and fails CI when any exceed the `exportCounts` ceilings in `scripts/budgets.json`; the failure names the package and the exact delta.
|
|
286
|
-
- **Artifact diet.** The same gate re-packs the root tarball and fails if packed bytes, unpacked bytes, or file count exceed `scripts/budgets.json#root` + 5%; the 0.9.0 pre-release baselines
|
|
287
|
+
- **Export-count budget.** `scripts/budget-gate.test.mjs` counts each publishable package's public exports (same name classes as `scripts/dead-exports.mjs`) and fails CI when any exceed the `exportCounts` ceilings in `scripts/budgets.json`; the failure names the package and the exact delta. The 0.10.0 cut carries `@arnilo/prism` 1456 and `@arnilo/prism-memory` 934 (the 0.9.0 pre-release baselines were 1445 and 892, plan 099 Task 0 measured 2026-09-19; the +11 root raise is logged for plans 103, 104, and 106 and the +42 memory raise for plans 102 and 105, per-entry in `scripts/budgets.json#exportCounts`), and every other ceiling is unchanged since its recorded rebaseline. Each raise carries its measured value and the plans that caused it, and `docs/_evidence/phase54-package-map.md` records the same per-package count in its Budget-Gated Exports column. Growth requires removing exports or rebaselining with a recorded reason.
|
|
288
|
+
- **Artifact diet.** The same gate re-packs the root tarball and fails if packed bytes, unpacked bytes, or file count exceed `scripts/budgets.json#root` + 5%; the 0.9.0 pre-release baselines were 1414295 packed / 4647338 unpacked / 533 files (measured 2026-09-19, plan 099 Task 0). The 0.11.0 cut raised packed bytes to 1486492 (measured 2026-09-24); unpacked stayed inside that band and file count measured 534. Tests, fixtures, plans, scripts, `src/`, and `docs/_evidence/**` stay out of the pack (plan 026 rule), and every page linked from shipped `docs/index.md` must be present.
|
|
287
289
|
- **No secrets or fixtures in tarballs.** Tests, fixtures, `src/`, `plans/`, `.agents/`, `roadmap.md`, and `tsconfig` files are excluded. The `docs avoid real-looking secret examples` docs check and the packaging guard's deny list prevent secret-bearing fixtures from shipping.
|
|
288
290
|
- **Live tests stay opt-in.** The default `npm test` is network-free by construction and never sets these vars. Provider/compaction live gates stay credential-gated and are not set by default or during `sdk:ready`. The PostgreSQL adapter live matrix is the exception that runs in CI via the dedicated `postgres-integration` job (still skipped in the default suite).
|
|
289
291
|
- `PRISM_LIVE_PROVIDER_TESTS=1` — gates the eight provider packages' `src/__tests__/live.test.ts` (`@arnilo/prism-providers/anthropic`, `provider-google`, `provider-openai`, `provider-opencode-go`, `provider-openrouter`, `provider-zai`, `provider-kimi`, `provider-neuralwatt`). Each provider live test also requires its own API key env var and skips safely when it is missing:
|
|
@@ -306,13 +308,13 @@ Frozen by Phase 12 Task 0 in `scripts/phase12-freeze-manifest.json` (schema gate
|
|
|
306
308
|
- `PRISM_TEST_KEYCHAIN=1` — gates `@arnilo/prism-core/credentials/node` system-keychain round-trips (requires a working OS keychain backend; skipped by default).
|
|
307
309
|
- Provider live tests read the API key from the env only when both gates are set; the key is used as a bearer token and never logged. `assertNoSecretLeak` verifies the key value does not appear in any streamed event. The compaction placeholders still carry no real credentials.
|
|
308
310
|
- Enforced by `network-free-guard.test.ts` (default suite stays network-free) and by source-scanning meta-tests that assert each `live.test.ts` keeps its `skip:` guard.
|
|
309
|
-
- **Supply-chain workflows.** `.github/workflows/security.yml` runs CodeQL JavaScript/TypeScript SAST, PR-only dependency review, `
|
|
311
|
+
- **Supply-chain workflows.** `.github/workflows/security.yml` runs CodeQL JavaScript/TypeScript SAST, PR-only dependency review, `bun audit --audit-level=moderate`, SPDX 2.3 generation, exact license allow/deny policy, tracked-source plus unpacked-tarball credential-pattern scans, and seven-day SBOM retention. Dependabot opens bounded weekly npm and GitHub Actions updates. Every third-party action uses a full immutable revision; workflows never use `pull_request_target`. GitHub repository secret scanning/push protection and required-check branch rules remain repository settings because GitHub provides no equivalent checked-in workflow toggle; enable `security / codeql`, `security / supply-chain`, PR dependency review, and release checks on protected branches.
|
|
310
312
|
- **Sandbox/browser protected workflow.** `.github/workflows/sandbox-browser.yml` is scheduled/manual only in protected `sandbox-browser` environment. It runs network-free adversarial eval fixtures by default, optionally enables digest-pinned Docker and Playwright gates via repository variables (`PRISM_TEST_DOCKER_IMAGE`, `PRISM_ENABLE_PLAYWRIGHT_GATE`), plus Obscura (`PRISM_ENABLE_OBSCURA_GATE`, `PRISM_OBSCURA_BIN`) and draw.io (`PRISM_ENABLE_DRAWIO_GATE`, `PRISM_TEST_DRAWIO_URL`) legs whose suites live in `@arnilo/prism-web-tools` and `@arnilo/prism-work` — the draw.io leg runs through `scripts/live-matrix.mjs` with `PRISM_LIVE_FILTER=work/drawio-live`; it receives no provider/npm/OIDC secrets, and uploads only a redacted aggregate status artifact (7-day retention).
|
|
311
313
|
- **Release attestations.** Tag publication uses GitHub OIDC with only `contents: read`, `id-token: write`, and `attestations: write` at the publish job. `actions/attest-build-provenance` attests every `.tgz` and `sbom.spdx.json` before npm publication; npm still receives `--provenance`. Verify downloaded attestations with GitHub CLI and npm signatures on the release host.
|
|
312
|
-
- **Install smoke is offline.** The install-smoke test packs core + every package into a temp dir and installs tarballs with `--offline --no-audit --no-fund` into a fresh project. External dependencies are satisfied from the lockfile-backed npm cache prepared by `
|
|
314
|
+
- **Install smoke is offline.** The install-smoke test packs core + every package into a temp dir and installs tarballs with `--offline --no-audit --no-fund` into a fresh project. External dependencies are satisfied from the lockfile-backed npm cache prepared by `bun ci`; any attempted uncached registry fetch fails the gate.
|
|
313
315
|
- **Packed-install e2e journeys (plan 012 Task 3).** `scripts/e2e-enterprise-journey.test.mjs` and `scripts/e2e-coding-journey.test.mjs` pack the first-party packages for their journey, install the exact tarballs into a fresh consumer project, and run the journey script inside that consumer — public exports only, no workspace-relative resolution (asserted per run). The **enterprise journey** composes OIDC identity → OPA policy decision (durable ledger) → agent run with durable events (memory, or real PostgreSQL when `PRISM_TEST_POSTGRES_URL` is set **and the `pg` peer is installed in that consumer**) → batched approval → OpenAPI side effect with idempotency → artifact upload + signed delivery, with policy-deny and hash-mismatch fail-closed injections. The durable leg is peer-gated and never assumed from the env alone: with `PRISM_TEST_POSTGRES_URL` ambient but `pg` unresolvable (the default, since `pg` is a peer of `@arnilo/prism-core` and the consumer installs only Prism tarballs), the fixture prints `SKIP durable postgres leg: …` and runs the memory event source instead of dying with `ERR_MODULE_NOT_FOUND`; the test reports that line as a TAP diagnostic and asserts it, so the skip can never be silent. Scope the env to the phase that needs it (`PRISM_TEST_POSTGRES_URL=… npm run test:postgres`, or `phase release:gate` in `.github/workflows/release.yml`) rather than exporting it globally. The **coding journey** composes an ACP editor session (init capability negotiation, session new + load/resume) → bounded coding tools (git-aware list/search, glob, read-before-write write, delete, move) → sandboxed process session → forge handoff with idempotent PR creation, with execution-policy and read-before-write denial paths. Each fixture asserts the installed version matches the packed manifest graph and stays within the frozen `e2eJourneyFixtureMsCeiling` (120 s in `scripts/phase12-freeze-manifest.json`).
|
|
314
316
|
- **Protected restart-recovery leg (plan 012 Task 4).** `scripts/phase12-restart-recovery.test.mjs` (run by `npm run test:postgres` after the Phase 7 suite) spawns two real processes against one PostgreSQL schema: replica A runs a durable agent, suspends on a batched tool approval, appends durable events and is then SIGKILLed by the driver; replica B reconnects and resumes. Operators re-run the leg with `PRISM_TEST_POSTGRES_URL="postgresql://…" npm run test:postgres` against a disposable PostgreSQL 16 (e.g. `pgvector/pgvector:pg16`). Without the URL the gate records a named `BLOCKED GATE` failure instead of skipping. Reconnect p95 and 16-worker append contention p95 are asserted against the frozen `reconnectP95Ms` / `pointOpP95Ms` ceilings; set `PRISM_PHASE12_RECORD_EVIDENCE=1` to refresh the checked-in evidence file `scripts/phase12-restart-recovery.json`.
|
|
315
|
-
- **Offline test budget.** The default `npm test` (no `PRISM_LIVE_PROVIDER_TESTS`) is pinned at **<
|
|
317
|
+
- **Offline test budget.** The default `npm test` (no `PRISM_LIVE_PROVIDER_TESTS`) is pinned at **< 110s** with a measured local baseline of **~92s** (one chain sum ~92s on this host, including the Node branch-coverage audit at ~20s; the pre-audit baseline was ~72s in `docs/_evidence/phase115-suite-budget.md`). The gate stage's former 33 s critical path is split across `scripts/phase54-legacy-registry-{dry-run,apply,fail-closed}.test.mjs` so worker files overlap, and the workspace stage runs one `npm` process per package two at a time, each taking `scripts/with-build-lock.mjs --shared` (readers overlap; `tsc` keeps the exclusive lock). Every stage except the SQLite suites runs on `node --test`; the SQLite suites use `bun test --timeout=0` (the only file set the Bun 1.4.2 inventory measured `bun-ok` and faster than Node), while `npm run test:coverage` measures with `bun test --coverage` (plan 114 moved the instrument to Bun) and the PostgreSQL TAP leg stays on Node. Plan 057 retired the historical `phase11-freeze` … `phase34-freeze`/`phase30-release` gate files from the default suite (17 files, 247 tests) — they stay in the repo as immutable release evidence and remain audit-runnable standalone via `node --test scripts/<file>.test.mjs`, with their self-wiring assertions flipped to assert non-wiring so the retirement cannot silently regress. Their criteria are content-verified, not filesystem-verified (plan 071 Tasks 5 and 10): capture ordering compares the baselines' recorded `captured` dates instead of file mtimes, phase markers resolve against the live file plus the frozen lineage (the phase plan of record and `docs/history/`) so a living doc that is rewritten between releases cannot fail an audit gate (a marker set that is checked this way is declared in the gate's own manifest — `phase26-freeze-manifest.json` `lineageCheckedFiles` — so the fallback stays bounded to the files that need it), a path retired since the freeze resolves to its recorded archive instead of being hash-compared (`status: "retired"` + `retiredTo` in the baseline, with the Task 0 `sha256` kept as history), and a version literal is asserted as a *transition* (`!`-prefixed marker: the pre-cut literal must be gone) rather than an equality that the next release sweep would invalidate. Release/security gates (`release-gate`, `tooling-gate`, `budget-gate`, `phase23-quality-gates`, `phase8–11` conformance) stay in the run. The full CI `sdk:ready` gate runs on Node 24 because docs tests execute 38 `examples/*.ts` demos via native TypeScript stripping; `scripts/examples-execution.test.mjs` spawns the rest or records a manifest skip. `npm run sdk:ready` also runs typecheck, pack dry-run, and the coverage summary, so it is allowed to exceed the `npm test` budget while remaining network-free. `npm run test:coverage` additionally runs the combined coverage summary (`npm run coverage:summary`, measured ~66s local on 16 cores: core + each workspace suite once under `bun test --coverage`; the whole `test:coverage` stage is **~137s**, measured 136.8/137.1 s after plan 115 Task 6, up from ~128s under the Node instrument because the core suite is ~1.8× slower under Bun). The stage runs the core suite once and hands that run to the summary: `test:coverage` captures the first `bun test --coverage` run's output and exit code, and `coverage-summary.mjs` parses that capture for the core row instead of spawning a second ~35 s run (the standalone `coverage-summary` and a non-zero exit code still measure, so the row is never stale), which recovered ~35 s of the ~170s pre-task stage; only the `phase23-coverage` fail-closed gate rerun measures the suite again. The core gate and the per-package lines floors are the hard thresholds. The CI `sdk:ready` step has `timeout-minutes: 30` as a hang backstop; the separate Node 22 compatibility job has `timeout-minutes: 10`. The budget was raised from 30s after the default suite grew to include every first-party package, offline install smoke, packaging guards, docs examples, and workspace tests, from 60s to 80s after the plan 115 Task 2 trims (the critical-path gate file split three ways and the shared-lock concurrent workspace stage) landed the suite at 71.7/73.9 s, then to 110s after plan 120 Task 6 added the Node branch-coverage audit.
|
|
316
318
|
|
|
317
319
|
### GitHub Actions pipeline (0.0.27+)
|
|
318
320
|
|
|
@@ -322,27 +324,27 @@ Frozen by Phase 12 Task 0 in `scripts/phase12-freeze-manifest.json` (schema gate
|
|
|
322
324
|
## Formatting, linting, and coverage
|
|
323
325
|
|
|
324
326
|
|
|
325
|
-
Prism uses one tool for formatting and linting — [Biome](https://biomejs.dev) — configured once at the repo root (`biome.json`) and inherited by every workspace. Coverage uses
|
|
327
|
+
Prism uses one tool for formatting and linting — [Biome](https://biomejs.dev) — configured once at the repo root (`biome.json`) and inherited by every workspace. Coverage uses Bun's built-in test coverage (`bun test --coverage`); there is no third-party coverage service. `npm test` also runs a Node branch-coverage audit (`scripts/branch-coverage-audit.mjs`, `--test-coverage-include=dist/**`, floor **83.49** in `scripts/branch-coverage.test.mjs`); it does not replace the Bun gate, which still records `branches: null`.
|
|
326
328
|
|
|
327
329
|
| Command | What it does |
|
|
328
330
|
| --- | --- |
|
|
329
331
|
| `npm run lint` | `biome lint .` — fails on any lint error (warnings are non-fatal). |
|
|
330
332
|
| `npm run format:check` | `biome format .` — fails if any file is unformatted. |
|
|
331
333
|
| `npm run format` | `biome format --write .` — normalizes formatting in place. |
|
|
332
|
-
| `npm run test:coverage` | `
|
|
334
|
+
| `npm run test:coverage` | `bun test --coverage --timeout=0` over the core suite with the Bun-measured core floors **lines 91.48%**, **functions 92.21%** (measured 94.48 / 95.21 − 3pp; the branch floor is dropped because Bun 1.4.2 emits no branch data), then `scripts/coverage-summary.mjs` + the `phase23-coverage` gate. The first run's output and exit code are captured and handed to the summary (`PRISM_COVERAGE_CORE_*`), so the core row is parsed from the run the contributor already saw — one measurement per stage; the standalone entry point and any non-zero captured exit still measure for themselves. Scoping is `bunfig.toml` `coveragePathIgnorePatterns`: the root file for the core row, one package-local file per gated workspace. |
|
|
333
335
|
|
|
334
|
-
All four gates run inside `npm run sdk:ready` (after `typecheck`, before `pack:dry-run`). A few rules are disabled in `biome.json` because they are false positives for this codebase: `noControlCharactersInRegex` and `noAssignInExpressions` (security/redaction code intentionally matches control characters and uses `while ((m = re.exec(…)))` loops), `noShadowRestrictedNames`, `noThenProperty` (the workflow DSL has a legitimate `then` branch field), `noExplicitAny`, `noVoidTypeReturn`, and `useYield`. Raise the
|
|
336
|
+
All four gates run inside `npm run sdk:ready` (after `typecheck`, before `pack:dry-run`). A few rules are disabled in `biome.json` because they are false positives for this codebase: `noControlCharactersInRegex` and `noAssignInExpressions` (security/redaction code intentionally matches control characters and uses `while ((m = re.exec(…)))` loops), `noShadowRestrictedNames`, `noThenProperty` (the workflow DSL has a legitimate `then` branch field), `noExplicitAny`, `noVoidTypeReturn`, and `useYield`. Raise coverage floors only by re-measuring: the core floors live in `scripts/coverage-thresholds.json` `core` and the per-package rows in the same file; `package.json` carries no numeric threshold.
|
|
335
337
|
|
|
336
338
|
### Coverage denominators and per-package thresholds
|
|
337
339
|
|
|
338
340
|
|
|
339
|
-
Workspace coverage rows used to include the symlinked root core `dist/` (workspace tests `import … from "@arnilo/prism"`, which resolves via `node_modules/@arnilo/prism -> ../..`), diluting every package denominator. Each workspace
|
|
341
|
+
Workspace coverage rows used to include the symlinked root core `dist/` (workspace tests `import … from "@arnilo/prism"`, which resolves via `node_modules/@arnilo/prism -> ../..`), diluting every package denominator. Each workspace row now runs `bun test --coverage` from its own package directory with a package-local `bunfig.toml` (`[test] coveragePathIgnorePatterns = ["../**"]`), so only `packages/<name>/dist/**` counts — Bun 1.4.2 reads bunfig only from the current directory, so the root file cannot scope a workspace row (evidence: `docs/_evidence/phase114-bun-coverage.md` §1.6).
|
|
340
342
|
|
|
341
343
|
| Fact | Value |
|
|
342
344
|
| --- | --- |
|
|
343
|
-
| Workspace
|
|
344
|
-
| Workspace discovery | any `*.test.js` under `packages/<name>/dist/**`, nested layouts included (`@arnilo/prism-acp-agent` builds to `dist/src/__tests__`, `@arnilo/prism-work` to `dist/<area>/__tests__`) — all
|
|
345
|
-
| Per-package gate | `lines >= threshold` from `scripts/coverage-thresholds.json` (recaptured 2026-09-
|
|
345
|
+
| Workspace filter | package-local `bunfig.toml` `[test] coveragePathIgnorePatterns = ["../**"]` per package (the equivalent of the old `--test-coverage-include=dist/**`) |
|
|
346
|
+
| Workspace discovery | any `*.test.js` under `packages/<name>/dist/**`, nested layouts included (`@arnilo/prism-acp-agent` builds to `dist/src/__tests__`, `@arnilo/prism-work` to `dist/<area>/__tests__`) — all 11 workspace packages are measured and artifact keys must match their live manifest names |
|
|
347
|
+
| Per-package gate | `lines >= threshold` from `scripts/coverage-thresholds.json` (recaptured 2026-09-23 under `bun test --coverage` = min of two back-to-back runs − 3pp; the two runs differed by ≤ 0.05pp); functions recorded, not gated, and branches recorded as `null` because Bun 1.4.2 emits no branch data. `phase23-coverage` also fails when a row names a package that is not in the live workspace graph — retired rows must be pruned |
|
|
346
348
|
| Protected exceptions | `@arnilo/prism-core` only (durable postgres/NATS legs need `PRISM_TEST_POSTGRES_URL` / `PRISM_TEST_NATS_URL`); exempt from the gate and reported separately with the reason. Env- or capability-gated legs elsewhere (memory postgres, coding-tools native sandbox, provider live legs) skip as protected skips but their packages stay gated on the measured run |
|
|
347
349
|
| Artifact | `scripts/coverage-summary.json` (gitignored, CI-retained): its package-key set must exactly equal live workspace `package.json` names; each row carries `lines`/`branches`/`functions`/`denominatorFiles`/`threshold`/`pass`/`protectedException` + `belowThreshold`; a row whose child failed additionally carries `status`/`exitCode`/`tail` (a redacted tail of the child's output) |
|
|
348
350
|
| Fail-closed | a non-protected package below its threshold, a suite failure, or a run producing no coverage data exits non-zero; a missing threshold entry is a config error |
|
|
@@ -394,7 +396,7 @@ Timing assertions in tests follow a deterministic-barrier policy: racy wall-cloc
|
|
|
394
396
|
| Timeout | 120s default; override with `PRISM_BUILD_LOCK_TIMEOUT_MS` |
|
|
395
397
|
| Retry | 100ms backoff; stale-PID reclaim via `process.kill(pid, 0)` |
|
|
396
398
|
| Fail-closed | acquisition error or timeout exits non-zero, nothing runs |
|
|
397
|
-
| Wrapped | `build:core`, every workspace `build`, the `node --test` runs in `test
|
|
399
|
+
| Wrapped | `build:core`, every workspace `build`, the `node --test` runs in `test`/workspace tests, the `bun test --coverage` run in `test:coverage`, `coverage-summary.mjs`, the script-gate `node --test` run (the `phase*-conformance`/`phase*-security` gates import `@arnilo/prism` from `dist`) |
|
|
398
400
|
| Not wrapped | `npm run clean` (standalone), `tsc -p examples --noEmit` and workspace `typecheck` (read `dist` `.d.ts`; within any single script the build completes before reads, so only a concurrent external emitter can cause a spurious typecheck error), `scripts/phase23-build-race.test.mjs` (the lock's own regression — it runs unwrapped so its children acquire the real lock) |
|
|
399
401
|
|
|
400
402
|
Directly invoking `tsc` instead of `npm run build` bypasses the lock — use the npm scripts when another build/test could be running in the same tree (CI runs them sequentially).
|
|
@@ -433,14 +435,14 @@ Development type packages track the **declared** engines floor, not the machine
|
|
|
433
435
|
| Public surface import smoke (all 21 root `exports` default targets) | 20.20.2 | ✅ all import cleanly. |
|
|
434
436
|
| Full core test suite | 20.20.2 | 1311/1312 — the single failure is `examples_demos_run_to_completion_and_emit_no_secret`, which executes `examples/*.ts` via Node's native TypeScript stripping (Node 22.6+). This is a test-harness capability, not an SDK runtime incompatibility, and is exactly why CI scopes Node 20 to build + import smoke. |
|
|
435
437
|
|
|
436
|
-
**CI enforcement** (`.github/workflows/release.yml`): the `verify` job runs `npm run sdk:ready` on Node 24; `node22-compat` runs `
|
|
438
|
+
**CI enforcement** (`.github/workflows/release.yml`): the `verify` job runs `npm run sdk:ready` on Node 24; `node22-compat` runs `bun ci`, `npm run build`, and the public-import smoke on Node 22; `supply-chain` runs `bun audit --audit-level=moderate`, SPDX/license checks, SBOM, and source-secret scans; `publish` `needs:` all of `verify`, `node22-compat`, `postgres-integration`, `codeql-release`, and `supply-chain`, so nothing publishes unless every leg — including the audit/SBOM gates — passes.
|
|
437
439
|
|
|
438
440
|
**Process for a major-upgrade PR:** (1) bump exactly one dependency major in its own branch; (2) `npm run sdk:ready` green; (3) packed-install evidence (`npm run pack:dry-run`, or a scratch `npm install <tarball>` import smoke for native deps like `better-sqlite3`); (4) review lockfile churn line-by-line; (5) the `supply-chain` job supplies audit/SBOM; (6) confirm no build-time regression beyond measured noise on the matrix above; (7) merge separately from any feature work.
|
|
439
441
|
|
|
440
442
|
## Release checklist
|
|
441
443
|
|
|
442
444
|
|
|
443
|
-
Every release gate maps to an exact enforcement test or command, so the checklist is executable rather than manual. Run `npm run sdk:ready` for the full local SDK readiness gate: `npm run typecheck`, `npm run lint`, `npm run format:check`, network-free `npm test`, `npm run test:coverage`, `npm run pack:dry-run`, and `npm run release:gate`. `npm run release:dry-run` is an alias for the same gate. The GitHub Actions `verify` job runs `
|
|
445
|
+
Every release gate maps to an exact enforcement test or command, so the checklist is executable rather than manual. Run `npm run sdk:ready` for the full local SDK readiness gate: `npm run typecheck`, `npm run lint`, `npm run format:check`, network-free `npm test`, `npm run test:coverage`, `npm run pack:dry-run`, and `npm run release:gate`. `npm run release:dry-run` is an alias for the same gate. The GitHub Actions `verify` job runs `bun ci` and `npm run sdk:ready` on Node 24; `node22-compat` runs `bun ci`, `npm run build`, and public export imports on Node 22; `postgres-integration` runs the opt-in PostgreSQL adapter suite against a CI Postgres service. Contributor installs use Bun: the committed lockfile is `bun.lock` (JSONC-shaped, read by `scripts/bun-lock.mjs`), `bun ci` is the frozen CI install, and `package-lock.json` is retired; `npm pack` and `npm publish` remain the publish path.
|
|
444
446
|
|
|
445
447
|
| Gate | Enforcement |
|
|
446
448
|
| --- | --- |
|
|
@@ -454,14 +456,15 @@ Every release gate maps to an exact enforcement test or command, so the checklis
|
|
|
454
456
|
| NeuralWatt package/docs/examples release gate | `packaging.test.ts` pins `@arnilo/prism-providers/neuralwatt` package exports/type declarations and `@arnilo/prism-providers` family membership; `docs.test.ts` asserts `docs/index.md` links `providers/neuralwatt.md` and `provider-caching.md`, and that `examples/cache-aware-prompt-assembly.ts` plus `examples/neuralwatt-agent-run.ts` exist and are listed. |
|
|
455
457
|
| Enterprise PostgreSQL package/docs/example gate | Packaging/install/public-contract tests include `@arnilo/prism-core/enterprise/postgres`; `docs.test.ts` pins its API page, four-store migration/ownership/unknown-outcome/async-router guidance, and `examples/enterprise-postgres-state.ts`; `npm run test:postgres` exercises migration, restart, contention, and cleanup with an explicit database URL. |
|
|
456
458
|
| Version graph and resumable publication | `release.test.ts` covers exact package/lock/range validation, topological order, registry collisions, dry-run, interrupted reports/resume, clean tagged git state, provenance/public/tag arguments, and token-safe errors. `release:check` and `release:publish` derive the workspace graph without a manual package list. |
|
|
457
|
-
|
|
|
459
|
+
| Freeze-test current-side package presence | `workspacePackageCounts()` (`scripts/package-truth.mjs`) counts every `packages/*` directory with a manifest. A new workspace package is counted, and the frozen expected side fails until one regen. |
|
|
460
|
+
| Release version claims have one source | `currentVersion()` (`scripts/package-truth.mjs`) reads the root manifest, and `scripts/version-literal-gate.test.mjs` fails when any surface that claims the release version disagrees with it: all 12 manifests, every internal `@arnilo/*` caret range, `bun.lock`, the `src/index.ts` version constant, the `docs/index.md` current-line banner, the `release.yml` tag list, and the generated `scripts/package-truth.json`. Each stale surface is named in one pre-flight (with a positive control for a half-finished cut), and the release-line suites (`packaging.test.ts`, `docs.test.ts`, `release.test.ts`, `phase24-truth`, `phase34-freeze`) derive their expectations from the manifest instead of pinning the cut. |
|
|
458
461
|
| Pre-publish compatibility gates | `release:gate` (in `sdk:ready`) fails on removed/changed `.d.ts` exports vs `scripts/compat-baseline/` (unless `--allow-break` + migration note), version-range/lockfile drift, and tarball deny-list violations (`plans/`, `code-reviews/`, `docs/review-coverage-*`, `*.map`, `__tests__/`); unit-tested in `scripts/release-gate.test.mjs`. |
|
|
459
|
-
| Legacy registry markers (plan 054 Task 7) | `scripts/phase54-legacy-registry.mjs --dry-run` verifies every retired name's final published version exists and `latest` is unchanged, and that each deprecation URL anchor exists in `docs/migrate-to-0.4.md`, without mutating the registry; `--apply --confirm` pre-flights all 54 entries and fails closed (zero mutations) on any mismatch, then idempotently adds the `legacy` dist-tag and `<0.4.0` deprecation warning (already-correct entries skipped; per-entry status in `release-artifacts/legacy-registry-plan.json` for safe resume). `packaging.test.ts` asserts the generated plan covers all 54 retired names with uniform messages and valid guide anchors; the offline fixture suite `scripts/phase54-legacy-registry.test.mjs` proves the dry-run/apply/resume behavior without network or tokens. |
|
|
460
|
-
| Formatting, linting, and coverage thresholds | `npm run lint` and `npm run format:check` run Biome (single root `biome.json`, workspaces inherit) and fail on any lint error or unformatted file; `npm run test:coverage` uses
|
|
462
|
+
| Legacy registry markers (plan 054 Task 7) | `scripts/phase54-legacy-registry.mjs --dry-run` verifies every retired name's final published version exists and `latest` is unchanged, and that each deprecation URL anchor exists in `docs/migrate-to-0.4.md`, without mutating the registry; `--apply --confirm` pre-flights all 54 entries and fails closed (zero mutations) on any mismatch, then idempotently adds the `legacy` dist-tag and `<0.4.0` deprecation warning (already-correct entries skipped; per-entry status in `release-artifacts/legacy-registry-plan.json` for safe resume). `packaging.test.ts` asserts the generated plan covers all 54 retired names with uniform messages and valid guide anchors; the offline fixture suite `scripts/phase54-legacy-registry-{dry-run,apply,fail-closed}.test.mjs` proves the dry-run/apply/resume behavior without network or tokens. |
|
|
463
|
+
| Formatting, linting, and coverage thresholds | `npm run lint` and `npm run format:check` run Biome (single root `biome.json`, workspaces inherit) and fail on any lint error or unformatted file; `npm run test:coverage` uses Bun's built-in `bun test --coverage` with Bun-measured floors (core lines 91.48 / functions 92.21; per-package lines rows in `scripts/coverage-thresholds.json`; no branch floor — Bun 1.4.2 emits no branch data) and no third-party service. All three run inside `sdk:ready`. |
|
|
461
464
|
| Supply-chain and live-canary policy | `supply-chain-security.test.ts` verifies SPDX allow/deny behavior, bounded source/artifact secret detection, credential-free canary reports, timeout/redacted failures, immutable action revisions, no `pull_request_target`, protected live environment, attestation paths, and publish dependency on `supply-chain`; CI adds CodeQL and PR dependency review. |
|
|
462
465
|
| Release secret scan covers the tracked release set | `scripts/phase27-release.test.mjs` enumerates `git ls-files` (plus built `packages/prism-core/dist` when present) and passes that explicit list to the unchanged `scanSecrets` — the same tracked set CI scans (`git ls-files -z \| xargs -0 node scripts/scan-secrets.mjs` in `release.yml`/`security.yml`), so no untracked or gitignored working-tree file can fail the gate or mask a tracked finding; a bare `node scripts/scan-secrets.mjs <path>` still walks everything for pre-commit use except the two local-only credential names `.gitignore` already excludes (`scripts/live.env`, `*.local.env`), and the gate reports the mode, file count, and untracked-but-unignored findings as a note without failing on them. |
|
|
463
466
|
| Workflow and script references resolve | `scripts/workflow-liveness.test.mjs` resolves every `-w <pkg>` / `--workspace <pkg>` target in `.github/workflows/*.yml` against the live workspace inventory and every named npm script against that package's manifest (root scripts included for a bare `npm run x`), resolves every `@arnilo/*` specifier in `scripts/**/*.mjs` against the live package and its `exports` subpaths, and rejects any `uses:` reference that is not a full 40-hex commit SHA — the drift class that left `sandbox-browser.yml` building four retired packages, `scripts/fixtures/phase26-coding-journey.mjs` packing `packages/coding-agent`/`-security` and `scripts/benchmark-scenarios/phase11-auth.mjs` importing `@arnilo/prism-openapi-tools`/`-server` after plan 054 folded them, with a positive control for each. |
|
|
464
|
-
| Network-free + offline test budget | `network-free-guard.test.ts` keeps the default suite network-free; budget pinned `<
|
|
467
|
+
| Network-free + offline test budget | `network-free-guard.test.ts` keeps the default suite network-free; budget pinned `< 110s` (measured baseline above). Install-smoke is offline (`--offline --no-audit --no-fund`, zero registry fetches). |
|
|
465
468
|
| Core security invariants reaffirmed | Runtime/docs tests hold the trust boundary: **no built-in app tools** (hosts register tools; the core ships only the mock provider and contract helpers), **no hidden provider/credential globals** (providers/credentials are host-owned `AgentConfig` fields, resolved via explicit `providerSource`/`CredentialResolver`), **no auto package discovery** (provider/tool/skill packages are opt-in and individually installed; contribution discovery is realpath-contained and emits inert envelopes the host registers), and **no secret persistence in core** (redaction applies before any `RunLedger`/`SessionStore` append; the ledger gate asserts each message event is written exactly once and redacted). |
|
|
466
469
|
|
|
467
470
|
A change that adds a public persistence/runtime surface, a new package, or a new example must extend the matching row's enforcement (add the page to `apiPages`, the package to the `packages` array, or the example to the demos list) so the checklist stays self-maintaining.
|