@arnilo/prism 0.2.1 → 0.2.2

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.
@@ -17,22 +17,24 @@ Do not put secrets, prompts, or raw OpenRouter keys into diagnostics. Do not hon
17
17
  | `createModelRouter({ resolver, stateStore?, ... })` | Wraps host `ProviderResolver`; omit `stateStore` for in-process memory state or pass durable async state. |
18
18
  | `allowList.providers` / `allowList.models` | Exact provider id / model id or `provider/model` |
19
19
  | `allowedResidencies` | Request residency must match when configured |
20
- | `budgets` / per-call `maxTokens` / `maxCostUsd` | Finite non-negative ceilings; `recordUsage` charges |
20
+ | `budgets` / per-call `maxTokens` / `maxCostUsd` | Finite non-negative ceilings; requests with a per-call cap reserve it atomically at admission; `recordUsage` commits actuals against the reservation |
21
+ | `budgets.reservationTtlMs` | How long an admission reservation pins capacity (default 60s); a run that outlives it reconciles as unknown usage |
21
22
  | `rateLimit` | Per identity+model key window |
22
23
  | `circuit` | Failure threshold + cooldown; keys capped |
23
24
  | `fallbacks` | Ordered candidates after primary; total attempts capped |
24
25
  | `allowOpenRouterRouting` | Default `false`; when false, routing metadata is stripped |
25
26
  | `onDiagnostics` | Optional redacted hook (e.g. policy ledger evidence ref) |
26
- | `router.resolve({ model, identity?, residency?, ... })` | Rich async selection |
27
+ | `router.resolve({ model, identity?, residency?, maxTokens?, ... })` | Rich async selection; returns `budgetReservation` when a per-request cap was reserved |
27
28
  | `router.providerSource` | Sync facade only for memory state; with `stateStore` it throws `ERR_PRISM_MODEL_ROUTER_ASYNC_STATE` rather than bypass durable checks. |
28
29
 
29
- Frozen caps (default / hard): attempts `3 / 8`, circuit keys `1,024 / 16,384`, diagnostics `8 KiB / 64 KiB`.
30
+ Frozen caps (default / hard): attempts `3 / 8`, circuit keys `1,024 / 16,384`, diagnostics `8 KiB / 64 KiB`, rate keys `4,096 / 65,536`, budget keys `4,096 / 65,536`.
30
31
 
31
32
  ## Outputs / response / events
32
33
 
33
- - `ModelRouterResolveResult` — selected `provider` + possibly stripped `model`, `diagnostics`, and `providerRequestPolicy`.
34
- - Deny throws `ModelRouterError` with code + redacted `diagnostics` (allow-list/residency/budget fail closed without calling resolver).
35
- - `await recordOutcome({ identity, success, circuitProbeToken? })` opens/closes circuits; `await recordUsage({ identity, ... })` advances budgets. Pass the probe token returned by `resolve` for a half-open outcome.
34
+ - `ModelRouterResolveResult` — selected `provider` + possibly stripped `model`, `diagnostics`, and `providerRequestPolicy`; `budgetReservation` carries the admission reservation handle when the request had a per-call budget cap.
35
+ - Deny throws `ModelRouterError` with code + redacted `diagnostics` (allow-list/residency/budget fail closed without calling resolver); budget denies carry `details.retryAfterMs`.
36
+ - `await recordOutcome({ identity, success, circuitProbeToken? })` opens/closes circuits; `await recordUsage({ identity, budgetReservation?, ... })` commits the reservation against actual usage (pass the handle returned by `resolve`) or advances budgets directly. Pass the probe token returned by `resolve` for a half-open outcome.
37
+ - A reservation whose TTL elapses before `recordUsage` charges the **reserved** amount and emits one redacted `unknown_usage` diagnostic (deterministic reconciliation, never a silent drop).
36
38
 
37
39
  ## Request/response example
38
40
 
