nexus-ai-pro 2.3.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/API_STABILITY.md +271 -0
  2. package/CHANGELOG.md +330 -1
  3. package/README.md +18 -4
  4. package/SECURITY.md +209 -0
  5. package/dist/adapter-kit/contracts.d.ts +87 -0
  6. package/dist/adapter-kit/contracts.js +237 -0
  7. package/dist/adapter-kit/errors.d.ts +39 -0
  8. package/dist/adapter-kit/errors.js +52 -0
  9. package/dist/adapter-kit/index.d.ts +144 -0
  10. package/dist/adapter-kit/index.js +178 -0
  11. package/dist/adapter-kit/semver.d.ts +12 -0
  12. package/dist/adapter-kit/semver.js +55 -0
  13. package/dist/agent/middleware/pii.js +2 -33
  14. package/dist/context-hub/hub.d.ts +53 -0
  15. package/dist/context-hub/hub.js +108 -3
  16. package/dist/context-hub/index.d.ts +2 -1
  17. package/dist/context-hub/index.js +2 -1
  18. package/dist/context-hub/signing.d.ts +95 -0
  19. package/dist/context-hub/signing.js +88 -0
  20. package/dist/core/secure-stream.js +4 -3
  21. package/dist/doctor/index.d.ts +4 -2
  22. package/dist/doctor/index.js +29 -0
  23. package/dist/embeddings/adapters.js +2 -1
  24. package/dist/evals/judge.js +2 -5
  25. package/dist/evaluate/cache.d.ts +0 -14
  26. package/dist/evaluate/cache.js +0 -30
  27. package/dist/evaluate/file-cache.d.ts +15 -0
  28. package/dist/evaluate/file-cache.js +30 -0
  29. package/dist/evaluate/index.d.ts +2 -1
  30. package/dist/evaluate/index.js +2 -1
  31. package/dist/evaluate/review.js +2 -2
  32. package/dist/evaluate/run.js +2 -2
  33. package/dist/evaluate/threads.d.ts +187 -0
  34. package/dist/evaluate/threads.js +187 -0
  35. package/dist/evaluate/version.js +3 -3
  36. package/dist/graph/graph.js +2 -2
  37. package/dist/graph/node-cache.js +3 -3
  38. package/dist/hallucination/rag.d.ts +4 -1
  39. package/dist/hallucination/rag.js +6 -1
  40. package/dist/images/asset-stores.js +2 -3
  41. package/dist/images/asset-support.js +2 -2
  42. package/dist/images/assets.js +1 -2
  43. package/dist/images/evals.js +4 -4
  44. package/dist/insights/proposals.js +1 -2
  45. package/dist/insights/regressions.d.ts +26 -2
  46. package/dist/insights/regressions.js +129 -3
  47. package/dist/loaders/git.js +8 -1
  48. package/dist/loaders/web.js +7 -1
  49. package/dist/mcp/client.d.ts +14 -0
  50. package/dist/mcp/client.js +59 -1
  51. package/dist/mcp/registry.d.ts +6 -0
  52. package/dist/mcp/registry.js +7 -0
  53. package/dist/operations/adapters.d.ts +2 -1
  54. package/dist/operations/runner.js +2 -0
  55. package/dist/operations/stats.js +11 -4
  56. package/dist/postgres/index.d.ts +2 -1
  57. package/dist/postgres/index.js +4 -0
  58. package/dist/postgres/operations.js +11 -4
  59. package/dist/postgres/store.d.ts +7 -0
  60. package/dist/postgres/store.js +21 -0
  61. package/dist/postgres/tenancy.d.ts +43 -0
  62. package/dist/postgres/tenancy.js +71 -0
  63. package/dist/prompts/file.js +28 -11
  64. package/dist/protocols/a2a.d.ts +235 -0
  65. package/dist/protocols/a2a.js +386 -0
  66. package/dist/protocols/acp.d.ts +87 -0
  67. package/dist/protocols/acp.js +322 -0
  68. package/dist/protocols/ag-ui.d.ts +120 -0
  69. package/dist/protocols/ag-ui.js +180 -0
  70. package/dist/protocols/shared.d.ts +80 -0
  71. package/dist/protocols/shared.js +49 -0
  72. package/dist/providers/anthropic.js +1 -0
  73. package/dist/providers/cohere.js +2 -2
  74. package/dist/providers/google.js +2 -11
  75. package/dist/providers/openai.js +1 -0
  76. package/dist/rag/file-ingestion.js +15 -9
  77. package/dist/runtime/detect.d.ts +17 -0
  78. package/dist/runtime/detect.js +16 -0
  79. package/dist/runtime/index.d.ts +19 -0
  80. package/dist/runtime/index.js +7 -0
  81. package/dist/security/injection-detector.d.ts +2 -0
  82. package/dist/security/injection-detector.js +7 -1
  83. package/dist/security/input-guard.js +5 -2
  84. package/dist/security/output-guard.js +8 -4
  85. package/dist/security/pii-detector.js +22 -6
  86. package/dist/security/prompt-hardening.js +6 -2
  87. package/dist/security/semantic-injection-classifier.d.ts +2 -1
  88. package/dist/security/semantic-injection-classifier.js +31 -35
  89. package/dist/security/upload-scanner.d.ts +8 -0
  90. package/dist/security/upload-scanner.js +51 -9
  91. package/dist/server/deployments.d.ts +188 -11
  92. package/dist/server/deployments.js +474 -36
  93. package/dist/server/metrics.d.ts +2 -0
  94. package/dist/server/metrics.js +7 -0
  95. package/dist/server/runs.d.ts +18 -1
  96. package/dist/server/runs.js +66 -7
  97. package/dist/server/server.js +9 -1
  98. package/dist/server/state.d.ts +12 -2
  99. package/dist/server/state.js +15 -0
  100. package/dist/sqlite/operations.js +15 -4
  101. package/dist/sqlite/store.d.ts +7 -0
  102. package/dist/sqlite/store.js +28 -0
  103. package/dist/store/memory.d.ts +8 -0
  104. package/dist/store/memory.js +12 -1
  105. package/dist/store/redis.d.ts +13 -0
  106. package/dist/store/redis.js +38 -0
  107. package/dist/testing/record.js +2 -2
  108. package/dist/tracing/instrument.js +1 -0
  109. package/dist/tracing/stores.js +2 -1
  110. package/dist/tracing/tracer.d.ts +4 -0
  111. package/dist/tracing/tracer.js +8 -2
  112. package/dist/types/config.d.ts +20 -0
  113. package/dist/types/operations.d.ts +7 -0
  114. package/dist/types/server.d.ts +67 -2
  115. package/dist/types/store.d.ts +9 -0
  116. package/dist/utils/base64.d.ts +4 -0
  117. package/dist/utils/base64.js +21 -0
  118. package/dist/utils/checksums.d.ts +22 -0
  119. package/dist/utils/checksums.js +39 -0
  120. package/dist/utils/ids.d.ts +8 -0
  121. package/dist/utils/ids.js +26 -2
  122. package/dist/utils/sha256.d.ts +22 -0
  123. package/dist/utils/sha256.js +120 -0
  124. package/dist/version.d.ts +1 -1
  125. package/dist/version.js +1 -1
  126. package/dist-cjs/adapter-kit/contracts.d.ts +87 -0
  127. package/dist-cjs/adapter-kit/contracts.js +243 -0
  128. package/dist-cjs/adapter-kit/errors.d.ts +39 -0
  129. package/dist-cjs/adapter-kit/errors.js +57 -0
  130. package/dist-cjs/adapter-kit/index.d.ts +144 -0
  131. package/dist-cjs/adapter-kit/index.js +194 -0
  132. package/dist-cjs/adapter-kit/semver.d.ts +12 -0
  133. package/dist-cjs/adapter-kit/semver.js +59 -0
  134. package/dist-cjs/agent/middleware/pii.js +4 -35
  135. package/dist-cjs/context-hub/hub.d.ts +53 -0
  136. package/dist-cjs/context-hub/hub.js +110 -4
  137. package/dist-cjs/context-hub/index.d.ts +2 -1
  138. package/dist-cjs/context-hub/index.js +7 -1
  139. package/dist-cjs/context-hub/signing.d.ts +95 -0
  140. package/dist-cjs/context-hub/signing.js +96 -0
  141. package/dist-cjs/core/secure-stream.js +4 -3
  142. package/dist-cjs/doctor/index.d.ts +4 -2
  143. package/dist-cjs/doctor/index.js +29 -0
  144. package/dist-cjs/embeddings/adapters.js +2 -1
  145. package/dist-cjs/evals/judge.js +2 -5
  146. package/dist-cjs/evaluate/cache.d.ts +0 -14
  147. package/dist-cjs/evaluate/cache.js +1 -65
  148. package/dist-cjs/evaluate/file-cache.d.ts +15 -0
  149. package/dist-cjs/evaluate/file-cache.js +67 -0
  150. package/dist-cjs/evaluate/index.d.ts +2 -1
  151. package/dist-cjs/evaluate/index.js +3 -2
  152. package/dist-cjs/evaluate/review.js +2 -2
  153. package/dist-cjs/evaluate/run.js +2 -2
  154. package/dist-cjs/evaluate/threads.d.ts +187 -0
  155. package/dist-cjs/evaluate/threads.js +198 -0
  156. package/dist-cjs/evaluate/version.js +3 -3
  157. package/dist-cjs/graph/graph.js +2 -2
  158. package/dist-cjs/graph/node-cache.js +3 -3
  159. package/dist-cjs/hallucination/rag.d.ts +4 -1
  160. package/dist-cjs/hallucination/rag.js +6 -1
  161. package/dist-cjs/images/asset-stores.js +2 -3
  162. package/dist-cjs/images/asset-support.js +2 -2
  163. package/dist-cjs/images/assets.js +1 -2
  164. package/dist-cjs/images/evals.js +4 -4
  165. package/dist-cjs/insights/proposals.js +1 -2
  166. package/dist-cjs/insights/regressions.d.ts +26 -2
  167. package/dist-cjs/insights/regressions.js +129 -3
  168. package/dist-cjs/loaders/git.js +8 -1
  169. package/dist-cjs/loaders/web.js +7 -1
  170. package/dist-cjs/mcp/client.d.ts +14 -0
  171. package/dist-cjs/mcp/client.js +60 -1
  172. package/dist-cjs/mcp/registry.d.ts +6 -0
  173. package/dist-cjs/mcp/registry.js +7 -0
  174. package/dist-cjs/operations/adapters.d.ts +2 -1
  175. package/dist-cjs/operations/runner.js +2 -0
  176. package/dist-cjs/operations/stats.js +11 -4
  177. package/dist-cjs/postgres/index.d.ts +2 -1
  178. package/dist-cjs/postgres/index.js +8 -1
  179. package/dist-cjs/postgres/operations.js +11 -4
  180. package/dist-cjs/postgres/store.d.ts +7 -0
  181. package/dist-cjs/postgres/store.js +21 -0
  182. package/dist-cjs/postgres/tenancy.d.ts +43 -0
  183. package/dist-cjs/postgres/tenancy.js +110 -0
  184. package/dist-cjs/prompts/file.js +28 -11
  185. package/dist-cjs/protocols/a2a.d.ts +235 -0
  186. package/dist-cjs/protocols/a2a.js +393 -0
  187. package/dist-cjs/protocols/acp.d.ts +87 -0
  188. package/dist-cjs/protocols/acp.js +326 -0
  189. package/dist-cjs/protocols/ag-ui.d.ts +120 -0
  190. package/dist-cjs/protocols/ag-ui.js +186 -0
  191. package/dist-cjs/protocols/shared.d.ts +80 -0
  192. package/dist-cjs/protocols/shared.js +59 -0
  193. package/dist-cjs/providers/anthropic.js +1 -0
  194. package/dist-cjs/providers/cohere.js +2 -2
  195. package/dist-cjs/providers/google.js +3 -12
  196. package/dist-cjs/providers/openai.js +1 -0
  197. package/dist-cjs/rag/file-ingestion.js +14 -8
  198. package/dist-cjs/runtime/detect.d.ts +17 -0
  199. package/dist-cjs/runtime/detect.js +19 -0
  200. package/dist-cjs/runtime/index.d.ts +19 -0
  201. package/dist-cjs/runtime/index.js +31 -0
  202. package/dist-cjs/security/injection-detector.d.ts +2 -0
  203. package/dist-cjs/security/injection-detector.js +7 -1
  204. package/dist-cjs/security/input-guard.js +5 -2
  205. package/dist-cjs/security/output-guard.js +8 -4
  206. package/dist-cjs/security/pii-detector.js +22 -6
  207. package/dist-cjs/security/prompt-hardening.js +6 -2
  208. package/dist-cjs/security/semantic-injection-classifier.d.ts +2 -1
  209. package/dist-cjs/security/semantic-injection-classifier.js +31 -35
  210. package/dist-cjs/security/upload-scanner.d.ts +8 -0
  211. package/dist-cjs/security/upload-scanner.js +53 -9
  212. package/dist-cjs/server/deployments.d.ts +188 -11
  213. package/dist-cjs/server/deployments.js +474 -36
  214. package/dist-cjs/server/metrics.d.ts +2 -0
  215. package/dist-cjs/server/metrics.js +8 -0
  216. package/dist-cjs/server/runs.d.ts +18 -1
  217. package/dist-cjs/server/runs.js +65 -6
  218. package/dist-cjs/server/server.js +9 -1
  219. package/dist-cjs/server/state.d.ts +12 -2
  220. package/dist-cjs/server/state.js +15 -0
  221. package/dist-cjs/sqlite/operations.js +15 -4
  222. package/dist-cjs/sqlite/store.d.ts +7 -0
  223. package/dist-cjs/sqlite/store.js +28 -0
  224. package/dist-cjs/store/memory.d.ts +8 -0
  225. package/dist-cjs/store/memory.js +12 -1
  226. package/dist-cjs/store/redis.d.ts +13 -0
  227. package/dist-cjs/store/redis.js +38 -0
  228. package/dist-cjs/testing/record.js +2 -2
  229. package/dist-cjs/tracing/instrument.js +1 -0
  230. package/dist-cjs/tracing/stores.js +2 -1
  231. package/dist-cjs/tracing/tracer.d.ts +4 -0
  232. package/dist-cjs/tracing/tracer.js +8 -2
  233. package/dist-cjs/types/config.d.ts +20 -0
  234. package/dist-cjs/types/operations.d.ts +7 -0
  235. package/dist-cjs/types/server.d.ts +67 -2
  236. package/dist-cjs/types/store.d.ts +9 -0
  237. package/dist-cjs/utils/base64.d.ts +4 -0
  238. package/dist-cjs/utils/base64.js +25 -0
  239. package/dist-cjs/utils/checksums.d.ts +22 -0
  240. package/dist-cjs/utils/checksums.js +46 -0
  241. package/dist-cjs/utils/ids.d.ts +8 -0
  242. package/dist-cjs/utils/ids.js +28 -2
  243. package/dist-cjs/utils/sha256.d.ts +22 -0
  244. package/dist-cjs/utils/sha256.js +125 -0
  245. package/dist-cjs/version.d.ts +1 -1
  246. package/dist-cjs/version.js +1 -1
  247. package/llms.txt +5 -2
  248. package/package.json +102 -6
