@arnilo/prism 0.2.2 → 0.2.3

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 CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.3] - 2026-08-14
4
+
5
+ ### Changed
6
+ - **Release 0.2.3 (plan 023)** is the build-coverage-and-release-evidence-integrity cut on the 0.2.x review-remediation line. API surface **additive-only** (plain reviewed compat gate at 0.2.3: delta is the version literal only — no export changes; baselines regenerated with `--update-baseline`, no `--allow-break`; freeze manifest `scripts/phase23-freeze-manifest.json` records per-task evidence tokens). Four tooling/evidence fixes, no runtime contract change: (1) **build serialization** (`build-serialization`, core tooling) — dependency-free `scripts/with-build-lock.mjs` holds one O_EXCL lockfile at `node_modules/.prism-build.lock` (pid + startedAt, read-back verified, stale-PID reclaim via `process.kill(pid, 0)`, 100 ms backoff to env-overridable `PRISM_BUILD_LOCK_TIMEOUT_MS` default 120 s, fail-closed exit 1) around every emit/test leaf (root `build:core`, both `node --test` segments, `test:coverage` leaves, coverage summary, all 43 workspace build/test/`test:postgres` scripts) so concurrent compilers and importers can never observe a partially emitted `dist/` — the 2026-08-12 review defect that 0.1.1's destructive-clean removal did not close; the lock is never held by orchestrator scripts, `PRISM_BUILD_LOCK_HELD=1` prevents accidental nesting, and the direct-`tsc` caveat is documented in `docs/release-and-install.md`. (2) **corrected workspace coverage denominators** (`coverage-denominators`, `scripts/coverage-summary.mjs`) — workspace coverage runs now pass package-local `--test-coverage-include=dist/**` so imported core `dist/` no longer pollutes package rows (`mcp` 45.47→90.25, `rag` 19.70→94.82, `memory` 20.37→72.00, `session-store-nats` 16.04→93.69, `enterprise-postgres` 21.93→43.26; `session-store-postgres` stays 22.89 as the protected durable-leg proof); the 60/70/75 core gate is unchanged; evidence-based per-package line thresholds in `scripts/coverage-thresholds.json` (freeze-run percentages minus 3 pp) with `protectedException` entries for the env-gated durable-leg packages (`session-store-postgres`, `enterprise-postgres`, `memory`, `session-store-nats`) shown separately; the machine-readable `scripts/coverage-summary.json` artifact records per-package lines/branches/functions/denominator files/threshold/pass-fail and the gate exits 1 on any non-protected below-threshold package. (3) **machine-auditable release skip manifest** (`skip-manifest`, `scripts/release-skip-manifest.mjs` → `scripts/release-evidence.json`) — every release surface (core `npm test`, each workspace suite, threat suites, `test:postgres` durable conformance, provider live legs, NATS real legs, the four live canaries) records `pass`/`skip`/`blocked`/`protected` with reason and required env names only (never secrets); the 33 protected/live skips are named; a required surface with absent evidence records `blocked` and `scripts/release.mjs` gate fails closed (`checkReleaseEvidence`) — missing credentials/services can never convert into a green release; the artifact is CI-retained and gitignored. (4) **stabilized quality gates** (`quality-gates`) — `biome.json` migrated to the 2.x canonical `linter.rules.preset: "recommended"` (deprecated key removed) and all 97 lint diagnostics resolved to zero (safe + reviewed `--unsafe` fixes across 108 files, 10 hand-removed dead `_`-renamed variables, 5 justified `biome-ignore` comments for shell-interpolation probes/verbatim fixtures/grep targets); the racy 150 ms MCP bridge timing assertion became a deterministic awaited-outcome barrier with a test-level timeout, and kept load-sensitive guards carry documented `ponytail:` ceilings; `npm run lint` writes machine-readable `scripts/lint-report.sarif` and `npm run sweep:unused` writes `scripts/unused-report.json`, both retained by the release workflow. New regression surface: `scripts/phase23-build-race.test.mjs` (8 lock/stress tests incl. synthetic partial-`dist` sensitivity, stale-lock reclaim, live-lock fail-closed), `scripts/phase23-coverage.test.mjs` (4 tests: include-filter + core-gate greps, thresholds JSON validation, real-artifact denominator proof, sabotaged fail-closed run), `scripts/phase23-skip-manifest.test.mjs` (6 tests: blocked-not-skip, protected-named with the frozen 33-skip floor, live-canary-not-pass, unexplained-skip-rejected, no-secret, wiring), `scripts/phase23-quality-gates.test.mjs` (5 tests: lint clean, preset config, timing quarantine, biome-ignore reasons, machine-readable reports), and `scripts/phase23-security.test.mjs` (3 tests: matrix item 4 concurrent emit+public-entry importer, matrix item 12 coverage denominator + protected rows, gate accounting — built public entrypoints, wired into `security:threat-suites`) plus the packed plain-JS `security23.mjs` consumer in install-smoke. Release graph stays **50** publishable manifests at exact **0.2.3**; zero new runtime dependency names (core remains dependency-free). Store compatibility with 0.2.2: **compatible, no migration** (no persisted-shape change). Exit gate green (core + script gates incl. phase21-freeze done-phase + all phase23 suites, `sdk:ready`, audit 0 moderate, pack dry-run 50/50 twice byte-identical, plain reviewed compat gate at 0.2.3, protected Postgres durable conformance evidence, release-evidence manifest with zero blocked surfaces, evidence in `scripts/phase23-baseline.json`). **Publication remains the operator handoff** (`docs/release-and-install.md` `0.2.3 publish handoff` — signed `v0.2.3` tag + npm OIDC).
7
+
3
8
  ## [0.2.2] - 2026-08-13
4
9
 
5
10
  ### Changed
@@ -500,7 +500,7 @@ export class RuntimeAgentSession {
500
500
  if (calls.length > 0)
501
501
  limits.charge("maxToolRounds");
502
502
  const durable = this.activeDurable;
503
- if (!durable || !durable.options.interruptBeforeTool || calls.length === 0)
503
+ if (!durable?.options.interruptBeforeTool || calls.length === 0)
504
504
  return;
505
505
  // Round-level gate: record one pending decision per uncovered gated call. Ungated
506
506
  // and sticky-allowed calls still dispatch; the suspension fires at the next provider
@@ -17,7 +17,7 @@ function validateTokens(name, value) {
17
17
  throw new CacheTelemetryError(`${name} must be a non-negative safe integer, got ${value}`);
18
18
  }
19
19
  }
20
- function sampleFor(usage, model) {
20
+ function sampleFor(_usage, model) {
21
21
  if (model)
22
22
  return { provider: model.provider, model: model.model };
23
23
  // Provider-only aggregation: no model supplied, attribute to the unknown bucket.
@@ -1213,7 +1213,7 @@ export interface ProductionPersistenceStore {
1213
1213
  readonly expectedVersion?: number;
1214
1214
  }): Promise<{
1215
1215
  readonly version: number;
1216
- } | void>;
1216
+ } | undefined>;
1217
1217
  /** DB-friendly branch read (mirrors `SessionStore.readBranchPath`): one ancestor-chain
1218
1218
  * query instead of `queryEntries({ sessionId })` + in-memory walk. Optional. */