@@ -139,10 +141,11 @@ await router.recordOutcome({ identity, provider, model, success: true, latencyMs
139
141
  ## Security and performance notes
140
142
 
141
143
  - Allow-list and residency denies never call the underlying resolver.
142
- - Without `stateStore`, budget/rate/circuit state is process-local, memory-capped, and oldest keys evict. It is not a cross-replica production path.
143
- - With `stateStore: createPostgresEnterpriseState(...).modelRouter`, rate/budget updates and circuit probes are atomic across replicas, use database time, and are owner/principal/provider/model scoped. Router calls become asynchronous and require verified identity.
144
- - Diagnostics carry identity refs and attempt outcomes only — no prompts/secrets. Durable state stores at most bounded numeric/timestamp/token material, never prompts or credentials.
145
- - Selection is O(attempts × state operations); no provider network I/O happens inside state updates. Recorded 0.0.23 PostgreSQL p95 point operations stayed under 50 ms and cursor/cleanup pages under 100 ms on the documented fixture.
144
+ - Budget admission is **reservation-based** when the request carries a per-request cap: `resolve` atomically reserves the full cap against remaining capacity (`max − used − reserved`) and returns a `budgetReservation` handle; parallel admissions can never collectively exceed the reserved budget. Commit the handle in `recordUsage` with the actual tokens/cost (a negative remainder is released back); release happens automatically on internal denial (rate limit, circuit open, provider miss), on TTL expiry, or on an explicit late commit (which charges the reserved amount as unknown usage). Requests without a per-request cap keep read-then-compare admission (`used >= cap` denies) and are outside the reservation guarantee.
145
+ - Without `stateStore`, budget/rate/circuit state is process-local, memory-capped (rate/budget/circuit keys), and LRU-evicts on insert; a held reservation's budget row is never evicted. It is not a cross-replica production path.
146
+ - With `stateStore: createPostgresEnterpriseState(...).modelRouter`, rate/budget updates, reservations, and circuit probes are atomic across replicas, use database time, and are owner/principal/provider/model scoped. Router calls become asynchronous and require verified identity.
147
+ - Diagnostics carry identity refs and attempt outcomes only — no prompts/secrets, tokens, or reservation material. Durable state stores at most bounded numeric/timestamp/token material, never prompts or credentials.
148
+ - Selection is O(attempts × state operations); no provider network I/O happens inside state updates. Reservation is one atomic UPSERT (denial adds one retry-after query); commit/release are O(1) row updates. Recorded 0.0.23 PostgreSQL p95 point operations stayed under 50 ms and cursor/cleanup pages under 100 ms on the documented fixture.
146
149
  - Raising hard caps requires a reviewed release update with tests and docs.
147
150
 
148
151
  ## Related APIs
@@ -15,7 +15,7 @@ Current contract groups:
15
15
  - Extensions/middleware: `ExtensionLifecycleEventName`, `ExtensionEvent`, `Extension`, `ExtensionAPI`, `MiddlewareHookName`, `Middleware`, `MiddlewareNext`, `MiddlewareRegistry`
16
16
  - Configuration/manifests: `ConfigProvider`, `ConfigLayer`, `ConfigLoadContext`, `PrismManifest`, `ManifestContributionDeclaration`, `ManifestResourceDeclaration`, `ManifestContributionKind`
17
17
  - Stores/resources/settings/credentials/compaction/retry/cache helpers: `SessionEntry`, `SessionStore`, `StoreFactory`, `Resource`, `ResourceLoader`, `ResourceLoadContext`, `SettingsProvider`, `CredentialRequest`, `Credential`, `CredentialResolver`, `CompactionStrategy`, `CompactionContext`, `CompactionResult`, `CompactionOptions`, `CompactionMiddlewarePayload`, `CompactionEntryData`, `DefaultCompactionStrategyOptions`, `RetryPolicy`, `RetryContext`, `RetryDecision`, `RetryOptions`, `RetryMiddlewarePayload`, `DefaultRetryPolicyOptions`, `CacheUsageReport`, `sanitizeCacheKey`, `mapCacheRetention`, `applyCacheControl`, `cacheHitRate`, `cacheSavings`, `cacheUsageReport`
18
- - Production persistence (adapter-facing): `ProductionPersistenceStore` (optional `lifecycle`), `CheckpointStore`, `CheckpointKey`, `CheckpointSaveInput`, `CheckpointRecord`, `CheckpointQuery`, `LeaseStore`, `LeaseKey`, `LeaseAcquireInput`, `LeaseClaimInput`, `LeaseRecord`, `PersistencePage`, `PersistenceQuery`, `OwnershipScope`, `SessionRecord`, `SessionQuery`, `BranchRecord`, `BranchQuery`, `SessionEntryQuery`, `RunRecord`, `RunQuery`, `RunFeedbackRecord`, `RunFeedbackStore`, `RunFeedbackQuery`, `AgentEventRecord`, `AgentEventQuery`, `ToolCallRecord`, `ToolCallQuery`, `UsageRecord`, `UsageQuery`, `AgentDefinitionRecord`, `AgentDefinitionQuery`, `RetentionPolicy`, `RetentionPolicyQuery`, `MigrationRecord`, `MigrationQuery`, `PersistenceLifecycleStore`, `LegalHoldRecord`, `TenantQuota`
18
+ - Production persistence (adapter-facing): `ProductionPersistenceStore` (optional `lifecycle`), `CheckpointStore`, `CheckpointKey`, `CheckpointSaveInput`, `CheckpointRecord`, `CheckpointQuery`, `LeaseStore`, `LeaseKey`, `LeaseAcquireInput`, `LeaseClaimInput`, `LeaseRecord`, `PersistencePage`, `PersistenceQuery`, `OwnershipScope`, `SessionRecord`, `SessionQuery`, `SessionMetadataConflict`, `SessionMetadataConflictError`, `SESSION_METADATA_CONFLICT_CODE`, `isSessionMetadataConflict`, `BranchRecord`, `BranchQuery`, `SessionEntryQuery`, `RunRecord`, `RunQuery`, `RunFeedbackRecord`, `RunFeedbackStore`, `RunFeedbackQuery`, `AgentEventRecord`, `AgentEventQuery`, `ToolCallRecord`, `ToolCallQuery`, `UsageRecord`, `UsageQuery`, `AgentDefinitionRecord`, `AgentDefinitionQuery`, `RetentionPolicy`, `RetentionPolicyQuery`, `MigrationRecord`, `MigrationQuery`, `PersistenceLifecycleStore`, `LegalHoldRecord`, `TenantQuota`
19
19
  - Identity: `Principal`, `AgentIdentity`, `IdentityVerifier`, `assertIdentityActive`, `narrowIdentity`, `ownershipFromIdentity`, `assertIdentityMatchesOwnership`, `assertIdentityPropagation`, `identityTelemetryAttributes`, `resolveRunIdentity`, `IdentityError`, identity limit constants
20
20
 
21
21
  ## When to use it
@@ -148,11 +148,11 @@ Important request shapes:
148
148
  | `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). |
149
149
  | `LeaseStore` | Atomic acquire/renew/release/get by namespace and key, with opaque claim tokens, expiry, ownership scope, and monotonically increasing takeover fences. `createMemoryLeaseStore()` is the reference implementation. |
150
150
  | `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. |
151
- | `EventMultiplexer<T>` | Generic bounded fan-in from async sources. `createEventMultiplexer()` owns queue limits, overflow policy, abort, source teardown, and close behavior. |
151
+ | `EventMultiplexer<T>` | Generic bounded fan-in from async sources. `createEventMultiplexer()` owns queue limits, overflow policy, abort, source teardown, and close behavior. Single-consumer contract: a second concurrent `subscribe()` throws `EventMultiplexerError` (`ERR_PRISM_EVENT_MULTIPLEXER_SINGLE_CONSUMER`); the slot frees when the active consumer completes/is `return()`ed at a yield or the multiplexer closes. `observe` fan-in is unchanged (broadcast happens at the source). |
152
152
  | `PersistencePage<T>` | Cursor-paginated result page: `items`, optional `nextCursor`, optional `total`. |
153
153
  | `PersistenceQuery` | Common pagination controls: `cursor?`, `limit?`, `order?: "asc" \| "desc"`. |
154
154
  | `OwnershipScope` | Multi-tenant scope: `tenantId?`, `accountId?`, `userId?`. Included in records and queries. |
155
- | `SessionRecord` / `SessionQuery` | Stored session and query filters (parent, agent definition, retention policy, timestamps, ownership). |
155
+ | `SessionRecord` / `SessionQuery` | Stored session and query filters (parent, agent definition, retention policy, timestamps, ownership). `SessionRecord.version` (with `appendSession` `expectedVersion`) is the optimistic metadata CAS: 0 = create-only, N = exact-version update; mismatch throws `SessionMetadataConflictError` (`metadata_conflict`). |
156
156
  | `SessionIndex` / `SessionSearchQuery` / `SessionSearchHit` | Bounded optional session search seam (`search` / `SessionStore.searchSessions?`). Filters: workspace (`metadata.workspaceRoot`), time, provider/model, label/summary, optional FTS `query`, ownership. Hits return `sessionId` + optional `leafId` for resume; never credentials. Caps via `resolveSessionSearchQuery` / `DEFAULT_*` / `HARD_MAX_*` session-search constants. |
157
157
  | `contextBudget` / `getContextBudgetReport` / `ContextBudgetError` | Opt-in assembler budget on `AssembleProviderInputOptions`; deterministic eviction; omission report in `ProviderRequest.metadata` (kinds/ids/sizes only). |
158
158
  | `AgentSession.steer` / `SteerOptions` / pending-steer caps | Mid-run enqueue into active run; optional `softInterrupt`; default 8 msgs / 64 KiB UTF-8. |
@@ -68,6 +68,7 @@ Run `npm run clean` explicitly after deleting source files or switching branches
68
68
  | `@arnilo/prism/providers/media` | `dist/providers/media.{js,d.ts}` |
69
69
  | `@arnilo/prism/testing/provider-conformance` | `dist/testing/provider-conformance.{js,d.ts}` |
70
70
  | `@arnilo/prism/testing/agent-event-source-conformance` | `dist/testing/agent-event-source-conformance.{js,d.ts}` |
71
+ | `@arnilo/prism/testing/state-concurrency-conformance` | `dist/testing/state-concurrency-conformance.{js,d.ts}` |
71
72
  | `@arnilo/prism/testing/session-store-conformance` | `dist/testing/session-store-conformance.{js,d.ts}` |
72
73
  | `@arnilo/prism/testing/compaction-conformance` | `dist/testing/compaction-conformance.{js,d.ts}` |
73
74
  | `@arnilo/prism/testing/tool-conformance` | `dist/testing/tool-conformance.{js,d.ts}` |
@@ -274,6 +275,25 @@ git push origin v0.1.2 # tag push triggers release.yml publish job (prove
274
275
 
275
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.
276
277
 
278
+ ### 0.2.2 publish handoff (plan 022 Task 6)
279
+
280
+ **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.
281
+
282
+ ```bash
283
+ # Operator prerequisites recorded: clean tree at the v0.2.2 tag candidate, GPG key, npm OIDC publisher.
284
+ npm test # core + workspace suites + all script gates (incl. phase22 conformance)
285
+ npm run security:threat-suites # phase8-11 + phase20 + phase21 + phase22 public-entry conformance
286
+ npm run sdk:ready # typecheck, lint, format, test, coverage, pack, release:gate
287
+ node scripts/release.mjs gate --version 0.2.2 # plain reviewed additive gate, 0 breaking deltas
288
+ npm run pack:dry-run # twice; diff reports — deterministic
289
+ npm audit --audit-level=moderate
290
+ npm run release:check -- --version 0.2.2 --report /tmp/prism-0.2.2-preflight.json
291
+ npm run release:publish -- --version 0.2.2 --dry-run --allow-dirty --allow-untagged --report /tmp/prism-0.2.2-dry-run.json
292
+ # run the dry-run twice and diff the reports: deterministic, byte-identical
293
+ ```
294
+
295
+ Protected evidence (never a passing skip): live OIDC JWKS through the default pinned path (`createOidcIdentityVerifier` against a real public IdP — real DNS/TLS/JWKS document, e.g. `https://login.microsoftonline.com/common/discovery/v2.0/keys`, success proven by a key-lookup miss after a 200 fetch), live OPA (dockerized `openpolicyagent/opa`, default pinned path fails closed `ssrf_denied`), and the durable state-concurrency legs (Postgres `prism_phase22_*` schemas for sessions/checkpoints/events + enterprise router reservations/idempotency, NATS restart-durable resume against the seam). Missing protected evidence records 0.2.2 as **blocked**, never a passing skip.
296
+
277
297
  ### 0.2.1 publish handoff (plan 021 Task 8)
278
298
 
279
299
  **Decision: GO when the operator prerequisites below are recorded.** Release **0.2.1** (plan 021) is the provider-completion and outbound-trust-boundaries cut on the 0.2.x review-remediation line. API surface **additive-only** vs 0.2.0 (plain reviewed compat gate at 0.2.1: the only deltas are the version literal and `@arnilo/prism-mcp` transport helpers `boundResponse`/`defaultResolver`/`isLoopbackAddress`/`isLoopbackHostname`/`normalizeHostname`/`raceAbort`/`requestPinned`/`resolvePinnedAddress` becoming re-exports of the lifted core primitives — same names/signatures, no removal; baselines regenerated with `--update-baseline`, no `--allow-break`; freeze manifest `scripts/phase21-freeze-manifest.json` machine-checks each task's diff and the preserved surface). Five documented security-motivated behavior tightenings in `docs/migration.md` `0.2.0 → 0.2.1`: (1) **strict stream completion is the shared default** (`strictCompletion: true` in `createOpenAICompatibleProvider`; explicit `false` stays the documented opt-out; truncated streams fail `ProviderTransportError` `incomplete_delta` instead of a successful `providerDone`; applies to Azure/Bedrock/Vertex/OpenRouter/ZAI/NeuralWatt); (2) **bounded success bodies** — additive `readBoundedResponseJson` (65,536-byte ceiling, depth 32, properties 4096, shape gate, abort, redacted errors, `response_body_shape` code) replaces unbounded `response.json()` on all ten model-discovery sites plus NeuralWatt quota, Alibaba embeddings, OpenAI uploads, and both OAuth success paths; (3) **DNS-pinned OIDC JWKS/OPA/content fetch, redirects rejected** — core `pinnedFetch` (one resolve, 1–32 bound, per-candidate SSRF validation, pinned-lookup socket) serves the default JWKS, OPA decision, and content/media paths; 3xx fails `MediaContentError` `redirect`; private/metadata/loopback fails `ssrf_denied`; MCP re-exports the lifted helpers byte-identically; (4) **shared bounded OAuth device/token polling** — core `pollDeviceCodeToken` serves provider-openai and credentials-node with equivalent cadence/backoff/redaction; (5) **edge fixes** — Azure/Vertex credential-once, Bedrock duplicate-case/repeated-query SigV4 canonicalization, OpenAI upload failed-DELETE retention, cache `__overflow__` tokens-only. New regression surface: `scripts/phase21-security.test.mjs` (10 conformance tests over built public entrypoints, wired into `security:threat-suites`) and a packed plain-JS `security21.mjs` consumer in install-smoke. Store compatibility with 0.2.0: **compatible, no migration**. Exit gate green: npm test core + script gates (incl. phase21-freeze done-phase), `sdk:ready` exit 0, audit 0 moderate, pack dry-run 50/50 twice byte-identical, plain reviewed compat gate at 0.2.1, live OIDC JWKS + live OPA protected evidence; evidence in `scripts/phase21-baseline.json` `exitGate`. Rollback = restore the 0.2.0 manifests/tag — but rollback restores the five boundary gaps, so hosts should disable truncated-stream acceptance, unbounded-body endpoints, redirect-following fetches, rotating-credential reuse, and upload cleanup at their own boundary if rollback is unavoidable.
package/docs/workflows.md CHANGED
@@ -262,7 +262,8 @@ runRpcServer({
262
262
 
263
263
  - Workflow semantics stay in this optional package; generic checkpoint persistence and bounded event fan-in live in core.
264
264
  - `ProductionPersistenceStore.checkpoints` and `.leases` are optional generic capabilities. First-party SQLite/PostgreSQL adapters own `prism_checkpoints` / `prism_leases`; workflows only adapt them.
265
- - `createWorkflowEventBus()` delegates queueing, source fan-in, overflow, abort, and close behavior to core `createEventMultiplexer()`.
265
+ - `createWorkflowEventBus()` delegates queueing, source fan-in, overflow, abort, and close behavior to core `createEventMultiplexer()`, including its single-consumer contract: a second concurrent `subscribe()` is rejected with `EventMultiplexerError` (`ERR_PRISM_EVENT_MULTIPLEXER_SINGLE_CONSUMER`) instead of silently splitting the stream.
266
+ - The in-process active-run registry (`registerActiveWorkflowRun` / `getActiveWorkflowRun` / `abortActiveWorkflowRun`) is **non-durable, in-process only — it does not survive restart**; durable active-run recovery is a later milestone. It is bounded: every register sweeps aborted/leaked entries (runs whose promise never settled) and the registry fails closed at `MAX_ACTIVE_WORKFLOW_RUNS` (512) rather than evicting a live run; `sweepActiveWorkflowRuns()` is available for hosts. Cross-tenant lookups stay ownership-isolated.
266
267
  - `createWorkflowCommands()` is optional; hosts can drive `workflow.start` / `enqueue` / `replay` / `status` / `list` / `cancel` / `resume`. The six `schedule.*` commands appear only when a scoped `schedules` service is supplied.
267
268
  - Hosts may bridge `WorkflowEvent` into OpenTelemetry or custom sinks; there is no built-in TUI.
268
269
  - Agent exclusivity is per session: one active `run()` at a time, same as core.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arnilo/prism",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Agent harness for AI providers, agents, sessions, and tools.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -34,6 +34,10 @@
34
34
  "types": "./dist/testing/agent-event-source-conformance.d.ts",
35
35
  "default": "./dist/testing/agent-event-source-conformance.js"
36
36
  },
37
+ "./testing/state-concurrency-conformance": {
38
+ "types": "./dist/testing/state-concurrency-conformance.d.ts",
39
+ "default": "./dist/testing/state-concurrency-conformance.js"
40
+ },
37
41
  "./testing/session-store-conformance": {
38
42
  "types": "./dist/testing/session-store-conformance.d.ts",
39
43
  "default": "./dist/testing/session-store-conformance.js"
@@ -150,13 +154,13 @@
150
154
  "format": "biome format --write .",
151
155
  "format:check": "biome format .",
152
156
  "pack:dry-run": "npm pack --dry-run && npm run pack:dry-run --workspaces --if-present",
153
- "test:postgres": "node scripts/require-postgres-url.mjs && npm run test:postgres --workspace @arnilo/prism-session-store-postgres && npm run test:postgres --workspace @arnilo/prism-memory && npm run test:postgres --workspace @arnilo/prism-enterprise-postgres && node --test scripts/phase7-conformance.test.mjs scripts/phase12-restart-recovery.test.mjs",
157
+ "test:postgres": "node scripts/require-postgres-url.mjs && npm run test:postgres --workspace @arnilo/prism-session-store-postgres && npm run test:postgres --workspace @arnilo/prism-memory && npm run test:postgres --workspace @arnilo/prism-enterprise-postgres && node --test scripts/phase7-conformance.test.mjs scripts/phase12-restart-recovery.test.mjs scripts/phase22-conformance.test.mjs",
154
158
  "release:dry-run": "npm run sdk:ready",
155
159
  "release:check": "node scripts/release.mjs check",
156
160
  "release:publish": "node scripts/release.mjs publish",
157
161
  "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",
158
162
  "release:gate": "node scripts/release.mjs gate",
159
- "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"
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"
160
164
  },
161
165
  "devDependencies": {
162
166
  "@biomejs/biome": "^2.5.5",