package/CHANGELOG.md CHANGED
@@ -4,6 +4,333 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.5.0] - 2026-10-09
8
+
9
+ Graduation finished, and conversations evaluated.
10
+
11
+ ### Added
12
+
13
+ - **Deployment changes that are atomic across replicas.**
14
+ - The long-term `Store` contract and the server's `ServerStateStore` gain an optional
15
+ `putIfVersion()`. It writes a record only when the stored one's `version` is still the version
16
+ the change was decided on, in one atomic step. The memory, Postgres, and SQLite stores, the server's
17
+ memory store, and the Redis store (when its client has `eval`) have it, and `fromStore()` passes
18
+ it on.
19
+ - A deployment change uses it. A replica that loses the race decides again on the winner's record,
20
+ so two changes made at once both land, in order. A change made on an `expectedVersion` is
21
+ refused with `DEPLOYMENT_CONFLICT` instead. Two replicas starting at once seed one record.
22
+ - `Deployments.atomicChanges` and `/scaling`'s `atomicChanges` say whether the state store
23
+ offers this.
24
+ - **Tenant usage in Postgres**, on the new `nexus-ai-pro/postgres/tenancy`: `PostgresTenantUsage`, so
25
+ every replica enforces one tenant limit without Redis. The `tenancy` adapter joins
26
+ `postgresMigrations()` and `nexus db migrate`.
27
+ - **Revision worker pools.** A worker image carries some revisions of an assistant, and claims only
28
+ the runs routed to them, so a new revision ships in a new image beside the old one.
29
+ - A queued run's kind names its revision, as `assistant:<id>@<revision>`, and a worker claims with
30
+ the kinds of the revisions it registered.
31
+ - A run that will wait in the queue is routed over the revisions any fresh worker reports
32
+ (`RevisionRequest.queued`), so the API replicas send traffic to a revision only a new image
33
+ carries. `RevisionChoice.local` says whether the answering replica has it.
34
+ - `/scaling` reports `queuedByRevision`, and `/metrics` `nexus_server_runs_queued_by_revision`, so an
35
+ autoscaler starts the pool a waiting revision needs.
36
+ - `OperationStoreFilter.kinds` filters by exact kinds on every operation store, and
37
+ `OperationStoreStats.queuedByKind` counts queued records per kind. `claimQueued()` checks each
38
+ candidate against the filter itself, for a store that ignores one.
39
+ - **Signed context bundles.**
40
+ - A hub with a `signer` signs every new version with Ed25519, through Web Crypto, so on any
41
+ runtime. The signature covers the bundle's content and its pinned prompts' content, under the
42
+ full SHA-256 that `contextDigest()` computes, not the 12-digit version.
43
+ - A hub with a `keyring` checks signatures with `verify()`. With `requireSignature`, it refuses a
44
+ bundle without a trusted signature, with a `ContextSignatureError`, on `import()` and whenever
45
+ `resolve()` or `renderPrompt()` serves one.
46
+ - An import keeps the signatures that claim its content, and adds the importing hub's. That is how
47
+ keys rotate.
48
+ - `generateContextKey()`, `ed25519Signer()`, and `ed25519Keyring()` make and load keys, as JSON Web
49
+ Keys or Web Crypto keys.
50
+ - **Canaries that stay fair.**
51
+ - A deployment records a `bucketStrategy`. `hash-v2` mixes the thread id's hash with
52
+ MurmurHash3's finalizer, so sequential ids such as `user-1` to `user-500` split as evenly as random
53
+ ones. `fnv1a-v1` stays the default, and every deployment recorded before keeps it, so no thread
54
+ moves through an upgrade. `Deployments.assistant()` takes `bucketStrategy`; `bucketing()` and a
55
+ `bucketing` change switch a recorded deployment. `bucket()` takes the strategy.
56
+ - Every replica counts the runs it finishes, per revision, in a rollup under the deployment version
57
+ they were routed with: counts, latency in buckets 5% apart, and cost. `stats()` with `deployment`
58
+ and `rollups()` sum them in one read, and `GET /deployments/:assistant` reports them.
59
+ - `watchCanaries({ from: 'rollups' })` judges a canary on every run since the split last changed,
60
+ instead of the latest 2,000 run records.
61
+ - **A doctor that knows the opt-in settings.** `nexus doctor` and `diagnose()` warn, in a new
62
+ `settings` check, when a `RedisOperationStore` runs without `index: true`, and when a `Tracer`
63
+ writes to a persistent store without `incremental`. Each warning names the setting that fixes it.
64
+ Both stay opt-in through 2.x. `RedisOperationStore.indexed`, `Tracer.store`, and
65
+ `Tracer.incremental` report the settings.
66
+ - **Graduation.** Deployments and the studio leave experimental status, each with its checklist and
67
+ evidence in `API_STABILITY.md` and a threat review in `SECURITY.md`.
68
+ - **The studio's routes, listed.** `Studio.routes` gives every route's method, path, role, and audit
69
+ action. `StudioOptions.maxBodyBytes` bounds a request body, and
70
+ `MemoryStudioJournalOptions.maxComments` bounds the comments kept in memory.
71
+ - **Whole conversations, evaluated** (`nexus-ai-pro/evaluate/threads`).
72
+ - `simulatedUser()` plays a user with a goal and a persona, driven by a model, and says when the goal
73
+ was met or when it gives up. `scriptedUser()` follows a script, for deterministic tests.
74
+ - `conversationTarget()` is an `evaluate()` target that holds a conversation per example and returns
75
+ the `Thread`: every turn, how long each answer took, the messages the agent added, and how it
76
+ ended. `graphThreadAgent()` drives an agent from `createAgent()`, which keeps its own tool calls
77
+ from turn to turn.
78
+ - Thread evaluators: `goalReached()`, `goalCompletion()` with a judge, `turnCount()`, and
79
+ `turnScores()`, which scores every turn with an evaluator of single answers and names the turn
80
+ where the conversation went wrong.
81
+ - **Context on every trace.** A traced model call records `metadata.context` beside
82
+ `metadata.prompt`, so a trace query or an insight narrows by bundle version.
83
+
84
+ ### Security
85
+
86
+ - **The studio**, from its threat review:
87
+ - A request body is refused with `413` past `maxBodyBytes`, 1 MiB by default, as it arrives and
88
+ before anyone is signed in. Before, a body was read whole into memory first.
89
+ - With accounts, only a bearer `Authorization` header stands in for the page token. Before, any
90
+ `Authorization` header did, and a browser adds Basic credentials to another site's request by
91
+ itself, so a sign-in proxy that uses Basic authentication let another site make changes.
92
+ - A change's body must be `application/json` (`415` otherwise) and a JSON object (`400`
93
+ otherwise).
94
+ - A change refused for want of the page token is recorded in the audit log.
95
+ - Graphs and review queues are looked up among the application's own entries, so a name such as
96
+ `constructor` no longer reaches the prototype.
97
+ - Malformed percent-encoding in a path is a `400`, not a server error.
98
+ - `limit`, `hours`, and `days` have ceilings: 1,000 items, 10,000 audit entries, 90 days of
99
+ issues, and 366 days of costs.
100
+ - The memory journal keeps at most 10,000 comments.
101
+ - The server no longer sends an internal error's message, and logs it instead.
102
+ - Only `http` and `https` pull-request links are rendered.
103
+ - **The agent server** refuses an assistant or revision named after a built-in property, such as
104
+ `constructor`, with `400`. Before, such a run was accepted and failed when it ran.
105
+
106
+ ### Fixed
107
+
108
+ - A worker that lacked the revision a run was routed to ran the assistant's live revision instead,
109
+ and the run still recorded the revision it was routed to. A worker now claims only runs of revisions
110
+ it carries.
111
+
112
+ ### Proof
113
+
114
+ - The studio's threat review is a test: every route is called by every role and refuses the roles
115
+ below its own, recording each refusal; over 2,000 requests with hostile paths, queries, and bodies
116
+ all answer below `500`, and no prototype is polluted. 2,000 sign-in, read, and change cycles leave
117
+ the heap flat, and 200 servers start and close with no handle left open.
118
+ - The doctor's seeded deployment warns about a Redis operation store without its index and a tracer
119
+ writing to a persistent store at the end of each trace, and the same deployment with both settings
120
+ turned on passes.
121
+ - Ten replicas make mixed changes to one deployment at once (canaries, splits, promotions,
122
+ rollbacks, and a bucketing change), held until all ten arrive, on the memory server store and the
123
+ memory, Redis, Postgres, and SQLite stores. Every change lands, each with a version of its own, and
124
+ the history is one linear sequence.
125
+ - Over 48 sets of 500 sequential thread ids, `hash-v2` places a canary's share within 2.3 standard
126
+ deviations of its weight, as random placement does; `fnv1a-v1` strays by up to 9.9. Over 10,000
127
+ sequential ids, `hash-v2` is within half a point at every weight.
128
+ - A canary that fails 0.8% of the time against 0.5% is held by a guard over the latest 2,000 run
129
+ records, and rolled back by a guard over rollups, which counts all 100,000 runs from three replicas
130
+ in one read.
131
+ - A simulated user holds 50 conversations with each of two versions of a support agent built with
132
+ `createAgent()`. The thread evaluators score every conversation, and `compareExperiments()` finds
133
+ the second version better on goals met, the judge's verdict, turns taken, and per-turn scores, with
134
+ no regression; the reverse comparison regresses. A conversation that failed names the turn where it
135
+ went wrong.
136
+ - A signed bundle imports into a hub that requires signatures. It is refused there when it is unsigned,
137
+ or edited after export even with its version recomputed. One edited where it is stored is refused
138
+ when it is served. A re-import through a hub with a new key keeps both signatures, so the bundle
139
+ stays trusted once the old key is retired. Every traced model call rendered from it names its
140
+ context and prompt versions.
141
+ - Two worker images drain one queue, one carrying v1 and the other v1 and v2, with an API replica
142
+ that carries only v1. Under a 50% canary, every run executes on the revision it was routed to, and
143
+ only the image with v2 runs v2. A run for a pool scaled to zero waits, is counted per revision on
144
+ `/scaling` and `/metrics`, and runs when that pool returns.
145
+ - Two replicas changing one deployment in the same instant both land, on the memory, Redis,
146
+ Postgres, and SQLite stores. The race test holds both writers until both arrive, and the same race
147
+ on a store without `putIfVersion()` loses a change. Of two changes made on one expected version,
148
+ exactly one is refused.
149
+ - Ten workers spending one tenant budget lose nothing, and never over-grant a slot, on Postgres as
150
+ on memory and Redis.
151
+
152
+ ## [2.4.0] - 2026-10-08
153
+
154
+ Graduation and interoperability.
155
+
156
+ ### Added
157
+
158
+ - **Canaries judged with confidence, and on quality.**
159
+ - `watchCanaries({ confidence })` judges each metric the way an evaluation comparison judges a
160
+ score: a seeded bootstrap interval that must not reach zero, and a minimum effect —
161
+ `minErrorRateIncrease`, the latency and cost margins, and `minFeedbackDrop`. A real regression
162
+ rolls back; noise and a trivially small change do not. `compareRuns()` takes the same options,
163
+ and each `Regression` carries its `interval`.
164
+ - `Deployments.evaluate()` scores an assistant's finished runs with evaluators from
165
+ `nexus-ai-pro/evaluate`, and records the scores as run feedback. A canary that answers worse is
166
+ then rolled back like one that fails more. Sampling is stable by run id, and a run is scored
167
+ once per source.
168
+ - Runs carry `feedback`. `POST /runs/:id/feedback` and `RunManager.addFeedback()` record a
169
+ person's rating as well.
170
+ - **An adapter kit**, on the new `nexus-ai-pro/adapter-kit`, for building adapters outside this
171
+ repository:
172
+ - `defineProviderAdapter()`, `defineEmbeddingsAdapter()`, `defineVectorStoreAdapter()`,
173
+ `defineRetrieverAdapter()`, and `defineStoreAdapter()`. Each takes a capability declaration
174
+ and the range of releases the adapter supports, read as npm reads ranges.
175
+ - Every instance normalizes its failures into an `AdapterError` with a code and `retryable`, and
176
+ reports each call to an `onCall` telemetry hook.
177
+ - `verify()` runs the adapter's contract suite, skipping only what it declares it cannot do, with
178
+ a benchmark held to the p95 budgets it declares.
179
+ - The vector-store, retriever, and store contracts ship as `runVectorStoreContract()`,
180
+ `runRetrieverContract()`, and `runStoreContract()`, with no test framework needed.
181
+ - **Agent protocols**, each on its own entry point and with no dependency:
182
+ - `nexus-ai-pro/protocols/ag-ui`: `agUiHandler()` serves a graph to any AG-UI frontend as
183
+ Server-Sent Events: each step, text as it streams, tool calls with their results, and the final
184
+ state. A run that needs approval finishes with an interrupt, and a resume entry on the same
185
+ thread continues it. `agUiEvents()` yields the same events for any other transport.
186
+ - `nexus-ai-pro/protocols/a2a`: `a2aHandler()` serves a graph as an A2A 1.0 agent: its card,
187
+ `SendMessage`, `SendStreamingMessage`, `GetTask`, and `CancelTask`. A context is a
188
+ conversation, and a task that needs approval asks for input and continues when a message answers
189
+ it. `a2aClient()` calls any A2A agent, and `a2aTool()` makes one a tool that declares the
190
+ host it reaches, so a permission policy decides it.
191
+ - `nexus-ai-pro/protocols/acp`: `serveAcp()` puts a graph in a code editor over the Agent
192
+ Client Protocol. The editor sees streamed text and thought, each tool call with its kind, file,
193
+ and diff, and the plan from `write_todos`. It asks the person to approve calls, and keeps an
194
+ always-allow or always-reject choice for the session.
195
+ - **A portable kernel**, on the new `nexus-ai-pro/runtime`: messages, responses, the provider and
196
+ tool contracts, streaming, and capabilities, in one small import that reaches nothing Node-only.
197
+ `runtimeInfo()` names the runtime the code is on.
198
+ - **The client, the provider adapters, graphs, agents, and the agent protocols run on Deno, Bun,
199
+ edge runtimes, and browsers.**
200
+ - Ids come from Web Crypto.
201
+ - SHA-256 for node-cache keys and dataset versions is computed in plain JavaScript where the
202
+ runtime has no synchronous native digest. The digests are identical, so existing cache keys
203
+ still match.
204
+ - Base64 and UTF-8 byte counts no longer need `Buffer`.
205
+ - 125 of 157 entry points now run anywhere. The new runtimes guide lists the rest, each with
206
+ exactly what it needs.
207
+ - The OpenAI, Anthropic, Google, and Cohere providers take a `fetch` option: a platform's bound
208
+ fetch, a proxy, or a test stub.
209
+ - **Measured graduation.** These surfaces leave experimental status, each with evidence for every
210
+ requirement of the graduation checklist, published in `API_STABILITY.md`:
211
+ - the SQLite adapters;
212
+ - the Postgres and SQLite vector stores;
213
+ - the loaders and the retrievers;
214
+ - the MCP registry;
215
+ - the context hub and insights;
216
+ - tenant limits and the server's worker queue.
217
+
218
+ Deployments, images, the Qdrant, Redis, Pinecone, Weaviate, and Chroma vector stores, and the
219
+ studio stay experimental, each with the requirement that remains named.
220
+ - `inheritEnv` on a local MCP server, in `StdioClientOptions` and in the registry's server
221
+ configuration: which of this process's environment variables the server inherits.
222
+ - Load and soak budgets for every graduated surface, held in CI by `npm run bench:surfaces`. A soak
223
+ fails on heap growth, on a handle left open, or on a timer never cleared, unref'd ones included.
224
+ - The scheduled live workflow records image conformance on every backend with a credential, and
225
+ runs the vector store contract against real Qdrant, Chroma, Weaviate, and Redis servers.
226
+
227
+ ### Security
228
+
229
+ Testing the security code to depth found these gaps, and each is closed:
230
+
231
+ - **The upload scanner trusted the caller.**
232
+ - A declared `sizeBytes` could carry a larger file past `maxBytes`; the larger of the declared and
233
+ actual sizes is now checked.
234
+ - `run.exe.`, `run.exe ` (Windows drops the trailing dot or space), and a name with a control
235
+ character such as `run.exe\0.txt` passed the extension block; names are now read as the file
236
+ system reads them, and a control character is itself a critical finding.
237
+ - Content given as bytes, which is how upload middleware delivers it, was never scanned; textual
238
+ bytes are now decoded and scanned.
239
+ - A file with no MIME type passed an allowlist; an allowlist now fails closed. Types are compared
240
+ without parameters, so `text/plain; charset=utf-8` is `text/plain`.
241
+ - **File ingestion read what it never scanned.** Text an extractor returns from a PDF or an image is
242
+ now scanned like a text file. A text file given as bytes is ingested rather than skipped.
243
+ - **The input guard put secrets in its findings.** A finding now carries a short preview, such as
244
+ `ghp_… (40 characters)`, never the secret. The SSRF check also catches `[::1]`, all of
245
+ `127.0.0.0/8`, and the Google and Alibaba metadata hosts.
246
+ - **Prompt hardening could be escaped.** User text holding the delimiter closed the block early; each
247
+ occurrence inside the text is now broken apart. The text parts of multi-part messages are wrapped
248
+ too.
249
+ - **The injection detectors read text as typed, not as the model reads it.** Both now fold
250
+ full-width letters and drop zero-width characters first, so `IGNORE previous instructions` and
251
+ `ig​nore previous instructions` are found. `neutralize()` leaves clean text exactly as it was.
252
+ - **The PII detector and the output guard took any long number for a card or a phone.** A
253
+ millisecond timestamp or an order id was masked, and in the output guard a card's last digits
254
+ could survive because the phone pattern claimed the rest first. Card numbers must now pass Luhn,
255
+ and phone numbers need 10 to 15 digits with a separator or a leading `+`. Cards are claimed before
256
+ phones, and a span one kind has claimed is not reported again as another.
257
+ - **Retrieved context reads as data.** `withRagContext()` now tells the model that the context is
258
+ data, not instructions.
259
+
260
+ The graduation reviews, published in `SECURITY.md`, found these, and each is closed:
261
+
262
+ - **The Git loader followed symbolic links.** A repository with a tracked link to a file of the host,
263
+ such as `~/.ssh/id_rsa`, had that file loaded into a knowledge base. A link is now never followed,
264
+ nor a path that leaves the checkout.
265
+ - **A gzipped sitemap could be a decompression bomb.** Inflation now stops at 50 MB, the sitemap
266
+ protocol's maximum, and the loader reports the sitemap.
267
+ - **A local MCP server inherited this process's whole environment**, every API key and database URL
268
+ included. It now starts with its `env` and only what a process needs to run: the path, the home
269
+ and temporary directories, the locale, the Windows system variables, proxies, and certificates.
270
+ Name what else a server needs in `env`, or set `inheritEnv`.
271
+ - **One caller's ratings could decide a canary.** Feedback was counted per rating, so a hundred low
272
+ ratings of one run met the minimum sample alone, and could roll back a canary or keep a bad one
273
+ alive. Each run now counts once per feedback key, as the mean of its ratings.
274
+ - **The file prompt store could lose a label move.** Its compare-and-set was a read and then a write,
275
+ so two moves at once in one process could both succeed, the later erasing the earlier. Label
276
+ writes to one file are now serialized across the process.
277
+ - **Two changes at once to one deployment, on one replica, could lose one**, such as a guard's
278
+ rollback and an operator's promotion. They now apply in order. Changes from two replicas in the
279
+ same instant still can, which keeps deployments experimental.
280
+
281
+ ### Fixed
282
+
283
+ - The agent server's guide called the server experimental, though it has been stable since 2.0.
284
+ - Bundling the client for an edge runtime failed: it reached the file-based evaluation cache, which
285
+ loads `node:fs`. `FileEvaluationCache` now lives in a module of its own, still exported from
286
+ `nexus-ai-pro/evaluate`.
287
+ - `extractCitations()` and `validateCitations()` read a Markdown link `[text](url)` as a citation,
288
+ and `[doc-1, doc-2]` as one id; links are ignored and lists are split.
289
+ - The semantic injection classifier embeds a request in one call instead of one per message, and
290
+ `detectSync()` honours `enabled: false` as `detect()` did.
291
+
292
+ ### Proof
293
+
294
+ - Grounding and verification, file ingestion, the upload scanner, the PII detector, and both
295
+ injection detectors are tested to the package's own coverage:
296
+ - grounding, the PII detector, prompt hardening, and the upload scanner at 100% of lines;
297
+ - file ingestion, which no test loaded before, at 97%.
298
+ - Each security fix above is a test that names the attack it stops or the false positive it rules
299
+ out.
300
+ - A canary seeded with a quality regression is rolled back. The proof runs on a real server: 300
301
+ runs, online evaluation recording the scores, and the guard reading them. A canary whose quality
302
+ differs only within the noise keeps its share. A difference that 20,000 runs make significant, but
303
+ that is under the minimum effect, is not a regression either.
304
+ - A community-style adapter, built only from the kit, passes its contract suite. It is a vector store
305
+ in plain JavaScript inside the clean-install test's consumer project, importing nothing but
306
+ `nexus-ai-pro/adapter-kit` from the installed tarball. A deliberately broken store fails exactly the
307
+ checks it breaks.
308
+ - A Nexus agent and a remote agent complete tasks over A2A in both directions:
309
+ - A client that writes raw JSON-RPC, as the specification describes it, drives the served agent
310
+ through a conversation, an approval, streaming, cancellation, and each error code.
311
+ - The client and tool complete tasks against a stub agent written from the specification alone.
312
+ - One Nexus agent calls another through the tool.
313
+ - A frontend renders a run through AG-UI with a plain `fetch` and Server-Sent Events parser, and
314
+ no Nexus client code, approval and resume included.
315
+ - An editor's end of an ACP connection approves an edit, and sees the plan, the diff, and the
316
+ answer.
317
+ - The kernel's tests pass on Node.js, Deno, Bun, and Vercel's edge runtime, and CI runs all four.
318
+ The tests are written against web-standard APIs only, with no test framework and nothing of Node's,
319
+ and they cover a provider over `fetch`, the client, a cached graph, an agent, and A2A and AG-UI.
320
+ A check walks every entry point's imports, static and dynamic. It fails the build when the client,
321
+ the kernel, a provider adapter, graphs, agents, or a protocol reaches anything Node-only.
322
+ - Every graduated surface passes its contract on every backend it supports. For the context hub,
323
+ that is memory, files, Redis, and Postgres, with two hubs racing for one label and never both
324
+ winning. For the retrievers, it is a vector retriever over memory, SQLite, and pgvector, besides
325
+ the keyword and hybrid retrievers.
326
+ - What the published 2.3.0 package wrote, this release reads, and what it derived, this release
327
+ derives the same: a SQLite file shared by a 2.3 and a 2.4 worker, a pgvector table, loader ids,
328
+ retriever rankings, MCP configurations, context bundle versions and exports, insight proposals
329
+ and error signatures, and canary routing, thread by thread.
330
+ - Each security fix has a test that names the attack, and fails on the code before it. The Git test
331
+ runs on Linux, where links are tracked as links, and the race test holds both writers until both
332
+ arrive, so it does not depend on timing.
333
+
7
334
  ## [2.3.0] - 2026-10-06
