@arnilo/prism 0.2.4 → 0.2.6

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/agent-session/create-agent.d.ts +6 -0
  3. package/dist/agent-session/create-agent.js +13 -0
  4. package/dist/agent-session/event-subscriber.d.ts +17 -0
  5. package/dist/agent-session/event-subscriber.js +68 -0
  6. package/dist/agent-session/helpers.d.ts +44 -0
  7. package/dist/agent-session/helpers.js +194 -0
  8. package/dist/agent-session/session.d.ts +101 -0
  9. package/dist/agent-session/session.js +1568 -0
  10. package/dist/agent-session.d.ts +6 -105
  11. package/dist/agent-session.js +6 -1833
  12. package/dist/contracts-core/agent.d.ts +257 -0
  13. package/dist/contracts-core/agent.js +5 -0
  14. package/dist/contracts-core/compaction.d.ts +72 -0
  15. package/dist/contracts-core/compaction.js +2 -0
  16. package/dist/contracts-core/content.d.ts +116 -0
  17. package/dist/contracts-core/content.js +2 -0
  18. package/dist/contracts-core/extensions.d.ts +163 -0
  19. package/dist/contracts-core/extensions.js +2 -0
  20. package/dist/contracts-core/loop.d.ts +98 -0
  21. package/dist/contracts-core/loop.js +2 -0
  22. package/dist/contracts-core/persistence.d.ts +366 -0
  23. package/dist/contracts-core/persistence.js +9 -0
  24. package/dist/contracts-core/provider.d.ts +97 -0
  25. package/dist/contracts-core/provider.js +2 -0
  26. package/dist/contracts-core/resources.d.ts +44 -0
  27. package/dist/contracts-core/resources.js +7 -0
  28. package/dist/contracts-core/run-limits.d.ts +81 -0
  29. package/dist/contracts-core/run-limits.js +2 -0
  30. package/dist/contracts-core/session.d.ts +187 -0
  31. package/dist/contracts-core/session.js +131 -0
  32. package/dist/contracts-core.d.ts +13 -1425
  33. package/dist/contracts-core.js +10 -138
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.js +1 -1
  36. package/docs/0.1.0-readiness.md +10 -10
  37. package/docs/acp.md +2 -0
  38. package/docs/browser-automation.md +1 -1
  39. package/docs/coding-agent-tools.md +5 -4
  40. package/docs/coding-review-and-diagnostics.md +76 -0
  41. package/docs/coding-security.md +2 -0
  42. package/docs/coding-workspaces.md +69 -0
  43. package/docs/forge-integration.md +6 -0
  44. package/docs/index.md +5 -5
  45. package/docs/indexed-code-search.md +82 -0
  46. package/docs/language-intelligence.md +15 -0
  47. package/docs/migration.md +20 -0
  48. package/docs/process-sessions.md +58 -3
  49. package/docs/release-and-install.md +71 -3
  50. package/docs/work-artifacts-and-review.md +4 -0
  51. package/package.json +2 -2