1219
1219
  readBranchPath?(query: SessionBranchRead): Promise<PersistencePage<SessionEntry>>;
package/dist/index.d.ts CHANGED
@@ -109,5 +109,5 @@ export { createToolParameterValidator, createToolRegistry, dispatchToolCall, fil
109
109
  export type { ResolvedUseCaseModel, ResolveUseCaseModelInput, UseCaseModelBinding, } from "./use-case-model.js";
110
110
  export { resolveUseCaseModel, resolveUseCaseModelBinding, useCaseCredentialProviderId, } from "./use-case-model.js";
111
111
  export declare const name = "prism";
112
- export declare const version = "0.2.2";
112
+ export declare const version = "0.2.3";
113
113
  export declare const description = "Agent harness for AI providers, agents, sessions, and tools.";
package/dist/index.js CHANGED
@@ -60,6 +60,6 @@ export { DEFAULT_TOOL_RESULT_FOLD_MAX_SUMMARY_BYTES, DEFAULT_TOOL_RESULT_FOLD_MI
60
60
  export { createToolParameterValidator, createToolRegistry, dispatchToolCall, filterTools } from "./tools.js";
61
61
  export { resolveUseCaseModel, resolveUseCaseModelBinding, useCaseCredentialProviderId, } from "./use-case-model.js";
62
62
  export const name = "prism";
63
- export const version = "0.2.2";
63
+ export const version = "0.2.3";
64
64
  export const description = "Agent harness for AI providers, agents, sessions, and tools.";
65
65
  //# sourceMappingURL=index.js.map
@@ -23,7 +23,6 @@ import { assertSsrfAllowedUrl, MediaContentError } from "./content.js";
23
23
  /** One DNS-pinned, redirect-free, byte-bounded fetch. See module comment. */
24
24
  export async function pinnedFetch(url, init, options) {
25
25
  const errorPrefix = options?.errorPrefix ?? "Request";
26
- const hostnameErrorPrefix = options?.hostnameErrorPrefix ?? errorPrefix;
27
26
  if (url.username || url.password)
28
27
  throw new MediaContentError("ssrf_denied", `${errorPrefix} URL must not embed credentials`);
29
28
  if (url.hash)
@@ -96,6 +96,6 @@ function truncateUtf8Bytes(text, maxBytes) {
96
96
  let end = Math.max(0, maxBytes - suffix.length);
97
97
  while (end > 0 && (encoded[end] & 0xc0) === 0x80)
98
98
  end--;
99
- return new TextDecoder().decode(encoded.slice(0, end)) + "…";
99
+ return `${new TextDecoder().decode(encoded.slice(0, end))}…`;
100
100
  }
101
101
  //# sourceMappingURL=skill-disclosure.js.map
@@ -64,7 +64,6 @@ export function applyRestoredSkillBodies(skills, bodies) {
64
64
  validateLoadedSkillBodies(bodies);
65
65
  if (bodies.length === 0)
66
66
  return skills;
67
- const byName = new Map(skills.map((skill) => [skill.name, skill]));
68
67
  const out = skills.map((skill) => {
69
68
  const body = bodies.find((entry) => entry.name === skill.name);
70
69
  return body ? { ...skill, instructions: body.instructions } : skill;
@@ -130,7 +130,7 @@ export function createMemoryToolEffectStore(options = {}) {
130
130
  },
131
131
  async resolveUnknown(input) {
132
132
  const record = current(input);
133
- if (!record || record.status !== "unknown" || record.version !== input.expectedVersion)
133
+ if (record?.status !== "unknown" || record.version !== input.expectedVersion)
134
134
  throw conflict();
135
135
  const result = input.result === undefined ? undefined : validateResult(input.result, input);
136
136
  const resultRef = input.resultRef === undefined ? undefined : validateReference(input.resultRef);
@@ -56,7 +56,7 @@ async function foldToolResultMessage(message, options, context) {
56
56
  if (message.role !== "tool")
57
57
  return message;
58
58
  const block = message.content.find((part) => part.type === "tool_result");
59
- if (!block || block.type !== "tool_result")
59
+ if (block?.type !== "tool_result")
60
60
  return message;
61
61
  const text = toolResultText(block.result, block.error, message.content);
62
62
  const folded = await maybeFold({
@@ -141,7 +141,7 @@ function capSummaryBytes(summary, maxBytes) {
141
141
  let end = Math.max(0, maxBytes - suffix.length);
142
142
  while (end > 0 && (encoded[end] & 0xc0) === 0x80)
143
143
  end--;
144
- return new TextDecoder().decode(encoded.slice(0, end)) + "…";
144
+ return `${new TextDecoder().decode(encoded.slice(0, end))}…`;
145
145
  }
146
146
  function inferToolResultTurns(history) {
147
147
  const turns = new Array(history.length).fill(1);
package/docs/index.md CHANGED
@@ -129,7 +129,7 @@ Prism is a TypeScript/Node.js agent harness. Host apps and extension packages ow
129
129
  - [Ponytail behavior integration](ponytail.md): optional `@arnilo/prism-ponytail` — upstream Ponytail skills/commands, `ponytail-mode` injector, session `ponytail-mode` persistence; resolves peer `@dietrichgebert/ponytail` or `upstreamPath`; opt-in (not in code/sdk profiles).
130
130
 
131
131
  ## Release and install
132
- - [Release and install](release-and-install.md): current **0.2.2** 50-package graph (root + 49 workspace packages) — plan 022 the concurrent-state-and-durability-integrity cut: atomic model-budget reservation (`ModelRouterStateStore.reserveBudget`/`commitBudget`/`releaseBudget` with fencing tokens, `reservationTtlMs` expiry and unknown-usage reconciliation, rate/budget key-map caps with LRU eviction that never drops a held reservation), atomic conversation metadata (`SessionRecord.version` + `appendSession` `expectedVersion` CAS across Postgres/SQLite — create-only `0`, exact-version `N>0`, legacy last-write-wins when omitted; `SessionMetadataConflictError` `metadata_conflict` with versions only, HTTP 409; concurrent create/branch/archive single-statement with branch caps inside the CAS, archive wins, deleted rows never resurrect), single-consumer `EventMultiplexer` (`EventMultiplexerError` `ERR_PRISM_EVENT_MULTIPLEXER_SINGLE_CONSUMER` instead of silent queue sharing), restart-stable NATS durable consumer identity (`prism_<hmac16>` with no random suffix — crash-resumed subscribe continues from the last ack, orphaned 0.2.1 consumers reclaimed on clean stop), and bounded non-durable active-run registries (sweep + fail-closed 512 cap `ERR_PRISM_WORKFLOW_RUN_REGISTRY_OVERFLOW`); new regression surface `scripts/phase22-security.test.mjs` (4 blockers + gate accounting over built public entrypoints) + packed plain-JS `security22.mjs` consumer + the `@arnilo/prism/testing/state-concurrency-conformance` harness (7 probes across memory/Postgres/SQLite/NATS legs, no timing-only sleeps) + the `scripts/phase22-conformance.test.mjs` gate; additive-only compat (new exports only, no removals); forward-only migrations 008 (`prism_sessions.version`) and 003 (`prism_model_router_budgets.reservations`); migration `0.2.1 → 0.2.2`; then plan 021 the provider-completion-and-outbound-trust-boundaries cut: strict stream completion is the shared OpenAI-compatible default (truncated streams fail `incomplete_delta`, explicit `strictCompletion: false` opt-out), bounded success bodies via `readBoundedResponseJson` on all discovery/quota/embeddings/upload/OAuth JSON endpoints (65,536-byte ceiling, depth/property/shape caps), DNS-pinned OIDC JWKS/OPA/content fetches through the core `pinnedFetch` primitive with 3xx redirects rejected outright (private/metadata answers fail closed `ssrf_denied`), shared bounded OAuth device/token polling (`pollDeviceCodeToken`) across provider-openai and credentials-node, and the four edge fixes (Azure/Vertex credential-once, Bedrock duplicate-case/repeated-query SigV4 canonicalization, OpenAI upload failed-DELETE retention, cache `__overflow__` tokens-only); public-entrypoint threat-suite `scripts/phase21-security.test.mjs` + packed plain-JS consumer; additive-only compat (MCP transport helpers re-exported from core, no removals); migration `0.2.0 → 0.2.1`; then plan 020 the fail-closed runtime-and-sandbox-security cut on the 0.2.x review-remediation line: durable-resume decision validation in core (`assertValidAgentRunResume` — unknown decisions/malformed batches fail closed with `ERR_PRISM_DECISION_*` before any state claim, checkpoint write, or tool execution; server parser remains defense in depth), isolated work-tool subprocess environments (`@arnilo/prism-work-tools` — fixed base allow-list + explicit env + forced HOME/telemetry + late-bound per-identity tokens, 64-name/64-KiB caps, absolute binary/configDir, linear output capture), and explicit sandbox capabilities (`@arnilo/prism-coding-security` — `SandboxAdapter.capabilities` with omission-is-false fail-closed resolution, `SandboxCodingComposition.capabilities` from verified wiring, `containmentClaim` deprecated as the conservative projection; Docker reports only verified controls, native reports filesystem/process/privilege `false`); public-entrypoint security conformance (`scripts/phase20-security.test.mjs`, wired into `security:threat-suites`), packed plain-JS consumer regressions, and the sandbox-browser workflow's fail-loud Docker/native capability evidence gate — 0.2.0 never ships while a blocker is skipped; migration and rollback notes in `docs/migration.md` `0.1.7 → 0.2.0`, store-compatible with 0.1.7 in both directions; 0.1.7 was the performance-and-DX patch — dependency-free `createCacheTelemetry()` per-provider/model cache hit/miss aggregator (bounded cardinality with `__overflow__`, token counters/rates only, host-activated), host-configurable `ModelRouterSelectionPolicy` on `createModelRouter` with the reference `createCostLatencySelection` (ModelCost rank then in-memory latency EMA, default ordered behavior byte-identical), `prism providers add <name>` OpenAI-compatible provider scaffold (manifest/provider/models/cache/conformance test/docs stub, npm-name + traversal + symlink-escape validation, placeholders only), and the async `AgUiProjection` verification closeout (plan 009 Task 15 evidence recorded, no new code); plan 017 the documented breaking cut — deprecated-option removal with `docs/migration.md` `0.1.4 → 0.1.5` section and reviewed compat-baseline regeneration via `--allow-break` then `--update-baseline`: the inert provider request knobs, `RunOptions.maxToolRounds`, observational-memory flat settings keys + top-level worker aliases, `ReadToolOptions.autoResizeImages`, `INIT_PROVIDERS`; all removals fail closed naming their replacement; plan 016 internal god-module split — `agents.ts`/`contracts.ts` reorganized behind barrel re-exports with a byte-identical public entry surface, measured tree-shaking improvement in `scripts/phase16-baseline.json`, and additive `@arnilo/prism-browser` Chrome DevTools Protocol capabilities — `browser_evaluate`/`browser_observe` and `block_urls`/`unblock_urls`/`throttle`/`emulate` act actions; plan 015 dead-code and deprecation hygiene on the frozen 0.1.x line — parameterized benchmark runner `scripts/benchmark.mjs` absorbing the per-version runners, archived review-coverage evidence in `docs/_evidence/`, non-blocking unused-code sweep `npm run sweep:unused`, opt-in checkpoint persistence for loaded-skill names and read-path sets; plan 014 Alibaba provider enrichment — embeddings, video input, verified compatible-mode surface decision table; plan 013 post-release hardening — build single-flight, MCP SSE relay test, combined coverage summary, canonical manifest-count narrative, ACP modes/config persistence guidance; Phase 12 release-candidate hardening; plan 012 — freeze manifest, compatibility matrix, upgrade matrix, packed-install e2e journeys, restart-recovery evidence, capacity envelopes, security policy), exact-peer/install/tarball rules, deterministic resumable publication and publish dry-run, frozen 0.1.x compatibility and support matrix (Node/PostgreSQL/platform/provider/protocol pins and unsupported combinations, machine-checked against `scripts/phase12-freeze-manifest.json`), protected PostgreSQL gate, pinned supply-chain gates, offline tests, the 0.0.15 provider/AI-SDK/RAG/memory protected live-canary matrix, and sandbox-browser Docker/Playwright gates.
132
+ - [Release and install](release-and-install.md): current **0.2.3** 50-package graph (root + 49 workspace packages) — plan 023 the build-coverage-and-release-evidence-integrity cut: **build serialization** (dependency-free `scripts/with-build-lock.mjs` — one O_EXCL lockfile at `node_modules/.prism-build.lock` serializing every emit/test leaf so concurrent compilers can never expose a partial live `dist/`, stale-PID reclaim, env-overridable `PRISM_BUILD_LOCK_TIMEOUT_MS`, fail-closed; documented direct-`tsc` caveat), **corrected workspace coverage denominators** (package-local `--test-coverage-include=dist/**` so imported core `dist` no longer pollutes workspace rows — `mcp` 45.47→90.25, `rag` 19.70→94.82; evidence-based per-package thresholds in `scripts/coverage-thresholds.json` with `protectedException` for durable-leg packages shown separately, machine-readable `scripts/coverage-summary.json`), **machine-auditable release skip manifest** (`scripts/release-skip-manifest.mjs` → `scripts/release-evidence.json`: every surface recorded `pass`/`skip`/`blocked`/`protected` with reason and required env; the 33 protected/live skips named; a required surface without evidence records `blocked` and fails the release gate fail-closed — missing credentials/services can never convert into a green release), and **stabilized quality gates** (Biome 2.x `preset` config migration with zero lint diagnostics, the racy 150ms MCP bridge timing assert replaced by a deterministic barrier, load-sensitive guards carry documented `ponytail:` ceilings, machine-readable `lint-report.sarif` + `unused-report.json` retained by CI); no runtime contract delta (compat gate at 0.2.3: version literal only), no migration; then plan 022 the concurrent-state-and-durability-integrity cut: atomic model-budget reservation (`ModelRouterStateStore.reserveBudget`/`commitBudget`/`releaseBudget` with fencing tokens, `reservationTtlMs` expiry and unknown-usage reconciliation, rate/budget key-map caps with LRU eviction that never drops a held reservation), atomic conversation metadata (`SessionRecord.version` + `appendSession` `expectedVersion` CAS across Postgres/SQLite — create-only `0`, exact-version `N>0`, legacy last-write-wins when omitted; `SessionMetadataConflictError` `metadata_conflict` with versions only, HTTP 409; concurrent create/branch/archive single-statement with branch caps inside the CAS, archive wins, deleted rows never resurrect), single-consumer `EventMultiplexer` (`EventMultiplexerError` `ERR_PRISM_EVENT_MULTIPLEXER_SINGLE_CONSUMER` instead of silent queue sharing), restart-stable NATS durable consumer identity (`prism_<hmac16>` with no random suffix — crash-resumed subscribe continues from the last ack, orphaned 0.2.1 consumers reclaimed on clean stop), and bounded non-durable active-run registries (sweep + fail-closed 512 cap `ERR_PRISM_WORKFLOW_RUN_REGISTRY_OVERFLOW`); new regression surface `scripts/phase22-security.test.mjs` (4 blockers + gate accounting over built public entrypoints) + packed plain-JS `security22.mjs` consumer + the `@arnilo/prism/testing/state-concurrency-conformance` harness (7 probes across memory/Postgres/SQLite/NATS legs, no timing-only sleeps) + the `scripts/phase22-conformance.test.mjs` gate; additive-only compat (new exports only, no removals); forward-only migrations 008 (`prism_sessions.version`) and 003 (`prism_model_router_budgets.reservations`); migration `0.2.1 → 0.2.2`; then plan 021 the provider-completion-and-outbound-trust-boundaries cut: strict stream completion is the shared OpenAI-compatible default (truncated streams fail `incomplete_delta`, explicit `strictCompletion: false` opt-out), bounded success bodies via `readBoundedResponseJson` on all discovery/quota/embeddings/upload/OAuth JSON endpoints (65,536-byte ceiling, depth/property/shape caps), DNS-pinned OIDC JWKS/OPA/content fetches through the core `pinnedFetch` primitive with 3xx redirects rejected outright (private/metadata answers fail closed `ssrf_denied`), shared bounded OAuth device/token polling (`pollDeviceCodeToken`) across provider-openai and credentials-node, and the four edge fixes (Azure/Vertex credential-once, Bedrock duplicate-case/repeated-query SigV4 canonicalization, OpenAI upload failed-DELETE retention, cache `__overflow__` tokens-only); public-entrypoint threat-suite `scripts/phase21-security.test.mjs` + packed plain-JS consumer; additive-only compat (MCP transport helpers re-exported from core, no removals); migration `0.2.0 → 0.2.1`; then plan 020 the fail-closed runtime-and-sandbox-security cut on the 0.2.x review-remediation line: durable-resume decision validation in core (`assertValidAgentRunResume` — unknown decisions/malformed batches fail closed with `ERR_PRISM_DECISION_*` before any state claim, checkpoint write, or tool execution; server parser remains defense in depth), isolated work-tool subprocess environments (`@arnilo/prism-work-tools` — fixed base allow-list + explicit env + forced HOME/telemetry + late-bound per-identity tokens, 64-name/64-KiB caps, absolute binary/configDir, linear output capture), and explicit sandbox capabilities (`@arnilo/prism-coding-security` — `SandboxAdapter.capabilities` with omission-is-false fail-closed resolution, `SandboxCodingComposition.capabilities` from verified wiring, `containmentClaim` deprecated as the conservative projection; Docker reports only verified controls, native reports filesystem/process/privilege `false`); public-entrypoint security conformance (`scripts/phase20-security.test.mjs`, wired into `security:threat-suites`), packed plain-JS consumer regressions, and the sandbox-browser workflow's fail-loud Docker/native capability evidence gate — 0.2.0 never ships while a blocker is skipped; migration and rollback notes in `docs/migration.md` `0.1.7 → 0.2.0`, store-compatible with 0.1.7 in both directions; 0.1.7 was the performance-and-DX patch — dependency-free `createCacheTelemetry()` per-provider/model cache hit/miss aggregator (bounded cardinality with `__overflow__`, token counters/rates only, host-activated), host-configurable `ModelRouterSelectionPolicy` on `createModelRouter` with the reference `createCostLatencySelection` (ModelCost rank then in-memory latency EMA, default ordered behavior byte-identical), `prism providers add <name>` OpenAI-compatible provider scaffold (manifest/provider/models/cache/conformance test/docs stub, npm-name + traversal + symlink-escape validation, placeholders only), and the async `AgUiProjection` verification closeout (plan 009 Task 15 evidence recorded, no new code); plan 017 the documented breaking cut — deprecated-option removal with `docs/migration.md` `0.1.4 → 0.1.5` section and reviewed compat-baseline regeneration via `--allow-break` then `--update-baseline`: the inert provider request knobs, `RunOptions.maxToolRounds`, observational-memory flat settings keys + top-level worker aliases, `ReadToolOptions.autoResizeImages`, `INIT_PROVIDERS`; all removals fail closed naming their replacement; plan 016 internal god-module split — `agents.ts`/`contracts.ts` reorganized behind barrel re-exports with a byte-identical public entry surface, measured tree-shaking improvement in `scripts/phase16-baseline.json`, and additive `@arnilo/prism-browser` Chrome DevTools Protocol capabilities — `browser_evaluate`/`browser_observe` and `block_urls`/`unblock_urls`/`throttle`/`emulate` act actions; plan 015 dead-code and deprecation hygiene on the frozen 0.1.x line — parameterized benchmark runner `scripts/benchmark.mjs` absorbing the per-version runners, archived review-coverage evidence in `docs/_evidence/`, non-blocking unused-code sweep `npm run sweep:unused`, opt-in checkpoint persistence for loaded-skill names and read-path sets; plan 014 Alibaba provider enrichment — embeddings, video input, verified compatible-mode surface decision table; plan 013 post-release hardening — build single-flight, MCP SSE relay test, combined coverage summary, canonical manifest-count narrative, ACP modes/config persistence guidance; Phase 12 release-candidate hardening; plan 012 — freeze manifest, compatibility matrix, upgrade matrix, packed-install e2e journeys, restart-recovery evidence, capacity envelopes, security policy), exact-peer/install/tarball rules, deterministic resumable publication and publish dry-run, frozen 0.1.x compatibility and support matrix (Node/PostgreSQL/platform/provider/protocol pins and unsupported combinations, machine-checked against `scripts/phase12-freeze-manifest.json`), protected PostgreSQL gate, pinned supply-chain gates, offline tests, the 0.0.15 provider/AI-SDK/RAG/memory protected live-canary matrix, and sandbox-browser Docker/Playwright gates.
133
133
  - [0.1.0 / 1.0 readiness gates](0.1.0-readiness.md): command-per-gate 1.0 readiness table — frozen API surface + compat gate, migration/docs tripwires, budget table, live-suite matrix, security matrix, current-line status (**0.0.23** published target), signed-publication/live-canary prerequisites for 1.0, and Phase 12 demand-evidence entry criteria.
134
134
  - [Review coverage archive](_evidence/): per-phase evidence freezes (plans 067–079, releases 0.0.4–0.0.16) — traceability matrices, provider validation, capability/primitive/limit matrices, benchmark budgets, and artifact-diet findings; tarball-excluded, kept in-repo for audit.
135
135
 
package/docs/migration.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Migration guide
2
2
 
3
+ ## 0.2.2 → 0.2.3 build, coverage, and release evidence integrity (no migration)
4
+
5
+ Release **0.2.3** (plan 023) is a **tooling-and-evidence-only cut**: build serialization (`scripts/with-build-lock.mjs` — one `O_EXCL` lockfile serializing every emit/test leaf so concurrent compilers never expose a partial live `dist/`), corrected workspace coverage denominators (package-local `--test-coverage-include=dist/**`, evidence-based per-package thresholds with `protectedException` durable-leg rows), the machine-auditable release skip manifest (`scripts/release-skip-manifest.mjs` → `scripts/release-evidence.json` with `pass`/`skip`/`blocked`/`protected` states; required surfaces without evidence record `blocked` and fail the release gate), and stabilized quality gates (Biome 2.x `preset` config migration with zero lint diagnostics, deterministic timing-assertion barriers, machine-readable `lint-report.sarif` + `unused-report.json`). **No runtime code path, persisted shape, event schema, default, or exported declaration changed** (the plain compat gate at 0.2.3 shows the version literal only). Store compatibility with 0.2.2: **compatible in both directions** — no migration step; rollback = restore the 0.2.2 manifests/tag (stores never change; rollback reopens only the partial-`dist` race and the polluted coverage denominator, both CI/tooling defects, never data defects).
6
+
3
7
  ## 0.2.1 → 0.2.2 concurrent state and durability integrity (plan 022)
4
8
 
5
9
  Release **0.2.2** (plan 022) makes four concurrency/durability boundaries atomic or fail-loud. The API surface is **additive-only** (plain reviewed compat gate at 0.2.2: expected deltas are the version literal, `ModelRouterStateStore.reserveBudget`/`commitBudget`/`releaseBudget` plus `ModelRouterReservation`/`ModelRouterBudgets.reservationTtlMs`/`ModelRouterLimits.maxRateKeys`/`maxBudgetKeys` (memory + Postgres), `SessionRecord.version` with `appendSession` `expectedVersion`, `EventMultiplexerError` with code `ERR_PRISM_EVENT_MULTIPLEXER_SINGLE_CONSUMER`, and the `@arnilo/prism/testing/state-concurrency-conformance` subpath; no removal, no `--allow-break`). Three of the four changes tighten behavior where 0.2.1 silently accepted a race — concurrent hosts may now see an explicit conflict where 0.2.1 lost an update or oversubscribed a budget:
@@ -275,6 +275,26 @@ git push origin v0.1.2 # tag push triggers release.yml publish job (prove
275
275
 
276
276
  **Rollback notes.** `release:publish --version 0.1.2 --resume --report release-artifacts/publish-report.json` resumes an interrupted publication and skips only registry versions whose internal dependency fingerprint matches the local manifest. A failed package aborts the run with its status written to the report; re-run after fixing the cause. npm cannot unpublish the `0.1.2` line after 72 hours — a post-publication defect ships as a `0.1.x` patch (additive-only compat promise, `release:gate` enforced), or as a documented break in the next line with a `docs/migration.md` entry. `0.1.2` is store-compatible with `0.1.1` in **both directions** (no migration ran — same checksum-protected contract), so an operator may defer or roll back the patch without a database rollback.
277
277
 
278
+ ### 0.2.3 publish handoff (plan 023 Task 6)
279
+
280
+ **Decision: GO when the operator prerequisites below are recorded.** Release **0.2.3** (plan 023) is the build-coverage-and-release-evidence-integrity cut on the 0.2.x review-remediation line. API surface **additive-only** (plain reviewed compat gate at 0.2.3: delta is the version literal only — no export changes; baselines regenerated with `--update-baseline`, no `--allow-break`; freeze manifest `scripts/phase23-freeze-manifest.json`). Four tooling/evidence fixes, **no runtime contract change and no migration**: (1) **build serialization** — dependency-free `scripts/with-build-lock.mjs` serializes every emit/test leaf with one `O_EXCL` lockfile at `node_modules/.prism-build.lock` (pid + startedAt, read-back verified, stale-PID reclaim, `PRISM_BUILD_LOCK_TIMEOUT_MS` env override, fail-closed exit 1), so concurrent compilers can never expose a partial live `dist/`; the lock is never held by orchestrator scripts and `PRISM_BUILD_LOCK_HELD=1` prevents accidental nesting. **Caveat:** the lock only guards the wrapped leaves — a direct `tsc` invoked outside the wrapper can still race an importer, exactly like any external writer. (2) **corrected workspace coverage denominators** — workspace coverage runs use package-local `--test-coverage-include=dist/**` (imported core `dist` no longer pollutes package rows), the 60/70/75 core gate is unchanged, per-package line thresholds in `scripts/coverage-thresholds.json` are evidence-based (freeze-run minus 3 pp), env-gated durable-leg packages (`session-store-postgres`, `enterprise-postgres`, `memory`, `session-store-nats`) are `protectedException` rows shown separately, and `scripts/coverage-summary.json` is the machine-readable artifact the release gate reads. (3) **release skip manifest** — `scripts/release-skip-manifest.mjs` records every surface (`pass`/`skip`/`blocked`/`protected`, reason, required env names only) into `scripts/release-evidence.json`; a required surface with absent evidence records `blocked` and `release.mjs gate` fails closed — missing credentials/services can never convert into a green release. (4) **stabilized quality gates** — Biome 2.x `preset` config migration with zero lint diagnostics, the racy 150 ms MCP bridge timing assert replaced by a deterministic barrier, load-sensitive guards carry documented `ponytail:` ceilings, and `lint-report.sarif` + `unused-report.json` are machine-readable and CI-retained. Regression surface: `phase23-build-race` (8), `phase23-coverage` (4), `phase23-skip-manifest` (6), `phase23-quality-gates` (5), `phase23-security` (3, matrix items 4 and 12 by name) + packed plain-JS `security23.mjs` consumer. Exit gate green: npm test core + workspace + script gates, `sdk:ready` exit 0, audit 0 moderate, secret scans 0 findings, pack dry-run 50/50 twice byte-identical, plain reviewed compat gate at 0.2.3, protected Postgres durable conformance evidence, release-evidence manifest with zero blocked surfaces; evidence in `scripts/phase23-baseline.json` `exitGate`. **Rollback notes.** Rollback = restore the 0.2.2 manifests/tag — but that reopens the partial-`dist` race window and the polluted coverage denominator, so prefer fixing the failing host on 0.2.3. Nothing persisted changes shape, so downgrade is store-safe. (CI remediation 2026-08-14: `coding-security` joined the `protectedException` rows — its native-sandbox legs probe `unshare --net` NETNS at load and skip on GitHub Actions runners, so the host-captured freeze threshold can never be met in CI; measured 72.80 lines in CI vs 80.18 on a NETNS-capable host.)
281
+
282
+ ```bash
283
+ # Operator prerequisites recorded: clean tree at the v0.2.3 tag candidate, GPG key, npm OIDC publisher.
284
+ node scripts/release.mjs bump --from 0.2.2 --to 0.2.3 # already applied by Task 6; idempotent
285
+ npm test # core + workspace suites + all script gates (incl. phase23 suites)
286
+ npm run security:threat-suites # phase8-11 + phase20 + phase21 + phase22 + phase23 public-entry conformance
287
+ PRISM_TEST_POSTGRES_URL=postgres://postgres:prism@127.0.0.1:54329/prism_test npm run sdk:ready
288
+ node scripts/release.mjs gate --version 0.2.3 # plain reviewed gate at 0.2.3: version literal only, 0 breaking deltas
289
+ npm run pack:dry-run # twice; diff reports — deterministic
290
+ npm audit --audit-level=moderate
291
+ npm run release:check -- --version 0.2.3 --report /tmp/prism-0.2.3-preflight.json
292
+ npm run release:publish -- --version 0.2.3 --dry-run --allow-dirty --allow-untagged --report /tmp/prism-0.2.3-dry-run.json
293
+ # run the dry-run twice and diff the reports: deterministic, byte-identical
294
+ ```
295
+
296
+ Protected evidence (never a passing skip): the durable state-concurrency legs (Postgres `prism_phase23_*` schemas for sessions/checkpoints/events + enterprise router reservations/idempotency — `npm run test:postgres` under `PRISM_TEST_POSTGRES_URL`), the phase23 public-entry build-race + coverage-denominator conformance, and the live canaries (provider OIDC/OPA, MCP, A2A, Brave — always `protected` rows in the manifest, never `pass`). The release skip manifest names every skip class with its required env; missing protected evidence records 0.2.3 as **blocked**, never a passing skip.
297
+
278
298
  ### 0.2.2 publish handoff (plan 022 Task 6)
279
299
 
280
300
  **Decision: GO when the operator prerequisites below are recorded.** Release **0.2.2** (plan 022) is the concurrent-state-and-durability-integrity cut on the 0.2.x review-remediation line. API surface **additive-only** vs 0.2.1 (plain reviewed compat gate at 0.2.2: deltas are the version literal plus `ModelRouterStateStore.reserveBudget`/`commitBudget`/`releaseBudget`, `ModelRouterReservation`, `ModelRouterBudgets.reservationTtlMs`, `ModelRouterLimits.maxRateKeys`/`maxBudgetKeys`, `SessionRecord.version` with `appendSession` `expectedVersion`, `EventMultiplexerError`, and the `@arnilo/prism/testing/state-concurrency-conformance` subpath — no removal; baselines regenerated with `--update-baseline`, no `--allow-break`; freeze manifest `scripts/phase22-freeze-manifest.json` records per-task evidence tokens). Four behavior tightenings documented in `docs/migration.md` `0.2.1 → 0.2.2`: (1) **atomic model-budget reservation** — `reserveBudget` at admission (used + reserved + requested <= window max, `{reservationId, fencingToken, admitted, retryAfterMs?}`), `commitBudget`/`releaseBudget` at outcome, TTL expiry (default 60 s) with late commits reconciled as `unknownUsage: true`; rate/budget key maps capped (4,096 default / 65,536 hard) with LRU eviction that never drops a held-reservation row; durable reservations live in a new `reservations` JSONB column (enterprise migration 003). (2) **atomic conversation metadata** — `SessionRecord.version` + `appendSession` `expectedVersion` (`0` create-only, `N>0` exact-CAS update-only, omitted = legacy last-write-wins); stale writes throw `SessionMetadataConflictError` `metadata_conflict` (versions only, HTTP 409); concurrent create/branch/archive single-statement with branch caps inside the CAS, archive wins, deleted rows never resurrect (migration 008). (3) **single-consumer EventMultiplexer** — second concurrent `subscribe()` throws `EventMultiplexerError` `ERR_PRISM_EVENT_MULTIPLEXER_SINGLE_CONSUMER`. (4) **restart-stable NATS durable identity + bounded non-durable active-run registries** — durable name exactly `prism_<hmac16>`, crash-resume continues from the last ack, orphaned 0.2.1 random-suffixed consumers reclaimed on clean stop; workflow active-run registry sweeps aborted entries and fails closed at the 512 cap. New regression surface: `scripts/phase22-security.test.mjs` (4 blockers + gate accounting over built public entrypoints, wired into `security:threat-suites`), packed plain-JS `security22.mjs` consumer in install-smoke, the `@arnilo/prism/testing/state-concurrency-conformance` harness (7 probes; memory leg in npm test, durable legs in `test:postgres` and the NATS seam; zero timing-only sleeps), and the `scripts/phase22-conformance.test.mjs` gate in the `test:postgres` chain. Store compatibility with 0.2.1: **forward-only migrations** (008 + 003), see `docs/migration.md` for rollback risk. Exit gate green: npm test core + workspace + script gates (incl. phase21-freeze done-phase + phase22 conformance), `sdk:ready` exit 0, audit 0 moderate, secret scans 0 findings, pack dry-run 50/50 twice byte-identical, plain reviewed compat gate at 0.2.2, protected OIDC/OPA evidence + durable state-concurrency evidence; evidence in `scripts/phase22-baseline.json` `exitGate`. Rollback = restore the 0.2.1 manifests/tag — but that reopens all four race windows, so prefer fixing the failing host on 0.2.2.
@@ -747,10 +767,63 @@ Prism uses one tool for formatting and linting — [Biome](https://biomejs.dev)
747
767
  | `npm run lint` | `biome lint .` — fails on any lint error (warnings are non-fatal). |
748
768
  | `npm run format:check` | `biome format .` — fails if any file is unformatted. |
749
769
  | `npm run format` | `biome format --write .` — normalizes formatting in place. |
750
- | `npm run test:coverage` | `node --test --experimental-test-coverage` over the core suite with enforced minimums: **lines 60%**, **functions 70%**, **branches 75%** (current baseline ≈ 64 / 72 / 79). Excludes `__tests__/`, `node_modules/`, and `scripts/` from the report. |
770
+ | `npm run test:coverage` | `node --test --experimental-test-coverage` over the core suite with enforced minimums: **lines 60%**, **functions 70%**, **branches 75%** (current baseline ≈ 90.5 / 84.2 / 90.6), then `scripts/coverage-summary.mjs` + the `phase23-coverage` gate. Excludes `__tests__/`, `node_modules/`, `scripts/`, and `packages/` from the core report. |
751
771
 
752
772
  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 coverage thresholds in `package.json` `test:coverage` as the baseline climbs.
753
773
 
774
+ ### Coverage denominators and per-package thresholds
775
+
776
+ 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 coverage run now passes `--test-coverage-include=dist/**`, so only `packages/<name>/dist/**` counts (the core run and its 60/70/75 gate are unchanged).
777
+
778
+ | Fact | Value |
779
+ | --- | --- |
780
+ | Workspace include filter | `--test-coverage-include=dist/**` per package (package-local denominator) |
781
+ | Per-package gate | `lines >= threshold` from `scripts/coverage-thresholds.json` (frozen 2026-08-14 = recompute − 3pp, two runs were byte-identical); branches/functions recorded, not gated |
782
+ | Protected exceptions | `@arnilo/prism-session-store-postgres`, `@arnilo/prism-enterprise-postgres`, `@arnilo/prism-memory`, `@arnilo/prism-session-store-nats` — durable legs need `PRISM_TEST_POSTGRES_URL` or a real NATS server; plus `@arnilo/prism-coding-security` — native-sandbox legs probe `unshare --net` (NETNS) and skip on CI runners (host runs exercise them); exempt from the gate, reported separately with the reason |
783
+ | Artifact | `scripts/coverage-summary.json` (gitignored, CI-retained): per-package `lines`/`branches`/`functions`/`denominatorFiles`/`threshold`/`pass`/`protectedException` + `belowThreshold` |
784
+ | 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 |
785
+ | Overrides | `PRISM_COVERAGE_THRESHOLDS`, `PRISM_COVERAGE_ARTIFACT` (used by the gate regression) |
786
+
787
+ A new workspace package must add an evidence-based threshold entry (or a `protectedException` reason) to `scripts/coverage-thresholds.json` before `test:coverage` passes.
788
+
789
+ ### Release evidence and protected skips
790
+
791
+ `npm run release:evidence` (run automatically at the start of `npm run release:gate`, and therefore at the end of `npm run sdk:ready`) aggregates every test surface into `scripts/release-evidence.json` — the machine-auditable release skip manifest. It records env var **names only, never values** (the manifest is retained and uploaded by CI).
792
+
793
+ | State | Meaning | Gate effect |
794
+ | --- | --- | --- |
795
+ | `pass` | the surface ran and its recorded evidence is green | pass |
796
+ | `skip` | a documented partial-skip surface (must carry `reason` + `requiredEnv`) | pass (defensive: an unexplained `skip` fails) |
797
+ | `protected` | a documented, permitted gap with a reason (+ required env where applicable) | pass, always visible |
798
+ | `blocked` | a required release surface cannot be attested (required env absent, or evidence missing) | **fail closed** — `release.mjs gate` refuses to release |
799
+
800
+ Surfaces: core `npm test` (counts and the skip total come from the latest `phase*-baseline.json` `exitGate.counts` — currently 33 protected/live skips, a frozen floor), `security:threat-suites`, every workspace suite (evidence from `scripts/coverage-summary.json` of the same run; `protectedException` packages are named with their reason), `test:postgres` durable conformance (**required**: `PRISM_TEST_POSTGRES_URL` must be set when `release:gate` runs — the release workflow's verify job declares it on the `release:gate` phase only, so the env never leaks into the env-gated docs demo / durable integration suites of `npm test`, and the `postgres-integration` job runs the suite against a real server; a local release must set it too, exactly like the phase-22 release profile), `test:nats` real JetStream legs (protected, 0.3.0), the `PRISM_LIVE_PROVIDER_TESTS` provider legs (protected, per package), and the four live canaries from `scripts/live-canary.mjs` (provider/MCP/A2A/web; run by the scheduled `live-canaries` workflow with real credentials — recorded `protected`, never `pass`). The manifest cross-references the latest baseline's `exitGate`/`protectedEvidence` so per-phase records stay the source of truth.
801
+
802
+ Override `PRISM_RELEASE_EVIDENCE` to redirect the manifest (used by the gate regression). The manifest is gitignored and CI-retained (`release-evidence` artifact). A release cannot ship with a required env absent and unexplained — the operator sees every blocked surface in the retained manifest.
803
+
804
+ ### Quality-gate reports and the Biome baseline
805
+
806
+ `npm run lint` runs Biome 2.x with the canonical config (`linter.rules.preset: "recommended"` — the deprecated `recommended: true` key is gone; `npx biome migrate --write` performs the rewrite) and writes a machine-readable SARIF report to `scripts/lint-report.sarif` in the same run (stable `--reporter=sarif`; the experimental `--reporter=json` schema is not used). The repo target is **zero** lint diagnostics; every remaining intentional diagnostic carries a justified `biome-ignore lint/<rule>: reason` comment (shell-interpolated strings, verbatim upstream fixtures, and literal grep targets in tests). Unused-code diagnostics are auto-fixed by `biome lint --write --unsafe .`; public-but-unused exports are never removed by Biome — they route to the unused-code sweep.
807
+
808
+ `npm run sweep:unused` (also part of `npm test`) runs the tsc `--noUnusedLocals/--noUnusedParameters` sweep across the core and every workspace tsconfig and writes both `scripts/unused-sweep-report.txt` (human) and `scripts/unused-report.json` (`--json`, machine-readable: per-tsconfig counts + the dead-export scan). It stays non-blocking by design. Both reports are gitignored and CI-retained (`quality-gate-reports` artifact, 30 days).
809
+
810
+ Timing assertions in tests follow a deterministic-barrier policy: racy wall-clock deltas are replaced by awaiting the conflicting operation and asserting its observable outcome (plus a test-level `timeout` as the anti-hang guard). The few remaining wall-clock guards are generous anti-hang/proof-of-promptness bounds, each marked with a `ponytail:` comment naming its ceiling; the document-reader budget test uses the recorded `scripts/budgets.json` ceilings.
811
+
812
+ ## Build serialization
813
+
814
+ `dist/` is compiled by `tsc` in many small writes, so a concurrent build and test in the same working tree could race — a test importing `@arnilo/prism` mid-emit could observe a partially-written module (reproduced during the 0.2.3 review). Emit-producing leaves (`tsc` builds) and dist-consuming test leaves (`node --test dist/__tests__/*.test.js`) are therefore serialized through a dependency-free lock: `node scripts/with-build-lock.mjs <command>` acquires an `O_EXCL` lockfile at `node_modules/.prism-build.lock` (contents: holder `pid` + timestamp, no secrets), waits with a 100ms backoff, and fails closed — never proceeding without the lock. A stale lock whose holder PID is dead is reclaimed; a live lock is never stolen. Acquisition is leaf-only (never the `npm test`/`sdk:ready` orchestrators), so nested `npm run build` children cannot deadlock.
815
+
816
+ | Fact | Value |
817
+ | --- | --- |
818
+ | Lock path | `node_modules/.prism-build.lock` (repo-root-relative; workspaces share the same lock) |
819
+ | Timeout | 120s default; override with `PRISM_BUILD_LOCK_TIMEOUT_MS` |
820
+ | Retry | 100ms backoff; stale-PID reclaim via `process.kill(pid, 0)` |
821
+ | Fail-closed | acquisition error or timeout exits non-zero, nothing runs |
822
+ | Wrapped | `build:core`, every workspace `build`, the `node --test` runs in `test`/`test:coverage`/workspace tests, `coverage-summary.mjs`, the script-gate `node --test` run (the `phase*-conformance`/`phase*-security` gates import `@arnilo/prism` from `dist`) |
823
+ | 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) |
824
+
825
+ 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).
826
+
754
827
  ## Dependency major-upgrade isolation
755
828
 
756
829
  Major dependency upgrades are **isolated, compatibility-tested changes — never bundled into a feature release.** A major bump (TypeScript, `@types/node`, `diff`, or any third-party runtime dependency and its successors) ships as its own commit/PR that runs `npm run sdk:ready` plus packed-install evidence, and is reviewed separately from feature work. Release commits contain no unreviewed major bumps.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arnilo/prism",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Agent harness for AI providers, agents, sessions, and tools.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -142,15 +142,15 @@
142
142
  "packages/prism-*"
143
143
  ],
144
144
  "scripts": {
145
- "build:core": "tsc",
145
+ "build:core": "node scripts/with-build-lock.mjs tsc",
146
146
  "clean": "rm -rf dist packages/*/dist",
147
147
  "build": "npm run build:core && npm run build --workspaces --if-present",
148
148
  "typecheck": "npm run build && npm run typecheck --workspaces --if-present && tsc -p examples --noEmit",
149
- "sweep:unused": "node scripts/sweep-unused.mjs",
150
- "test": "npm run build && node --test dist/__tests__/*.test.js && node --test scripts/release-gate.test.mjs scripts/tooling-gate.test.mjs scripts/budget-gate.test.mjs scripts/phase8-conformance.test.mjs scripts/phase9-conformance.test.mjs scripts/phase10-conformance.test.mjs scripts/phase11-conformance.test.mjs scripts/phase11-freeze.test.mjs scripts/phase12-freeze.test.mjs scripts/phase13-freeze.test.mjs scripts/phase14-freeze.test.mjs scripts/phase15-freeze.test.mjs scripts/phase16-freeze.test.mjs scripts/phase17-freeze.test.mjs scripts/phase18-freeze.test.mjs scripts/phase19-freeze.test.mjs scripts/phase20-freeze.test.mjs scripts/phase21-freeze.test.mjs scripts/benchmark-0.1.0.test.mjs scripts/sweep-unused.test.mjs scripts/e2e-enterprise-journey.test.mjs scripts/e2e-coding-journey.test.mjs && npm run test --workspaces --if-present",
151
- "test:coverage": "node --test --experimental-test-coverage --test-coverage-lines=60 --test-coverage-functions=70 --test-coverage-branches=75 --test-coverage-exclude='**/__tests__/**' --test-coverage-exclude='**/node_modules/**' --test-coverage-exclude='**/scripts/**' --test-coverage-exclude='**/packages/**' --test-coverage-exclude='**/examples/**' dist/__tests__/*.test.js && node scripts/coverage-summary.mjs",
152
- "coverage:summary": "node scripts/coverage-summary.mjs",
153
- "lint": "biome lint .",
149
+ "sweep:unused": "node scripts/sweep-unused.mjs --json",
150
+ "test": "npm run build && node scripts/with-build-lock.mjs node --test dist/__tests__/*.test.js && node scripts/with-build-lock.mjs node --test scripts/release-gate.test.mjs scripts/tooling-gate.test.mjs scripts/budget-gate.test.mjs scripts/phase8-conformance.test.mjs scripts/phase9-conformance.test.mjs scripts/phase10-conformance.test.mjs scripts/phase11-conformance.test.mjs scripts/phase11-freeze.test.mjs scripts/phase12-freeze.test.mjs scripts/phase13-freeze.test.mjs scripts/phase14-freeze.test.mjs scripts/phase15-freeze.test.mjs scripts/phase16-freeze.test.mjs scripts/phase17-freeze.test.mjs scripts/phase18-freeze.test.mjs scripts/phase19-freeze.test.mjs scripts/phase20-freeze.test.mjs scripts/phase21-freeze.test.mjs scripts/benchmark-0.1.0.test.mjs scripts/sweep-unused.test.mjs scripts/e2e-enterprise-journey.test.mjs scripts/e2e-coding-journey.test.mjs scripts/phase23-quality-gates.test.mjs && node --test scripts/phase23-build-race.test.mjs && npm run test --workspaces --if-present",
151
+ "test:coverage": "node scripts/with-build-lock.mjs node --test --experimental-test-coverage --test-coverage-lines=60 --test-coverage-functions=70 --test-coverage-branches=75 --test-coverage-exclude='**/__tests__/**' --test-coverage-exclude='**/node_modules/**' --test-coverage-exclude='**/scripts/**' --test-coverage-exclude='**/packages/**' --test-coverage-exclude='**/examples/**' dist/__tests__/*.test.js && node scripts/with-build-lock.mjs node scripts/coverage-summary.mjs && node --test scripts/phase23-coverage.test.mjs && node --test scripts/phase23-skip-manifest.test.mjs",
152
+ "coverage:summary": "node scripts/with-build-lock.mjs node scripts/coverage-summary.mjs",
153
+ "lint": "biome lint . --reporter=sarif --reporter-file=scripts/lint-report.sarif",
154
154
  "format": "biome format --write .",
155
155
  "format:check": "biome format .",
156
156
  "pack:dry-run": "npm pack --dry-run && npm run pack:dry-run --workspaces --if-present",
@@ -158,9 +158,10 @@
158
158
  "release:dry-run": "npm run sdk:ready",
159
159
  "release:check": "node scripts/release.mjs check",
160
160
  "release:publish": "node scripts/release.mjs publish",
161
+ "release:evidence": "node scripts/release-skip-manifest.mjs",
161
162
  "sdk:ready": "npm run typecheck && npm run lint && npm run format:check && npm test && npm run test:coverage && npm run pack:dry-run && npm run release:gate",
162
- "release:gate": "node scripts/release.mjs gate",
163
- "security:threat-suites": "node --test scripts/phase8-conformance.test.mjs scripts/phase9-conformance.test.mjs scripts/phase10-conformance.test.mjs scripts/phase11-conformance.test.mjs scripts/phase20-security.test.mjs scripts/phase21-security.test.mjs scripts/phase22-security.test.mjs"
163
+ "release:gate": "node scripts/release-skip-manifest.mjs && node scripts/release.mjs gate",
164
+ "security:threat-suites": "node --test scripts/phase8-conformance.test.mjs scripts/phase9-conformance.test.mjs scripts/phase10-conformance.test.mjs scripts/phase11-conformance.test.mjs scripts/phase20-security.test.mjs scripts/phase21-security.test.mjs scripts/phase22-security.test.mjs scripts/phase23-security.test.mjs"
164
165
  },
165
166
  "devDependencies": {
166
167
  "@biomejs/biome": "^2.5.5",