8
335
 
9
336
  Agents that act safely, on production retrieval.
@@ -1777,7 +2104,9 @@ shared policy vocabulary is in place for a future release that revisits this.
1777
2104
  - URL fetching rejects unsafe private-network targets and limits response reads.
1778
2105
  - CLI and audit findings no longer disclose detected secret values.
1779
2106
 
1780
- [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.3.0...HEAD
2107
+ [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.5.0...HEAD
2108
+ [2.5.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.4.0...v2.5.0
2109
+ [2.4.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.3.0...v2.4.0
1781
2110
  [2.3.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.2.0...v2.3.0
1782
2111
  [2.2.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.1.0...v2.2.0
1783
2112
  [2.1.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.0.0...v2.1.0
package/README.md CHANGED
@@ -8,7 +8,10 @@ team can share.
8
8
  Import only what you use. The root import is the client and nothing else, and every other capability
9
9
  has its own entry point with a size budget CI enforces; [the packaging guide](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/packaging.md)
10
10
  publishes what each one costs. The package has no required dependencies: a graph-only application
11
- loads 80 KB, and installs nothing but this package.
11
+ loads 81 KB, and installs nothing but this package.
12
+ The client, the provider adapters, graphs, agents, and the agent protocols run on Node.js, Deno,
13
+ Bun, edge runtimes, and browsers; [the runtimes guide](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/runtimes.md)
14
+ lists the entry points that need Node.js, and why.
12
15
 
13
16
  - NPM: https://www.npmjs.com/package/nexus-ai-pro
14
17
  - GitHub: https://github.com/mkhitar-abrahamyan/nexus-ai
@@ -136,6 +139,7 @@ from the doc comments. The guides live in the repository, so these links go to G
136
139
  | [The deep-agent preset](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/deep-agents.md) | Long, multi-step agents: a plan, a sandboxed workspace, helpers, skills, and a context that stays small |
137
140
  | [Long-term memory](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/memory.md) | Namespaced memory with semantic search, in memory or Redis |
138
141
  | [MCP](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/mcp.md) | Borrowing tools from MCP servers, a registry of many, and serving your own |
142
+ | [Agent protocols](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/protocols.md) | Serving an agent to user interfaces over AG-UI, to other agents over A2A, and to code editors over ACP, and calling remote agents as tools |
139
143
  | [Traces](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/tracing.md) | Run trees, queries, feedback, comparison, and alerts |
140
144
  | [Evaluation](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/evaluation.md) | Datasets, evaluators, experiments, comparisons with a verdict, review queues, LLM judges |
141
145
  | [Context hub](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/context-hub.md) | Prompts, instructions, tools, and skills versioned together, promoted through gates, and moved between projects |
@@ -156,6 +160,7 @@ from the doc comments. The guides live in the repository, so these links go to G
156
160
  | [Realtime voice](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/realtime.md) | Browser and server realtime sessions with barge-in, tools, and exports |
157
161
  | [Telephony](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/telephony.md) | Calls, webhooks, phone numbers, and phone agents on realtime sessions |
158
162
  | [Testing](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/testing.md) | Recording and replaying provider traffic, and conformance suites |
163
+ | [Adapter kit](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/adapter-kit.md) | Building a provider, store, or retriever outside this repository, and verifying it against the same contracts |
159
164
  | [Studio](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/studio.md) | A shared UI for traces, threads, approvals, experiments, prompts, deployments, costs, and health, as a separate package |
160
165
  | [Agent server](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/server.md) | Self-hosted HTTP server for assistants: threads, durable runs, resumable streams, cron, a worker queue, and metrics |
161
166
  | [Deployments](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/deployments.md) | Revisions and canaries with automatic rollback, autoscaling on Kubernetes and Helm, and per-tenant limits |
@@ -163,6 +168,7 @@ from the doc comments. The guides live in the repository, so these links go to G
163
168
  | [Workflows](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/workflows.md) | Ready-made chains and domain workflows |
164
169
  | [Command line](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/cli.md) | The `nexus` command |
165
170
  | [Packaging and install weight](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/packaging.md) | Every entry point and what it costs |
171
+ | [Runtimes](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/runtimes.md) | The portable kernel, and running the client, providers, graphs, and agents on Deno, Bun, edge runtimes, and browsers |
166
172
 
167
173
  ## Examples
168
174
 
@@ -256,12 +262,20 @@ Shipped so far:
256
262
  - 2.3 agents that act safely, on production retrieval: capabilities and permission policies, a
257
263
  sandbox contract, a middleware catalog, server authentication with one principal on every run,
258
264
  keyword search in Postgres and Elasticsearch with rerankers, durable ingestion, and a deep-agent
259
- preset.
265
+ preset;
266
+ - 2.4 graduation and interoperability: SQLite, loaders, retrievers, the MCP registry, the context
267
+ hub, insights, and tenant limits out of experimental with published evidence; canaries judged with
268
+ confidence and on quality; an adapter kit; AG-UI, A2A, and ACP; and a portable kernel, with the
269
+ client, providers, graphs, and agents running on Deno, Bun, edge runtimes, and browsers;
270
+ - 2.5 graduation finished, and conversations evaluated: deployments that change atomically across
271
+ replicas, revision worker pools, fair canaries judged on every run, and the studio out of
272
+ experimental after a threat review; Postgres tenant usage; signed context named on every trace;
273
+ simulated users and thread evaluators; and a doctor that knows the opt-in settings.
260
274
 
261
275
  Upgrading from 1.x: `npx nexus migrate src --write` moves your imports, and
262
276
  [MIGRATING.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/MIGRATING.md) covers the rest.
263
- Next, 2.4 takes the experimental surfaces out of experimental with evidence, adds an adapter kit, and
264
- speaks the agent protocols beyond MCP from a portable kernel. The [roadmap](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md) lists the
277
+ Next, 2.6 completes the agent protocols: tool progress on every stream, AG-UI deltas and frontend
278
+ tools, A2A tasks and ACP sessions that survive a restart, and a browser in CI. The [roadmap](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md) lists the
265
279
  releases after it.
266
280
 
267
281
  ## Before Production
package/SECURITY.md CHANGED
@@ -44,3 +44,212 @@ Nexus guardrails reduce common AI application risks, but they are not a complete
44
44
  - Browser WebRTC still requires a backend security boundary. A direct media path reduces latency; it
45
45
  does not remove the need for server-owned credentials, session policy, tool authorization, abuse
46
46
  monitoring, and provider-side safety controls.
47
+
48
+ ## Threat reviews
49
+
50
+ Every surface that leaves experimental status is reviewed for how untrusted input reaches it and
51
+ what it could do with that input. Each review is published here with its findings, which are closed
52
+ before the surface graduates. A finding is closed in one of two ways: by a fix with a test that
53
+ names the attack, or by a documented limitation that the surface's guide states.
54
+
55
+ These reviews were done for 2.4, and for 2.5 where a review says so.
56
+
57
+ ### SQLite adapters
58
+
59
+ Trust boundary: an application's data, an operation's payload, and metadata filters that may come
60
+ from a request.
61
+ - Table names are checked against a strict pattern and quoted. Every value, including each metadata
62
+ filter key and its JSON path, is a bound parameter, so a filter cannot change a statement.
63
+ - A migration records its checksum, and a changed migration is refused rather than reapplied.
64
+ - Every change to an operation is one `UPDATE … WHERE sequence = expected`, so two workers sharing
65
+ a file never both claim it. The ten-worker race tests run on SQLite.
66
+ - Tenant data is scoped by key prefix, and the isolation suite runs on every SQLite store.
67
+
68
+ Findings: none.
69
+
70
+ ### The Postgres and SQLite vector stores
71
+
72
+ Trust boundary: metadata filters, which applications often build from a request.
73
+ - The pgvector store sends a filter as one bound `jsonb` containment parameter, and quotes its table
74
+ and index names.
75
+ - The SQLite store binds each key's JSON path and value, and matches a value's JSON type as well as
76
+ its value, so `"1"` never matches `1`.
77
+
78
+ Findings: none.
79
+
80
+ ### Loaders
81
+
82
+ Trust boundary: everything a loader reads is untrusted. A repository, a website, a sitemap, or an
83
+ uploaded file may be written by an attacker who wants it in a knowledge base, or who wants the
84
+ loader to read something else.
85
+ - Web pages and sitemaps go through the SSRF-safe fetch. Private, loopback, and cloud-metadata
86
+ addresses are refused, every redirect is checked again, and bodies are capped.
87
+ - The Git loader runs `git` without a shell. It disables the `ext::` transport, puts `--` before
88
+ the repository, and refuses a repository that starts with `-`.
89
+ - `loadDirectory()` does not follow symbolic links.
90
+
91
+ Findings, both fixed in 2.4:
92
+ - **The Git loader followed symbolic links.** A tracked link such as `notes.md` pointing to
93
+ `~/.ssh/id_rsa` was read and ingested, so a knowledge base could quote a file of the host. A
94
+ link is now never followed, nor a path that resolves outside the checkout. Proof: a repository
95
+ holding a tracked link to a file outside it loads without that file, cloned and in place. On
96
+ Linux, the test fails on the earlier loader.
97
+ - **A gzipped sitemap could be a decompression bomb.** A few megabytes of gzip inflated without a
98
+ limit. Inflation now stops at 50 MB, the sitemap protocol's own maximum. Proof: 60 MB of gzip
99
+ that inflates past it is refused.
100
+
101
+ ### Retrievers
102
+
103
+ Trust boundary: queries come from users, and documents may hold text written to steer a model.
104
+ - Tokenizing is one linear regular expression, with no backtracking on hostile input.
105
+ - Filters compare values; they never become code or a query language.
106
+ - A model reranker or query-variant generator reads documents and queries as data in its prompt. Its
107
+ output is parsed as JSON and used only to order or widen a search, never to call a tool.
108
+
109
+ Findings: none.
110
+
111
+ ### MCP registry
112
+
113
+ Trust boundary: the configuration file is trusted, since it names programs to run. What a server
114
+ says about itself is not: its tool names, its descriptions, and its results.
115
+ - A server's tools are prefixed with its name by default, so one server cannot shadow another's
116
+ tool.
117
+ - Allow and deny lists limit what agents see, and deny wins.
118
+ - A tool from a server declares no capabilities. A permission policy denies such a tool unless its
119
+ `undeclared` rule says otherwise, and `humanApproval()` asks about it by default.
120
+ - `${NAME}` placeholders keep credentials out of the file. A missing variable is named in the error,
121
+ never its value.
122
+
123
+ Finding, fixed in 2.4: **a local server inherited this process's whole environment.** Any
124
+ `npx`-started server, which is usually third-party code, saw every API key and database URL the
125
+ application held. A server now starts with `env` and only what a process needs to run: the path,
126
+ the home and temporary directories, the locale, proxies, and certificates. The new `inheritEnv`
127
+ option widens this by name, or entirely. Proof: a real child process started by the transport sees
128
+ the variables it was given, and not a secret set in the parent.
129
+
130
+ ### Context hub
131
+
132
+ Trust boundary: an imported bundle may come from another team or another organization. Who may
133
+ commit and promote is the application's access control, which the studio's roles enforce.
134
+ - An import recomputes each bundle's and each prompt's content version, and refuses one whose content
135
+ changed after export.
136
+ - A bundle names tools, but `bindTools()` binds only implementations the application supplies, so
137
+ a bundle cannot add code.
138
+ - Promotion runs the label's gates, an experiment over the exact version among them.
139
+ - Labels move by compare-and-set, so a promotion decided on a stale version is refused.
140
+
141
+ Finding, fixed in 2.4: **the file store's compare-and-set could lose a move.** Two label moves at
142
+ once in one process could both pass the check, and the later write erased the earlier one. Label
143
+ writes to one file are now serialized across the process. Proof: two hubs whose writes are held
144
+ until both arrive race for one label on every backend, and on the file store the earlier code
145
+ loses a move every time. Several processes promoting the same label at once still need the Redis or
146
+ Postgres store, as the prompts guide says.
147
+
148
+ ### Insights
149
+
150
+ Trust boundary: runs and their errors carry user data, and a model proposes fixes.
151
+ - An error signature replaces numbers, quoted values, URLs, ids, and long hexadecimal strings, so a
152
+ cluster's summary does not carry a customer's data.
153
+ - A model's proposed fix is parsed as data, evaluated on a dataset, and never applied without a
154
+ person. A promotion still runs the label's gates.
155
+ - A proposal id is checked against a strict pattern before it becomes a file name.
156
+
157
+ Findings: none.
158
+
159
+ ### Tenant limits and the worker queue
160
+
161
+ Trust boundary: a tenant is whoever the application's `authenticate()` says, never a header the
162
+ server trusts by itself.
163
+ - Concurrency, rate, and budget limits are kept in a shared store, so several replicas enforce one
164
+ limit. Admission and release are tested by ten racing workers.
165
+ - A run is released exactly once, however it ends.
166
+ - The queue claims an operation under a lease in one atomic step on every store. A lost lease
167
+ cancels the run's signal, and a crash at any durable boundary resumes to the same state.
168
+
169
+ Findings: none.
170
+
171
+ ### Deployments
172
+
173
+ Reviewed for 2.4, and again for 2.5, when it graduated.
174
+
175
+ Trust boundary: a deployment is changed by an admin, a canary guard, or the studio's admins; a run
176
+ names its assistant, and optionally a revision, in a request from any caller with the write scope.
177
+ Replicas, guards, and the studio share the server's state store, which they trust.
178
+ - Every change is decided on the version it read and written by compare-and-set, on every included
179
+ state store, so a stale change never overwrites a newer one.
180
+ - A guard acts on the version it judged, so guards on every replica never undo each other.
181
+ - A replica writes rollups only to its own record. Counts are as trustworthy as the state store, as
182
+ run records already are.
183
+
184
+ Findings:
185
+ - **Fixed in 2.4: one caller's ratings could decide a canary.** A canary was judged on feedback
186
+ entries, not on runs. A hundred low ratings of one run met the minimum sample alone, and could
187
+ roll back a canary for everyone, or keep a bad one alive. Each run now counts once per feedback
188
+ key, as the mean of its ratings.
189
+ - **Fixed in 2.4: two changes at once on one replica could lose one**, such as a guard's rollback
190
+ and an operator's promotion. Changes to one deployment now apply in order.
191
+ - **Fixed in 2.5: two replicas changing one deployment in the same instant could lose one change.**
192
+ The state store gained `putIfVersion()`, and a replica that loses the race decides again on the
193
+ winner's record. Proof: ten replicas held until all arrive make mixed changes on every store, and
194
+ every change lands in one linear history.
195
+ - **Fixed in 2.5: a name from a request could reach an object's prototype.** A run for the assistant
196
+ `constructor`, or naming the revision `constructor`, was accepted and failed when it ran. Only an
197
+ application's own assistants and revisions are looked up now, and both are refused with `400`.
198
+ - **Documented limitation: `fnv1a-v1` spreads sequential ids unevenly.** It stays the default, since
199
+ its positions are part of the stability promise. `hash-v2` spreads them evenly, opt-in per
200
+ deployment.
201
+ - **Documented limitation: a replica that dies loses the runs it counted in its last `rollupMs`.**
202
+ Rollups are statistics; the run records stay whole.
203
+
204
+ ### The studio and its accounts
205
+
206
+ Reviewed for 2.5, when it graduated.
207
+
208
+ Trust boundary: anyone who can reach the port, before signing in; a signed-in person, limited by
209
+ their role; a page on another site acting through a signed-in person's browser; and the data the
210
+ studio shows, which traces, models, and people wrote.
211
+ - The `Host` header must be a loopback name or one in `allowedHosts`, which closes DNS rebinding.
212
+ - Tokens are compared in constant time. A token in the URL is moved into an HTTP-only, `SameSite=Strict`
213
+ cookie, `Secure` over HTTPS, and taken out of the address bar. `headerAuth()` trusts identity
214
+ headers only with the proxy's secret or from its addresses.
215
+ - Every change needs a page token in a header, an HMAC of the user and their role under the server's
216
+ secret, which another site can neither read nor send. Every refusal is audited.
217
+ - Every response carries a content security policy without inline script, `nosniff`, `no-store`,
218
+ and no referrer. The page inserts every value as text.
219
+
220
+ Findings, each fixed in 2.5 with a test in `studio/test/security.test.ts`:
221
+ - **A request body was read whole before anyone signed in.** Anyone who could reach the port could
222
+ fill the server's memory. A body past `maxBodyBytes`, 1 MiB by default, is now refused with `413`
223
+ as it arrives.
224
+ - **Behind a sign-in proxy that uses Basic authentication, another site could make a change.** Any
225
+ `Authorization` header counted as proof that a script, not a browser, sent the change. But a
226
+ browser attaches Basic credentials to another site's request by itself. Only a bearer token counts
227
+ now, and a change's body must be `application/json`, which a form on another site cannot send.
228
+ - **A change refused for want of the page token was not audited.** It is now, under the person whose
229
+ browser sent it, so a forged change leaves a trace.
230
+ - **A name from the URL could reach an object's prototype.** A graph or review queue named
231
+ `constructor` failed with a server error. Only an application's own entries are looked up.
232
+ - **A body that was not a JSON object reached handlers that read its fields.** `null`, an array, or a
233
+ number failed with a server error. Such a body is now refused with `400`.
234
+ - **A path with malformed percent-encoding failed with a server error.** It is now a `400`.
235
+ - **A viewer could ask a store for everything it holds.** `limit`, `hours`, and `days` had no
236
+ ceiling. A list now returns at most 1,000 items, the audit log 10,000 entries, the issues view 90
237
+ days, and the costs view 366 days; past that, the request is refused with `400`.
238
+ - **The memory journal kept every comment.** It now keeps the latest 10,000, as it keeps audit entries.
239
+ - **The server answered an internal error with its message.** That reply could reach anyone who can
240
+ reach the port. It now says only that the request failed, and logs the error on the server.
241
+ - **A proposal's pull-request link was rendered as stored.** The content security policy already
242
+ blocked a `javascript:` address. Only `http` and `https` links are rendered now.
243
+
244
+ Documented limitations, which the studio guide states:
245
+ - The studio has no tenants of its own. Every account sees every source the studio is given, so a
246
+ tenant's people get a studio over that tenant's `tenantScope()` views.
247
+ - A page token stays valid while the secret, the user, and their role do. Rotate `secret` to revoke
248
+ every page at once.
249
+ - Someone without a session who opens another person's personal link is signed in as that person.
250
+ The page shows who is signed in.
251
+ - `FileStudioJournal` reads its whole file for each listing, so rotate a long-lived one.
252
+ - The playground spends model budget, which is why it needs the editor role.
253
+
254
+ The fuzzing behind these: every route called by every role, and over 2,000 requests with hostile
255
+ paths, queries, and bodies. Each must answer below `500`, and no prototype may be polluted.