@@ -0,0 +1,82 @@
1
+ # Indexed code search
2
+
3
+ Optional host-owned incremental search index for `repo_search` (plan 026 Task 2, `@arnilo/prism-coding-agent`). The index is a seam: Prism defines the contract, validates requests and results, and scopes identity and freshness — the host owns persistence, build, watch, and any embedding/ranking engine. There is no bundled index engine, vector store, watcher daemon, or embedding SDK.
4
+
5
+ ## Activation
6
+
7
+ Nothing starts on import or construction. A host builds and updates the index explicitly through the facade:
8
+
9
+ ```ts
10
+ import { createIndexedRepositoryOperations, createGitAwareRepositoryOperations } from "@arnilo/prism-coding-agent";
11
+
12
+ const operations = createIndexedRepositoryOperations(cwd, {
13
+ index: hostIndex, // RepositoryIndexBackend
14
+ fallback: createGitAwareRepositoryOperations(cwd),
15
+ allowedModes: ["literal", "indexed_literal", "semantic"],
16
+ stale: { maxAgeMs: 60_000, requireSourceRevision: true },
17
+ });
18
+
19
+ await operations.index.update({ repositoryId, worktreeId, sourceRevision, changes });
20
+ await operations.index.remove({ paths });
21
+ await operations.index.status();
22
+ await operations.index.dispose();
23
+ ```
24
+
25
+ The returned object is a `RepositoryOperations` with an added `index` facade. `mode: "literal"` routes to the fallback unchanged; `indexed_literal` and `semantic` are served only by the host backend.
26
+
27
+ ## Backend contract
28
+
29
+ `RepositoryIndexBackend`:
30
+
31
+ - `capabilities.semantic` — explicit declaration; semantic mode is never duck-typed.
32
+ - `update(request)` — add/edit changes with `repositoryId`/`worktreeId`/`sourceRevision` (bounded, never credential-bearing).
33
+ - `remove(request)` — drop paths.
34
+ - `search(request)` — bounded query with `mode`, optional repo-relative `path` scope, `maxResults`, `signal`, `deadlineMs`.
35
+ - `status()` — state `empty | building | ready | stale | failed`, `sourceRevision`, `updatedAt`.
36
+ - `dispose()`.
37
+
38
+ Index state machine is frozen: `empty | building | ready | stale | failed`.
39
+
40
+ ## Mode semantics
41
+
42
+ - `literal` (default): native bounded substring search; byte-identical behavior to a host without an index.
43
+ - `indexed_literal`: host index result, relevance scores.
44
+ - `semantic`: host semantic search; requires `capabilities.semantic === true`.
45
+
46
+ Missing capability, mode not in `allowedModes`, stale revision, or failed index returns a stable `ERR_PRISM_INDEX_*` error. There is **no silent fallback** that changes query meaning — an index result is never replaced by a literal scan behind the caller's back.
47
+
48
+ `createRepoSearchTool(cwd, { operations, modes })` exposes exactly the modes listed in `modes` (default `["literal"]`); the JSON schema `enum` matches.
49
+
50
+ ## Stale-index and freshness
51
+
52
+ `stale: { maxAgeMs, requireSourceRevision }`:
53
+
54
+ - `maxAgeMs` (default 60 000, hard 300 000): queries fail with `ERR_PRISM_INDEX_STALE` when `updatedAt` is absent or older than the window.
55
+ - `requireSourceRevision: true`: queries fail when the index does not attest a `sourceRevision`.
56
+
57
+ State `empty`/`building` also fail stale; state `failed` fails with `ERR_PRISM_INDEX_FAILED`; unknown states fail closed. The stale-index contract refuses to serve rather than silently degrade. Hosts choose the window; the default is deliberately conservative.
58
+
59
+ ## Trust
60
+
61
+ Index output is untrusted:
62
+
63
+ - every hit path is containment-checked under the repository root and the requested path scope (absolute paths, `..` escapes, backslashes, and scope escapes fail with `ERR_PRISM_INDEX_UNTRUSTED`);
64
+ - scores must be finite and in `[0, 1]`, otherwise fail closed;
65
+ - duplicate paths are deduped (first wins);
66
+ - snippets are truncated to the byte cap (default 4096, hard 16384);
67
+ - result count is capped (default 1000, hard 10000);
68
+ - results carry `indexed` provenance (mode/state/sourceRevision/updatedAt) and `untrusted_index: true` — consumers must not treat index text as fact; mutations still require a fresh read/policy.
69
+
70
+ Backend throws are mapped to generic `ERR_PRISM_INDEX_FAILED` without embedded backend error text; query deadline (default 30 s, hard 120 s) and abort map to `ERR_PRISM_INDEX_TIMEOUT`.
71
+
72
+ ## Update caps
73
+
74
+ Per update: 1000 changes default / 10000 hard, 16 MiB / 64 MiB total request bytes (paths, old paths, revision, ids). `rename` is routed as remove of the old path plus add of the new; `delete` routes to `remove`. Over-limit updates fail with `ERR_PRISM_INDEX_LIMIT` before any backend call.
75
+
76
+ ## Errors
77
+
78
+ `ERR_PRISM_INDEX_UNSUPPORTED`, `ERR_PRISM_INDEX_STALE`, `ERR_PRISM_INDEX_FAILED`, `ERR_PRISM_INDEX_LIMIT`, `ERR_PRISM_INDEX_TIMEOUT`, `ERR_PRISM_INDEX_UNTRUSTED` (`IndexError`).
79
+
80
+ ## Scale evidence
81
+
82
+ `scripts/phase26-index-benchmark.test.mjs` runs in the root test chain: a 100000-file metadata fixture, indexed query p95 ≤ 250 ms, 1000-file batch update ≤ 1 s, peak heap ≤ +64 MiB, semantic query bounded, and the literal baseline unchanged.
@@ -40,6 +40,21 @@ await lang.rename({ file: "src/a.ts", line: 10, character: 4, newName: "renamed"
40
40
  await lang.dispose();
41
41
  ```
42
42
 
43
+ ## Document synchronization and diagnostics (0.2.6, plan 026)
44
+
45
+ LSP stays opt-in: `createLanguageIntelligence` is a standalone host-activated factory — nothing spawns on construction and no agent/tool assembly instantiates it.
46
+
47
+ - `syncDocument(file)` — re-syncs a file after an external edit via full-content `textDocument/didChange` (protocol-valid LSP 3.17; no diff engine). Versions are monotonic per document: didOpen stamps 1, each didChange increments.
48
+ - `diagnosticDelta({ files, previous })` — bounded diagnostic refresh for changed files only (never a whole-workspace pull). When the server advertises `diagnosticProvider`, the client uses pull diagnostics (`textDocument/diagnostic` with `previousResultId` reuse — `kind: full|unchanged`; the cached set is reused on `unchanged`); otherwise it reads the push cache (`textDocument/publishDiagnostics` always replaces the full set, `[]` clears). Results are normalized (`NormalizedDiagnostic`), generation-stamped with the document version, and diffed with `diagnosticDelta` into deterministic `added` / `removed` / `unchanged`. Stale views (a previous generation newer than the refresh) are dropped per file — a stale-version response never overwrites newer results. Refresh honors the standard LSP caps (message bytes, diagnostics/file, results/query, timeout, files per request).
49
+
50
+ ```ts
51
+ await lang.syncDocument("src/app.ts");
52
+ const delta = await lang.diagnosticDelta({
53
+ files: ["src/app.ts"],
54
+ previous: { "src/app.ts": { generation: 3, diagnostics: priorDiags } },
55
+ });
56
+ ```
57
+
43
58
  ## Inputs / request
44
59
 
45
60
  `createLanguageIntelligence` options:
package/docs/migration.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Migration guide
2
2
 
3
+ ## 0.2.5 → 0.2.6 durable recovery, workspaces, and coding-agent readiness (additive)
4
+
5
+ Release **0.2.6** (plan 026) adds the coding-agent readiness capabilities behind optional host-activated seams: host-selected PTY backends, the indexed/semantic repository-search seam, the ownership-scoped multi-repository/worktree lifecycle, durable process/ACP recovery, and the patch-review/diagnostics workflow. **Additive-only: no exported declaration removed or changed, no persisted 0.2.5 shape repurposed.**
6
+
7
+ New durable records use **separate versioned checkpoint namespaces**, never the 0.2.5 shapes:
8
+
9
+ - `prism.coding-agent.process.v1` (schemaVersion 1) — managed-process recovery records. Readers reject unknown schema versions and corrupt/foreign records fail closed (dropped, never recovered).
10
+ - `prism.coding-agent.workspace.v1` (schemaVersion 1) — coding workspace lifecycle records.
11
+ - `prism.coding-agent.cancel.v1` (schemaVersion 1) — durable ACP run-cancel markers.
12
+
13
+ `CodingCheckpointMetadata` (schemaVersion 1, `prism.coding-agent`) is **never silently repurposed**; 0.2.5 readers reject unknown schema versions as before.
14
+
15
+ **ACP active-run references (Task 5 decision: additive optional field).** `PersistedAcpSession` gains an optional bounded `activeRun` ref (frozen 512-byte cap) recorded while a durable run is live. The decision recorded here: an additive optional field, not a separate recovery namespace, because the ref is advisory metadata — the authoritative run status is always re-queried from `AgentRunLifecycle.status` at restore time, and 0.2.5 hosts safely ignore the field. `PersistedAcpSession.activeRun` stays optional; 0.2.5 records remain readable and a 0.2.5 host reading a 0.2.6 record does not lose required recovery state (the run state itself lives in the existing `prism.agent-run` records, which are untouched).
16
+
17
+ **Downgrade to 0.2.5** is safe only after stopping 0.2.6 workers/replicas and marking any live 0.2.6 process/workspace records `unknown` (their leases expire within TTL); durable state never serializes a PTY fd, browser context, process object, controller, pending promise, raw terminal output, env, token, or credential, and no exact-process-survival claim is made (attach-if-attested, otherwise unknown).
18
+
19
+ ## 0.2.4 → 0.2.5 maintainability and bounded performance (no migration)
20
+
21
+ Release **0.2.5** (plan 025) is the maintainability-and-bounded-performance cut: the six remaining implementation god-modules split into cohesive internal family files behind preserved barrels (compat-preserving, no `exports`-map subpath), 21 pure persistence helpers moved into the dependency-free `session-store-codecs` package (ownership scope/assertion, checkpoint stale/encode/decode, branch cursors, lifecycle quota/reason/page-limit, search metadata/clipping, deepFreeze/string-array/throwIfAborted, feedback row mapping), the quadratic per-push `Buffer.concat` loops in language framing and tar parsing became chunk-array readers (linear; caps and fail-closed overflow byte-identical), two internal dead type aliases removed (`PostgresPersistenceCloseOptions`, `SqlitePersistenceCloseOptions` — never re-exported from their adapter indexes), and 76 behavior-backed coverage regressions closed the low-coverage core areas (core 91.43/84.80/91.60 lines/branches/functions). **No runtime contract change and no migration**: no exported declaration was removed or changed (the plain reviewed compat gate at 0.2.5 shows the version literal plus 105 additive internal-helper exports), no persisted shape/schema/default/behavior changed, no new runtime dependency. Store compatibility with 0.2.4: **compatible in both directions** — no migration step; rollback = restore the 0.2.4 manifests/tag (stores never change; the added exports disappear on downgrade). The 20 dead-but-compat-tracked exports deferred from Task 4 are the 0.3.0 breaking-cut removal list (see `docs/_evidence/phase25-dead-exports-triage.md`).
22
+
3
23
  ## 0.2.3 → 0.2.4 package, documentation, and compatibility truth (plan 024)
4
24
 
5
25
  Release **0.2.4** (plan 024) is the package-documentation-and-compatibility-truth cut: umbrella wording now states the manifest closures (`@arnilo/prism-providers` = 11 of 14 first-party provider adapters, omitting Azure/Bedrock/Vertex; `@arnilo/prism-all` = 20 direct / 43 transitive first-party packages with the named omission set), and `scripts/package-truth.json` (generated by `scripts/package-truth.mjs`) is the manifest-derived single source for counts, provider membership, umbrella closures, and profile closures. **Peer-version policy (Decision A — exact pins):** every code package peers the bare exact `@arnilo/prism@0.2.4` version (no range, no `*`); all `@arnilo/prism-*` packages move at the same version (**atomic-upgrade rule** — a partial upgrade fails clearly at install time with npm `ERESOLVE` naming the conflicting peer); the range widens to `^1.0.0` at the 1.x stable release; third-party `@arnilo/prism-*` adapters peer on the documented exact current version (full policy in the release-and-install Extension notes). **No runtime code path, persisted shape, event schema, default, or exported declaration changed** (the plain reviewed compat gate at 0.2.4 shows the version literal only). Store compatibility with 0.2.3: **compatible in both directions** — no migration step; rollback = restore the 0.2.3 manifests/tag.
@@ -17,7 +17,7 @@
17
17
 
18
18
  ## When to use it
19
19
 
20
- Use when a host needs attachable long-running processes (watch modes, language servers, interactive CLIs) that one-shot `shell` cannot model. Do not use as a job-control language or PTY emulator — `pty: true` fails closed with `ERR_PRISM_PROCESS_PTY_UNSUPPORTED` until a platform capability is wired. Pass a sandbox with `startProcess` for contained long-running work; omit `sandbox` for native spawn.
20
+ Use when a host needs attachable long-running processes (watch modes, language servers, interactive CLIs) that one-shot `shell` cannot model. Do not use as a job-control language. `pty: true` (host-selected PTY) requires a `ptyBackend` passed to `createProcessSessions`; without one it fails closed before spawn with `ERR_PRISM_PROCESS_PTY_UNSUPPORTED`. Pass a sandbox with `startProcess` for contained long-running work; omit `sandbox` for native spawn.
21
21
 
22
22
  ```ts
23
23
  import { createProcessSessions } from "@arnilo/prism-coding-agent";
@@ -43,6 +43,7 @@ await sessions.dispose();
43
43
  | `ownership?` | `OwnershipScope` | Default owner key for sessions. |
44
44
  | `identity?` | `AgentIdentity` | When ownership omitted, owner key projects from identity. |
45
45
  | `sandbox?` | `ProcessSandboxBackend` | When set: require `startProcess` or fail closed; `status` loss → all running → `unknown`. |
46
+ | `ptyBackend?` | `ProcessPtyBackend` | Host-selected interactive-terminal backend. `pty: true` delegates only here; absent backend → `ERR_PRISM_PROCESS_PTY_UNSUPPORTED` before spawn. Host supplies the PTY engine (e.g. node-pty); Prism never depends on one. |
46
47
 
47
48
  `ProcessStartRequest`:
48
49
 
@@ -51,7 +52,8 @@ await sessions.dispose();
51
52
  | `command` / `args?` | Executable + argv (not a shell string). |
52
53
  | `cwd?` | Relative/absolute path contained under registry `cwd`. |
53
54
  | `env?` | Extra env merged onto `process.env` (never in fingerprint). |
54
- | `pty?` | Default false; unsupported → `ERR_PRISM_PROCESS_PTY_UNSUPPORTED`. |
55
+ | `pty?` | Default false. `true` requires the `ptyBackend` host option (delegated only to it); unsupported host → `ERR_PRISM_PROCESS_PTY_UNSUPPORTED` before spawn. |
56
+ | `terminal?` | `{ columns, rows, term? }` for `pty: true`; defaults `120 × 40`, `xterm-256color`. Bounds: columns 1–120 (hard 500), rows 1–40 (hard 200), TERM ≤ 64 bytes (hard 256). |
55
57
  | `lifetimeMs?` | Bounded by `maxLifetimeMs`. |
56
58
  | `owner?` | Override owner string. |
57
59
  | `releaseOnCancel?` | If true, `cancelOwned` releases instead of killing. |
@@ -68,10 +70,23 @@ await sessions.dispose();
68
70
  | `cancelOwned(owner)` | Kill (default) or release owned running sessions. |
69
71
  | `markUnknown` | Backend-loss terminal state; never fabricates `exitCode`. |
70
72
  | `reconcile()` | Host resume: mark every running/starting session `unknown` (O(sessions)). |
73
+ | `resize?` | Only when `ptyBackend.capabilities.resize` is true: bounded `{ columns, rows }` routed to the live terminal. |
71
74
 
72
75
  Events: `process_started`, `process_exited`, `process_killed`, `process_released`, `process_expired`, `process_unknown`.
73
76
 
74
- Errors: `ERR_PRISM_PROCESS_POLICY`, `ERR_PRISM_PROCESS_OWNERSHIP`, `ERR_PRISM_PROCESS_STATE`, `ERR_PRISM_PROCESS_LIMIT`, `ERR_PRISM_PROCESS_PTY_UNSUPPORTED`, `ERR_PRISM_PROCESS_UNSUPPORTED`.
77
+ Errors: `ERR_PRISM_PROCESS_POLICY`, `ERR_PRISM_PROCESS_OWNERSHIP`, `ERR_PRISM_PROCESS_STATE`, `ERR_PRISM_PROCESS_LIMIT`, `ERR_PRISM_PROCESS_PTY_UNSUPPORTED`, `ERR_PRISM_PROCESS_PTY_BACKEND`, `ERR_PRISM_PROCESS_PTY_LIMIT`, `ERR_PRISM_PROCESS_UNSUPPORTED`.
78
+
79
+ ## Host-selected PTY backend contract
80
+
81
+ `ptyBackend` is the host's interactive-terminal capability (plan 026 Task 1):
82
+
83
+ - **Delegation only.** `pty: true` starts a session exclusively through `ptyBackend.startPty`; the native spawn path never allocates a terminal. A missing backend or one without `startPty` fails closed before any spawn (`ERR_PRISM_PROCESS_PTY_UNSUPPORTED`). The non-PTY path is byte-compatible with the 0.2.5 baseline.
84
+ - **Contract.** `startPty({ file, args, cwd, env, columns, rows, term, onData })` returns `{ metadata?, write, signal, kill, release, wait, resize? }`. `capabilities.resize` is explicit — `resize` on the session exists only when declared; never duck-typed. `wait()` resolves on process exit (and rejects on backend loss); the host stops delivering `onData` once the session is terminal.
85
+ - **Terminal data is untrusted output.** Control sequences are never parsed or emulated; the host (or an attached terminal client) interprets them. Input is raw terminal bytes; NUL is rejected with a policy error, other control bytes pass through as terminal data.
86
+ - **Bounded attach.** `startPty` must settle within `maxPtyAttachTimeoutMs` (30 s default, 120 s hard); overflow removes the session record and fails with `ERR_PRISM_PROCESS_PTY_LIMIT`. Resize is rate-limited (60/min default, 600 hard) and fails with the same code. Backend `metadata` is bounded (`maxPtyBackendMetadataBytes`, 4 KiB default / 16 KiB hard).
87
+ - **Backend loss.** A throwing `startPty` or a lost `wait()` surfaces as `ERR_PRISM_PROCESS_PTY_BACKEND` with a generic message (backend error text is never embedded); the session becomes `unknown` with `exitCode: null` — never fabricated.
88
+ - **Parity.** Policy, cwd/ownership, cancel/expiry sweeps, input/lifetime/output caps, command fingerprint, events, and redaction behave exactly as non-PTY sessions; PTY sessions count against `maxSessions`.
89
+ - **Recovery caveat (phase 26, task 5).** PTY sessions are not durable across restart: no serialized terminal fd or raw output is ever persisted. Restart recovery reports such sessions `unknown` unless a host `recoveryBackend` re-attaches them; there is no exact-process-survival claim.
75
90
 
76
91
  ## Request/response example
77
92
 
@@ -123,6 +138,46 @@ await sessions.dispose();
123
138
  - Command fingerprint is SHA-256 of `[command, ...args]` only (no env).
124
139
  - Docker reference adapter does not implement `startProcess` yet — fail closed until a capable runtime is wired.
125
140
 
141
+ ## Durable process recovery (plan 026 Task 5)
142
+
143
+ Optional, host-activated: pass `checkpoints` + `leases` + `ownerId` (all three
144
+ together; a partial recovery configuration fails closed at construction) and
145
+ optionally `recoveryBackend` + `recoveryLimits`. With durability configured:
146
+
147
+ - Intent is persisted BEFORE spawn into the versioned namespace
148
+ `prism.coding-agent.process.v1` (schemaVersion 1, category `coding-process`),
149
+ and every lifecycle transition (running, exited, killed, released, expired,
150
+ unknown) is a CheckpointStore CAS write under a monotonic LeaseStore fencing
151
+ token. Transition writes are serialized per record so CAS order never inverts
152
+ on slow stores.
153
+ - `recover()` reconciles durable records against the live registry:
154
+ - records already live here report `attached` without mutation;
155
+ - terminal records report `terminal` with their exit code;
156
+ - `starting|running` records attach-if-attested: a `backendRef` (opaque
157
+ non-secret ref surfaced by a PTY/sandbox handle's optional `ref`) plus a
158
+ host `recoveryBackend.attach(ref)` (bounded attach timeout 30 s default /
159
+ 120 s hard) may reattach; otherwise the record atomically becomes `unknown`
160
+ with exit code null — no PID probing, no fabricated exit, no duplicate
161
+ spawn. `recover()` without durability configured throws
162
+ `ERR_PRISM_RECOVERY_UNSUPPORTED`.
163
+ - Recovered sessions are live registry sessions: input/signal/kill/release/
164
+ resize/wait reach the attached backend; output streaming is not re-established
165
+ after a restart (the host backend owns any buffered output behind its ref).
166
+ - Replica coordination: every mutation takes a per-record lease (30 s default /
167
+ 300 s hard); a crashed replica's lease lapses within TTL, a live one renews
168
+ on transitions and releases on terminal transitions. A held lease makes the
169
+ second replica report `unknown` without touching the record; CAS/fence
170
+ conflicts fail closed with `ERR_PRISM_RECOVERY_FENCE`.
171
+ - `cancelOwned` after recovery either reaches the attached backend or records
172
+ the durable record unknown — never a fabricated exit.
173
+ - Durable records are metadata only: no child/PTY handle, controller, promise,
174
+ raw output, env, token, or credential is ever serialized; forbidden fields,
175
+ corrupt, oversized, or cross-tenant records fail closed (dropped, never
176
+ recovered). Records are capped (32 default / 128 hard; oldest terminal
177
+ records evict beyond the cap; running records are never evicted).
178
+ - Errors: `ERR_PRISM_RECOVERY_UNSUPPORTED` / `_LIMIT` / `_OWNERSHIP` / `_FENCE`
179
+ / `_UNKNOWN` / `_UNTRUSTED` / `_TIMEOUT` (`ProcessRecoveryError`).
180
+
126
181
  ## Security and performance notes
127
182
 
128
183
  | Cap | Default | Hard |
@@ -4,7 +4,7 @@
4
4
 
5
5
  Prism is published as **50 publishable manifests**: the root `@arnilo/prism` core package plus **49 workspace packages** — 14 provider adapters, 9 `prism-*` family/profile packages, and 26 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 50th manifest is the 0.1.6 plan 018 optional `@arnilo/prism-document-reader` package (bounded PDF/Office literal-text extraction for the coding read tool; ships only because its `doc-reader` closeout is demanded — a deferred closeout keeps the graph at 49). This page describes how they are packed, what each tarball contains, how to install them, the required `@arnilo/prism` peer dependency, the release workflow, and the offline test budget. The measurable 1.0 readiness gates (command-per-gate) live in [`0.1.0-readiness.md`](./0.1.0-readiness.md).
6
6
 
7
- Core `@arnilo/prism` ships runtime, CLI, templates, and docs. Every code package has a required `@arnilo/prism@0.2.4` peer; profiles are pure manifests. Installation activates no provider, listener, database, browser, credential, or tool capability.
7
+ Core `@arnilo/prism` ships runtime, CLI, templates, and docs. Every code package has a required `@arnilo/prism@0.2.6` peer; profiles are pure manifests. Installation activates no provider, listener, database, browser, credential, or tool capability.
8
8
 
9
9
  Current **50** publishable manifests (root + 49 workspace packages):
10
10
 
@@ -94,7 +94,7 @@ A packed tarball contains only public compiled output and release files:
94
94
  - Code packages ship `README.md`, `LICENSE`, and `CHANGELOG.md`; family/profile packages ship `README.md` and `CHANGELOG.md`.
95
95
  - The core tarball additionally ships the full `docs/` directory (the docs hub) and `templates/init/` used by `prism init`.
96
96
  - `dist/cli.js` and the `bin` link in core.
97
- - **Tarball filenames.** npm strips the `@scope/` prefix, so the core package `@arnilo/prism` produces a tarball named `arnilo-prism-0.2.4.tgz`; first-party packages produce `arnilo-prism-provider-<name>-0.2.4.tgz` / `arnilo-prism-compaction-<name>-0.2.4.tgz` / `arnilo-prism-coding-agent-0.2.4.tgz`; family/profile packages produce `arnilo-prism-{providers,compaction,base,code,sdk,all}-0.2.4.tgz`. 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).
97
+ - **Tarball filenames.** npm strips the `@scope/` prefix, so the core package `@arnilo/prism` produces a tarball named `arnilo-prism-0.2.6.tgz`; first-party packages produce `arnilo-prism-provider-<name>-0.2.6.tgz` / `arnilo-prism-compaction-<name>-0.2.6.tgz` / `arnilo-prism-coding-agent-0.2.6.tgz`; family/profile packages produce `arnilo-prism-{providers,compaction,base,code,sdk,all}-0.2.6.tgz`. 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).
98
98
 
99
99
  Excluded from every tarball by `files` negation:
100
100
 
@@ -303,6 +303,74 @@ npm run release:publish -- --version 0.2.4 --dry-run --allow-dirty --allow-untag
303
303
 
304
304
  Protected evidence (never a passing skip): the durable state-concurrency legs (Postgres `prism_phase24_*` schemas — `npm run test:postgres` under `PRISM_TEST_POSTGRES_URL`; absent credentials record **blocked** per the release skip manifest), the phase24 package-truth conformance over built dist + packed tarballs, 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.4 as **blocked**, never a passing skip.
305
305
 
306
+ ## Protected coding journey (0.2.6, plan 026 Task 7)
307
+
308
+ The 0.2.6 protected release profile requires real end-to-end coding-agent evidence, never a passing skip. `scripts/phase26-coding-journey.test.mjs` packs the published packages into a fresh consumer, installs the pinned host browser, and runs `scripts/fixtures/phase26-coding-journey.mjs` against real host services: a real LLM provider call through the Prism `AIProvider` contract (host adapter module), a digest-pinned Docker sandbox (`PRISM_TEST_DOCKER_IMAGE` must be `name@sha256:...`), the durable worktree lifecycle over real Postgres checkpoints/leases, a provider-driven edit through ACP with policy approval, a named check with a host parser and `diagnosticDelta`, a patch review composed over the server `ArtifactService` (accepted then superseded), process recovery across replicas (attach-if-attested, never re-spawn), durable ACP cancellation (terminal-idempotent, never replays tools), real GitHub push + lookup-before-create PR + reconcile + cleanup, host Playwright browser inspection, and the host PTY adapter when in the frozen profile.
309
+
310
+ ```bash
311
+ # Full protected profile (all legs real):
312
+ PRISM_CODING_JOURNEY=1 \
313
+ PRISM_TEST_POSTGRES_URL=postgres://... \
314
+ PRISM_TEST_DOCKER_BIN=/usr/bin/docker \
315
+ PRISM_TEST_DOCKER_IMAGE=name@sha256:... \
316
+ PRISM_LIVE_PLAYWRIGHT=1 \
317
+ PRISM_CODING_FORGE_REPOSITORY=owner/repo \
318
+ PRISM_CODING_FORGE_TOKEN=ghp_... \
319
+ PRISM_CODING_PROVIDER=/abs/path/provider-adapter.mjs \
320
+ PRISM_TEST_PTY_BACKEND=/abs/path/pty-host.mjs \
321
+ node --test scripts/phase26-coding-journey.test.mjs
322
+ ```
323
+
324
+ Every side effect carries the run suffix and is cleaned up idempotently (PR closed, branch deleted, worktree removed, containers/children/browser context closed); unknown cleanup blocks the journey. The retained report `scripts/phase26-coding-journey-report.json` is regenerated on every real run — `journey.state` pass records the surface `pass` in release evidence; blocked/partial reports record `blocked` (fail closed); `not_run`/missing records are documented protected gaps (`requiredEnv PRISM_CODING_JOURNEY`). The CI profile runs in `.github/workflows/coding-journey.yml` (scheduled + dispatch; Postgres service, host Docker, pinned Chromium, provider + forge secrets from the `coding-journey` environment) and uploads the report artifact. Leak scans cover journey stdout/stderr and the report against every credential-looking env value — the report records env names only. Frozen ceilings: journey wall 20 min (hard 40 min), cleanup 5 min (hard 15 min).
325
+
326
+ ### 0.2.6 publish handoff (plan 026 Task 8)
327
+
328
+ **Decision: GO when the operator prerequisites below are recorded.** Release **0.2.6** (plan 026) is the fully-featured coding-agent-readiness cut on the 0.2.x review-remediation line. API surface **additive-only** (plain reviewed compat gate at 0.2.6: expected deltas are the version literal plus the Task 1–6 additive exports — PTY backend/handle types, indexed-search seam, workspace lifecycle, process/ACP recovery, review manifest + diagnostics; zero removals; baselines regenerated with `--update-baseline`, no `--allow-break`; freeze manifest `scripts/phase26-freeze-manifest.json` records per-task evidence tokens, state machines, caps, the demand registry, and deviations D-T3-1/D-T5-1/D-T7-1). Seven roadmap items, **no runtime contract change, additive migration**: (1) **host-selected PTY/interactive terminal backend** (`pty-backend`) — `createProcessSessions` gains an optional `ptyBackend` with explicit `capabilities.resize`; `pty: true` without a backend fails byte-compatibly with `ERR_PRISM_PROCESS_PTY_UNSUPPORTED` before spawn; bounded geometry/TERM/attach/resize-rate/metadata caps, generic backend errors that never leak backend text, NUL rejected as a policy error, backend loss surfaces as `unknown` with `exitCode: null`; the protected PTY leg (`scripts/phase26-pty-protected.test.mjs`, gated by `PRISM_TEST_PTY_BACKEND`) passed 4/4 against a real PTY host (python3 `pty.fork` + `TIOCSWINSZ`). (2) **scalable indexed code-search seam** (`indexed-search`) — `createIndexedRepositoryOperations` composes a host-owned incremental index with bounded literal search as the unchanged default; `indexed_literal`/`semantic` fail closed on stale/failed/unsupported/untrusted indexes (`ERR_PRISM_INDEX_*`), no silent semantic-to-literal downgrade, results labeled `untrusted_index`; 100k-entry benchmark p95 ≤ 250 ms, 1k-file update ≤ 1 s, heap ≤ 64 MiB. (3) **ownership-scoped multi-repository/worktree lifecycle** (`workspace-lifecycle`) — `createCodingWorkspaceLifecycle` over CheckpointStore CAS + LeaseStore fencing (`prism.coding-agent.workspace.v1`), locked worktrees with `prism-workspace:` reasons, credential-free remote fingerprints, idempotent create, verify revalidation, cleanup refusal matrix, `ERR_PRISM_WORKSPACE_*`; `GitOperations` gains worktree `lock`/`unlock` + `fingerprint()`. (4) **forge breadth demand-gated** (`forge-breadth`) — GitLab/Bitbucket stay **deferred** in the demand registry with no named consumer; no adapter source ships; activation requires a recorded named consumer/date/use case. (5) **durable ACP/live-task and managed-process recovery** (`durable-recovery`) — bounded process intent/metadata persisted before spawn (`prism.coding-agent.process.v1`), serialized per-record CAS transition writes, attach-if-attested `recover()` reporting `attached|terminal|unknown` with no fabricated exit code and no PID probing; per-record leases fence replicas (memory + real Postgres two-replica conformance 8/8); ACP `activeRun` refs (additive optional, 0.2.5 records stay readable) + `createAcpRunRecovery` status re-resolution and durable fence-checked cancellation (`prism.coding-agent.cancel.v1`) that never replays a pending/dispatched tool. (6) **bounded patch review and incremental diagnostics** (`review-diagnostics`) — `createCodingPatchReviewManifest` + `assertCodingPatchAccepted` (pending/accepted/rejected/superseded bound to digest + revision + identity, stale acceptance refused, never auto-applies/commits/pushes/merges) composed over the server `ArtifactService`; `normalizeDiagnostics`/`diagnosticDelta` with deterministic added/removed/unchanged deltas and host-supplied check parsers; opt-in LSP `syncDocument`/`diagnosticDelta` (monotonic versions, resultId reuse) — LSP stays strictly opt-in, nothing spawns from tool factories or agent assembly. (7) **protected real coding journey** (`coding-journey`) — `scripts/phase26-coding-journey.test.mjs` packs 10 packages into a fresh consumer and drives real host services (provider call, digest-pinned Docker sandbox, Postgres worktree lifecycle, provider-driven ACP edit with policy approval, named check + `diagnosticDelta`, patch review over the server artifact store, cross-replica process recovery, durable cancellation, real GitHub push/lookup-before-create PR/reconcile/cleanup, host Playwright inspection, host PTY adapter in the frozen profile) under frozen wall/cleanup ceilings with run-suffix side effects and per-step idempotent cleanup; missing credentials/services or skipped substeps record **blocked**, never a passing skip; the retained `scripts/phase26-coding-journey-report.json` (timings/states/ids only) gates release evidence (pass/blocked/protected). Release graph stays **50** publishable manifests at exact **0.2.6**; zero new runtime dependency names (core remains dependency-free); 43 code packages + 6 pure-manifest family/profile.
329
+
330
+ **Measured reductions and deltas (recorded in `scripts/phase26-baseline.json` `exitGate`).** New error families: `ERR_PRISM_PROCESS_PTY_*`, `ERR_PRISM_INDEX_*`, `ERR_PRISM_WORKSPACE_*`, `ERR_PRISM_RECOVERY_*`, `ERR_PRISM_REVIEW_*` — all additive, all fail closed. New protected env names (never values): `PRISM_TEST_PTY_BACKEND`, `PRISM_TEST_POSTGRES_URL`, `PRISM_TEST_DOCKER_BIN`, `PRISM_TEST_DOCKER_IMAGE`, `PRISM_LIVE_PLAYWRIGHT`, `PRISM_CODING_FORGE_REPOSITORY`, `PRISM_CODING_FORGE_TOKEN`, `PRISM_CODING_PROVIDER`, `PRISM_CODING_JOURNEY`. Coverage stayed above the recorded 0.2.5 floors. **Rollback notes.** Rollback = restore the 0.2.5 manifests/tag. Three new versioned checkpoint namespaces exist (`prism.coding-agent.process.v1`, `prism.coding-agent.workspace.v1`, `prism.coding-agent.cancel.v1`) plus the additive optional ACP `activeRun` ref; before downgrading, stop all 0.2.6 workers and mark active PTY/process/recovery records unknown (a crashed 0.2.6 replica that resumes on 0.2.5 fails closed — recovery never fabricates an exit code or re-spawns without host attestation). No 0.2.5 persisted shape changed, so an ordinary downgrade is store-safe; the added exports simply disappear.
331
+
332
+ ```bash
333
+ # Operator prerequisites recorded: clean tree at the v0.2.6 tag candidate, GPG key, npm OIDC publisher.
334
+ node scripts/release.mjs bump --from 0.2.5 --to 0.2.6 # already applied by Task 8; idempotent
335
+ npm test # core + workspace suites + all script gates (incl. phase26-freeze + phase26-index-benchmark)
336
+ npm run security:threat-suites # phase8-11 + phase20-25 public-entry conformance
337
+ PRISM_TEST_POSTGRES_URL=postgres://postgres:prism@127.0.0.1:54329/prism_test npm run test:postgres
338
+ node --test scripts/phase26-recovery-conformance.test.mjs # protected: memory + real Postgres two-replica recovery/workspace conformance
339
+ node --test scripts/phase26-pty-protected.test.mjs # protected: real PTY host (PRISM_TEST_PTY_BACKEND)
340
+ PRISM_TEST_POSTGRES_URL=postgres://postgres:prism@127.0.0.1:54329/prism_test npm run sdk:ready
341
+ node scripts/release.mjs gate --version 0.2.6 # plain reviewed gate at 0.2.6: version literal + additive exports only, 0 breaking deltas
342
+ npm run pack:dry-run # twice; diff reports — deterministic
343
+ npm audit --audit-level=moderate
344
+ npm run release:check -- --version 0.2.6 --report /tmp/prism-0.2.6-preflight.json
345
+ npm run release:publish -- --version 0.2.6 --dry-run --allow-dirty --allow-untagged --report /tmp/prism-0.2.6-dry-run.json
346
+ # run the dry-run twice and diff the reports: deterministic, byte-identical
347
+ ```
348
+
349
+ Protected evidence (never a passing skip): the durable recovery/workspace conformance legs (real Postgres two-replica split-brain fence, cross-replica cancellation, terminal-before-recovery), the protected PTY leg (real PTY host adapter), the protected real coding journey (`scripts/phase26-coding-journey-report.json` — pass/blocked/protected, never a passing skip; runs in `.github/workflows/coding-journey.yml` with real provider/Docker/Playwright/GitHub/Postgres/PTY services), 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.6 as **blocked**, never a passing skip.
350
+
351
+ ### 0.2.5 publish handoff (plan 025 Task 6)
352
+
353
+ **Decision: GO when the operator prerequisites below are recorded.** Release **0.2.5** (plan 025) is the maintainability-and-bounded-performance cut on the 0.2.x review-remediation line. API surface **additive-only** (plain reviewed compat gate at 0.2.5: expected deltas are the version literal plus 105 additive internal-helper exports from the splits/dedup — 84 Task 1 cross-family helpers across `@arnilo/prism`/`prism-coding-agent`/`prism-workflows`/`prism-server`/`prism-ag-ui` and 21 `prism-session-store-codecs` helpers — zero removals; baselines regenerated with `--update-baseline`, no `--allow-break`; freeze manifest `scripts/phase25-freeze-manifest.json`). Internal-only refactoring and bounded-performance work, **no runtime contract change and no migration**: (1) **god-module splits, compat-preserving** — the six remaining implementation god-modules split along cohesive boundaries into internal family files behind preserved barrels (0.1.4 precedent): `src/contracts-core.ts` (1,719 L → 10 families, max 403 L), `src/agent-session.ts` (2,049 L → 4 modules; the 1,686-L `RuntimeAgentSession` class is kept intact — a single TS class cannot span files without exporting private methods, recorded reason), `packages/workflows/src/run.ts` (1,227 L → 6 families, max 417 L), `packages/server/src/handler.ts` (1,005 L → 8 families, max 444 L), `packages/coding-agent/src/repository.ts` (974 L → 7 families, max 299 L), `packages/ag-ui/src/acp/agent.ts` (836 L → 8 families, max 386 L). No `exports` map gained a subpath; `ponytail:` comments preserved verbatim; every package verified with **zero breaking compat deltas** (`scripts/phase25-compat-diff.mjs`). (2) **persistence-mechanics dedup** — 21 pure helpers (ownership scope/assertion, checkpoint stale/encode/decode, branch cursors, lifecycle quota/reason/page-limit, search metadata/clipping, deepFreeze/string-array/throwIfAborted, feedback row mapping) moved into the dependency-free `packages/session-store-codecs` (426 → 624 L, stdlib only, no SQL dialect leakage); the adapters shrank 273 lines total (postgres 1,104 → 1,041, sqlite 1,088 → 1,010); SQL fragments, DDL templates, and query execution stay per-adapter; no persisted-shape change; cross-store conformance proves identical semantics before/after. (3) **quadratic accumulation removed** — the per-push `Buffer.concat` loops in language framing (`LspFrameReader`) and tar parsing (`summarizeTarStream`) became chunk-array readers (two-phase header parse + offset-advance `drop`, `take`/`drop` sliding window); caps and fail-closed overflow behavior byte-identical; framing measured **~100–200× faster at N=4000 chunks** (1,298.4 ms → 11.2 ms) and tar linear at 8 MiB; CLI capture (`collectOutput`) audited — already linear since plan 020; the near-limit probe `scripts/phase25-bounded-accumulation.test.mjs` (10 tests) is wired into the npm test gate segment and asserts linear copying by byte-count instrumentation. (4) **dead-code cleanup, internal-only** — the 62 `dead-exports.mjs` candidates triaged into 2 removals (`PostgresPersistenceCloseOptions`, `SqlitePersistenceCloseOptions` — internal type aliases never re-exported from their adapter indexes) + 60 allow-listed with reasons in `docs/_evidence/phase25-dead-exports-triage.md` (37 test-used false positives, 20 dead-but-compat-tracked deferred to the 0.3.0 breaking cut, 3 public type aliases deferred); the named-internal audit (`agent-session`/`cache-telemetry`/`skill-load`) recorded as clean at 0.2.4; no public export removed, so **no `docs/migration.md` removal note is required** (this section records the no-runtime-contract-delta statement instead). (5) **coverage close, behavior-backed** — 76 focused regressions (approval 43 — the untested `agent-approval.ts` resolve/validate/pending paths; conversations 22 — cursor codec + thread projection; artifacts 6 — approval-state/checkpoint-key/error codes; tool-effect-store conformance 5 — violation throws + option defaults; compaction relies on its 17 existing package suites); core coverage rose **90.53/84.20/90.54 → 91.43/84.80/91.60** lines/branches/functions (gate 60/70/75; all 39 non-protected packages above their evidence thresholds). Release graph stays **50** publishable manifests at exact **0.2.5**; zero new runtime dependency names (core remains dependency-free); 43 code packages + 6 pure-manifest family/profile.
354
+
355
+ **Measured reductions and deltas (recorded in `scripts/phase25-baseline.json` `exitGate`).** File-size/complexity: the six monoliths (totaling ~8.2 kL) split into families with max sizes 299–444 L (the 1,686-L class exception recorded); the adapters shrank 273 L (net −75 with the codecs growth). Tree-shaking/startup: no regression — root packed bytes unchanged (800,042), startup `importMs` within the 250 ms ceiling, the +30 root `fileCount` (326 → 356) is Task 1 split `dist` files (`.map` excluded) re-baselined with a dated `$comment`. Near-limit perf: framing linear at 4,000 chunks (~100–200× faster), tar linear at 8 MiB, both with byte-identical caps and fail-closed overflow. Coverage: core +0.90/+0.60/+1.06 lines/branches/functions with 76 behavior-backed tests (no line-count padding). **Rollback notes.** Rollback = restore the 0.2.4 manifests/tag. Nothing persisted changes shape, no default or behavior changed, and the only public-surface deltas are additive; downgrade is store-safe and code-safe — the only visible deltas are the version literal and the extra exports (a 0.2.4 consumer can downgrade without code changes; the added helpers simply disappear).
356
+
357
+ ```bash
358
+ # Operator prerequisites recorded: clean tree at the v0.2.5 tag candidate, GPG key, npm OIDC publisher.
359
+ node scripts/release.mjs bump --from 0.2.4 --to 0.2.5 # already applied by Task 6; idempotent
360
+ npm test # core + workspace suites + all script gates (incl. phase24-truth + phase25-bounded-accumulation)
361
+ npm run security:threat-suites # phase8-11 + phase20-24 public-entry conformance
362
+ PRISM_TEST_POSTGRES_URL=postgres://postgres:prism@127.0.0.1:54329/prism_test npm run test:postgres # incl. the cross-store conformance legs
363
+ PRISM_TEST_POSTGRES_URL=postgres://postgres:prism@127.0.0.1:54329/prism_test npm run sdk:ready
364
+ node scripts/release.mjs gate --version 0.2.5 # plain reviewed gate at 0.2.5: version literal + additive exports only, 0 breaking deltas
365
+ npm run pack:dry-run # twice; diff reports — deterministic
366
+ npm audit --audit-level=moderate
367
+ npm run release:check -- --version 0.2.5 --report /tmp/prism-0.2.5-preflight.json
368
+ npm run release:publish -- --version 0.2.5 --dry-run --allow-dirty --allow-untagged --report /tmp/prism-0.2.5-dry-run.json
369
+ # run the dry-run twice and diff the reports: deterministic, byte-identical
370
+ ```
371
+
372
+ Protected evidence (never a passing skip): the durable state-concurrency legs + the Task 2 **cross-store conformance** legs (Postgres `prism_phase25_*` schemas — `npm run test:postgres` under `PRISM_TEST_POSTGRES_URL`; absent credentials record **blocked** per the release skip manifest), the phase25 bounded-accumulation near-limit probe over built dist, 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.5 as **blocked**, never a passing skip.
373
+
306
374
  ### 0.2.3 publish handoff (plan 023 Task 6)
307
375
 
308
376
  **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.)
