@arnilo/prism 0.10.0 → 0.11.1
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 +32 -1
- package/README.md +18 -16
- package/dist/agent-run-lifecycle.d.ts +2 -1
- package/dist/agent-run-lifecycle.js +1 -1
- package/dist/agent-session/session/assemble.js +9 -7
- package/dist/agent-session/session/tool-round.js +30 -20
- package/dist/agent-session/session/types.d.ts +1 -0
- package/dist/agent-session/session.d.ts +1 -0
- package/dist/agent-session/session.js +3 -2
- package/dist/checkpoint-restore.d.ts +50 -14
- package/dist/checkpoint-restore.js +104 -28
- package/dist/contracts-core/session.d.ts +2 -1
- package/dist/contracts-run-state.d.ts +12 -4
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/leases.js +32 -6
- 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-limits.d.ts +11 -5
- package/dist/session-stores.js +61 -12
- package/dist/testing/prefix-stability-conformance.d.ts +44 -1
- package/dist/testing/prefix-stability-conformance.js +92 -29
- 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 +7 -6
- package/docs/agent-session-runtime.md +1 -1
- package/docs/coding-agent-tools.md +1 -1
- package/docs/coding-tools.md +7 -11
- package/docs/context-and-skills.md +6 -7
- package/docs/contribution-discovery.md +13 -0
- package/docs/durable-runs.md +10 -3
- package/docs/embeddings.md +3 -1
- package/docs/execution-timeline.md +6 -0
- package/docs/extensions.md +1 -2
- package/docs/impeccable.md +1 -2
- package/docs/index.md +26 -21
- package/docs/live-testing.md +1 -2
- package/docs/memory-fabric.md +3 -2
- package/docs/migrate-to-0.11.md +65 -0
- package/docs/migration.md +12 -1
- package/docs/node-jsonl-session-store.md +4 -3
- package/docs/operations.md +1 -1
- package/docs/peer-dependencies.md +3 -5
- package/docs/policy-and-audit.md +1 -1
- package/docs/prefix-stability-conformance.md +30 -7
- package/docs/provider-packages.md +23 -21
- package/docs/providers/laya.md +113 -0
- package/docs/providers/typesafe.md +145 -0
- package/docs/public-contracts.md +1 -1
- package/docs/rag.md +2 -2
- package/docs/release-and-install.md +60 -58
- package/docs/runs-and-usage.md +6 -4
- package/docs/session-stores.md +2 -2
- package/docs/supervisors.md +14 -6
- package/docs/testing.md +17 -9
- package/docs/workflows.md +2 -2
- package/package.json +5 -4
- package/docs/caveman.md +0 -130
- package/docs/graft.md +0 -149
- package/docs/ponytail.md +0 -129
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# TypeSafe Jev provider package
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
`@arnilo/prism-providers/typesafe` registers the TypeSafe Jev decision model. Jev does not generate free text, stream, or call tools. A request is valid only with `options.structuredOutput`: the JSON schema compiles to System One questions, the messages become the state, and one `POST /v1/systemone` round trip returns typed answers rendered as schema-valid JSON.
|
|
6
|
+
|
|
7
|
+
The package registers provider `typesafe`, models `jev-latest` and `jev-preview`, and an `api_key` auth method through `createExtensionKernel().load([...])`. Versioned pins (`jev-1.13.0`) go through `defineTypeSafeModel`.
|
|
8
|
+
|
|
9
|
+
## When to use it
|
|
10
|
+
|
|
11
|
+
Use it when a host wants a hosted yes/no, pick-one, or rubric decision and already has a JSON object schema. Do not use it as a general chat model, a tool-calling agent, or a streaming text model.
|
|
12
|
+
|
|
13
|
+
## Inputs / request
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import {
|
|
17
|
+
createTypeSafeProvider,
|
|
18
|
+
createTypeSafeProviderPackage,
|
|
19
|
+
defineTypeSafeModel,
|
|
20
|
+
TYPESAFE_API_KEY_ENV,
|
|
21
|
+
TYPESAFE_DEFAULT_BASE_URL,
|
|
22
|
+
typeSafeModels,
|
|
23
|
+
} from "@arnilo/prism-providers/typesafe";
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
| Field | Type | Purpose |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| `apiKey` | `CredentialValueSource` | Bearer token. Hosts wire `TYPESAFE_API_KEY` through the credential seam. The library does not read `process.env`. |
|
|
29
|
+
| `fetch` | `typeof fetch` | Optional fetch for tests and hosts. |
|
|
30
|
+
| `baseUrl` | `string` | Overrides `https://api.typesafe.ai`. Trailing slashes are trimmed. `/v1/systemone` is appended per request. |
|
|
31
|
+
| `id` | `string` | Overrides the provider id (default `typesafe`). |
|
|
32
|
+
| `models` | `readonly ModelConfig[]` | Overrides `typeSafeModels`. |
|
|
33
|
+
| `maxRetries` | `number` | Retries after the first attempt. Default 2. Forwarded to the shared client. |
|
|
34
|
+
|
|
35
|
+
`generate()` accepts a normal `ProviderRequest` with these constraints:
|
|
36
|
+
|
|
37
|
+
| Field | Requirement |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| `options.structuredOutput` | Required. `{ name, schema }`. Missing schema fails before any fetch. |
|
|
40
|
+
| `tools` | Rejected. Decision model, no tool support. Fails before any fetch. |
|
|
41
|
+
| `options.compat.boolean_threshold` | Optional number in `0..1`. Default `0.5`. Out of range fails before any fetch. A boolean is true only when the noul probability is strictly greater than the threshold, so `0.5` renders `false` at the default. |
|
|
42
|
+
| `messages` | State only. A single user text message is sent as a string; otherwise the client sends `[{ role, text }]` and drops non-text parts. Do not write the question into the state — the model judges that text instead of answering it. |
|
|
43
|
+
| `model.model` | Sent verbatim. The responding version in the API body is not rewritten into the output. |
|
|
44
|
+
| `signal` | A signal already aborted throws before fetch. Abort during the round trip rejects the request. |
|
|
45
|
+
|
|
46
|
+
Schema mapping (unsupported shapes throw before any network call and name the field path):
|
|
47
|
+
|
|
48
|
+
| JSON Schema | Question |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| `boolean` | `noul`. Instructions come from `description`, then `title`, then the field path. |
|
|
51
|
+
| string `enum` of 2–255 values | `choice`. Criteria are the option labels unless `x-systemone.options` (or the `x-typesafe` alias) supplies descriptions. |
|
|
52
|
+
| integer `enum` equal to `0..N-1` with 2–10 described levels | `score`. Levels come from `x-systemone.levels` (alias `x-typesafe`), aligned to the declared enum order and sent in numeric order. An undescribed whole-number enum stays a `choice`. |
|
|
53
|
+
| nested object | One question per leaf, id `outer.inner`. |
|
|
54
|
+
| string, unbounded number, array, union, empty object | Rejected. |
|
|
55
|
+
|
|
56
|
+
Extension keys live on the property:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{ "type": "boolean", "x-systemone": { "criteria": { "true": "destructive", "false": "safe" } } }
|
|
60
|
+
{ "type": "string", "enum": ["run", "reject"], "x-systemone": { "options": { "run": "safe", "reject": "destructive" } } }
|
|
61
|
+
{ "type": "integer", "enum": [0, 1, 2], "x-systemone": { "levels": ["opaque", "partial", "actionable"] } }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Limits rejected up front: more than 255 choice options, more than 10 score levels, more than 256 questions. Field names that contain a dot are rejected so dotted nesting stays unambiguous.
|
|
65
|
+
|
|
66
|
+
Curated models declare `capabilities: { input: ["text"], output: ["text"], tools: false, streaming: false, structuredOutput: "json_schema" }`, `limits: { contextWindow: 32000, maxOutputTokens: 0 }`, and `cost: { input: 0.04, output: 0, currency: "USD", unit: "per_million_tokens" }`.
|
|
67
|
+
|
|
68
|
+
## Outputs / response / events
|
|
69
|
+
|
|
70
|
+
| Event | Behavior |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| `message_start` | One, before the answer. |
|
|
73
|
+
| `content_delta` | One text block. The text is `JSON.stringify` of the schema-shaped object. |
|
|
74
|
+
| `usage` | `inputTokens` from the API. `outputTokens` is `0`. `totalTokens` equals `inputTokens`. Omitted when the API sends no usage. |
|
|
75
|
+
| `done` | `stopReason: "end_turn"`, same usage when present. |
|
|
76
|
+
| `error` | Terminal event for gate failures, HTTP failures, and render failures. `401` is not retried. `422` is not retried and the message includes the API field detail. `429` and `5xx` (including `529`) retry, honoring `Retry-After`. |
|
|
77
|
+
|
|
78
|
+
Booleans come from noul vs `boolean_threshold`. Enums come from the choice string (whole-number options stay numbers). Rubric integers are `Math.round` of the score, half rounds up, clamped to the rubric. Nested objects are reassembled from dotted ids. Confidence, probabilities, and score distributions are not copied onto events; the text must stay schema-valid.
|
|
79
|
+
|
|
80
|
+
## Request/response example
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"model": "jev-latest",
|
|
85
|
+
"state": "rm -rf ./build",
|
|
86
|
+
"questions": {
|
|
87
|
+
"verdict": {
|
|
88
|
+
"type": "choice",
|
|
89
|
+
"instructions": "How should this be handled?",
|
|
90
|
+
"criteria": { "run": "run", "reject": "reject", "ask": "ask" }
|
|
91
|
+
},
|
|
92
|
+
"irreversible": { "type": "noul", "instructions": "Would running this destroy data?" }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"model": "jev-1.13.0",
|
|
100
|
+
"answers": {
|
|
101
|
+
"verdict": { "type": "choice", "choice": "ask" },
|
|
102
|
+
"irreversible": { "type": "noul", "noul": 0.91 }
|
|
103
|
+
},
|
|
104
|
+
"usage": { "input_tokens": 96, "output_tokens": 0 }
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Rendered text: `{"verdict":"ask","irreversible":true}`.
|
|
109
|
+
|
|
110
|
+
## Implementation example
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { createEnvCredentialResolver, createExtensionKernel } from "@arnilo/prism";
|
|
114
|
+
import { createTypeSafeProviderPackage, TYPESAFE_API_KEY_ENV } from "@arnilo/prism-providers/typesafe";
|
|
115
|
+
|
|
116
|
+
const kernel = createExtensionKernel();
|
|
117
|
+
await kernel.load([
|
|
118
|
+
createTypeSafeProviderPackage({
|
|
119
|
+
apiKey: createEnvCredentialResolver(process.env, { typesafe: TYPESAFE_API_KEY_ENV }),
|
|
120
|
+
}),
|
|
121
|
+
]);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
A request must carry `options.structuredOutput`. The answer arrives as one text delta.
|
|
125
|
+
|
|
126
|
+
## Extension and configuration notes
|
|
127
|
+
|
|
128
|
+
- Replace the catalog with `models` or register extra pins via `defineTypeSafeModel({ model: "jev-1.13.0" })`. Defaults (capabilities, 32k context, zero output tokens, $0.04/1M input) still apply.
|
|
129
|
+
- `compat.boolean_threshold` is the only request knob. There is no temperature, tool choice, or streaming flag.
|
|
130
|
+
- `baseUrl` points the same client at a proxy. It does not change the wire shape.
|
|
131
|
+
- No model listing runs at setup or during `generate()`.
|
|
132
|
+
|
|
133
|
+
## Security and performance notes
|
|
134
|
+
|
|
135
|
+
- **Egress.** State text and compiled questions leave the process for TypeSafe's hosted API (`https://api.typesafe.ai` unless `baseUrl` overrides it). Do not put secrets in the state.
|
|
136
|
+
- **Credentials.** The bearer token is resolved at the provider edge and redacted from error events. It is never logged.
|
|
137
|
+
- **Cost and latency.** One `POST /v1/systemone` round trip per structured-output request. Questions are free in parallel; state tokens are the cost. Output tokens are not billed (`output: 0`). There is no streaming and no second hop for model discovery.
|
|
138
|
+
- **Fail closed.** Missing schema, tools, an out-of-range threshold, and unsupported fields fail before fetch.
|
|
139
|
+
|
|
140
|
+
## Related APIs
|
|
141
|
+
|
|
142
|
+
- [Laya](laya.md): same wire and schema mapping against a self-hosted `laya-serve`.
|
|
143
|
+
- [Structured output](../structured-output.md): `options.structuredOutput` contract this adapter requires.
|
|
144
|
+
- [Provider packages](../provider-packages.md): registration and auth-method shape.
|
|
145
|
+
- [Credentials and redaction](../credentials-and-redaction.md): `CredentialValueSource` and `createEnvCredentialResolver`.
|
package/docs/public-contracts.md
CHANGED
|
@@ -147,7 +147,7 @@ Important request shapes:
|
|
|
147
147
|
| `PrismManifest` | Data-only package manifest with config defaults, contribution declarations, and resource declarations. |
|
|
148
148
|
| `ProductionPersistenceStore` | Adapter-facing interface for durable, paginated, multi-tenant storage plus optional `checkpoints?: CheckpointStore`, `leases?: LeaseStore`, and `feedback?: RunFeedbackStore`. No SQL/ORM/host file storage/network dependency. |
|
|
149
149
|
| `CheckpointStore` | Generic versioned checkpoint capability: save/load/bounded-list/delete by namespace and key, with ownership, exact-version CAS, and lease fencing. `createMemoryCheckpointStore()` is the reference implementation; it is bounded — `maxRecords` (default 10,000, evicts least-recently-saved) and `maxValueBytes` (default 1 MiB per JSON value). |
|
|
150
|
-
| `LeaseStore` | Atomic acquire/renew/release/get by namespace and key, with opaque claim tokens, expiry, ownership scope, and
|
|
150
|
+
| `LeaseStore` | Atomic acquire/renew/release/get by namespace and key, with opaque claim tokens, expiry, ownership scope, and takeover fences that increase while the row remains. `createMemoryLeaseStore()` sweeps expired rows at 1,024 and an evicted key restarts at fencing 1; durable adapters keep the counter. |
|
|
151
151
|
| `RunFeedbackStore` | Immutable append, bounded owned query, and owned deletion for ratings/comments/tags linked to existing run/trace/evaluation IDs. `createMemoryRunFeedbackStore()` is the reference implementation. |
|
|
152
152
|
| `EventMultiplexer<T>` | Generic bounded fan-in from async sources. `createEventMultiplexer()` owns queue limits, overflow policy, abort, source teardown, and close behavior. Graceful `close()` stops publishes/sources and drains already-queued events before the subscriber completes; overflow `close` still emits one notice and terminates. Single-consumer contract: a second concurrent `subscribe()` throws `EventMultiplexerError` (`ERR_PRISM_EVENT_MULTIPLEXER_SINGLE_CONSUMER`); the slot frees when the active consumer completes or is `return()`ed at a yield. `observe` fan-in is unchanged (broadcast happens at the source). |
|
|
153
153
|
| `PersistencePage<T>` | Cursor-paginated result page: `items`, optional `nextCursor`, optional `total`. |
|
package/docs/rag.md
CHANGED
|
@@ -109,7 +109,7 @@ 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
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
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.
|
|
@@ -260,7 +260,7 @@ const result = await retrieveContext("How do approvals work?", { embedder, store
|
|
|
260
260
|
```
|
|
261
261
|
|
|
262
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.
|
|
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
|
|
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
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.
|
|
265
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.
|
|
266
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.
|
|
@@ -5,61 +5,63 @@
|
|
|
5
5
|
## What it does
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
Prism's current **0.
|
|
8
|
+
Prism's current **0.11.1** line has **12 publishable manifests**: the root `@arnilo/prism` core package plus **11 workspace packages** — **22 provider adapters** (22 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.1` 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.1 lockstep cut** moved all **twelve** manifests together. The current declared peer is `@arnilo/prism@^0.11.1` 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.11.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-hooks` | 0.
|
|
27
|
-
| `@arnilo/prism-mcp` | 0.
|
|
28
|
-
| `@arnilo/prism-memory` | 0.
|
|
29
|
-
| `@arnilo/prism-web-tools` | 0.
|
|
30
|
-
| `@arnilo/prism-work` | 0.
|
|
19
|
+
| `@arnilo/prism` | 0.11.1 | core — runtime, CLI/RPC, templates, docs |
|
|
20
|
+
| `@arnilo/prism-channels` | 0.11.1 | 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.1 | family — /agent, /security, /openapi, /computer-use-linux, /dev, /impeccable subpaths |
|
|
22
|
+
| `@arnilo/prism-core` | 0.11.1 | family — /runtime, /sessions, /governance, /credentials, /enterprise, /validation subpaths |
|
|
23
|
+
| `@arnilo/prism-providers` | 0.11.1 | family — all provider adapters as `/<adapter>` subpaths |
|
|
24
|
+
| `@arnilo/prism-acp-agent` | 0.11.1 | capability — ACP adapter |
|
|
25
|
+
| `@arnilo/prism-ag-ui` | 0.11.1 | capability — AG-UI/A2A/A2UI adapter |
|
|
26
|
+
| `@arnilo/prism-hooks` | 0.11.1 | capability — Claude/Codex-compatible hooks.json adapter compiled onto middleware, guardrail, injector, and stop-hook seams |
|
|
27
|
+
| `@arnilo/prism-mcp` | 0.11.1 | capability — MCP client/server/OAuth interop |
|
|
28
|
+
| `@arnilo/prism-memory` | 0.11.1 | capability — memory plus /rag, /compaction/*, /fabric, /wiki subpaths |
|
|
29
|
+
| `@arnilo/prism-web-tools` | 0.11.1 | capability — Brave/Exa/Firecrawl plus peer-gated /browser and /obscura subpaths |
|
|
30
|
+
| `@arnilo/prism-work` | 0.11.1 | capability — /connectors, /documents, /sheets, /diagrams, /document-reader, /sandbox, /skills, /tools subpaths |
|
|
31
31
|
<!-- generated:package-truth:inventory end -->
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
<!-- generated:package-truth:providers begin -->
|
|
35
|
-
**
|
|
35
|
+
**22 provider adapters** — first-party adapters ship as `@arnilo/prism-providers/<adapter>` subpaths in one tarball (importing one never evaluates another):
|
|
36
36
|
|
|
37
37
|
| adapter package | version |
|
|
38
38
|
| --- | --- |
|
|
39
|
-
| `@arnilo/prism-providers/ai-sdk` | 0.
|
|
40
|
-
| `@arnilo/prism-providers/alibaba` | 0.
|
|
41
|
-
| `@arnilo/prism-providers/anthropic` | 0.
|
|
42
|
-
| `@arnilo/prism-providers/azure` | 0.
|
|
43
|
-
| `@arnilo/prism-providers/bedrock` | 0.
|
|
44
|
-
| `@arnilo/prism-providers/clinepass` | 0.
|
|
45
|
-
| `@arnilo/prism-providers/commandcode` | 0.
|
|
46
|
-
| `@arnilo/prism-providers/deepseek` | 0.
|
|
47
|
-
| `@arnilo/prism-providers/google` | 0.
|
|
48
|
-
| `@arnilo/prism-providers/hyper` | 0.
|
|
49
|
-
| `@arnilo/prism-providers/kimi` | 0.
|
|
50
|
-
| `@arnilo/prism-providers/
|
|
51
|
-
| `@arnilo/prism-providers/
|
|
52
|
-
| `@arnilo/prism-providers/
|
|
53
|
-
| `@arnilo/prism-providers/
|
|
54
|
-
| `@arnilo/prism-providers/
|
|
55
|
-
| `@arnilo/prism-providers/
|
|
56
|
-
| `@arnilo/prism-providers/
|
|
57
|
-
| `@arnilo/prism-providers/
|
|
58
|
-
| `@arnilo/prism-providers/
|
|
39
|
+
| `@arnilo/prism-providers/ai-sdk` | 0.11.1 |
|
|
40
|
+
| `@arnilo/prism-providers/alibaba` | 0.11.1 |
|
|
41
|
+
| `@arnilo/prism-providers/anthropic` | 0.11.1 |
|
|
42
|
+
| `@arnilo/prism-providers/azure` | 0.11.1 |
|
|
43
|
+
| `@arnilo/prism-providers/bedrock` | 0.11.1 |
|
|
44
|
+
| `@arnilo/prism-providers/clinepass` | 0.11.1 |
|
|
45
|
+
| `@arnilo/prism-providers/commandcode` | 0.11.1 |
|
|
46
|
+
| `@arnilo/prism-providers/deepseek` | 0.11.1 |
|
|
47
|
+
| `@arnilo/prism-providers/google` | 0.11.1 |
|
|
48
|
+
| `@arnilo/prism-providers/hyper` | 0.11.1 |
|
|
49
|
+
| `@arnilo/prism-providers/kimi` | 0.11.1 |
|
|
50
|
+
| `@arnilo/prism-providers/laya` | 0.11.1 |
|
|
51
|
+
| `@arnilo/prism-providers/model-discovery` | 0.11.1 |
|
|
52
|
+
| `@arnilo/prism-providers/neuralwatt` | 0.11.1 |
|
|
53
|
+
| `@arnilo/prism-providers/ollama` | 0.11.1 |
|
|
54
|
+
| `@arnilo/prism-providers/openai` | 0.11.1 |
|
|
55
|
+
| `@arnilo/prism-providers/opencode-go` | 0.11.1 |
|
|
56
|
+
| `@arnilo/prism-providers/openrouter` | 0.11.1 |
|
|
57
|
+
| `@arnilo/prism-providers/typesafe` | 0.11.1 |
|
|
58
|
+
| `@arnilo/prism-providers/vertex` | 0.11.1 |
|
|
59
|
+
| `@arnilo/prism-providers/xai` | 0.11.1 |
|
|
60
|
+
| `@arnilo/prism-providers/zai` | 0.11.1 |
|
|
59
61
|
<!-- generated:package-truth:providers end -->
|
|
60
62
|
|
|
61
63
|
|
|
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`,
|
|
64
|
+
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.
|
|
63
65
|
|
|
64
66
|
## When to use it
|
|
65
67
|
|
|
@@ -91,7 +93,6 @@ Consumers install the core package for the runtime and add first-party packages
|
|
|
91
93
|
| Install Obscura browser-engine tools (host supplies the binary; `/obscura`) | `npm install @arnilo/prism @arnilo/prism-web-tools @arnilo/prism-mcp` |
|
|
92
94
|
| Install RAG retrieval (memory family `/rag`) | `npm install @arnilo/prism @arnilo/prism-memory` |
|
|
93
95
|
| Install the Wiki CLI and skills (memory family `/wiki`) | `npm install @arnilo/prism @arnilo/prism-memory` (`npx prism-wiki --help`) |
|
|
94
|
-
| Install the Graft context-graph bridge (`/graft`, host supplies the CLI) | `npm install @arnilo/prism @arnilo/prism-memory` (+ host-installed `@nanonets/graft`) |
|
|
95
96
|
| Install work connectors | `npm install @arnilo/prism @arnilo/prism-work` (import `@arnilo/prism-work/connectors`) |
|
|
96
97
|
| Install document/spreadsheet/presentation engine | `npm install @arnilo/prism @arnilo/prism-work` (import `@arnilo/prism-work/documents`) |
|
|
97
98
|
| Install spreadsheet and CSV data engine | `npm install @arnilo/prism @arnilo/prism-work` (import `@arnilo/prism-work/sheets`) |
|
|
@@ -153,13 +154,13 @@ A packed tarball contains only public compiled output and release files:
|
|
|
153
154
|
- Code packages ship `README.md`, `LICENSE`, and `CHANGELOG.md`; family/profile packages ship `README.md` and `CHANGELOG.md`.
|
|
154
155
|
- 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`.
|
|
155
156
|
- `dist/cli.js` and the `bin` link in core.
|
|
156
|
-
- **Tarball filenames.** npm strips the `@scope/` prefix, so the core package `@arnilo/prism` produces a tarball named `arnilo-prism-0.
|
|
157
|
+
- **Tarball filenames.** npm strips the `@scope/` prefix, so the core package `@arnilo/prism` produces a tarball named `arnilo-prism-0.11.1.tgz`; family packages produce `arnilo-prism-core-0.11.1.tgz`, `arnilo-prism-coding-tools-0.11.1.tgz`, `arnilo-prism-providers-0.11.1.tgz` (all 22 adapters inside), `arnilo-prism-channels-0.11.1.tgz`, `arnilo-prism-memory-0.11.1.tgz`, and `arnilo-prism-web-tools-0.11.1.tgz`; capability packages like `arnilo-prism-hooks-0.11.1.tgz`, `arnilo-prism-mcp-0.11.1.tgz`, and `arnilo-prism-work-0.11.1.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).
|
|
157
158
|
|
|
158
159
|
Excluded from every tarball by `files` negation:
|
|
159
160
|
|
|
160
161
|
- `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).
|
|
161
162
|
- `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.
|
|
162
|
-
- `src/`, `plans/`, `.agents/`, `.github/`, `tsconfig*.json`, `roadmap.md`, and `
|
|
163
|
+
- `src/`, `plans/`, `.agents/`, `.github/`, `tsconfig*.json`, `roadmap.md`, and `bun.lock` are never packed (outside the `files` whitelist and/or explicitly ignored).
|
|
163
164
|
|
|
164
165
|
`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.
|
|
165
166
|
|
|
@@ -254,13 +255,13 @@ Frozen by Phase 12 Task 0 in `scripts/phase12-freeze-manifest.json` (schema gate
|
|
|
254
255
|
|
|
255
256
|
| Runtime | Supported | Measured in CI |
|
|
256
257
|
| --- | --- | --- |
|
|
257
|
-
| 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.
|
|
258
|
+
| 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. |
|
|
258
259
|
| PostgreSQL | 16 (`pgvector/pgvector:pg16`) | `postgres-integration` service container |
|
|
259
260
|
|
|
260
261
|
## Extension and configuration notes
|
|
261
262
|
|
|
262
263
|
|
|
263
|
-
- **Required `@arnilo/prism` peer.** Every first-party code package declares a non-optional **caret** `@arnilo/prism@^0.
|
|
264
|
+
- **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.
|
|
264
265
|
- **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.
|
|
265
266
|
- **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.
|
|
266
267
|
- **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).
|
|
@@ -286,7 +287,7 @@ Frozen by Phase 12 Task 0 in `scripts/phase12-freeze-manifest.json` (schema gate
|
|
|
286
287
|
|
|
287
288
|
|
|
288
289
|
- **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.
|
|
289
|
-
- **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
|
|
290
|
+
- **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.
|
|
290
291
|
- **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.
|
|
291
292
|
- **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).
|
|
292
293
|
- `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:
|
|
@@ -309,13 +310,13 @@ Frozen by Phase 12 Task 0 in `scripts/phase12-freeze-manifest.json` (schema gate
|
|
|
309
310
|
- `PRISM_TEST_KEYCHAIN=1` — gates `@arnilo/prism-core/credentials/node` system-keychain round-trips (requires a working OS keychain backend; skipped by default).
|
|
310
311
|
- 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.
|
|
311
312
|
- 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.
|
|
312
|
-
- **Supply-chain workflows.** `.github/workflows/security.yml` runs CodeQL JavaScript/TypeScript SAST, PR-only dependency review, `
|
|
313
|
+
- **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.
|
|
313
314
|
- **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).
|
|
314
315
|
- **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.
|
|
315
|
-
- **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 `
|
|
316
|
+
- **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.
|
|
316
317
|
- **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`).
|
|
317
318
|
- **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`.
|
|
318
|
-
- **Offline test budget.** The default `npm test` (no `PRISM_LIVE_PROVIDER_TESTS`) is pinned at **<
|
|
319
|
+
- **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.
|
|
319
320
|
|
|
320
321
|
### GitHub Actions pipeline (0.0.27+)
|
|
321
322
|
|
|
@@ -325,27 +326,27 @@ Frozen by Phase 12 Task 0 in `scripts/phase12-freeze-manifest.json` (schema gate
|
|
|
325
326
|
## Formatting, linting, and coverage
|
|
326
327
|
|
|
327
328
|
|
|
328
|
-
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
|
|
329
|
+
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`.
|
|
329
330
|
|
|
330
331
|
| Command | What it does |
|
|
331
332
|
| --- | --- |
|
|
332
333
|
| `npm run lint` | `biome lint .` — fails on any lint error (warnings are non-fatal). |
|
|
333
334
|
| `npm run format:check` | `biome format .` — fails if any file is unformatted. |
|
|
334
335
|
| `npm run format` | `biome format --write .` — normalizes formatting in place. |
|
|
335
|
-
| `npm run test:coverage` | `
|
|
336
|
+
| `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. |
|
|
336
337
|
|
|
337
|
-
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
|
|
338
|
+
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.
|
|
338
339
|
|
|
339
340
|
### Coverage denominators and per-package thresholds
|
|
340
341
|
|
|
341
342
|
|
|
342
|
-
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
|
|
343
|
+
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).
|
|
343
344
|
|
|
344
345
|
| Fact | Value |
|
|
345
346
|
| --- | --- |
|
|
346
|
-
| Workspace
|
|
347
|
+
| Workspace filter | package-local `bunfig.toml` `[test] coveragePathIgnorePatterns = ["../**"]` per package (the equivalent of the old `--test-coverage-include=dist/**`) |
|
|
347
348
|
| 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 |
|
|
348
|
-
| Per-package gate | `lines >= threshold` from `scripts/coverage-thresholds.json` (recaptured 2026-09-
|
|
349
|
+
| 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 |
|
|
349
350
|
| 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 |
|
|
350
351
|
| 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) |
|
|
351
352
|
| 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 |
|
|
@@ -397,7 +398,7 @@ Timing assertions in tests follow a deterministic-barrier policy: racy wall-cloc
|
|
|
397
398
|
| Timeout | 120s default; override with `PRISM_BUILD_LOCK_TIMEOUT_MS` |
|
|
398
399
|
| Retry | 100ms backoff; stale-PID reclaim via `process.kill(pid, 0)` |
|
|
399
400
|
| Fail-closed | acquisition error or timeout exits non-zero, nothing runs |
|
|
400
|
-
| Wrapped | `build:core`, every workspace `build`, the `node --test` runs in `test
|
|
401
|
+
| 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`) |
|
|
401
402
|
| 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) |
|
|
402
403
|
|
|
403
404
|
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).
|
|
@@ -436,14 +437,14 @@ Development type packages track the **declared** engines floor, not the machine
|
|
|
436
437
|
| Public surface import smoke (all 21 root `exports` default targets) | 20.20.2 | ✅ all import cleanly. |
|
|
437
438
|
| 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. |
|
|
438
439
|
|
|
439
|
-
**CI enforcement** (`.github/workflows/release.yml`): the `verify` job runs `npm run sdk:ready` on Node 24; `node22-compat` runs `
|
|
440
|
+
**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.
|
|
440
441
|
|
|
441
442
|
**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.
|
|
442
443
|
|
|
443
444
|
## Release checklist
|
|
444
445
|
|
|
445
446
|
|
|
446
|
-
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 `
|
|
447
|
+
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.
|
|
447
448
|
|
|
448
449
|
| Gate | Enforcement |
|
|
449
450
|
| --- | --- |
|
|
@@ -457,14 +458,15 @@ Every release gate maps to an exact enforcement test or command, so the checklis
|
|
|
457
458
|
| 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. |
|
|
458
459
|
| 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. |
|
|
459
460
|
| 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. |
|
|
460
|
-
|
|
|
461
|
+
| 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. |
|
|
462
|
+
| 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. |
|
|
461
463
|
| 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`. |
|
|
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.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
|
|
464
|
+
| 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. |
|
|
465
|
+
| 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`. |
|
|
464
466
|
| 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. |
|
|
465
467
|
| 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. |
|
|
466
468
|
| 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. |
|
|
467
|
-
| Network-free + offline test budget | `network-free-guard.test.ts` keeps the default suite network-free; budget pinned `<
|
|
469
|
+
| 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). |
|
|
468
470
|
| 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). |
|
|
469
471
|
|
|
470
472
|
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.
|