@@ -723,7 +791,7 @@ Audit fixes, dependency updates, and security patches land only for the supporte
723
791
 
724
792
  ## Extension and configuration notes
725
793
 
726
- - **Required `@arnilo/prism` peer.** Every first-party code package declares a non-optional **exact** `@arnilo/prism@0.2.4` peer (`peerDependenciesMeta` must not mark `@arnilo/prism` optional; other peers such as `playwright-core` may be optional). **Peer-version policy (plan 024, Decision A — exact pins):** the peer spec is the bare exact current version — no `~`/`^`/`>=` range, no `*` — for the whole 0.2.x line, and all `@arnilo/prism-*` packages move at the same version (the **atomic-upgrade rule**). A partial upgrade (e.g. `@arnilo/prism@0.2.4` installed with a package peering `@arnilo/prism@0.2.5`) is unsupported and fails clearly at install time with `ERESOLVE unable to resolve dependency tree` naming the conflicting peer — never a silent install of a pair that was never tested together. A third-party `@arnilo/prism-*` adapter declares the same exact peer on the documented current version; an unsupported mixture fails at install time, not at runtime. The range widens to `^1.0.0` at the 1.x stable release (the 1.0 readiness gates go operator-green on the 0.2.x line); rollback of a release moves the pins back atomically with the manifests/tag. 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.
794
+ - **Required `@arnilo/prism` peer.** Every first-party code package declares a non-optional **exact** `@arnilo/prism@0.2.6` peer (`peerDependenciesMeta` must not mark `@arnilo/prism` optional; other peers such as `playwright-core` may be optional). **Peer-version policy (plan 024, Decision A — exact pins):** the peer spec is the bare exact current version — no `~`/`^`/`>=` range, no `*` — for the whole 0.2.x line, and all `@arnilo/prism-*` packages move at the same version (the **atomic-upgrade rule**). A partial upgrade (e.g. `@arnilo/prism@0.2.6` installed with a package peering `@arnilo/prism@0.2.7`) is unsupported and fails clearly at install time with `ERESOLVE unable to resolve dependency tree` naming the conflicting peer — never a silent install of a pair that was never tested together. A third-party `@arnilo/prism-*` adapter declares the same exact peer on the documented current version; an unsupported mixture fails at install time, not at runtime. The range widens to `^1.0.0` at the 1.x stable release (the 1.0 readiness gates go operator-green on the 0.2.x line); rollback of a release moves the pins back atomically with the manifests/tag. 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.
727
795
  - **Public access.** All 50 manifests (root + 49 workspace packages: 43 code packages + 6 pure-manifest family/profile packages — the 9 `prism-*` family/profile set is the 6 pure-manifest profiles plus the 3 code packages `prism-caveman`, `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.
728
796
  - **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).
729
797
  - **Release workflow.** `.github/workflows/release.yml` has six jobs. `verify` runs network-free SDK readiness on Node 24; `node20-compat` builds/imports every public root `exports` default target on Node 20 for declared `engines.node >=20` (docs examples need Node >=22.6 native TypeScript stripping); `postgres-integration` uses `pgvector/pgvector:pg16`; `supply-chain` runs high-severity audit, SPDX/license policy, and tracked-source secret scanning; and tag-only `codeql-release` runs SAST. Tag-only `publish` needs all five gates, preserves clean exact-tag/version/topological publication, and alone receives `NPM_TOKEN`, `id-token: write`, and `attestations: write`. Before npm publish it packs all current tarballs, generates checksums plus SPDX, scans unpacked public artifacts, creates GitHub attestations for tarballs and SBOM, then retains artifacts for 30 days. Registry state remains the resumable journal. Local `npm run release:dry-run` remains network-free SDK readiness; local PostgreSQL coverage is `PRISM_TEST_POSTGRES_URL=... npm run test:postgres`.
@@ -91,6 +91,10 @@ export const handler = createArtifactHandler({ service: artifacts, authorize: ho
91
91
  - Frozen caps (default / hard): artifacts per thread 64/256; revisions per artifact 32/128; record 8/64 KiB; preview 16/64 KiB; citations 32/128 and 2/8 KiB each; MIME 128/512 B; hash 256/1 KiB; compare exactly 2 revisions; delivery TTL 5 min/24 h; delivery token 4/16 KiB. Raising the revision cap may require raising `recordBytes` (aggregate backstop).
92
92
  - Compare is hash+metadata-bounded (hosts render content); no file bodies are persisted or transferred. With a wired body store, bodies live in the host's object store and are streamed through the adapter (bounded by `maxBodyBytes` 64 MiB/512 MiB, concurrent transfers 4/16, presign TTL 10 min/24 h); object-store outages surface typed `ERR_PRISM_S3_*` / `ERR_PRISM_ARTIFACT_BODY_*` errors, never silent success.
93
93
 
94
+ ## Coding patch review composition (0.2.6, plan 026)
95
+
96
+ `@arnilo/prism-coding-agent` composes over this service for the coding patch review workflow: `createCodingPatchReviewManifest` builds a bounded manifest (repository/worktree identity, base/head, patch digest, changed paths, diffstat, check and diagnostic summaries) and returns a structural `ArtifactAttachInput` whose `preview.review` embeds the manifest and whose `hash` is the patch SHA-256; `assertCodingPatchAccepted` derives `pending|accepted|rejected|superseded` from the returned `ArtifactRecord` by binding to the exact artifact revision, digest, and identity — any patch/repository/worktree/base/head change supersedes a prior acceptance (a newer revision attached after approval makes the old acceptance stale and refused). Decisions never apply/commit/push/merge; the manifest never embeds a raw patch body. Full contract: [Coding review and diagnostics](coding-review-and-diagnostics.md).
97
+
94
98
  ## Related APIs
95
99
 
96
100
  - [Server](server.md): `createArtifactService` / `createArtifactHandler` mount alongside the Prism handler; ownership only from `authorize`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arnilo/prism",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Agent harness for AI providers, agents, sessions, and tools.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -147,7 +147,7 @@
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
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 scripts/phase24-truth.test.mjs && node --test scripts/phase23-build-race.test.mjs && npm run test --workspaces --if-present",
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 scripts/phase24-truth.test.mjs scripts/phase25-bounded-accumulation.test.mjs scripts/phase26-freeze.test.mjs scripts/phase26-index-benchmark.test.mjs && node --test scripts/phase23-build-race.test.mjs && npm run test --workspaces --if-present",
151
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
152
  "coverage:summary": "node scripts/with-build-lock.mjs node scripts/coverage-summary.mjs",
153
153
  "lint": "biome lint . --reporter=sarif --reporter-file=scripts/lint-report.sarif",